about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.elpaignore2
-rw-r--r--.gitignore15
-rw-r--r--.gitmodules3
-rw-r--r--.mailmap10
-rw-r--r--AUTHORS13
-rw-r--r--COPYING339
-rw-r--r--LICENSE674
-rw-r--r--Makefile170
-rw-r--r--README99
-rw-r--r--README.md23
-rw-r--r--cache.c468
-rw-r--r--cache.h37
-rw-r--r--cgit.c1112
-rw-r--r--cgit.css895
-rw-r--r--cgit.h398
-rw-r--r--cgit.mk141
-rw-r--r--cgit.pngbin1366 -> 0 bytes
-rw-r--r--cgitrc.5.txt1008
-rw-r--r--cmd.c208
-rw-r--r--cmd.h16
-rw-r--r--configfile.c90
-rw-r--r--configfile.h10
-rwxr-xr-xcontrib/hooks/post-receive.agefile19
-rw-r--r--exwm-background.el199
-rw-r--r--exwm-config.el131
-rw-r--r--exwm-core.el411
-rw-r--r--exwm-floating.el780
-rw-r--r--exwm-input.el1248
-rw-r--r--exwm-layout.el631
-rw-r--r--exwm-manage.el833
-rw-r--r--exwm-randr.el369
-rw-r--r--exwm-systemtray.el701
-rw-r--r--exwm-workspace.el1768
-rw-r--r--exwm-xim.el810
-rw-r--r--exwm-xsettings.el336
-rw-r--r--exwm.el1113
-rw-r--r--favicon.icobin1078 -> 0 bytes
-rw-r--r--filter.c457
-rwxr-xr-xfilters/about-formatting.sh27
-rwxr-xr-xfilters/commit-links.sh28
-rw-r--r--filters/email-gravatar.lua35
-rwxr-xr-xfilters/email-gravatar.py39
-rw-r--r--filters/email-libravatar.lua36
-rw-r--r--filters/file-authentication.lua359
-rw-r--r--filters/gentoo-ldap-authentication.lua360
-rwxr-xr-xfilters/html-converters/man2html4
-rwxr-xr-xfilters/html-converters/md2html307
-rwxr-xr-xfilters/html-converters/rst2html2
-rwxr-xr-xfilters/html-converters/txt2html4
-rw-r--r--filters/owner-example.lua17
-rw-r--r--filters/simple-authentication.lua314
-rwxr-xr-xfilters/syntax-highlighting.py55
-rwxr-xr-xfilters/syntax-highlighting.sh121
-rwxr-xr-xgen-version.sh20
m---------git0
-rw-r--r--html.c344
-rw-r--r--html.h37
-rw-r--r--parsing.c224
-rw-r--r--robots.txt3
-rw-r--r--scan-tree.c270
-rw-r--r--scan-tree.h2
-rw-r--r--shared.c579
-rw-r--r--tests/.gitignore2
-rw-r--r--tests/Makefile17
-rw-r--r--tests/filters/dump.lua17
-rwxr-xr-xtests/filters/dump.sh4
-rwxr-xr-xtests/setup.sh176
-rwxr-xr-xtests/t0001-validate-git-versions.sh41
-rwxr-xr-xtests/t0010-validate-html.sh40
-rwxr-xr-xtests/t0020-validate-cache.sh78
-rwxr-xr-xtests/t0101-index.sh17
-rwxr-xr-xtests/t0102-summary.sh25
-rwxr-xr-xtests/t0103-log.sh24
-rwxr-xr-xtests/t0104-tree.sh32
-rwxr-xr-xtests/t0105-commit.sh36
-rwxr-xr-xtests/t0106-diff.sh19
-rwxr-xr-xtests/t0107-snapshot.sh82
-rwxr-xr-xtests/t0108-patch.sh62
-rwxr-xr-xtests/t0109-gitconfig.sh42
-rwxr-xr-xtests/t0110-rawdiff.sh42
-rwxr-xr-xtests/t0111-filter.sh46
-rwxr-xr-xtests/valgrind/bin/cgit12
-rw-r--r--ui-atom.c149
-rw-r--r--ui-atom.h6
-rw-r--r--ui-blame.c302
-rw-r--r--ui-blame.h6
-rw-r--r--ui-blob.c181
-rw-r--r--ui-blob.h8
-rw-r--r--ui-clone.c126
-rw-r--r--ui-clone.h8
-rw-r--r--ui-commit.c147
-rw-r--r--ui-commit.h6
-rw-r--r--ui-diff.c501
-rw-r--r--ui-diff.h15
-rw-r--r--ui-log.c550
-rw-r--r--ui-log.h9
-rw-r--r--ui-patch.c98
-rw-r--r--ui-patch.h7
-rw-r--r--ui-plain.c207
-rw-r--r--ui-plain.h6
-rw-r--r--ui-refs.c219
-rw-r--r--ui-refs.h8
-rw-r--r--ui-repolist.c379
-rw-r--r--ui-repolist.h7
-rw-r--r--ui-shared.c1210
-rw-r--r--ui-shared.h87
-rw-r--r--ui-snapshot.c302
-rw-r--r--ui-snapshot.h7
-rw-r--r--ui-ssdiff.c420
-rw-r--r--ui-ssdiff.h25
-rw-r--r--ui-stats.c426
-rw-r--r--ui-stats.h28
-rw-r--r--ui-summary.c148
-rw-r--r--ui-summary.h7
-rw-r--r--ui-tag.c120
-rw-r--r--ui-tag.h6
-rw-r--r--ui-tree.c388
-rw-r--r--ui-tree.h6
-rw-r--r--xinitrc20
119 files changed, 10052 insertions, 15631 deletions
diff --git a/.elpaignore b/.elpaignore
new file mode 100644
index 0000000000..f0f644ee08
--- /dev/null
+++ b/.elpaignore
@@ -0,0 +1,2 @@
+LICENSE
+README.md
diff --git a/.gitignore b/.gitignore
index 661df346c2..9e4b0ee5b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,3 @@
-# Files I don't care to see in git-status/commit
-/cgit
-cgit.conf
-CGIT-CFLAGS
-VERSION
-cgitrc.5
-cgitrc.5.fo
-cgitrc.5.html
-cgitrc.5.pdf
-cgitrc.5.xml
-*.o
-*.d
+*.elc
+*-pkg.el
+*-autoloads.el
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 5c6ecb4f89..0000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "git"]
-	url = https://git.kernel.org/pub/scm/git/git.git
-	path = git
diff --git a/.mailmap b/.mailmap
deleted file mode 100644
index 03b54796cf..0000000000
--- a/.mailmap
+++ /dev/null
@@ -1,10 +0,0 @@
-Florian Pritz <bluewind@xinu.at> <bluewind@xssn.at>
-Harley Laue <losinggeneration@gmail.com> <losinggeneration@aim.com>
-John Keeping <john@keeping.me.uk> <john@metanate.com>
-Lars Hjemli <hjemli@gmail.com> <larsh@hal-2004.(none)>
-Lars Hjemli <hjemli@gmail.com> <larsh@hatman.(none)>
-Lars Hjemli <hjemli@gmail.com> <larsh@slackbox.hjemli.net>
-Lars Hjemli <hjemli@gmail.com> <larsh@slaptop.hjemli.net>
-Lukas Fleischer <lfleischer@lfos.de> <cgit@cryptocrack.de>
-Lukas Fleischer <lfleischer@lfos.de> <info@cryptocrack.de>
-Stefan Bühler <source@stbuehler.de> <lighttpd@stbuehler.de>
diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644
index 031de338f9..0000000000
--- a/AUTHORS
+++ /dev/null
@@ -1,13 +0,0 @@
-Maintainer:
-	Jason A. Donenfeld <Jason@zx2c4.com>
-
-Contributors:
-	Jason A. Donenfeld <Jason@zx2c4.com>
-	Lukas Fleischer <cgit@cryptocrack.de>
-	Johan Herland <johan@herland.net>
-	Lars Hjemli <hjemli@gmail.com>
-	Ferry Huberts <ferry.huberts@pelagic.nl>
-	John Keeping <john@keeping.me.uk>
-
-Previous Maintainer:
-	Lars Hjemli <hjemli@gmail.com>
diff --git a/COPYING b/COPYING
deleted file mode 100644
index d159169d10..0000000000
--- a/COPYING
+++ /dev/null
@@ -1,339 +0,0 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users.  This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it.  (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.)  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
-  To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have.  You must make sure that they, too, receive or can get the
-source code.  And you must show them these terms so they know their
-rights.
-
-  We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
-  Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software.  If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
-  Finally, any free program is threatened constantly by software
-patents.  We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary.  To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                    GNU GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License.  The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language.  (Hereinafter, translation is included without limitation in
-the term "modification".)  Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
-  1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
-  2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) You must cause the modified files to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    b) You must cause any work that you distribute or publish, that in
-    whole or in part contains or is derived from the Program or any
-    part thereof, to be licensed as a whole at no charge to all third
-    parties under the terms of this License.
-
-    c) If the modified program normally reads commands interactively
-    when run, you must cause it, when started running for such
-    interactive use in the most ordinary way, to print or display an
-    announcement including an appropriate copyright notice and a
-    notice that there is no warranty (or else, saying that you provide
-    a warranty) and that users may redistribute the program under
-    these conditions, and telling the user how to view a copy of this
-    License.  (Exception: if the Program itself is interactive but
-    does not normally print such an announcement, your work based on
-    the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
-    a) Accompany it with the complete corresponding machine-readable
-    source code, which must be distributed under the terms of Sections
-    1 and 2 above on a medium customarily used for software interchange; or,
-
-    b) Accompany it with a written offer, valid for at least three
-    years, to give any third party, for a charge no more than your
-    cost of physically performing source distribution, a complete
-    machine-readable copy of the corresponding source code, to be
-    distributed under the terms of Sections 1 and 2 above on a medium
-    customarily used for software interchange; or,
-
-    c) Accompany it with the information you received as to the offer
-    to distribute corresponding source code.  (This alternative is
-    allowed only for noncommercial distribution and only if you
-    received the program in object code or executable form with such
-    an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it.  For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable.  However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License.  Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
-  5. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Program or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
-  6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
-  7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded.  In such case, this License incorporates
-the limitation as if written in the body of this License.
-
-  9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation.  If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
-  10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission.  For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this.  Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
-                            NO WARRANTY
-
-  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
-  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License along
-    with this program; if not, write to the Free Software Foundation, Inc.,
-    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
-    Gnomovision version 69, Copyright (C) year name of author
-    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
-  `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
-  <signature of Ty Coon>, 1 April 1989
-  Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs.  If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library.  If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000..9cecc1d466
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    {one line to give the program's name and a brief idea of what it does.}
+    Copyright (C) {year}  {name of author}
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    {project}  Copyright (C) {year}  {fullname}
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 96ad7cd878..0000000000
--- a/Makefile
+++ /dev/null
@@ -1,170 +0,0 @@
-all::
-
-CGIT_VERSION = v1.2.1
-CGIT_SCRIPT_NAME = cgit.cgi
-CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
-CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
-CGIT_CONFIG = /etc/cgitrc
-CACHE_ROOT = /var/cache/cgit
-prefix = /usr/local
-libdir = $(prefix)/lib
-filterdir = $(libdir)/cgit/filters
-docdir = $(prefix)/share/doc/cgit
-htmldir = $(docdir)
-pdfdir = $(docdir)
-mandir = $(prefix)/share/man
-SHA1_HEADER = <openssl/sha.h>
-GIT_VER = 2.23.0
-GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.xz
-INSTALL = install
-COPYTREE = cp -r
-MAN5_TXT = $(wildcard *.5.txt)
-MAN_TXT  = $(MAN5_TXT)
-DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT))
-DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
-DOC_PDF  = $(patsubst %.txt,%.pdf,$(MAN_TXT))
-
-ASCIIDOC = asciidoc
-ASCIIDOC_EXTRA =
-ASCIIDOC_HTML = xhtml11
-ASCIIDOC_COMMON = $(ASCIIDOC) $(ASCIIDOC_EXTRA)
-TXT_TO_HTML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_HTML)
-
-# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
-# do not support the 'size specifiers' introduced by C99, namely ll, hh,
-# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
-# some C compilers supported these specifiers prior to C99 as an extension.
-#
-# Define HAVE_LINUX_SENDFILE to use sendfile()
-
-#-include config.mak
-
--include git/config.mak.uname
-#
-# Let the user override the above settings.
-#
--include cgit.conf
-
-export CGIT_VERSION CGIT_SCRIPT_NAME CGIT_SCRIPT_PATH CGIT_DATA_PATH CGIT_CONFIG CACHE_ROOT
-
-#
-# Define a way to invoke make in subdirs quietly, shamelessly ripped
-# from git.git
-#
-QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
-QUIET_SUBDIR1  =
-
-ifneq ($(findstring w,$(MAKEFLAGS)),w)
-PRINT_DIR = --no-print-directory
-else # "make -w"
-NO_SUBDIR = :
-endif
-
-ifndef V
-	QUIET_SUBDIR0  = +@subdir=
-	QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
-			 $(MAKE) $(PRINT_DIR) -C $$subdir
-	QUIET_TAGS     = @echo '   ' TAGS $@;
-	export V
-endif
-
-.SUFFIXES:
-
-all:: cgit
-
-cgit:
-	$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk ../cgit $(EXTRA_GIT_TARGETS) NO_CURL=1
-
-sparse:
-	$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk NO_CURL=1 cgit-sparse
-
-test:
-	@$(MAKE) --no-print-directory cgit EXTRA_GIT_TARGETS=all
-	$(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
-
-install: all
-	$(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
-	$(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
-	$(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
-	$(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
-	$(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
-	$(INSTALL) -m 0644 favicon.ico $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico
-	$(INSTALL) -m 0644 robots.txt $(DESTDIR)$(CGIT_DATA_PATH)/robots.txt
-	$(INSTALL) -m 0755 -d $(DESTDIR)$(filterdir)
-	$(COPYTREE) filters/* $(DESTDIR)$(filterdir)
-
-install-doc: install-man install-html install-pdf
-
-install-man: doc-man
-	$(INSTALL) -m 0755 -d $(DESTDIR)$(mandir)/man5
-	$(INSTALL) -m 0644 $(DOC_MAN5) $(DESTDIR)$(mandir)/man5
-
-install-html: doc-html
-	$(INSTALL) -m 0755 -d $(DESTDIR)$(htmldir)
-	$(INSTALL) -m 0644 $(DOC_HTML) $(DESTDIR)$(htmldir)
-
-install-pdf: doc-pdf
-	$(INSTALL) -m 0755 -d $(DESTDIR)$(pdfdir)
-	$(INSTALL) -m 0644 $(DOC_PDF) $(DESTDIR)$(pdfdir)
-
-uninstall:
-	rm -f $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
-	rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
-	rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
-	rm -f $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico
-
-uninstall-doc: uninstall-man uninstall-html uninstall-pdf
-
-uninstall-man:
-	@for i in $(DOC_MAN5); do \
-	    rm -fv $(DESTDIR)$(mandir)/man5/$$i; \
-	done
-
-uninstall-html:
-	@for i in $(DOC_HTML); do \
-	    rm -fv $(DESTDIR)$(htmldir)/$$i; \
-	done
-
-uninstall-pdf:
-	@for i in $(DOC_PDF); do \
-	    rm -fv $(DESTDIR)$(pdfdir)/$$i; \
-	done
-
-doc: doc-man doc-html doc-pdf
-doc-man: doc-man5
-doc-man5: $(DOC_MAN5)
-doc-html: $(DOC_HTML)
-doc-pdf: $(DOC_PDF)
-
-%.5 : %.5.txt
-	a2x -f manpage $<
-
-$(DOC_HTML): %.html : %.txt
-	$(TXT_TO_HTML) -o $@+ $< && \
-	mv $@+ $@
-
-$(DOC_PDF): %.pdf : %.txt
-	a2x -f pdf cgitrc.5.txt
-
-clean: clean-doc
-	$(RM) cgit VERSION CGIT-CFLAGS *.o tags
-	$(RM) -r .deps
-
-cleanall: clean
-	$(MAKE) -C git clean
-
-clean-doc:
-	$(RM) cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
-
-get-git:
-	curl -L $(GIT_URL) | tar -xJf - && rm -rf git && mv git-$(GIT_VER) git
-
-tags:
-	$(QUIET_TAGS)find . -name '*.[ch]' | xargs ctags
-
-.PHONY: all cgit git get-git
-.PHONY: clean clean-doc cleanall
-.PHONY: doc doc-html doc-man doc-pdf
-.PHONY: install install-doc install-html install-man install-pdf
-.PHONY: tags test
-.PHONY: uninstall uninstall-doc uninstall-html uninstall-man uninstall-pdf
diff --git a/README b/README
deleted file mode 100644
index 7a6b4a40ca..0000000000
--- a/README
+++ /dev/null
@@ -1,99 +0,0 @@
-cgit - CGI for Git
-==================
-
-This is an attempt to create a fast web interface for the Git SCM, using a
-built-in cache to decrease server I/O pressure.
-
-Installation
-------------
-
-Building cgit involves building a proper version of Git. How to do this
-depends on how you obtained the cgit sources:
-
-a) If you're working in a cloned cgit repository, you first need to
-initialize and update the Git submodule:
-
-    $ git submodule init     # register the Git submodule in .git/config
-    $ $EDITOR .git/config    # if you want to specify a different url for git
-    $ git submodule update   # clone/fetch and checkout correct git version
-
-b) If you're building from a cgit tarball, you can download a proper git
-version like this:
-
-    $ make get-git
-
-When either a) or b) has been performed, you can build and install cgit like
-this:
-
-    $ make
-    $ sudo make install
-
-This will install `cgit.cgi` and `cgit.css` into `/var/www/htdocs/cgit`. You
-can configure this location (and a few other things) by providing a `cgit.conf`
-file (see the Makefile for details).
-
-If you'd like to compile without Lua support, you may use:
-
-    $ make NO_LUA=1
-
-And if you'd like to specify a Lua implementation, you may use:
-
-    $ make LUA_PKGCONFIG=lua5.1
-
-If this is not specified, the Lua implementation will be auto-detected,
-preferring LuaJIT if many are present. Acceptable values are generally "lua",
-"luajit", "lua5.1", and "lua5.2".
-
-
-Dependencies
-------------
-
-* libzip
-* libcrypto (OpenSSL)
-* libssl (OpenSSL)
-* optional: luajit or lua, most reliably used when pkg-config is available
-
-Apache configuration
---------------------
-
-A new `Directory` section must probably be added for cgit, possibly something
-like this:
-
-    <Directory "/var/www/htdocs/cgit/">
-        AllowOverride None
-        Options +ExecCGI
-        Order allow,deny
-        Allow from all
-    </Directory>
-
-
-Runtime configuration
----------------------
-
-The file `/etc/cgitrc` is read by cgit before handling a request. In addition
-to runtime parameters, this file may also contain a list of repositories
-displayed by cgit (see `cgitrc.5.txt` for further details).
-
-The cache
----------
-
-When cgit is invoked it looks for a cache file matching the request and
-returns it to the client. If no such cache file exists (or if it has expired),
-the content for the request is written into the proper cache file before the
-file is returned.
-
-If the cache file has expired but cgit is unable to obtain a lock for it, the
-stale cache file is returned to the client. This is done to favour page
-throughput over page freshness.
-
-The generated content contains the complete response to the client, including
-the HTTP headers `Modified` and `Expires`.
-
-Online presence
----------------
-
-* The cgit homepage is hosted by cgit at <https://git.zx2c4.com/cgit/about/>
-
-* Patches, bug reports, discussions and support should go to the cgit
-  mailing list: <cgit@lists.zx2c4.com>. To sign up, visit
-  <https://lists.zx2c4.com/mailman/listinfo/cgit>
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..58585cdce9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+# Emacs X Window Manager
+
+EXWM (Emacs X Window Manager) is a full-featured tiling X window manager
+for Emacs built on top of [XELB](https://github.com/emacs-exwm/xelb).
+
+It features:
++ Fully keyboard-driven operations
++ Hybrid layout modes (tiling & stacking)
++ Dynamic workspace support
++ ICCCM/EWMH compliance
+
+Optional features:
++ RandR (multi-monitor) support
++ System tray
++ Input method
++ Background setting support
++ XSETTINGS server
+
+Please check out the
+[screenshots](https://github.com/emacs-exwm/exwm/wiki/Screenshots)
+to get an overview of what EXWM is capable of, and the
+[user guide](https://github.com/emacs-exwm/exwm/wiki)
+for installation instructions and a detailed explanation of its usage.
diff --git a/cache.c b/cache.c
deleted file mode 100644
index 2c70be784d..0000000000
--- a/cache.c
+++ /dev/null
@@ -1,468 +0,0 @@
-/* cache.c: cache management
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- *
- *
- * The cache is just a directory structure where each file is a cache slot,
- * and each filename is based on the hash of some key (e.g. the cgit url).
- * Each file contains the full key followed by the cached content for that
- * key.
- *
- */
-
-#include "cgit.h"
-#include "cache.h"
-#include "html.h"
-#ifdef HAVE_LINUX_SENDFILE
-#include <sys/sendfile.h>
-#endif
-
-#define CACHE_BUFSIZE (1024 * 4)
-
-struct cache_slot {
-	const char *key;
-	size_t keylen;
-	int ttl;
-	cache_fill_fn fn;
-	int cache_fd;
-	int lock_fd;
-	int stdout_fd;
-	const char *cache_name;
-	const char *lock_name;
-	int match;
-	struct stat cache_st;
-	int bufsize;
-	char buf[CACHE_BUFSIZE];
-};
-
-/* Open an existing cache slot and fill the cache buffer with
- * (part of) the content of the cache file. Return 0 on success
- * and errno otherwise.
- */
-static int open_slot(struct cache_slot *slot)
-{
-	char *bufz;
-	ssize_t bufkeylen = -1;
-
-	slot->cache_fd = open(slot->cache_name, O_RDONLY);
-	if (slot->cache_fd == -1)
-		return errno;
-
-	if (fstat(slot->cache_fd, &slot->cache_st))
-		return errno;
-
-	slot->bufsize = xread(slot->cache_fd, slot->buf, sizeof(slot->buf));
-	if (slot->bufsize < 0)
-		return errno;
-
-	bufz = memchr(slot->buf, 0, slot->bufsize);
-	if (bufz)
-		bufkeylen = bufz - slot->buf;
-
-	if (slot->key)
-		slot->match = bufkeylen == slot->keylen &&
-		    !memcmp(slot->key, slot->buf, bufkeylen + 1);
-
-	return 0;
-}
-
-/* Close the active cache slot */
-static int close_slot(struct cache_slot *slot)
-{
-	int err = 0;
-	if (slot->cache_fd > 0) {
-		if (close(slot->cache_fd))
-			err = errno;
-		else
-			slot->cache_fd = -1;
-	}
-	return err;
-}
-
-/* Print the content of the active cache slot (but skip the key). */
-static int print_slot(struct cache_slot *slot)
-{
-#ifdef HAVE_LINUX_SENDFILE
-	off_t start_off;
-	int ret;
-
-	start_off = slot->keylen + 1;
-
-	do {
-		ret = sendfile(STDOUT_FILENO, slot->cache_fd, &start_off,
-				slot->cache_st.st_size - start_off);
-		if (ret < 0) {
-			if (errno == EAGAIN || errno == EINTR)
-				continue;
-			return errno;
-		}
-		return 0;
-	} while (1);
-#else
-	ssize_t i, j;
-
-	i = lseek(slot->cache_fd, slot->keylen + 1, SEEK_SET);
-	if (i != slot->keylen + 1)
-		return errno;
-
-	do {
-		i = j = xread(slot->cache_fd, slot->buf, sizeof(slot->buf));
-		if (i > 0)
-			j = xwrite(STDOUT_FILENO, slot->buf, i);
-	} while (i > 0 && j == i);
-
-	if (i < 0 || j != i)
-		return errno;
-	else
-		return 0;
-#endif
-}
-
-/* Check if the slot has expired */
-static int is_expired(struct cache_slot *slot)
-{
-	if (slot->ttl < 0)
-		return 0;
-	else
-		return slot->cache_st.st_mtime + slot->ttl * 60 < time(NULL);
-}
-
-/* Check if the slot has been modified since we opened it.
- * NB: If stat() fails, we pretend the file is modified.
- */
-static int is_modified(struct cache_slot *slot)
-{
-	struct stat st;
-
-	if (stat(slot->cache_name, &st))
-		return 1;
-	return (st.st_ino != slot->cache_st.st_ino ||
-		st.st_mtime != slot->cache_st.st_mtime ||
-		st.st_size != slot->cache_st.st_size);
-}
-
-/* Close an open lockfile */
-static int close_lock(struct cache_slot *slot)
-{
-	int err = 0;
-	if (slot->lock_fd > 0) {
-		if (close(slot->lock_fd))
-			err = errno;
-		else
-			slot->lock_fd = -1;
-	}
-	return err;
-}
-
-/* Create a lockfile used to store the generated content for a cache
- * slot, and write the slot key + \0 into it.
- * Returns 0 on success and errno otherwise.
- */
-static int lock_slot(struct cache_slot *slot)
-{
-	struct flock lock = {
-		.l_type = F_WRLCK,
-		.l_whence = SEEK_SET,
-		.l_start = 0,
-		.l_len = 0,
-	};
-
-	slot->lock_fd = open(slot->lock_name, O_RDWR | O_CREAT,
-			     S_IRUSR | S_IWUSR);
-	if (slot->lock_fd == -1)
-		return errno;
-	if (fcntl(slot->lock_fd, F_SETLK, &lock) < 0) {
-		int saved_errno = errno;
-		close(slot->lock_fd);
-		slot->lock_fd = -1;
-		return saved_errno;
-	}
-	if (xwrite(slot->lock_fd, slot->key, slot->keylen + 1) < 0)
-		return errno;
-	return 0;
-}
-
-/* Release the current lockfile. If `replace_old_slot` is set the
- * lockfile replaces the old cache slot, otherwise the lockfile is
- * just deleted.
- */
-static int unlock_slot(struct cache_slot *slot, int replace_old_slot)
-{
-	int err;
-
-	if (replace_old_slot)
-		err = rename(slot->lock_name, slot->cache_name);
-	else
-		err = unlink(slot->lock_name);
-
-	/* Restore stdout and close the temporary FD. */
-	if (slot->stdout_fd >= 0) {
-		dup2(slot->stdout_fd, STDOUT_FILENO);
-		close(slot->stdout_fd);
-		slot->stdout_fd = -1;
-	}
-
-	if (err)
-		return errno;
-
-	return 0;
-}
-
-/* Generate the content for the current cache slot by redirecting
- * stdout to the lock-fd and invoking the callback function
- */
-static int fill_slot(struct cache_slot *slot)
-{
-	/* Preserve stdout */
-	slot->stdout_fd = dup(STDOUT_FILENO);
-	if (slot->stdout_fd == -1)
-		return errno;
-
-	/* Redirect stdout to lockfile */
-	if (dup2(slot->lock_fd, STDOUT_FILENO) == -1)
-		return errno;
-
-	/* Generate cache content */
-	slot->fn();
-
-	/* Make sure any buffered data is flushed to the file */
-	if (fflush(stdout))
-		return errno;
-
-	/* update stat info */
-	if (fstat(slot->lock_fd, &slot->cache_st))
-		return errno;
-
-	return 0;
-}
-
-/* Crude implementation of 32-bit FNV-1 hash algorithm,
- * see http://www.isthe.com/chongo/tech/comp/fnv/ for details
- * about the magic numbers.
- */
-#define FNV_OFFSET 0x811c9dc5
-#define FNV_PRIME  0x01000193
-
-unsigned long hash_str(const char *str)
-{
-	unsigned long h = FNV_OFFSET;
-	unsigned char *s = (unsigned char *)str;
-
-	if (!s)
-		return h;
-
-	while (*s) {
-		h *= FNV_PRIME;
-		h ^= *s++;
-	}
-	return h;
-}
-
-static int process_slot(struct cache_slot *slot)
-{
-	int err;
-
-	err = open_slot(slot);
-	if (!err && slot->match) {
-		if (is_expired(slot)) {
-			if (!lock_slot(slot)) {
-				/* If the cachefile has been replaced between
-				 * `open_slot` and `lock_slot`, we'll just
-				 * serve the stale content from the original
-				 * cachefile. This way we avoid pruning the
-				 * newly generated slot. The same code-path
-				 * is chosen if fill_slot() fails for some
-				 * reason.
-				 *
-				 * TODO? check if the new slot contains the
-				 * same key as the old one, since we would
-				 * prefer to serve the newest content.
-				 * This will require us to open yet another
-				 * file-descriptor and read and compare the
-				 * key from the new file, so for now we're
-				 * lazy and just ignore the new file.
-				 */
-				if (is_modified(slot) || fill_slot(slot)) {
-					unlock_slot(slot, 0);
-					close_lock(slot);
-				} else {
-					close_slot(slot);
-					unlock_slot(slot, 1);
-					slot->cache_fd = slot->lock_fd;
-				}
-			}
-		}
-		if ((err = print_slot(slot)) != 0) {
-			cache_log("[cgit] error printing cache %s: %s (%d)\n",
-				  slot->cache_name,
-				  strerror(err),
-				  err);
-		}
-		close_slot(slot);
-		return err;
-	}
-
-	/* If the cache slot does not exist (or its key doesn't match the
-	 * current key), lets try to create a new cache slot for this
-	 * request. If this fails (for whatever reason), lets just generate
-	 * the content without caching it and fool the caller to believe
-	 * everything worked out (but print a warning on stdout).
-	 */
-
-	close_slot(slot);
-	if ((err = lock_slot(slot)) != 0) {
-		cache_log("[cgit] Unable to lock slot %s: %s (%d)\n",
-			  slot->lock_name, strerror(err), err);
-		slot->fn();
-		return 0;
-	}
-
-	if ((err = fill_slot(slot)) != 0) {
-		cache_log("[cgit] Unable to fill slot %s: %s (%d)\n",
-			  slot->lock_name, strerror(err), err);
-		unlock_slot(slot, 0);
-		close_lock(slot);
-		slot->fn();
-		return 0;
-	}
-	// We've got a valid cache slot in the lock file, which
-	// is about to replace the old cache slot. But if we
-	// release the lockfile and then try to open the new cache
-	// slot, we might get a race condition with a concurrent
-	// writer for the same cache slot (with a different key).
-	// Lets avoid such a race by just printing the content of
-	// the lock file.
-	slot->cache_fd = slot->lock_fd;
-	unlock_slot(slot, 1);
-	if ((err = print_slot(slot)) != 0) {
-		cache_log("[cgit] error printing cache %s: %s (%d)\n",
-			  slot->cache_name,
-			  strerror(err),
-			  err);
-	}
-	close_slot(slot);
-	return err;
-}
-
-/* Print cached content to stdout, generate the content if necessary. */
-int cache_process(int size, const char *path, const char *key, int ttl,
-		  cache_fill_fn fn)
-{
-	unsigned long hash;
-	int i;
-	struct strbuf filename = STRBUF_INIT;
-	struct strbuf lockname = STRBUF_INIT;
-	struct cache_slot slot;
-	int result;
-
-	/* If the cache is disabled, just generate the content */
-	if (size <= 0 || ttl == 0) {
-		fn();
-		return 0;
-	}
-
-	/* Verify input, calculate filenames */
-	if (!path) {
-		cache_log("[cgit] Cache path not specified, caching is disabled\n");
-		fn();
-		return 0;
-	}
-	if (!key)
-		key = "";
-	hash = hash_str(key) % size;
-	strbuf_addstr(&filename, path);
-	strbuf_ensure_end(&filename, '/');
-	for (i = 0; i < 8; i++) {
-		strbuf_addf(&filename, "%x", (unsigned char)(hash & 0xf));
-		hash >>= 4;
-	}
-	strbuf_addbuf(&lockname, &filename);
-	strbuf_addstr(&lockname, ".lock");
-	slot.fn = fn;
-	slot.ttl = ttl;
-	slot.stdout_fd = -1;
-	slot.cache_name = filename.buf;
-	slot.lock_name = lockname.buf;
-	slot.key = key;
-	slot.keylen = strlen(key);
-	result = process_slot(&slot);
-
-	strbuf_release(&filename);
-	strbuf_release(&lockname);
-	return result;
-}
-
-/* Return a strftime formatted date/time
- * NB: the result from this function is to shared memory
- */
-static char *sprintftime(const char *format, time_t time)
-{
-	static char buf[64];
-	struct tm *tm;
-
-	if (!time)
-		return NULL;
-	tm = gmtime(&time);
-	strftime(buf, sizeof(buf)-1, format, tm);
-	return buf;
-}
-
-int cache_ls(const char *path)
-{
-	DIR *dir;
-	struct dirent *ent;
-	int err = 0;
-	struct cache_slot slot = { NULL };
-	struct strbuf fullname = STRBUF_INIT;
-	size_t prefixlen;
-
-	if (!path) {
-		cache_log("[cgit] cache path not specified\n");
-		return -1;
-	}
-	dir = opendir(path);
-	if (!dir) {
-		err = errno;
-		cache_log("[cgit] unable to open path %s: %s (%d)\n",
-			  path, strerror(err), err);
-		return err;
-	}
-	strbuf_addstr(&fullname, path);
-	strbuf_ensure_end(&fullname, '/');
-	prefixlen = fullname.len;
-	while ((ent = readdir(dir)) != NULL) {
-		if (strlen(ent->d_name) != 8)
-			continue;
-		strbuf_setlen(&fullname, prefixlen);
-		strbuf_addstr(&fullname, ent->d_name);
-		slot.cache_name = fullname.buf;
-		if ((err = open_slot(&slot)) != 0) {
-			cache_log("[cgit] unable to open path %s: %s (%d)\n",
-				  fullname.buf, strerror(err), err);
-			continue;
-		}
-		htmlf("%s %s %10"PRIuMAX" %s\n",
-		      fullname.buf,
-		      sprintftime("%Y-%m-%d %H:%M:%S",
-				  slot.cache_st.st_mtime),
-		      (uintmax_t)slot.cache_st.st_size,
-		      slot.buf);
-		close_slot(&slot);
-	}
-	closedir(dir);
-	strbuf_release(&fullname);
-	return 0;
-}
-
-/* Print a message to stdout */
-void cache_log(const char *format, ...)
-{
-	va_list args;
-	va_start(args, format);
-	vfprintf(stderr, format, args);
-	va_end(args);
-}
-
diff --git a/cache.h b/cache.h
deleted file mode 100644
index 470da4fc15..0000000000
--- a/cache.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Since git has it's own cache.h which we include,
- * lets test on CGIT_CACHE_H to avoid confusion
- */
-
-#ifndef CGIT_CACHE_H
-#define CGIT_CACHE_H
-
-typedef void (*cache_fill_fn)(void);
-
-
-/* Print cached content to stdout, generate the content if necessary.
- *
- * Parameters
- *   size    max number of cache files
- *   path    directory used to store cache files
- *   key     the key used to lookup cache files
- *   ttl     max cache time in seconds for this key
- *   fn      content generator function for this key
- *
- * Return value
- *   0 indicates success, everything else is an error
- */
-extern int cache_process(int size, const char *path, const char *key, int ttl,
-			 cache_fill_fn fn);
-
-
-/* List info about all cache entries on stdout */
-extern int cache_ls(const char *path);
-
-/* Print a message to stdout */
-__attribute__((format (printf,1,2)))
-extern void cache_log(const char *format, ...);
-
-extern unsigned long hash_str(const char *str);
-
-#endif /* CGIT_CACHE_H */
diff --git a/cgit.c b/cgit.c
deleted file mode 100644
index ac8c6418ba..0000000000
--- a/cgit.c
+++ /dev/null
@@ -1,1112 +0,0 @@
-/* cgit.c: cgi for the git scm
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "cache.h"
-#include "cmd.h"
-#include "configfile.h"
-#include "html.h"
-#include "ui-shared.h"
-#include "ui-stats.h"
-#include "ui-blob.h"
-#include "ui-summary.h"
-#include "scan-tree.h"
-
-const char *cgit_version = CGIT_VERSION;
-
-__attribute__((constructor))
-static void constructor_environment()
-{
-	/* Do not look in /etc/ for gitconfig and gitattributes. */
-	setenv("GIT_CONFIG_NOSYSTEM", "1", 1);
-	setenv("GIT_ATTR_NOSYSTEM", "1", 1);
-	unsetenv("HOME");
-	unsetenv("XDG_CONFIG_HOME");
-}
-
-static void add_mimetype(const char *name, const char *value)
-{
-	struct string_list_item *item;
-
-	item = string_list_insert(&ctx.cfg.mimetypes, name);
-	item->util = xstrdup(value);
-}
-
-static void process_cached_repolist(const char *path);
-
-static void repo_config(struct cgit_repo *repo, const char *name, const char *value)
-{
-	const char *path;
-	struct string_list_item *item;
-
-	if (!strcmp(name, "name"))
-		repo->name = xstrdup(value);
-	else if (!strcmp(name, "clone-url"))
-		repo->clone_url = xstrdup(value);
-	else if (!strcmp(name, "desc"))
-		repo->desc = xstrdup(value);
-	else if (!strcmp(name, "owner"))
-		repo->owner = xstrdup(value);
-	else if (!strcmp(name, "homepage"))
-		repo->homepage = xstrdup(value);
-	else if (!strcmp(name, "defbranch"))
-		repo->defbranch = xstrdup(value);
-	else if (!strcmp(name, "extra-head-content"))
-		repo->extra_head_content = xstrdup(value);
-	else if (!strcmp(name, "snapshots"))
-		repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
-	else if (!strcmp(name, "enable-blame"))
-		repo->enable_blame = atoi(value);
-	else if (!strcmp(name, "enable-commit-graph"))
-		repo->enable_commit_graph = atoi(value);
-	else if (!strcmp(name, "enable-log-filecount"))
-		repo->enable_log_filecount = atoi(value);
-	else if (!strcmp(name, "enable-log-linecount"))
-		repo->enable_log_linecount = atoi(value);
-	else if (!strcmp(name, "enable-remote-branches"))
-		repo->enable_remote_branches = atoi(value);
-	else if (!strcmp(name, "enable-subject-links"))
-		repo->enable_subject_links = atoi(value);
-	else if (!strcmp(name, "enable-html-serving"))
-		repo->enable_html_serving = atoi(value);
-	else if (!strcmp(name, "branch-sort")) {
-		if (!strcmp(value, "age"))
-			repo->branch_sort = 1;
-		if (!strcmp(value, "name"))
-			repo->branch_sort = 0;
-	} else if (!strcmp(name, "commit-sort")) {
-		if (!strcmp(value, "date"))
-			repo->commit_sort = 1;
-		if (!strcmp(value, "topo"))
-			repo->commit_sort = 2;
-	} else if (!strcmp(name, "max-stats"))
-		repo->max_stats = cgit_find_stats_period(value, NULL);
-	else if (!strcmp(name, "module-link"))
-		repo->module_link= xstrdup(value);
-	else if (skip_prefix(name, "module-link.", &path)) {
-		item = string_list_append(&repo->submodules, xstrdup(path));
-		item->util = xstrdup(value);
-	} else if (!strcmp(name, "section"))
-		repo->section = xstrdup(value);
-	else if (!strcmp(name, "snapshot-prefix"))
-		repo->snapshot_prefix = xstrdup(value);
-	else if (!strcmp(name, "readme") && value != NULL) {
-		if (repo->readme.items == ctx.cfg.readme.items)
-			memset(&repo->readme, 0, sizeof(repo->readme));
-		string_list_append(&repo->readme, xstrdup(value));
-	} else if (!strcmp(name, "logo") && value != NULL)
-		repo->logo = xstrdup(value);
-	else if (!strcmp(name, "logo-link") && value != NULL)
-		repo->logo_link = xstrdup(value);
-	else if (!strcmp(name, "hide"))
-		repo->hide = atoi(value);
-	else if (!strcmp(name, "ignore"))
-		repo->ignore = atoi(value);
-	else if (ctx.cfg.enable_filter_overrides) {
-		if (!strcmp(name, "about-filter"))
-			repo->about_filter = cgit_new_filter(value, ABOUT);
-		else if (!strcmp(name, "commit-filter"))
-			repo->commit_filter = cgit_new_filter(value, COMMIT);
-		else if (!strcmp(name, "source-filter"))
-			repo->source_filter = cgit_new_filter(value, SOURCE);
-		else if (!strcmp(name, "email-filter"))
-			repo->email_filter = cgit_new_filter(value, EMAIL);
-		else if (!strcmp(name, "owner-filter"))
-			repo->owner_filter = cgit_new_filter(value, OWNER);
-	}
-}
-
-static void config_cb(const char *name, const char *value)
-{
-	const char *arg;
-
-	if (!strcmp(name, "section"))
-		ctx.cfg.section = xstrdup(value);
-	else if (!strcmp(name, "repo.url"))
-		ctx.repo = cgit_add_repo(value);
-	else if (ctx.repo && !strcmp(name, "repo.path"))
-		ctx.repo->path = trim_end(value, '/');
-	else if (ctx.repo && skip_prefix(name, "repo.", &arg))
-		repo_config(ctx.repo, arg, value);
-	else if (!strcmp(name, "readme"))
-		string_list_append(&ctx.cfg.readme, xstrdup(value));
-	else if (!strcmp(name, "root-title"))
-		ctx.cfg.root_title = xstrdup(value);
-	else if (!strcmp(name, "root-desc"))
-		ctx.cfg.root_desc = xstrdup(value);
-	else if (!strcmp(name, "root-readme"))
-		ctx.cfg.root_readme = xstrdup(value);
-	else if (!strcmp(name, "css"))
-		ctx.cfg.css = xstrdup(value);
-	else if (!strcmp(name, "favicon"))
-		ctx.cfg.favicon = xstrdup(value);
-	else if (!strcmp(name, "footer"))
-		ctx.cfg.footer = xstrdup(value);
-	else if (!strcmp(name, "head-include"))
-		ctx.cfg.head_include = xstrdup(value);
-	else if (!strcmp(name, "header"))
-		ctx.cfg.header = xstrdup(value);
-	else if (!strcmp(name, "logo"))
-		ctx.cfg.logo = xstrdup(value);
-	else if (!strcmp(name, "logo-link"))
-		ctx.cfg.logo_link = xstrdup(value);
-	else if (!strcmp(name, "module-link"))
-		ctx.cfg.module_link = xstrdup(value);
-	else if (!strcmp(name, "strict-export"))
-		ctx.cfg.strict_export = xstrdup(value);
-	else if (!strcmp(name, "virtual-root"))
-		ctx.cfg.virtual_root = ensure_end(value, '/');
-	else if (!strcmp(name, "noplainemail"))
-		ctx.cfg.noplainemail = atoi(value);
-	else if (!strcmp(name, "noheader"))
-		ctx.cfg.noheader = atoi(value);
-	else if (!strcmp(name, "snapshots"))
-		ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
-	else if (!strcmp(name, "enable-filter-overrides"))
-		ctx.cfg.enable_filter_overrides = atoi(value);
-	else if (!strcmp(name, "enable-follow-links"))
-		ctx.cfg.enable_follow_links = atoi(value);
-	else if (!strcmp(name, "enable-http-clone"))
-		ctx.cfg.enable_http_clone = atoi(value);
-	else if (!strcmp(name, "enable-index-links"))
-		ctx.cfg.enable_index_links = atoi(value);
-	else if (!strcmp(name, "enable-index-owner"))
-		ctx.cfg.enable_index_owner = atoi(value);
-	else if (!strcmp(name, "enable-blame"))
-		ctx.cfg.enable_blame = atoi(value);
-	else if (!strcmp(name, "enable-commit-graph"))
-		ctx.cfg.enable_commit_graph = atoi(value);
-	else if (!strcmp(name, "enable-log-filecount"))
-		ctx.cfg.enable_log_filecount = atoi(value);
-	else if (!strcmp(name, "enable-log-linecount"))
-		ctx.cfg.enable_log_linecount = atoi(value);
-	else if (!strcmp(name, "enable-remote-branches"))
-		ctx.cfg.enable_remote_branches = atoi(value);
-	else if (!strcmp(name, "enable-subject-links"))
-		ctx.cfg.enable_subject_links = atoi(value);
-	else if (!strcmp(name, "enable-html-serving"))
-		ctx.cfg.enable_html_serving = atoi(value);
-	else if (!strcmp(name, "enable-tree-linenumbers"))
-		ctx.cfg.enable_tree_linenumbers = atoi(value);
-	else if (!strcmp(name, "enable-git-config"))
-		ctx.cfg.enable_git_config = atoi(value);
-	else if (!strcmp(name, "max-stats"))
-		ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
-	else if (!strcmp(name, "cache-size"))
-		ctx.cfg.cache_size = atoi(value);
-	else if (!strcmp(name, "cache-root"))
-		ctx.cfg.cache_root = xstrdup(expand_macros(value));
-	else if (!strcmp(name, "cache-root-ttl"))
-		ctx.cfg.cache_root_ttl = atoi(value);
-	else if (!strcmp(name, "cache-repo-ttl"))
-		ctx.cfg.cache_repo_ttl = atoi(value);
-	else if (!strcmp(name, "cache-scanrc-ttl"))
-		ctx.cfg.cache_scanrc_ttl = atoi(value);
-	else if (!strcmp(name, "cache-static-ttl"))
-		ctx.cfg.cache_static_ttl = atoi(value);
-	else if (!strcmp(name, "cache-dynamic-ttl"))
-		ctx.cfg.cache_dynamic_ttl = atoi(value);
-	else if (!strcmp(name, "cache-about-ttl"))
-		ctx.cfg.cache_about_ttl = atoi(value);
-	else if (!strcmp(name, "cache-snapshot-ttl"))
-		ctx.cfg.cache_snapshot_ttl = atoi(value);
-	else if (!strcmp(name, "case-sensitive-sort"))
-		ctx.cfg.case_sensitive_sort = atoi(value);
-	else if (!strcmp(name, "about-filter"))
-		ctx.cfg.about_filter = cgit_new_filter(value, ABOUT);
-	else if (!strcmp(name, "commit-filter"))
-		ctx.cfg.commit_filter = cgit_new_filter(value, COMMIT);
-	else if (!strcmp(name, "email-filter"))
-		ctx.cfg.email_filter = cgit_new_filter(value, EMAIL);
-	else if (!strcmp(name, "owner-filter"))
-		ctx.cfg.owner_filter = cgit_new_filter(value, OWNER);
-	else if (!strcmp(name, "auth-filter"))
-		ctx.cfg.auth_filter = cgit_new_filter(value, AUTH);
-	else if (!strcmp(name, "embedded"))
-		ctx.cfg.embedded = atoi(value);
-	else if (!strcmp(name, "max-atom-items"))
-		ctx.cfg.max_atom_items = atoi(value);
-	else if (!strcmp(name, "max-message-length"))
-		ctx.cfg.max_msg_len = atoi(value);
-	else if (!strcmp(name, "max-repodesc-length"))
-		ctx.cfg.max_repodesc_len = atoi(value);
-	else if (!strcmp(name, "max-blob-size"))
-		ctx.cfg.max_blob_size = atoi(value);
-	else if (!strcmp(name, "max-repo-count"))
-		ctx.cfg.max_repo_count = atoi(value);
-	else if (!strcmp(name, "max-commit-count"))
-		ctx.cfg.max_commit_count = atoi(value);
-	else if (!strcmp(name, "project-list"))
-		ctx.cfg.project_list = xstrdup(expand_macros(value));
-	else if (!strcmp(name, "scan-path"))
-		if (ctx.cfg.cache_size)
-			process_cached_repolist(expand_macros(value));
-		else if (ctx.cfg.project_list)
-			scan_projects(expand_macros(value),
-				      ctx.cfg.project_list, repo_config);
-		else
-			scan_tree(expand_macros(value), repo_config);
-	else if (!strcmp(name, "scan-hidden-path"))
-		ctx.cfg.scan_hidden_path = atoi(value);
-	else if (!strcmp(name, "section-from-path"))
-		ctx.cfg.section_from_path = atoi(value);
-	else if (!strcmp(name, "repository-sort"))
-		ctx.cfg.repository_sort = xstrdup(value);
-	else if (!strcmp(name, "section-sort"))
-		ctx.cfg.section_sort = atoi(value);
-	else if (!strcmp(name, "source-filter"))
-		ctx.cfg.source_filter = cgit_new_filter(value, SOURCE);
-	else if (!strcmp(name, "summary-log"))
-		ctx.cfg.summary_log = atoi(value);
-	else if (!strcmp(name, "summary-branches"))
-		ctx.cfg.summary_branches = atoi(value);
-	else if (!strcmp(name, "summary-tags"))
-		ctx.cfg.summary_tags = atoi(value);
-	else if (!strcmp(name, "side-by-side-diffs"))
-		ctx.cfg.difftype = atoi(value) ? DIFF_SSDIFF : DIFF_UNIFIED;
-	else if (!strcmp(name, "agefile"))
-		ctx.cfg.agefile = xstrdup(value);
-	else if (!strcmp(name, "mimetype-file"))
-		ctx.cfg.mimetype_file = xstrdup(value);
-	else if (!strcmp(name, "renamelimit"))
-		ctx.cfg.renamelimit = atoi(value);
-	else if (!strcmp(name, "remove-suffix"))
-		ctx.cfg.remove_suffix = atoi(value);
-	else if (!strcmp(name, "robots"))
-		ctx.cfg.robots = xstrdup(value);
-	else if (!strcmp(name, "clone-prefix"))
-		ctx.cfg.clone_prefix = xstrdup(value);
-	else if (!strcmp(name, "clone-url"))
-		ctx.cfg.clone_url = xstrdup(value);
-	else if (!strcmp(name, "local-time"))
-		ctx.cfg.local_time = atoi(value);
-	else if (!strcmp(name, "commit-sort")) {
-		if (!strcmp(value, "date"))
-			ctx.cfg.commit_sort = 1;
-		if (!strcmp(value, "topo"))
-			ctx.cfg.commit_sort = 2;
-	} else if (!strcmp(name, "branch-sort")) {
-		if (!strcmp(value, "age"))
-			ctx.cfg.branch_sort = 1;
-		if (!strcmp(value, "name"))
-			ctx.cfg.branch_sort = 0;
-	} else if (skip_prefix(name, "mimetype.", &arg))
-		add_mimetype(arg, value);
-	else if (!strcmp(name, "include"))
-		parse_configfile(expand_macros(value), config_cb);
-}
-
-static void querystring_cb(const char *name, const char *value)
-{
-	if (!value)
-		value = "";
-
-	if (!strcmp(name,"r")) {
-		ctx.qry.repo = xstrdup(value);
-		ctx.repo = cgit_get_repoinfo(value);
-	} else if (!strcmp(name, "p")) {
-		ctx.qry.page = xstrdup(value);
-	} else if (!strcmp(name, "url")) {
-		if (*value == '/')
-			value++;
-		ctx.qry.url = xstrdup(value);
-		cgit_parse_url(value);
-	} else if (!strcmp(name, "qt")) {
-		ctx.qry.grep = xstrdup(value);
-	} else if (!strcmp(name, "q")) {
-		ctx.qry.search = xstrdup(value);
-	} else if (!strcmp(name, "h")) {
-		ctx.qry.head = xstrdup(value);
-		ctx.qry.has_symref = 1;
-	} else if (!strcmp(name, "id")) {
-		ctx.qry.sha1 = xstrdup(value);
-		ctx.qry.has_sha1 = 1;
-	} else if (!strcmp(name, "id2")) {
-		ctx.qry.sha2 = xstrdup(value);
-		ctx.qry.has_sha1 = 1;
-	} else if (!strcmp(name, "ofs")) {
-		ctx.qry.ofs = atoi(value);
-	} else if (!strcmp(name, "path")) {
-		ctx.qry.path = trim_end(value, '/');
-	} else if (!strcmp(name, "name")) {
-		ctx.qry.name = xstrdup(value);
-	} else if (!strcmp(name, "s")) {
-		ctx.qry.sort = xstrdup(value);
-	} else if (!strcmp(name, "showmsg")) {
-		ctx.qry.showmsg = atoi(value);
-	} else if (!strcmp(name, "period")) {
-		ctx.qry.period = xstrdup(value);
-	} else if (!strcmp(name, "dt")) {
-		ctx.qry.difftype = atoi(value);
-		ctx.qry.has_difftype = 1;
-	} else if (!strcmp(name, "ss")) {
-		/* No longer generated, but there may be links out there. */
-		ctx.qry.difftype = atoi(value) ? DIFF_SSDIFF : DIFF_UNIFIED;
-		ctx.qry.has_difftype = 1;
-	} else if (!strcmp(name, "all")) {
-		ctx.qry.show_all = atoi(value);
-	} else if (!strcmp(name, "context")) {
-		ctx.qry.context = atoi(value);
-	} else if (!strcmp(name, "ignorews")) {
-		ctx.qry.ignorews = atoi(value);
-	} else if (!strcmp(name, "follow")) {
-		ctx.qry.follow = atoi(value);
-	}
-}
-
-static void prepare_context(void)
-{
-	memset(&ctx, 0, sizeof(ctx));
-	ctx.cfg.agefile = "info/web/last-modified";
-	ctx.cfg.cache_size = 0;
-	ctx.cfg.cache_max_create_time = 5;
-	ctx.cfg.cache_root = CGIT_CACHE_ROOT;
-	ctx.cfg.cache_about_ttl = 15;
-	ctx.cfg.cache_snapshot_ttl = 5;
-	ctx.cfg.cache_repo_ttl = 5;
-	ctx.cfg.cache_root_ttl = 5;
-	ctx.cfg.cache_scanrc_ttl = 15;
-	ctx.cfg.cache_dynamic_ttl = 5;
-	ctx.cfg.cache_static_ttl = -1;
-	ctx.cfg.case_sensitive_sort = 1;
-	ctx.cfg.branch_sort = 0;
-	ctx.cfg.commit_sort = 0;
-	ctx.cfg.css = "/cgit.css";
-	ctx.cfg.logo = "/cgit.png";
-	ctx.cfg.favicon = "/favicon.ico";
-	ctx.cfg.local_time = 0;
-	ctx.cfg.enable_http_clone = 1;
-	ctx.cfg.enable_index_owner = 1;
-	ctx.cfg.enable_tree_linenumbers = 1;
-	ctx.cfg.enable_git_config = 0;
-	ctx.cfg.max_repo_count = 50;
-	ctx.cfg.max_commit_count = 50;
-	ctx.cfg.max_lock_attempts = 5;
-	ctx.cfg.max_msg_len = 80;
-	ctx.cfg.max_repodesc_len = 80;
-	ctx.cfg.max_blob_size = 0;
-	ctx.cfg.max_stats = 0;
-	ctx.cfg.project_list = NULL;
-	ctx.cfg.renamelimit = -1;
-	ctx.cfg.remove_suffix = 0;
-	ctx.cfg.robots = "index, nofollow";
-	ctx.cfg.root_title = "Git repository browser";
-	ctx.cfg.root_desc = "a fast webinterface for the git dscm";
-	ctx.cfg.scan_hidden_path = 0;
-	ctx.cfg.script_name = CGIT_SCRIPT_NAME;
-	ctx.cfg.section = "";
-	ctx.cfg.repository_sort = "name";
-	ctx.cfg.section_sort = 1;
-	ctx.cfg.summary_branches = 10;
-	ctx.cfg.summary_log = 10;
-	ctx.cfg.summary_tags = 10;
-	ctx.cfg.max_atom_items = 10;
-	ctx.cfg.difftype = DIFF_UNIFIED;
-	ctx.env.cgit_config = getenv("CGIT_CONFIG");
-	ctx.env.http_host = getenv("HTTP_HOST");
-	ctx.env.https = getenv("HTTPS");
-	ctx.env.no_http = getenv("NO_HTTP");
-	ctx.env.path_info = getenv("PATH_INFO");
-	ctx.env.query_string = getenv("QUERY_STRING");
-	ctx.env.request_method = getenv("REQUEST_METHOD");
-	ctx.env.script_name = getenv("SCRIPT_NAME");
-	ctx.env.server_name = getenv("SERVER_NAME");
-	ctx.env.server_port = getenv("SERVER_PORT");
-	ctx.env.http_cookie = getenv("HTTP_COOKIE");
-	ctx.env.http_referer = getenv("HTTP_REFERER");
-	ctx.env.content_length = getenv("CONTENT_LENGTH") ? strtoul(getenv("CONTENT_LENGTH"), NULL, 10) : 0;
-	ctx.env.authenticated = 0;
-	ctx.page.mimetype = "text/html";
-	ctx.page.charset = PAGE_ENCODING;
-	ctx.page.filename = NULL;
-	ctx.page.size = 0;
-	ctx.page.modified = time(NULL);
-	ctx.page.expires = ctx.page.modified;
-	ctx.page.etag = NULL;
-	string_list_init(&ctx.cfg.mimetypes, 1);
-	if (ctx.env.script_name)
-		ctx.cfg.script_name = xstrdup(ctx.env.script_name);
-	if (ctx.env.query_string)
-		ctx.qry.raw = xstrdup(ctx.env.query_string);
-	if (!ctx.env.cgit_config)
-		ctx.env.cgit_config = CGIT_CONFIG;
-}
-
-struct refmatch {
-	char *req_ref;
-	char *first_ref;
-	int match;
-};
-
-static int find_current_ref(const char *refname, const struct object_id *oid,
-			    int flags, void *cb_data)
-{
-	struct refmatch *info;
-
-	info = (struct refmatch *)cb_data;
-	if (!strcmp(refname, info->req_ref))
-		info->match = 1;
-	if (!info->first_ref)
-		info->first_ref = xstrdup(refname);
-	return info->match;
-}
-
-static void free_refmatch_inner(struct refmatch *info)
-{
-	if (info->first_ref)
-		free(info->first_ref);
-}
-
-static char *find_default_branch(struct cgit_repo *repo)
-{
-	struct refmatch info;
-	char *ref;
-
-	info.req_ref = repo->defbranch;
-	info.first_ref = NULL;
-	info.match = 0;
-	for_each_branch_ref(find_current_ref, &info);
-	if (info.match)
-		ref = info.req_ref;
-	else
-		ref = info.first_ref;
-	if (ref)
-		ref = xstrdup(ref);
-	free_refmatch_inner(&info);
-
-	return ref;
-}
-
-static char *guess_defbranch(void)
-{
-	const char *ref, *refname;
-	struct object_id oid;
-
-	ref = resolve_ref_unsafe("HEAD", 0, &oid, NULL);
-	if (!ref || !skip_prefix(ref, "refs/heads/", &refname))
-		return "master";
-	return xstrdup(refname);
-}
-
-/* The caller must free filename and ref after calling this. */
-static inline void parse_readme(const char *readme, char **filename, char **ref, struct cgit_repo *repo)
-{
-	const char *colon;
-
-	*filename = NULL;
-	*ref = NULL;
-
-	if (!readme || !readme[0])
-		return;
-
-	/* Check if the readme is tracked in the git repo. */
-	colon = strchr(readme, ':');
-	if (colon && strlen(colon) > 1) {
-		/* If it starts with a colon, we want to use
-		 * the default branch */
-		if (colon == readme && repo->defbranch)
-			*ref = xstrdup(repo->defbranch);
-		else
-			*ref = xstrndup(readme, colon - readme);
-		readme = colon + 1;
-	}
-
-	/* Prepend repo path to relative readme path unless tracked. */
-	if (!(*ref) && readme[0] != '/')
-		*filename = fmtalloc("%s/%s", repo->path, readme);
-	else
-		*filename = xstrdup(readme);
-}
-static void choose_readme(struct cgit_repo *repo)
-{
-	int found;
-	char *filename, *ref;
-	struct string_list_item *entry;
-
-	if (!repo->readme.nr)
-		return;
-
-	found = 0;
-	for_each_string_list_item(entry, &repo->readme) {
-		parse_readme(entry->string, &filename, &ref, repo);
-		if (!filename) {
-			free(filename);
-			free(ref);
-			continue;
-		}
-		if (ref) {
-			if (cgit_ref_path_exists(filename, ref, 1)) {
-				found = 1;
-				break;
-			}
-		}
-		else if (!access(filename, R_OK)) {
-			found = 1;
-			break;
-		}
-		free(filename);
-		free(ref);
-	}
-	repo->readme.strdup_strings = 1;
-	string_list_clear(&repo->readme, 0);
-	repo->readme.strdup_strings = 0;
-	if (found)
-		string_list_append(&repo->readme, filename)->util = ref;
-}
-
-static void print_no_repo_clone_urls(const char *url)
-{
-        html("<tr><td><a rel='vcs-git' href='");
-        html_url_path(url);
-        html("' title='");
-        html_attr(ctx.repo->name);
-        html(" Git repository'>");
-        html_txt(url);
-        html("</a></td></tr>\n");
-}
-
-static void prepare_repo_env(int *nongit)
-{
-	/* The path to the git repository. */
-	setenv("GIT_DIR", ctx.repo->path, 1);
-
-	/* Setup the git directory and initialize the notes system. Both of these
-	 * load local configuration from the git repository, so we do them both while
-	 * the HOME variables are unset. */
-	setup_git_directory_gently(nongit);
-	init_display_notes(NULL);
-}
-
-static int prepare_repo_cmd(int nongit)
-{
-	struct object_id oid;
-	int rc;
-
-	if (nongit) {
-		const char *name = ctx.repo->name;
-		rc = errno;
-		ctx.page.title = fmtalloc("%s - %s", ctx.cfg.root_title,
-						"config error");
-		ctx.repo = NULL;
-		cgit_print_http_headers();
-		cgit_print_docstart();
-		cgit_print_pageheader();
-		cgit_print_error("Failed to open %s: %s", name,
-				 rc ? strerror(rc) : "Not a valid git repository");
-		cgit_print_docend();
-		return 1;
-	}
-	ctx.page.title = fmtalloc("%s - %s", ctx.repo->name, ctx.repo->desc);
-
-	if (!ctx.repo->defbranch)
-		ctx.repo->defbranch = guess_defbranch();
-
-	if (!ctx.qry.head) {
-		ctx.qry.nohead = 1;
-		ctx.qry.head = find_default_branch(ctx.repo);
-	}
-
-	if (!ctx.qry.head) {
-		cgit_print_http_headers();
-		cgit_print_docstart();
-		cgit_print_pageheader();
-		cgit_print_error("Repository seems to be empty");
-		if (!strcmp(ctx.qry.page, "summary")) {
-			html("<table class='list'><tr class='nohover'><td>&nbsp;</td></tr><tr class='nohover'><th class='left'>Clone</th></tr>\n");
-			cgit_prepare_repo_env(ctx.repo);
-			cgit_add_clone_urls(print_no_repo_clone_urls);
-			html("</table>\n");
-		}
-		cgit_print_docend();
-		return 1;
-	}
-
-	if (get_oid(ctx.qry.head, &oid)) {
-		char *old_head = ctx.qry.head;
-		ctx.qry.head = xstrdup(ctx.repo->defbranch);
-		cgit_print_error_page(404, "Not found",
-				"Invalid branch: %s", old_head);
-		free(old_head);
-		return 1;
-	}
-	string_list_sort(&ctx.repo->submodules);
-	cgit_prepare_repo_env(ctx.repo);
-	choose_readme(ctx.repo);
-	return 0;
-}
-
-static inline void open_auth_filter(const char *function)
-{
-	cgit_open_filter(ctx.cfg.auth_filter, function,
-		ctx.env.http_cookie ? ctx.env.http_cookie : "",
-		ctx.env.request_method ? ctx.env.request_method : "",
-		ctx.env.query_string ? ctx.env.query_string : "",
-		ctx.env.http_referer ? ctx.env.http_referer : "",
-		ctx.env.path_info ? ctx.env.path_info : "",
-		ctx.env.http_host ? ctx.env.http_host : "",
-		ctx.env.https ? ctx.env.https : "",
-		ctx.qry.repo ? ctx.qry.repo : "",
-		ctx.qry.page ? ctx.qry.page : "",
-		cgit_currentfullurl(),
-		cgit_loginurl());
-}
-
-/* We intentionally keep this rather small, instead of looping and
- * feeding it to the filter a couple bytes at a time. This way, the
- * filter itself does not need to handle any denial of service or
- * buffer bloat issues. If this winds up being too small, people
- * will complain on the mailing list, and we'll increase it as needed. */
-#define MAX_AUTHENTICATION_POST_BYTES 4096
-/* The filter is expected to spit out "Status: " and all headers. */
-static inline void authenticate_post(void)
-{
-	char buffer[MAX_AUTHENTICATION_POST_BYTES];
-	ssize_t len;
-
-	open_auth_filter("authenticate-post");
-	len = ctx.env.content_length;
-	if (len > MAX_AUTHENTICATION_POST_BYTES)
-		len = MAX_AUTHENTICATION_POST_BYTES;
-	if ((len = read(STDIN_FILENO, buffer, len)) < 0)
-		die_errno("Could not read POST from stdin");
-	if (write(STDOUT_FILENO, buffer, len) < 0)
-		die_errno("Could not write POST to stdout");
-	cgit_close_filter(ctx.cfg.auth_filter);
-	exit(0);
-}
-
-static inline void authenticate_cookie(void)
-{
-	/* If we don't have an auth_filter, consider all cookies valid, and thus return early. */
-	if (!ctx.cfg.auth_filter) {
-		ctx.env.authenticated = 1;
-		return;
-	}
-
-	/* If we're having something POST'd to /login, we're authenticating POST,
-	 * instead of the cookie, so call authenticate_post and bail out early.
-	 * This pattern here should match /?p=login with POST. */
-	if (ctx.env.request_method && ctx.qry.page && !ctx.repo && \
-	    !strcmp(ctx.env.request_method, "POST") && !strcmp(ctx.qry.page, "login")) {
-		authenticate_post();
-		return;
-	}
-
-	/* If we've made it this far, we're authenticating the cookie for real, so do that. */
-	open_auth_filter("authenticate-cookie");
-	ctx.env.authenticated = cgit_close_filter(ctx.cfg.auth_filter);
-}
-
-static void process_request(void)
-{
-	struct cgit_cmd *cmd;
-	int nongit = 0;
-
-	/* If we're not yet authenticated, no matter what page we're on,
-	 * display the authentication body from the auth_filter. This should
-	 * never be cached. */
-	if (!ctx.env.authenticated) {
-		ctx.page.title = "Authentication Required";
-		cgit_print_http_headers();
-		cgit_print_docstart();
-		cgit_print_pageheader();
-		open_auth_filter("body");
-		cgit_close_filter(ctx.cfg.auth_filter);
-		cgit_print_docend();
-		return;
-	}
-
-	if (ctx.repo)
-		prepare_repo_env(&nongit);
-
-	cmd = cgit_get_cmd();
-	if (!cmd) {
-		ctx.page.title = "cgit error";
-		cgit_print_error_page(404, "Not found", "Invalid request");
-		return;
-	}
-
-	if (!ctx.cfg.enable_http_clone && cmd->is_clone) {
-		ctx.page.title = "cgit error";
-		cgit_print_error_page(404, "Not found", "Invalid request");
-		return;
-	}
-
-	if (cmd->want_repo && !ctx.repo) {
-		cgit_print_error_page(400, "Bad request",
-				"No repository selected");
-		return;
-	}
-
-	/* If cmd->want_vpath is set, assume ctx.qry.path contains a "virtual"
-	 * in-project path limit to be made available at ctx.qry.vpath.
-	 * Otherwise, no path limit is in effect (ctx.qry.vpath = NULL).
-	 */
-	ctx.qry.vpath = cmd->want_vpath ? ctx.qry.path : NULL;
-
-	if (ctx.repo && prepare_repo_cmd(nongit))
-		return;
-
-	cmd->fn();
-}
-
-static int cmp_repos(const void *a, const void *b)
-{
-	const struct cgit_repo *ra = a, *rb = b;
-	return strcmp(ra->url, rb->url);
-}
-
-static char *build_snapshot_setting(int bitmap)
-{
-	const struct cgit_snapshot_format *f;
-	struct strbuf result = STRBUF_INIT;
-
-	for (f = cgit_snapshot_formats; f->suffix; f++) {
-		if (cgit_snapshot_format_bit(f) & bitmap) {
-			if (result.len)
-				strbuf_addch(&result, ' ');
-			strbuf_addstr(&result, f->suffix);
-		}
-	}
-	return strbuf_detach(&result, NULL);
-}
-
-static char *get_first_line(char *txt)
-{
-	char *t = xstrdup(txt);
-	char *p = strchr(t, '\n');
-	if (p)
-		*p = '\0';
-	return t;
-}
-
-static void print_repo(FILE *f, struct cgit_repo *repo)
-{
-	struct string_list_item *item;
-	fprintf(f, "repo.url=%s\n", repo->url);
-	fprintf(f, "repo.name=%s\n", repo->name);
-	fprintf(f, "repo.path=%s\n", repo->path);
-	if (repo->owner)
-		fprintf(f, "repo.owner=%s\n", repo->owner);
-	if (repo->desc) {
-		char *tmp = get_first_line(repo->desc);
-		fprintf(f, "repo.desc=%s\n", tmp);
-		free(tmp);
-	}
-	for_each_string_list_item(item, &repo->readme) {
-		if (item->util)
-			fprintf(f, "repo.readme=%s:%s\n", (char *)item->util, item->string);
-		else
-			fprintf(f, "repo.readme=%s\n", item->string);
-	}
-	if (repo->defbranch)
-		fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
-	if (repo->extra_head_content)
-		fprintf(f, "repo.extra-head-content=%s\n", repo->extra_head_content);
-	if (repo->module_link)
-		fprintf(f, "repo.module-link=%s\n", repo->module_link);
-	if (repo->section)
-		fprintf(f, "repo.section=%s\n", repo->section);
-	if (repo->homepage)
-		fprintf(f, "repo.homepage=%s\n", repo->homepage);
-	if (repo->clone_url)
-		fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
-	fprintf(f, "repo.enable-blame=%d\n",
-	        repo->enable_blame);
-	fprintf(f, "repo.enable-commit-graph=%d\n",
-	        repo->enable_commit_graph);
-	fprintf(f, "repo.enable-log-filecount=%d\n",
-	        repo->enable_log_filecount);
-	fprintf(f, "repo.enable-log-linecount=%d\n",
-	        repo->enable_log_linecount);
-	if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
-		cgit_fprintf_filter(repo->about_filter, f, "repo.about-filter=");
-	if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
-		cgit_fprintf_filter(repo->commit_filter, f, "repo.commit-filter=");
-	if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
-		cgit_fprintf_filter(repo->source_filter, f, "repo.source-filter=");
-	if (repo->email_filter && repo->email_filter != ctx.cfg.email_filter)
-		cgit_fprintf_filter(repo->email_filter, f, "repo.email-filter=");
-	if (repo->owner_filter && repo->owner_filter != ctx.cfg.owner_filter)
-		cgit_fprintf_filter(repo->owner_filter, f, "repo.owner-filter=");
-	if (repo->snapshots != ctx.cfg.snapshots) {
-		char *tmp = build_snapshot_setting(repo->snapshots);
-		fprintf(f, "repo.snapshots=%s\n", tmp ? tmp : "");
-		free(tmp);
-	}
-	if (repo->snapshot_prefix)
-		fprintf(f, "repo.snapshot-prefix=%s\n", repo->snapshot_prefix);
-	if (repo->max_stats != ctx.cfg.max_stats)
-		fprintf(f, "repo.max-stats=%s\n",
-		        cgit_find_stats_periodname(repo->max_stats));
-	if (repo->logo)
-		fprintf(f, "repo.logo=%s\n", repo->logo);
-	if (repo->logo_link)
-		fprintf(f, "repo.logo-link=%s\n", repo->logo_link);
-	fprintf(f, "repo.enable-remote-branches=%d\n", repo->enable_remote_branches);
-	fprintf(f, "repo.enable-subject-links=%d\n", repo->enable_subject_links);
-	fprintf(f, "repo.enable-html-serving=%d\n", repo->enable_html_serving);
-	if (repo->branch_sort == 1)
-		fprintf(f, "repo.branch-sort=age\n");
-	if (repo->commit_sort) {
-		if (repo->commit_sort == 1)
-			fprintf(f, "repo.commit-sort=date\n");
-		else if (repo->commit_sort == 2)
-			fprintf(f, "repo.commit-sort=topo\n");
-	}
-	fprintf(f, "repo.hide=%d\n", repo->hide);
-	fprintf(f, "repo.ignore=%d\n", repo->ignore);
-	fprintf(f, "\n");
-}
-
-static void print_repolist(FILE *f, struct cgit_repolist *list, int start)
-{
-	int i;
-
-	for (i = start; i < list->count; i++)
-		print_repo(f, &list->repos[i]);
-}
-
-/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
- * and return 0 on success.
- */
-static int generate_cached_repolist(const char *path, const char *cached_rc)
-{
-	struct strbuf locked_rc = STRBUF_INIT;
-	int result = 0;
-	int idx;
-	FILE *f;
-
-	strbuf_addf(&locked_rc, "%s.lock", cached_rc);
-	f = fopen(locked_rc.buf, "wx");
-	if (!f) {
-		/* Inform about the error unless the lockfile already existed,
-		 * since that only means we've got concurrent requests.
-		 */
-		result = errno;
-		if (result != EEXIST)
-			fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
-				locked_rc.buf, strerror(result), result);
-		goto out;
-	}
-	idx = cgit_repolist.count;
-	if (ctx.cfg.project_list)
-		scan_projects(path, ctx.cfg.project_list, repo_config);
-	else
-		scan_tree(path, repo_config);
-	print_repolist(f, &cgit_repolist, idx);
-	if (rename(locked_rc.buf, cached_rc))
-		fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
-			locked_rc.buf, cached_rc, strerror(errno), errno);
-	fclose(f);
-out:
-	strbuf_release(&locked_rc);
-	return result;
-}
-
-static void process_cached_repolist(const char *path)
-{
-	struct stat st;
-	struct strbuf cached_rc = STRBUF_INIT;
-	time_t age;
-	unsigned long hash;
-
-	hash = hash_str(path);
-	if (ctx.cfg.project_list)
-		hash += hash_str(ctx.cfg.project_list);
-	strbuf_addf(&cached_rc, "%s/rc-%8lx", ctx.cfg.cache_root, hash);
-
-	if (stat(cached_rc.buf, &st)) {
-		/* Nothing is cached, we need to scan without forking. And
-		 * if we fail to generate a cached repolist, we need to
-		 * invoke scan_tree manually.
-		 */
-		if (generate_cached_repolist(path, cached_rc.buf)) {
-			if (ctx.cfg.project_list)
-				scan_projects(path, ctx.cfg.project_list,
-					      repo_config);
-			else
-				scan_tree(path, repo_config);
-		}
-		goto out;
-	}
-
-	parse_configfile(cached_rc.buf, config_cb);
-
-	/* If the cached configfile hasn't expired, lets exit now */
-	age = time(NULL) - st.st_mtime;
-	if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
-		goto out;
-
-	/* The cached repolist has been parsed, but it was old. So lets
-	 * rescan the specified path and generate a new cached repolist
-	 * in a child-process to avoid latency for the current request.
-	 */
-	if (fork())
-		goto out;
-
-	exit(generate_cached_repolist(path, cached_rc.buf));
-out:
-	strbuf_release(&cached_rc);
-}
-
-static void cgit_parse_args(int argc, const char **argv)
-{
-	int i;
-	const char *arg;
-	int scan = 0;
-
-	for (i = 1; i < argc; i++) {
-		if (!strcmp(argv[i], "--version")) {
-			printf("CGit %s | https://git.zx2c4.com/cgit/\n\nCompiled in features:\n", CGIT_VERSION);
-#ifdef NO_LUA
-			printf("[-] ");
-#else
-			printf("[+] ");
-#endif
-			printf("Lua scripting\n");
-#ifndef HAVE_LINUX_SENDFILE
-			printf("[-] ");
-#else
-			printf("[+] ");
-#endif
-			printf("Linux sendfile() usage\n");
-
-			exit(0);
-		}
-		if (skip_prefix(argv[i], "--cache=", &arg)) {
-			ctx.cfg.cache_root = xstrdup(arg);
-		} else if (!strcmp(argv[i], "--nohttp")) {
-			ctx.env.no_http = "1";
-		} else if (skip_prefix(argv[i], "--query=", &arg)) {
-			ctx.qry.raw = xstrdup(arg);
-		} else if (skip_prefix(argv[i], "--repo=", &arg)) {
-			ctx.qry.repo = xstrdup(arg);
-		} else if (skip_prefix(argv[i], "--page=", &arg)) {
-			ctx.qry.page = xstrdup(arg);
-		} else if (skip_prefix(argv[i], "--head=", &arg)) {
-			ctx.qry.head = xstrdup(arg);
-			ctx.qry.has_symref = 1;
-		} else if (skip_prefix(argv[i], "--sha1=", &arg)) {
-			ctx.qry.sha1 = xstrdup(arg);
-			ctx.qry.has_sha1 = 1;
-		} else if (skip_prefix(argv[i], "--ofs=", &arg)) {
-			ctx.qry.ofs = atoi(arg);
-		} else if (skip_prefix(argv[i], "--scan-tree=", &arg) ||
-		           skip_prefix(argv[i], "--scan-path=", &arg)) {
-			/*
-			 * HACK: The global snapshot bit mask defines the set
-			 * of allowed snapshot formats, but the config file
-			 * hasn't been parsed yet so the mask is currently 0.
-			 * By setting all bits high before scanning we make
-			 * sure that any in-repo cgitrc snapshot setting is
-			 * respected by scan_tree().
-			 *
-			 * NOTE: We assume that there aren't more than 8
-			 * different snapshot formats supported by cgit...
-			 */
-			ctx.cfg.snapshots = 0xFF;
-			scan++;
-			scan_tree(arg, repo_config);
-		}
-	}
-	if (scan) {
-		qsort(cgit_repolist.repos, cgit_repolist.count,
-			sizeof(struct cgit_repo), cmp_repos);
-		print_repolist(stdout, &cgit_repolist, 0);
-		exit(0);
-	}
-}
-
-static int calc_ttl(void)
-{
-	if (!ctx.repo)
-		return ctx.cfg.cache_root_ttl;
-
-	if (!ctx.qry.page)
-		return ctx.cfg.cache_repo_ttl;
-
-	if (!strcmp(ctx.qry.page, "about"))
-		return ctx.cfg.cache_about_ttl;
-
-	if (!strcmp(ctx.qry.page, "snapshot"))
-		return ctx.cfg.cache_snapshot_ttl;
-
-	if (ctx.qry.has_sha1)
-		return ctx.cfg.cache_static_ttl;
-
-	if (ctx.qry.has_symref)
-		return ctx.cfg.cache_dynamic_ttl;
-
-	return ctx.cfg.cache_repo_ttl;
-}
-
-int cmd_main(int argc, const char **argv)
-{
-	const char *path;
-	int err, ttl;
-
-	cgit_init_filters();
-	atexit(cgit_cleanup_filters);
-
-	prepare_context();
-	cgit_repolist.length = 0;
-	cgit_repolist.count = 0;
-	cgit_repolist.repos = NULL;
-
-	cgit_parse_args(argc, argv);
-	parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
-	ctx.repo = NULL;
-	http_parse_querystring(ctx.qry.raw, querystring_cb);
-
-	/* If virtual-root isn't specified in cgitrc, lets pretend
-	 * that virtual-root equals SCRIPT_NAME, minus any possibly
-	 * trailing slashes.
-	 */
-	if (!ctx.cfg.virtual_root && ctx.cfg.script_name)
-		ctx.cfg.virtual_root = ensure_end(ctx.cfg.script_name, '/');
-
-	/* If no url parameter is specified on the querystring, lets
-	 * use PATH_INFO as url. This allows cgit to work with virtual
-	 * urls without the need for rewriterules in the webserver (as
-	 * long as PATH_INFO is included in the cache lookup key).
-	 */
-	path = ctx.env.path_info;
-	if (!ctx.qry.url && path) {
-		if (path[0] == '/')
-			path++;
-		ctx.qry.url = xstrdup(path);
-		if (ctx.qry.raw) {
-			char *newqry = fmtalloc("%s?%s", path, ctx.qry.raw);
-			free(ctx.qry.raw);
-			ctx.qry.raw = newqry;
-		} else
-			ctx.qry.raw = xstrdup(ctx.qry.url);
-		cgit_parse_url(ctx.qry.url);
-	}
-
-	/* Before we go any further, we set ctx.env.authenticated by checking to see
-	 * if the supplied cookie is valid. All cookies are valid if there is no
-	 * auth_filter. If there is an auth_filter, the filter decides. */
-	authenticate_cookie();
-
-	ttl = calc_ttl();
-	if (ttl < 0)
-		ctx.page.expires += 10 * 365 * 24 * 60 * 60; /* 10 years */
-	else
-		ctx.page.expires += ttl * 60;
-	if (!ctx.env.authenticated || (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")))
-		ctx.cfg.cache_size = 0;
-	err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
-			    ctx.qry.raw, ttl, process_request);
-	cgit_cleanup_filters();
-	if (err)
-		cgit_print_error("Error processing page: %s (%d)",
-				 strerror(err), err);
-	return err;
-}
diff --git a/cgit.css b/cgit.css
deleted file mode 100644
index d4aadbfa10..0000000000
--- a/cgit.css
+++ /dev/null
@@ -1,895 +0,0 @@
-div#cgit {
-	padding: 0em;
-	margin: 0em;
-	font-family: sans-serif;
-	font-size: 10pt;
-	color: #333;
-	background: white;
-	padding: 4px;
-}
-
-div#cgit a {
-	color: blue;
-	text-decoration: none;
-}
-
-div#cgit a:hover {
-	text-decoration: underline;
-}
-
-div#cgit table {
-	border-collapse: collapse;
-}
-
-div#cgit table#header {
-	width: 100%;
-	margin-bottom: 1em;
-}
-
-div#cgit table#header td.logo {
-	width: 96px;
-	vertical-align: top;
-}
-
-div#cgit table#header td.main {
-	font-size: 250%;
-	padding-left: 10px;
-	white-space: nowrap;
-}
-
-div#cgit table#header td.main a {
-	color: #000;
-}
-
-div#cgit table#header td.form {
-	text-align: right;
-	vertical-align: bottom;
-	padding-right: 1em;
-	padding-bottom: 2px;
-	white-space: nowrap;
-}
-
-div#cgit table#header td.form form,
-div#cgit table#header td.form input,
-div#cgit table#header td.form select {
-	font-size: 90%;
-}
-
-div#cgit table#header td.sub {
-	color: #777;
-	border-top: solid 1px #ccc;
-	padding-left: 10px;
-}
-
-div#cgit table.tabs {
-	border-bottom: solid 3px #ccc;
-	border-collapse: collapse;
-	margin-top: 2em;
-	margin-bottom: 0px;
-	width: 100%;
-}
-
-div#cgit table.tabs td {
-	padding: 0px 1em;
-	vertical-align: bottom;
-}
-
-div#cgit table.tabs td a {
-	padding: 2px 0.75em;
-	color: #777;
-	font-size: 110%;
-}
-
-div#cgit table.tabs td a.active {
-	color: #000;
-	background-color: #ccc;
-}
-
-div#cgit table.tabs a[href^="http://"]:after, div#cgit table.tabs a[href^="https://"]:after {
-	content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhcJDQY+gm2TAAAAHWlUWHRDb21tZW50AAAAAABDcmVhdGVkIHdpdGggR0lNUGQuZQcAAABbSURBVAhbY2BABs4MU4CwhYHBh2Erww4wrGFQZHjI8B8IgUIscJWyDHcggltQhI4zGDCcRwhChPggHIggP1QoAVmQkSETrGoHsiAEsACtBYN0oDAMbgU6EBcAAL2eHUt4XUU4AAAAAElFTkSuQmCC);
-	opacity: 0.5;
-	margin: 0 0 0 5px;
-}
-
-div#cgit table.tabs td.form {
-	text-align: right;
-}
-
-div#cgit table.tabs td.form form {
-	padding-bottom: 2px;
-	font-size: 90%;
-	white-space: nowrap;
-}
-
-div#cgit table.tabs td.form input,
-div#cgit table.tabs td.form select {
-	font-size: 90%;
-}
-
-div#cgit div.path {
-	margin: 0px;
-	padding: 5px 2em 2px 2em;
-	color: #000;
-	background-color: #eee;
-}
-
-div#cgit div.content {
-	margin: 0px;
-	padding: 2em;
-	border-bottom: solid 3px #ccc;
-}
-
-
-div#cgit table.list {
-	width: 100%;
-	border: none;
-	border-collapse: collapse;
-}
-
-div#cgit table.list tr {
-	background: white;
-}
-
-div#cgit table.list tr.logheader {
-	background: #eee;
-}
-
-div#cgit table.list tr:nth-child(even) {
-	background: #f7f7f7;
-}
-
-div#cgit table.list tr:nth-child(odd) {
-	background: white;
-}
-
-div#cgit table.list tr:hover {
-	background: #eee;
-}
-
-div#cgit table.list tr.nohover {
-	background: white;
-}
-
-div#cgit table.list tr.nohover:hover {
-	background: white;
-}
-
-div#cgit table.list tr.nohover-highlight:hover:nth-child(even) {
-	background: #f7f7f7;
-}
-
-div#cgit table.list tr.nohover-highlight:hover:nth-child(odd) {
-	background: white;
-}
-
-div#cgit table.list th {
-	font-weight: bold;
-	/* color: #888;
-	border-top: dashed 1px #888;
-	border-bottom: dashed 1px #888;
-	*/
-	padding: 0.1em 0.5em 0.05em 0.5em;
-	vertical-align: baseline;
-}
-
-div#cgit table.list td {
-	border: none;
-	padding: 0.1em 0.5em 0.1em 0.5em;
-}
-
-div#cgit table.list td.commitgraph {
-	font-family: monospace;
-	white-space: pre;
-}
-
-div#cgit table.list td.commitgraph .column1 {
-	color: #a00;
-}
-
-div#cgit table.list td.commitgraph .column2 {
-	color: #0a0;
-}
-
-div#cgit table.list td.commitgraph .column3 {
-	color: #aa0;
-}
-
-div#cgit table.list td.commitgraph .column4 {
-	color: #00a;
-}
-
-div#cgit table.list td.commitgraph .column5 {
-	color: #a0a;
-}
-
-div#cgit table.list td.commitgraph .column6 {
-	color: #0aa;
-}
-
-div#cgit table.list td.logsubject {
-	font-family: monospace;
-	font-weight: bold;
-}
-
-div#cgit table.list td.logmsg {
-	font-family: monospace;
-	white-space: pre;
-	padding: 0 0.5em;
-}
-
-div#cgit table.list td a {
-	color: black;
-}
-
-div#cgit table.list td a.ls-dir {
-	font-weight: bold;
-	color: #00f;
-}
-
-div#cgit table.list td a:hover {
-	color: #00f;
-}
-
-div#cgit img {
-	border: none;
-}
-
-div#cgit input#switch-btn {
-	margin: 2px 0px 0px 0px;
-}
-
-div#cgit td#sidebar input.txt {
-	width: 100%;
-	margin: 2px 0px 0px 0px;
-}
-
-div#cgit table#grid {
-	margin: 0px;
-}
-
-div#cgit td#content {
-	vertical-align: top;
-	padding: 1em 2em 1em 1em;
-	border: none;
-}
-
-div#cgit div#summary {
-	vertical-align: top;
-	margin-bottom: 1em;
-}
-
-div#cgit table#downloads {
-	float: right;
-	border-collapse: collapse;
-	border: solid 1px #777;
-	margin-left: 0.5em;
-	margin-bottom: 0.5em;
-}
-
-div#cgit table#downloads th {
-	background-color: #ccc;
-}
-
-div#cgit div#blob {
-	border: solid 1px black;
-}
-
-div#cgit div.error {
-	color: red;
-	font-weight: bold;
-	margin: 1em 2em;
-}
-
-div#cgit a.ls-blob, div#cgit a.ls-dir, div#cgit .ls-mod {
-	font-family: monospace;
-}
-
-div#cgit td.ls-size {
-	text-align: right;
-	font-family: monospace;
-	width: 10em;
-}
-
-div#cgit td.ls-mode {
-	font-family: monospace;
-	width: 10em;
-}
-
-div#cgit table.blob {
-	margin-top: 0.5em;
-	border-top: solid 1px black;
-}
-
-div#cgit table.blob td.hashes,
-div#cgit table.blob td.lines {
-	margin: 0; padding: 0 0 0 0.5em;
-	vertical-align: top;
-	color: black;
-}
-
-div#cgit table.blob td.linenumbers {
-	margin: 0; padding: 0 0.5em 0 0.5em;
-	vertical-align: top;
-	text-align: right;
-	border-right: 1px solid gray;
-}
-
-div#cgit table.blob pre {
-	padding: 0; margin: 0;
-}
-
-div#cgit table.blob td.linenumbers a,
-div#cgit table.ssdiff td.lineno a {
-	color: gray;
-	text-align: right;
-	text-decoration: none;
-}
-
-div#cgit table.blob td.linenumbers a:hover,
-div#cgit table.ssdiff td.lineno a:hover {
-	color: black;
-}
-
-div#cgit table.blame td.hashes,
-div#cgit table.blame td.lines,
-div#cgit table.blame td.linenumbers {
-	padding: 0;
-}
-
-div#cgit table.blame td.hashes div.alt,
-div#cgit table.blame td.lines div.alt {
-	padding: 0 0.5em 0 0.5em;
-}
-
-div#cgit table.blame td.linenumbers div.alt {
-	padding: 0 0.5em 0 0;
-}
-
-div#cgit table.blame div.alt:nth-child(even) {
-	background: #eee;
-}
-
-div#cgit table.blame div.alt:nth-child(odd) {
-	background: white;
-}
-
-div#cgit table.blame td.lines > div {
-	position: relative;
-}
-
-div#cgit table.blame td.lines > div > pre {
-	padding: 0 0 0 0.5em;
-	position: absolute;
-	top: 0;
-}
-
-div#cgit table.bin-blob {
-	margin-top: 0.5em;
-	border: solid 1px black;
-}
-
-div#cgit table.bin-blob th {
-	font-family: monospace;
-	white-space: pre;
-	border: solid 1px #777;
-	padding: 0.5em 1em;
-}
-
-div#cgit table.bin-blob td {
-	font-family: monospace;
-	white-space: pre;
-	border-left: solid 1px #777;
-	padding: 0em 1em;
-}
-
-div#cgit table.nowrap td {
-	white-space: nowrap;
-}
-
-div#cgit table.commit-info {
-	border-collapse: collapse;
-	margin-top: 1.5em;
-}
-
-div#cgit div.cgit-panel {
-	float: right;
-	margin-top: 1.5em;
-}
-
-div#cgit div.cgit-panel table {
-	border-collapse: collapse;
-	border: solid 1px #aaa;
-	background-color: #eee;
-}
-
-div#cgit div.cgit-panel th {
-	text-align: center;
-}
-
-div#cgit div.cgit-panel td {
-	padding: 0.25em 0.5em;
-}
-
-div#cgit div.cgit-panel td.label {
-	padding-right: 0.5em;
-}
-
-div#cgit div.cgit-panel td.ctrl {
-	padding-left: 0.5em;
-}
-
-div#cgit table.commit-info th {
-	text-align: left;
-	font-weight: normal;
-	padding: 0.1em 1em 0.1em 0.1em;
-	vertical-align: top;
-}
-
-div#cgit table.commit-info td {
-	font-weight: normal;
-	padding: 0.1em 1em 0.1em 0.1em;
-}
-
-div#cgit div.commit-subject {
-	font-weight: bold;
-	font-size: 125%;
-	margin: 1.5em 0em 0.5em 0em;
-	padding: 0em;
-}
-
-div#cgit div.commit-msg {
-	white-space: pre;
-	font-family: monospace;
-}
-
-div#cgit div.notes-header {
-	font-weight: bold;
-	padding-top: 1.5em;
-}
-
-div#cgit div.notes {
-	white-space: pre;
-	font-family: monospace;
-	border: solid 1px #ee9;
-	background-color: #ffd;
-	padding: 0.3em 2em 0.3em 1em;
-	float: left;
-}
-
-div#cgit div.notes-footer {
-	clear: left;
-}
-
-div#cgit div.diffstat-header {
-	font-weight: bold;
-	padding-top: 1.5em;
-}
-
-div#cgit table.diffstat {
-	border-collapse: collapse;
-	border: solid 1px #aaa;
-	background-color: #eee;
-}
-
-div#cgit table.diffstat th {
-	font-weight: normal;
-	text-align: left;
-	text-decoration: underline;
-	padding: 0.1em 1em 0.1em 0.1em;
-	font-size: 100%;
-}
-
-div#cgit table.diffstat td {
-	padding: 0.2em 0.2em 0.1em 0.1em;
-	font-size: 100%;
-	border: none;
-}
-
-div#cgit table.diffstat td.mode {
-	white-space: nowrap;
-}
-
-div#cgit table.diffstat td span.modechange {
-	padding-left: 1em;
-	color: red;
-}
-
-div#cgit table.diffstat td.add a {
-	color: green;
-}
-
-div#cgit table.diffstat td.del a {
-	color: red;
-}
-
-div#cgit table.diffstat td.upd a {
-	color: blue;
-}
-
-div#cgit table.diffstat td.graph {
-	width: 500px;
-	vertical-align: middle;
-}
-
-div#cgit table.diffstat td.graph table {
-	border: none;
-}
-
-div#cgit table.diffstat td.graph td {
-	padding: 0px;
-	border: 0px;
-	height: 7pt;
-}
-
-div#cgit table.diffstat td.graph td.add {
-	background-color: #5c5;
-}
-
-div#cgit table.diffstat td.graph td.rem {
-	background-color: #c55;
-}
-
-div#cgit div.diffstat-summary {
-	color: #888;
-	padding-top: 0.5em;
-}
-
-div#cgit table.diff {
-	width: 100%;
-}
-
-div#cgit table.diff td {
-	font-family: monospace;
-	white-space: pre;
-}
-
-div#cgit table.diff td div.head {
-	font-weight: bold;
-	margin-top: 1em;
-	color: black;
-}
-
-div#cgit table.diff td div.hunk {
-	color: #009;
-}
-
-div#cgit table.diff td div.add {
-	color: green;
-}
-
-div#cgit table.diff td div.del {
-	color: red;
-}
-
-div#cgit .sha1 {
-	font-family: monospace;
-	font-size: 90%;
-}
-
-div#cgit .left {
-	text-align: left;
-}
-
-div#cgit .right {
-	text-align: right;
-}
-
-div#cgit table.list td.reposection {
-	font-style: italic;
-	color: #888;
-}
-
-div#cgit a.button {
-	font-size: 80%;
-	padding: 0em 0.5em;
-}
-
-div#cgit a.primary {
-	font-size: 100%;
-}
-
-div#cgit a.secondary {
-	font-size: 90%;
-}
-
-div#cgit td.toplevel-repo {
-
-}
-
-div#cgit table.list td.sublevel-repo {
-	padding-left: 1.5em;
-}
-
-div#cgit ul.pager {
-	list-style-type: none;
-	text-align: center;
-	margin: 1em 0em 0em 0em;
-	padding: 0;
-}
-
-div#cgit ul.pager li {
-	display: inline-block;
-	margin: 0.25em 0.5em;
-}
-
-div#cgit ul.pager a {
-	color: #777;
-}
-
-div#cgit ul.pager .current {
-	font-weight: bold;
-}
-
-div#cgit span.age-mins {
-	font-weight: bold;
-	color: #080;
-}
-
-div#cgit span.age-hours {
-	color: #080;
-}
-
-div#cgit span.age-days {
-	color: #040;
-}
-
-div#cgit span.age-weeks {
-	color: #444;
-}
-
-div#cgit span.age-months {
-	color: #888;
-}
-
-div#cgit span.age-years {
-	color: #bbb;
-}
-
-div#cgit span.insertions {
-	color: #080;
-}
-
-div#cgit span.deletions {
-	color: #800;
-}
-
-div#cgit div.footer {
-	margin-top: 0.5em;
-	text-align: center;
-	font-size: 80%;
-	color: #ccc;
-}
-
-div#cgit div.footer a {
-	color: #ccc;
-	text-decoration: none;
-}
-
-div#cgit div.footer a:hover {
-	text-decoration: underline;
-}
-
-div#cgit a.branch-deco {
-	color: #000;
-	margin: 0px 0.5em;
-	padding: 0px 0.25em;
-	background-color: #88ff88;
-	border: solid 1px #007700;
-}
-
-div#cgit a.tag-deco {
-	color: #000;
-	margin: 0px 0.5em;
-	padding: 0px 0.25em;
-	background-color: #ffff88;
-	border: solid 1px #777700;
-}
-
-div#cgit a.tag-annotated-deco {
-	color: #000;
-	margin: 0px 0.5em;
-	padding: 0px 0.25em;
-	background-color: #ffcc88;
-	border: solid 1px #777700;
-}
-
-div#cgit a.remote-deco {
-	color: #000;
-	margin: 0px 0.5em;
-	padding: 0px 0.25em;
-	background-color: #ccccff;
-	border: solid 1px #000077;
-}
-
-div#cgit a.deco {
-	color: #000;
-	margin: 0px 0.5em;
-	padding: 0px 0.25em;
-	background-color: #ff8888;
-	border: solid 1px #770000;
-}
-
-div#cgit div.commit-subject a.branch-deco,
-div#cgit div.commit-subject a.tag-deco,
-div#cgit div.commit-subject a.tag-annotated-deco,
-div#cgit div.commit-subject a.remote-deco,
-div#cgit div.commit-subject a.deco {
-	margin-left: 1em;
-	font-size: 75%;
-}
-
-div#cgit table.stats {
-	border: solid 1px black;
-	border-collapse: collapse;
-}
-
-div#cgit table.stats th {
-	text-align: left;
-	padding: 1px 0.5em;
-	background-color: #eee;
-	border: solid 1px black;
-}
-
-div#cgit table.stats td {
-	text-align: right;
-	padding: 1px 0.5em;
-	border: solid 1px black;
-}
-
-div#cgit table.stats td.total {
-	font-weight: bold;
-	text-align: left;
-}
-
-div#cgit table.stats td.sum {
-	color: #c00;
-	font-weight: bold;
-/*	background-color: #eee; */
-}
-
-div#cgit table.stats td.left {
-	text-align: left;
-}
-
-div#cgit table.vgraph {
-	border-collapse: separate;
-	border: solid 1px black;
-	height: 200px;
-}
-
-div#cgit table.vgraph th {
-	background-color: #eee;
-	font-weight: bold;
-	border: solid 1px white;
-	padding: 1px 0.5em;
-}
-
-div#cgit table.vgraph td {
-	vertical-align: bottom;
-	padding: 0px 10px;
-}
-
-div#cgit table.vgraph div.bar {
-	background-color: #eee;
-}
-
-div#cgit table.hgraph {
-	border: solid 1px black;
-	width: 800px;
-}
-
-div#cgit table.hgraph th {
-	background-color: #eee;
-	font-weight: bold;
-	border: solid 1px black;
-	padding: 1px 0.5em;
-}
-
-div#cgit table.hgraph td {
-	vertical-align: middle;
-	padding: 2px 2px;
-}
-
-div#cgit table.hgraph div.bar {
-	background-color: #eee;
-	height: 1em;
-}
-
-div#cgit table.ssdiff {
-	width: 100%;
-}
-
-div#cgit table.ssdiff td {
-	font-size: 75%;
-	font-family: monospace;
-	white-space: pre;
-	padding: 1px 4px 1px 4px;
-	border-left: solid 1px #aaa;
-	border-right: solid 1px #aaa;
-}
-
-div#cgit table.ssdiff td.add {
-	color: black;
-	background: #cfc;
-	min-width: 50%;
-}
-
-div#cgit table.ssdiff td.add_dark {
-	color: black;
-	background: #aca;
-	min-width: 50%;
-}
-
-div#cgit table.ssdiff span.add {
-	background: #cfc;
-	font-weight: bold;
-}
-
-div#cgit table.ssdiff td.del {
-	color: black;
-	background: #fcc;
-	min-width: 50%;
-}
-
-div#cgit table.ssdiff td.del_dark {
-	color: black;
-	background: #caa;
-	min-width: 50%;
-}
-
-div#cgit table.ssdiff span.del {
-	background: #fcc;
-	font-weight: bold;
-}
-
-div#cgit table.ssdiff td.changed {
-	color: black;
-	background: #ffc;
-	min-width: 50%;
-}
-
-div#cgit table.ssdiff td.changed_dark {
-	color: black;
-	background: #cca;
-	min-width: 50%;
-}
-
-div#cgit table.ssdiff td.lineno {
-	color: black;
-	background: #eee;
-	text-align: right;
-	width: 3em;
-	min-width: 3em;
-}
-
-div#cgit table.ssdiff td.hunk {
-	color: black;
-	background: #ccf;
-	border-top: solid 1px #aaa;
-	border-bottom: solid 1px #aaa;
-}
-
-div#cgit table.ssdiff td.head {
-	border-top: solid 1px #aaa;
-	border-bottom: solid 1px #aaa;
-}
-
-div#cgit table.ssdiff td.head div.head {
-	font-weight: bold;
-	color: black;
-}
-
-div#cgit table.ssdiff td.foot {
-	border-top: solid 1px #aaa;
-	border-left: none;
-	border-right: none;
-	border-bottom: none;
-}
-
-div#cgit table.ssdiff td.space {
-	border: none;
-}
-
-div#cgit table.ssdiff td.space div {
-	min-height: 3em;
-}
diff --git a/cgit.h b/cgit.h
deleted file mode 100644
index 7ec46b4846..0000000000
--- a/cgit.h
+++ /dev/null
@@ -1,398 +0,0 @@
-#ifndef CGIT_H
-#define CGIT_H
-
-
-#include <git-compat-util.h>
-#include <stdbool.h>
-
-#include <cache.h>
-#include <grep.h>
-#include <object.h>
-#include <object-store.h>
-#include <tree.h>
-#include <commit.h>
-#include <tag.h>
-#include <diff.h>
-#include <diffcore.h>
-#include <argv-array.h>
-#include <refs.h>
-#include <revision.h>
-#include <log-tree.h>
-#include <archive.h>
-#include <string-list.h>
-#include <xdiff-interface.h>
-#include <xdiff/xdiff.h>
-#include <utf8.h>
-#include <notes.h>
-#include <graph.h>
-
-/* Add isgraph(x) to Git's sane ctype support (see git-compat-util.h) */
-#undef isgraph
-#define isgraph(x) (isprint((x)) && !isspace((x)))
-
-
-/*
- * Limits used for relative dates
- */
-#define TM_MIN    60
-#define TM_HOUR  (TM_MIN * 60)
-#define TM_DAY   (TM_HOUR * 24)
-#define TM_WEEK  (TM_DAY * 7)
-#define TM_YEAR  (TM_DAY * 365)
-#define TM_MONTH (TM_YEAR / 12.0)
-
-
-/*
- * Default encoding
- */
-#define PAGE_ENCODING "UTF-8"
-
-#define BIT(x)	(1U << (x))
-
-typedef void (*configfn)(const char *name, const char *value);
-typedef void (*filepair_fn)(struct diff_filepair *pair);
-typedef void (*linediff_fn)(char *line, int len);
-
-typedef enum {
-	DIFF_UNIFIED, DIFF_SSDIFF, DIFF_STATONLY
-} diff_type;
-
-typedef enum {
-	ABOUT, COMMIT, SOURCE, EMAIL, AUTH, OWNER
-} filter_type;
-
-struct cgit_filter {
-	int (*open)(struct cgit_filter *, va_list ap);
-	int (*close)(struct cgit_filter *);
-	void (*fprintf)(struct cgit_filter *, FILE *, const char *prefix);
-	void (*cleanup)(struct cgit_filter *);
-	int argument_count;
-};
-
-struct cgit_exec_filter {
-	struct cgit_filter base;
-	char *cmd;
-	char **argv;
-	int old_stdout;
-	int pid;
-};
-
-struct cgit_repo {
-	char *url;
-	char *name;
-	char *path;
-	char *desc;
-	char *extra_head_content;
-	char *owner;
-	char *homepage;
-	char *defbranch;
-	char *module_link;
-	struct string_list readme;
-	char *section;
-	char *clone_url;
-	char *logo;
-	char *logo_link;
-	char *snapshot_prefix;
-	int snapshots;
-	int enable_blame;
-	int enable_commit_graph;
-	int enable_log_filecount;
-	int enable_log_linecount;
-	int enable_remote_branches;
-	int enable_subject_links;
-	int enable_html_serving;
-	int max_stats;
-	int branch_sort;
-	int commit_sort;
-	time_t mtime;
-	struct cgit_filter *about_filter;
-	struct cgit_filter *commit_filter;
-	struct cgit_filter *source_filter;
-	struct cgit_filter *email_filter;
-	struct cgit_filter *owner_filter;
-	struct string_list submodules;
-	int hide;
-	int ignore;
-};
-
-typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
-	      const char *value);
-
-struct cgit_repolist {
-	int length;
-	int count;
-	struct cgit_repo *repos;
-};
-
-struct commitinfo {
-	struct commit *commit;
-	char *author;
-	char *author_email;
-	unsigned long author_date;
-	int author_tz;
-	char *committer;
-	char *committer_email;
-	unsigned long committer_date;
-	int committer_tz;
-	char *subject;
-	char *msg;
-	char *msg_encoding;
-};
-
-struct taginfo {
-	char *tagger;
-	char *tagger_email;
-	unsigned long tagger_date;
-	int tagger_tz;
-	char *msg;
-};
-
-struct refinfo {
-	const char *refname;
-	struct object *object;
-	union {
-		struct taginfo *tag;
-		struct commitinfo *commit;
-	};
-};
-
-struct reflist {
-	struct refinfo **refs;
-	int alloc;
-	int count;
-};
-
-struct cgit_query {
-	int has_symref;
-	int has_sha1;
-	int has_difftype;
-	char *raw;
-	char *repo;
-	char *page;
-	char *search;
-	char *grep;
-	char *head;
-	char *sha1;
-	char *sha2;
-	char *path;
-	char *name;
-	char *url;
-	char *period;
-	int   ofs;
-	int nohead;
-	char *sort;
-	int showmsg;
-	diff_type difftype;
-	int show_all;
-	int context;
-	int ignorews;
-	int follow;
-	char *vpath;
-};
-
-struct cgit_config {
-	char *agefile;
-	char *cache_root;
-	char *clone_prefix;
-	char *clone_url;
-	char *css;
-	char *favicon;
-	char *footer;
-	char *head_include;
-	char *header;
-	char *logo;
-	char *logo_link;
-	char *mimetype_file;
-	char *module_link;
-	char *project_list;
-	struct string_list readme;
-	char *robots;
-	char *root_title;
-	char *root_desc;
-	char *root_readme;
-	char *script_name;
-	char *section;
-	char *repository_sort;
-	char *virtual_root;	/* Always ends with '/'. */
-	char *strict_export;
-	int cache_size;
-	int cache_dynamic_ttl;
-	int cache_max_create_time;
-	int cache_repo_ttl;
-	int cache_root_ttl;
-	int cache_scanrc_ttl;
-	int cache_static_ttl;
-	int cache_about_ttl;
-	int cache_snapshot_ttl;
-	int case_sensitive_sort;
-	int embedded;
-	int enable_filter_overrides;
-	int enable_follow_links;
-	int enable_http_clone;
-	int enable_index_links;
-	int enable_index_owner;
-	int enable_blame;
-	int enable_commit_graph;
-	int enable_log_filecount;
-	int enable_log_linecount;
-	int enable_remote_branches;
-	int enable_subject_links;
-	int enable_html_serving;
-	int enable_tree_linenumbers;
-	int enable_git_config;
-	int local_time;
-	int max_atom_items;
-	int max_repo_count;
-	int max_commit_count;
-	int max_lock_attempts;
-	int max_msg_len;
-	int max_repodesc_len;
-	int max_blob_size;
-	int max_stats;
-	int noplainemail;
-	int noheader;
-	int renamelimit;
-	int remove_suffix;
-	int scan_hidden_path;
-	int section_from_path;
-	int snapshots;
-	int section_sort;
-	int summary_branches;
-	int summary_log;
-	int summary_tags;
-	diff_type difftype;
-	int branch_sort;
-	int commit_sort;
-	struct string_list mimetypes;
-	struct cgit_filter *about_filter;
-	struct cgit_filter *commit_filter;
-	struct cgit_filter *source_filter;
-	struct cgit_filter *email_filter;
-	struct cgit_filter *owner_filter;
-	struct cgit_filter *auth_filter;
-};
-
-struct cgit_page {
-	time_t modified;
-	time_t expires;
-	size_t size;
-	const char *mimetype;
-	const char *charset;
-	const char *filename;
-	const char *etag;
-	const char *title;
-	int status;
-	const char *statusmsg;
-};
-
-struct cgit_environment {
-	const char *cgit_config;
-	const char *http_host;
-	const char *https;
-	const char *no_http;
-	const char *path_info;
-	const char *query_string;
-	const char *request_method;
-	const char *script_name;
-	const char *server_name;
-	const char *server_port;
-	const char *http_cookie;
-	const char *http_referer;
-	unsigned int content_length;
-	int authenticated;
-};
-
-struct cgit_context {
-	struct cgit_environment env;
-	struct cgit_query qry;
-	struct cgit_config cfg;
-	struct cgit_repo *repo;
-	struct cgit_page page;
-};
-
-typedef int (*write_archive_fn_t)(const char *, const char *);
-
-struct cgit_snapshot_format {
-	const char *suffix;
-	const char *mimetype;
-	write_archive_fn_t write_func;
-};
-
-extern const char *cgit_version;
-
-extern struct cgit_repolist cgit_repolist;
-extern struct cgit_context ctx;
-extern const struct cgit_snapshot_format cgit_snapshot_formats[];
-
-extern char *cgit_default_repo_desc;
-extern struct cgit_repo *cgit_add_repo(const char *url);
-extern struct cgit_repo *cgit_get_repoinfo(const char *url);
-extern void cgit_repo_config_cb(const char *name, const char *value);
-
-extern int chk_zero(int result, char *msg);
-extern int chk_positive(int result, char *msg);
-extern int chk_non_negative(int result, char *msg);
-
-extern char *trim_end(const char *str, char c);
-extern char *ensure_end(const char *str, char c);
-
-extern void strbuf_ensure_end(struct strbuf *sb, char c);
-
-extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
-extern void cgit_free_reflist_inner(struct reflist *list);
-extern int cgit_refs_cb(const char *refname, const struct object_id *oid,
-			int flags, void *cb_data);
-
-extern void cgit_free_commitinfo(struct commitinfo *info);
-extern void cgit_free_taginfo(struct taginfo *info);
-
-void cgit_diff_tree_cb(struct diff_queue_struct *q,
-		       struct diff_options *options, void *data);
-
-extern int cgit_diff_files(const struct object_id *old_oid,
-			   const struct object_id *new_oid,
-			   unsigned long *old_size, unsigned long *new_size,
-			   int *binary, int context, int ignorews,
-			   linediff_fn fn);
-
-extern void cgit_diff_tree(const struct object_id *old_oid,
-			   const struct object_id *new_oid,
-			   filepair_fn fn, const char *prefix, int ignorews);
-
-extern void cgit_diff_commit(struct commit *commit, filepair_fn fn,
-			     const char *prefix);
-
-__attribute__((format (printf,1,2)))
-extern char *fmt(const char *format,...);
-
-__attribute__((format (printf,1,2)))
-extern char *fmtalloc(const char *format,...);
-
-extern struct commitinfo *cgit_parse_commit(struct commit *commit);
-extern struct taginfo *cgit_parse_tag(struct tag *tag);
-extern void cgit_parse_url(const char *url);
-
-extern const char *cgit_repobasename(const char *reponame);
-
-extern int cgit_parse_snapshots_mask(const char *str);
-extern const struct object_id *cgit_snapshot_get_sig(const char *ref,
-						     const struct cgit_snapshot_format *f);
-extern const unsigned cgit_snapshot_format_bit(const struct cgit_snapshot_format *f);
-
-extern int cgit_open_filter(struct cgit_filter *filter, ...);
-extern int cgit_close_filter(struct cgit_filter *filter);
-extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix);
-extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv);
-extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype);
-extern void cgit_cleanup_filters(void);
-extern void cgit_init_filters(void);
-
-extern void cgit_prepare_repo_env(struct cgit_repo * repo);
-
-extern int readfile(const char *path, char **buf, size_t *size);
-
-extern char *expand_macros(const char *txt);
-
-extern char *get_mimetype_for_filename(const char *filename);
-
-#endif /* CGIT_H */
diff --git a/cgit.mk b/cgit.mk
deleted file mode 100644
index 3fcc1ca314..0000000000
--- a/cgit.mk
+++ /dev/null
@@ -1,141 +0,0 @@
-# This Makefile is run in the "git" directory in order to re-use Git's
-# build variables and operating system detection.  Hence all files in
-# CGit's directory must be prefixed with "../".
-include Makefile
-
-CGIT_PREFIX = ../
-
--include $(CGIT_PREFIX)cgit.conf
-
-# The CGIT_* variables are inherited when this file is called from the
-# main Makefile - they are defined there.
-
-$(CGIT_PREFIX)VERSION: force-version
-	@cd $(CGIT_PREFIX) && '$(SHELL_PATH_SQ)' ./gen-version.sh "$(CGIT_VERSION)"
--include $(CGIT_PREFIX)VERSION
-.PHONY: force-version
-
-# CGIT_CFLAGS is a separate variable so that we can track it separately
-# and avoid rebuilding all of Git when these variables change.
-CGIT_CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
-CGIT_CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
-CGIT_CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
-
-PKG_CONFIG ?= pkg-config
-
-ifdef NO_C99_FORMAT
-	CFLAGS += -DNO_C99_FORMAT
-endif
-
-ifdef NO_LUA
-	LUA_MESSAGE := linking without specified Lua support
-	CGIT_CFLAGS += -DNO_LUA
-else
-ifeq ($(LUA_PKGCONFIG),)
-	LUA_PKGCONFIG := $(shell for pc in luajit lua lua5.2 lua5.1; do \
-			$(PKG_CONFIG) --exists $$pc 2>/dev/null && echo $$pc && break; \
-			done)
-	LUA_MODE := autodetected
-else
-	LUA_MODE := specified
-endif
-ifneq ($(LUA_PKGCONFIG),)
-	LUA_MESSAGE := linking with $(LUA_MODE) $(LUA_PKGCONFIG)
-	LUA_LIBS := $(shell $(PKG_CONFIG) --libs $(LUA_PKGCONFIG) 2>/dev/null)
-	LUA_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(LUA_PKGCONFIG) 2>/dev/null)
-	CGIT_LIBS += $(LUA_LIBS)
-	CGIT_CFLAGS += $(LUA_CFLAGS)
-else
-	LUA_MESSAGE := linking without autodetected Lua support
-	NO_LUA := YesPlease
-	CGIT_CFLAGS += -DNO_LUA
-endif
-
-endif
-
-# Add -ldl to linker flags on systems that commonly use GNU libc.
-ifneq (,$(filter $(uname_S),Linux GNU GNU/kFreeBSD))
-	CGIT_LIBS += -ldl
-endif
-
-# glibc 2.1+ offers sendfile which the most common C library on Linux
-ifeq ($(uname_S),Linux)
-	HAVE_LINUX_SENDFILE = YesPlease
-endif
-
-ifdef HAVE_LINUX_SENDFILE
-	CGIT_CFLAGS += -DHAVE_LINUX_SENDFILE
-endif
-
-CGIT_OBJ_NAMES += cgit.o
-CGIT_OBJ_NAMES += cache.o
-CGIT_OBJ_NAMES += cmd.o
-CGIT_OBJ_NAMES += configfile.o
-CGIT_OBJ_NAMES += filter.o
-CGIT_OBJ_NAMES += html.o
-CGIT_OBJ_NAMES += parsing.o
-CGIT_OBJ_NAMES += scan-tree.o
-CGIT_OBJ_NAMES += shared.o
-CGIT_OBJ_NAMES += ui-atom.o
-CGIT_OBJ_NAMES += ui-blame.o
-CGIT_OBJ_NAMES += ui-blob.o
-CGIT_OBJ_NAMES += ui-clone.o
-CGIT_OBJ_NAMES += ui-commit.o
-CGIT_OBJ_NAMES += ui-diff.o
-CGIT_OBJ_NAMES += ui-log.o
-CGIT_OBJ_NAMES += ui-patch.o
-CGIT_OBJ_NAMES += ui-plain.o
-CGIT_OBJ_NAMES += ui-refs.o
-CGIT_OBJ_NAMES += ui-repolist.o
-CGIT_OBJ_NAMES += ui-shared.o
-CGIT_OBJ_NAMES += ui-snapshot.o
-CGIT_OBJ_NAMES += ui-ssdiff.o
-CGIT_OBJ_NAMES += ui-stats.o
-CGIT_OBJ_NAMES += ui-summary.o
-CGIT_OBJ_NAMES += ui-tag.o
-CGIT_OBJ_NAMES += ui-tree.o
-
-CGIT_OBJS := $(addprefix $(CGIT_PREFIX),$(CGIT_OBJ_NAMES))
-
-# Only cgit.c reference CGIT_VERSION so we only rebuild its objects when the
-# version changes.
-CGIT_VERSION_OBJS := $(addprefix $(CGIT_PREFIX),cgit.o cgit.sp)
-$(CGIT_VERSION_OBJS): $(CGIT_PREFIX)VERSION
-$(CGIT_VERSION_OBJS): EXTRA_CPPFLAGS = \
-	-DCGIT_VERSION='"$(CGIT_VERSION)"'
-
-# Git handles dependencies using ":=" so dependencies in CGIT_OBJ are not
-# handled by that and we must handle them ourselves.
-cgit_dep_files := $(foreach f,$(CGIT_OBJS),$(dir $f).depend/$(notdir $f).d)
-cgit_dep_files_present := $(wildcard $(cgit_dep_files))
-ifneq ($(cgit_dep_files_present),)
-include $(cgit_dep_files_present)
-endif
-
-ifeq ($(wildcard $(CGIT_PREFIX).depend),)
-missing_dep_dirs += $(CGIT_PREFIX).depend
-endif
-
-$(CGIT_PREFIX).depend:
-	@mkdir -p $@
-
-$(CGIT_PREFIX)CGIT-CFLAGS: FORCE
-	@FLAGS='$(subst ','\'',$(CGIT_CFLAGS))'; \
-	    if test x"$$FLAGS" != x"`cat ../CGIT-CFLAGS 2>/dev/null`" ; then \
-		echo 1>&2 "    * new CGit build flags"; \
-		echo "$$FLAGS" >$(CGIT_PREFIX)CGIT-CFLAGS; \
-            fi
-
-$(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs)
-	$(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $<
-
-$(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS)
-	@echo 1>&1 "    * $(LUA_MESSAGE)"
-	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS)
-
-CGIT_SP_OBJS := $(patsubst %.o,%.sp,$(CGIT_OBJS))
-
-$(CGIT_SP_OBJS): %.sp: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS FORCE
-	$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $(SPARSE_FLAGS) $<
-
-cgit-sparse: $(CGIT_SP_OBJS)
diff --git a/cgit.png b/cgit.png
deleted file mode 100644
index 425528ee39..0000000000
--- a/cgit.png
+++ /dev/null
Binary files differdiff --git a/cgitrc.5.txt b/cgitrc.5.txt
deleted file mode 100644
index ba77826fd0..0000000000
--- a/cgitrc.5.txt
+++ /dev/null
@@ -1,1008 +0,0 @@
-:man source:   cgit
-:man manual:   cgit
-
-CGITRC(5)
-========
-
-
-NAME
-----
-cgitrc - runtime configuration for cgit
-
-
-SYNOPSIS
---------
-Cgitrc contains all runtime settings for cgit, including the list of git
-repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
-lines, and lines starting with '#', are ignored.
-
-
-LOCATION
---------
-The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
-runtime, cgit will consult the environment variable CGIT_CONFIG and, if
-defined, use its value instead.
-
-
-GLOBAL SETTINGS
----------------
-about-filter::
-	Specifies a command which will be invoked to format the content of
-	about pages (both top-level and for each repository). The command will
-	get the content of the about-file on its STDIN, the name of the file
-	as the first argument, and the STDOUT from the command will be
-	included verbatim on the about page. Default value: none. See
-	also: "FILTER API".
-
-agefile::
-	Specifies a path, relative to each repository path, which can be used
-	to specify the date and time of the youngest commit in the repository.
-	The first line in the file is used as input to the "parse_date"
-	function in libgit. Recommended timestamp-format is "yyyy-mm-dd
-	hh:mm:ss". You may want to generate this file from a post-receive
-	hook. Default value: "info/web/last-modified".
-
-auth-filter::
-	Specifies a command that will be invoked for authenticating repository
-	access. Receives quite a few arguments, and data on both stdin and
-	stdout for authentication processing. Details follow later in this
-	document. If no auth-filter is specified, no authentication is
-	performed. Default value: none. See also: "FILTER API".
-
-branch-sort::
-	Flag which, when set to "age", enables date ordering in the branch ref
-	list, and when set to "name" enables ordering by branch name. Default
-	value: "name".
-
-cache-about-ttl::
-	Number which specifies the time-to-live, in minutes, for the cached
-	version of the repository about page. See also: "CACHE". Default
-	value: "15".
-
-cache-dynamic-ttl::
-	Number which specifies the time-to-live, in minutes, for the cached
-	version of repository pages accessed without a fixed SHA1. See also:
-	"CACHE". Default value: "5".
-
-cache-repo-ttl::
-	Number which specifies the time-to-live, in minutes, for the cached
-	version of the repository summary page. See also: "CACHE". Default
-	value: "5".
-
-cache-root::
-	Path used to store the cgit cache entries. Default value:
-	"/var/cache/cgit". See also: "MACRO EXPANSION".
-
-cache-root-ttl::
-	Number which specifies the time-to-live, in minutes, for the cached
-	version of the repository index page. See also: "CACHE". Default
-	value: "5".
-
-cache-scanrc-ttl::
-	Number which specifies the time-to-live, in minutes, for the result
-	of scanning a path for git repositories. See also: "CACHE". Default
-	value: "15".
-
-case-sensitive-sort::
-	Sort items in the repo list case sensitively. Default value: "1".
-	See also: repository-sort, section-sort.
-
-cache-size::
-	The maximum number of entries in the cgit cache. When set to "0",
-	caching is disabled. See also: "CACHE". Default value: "0"
-
-cache-snapshot-ttl::
-	Number which specifies the time-to-live, in minutes, for the cached
-	version of snapshots. See also: "CACHE". Default value: "5".
-
-cache-static-ttl::
-	Number which specifies the time-to-live, in minutes, for the cached
-	version of repository pages accessed with a fixed SHA1. See also:
-	"CACHE". Default value: -1".
-
-clone-prefix::
-	Space-separated list of common prefixes which, when combined with a
-	repository url, generates valid clone urls for the repository. This
-	setting is only used if `repo.clone-url` is unspecified. Default value:
-	none.
-
-clone-url::
-	Space-separated list of clone-url templates. This setting is only
-	used if `repo.clone-url` is unspecified. Default value: none. See
-	also: "MACRO EXPANSION", "FILTER API".
-
-commit-filter::
-	Specifies a command which will be invoked to format commit messages.
-	The command will get the message on its STDIN, and the STDOUT from the
-	command will be included verbatim as the commit message, i.e. this can
-	be used to implement bugtracker integration. Default value: none.
-	See also: "FILTER API".
-
-commit-sort::
-	Flag which, when set to "date", enables strict date ordering in the
-	commit log, and when set to "topo" enables strict topological
-	ordering. If unset, the default ordering of "git log" is used. Default
-	value: unset.
-
-css::
-	Url which specifies the css document to include in all cgit pages.
-	Default value: "/cgit.css".
-
-email-filter::
-	Specifies a command which will be invoked to format names and email
-	address of committers, authors, and taggers, as represented in various
-	places throughout the cgit interface. This command will receive an
-	email address and an origin page string as its command line arguments,
-	and the text to format on STDIN. It is to write the formatted text back
-	out onto STDOUT. Default value: none. See also: "FILTER API".
-
-embedded::
-	Flag which, when set to "1", will make cgit generate a html fragment
-	suitable for embedding in other html pages. Default value: none. See
-	also: "noheader".
-
-enable-blame::
-	Flag which, when set to "1", will allow cgit to provide a "blame" page
-	for files, and will make it generate links to that page in appropriate
-	places. Default value: "0".
-
-enable-commit-graph::
-	Flag which, when set to "1", will make cgit print an ASCII-art commit
-	history graph to the left of the commit messages in the repository
-	log page. Default value: "0".
-
-enable-filter-overrides::
-	Flag which, when set to "1", allows all filter settings to be
-	overridden in repository-specific cgitrc files. Default value: none.
-
-enable-follow-links::
-	Flag which, when set to "1", allows users to follow a file in the log
-	view.  Default value: "0".
-
-enable-git-config::
-	Flag which, when set to "1", will allow cgit to use git config to set
-	any repo specific settings. This option is used in conjunction with
-	"scan-path", and must be defined prior, to augment repo-specific
-	settings. The keys gitweb.owner, gitweb.category, gitweb.description,
-	and gitweb.homepage will map to the cgit keys repo.owner, repo.section,
-	repo.desc, and repo.homepage respectively. All git config keys that begin
-	with "cgit." will be mapped to the corresponding "repo." key in cgit.
-	Default value: "0". See also: scan-path, section-from-path.
-
-enable-http-clone::
-	If set to "1", cgit will act as a dumb HTTP endpoint for git clones.
-	You can add "http://$HTTP_HOST$SCRIPT_NAME/$CGIT_REPO_URL" to clone-url
-	to expose this feature. If you use an alternate way of serving git
-	repositories, you may wish to disable this. Default value: "1".
-
-enable-html-serving::
-	Flag which, when set to "1", will allow the /plain handler to serve
-	mimetype headers that result in the file being treated as HTML by the
-	browser. When set to "0", such file types are returned instead as
-	text/plain or application/octet-stream. Default value: "0". See also:
-	"repo.enable-html-serving".
-
-enable-index-links::
-	Flag which, when set to "1", will make cgit generate extra links for
-	each repo in the repository index (specifically, to the "summary",
-	"commit" and "tree" pages). Default value: "0".
-
-enable-index-owner::
-	Flag which, when set to "1", will make cgit display the owner of
-	each repo in the repository index. Default value: "1".
-
-enable-log-filecount::
-	Flag which, when set to "1", will make cgit print the number of
-	modified files for each commit on the repository log page. Default
-	value: "0".
-
-enable-log-linecount::
-	Flag which, when set to "1", will make cgit print the number of added
-	and removed lines for each commit on the repository log page. Default
-	value: "0".
-
-enable-remote-branches::
-	Flag which, when set to "1", will make cgit display remote branches
-	in the summary and refs views. Default value: "0". See also:
-	"repo.enable-remote-branches".
-
-enable-subject-links::
-	Flag which, when set to "1", will make cgit use the subject of the
-	parent commit as link text when generating links to parent commits
-	in commit view. Default value: "0". See also:
-	"repo.enable-subject-links".
-
-enable-tree-linenumbers::
-	Flag which, when set to "1", will make cgit generate linenumber links
-	for plaintext blobs printed in the tree view. Default value: "1".
-
-favicon::
-	Url used as link to a shortcut icon for cgit. It is suggested to use
-	the value "/favicon.ico" since certain browsers will ignore other
-	values. Default value: "/favicon.ico".
-
-footer::
-	The content of the file specified with this option will be included
-	verbatim at the bottom of all pages (i.e. it replaces the standard
-	"generated by..." message. Default value: none.
-
-head-include::
-	The content of the file specified with this option will be included
-	verbatim in the html HEAD section on all pages. Default value: none.
-
-header::
-	The content of the file specified with this option will be included
-	verbatim at the top of all pages. Default value: none.
-
-include::
-	Name of a configfile to include before the rest of the current config-
-	file is parsed. Default value: none. See also: "MACRO EXPANSION".
-
-local-time::
-	Flag which, if set to "1", makes cgit print commit and tag times in the
-	servers timezone. Default value: "0".
-
-logo::
-	Url which specifies the source of an image which will be used as a logo
-	on all cgit pages. Default value: "/cgit.png".
-
-logo-link::
-	Url loaded when clicking on the cgit logo image. If unspecified the
-	calculated url of the repository index page will be used. Default
-	value: none.
-
-max-atom-items::
-	Specifies the number of items to display in atom feeds view. Default
-	value: "10".
-
-max-blob-size::
-	Specifies the maximum size of a blob to display HTML for in KBytes.
-	Default value: "0" (limit disabled).
-
-max-commit-count::
-	Specifies the number of entries to list per page in "log" view. Default
-	value: "50".
-
-max-message-length::
-	Specifies the maximum number of commit message characters to display in
-	"log" view. Default value: "80".
-
-max-repo-count::
-	Specifies the number of entries to list per page on the	repository
-	index page. Default value: "50".
-
-max-repodesc-length::
-	Specifies the maximum number of repo description characters to display
-	on the repository index page. Default value: "80".
-
-max-stats::
-	Set the default maximum statistics period. Valid values are "week",
-	"month", "quarter" and "year". If unspecified, statistics are
-	disabled. Default value: none. See also: "repo.max-stats".
-
-mimetype.<ext>::
-	Set the mimetype for the specified filename extension. This is used
-	by the `plain` command when returning blob content.
-
-mimetype-file::
-	Specifies the file to use for automatic mimetype lookup. If specified
-	then this field is used as a fallback when no "mimetype.<ext>" match is
-	found. If unspecified then no such lookup is performed. The typical file
-	to use on a Linux system is /etc/mime.types. The format of the file must
-	comply to:
-	- a comment line is an empty line or a line starting with a hash (#),
-	  optionally preceded by whitespace
-	- a non-comment line starts with the mimetype (like image/png), followed
-	  by one or more file extensions (like jpg), all separated by whitespace
-	Default value: none. See also: "mimetype.<ext>".
-
-module-link::
-	Text which will be used as the formatstring for a hyperlink when a
-	submodule is printed in a directory listing. The arguments for the
-	formatstring are the path and SHA1 of the submodule commit. Default
-	value: none.
-
-noplainemail::
-	If set to "1" showing full author email addresses will be disabled.
-	Default value: "0".
-
-noheader::
-	Flag which, when set to "1", will make cgit omit the standard header
-	on all pages. Default value: none. See also: "embedded".
-
-owner-filter::
-	Specifies a command which will be invoked to format the Owner
-	column of the main page.  The command will get the owner on STDIN,
-	and the STDOUT from the command will be included verbatim in the
-	table.  This can be used to link to additional context such as an
-	owners home page.  When active this filter is used instead of the
-	default owner query url.  Default value: none.
-	See also: "FILTER API".
-
-project-list::
-	A list of subdirectories inside of scan-path, relative to it, that
-	should loaded as git repositories. This must be defined prior to
-	scan-path. Default value: none. See also: scan-path, "MACRO
-	EXPANSION".
-
-readme::
-	Text which will be used as default value for "repo.readme". Multiple
-	config keys may be specified, and cgit will use the first found file
-	in this list. This is useful in conjunction with scan-path. Default
-	value: none. See also: scan-path, repo.readme.
-
-remove-suffix::
-	If set to "1" and scan-path is enabled, if any repositories are found
-	with a suffix of ".git", this suffix will be removed for the url and
-	name. This must be defined prior to scan-path. Default value: "0".
-	See also: scan-path.
-
-renamelimit::
-	Maximum number of files to consider when detecting renames. The value
-	 "-1" uses the compiletime value in git (for further info, look at
-	  `man git-diff`). Default value: "-1".
-
-repository-sort::
-	The way in which repositories in each section are sorted. Valid values
-	are "name" for sorting by the repo name or "age" for sorting by the
-	most recently updated repository. Default value: "name". See also:
-	section, case-sensitive-sort, section-sort.
-
-robots::
-	Text used as content for the "robots" meta-tag. Default value:
-	"index, nofollow".
-
-root-desc::
-	Text printed below the heading on the repository index page. Default
-	value: "a fast webinterface for the git dscm".
-
-root-readme::
-	The content of the file specified with this option will be included
-	verbatim below the "about" link on the repository index page. Default
-	value: none.
-
-root-title::
-	Text printed as heading on the repository index page. Default value:
-	"Git Repository Browser".
-
-scan-hidden-path::
-	If set to "1" and scan-path is enabled, scan-path will recurse into
-	directories whose name starts with a period ('.'). Otherwise,
-	scan-path will stay away from such directories (considered as
-	"hidden"). Note that this does not apply to the ".git" directory in
-	non-bare repos. This must be defined prior to scan-path.
-	Default value: 0. See also: scan-path.
-
-scan-path::
-	A path which will be scanned for repositories. If caching is enabled,
-	the result will be cached as a cgitrc include-file in the cache
-	directory. If project-list has been defined prior to scan-path,
-	scan-path loads only the directories listed in the file pointed to by
-	project-list. Be advised that only the global settings taken
-	before the scan-path directive will be applied to each repository.
-	Default value: none. See also: cache-scanrc-ttl, project-list,
-	"MACRO EXPANSION".
-
-section::
-	The name of the current repository section - all repositories defined
-	after this option will inherit the current section name. Default value:
-	none.
-
-section-sort::
-	Flag which, when set to "1", will sort the sections on the repository
-	listing by name. Set this flag to "0" if the order in the cgitrc file should
-	be preserved. Default value: "1". See also: section,
-	case-sensitive-sort, repository-sort.
-
-section-from-path::
-	A number which, if defined prior to scan-path, specifies how many
-	path elements from each repo path to use as a default section name.
-	If negative, cgit will discard the specified number of path elements
-	above the repo directory. Default value: "0".
-
-side-by-side-diffs::
-	If set to "1" shows side-by-side diffs instead of unidiffs per
-	default. Default value: "0".
-
-snapshots::
-	Text which specifies the default set of snapshot formats that cgit
-	generates links for. The value is a space-separated list of zero or
-	more of the values "tar", "tar.gz", "tar.bz2", "tar.xz" and "zip".
-	The special value "all" enables all snapshot formats.
-	Default value: none.
-
-source-filter::
-	Specifies a command which will be invoked to format plaintext blobs
-	in the tree view. The command will get the blob content on its STDIN
-	and the name of the blob as its only command line argument. The STDOUT
-	from the command will be included verbatim as the blob contents, i.e.
-	this can be used to implement e.g. syntax highlighting. Default value:
-	none. See also: "FILTER API".
-
-summary-branches::
-	Specifies the number of branches to display in the repository "summary"
-	view. Default value: "10".
-
-summary-log::
-	Specifies the number of log entries to display in the repository
-	"summary" view. Default value: "10".
-
-summary-tags::
-	Specifies the number of tags to display in the repository "summary"
-	view. Default value: "10".
-
-strict-export::
-	Filename which, if specified, needs to be present within the repository
-	for cgit to allow access to that repository. This can be used to emulate
-	gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's
-	repositories to match those exported by git-daemon. This option must
-	be defined prior to scan-path.
-
-virtual-root::
-	Url which, if specified, will be used as root for all cgit links. It
-	will also cause cgit to generate 'virtual urls', i.e. urls like
-	'/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
-	value: none.
-	NOTE: cgit has recently learned how to use PATH_INFO to achieve the
-	same kind of virtual urls, so this option will probably be deprecated.
-
-
-REPOSITORY SETTINGS
--------------------
-repo.about-filter::
-	Override the default about-filter. Default value: none. See also:
-	"enable-filter-overrides". See also: "FILTER API".
-
-repo.branch-sort::
-	Flag which, when set to "age", enables date ordering in the branch ref
-	list, and when set to "name" enables ordering by branch name. Default
-	value: "name".
-
-repo.clone-url::
-	A list of space-separated urls which can be used to clone this repo.
-	Default value: none. See also: "MACRO EXPANSION".
-
-repo.commit-filter::
-	Override the default commit-filter. Default value: none. See also:
-	"enable-filter-overrides". See also: "FILTER API".
-
-repo.commit-sort::
-	Flag which, when set to "date", enables strict date ordering in the
-	commit log, and when set to "topo" enables strict topological
-	ordering. If unset, the default ordering of "git log" is used. Default
-	value: unset.
-
-repo.defbranch::
-	The name of the default branch for this repository. If no such branch
-	exists in the repository, the first branch name (when sorted) is used
-	as default instead. Default value: branch pointed to by HEAD, or
-	"master" if there is no suitable HEAD.
-
-repo.desc::
-	The value to show as repository description. Default value: none.
-
-repo.email-filter::
-	Override the default email-filter. Default value: none. See also:
-	"enable-filter-overrides". See also: "FILTER API".
-
-repo.enable-blame::
-	A flag which can be used to disable the global setting
-	`enable-blame'. Default value: none.
-
-repo.enable-commit-graph::
-	A flag which can be used to disable the global setting
-	`enable-commit-graph'. Default value: none.
-
-repo.enable-html-serving::
-	A flag which can be used to override the global setting
-	`enable-html-serving`. Default value: none.
-
-repo.enable-log-filecount::
-	A flag which can be used to disable the global setting
-	`enable-log-filecount'. Default value: none.
-
-repo.enable-log-linecount::
-	A flag which can be used to disable the global setting
-	`enable-log-linecount'. Default value: none.
-
-repo.enable-remote-branches::
-	Flag which, when set to "1", will make cgit display remote branches
-	in the summary and refs views. Default value: <enable-remote-branches>.
-
-repo.enable-subject-links::
-	A flag which can be used to override the global setting
-	`enable-subject-links'. Default value: none.
-
-repo.extra-head-content::
-	This value will be added verbatim to the head section of each page
-	displayed for this repo. Default value: none.
-
-repo.hide::
-	Flag which, when set to "1", hides the repository from the repository
-	index. The repository can still be accessed by providing a direct path.
-	Default value: "0". See also: "repo.ignore".
-
-repo.homepage::
-	The value to show as repository homepage. Default value: none.
-
-repo.ignore::
-	Flag which, when set to "1", ignores the repository. The repository
-	is not shown in the index and cannot be accessed by providing a direct
-	path. Default value: "0". See also: "repo.hide".
-
-repo.logo::
-	Url which specifies the source of an image which will be used as a logo
-	on this repo's pages. Default value: global logo.
-
-repo.logo-link::
-	Url loaded when clicking on the cgit logo image. If unspecified the
-	calculated url of the repository index page will be used. Default
-	value: global logo-link.
-
-repo.module-link::
-	Text which will be used as the formatstring for a hyperlink when a
-	submodule is printed in a directory listing. The arguments for the
-	formatstring are the path and SHA1 of the submodule commit. Default
-	value: <module-link>
-
-repo.module-link.<path>::
-	Text which will be used as the formatstring for a hyperlink when a
-	submodule with the specified subdirectory path is printed in a
-	directory listing. The only argument for the formatstring is the SHA1
-	of the submodule commit. Default value: none.
-
-repo.max-stats::
-	Override the default maximum statistics period. Valid values are equal
-	to the values specified for the global "max-stats" setting. Default
-	value: none.
-
-repo.name::
-	The value to show as repository name. Default value: <repo.url>.
-
-repo.owner::
-	A value used to identify the owner of the repository. Default value:
-	none.
-
-repo.owner-filter::
-	Override the default owner-filter. Default value: none. See also:
-	"enable-filter-overrides". See also: "FILTER API".
-
-repo.path::
-	An absolute path to the repository directory. For non-bare repositories
-	this is the .git-directory. Default value: none.
-
-repo.readme::
-	A path (relative to <repo.path>) which specifies a file to include
-	verbatim as the "About" page for this repo. You may also specify a
-	git refspec by head or by hash by prepending the refspec followed by
-	a colon. For example, "master:docs/readme.mkd". If the value begins
-	with a colon, i.e. ":docs/readme.rst", the default branch of the
-	repository will be used. Sharing any file will expose that entire
-	directory tree to the "/about/PATH" endpoints, so be sure that there
-	are no non-public files located in the same directory as the readme
-	file. Default value: <readme>.
-
-repo.section::
-	Override the current section name for this repository. Default value:
-	none.
-
-repo.snapshots::
-	A mask of snapshot formats for this repo that cgit generates links for,
-	restricted by the global "snapshots" setting. Default value:
-	<snapshots>.
-
-repo.snapshot-prefix::
-	Prefix to use for snapshot links instead of the repository basename.
-	For example, the "linux-stable" repository may wish to set this to
-	"linux" so that snapshots are in the format "linux-3.15.4" instead
-	of "linux-stable-3.15.4".  Default value: <empty> meaning to use
-	the repository basename.
-
-repo.source-filter::
-	Override the default source-filter. Default value: none. See also:
-	"enable-filter-overrides". See also: "FILTER API".
-
-repo.url::
-	The relative url used to access the repository. This must be the first
-	setting specified for each repo. Default value: none.
-
-
-REPOSITORY-SPECIFIC CGITRC FILE
--------------------------------
-When the option "scan-path" is used to auto-discover git repositories, cgit
-will try to parse the file "cgitrc" within any found repository. Such a
-repo-specific config file may contain any of the repo-specific options
-described above, except "repo.url" and "repo.path". Additionally, the "filter"
-options are only acknowledged in repo-specific config files when
-"enable-filter-overrides" is set to "1".
-
-Note: the "repo." prefix is dropped from the option names in repo-specific
-config files, e.g. "repo.desc" becomes "desc".
-
-
-FILTER API
-----------
-By default, filters are separate processes that are executed each time they
-are needed.  Alternative technologies may be used by prefixing the filter
-specification with the relevant string; available values are:
-
-'exec:'::
-	The default "one process per filter" mode.
-
-'lua:'::
-	Executes the script using a built-in Lua interpreter. The script is
-	loaded once per execution of cgit, and may be called multiple times
-	during cgit's lifetime, making it a good choice for repeated filters
-	such as the 'email filter'. It responds to three functions:
-
-	'filter_open(argument1, argument2, argument3, ...)'::
-		This is called upon activation of the filter for a particular
-		set of data.
-	'filter_write(buffer)'::
-		This is called whenever cgit writes data to the webpage.
-	'filter_close()'::
-		This is called when the current filtering operation is
-		completed. It must return an integer value. Usually 0
-		indicates success.
-
-	Additionally, cgit exposes to the Lua the following built-in functions:
-
-	'html(str)'::
-		Writes 'str' to the webpage.
-	'html_txt(str)'::
-		HTML escapes and writes 'str' to the webpage.
-	'html_attr(str)'::
-		HTML escapes for an attribute and writes "str' to the webpage.
-	'html_url_path(str)'::
-		URL escapes for a path and writes 'str' to the webpage.
-	'html_url_arg(str)'::
-		URL escapes for an argument and writes 'str' to the webpage.
-	'html_include(file)'::
-		Includes 'file' in webpage.
-
-
-Parameters are provided to filters as follows.
-
-about filter::
-	This filter is given a single parameter: the filename of the source
-	file to filter. The filter can use the filename to determine (for
-	example) the type of syntax to follow when formatting the readme file.
-	The about text that is to be filtered is available on standard input
-	and the filtered text is expected on standard output.
-
-auth filter::
-	The authentication filter receives 12 parameters:
-	  - filter action, explained below, which specifies which action the
-	    filter is called for
-	  - http cookie
-	  - http method
-	  - http referer
-	  - http path
-	  - http https flag
-	  - cgit repo
-	  - cgit page
-	  - cgit url
-	  - cgit login url
-	When the filter action is "body", this filter must write to output the
-	HTML for displaying the login form, which POSTs to the login url. When
-	the filter action is "authenticate-cookie", this filter must validate
-	the http cookie and return a 0 if it is invalid or 1 if it is invalid,
-	in the exit code / close function. If the filter action is
-	"authenticate-post", this filter receives POST'd parameters on
-	standard input, and should write a complete CGI response, preferably
-	with a 302 redirect, and write to output one or more "Set-Cookie"
-	HTTP headers, each followed by a newline.
-
-	Please see `filters/simple-authentication.lua` for a clear example
-	script that may be modified.
-
-commit filter::
-	This filter is given no arguments. The commit message text that is to
-	be filtered is available on standard input and the filtered text is
-	expected on standard output.
-
-email filter::
-	This filter is given two parameters: the email address of the relevant
-	author and a string indicating the originating page. The filter will
-	then receive the text string to format on standard input and is
-	expected to write to standard output the formatted text to be included
-	in the page.
-
-owner filter::
-	This filter is given no arguments.  The owner text is available on
-	standard input and the filter is expected to write to standard
-	output.  The output is included in the Owner column.
-
-source filter::
-	This filter is given a single parameter: the filename of the source
-	file to filter. The filter can use the filename to determine (for
-	example) the syntax highlighting mode. The contents of the source
-	file that is to be filtered is available on standard input and the
-	filtered contents is expected on standard output.
-
-
-All filters are handed the following environment variables:
-
-- CGIT_REPO_URL (from repo.url)
-- CGIT_REPO_NAME (from repo.name)
-- CGIT_REPO_PATH (from repo.path)
-- CGIT_REPO_OWNER (from repo.owner)
-- CGIT_REPO_DEFBRANCH (from repo.defbranch)
-- CGIT_REPO_SECTION (from repo.section)
-- CGIT_REPO_CLONE_URL (from repo.clone-url)
-
-If a setting is not defined for a repository and the corresponding global
-setting is also not defined (if applicable), then the corresponding
-environment variable will be unset.
-
-
-MACRO EXPANSION
----------------
-The following cgitrc options support a simple macro expansion feature,
-where tokens prefixed with "$" are replaced with the value of a similarly
-named environment variable:
-
-- cache-root
-- include
-- project-list
-- scan-path
-
-Macro expansion will also happen on the content of $CGIT_CONFIG, if
-defined.
-
-One usage of this feature is virtual hosting, which in its simplest form
-can be accomplished by adding the following line to /etc/cgitrc:
-
-	include=/etc/cgitrc.d/$HTTP_HOST
-
-The following options are expanded during request processing, and support
-the environment variables defined in "FILTER API":
-
-- clone-url
-- repo.clone-url
-
-
-CACHE
------
-
-All cache ttl values are in minutes. Negative ttl values indicate that a page
-type will never expire, and thus the first time a URL is accessed, the result
-will be cached indefinitely, even if the underlying git repository changes.
-Conversely, when a ttl value is zero, the cache is disabled for that
-particular page type, and the page type is never cached.
-
-SIGNATURES
-----------
-
-Cgit can host .asc signatures corresponding to various snapshot formats,
-through use of git notes. For example, the following command may be used to
-add a signature to a .tar.xz archive:
-
-    git notes --ref=refs/notes/signatures/tar.xz add -C "$(
-	gpg --output - --armor --detach-sign cgit-1.1.tar.xz |
-	git hash-object -w --stdin
-    )" v1.1
-
-If it is instead desirable to attach a signature of the underlying .tar, this
-will be linked, as a special case, beside a .tar.* link that does not have its
-own signature. For example, a signature of a tarball of the latest tag might
-be added with a similar command:
-
-    tag="$(git describe --abbrev=0)"
-    git notes --ref=refs/notes/signatures/tar add -C "$(
-        git archive --format tar --prefix "cgit-${tag#v}/" "$tag" |
-        gpg --output - --armor --detach-sign |
-        git hash-object -w --stdin
-    )" "$tag"
-
-Since git-archive(1) is expected to produce stable output between versions,
-this allows one to generate a long-term signature of the contents of a given
-tag.
-
-EXAMPLE CGITRC FILE
--------------------
-
-....
-# Enable caching of up to 1000 output entries
-cache-size=1000
-
-
-# Specify some default clone urls using macro expansion
-clone-url=git://foo.org/$CGIT_REPO_URL git@foo.org:$CGIT_REPO_URL
-
-# Specify the css url
-css=/css/cgit.css
-
-
-# Show owner on index page
-enable-index-owner=1
-
-
-# Allow http transport git clone
-enable-http-clone=1
-
-
-# Show extra links for each repository on the index page
-enable-index-links=1
-
-
-# Enable blame page and create links to it from tree page
-enable-blame=1
-
-
-# Enable ASCII art commit history graph on the log pages
-enable-commit-graph=1
-
-
-# Show number of affected files per commit on the log pages
-enable-log-filecount=1
-
-
-# Show number of added/removed lines per commit on the log pages
-enable-log-linecount=1
-
-
-# Sort branches by date
-branch-sort=age
-
-
-# Add a cgit favicon
-favicon=/favicon.ico
-
-
-# Use a custom logo
-logo=/img/mylogo.png
-
-
-# Enable statistics per week, month and quarter
-max-stats=quarter
-
-
-# Set the title and heading of the repository index page
-root-title=example.com git repositories
-
-
-# Set a subheading for the repository index page
-root-desc=tracking the foobar development
-
-
-# Include some more info about example.com on the index page
-root-readme=/var/www/htdocs/about.html
-
-
-# Allow download of tar.gz, tar.bz2 and zip-files
-snapshots=tar.gz tar.bz2 zip
-
-
-##
-## List of common mimetypes
-##
-
-mimetype.gif=image/gif
-mimetype.html=text/html
-mimetype.jpg=image/jpeg
-mimetype.jpeg=image/jpeg
-mimetype.pdf=application/pdf
-mimetype.png=image/png
-mimetype.svg=image/svg+xml
-
-
-# Highlight source code with python pygments-based highlighter
-source-filter=/var/www/cgit/filters/syntax-highlighting.py
-
-# Format markdown, restructuredtext, manpages, text files, and html files
-# through the right converters
-about-filter=/var/www/cgit/filters/about-formatting.sh
-
-##
-## Search for these files in the root of the default branch of repositories
-## for coming up with the about page:
-##
-readme=:README.md
-readme=:readme.md
-readme=:README.mkd
-readme=:readme.mkd
-readme=:README.rst
-readme=:readme.rst
-readme=:README.html
-readme=:readme.html
-readme=:README.htm
-readme=:readme.htm
-readme=:README.txt
-readme=:readme.txt
-readme=:README
-readme=:readme
-readme=:INSTALL.md
-readme=:install.md
-readme=:INSTALL.mkd
-readme=:install.mkd
-readme=:INSTALL.rst
-readme=:install.rst
-readme=:INSTALL.html
-readme=:install.html
-readme=:INSTALL.htm
-readme=:install.htm
-readme=:INSTALL.txt
-readme=:install.txt
-readme=:INSTALL
-readme=:install
-
-
-##
-## List of repositories.
-## PS: Any repositories listed when section is unset will not be
-##     displayed under a section heading
-## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
-##      and included like this:
-##        include=/etc/cgitrepos
-##
-
-
-repo.url=foo
-repo.path=/pub/git/foo.git
-repo.desc=the master foo repository
-repo.owner=fooman@example.com
-repo.readme=info/web/about.html
-
-
-repo.url=bar
-repo.path=/pub/git/bar.git
-repo.desc=the bars for your foo
-repo.owner=barman@example.com
-repo.readme=info/web/about.html
-
-
-# The next repositories will be displayed under the 'extras' heading
-section=extras
-
-
-repo.url=baz
-repo.path=/pub/git/baz.git
-repo.desc=a set of extensions for bar users
-
-repo.url=wiz
-repo.path=/pub/git/wiz.git
-repo.desc=the wizard of foo
-
-
-# Add some mirrored repositories
-section=mirrors
-
-
-repo.url=git
-repo.path=/pub/git/git.git
-repo.desc=the dscm
-
-
-repo.url=linux
-repo.path=/pub/git/linux.git
-repo.desc=the kernel
-
-# Disable adhoc downloads of this repo
-repo.snapshots=0
-
-# Disable line-counts for this repo
-repo.enable-log-linecount=0
-
-# Restrict the max statistics period for this repo
-repo.max-stats=month
-....
-
-
-BUGS
-----
-Comments currently cannot appear on the same line as a setting; the comment
-will be included as part of the value. E.g. this line:
-
-	robots=index  # allow indexing
-
-will generate the following html element:
-
-	<meta name='robots' content='index  # allow indexing'/>
-
-
-
-AUTHOR
-------
-Lars Hjemli <hjemli@gmail.com>
-Jason A. Donenfeld <Jason@zx2c4.com>
diff --git a/cmd.c b/cmd.c
deleted file mode 100644
index bf6d8f516f..0000000000
--- a/cmd.c
+++ /dev/null
@@ -1,208 +0,0 @@
-/* cmd.c: the cgit command dispatcher
- *
- * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "cmd.h"
-#include "cache.h"
-#include "ui-shared.h"
-#include "ui-atom.h"
-#include "ui-blame.h"
-#include "ui-blob.h"
-#include "ui-clone.h"
-#include "ui-commit.h"
-#include "ui-diff.h"
-#include "ui-log.h"
-#include "ui-patch.h"
-#include "ui-plain.h"
-#include "ui-refs.h"
-#include "ui-repolist.h"
-#include "ui-snapshot.h"
-#include "ui-stats.h"
-#include "ui-summary.h"
-#include "ui-tag.h"
-#include "ui-tree.h"
-
-static void HEAD_fn(void)
-{
-	cgit_clone_head();
-}
-
-static void atom_fn(void)
-{
-	cgit_print_atom(ctx.qry.head, ctx.qry.path, ctx.cfg.max_atom_items);
-}
-
-static void about_fn(void)
-{
-	if (ctx.repo) {
-		size_t path_info_len = ctx.env.path_info ? strlen(ctx.env.path_info) : 0;
-		if (!ctx.qry.path &&
-		    ctx.qry.url[strlen(ctx.qry.url) - 1] != '/' &&
-		    (!path_info_len || ctx.env.path_info[path_info_len - 1] != '/')) {
-			char *currenturl = cgit_currenturl();
-			char *redirect = fmtalloc("%s/", currenturl);
-			cgit_redirect(redirect, true);
-			free(currenturl);
-			free(redirect);
-		} else if (ctx.repo->readme.nr)
-			cgit_print_repo_readme(ctx.qry.path);
-		else if (ctx.repo->homepage)
-			cgit_redirect(ctx.repo->homepage, false);
-		else {
-			char *currenturl = cgit_currenturl();
-			char *redirect = fmtalloc("%s../", currenturl);
-			cgit_redirect(redirect, false);
-			free(currenturl);
-			free(redirect);
-		}
-	} else
-		cgit_print_site_readme();
-}
-
-static void blame_fn(void)
-{
-	if (ctx.repo->enable_blame)
-		cgit_print_blame();
-	else
-		cgit_print_error_page(403, "Forbidden", "Blame is disabled");
-}
-
-static void blob_fn(void)
-{
-	cgit_print_blob(ctx.qry.sha1, ctx.qry.path, ctx.qry.head, 0);
-}
-
-static void commit_fn(void)
-{
-	cgit_print_commit(ctx.qry.sha1, ctx.qry.path);
-}
-
-static void diff_fn(void)
-{
-	cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 0);
-}
-
-static void rawdiff_fn(void)
-{
-	cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 1);
-}
-
-static void info_fn(void)
-{
-	cgit_clone_info();
-}
-
-static void log_fn(void)
-{
-	cgit_print_log(ctx.qry.sha1, ctx.qry.ofs, ctx.cfg.max_commit_count,
-		       ctx.qry.grep, ctx.qry.search, ctx.qry.path, 1,
-		       ctx.repo->enable_commit_graph,
-		       ctx.repo->commit_sort);
-}
-
-static void ls_cache_fn(void)
-{
-	ctx.page.mimetype = "text/plain";
-	ctx.page.filename = "ls-cache.txt";
-	cgit_print_http_headers();
-	cache_ls(ctx.cfg.cache_root);
-}
-
-static void objects_fn(void)
-{
-	cgit_clone_objects();
-}
-
-static void repolist_fn(void)
-{
-	cgit_print_repolist();
-}
-
-static void patch_fn(void)
-{
-	cgit_print_patch(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path);
-}
-
-static void plain_fn(void)
-{
-	cgit_print_plain();
-}
-
-static void refs_fn(void)
-{
-	cgit_print_refs();
-}
-
-static void snapshot_fn(void)
-{
-	cgit_print_snapshot(ctx.qry.head, ctx.qry.sha1, ctx.qry.path,
-			    ctx.qry.nohead);
-}
-
-static void stats_fn(void)
-{
-	cgit_show_stats();
-}
-
-static void summary_fn(void)
-{
-	cgit_print_summary();
-}
-
-static void tag_fn(void)
-{
-	cgit_print_tag(ctx.qry.sha1);
-}
-
-static void tree_fn(void)
-{
-	cgit_print_tree(ctx.qry.sha1, ctx.qry.path);
-}
-
-#define def_cmd(name, want_repo, want_vpath, is_clone) \
-	{#name, name##_fn, want_repo, want_vpath, is_clone}
-
-struct cgit_cmd *cgit_get_cmd(void)
-{
-	static struct cgit_cmd cmds[] = {
-		def_cmd(HEAD, 1, 0, 1),
-		def_cmd(atom, 1, 0, 0),
-		def_cmd(about, 0, 0, 0),
-		def_cmd(blame, 1, 1, 0),
-		def_cmd(blob, 1, 0, 0),
-		def_cmd(commit, 1, 1, 0),
-		def_cmd(diff, 1, 1, 0),
-		def_cmd(info, 1, 0, 1),
-		def_cmd(log, 1, 1, 0),
-		def_cmd(ls_cache, 0, 0, 0),
-		def_cmd(objects, 1, 0, 1),
-		def_cmd(patch, 1, 1, 0),
-		def_cmd(plain, 1, 0, 0),
-		def_cmd(rawdiff, 1, 1, 0),
-		def_cmd(refs, 1, 0, 0),
-		def_cmd(repolist, 0, 0, 0),
-		def_cmd(snapshot, 1, 0, 0),
-		def_cmd(stats, 1, 1, 0),
-		def_cmd(summary, 1, 0, 0),
-		def_cmd(tag, 1, 0, 0),
-		def_cmd(tree, 1, 1, 0),
-	};
-	int i;
-
-	if (ctx.qry.page == NULL) {
-		if (ctx.repo)
-			ctx.qry.page = "summary";
-		else
-			ctx.qry.page = "repolist";
-	}
-
-	for (i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
-		if (!strcmp(ctx.qry.page, cmds[i].name))
-			return &cmds[i];
-	return NULL;
-}
diff --git a/cmd.h b/cmd.h
deleted file mode 100644
index 6249b1d892..0000000000
--- a/cmd.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#ifndef CMD_H
-#define CMD_H
-
-typedef void (*cgit_cmd_fn)(void);
-
-struct cgit_cmd {
-	const char *name;
-	cgit_cmd_fn fn;
-	unsigned int want_repo:1,
-		want_vpath:1,
-		is_clone:1;
-};
-
-extern struct cgit_cmd *cgit_get_cmd(void);
-
-#endif /* CMD_H */
diff --git a/configfile.c b/configfile.c
deleted file mode 100644
index e0391091e1..0000000000
--- a/configfile.c
+++ /dev/null
@@ -1,90 +0,0 @@
-/* configfile.c: parsing of config files
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include <git-compat-util.h>
-#include "configfile.h"
-
-static int next_char(FILE *f)
-{
-	int c = fgetc(f);
-	if (c == '\r') {
-		c = fgetc(f);
-		if (c != '\n') {
-			ungetc(c, f);
-			c = '\r';
-		}
-	}
-	return c;
-}
-
-static void skip_line(FILE *f)
-{
-	int c;
-
-	while ((c = next_char(f)) && c != '\n' && c != EOF)
-		;
-}
-
-static int read_config_line(FILE *f, struct strbuf *name, struct strbuf *value)
-{
-	int c = next_char(f);
-
-	strbuf_reset(name);
-	strbuf_reset(value);
-
-	/* Skip comments and preceding spaces. */
-	for(;;) {
-		if (c == EOF)
-			return 0;
-		else if (c == '#' || c == ';')
-			skip_line(f);
-		else if (!isspace(c))
-			break;
-		c = next_char(f);
-	}
-
-	/* Read variable name. */
-	while (c != '=') {
-		if (c == '\n' || c == EOF)
-			return 0;
-		strbuf_addch(name, c);
-		c = next_char(f);
-	}
-
-	/* Read variable value. */
-	c = next_char(f);
-	while (c != '\n' && c != EOF) {
-		strbuf_addch(value, c);
-		c = next_char(f);
-	}
-
-	return 1;
-}
-
-int parse_configfile(const char *filename, configfile_value_fn fn)
-{
-	static int nesting;
-	struct strbuf name = STRBUF_INIT;
-	struct strbuf value = STRBUF_INIT;
-	FILE *f;
-
-	/* cancel deeply nested include-commands */
-	if (nesting > 8)
-		return -1;
-	if (!(f = fopen(filename, "r")))
-		return -1;
-	nesting++;
-	while (read_config_line(f, &name, &value))
-		fn(name.buf, value.buf);
-	nesting--;
-	fclose(f);
-	strbuf_release(&name);
-	strbuf_release(&value);
-	return 0;
-}
-
diff --git a/configfile.h b/configfile.h
deleted file mode 100644
index af7ca19735..0000000000
--- a/configfile.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef CONFIGFILE_H
-#define CONFIGFILE_H
-
-#include "cgit.h"
-
-typedef void (*configfile_value_fn)(const char *name, const char *value);
-
-extern int parse_configfile(const char *filename, configfile_value_fn fn);
-
-#endif /* CONFIGFILE_H */
diff --git a/contrib/hooks/post-receive.agefile b/contrib/hooks/post-receive.agefile
deleted file mode 100755
index 2f72ae9c0d..0000000000
--- a/contrib/hooks/post-receive.agefile
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/sh
-#
-# An example hook to update the "agefile" for CGit's idle time calculation.
-#
-# This hook assumes that you are using the default agefile location of
-# "info/web/last-modified".  If you change the value in your cgitrc then you
-# must also change it here.
-#
-# To install the hook, copy (or link) it to the file "hooks/post-receive" in
-# each of your repositories.
-#
-
-agefile="$(git rev-parse --git-dir)"/info/web/last-modified
-
-mkdir -p "$(dirname "$agefile")" &&
-git for-each-ref \
-	--sort=-authordate --count=1 \
-	--format='%(authordate:iso8601)' \
-	>"$agefile"
diff --git a/exwm-background.el b/exwm-background.el
new file mode 100644
index 0000000000..fa663d8fe6
--- /dev/null
+++ b/exwm-background.el
@@ -0,0 +1,199 @@
+;;; exwm-background.el --- X Background Module for EXWM  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2022-2024 Free Software Foundation, Inc.
+
+;; Author: Steven Allen <steven@stebalien.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module adds X background color setting support to EXWM.
+
+;; To use this module, load and enable it as follows:
+;;   (require 'exwm-background)
+;;   (exwm-background-enable)
+;;
+;; By default, this will apply the theme's background color.  However, that
+;; color can be customized via the `exwm-background-color' setting.
+
+;;; Code:
+
+(require 'exwm-core)
+
+(defcustom exwm-background-color nil
+  "Background color for Xorg."
+  :type '(choice
+          (color :tag "Background Color")
+          (const :tag "Default" nil))
+  :group 'exwm
+  :initialize #'custom-initialize-default
+  :set (lambda (symbol value)
+         (set-default-toplevel-value symbol value)
+         (exwm-background--update)))
+
+(defconst exwm-background--properties '("_XROOTPMAP_ID" "_XSETROOT_ID" "ESETROOT_PMAP_ID")
+  "The background properties to set.
+We can't need to set these so that compositing window managers
+can correctly display the background color.")
+
+(defvar exwm-background--connection nil
+  "The X connection used for setting the background.
+We use a separate connection as other background-setting tools
+may kill this connection when they replace it.")
+
+(defvar exwm-background--pixmap nil
+  "Cached background pixmap.")
+
+(defvar exwm-background--atoms nil
+  "Cached background atoms.")
+
+(defun exwm-background--update (&rest _)
+  "Update the EXWM background."
+
+  ;; Always reconnect as any tool that sets the background may have disconnected us (to force X to
+  ;; free resources).
+  (exwm-background--connect)
+
+  (let ((gc (xcb:generate-id exwm-background--connection))
+        (color (exwm--color->pixel (or exwm-background-color
+                                       (face-background 'default)))))
+    ;; Fill the pixmap.
+    (xcb:+request exwm-background--connection
+        (make-instance 'xcb:CreateGC
+                       :cid gc :drawable exwm-background--pixmap
+                       :value-mask (logior xcb:GC:Foreground
+                                           xcb:GC:GraphicsExposures)
+                       :foreground color
+                       :graphics-exposures 0))
+
+    (xcb:+request exwm-background--connection
+        (make-instance 'xcb:PolyFillRectangle
+                       :gc gc :drawable exwm-background--pixmap
+                       :rectangles
+                       (list
+                        (make-instance
+                         'xcb:RECTANGLE
+                         :x 0 :y 0 :width 1 :height 1))))
+    (xcb:+request exwm-background--connection (make-instance 'xcb:FreeGC :gc gc)))
+
+  ;; Reapply it to force an update (also clobber anyone else who may have set it).
+  (xcb:+request exwm-background--connection
+      (make-instance 'xcb:ChangeWindowAttributes
+                     :window exwm--root
+                     :value-mask xcb:CW:BackPixmap
+                     :background-pixmap exwm-background--pixmap))
+
+  (let (old)
+    ;; Collect old pixmaps so we can kill other background clients (all the background setting tools
+    ;; seem to do this).
+    (dolist (atom exwm-background--atoms)
+      (when-let* ((reply (xcb:+request-unchecked+reply exwm-background--connection
+                             (make-instance 'xcb:GetProperty
+                                            :delete 0
+                                            :window exwm--root
+                                            :property atom
+                                            :type xcb:Atom:PIXMAP
+                                            :long-offset 0
+                                            :long-length 1)))
+                  (value (vconcat (slot-value reply 'value)))
+                  ((length= value 4))
+                  (pixmap (funcall (if xcb:lsb #'xcb:-unpack-u4-lsb #'xcb:-unpack-u4)
+                                   value 0))
+                  ((not (or (= pixmap exwm-background--pixmap)
+                            (member pixmap old)))))
+        (push pixmap old)))
+
+    ;; Change the background.
+    (dolist (atom exwm-background--atoms)
+      (xcb:+request exwm-background--connection
+          (make-instance 'xcb:ChangeProperty
+                         :window exwm--root
+                         :property atom
+                         :type xcb:Atom:PIXMAP
+                         :format 32
+                         :mode xcb:PropMode:Replace
+                         :data-len 1
+                         :data
+                         (funcall (if xcb:lsb
+                                      #'xcb:-pack-u4-lsb
+                                    #'xcb:-pack-u4)
+                                  exwm-background--pixmap))))
+
+    ;; Kill the old background clients.
+    (dolist (pixmap old)
+      (xcb:+request exwm-background--connection
+          (make-instance 'xcb:KillClient :resource pixmap))))
+
+  (xcb:flush exwm-background--connection))
+
+(defun exwm-background--connected-p ()
+  (and exwm-background--connection
+       (process-live-p (slot-value exwm-background--connection 'process))))
+
+(defun exwm-background--connect ()
+  (unless (exwm-background--connected-p)
+    (setq exwm-background--connection (xcb:connect))
+    ;;prevent query message on exit
+    (set-process-query-on-exit-flag (slot-value exwm-background--connection 'process) nil)
+
+    ;; Intern the background property atoms.
+    (setq exwm-background--atoms
+          (mapcar
+           (lambda (prop) (exwm--intern-atom prop exwm-background--connection))
+           exwm-background--properties))
+
+    ;; Create the pixmap.
+    (setq exwm-background--pixmap (xcb:generate-id exwm-background--connection))
+    (xcb:+request exwm-background--connection
+        (make-instance 'xcb:CreatePixmap
+                       :depth
+                       (slot-value
+                        (xcb:+request-unchecked+reply exwm-background--connection
+                            (make-instance 'xcb:GetGeometry :drawable exwm--root))
+                        'depth)
+                       :pid exwm-background--pixmap
+                       :drawable exwm--root
+                       :width 1 :height 1))))
+
+(defun exwm-background--init ()
+  "Initialize background module."
+  (exwm--log)
+  (add-hook 'enable-theme-functions 'exwm-background--update)
+  (add-hook 'disable-theme-functions 'exwm-background--update)
+  (exwm-background--update))
+
+(defun exwm-background--exit ()
+  "Uninitialize the background module."
+  (exwm--log)
+  (remove-hook 'enable-theme-functions 'exwm-background--update)
+  (remove-hook 'disable-theme-functions 'exwm-background--update)
+  (when (and exwm-background--connection
+             (slot-value exwm-background--connection 'connected))
+    (xcb:disconnect exwm-background--connection))
+  (setq exwm-background--pixmap nil
+        exwm-background--connection nil
+        exwm-background--atoms nil))
+
+(defun exwm-background-enable ()
+  "Enable background support for EXWM."
+  (exwm--log)
+  (add-hook 'exwm-init-hook #'exwm-background--init)
+  (add-hook 'exwm-exit-hook #'exwm-background--exit))
+
+(provide 'exwm-background)
+
+;;; exwm-background.el ends here
diff --git a/exwm-config.el b/exwm-config.el
new file mode 100644
index 0000000000..a9f21e9c8c
--- /dev/null
+++ b/exwm-config.el
@@ -0,0 +1,131 @@
+;;; exwm-config.el --- Predefined configurations  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2015-2024 Free Software Foundation, Inc.
+
+;; Author: Chris Feng <chris.w.feng@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module contains typical (yet minimal) configurations of EXWM.
+
+;;; Code:
+
+(require 'exwm)
+(require 'ido)
+
+(define-obsolete-function-alias 'exwm-config-default
+  #'exwm-config-example "27.1")
+
+(defun exwm-config-example ()
+  "Default configuration of EXWM."
+  ;; Set the initial workspace number.
+  (unless (get 'exwm-workspace-number 'saved-value)
+    (setq exwm-workspace-number 4))
+  ;; Make class name the buffer name
+  (add-hook 'exwm-update-class-hook
+            (lambda ()
+              (exwm-workspace-rename-buffer exwm-class-name)))
+  ;; Global keybindings.
+  (unless (get 'exwm-input-global-keys 'saved-value)
+    (setq exwm-input-global-keys
+          `(
+            ;; 's-r': Reset (to line-mode).
+            ([?\s-r] . exwm-reset)
+            ;; 's-w': Switch workspace.
+            ([?\s-w] . exwm-workspace-switch)
+            ;; 's-&': Launch application.
+            ([?\s-&] . (lambda (command)
+                         (interactive (list (read-shell-command "$ ")))
+                         (start-process-shell-command command nil command)))
+            ;; 's-N': Switch to certain workspace.
+            ,@(mapcar (lambda (i)
+                        `(,(kbd (format "s-%d" i)) .
+                          (lambda ()
+                            (interactive)
+                            (exwm-workspace-switch-create ,i))))
+                      (number-sequence 0 9)))))
+  ;; Line-editing shortcuts
+  (unless (get 'exwm-input-simulation-keys 'saved-value)
+    (setq exwm-input-simulation-keys
+          '(([?\C-b] . [left])
+            ([?\C-f] . [right])
+            ([?\C-p] . [up])
+            ([?\C-n] . [down])
+            ([?\C-a] . [home])
+            ([?\C-e] . [end])
+            ([?\M-v] . [prior])
+            ([?\C-v] . [next])
+            ([?\C-d] . [delete])
+            ([?\C-k] . [S-end delete]))))
+  ;; Enable EXWM
+  (exwm-enable)
+  ;; Configure Ido
+  (exwm-config-ido)
+  ;; Other configurations
+  (exwm-config-misc))
+
+(defun exwm-config--fix/ido-buffer-window-other-frame ()
+  "Fix `ido-buffer-window-other-frame'."
+  (defalias 'exwm-config-ido-buffer-window-other-frame
+    (symbol-function #'ido-buffer-window-other-frame))
+  (defun ido-buffer-window-other-frame (buffer)
+    "This is a version redefined by EXWM.
+
+You can find the original one at `exwm-config-ido-buffer-window-other-frame'."
+    (with-current-buffer (window-buffer (selected-window))
+      (if (and (derived-mode-p 'exwm-mode)
+               exwm--floating-frame)
+          ;; Switch from a floating frame.
+          (with-current-buffer buffer
+            (if (and (derived-mode-p 'exwm-mode)
+                     exwm--floating-frame
+                     (eq exwm--frame exwm-workspace--current))
+                ;; Switch to another floating frame.
+                (frame-root-window exwm--floating-frame)
+              ;; Do not switch if the buffer is not on the current workspace.
+              (or (get-buffer-window buffer exwm-workspace--current)
+                  (selected-window))))
+        (with-current-buffer buffer
+          (when (derived-mode-p 'exwm-mode)
+            (if (eq exwm--frame exwm-workspace--current)
+                (when exwm--floating-frame
+                  ;; Switch to a floating frame on the current workspace.
+                  (frame-selected-window exwm--floating-frame))
+              ;; Do not switch to exwm-mode buffers on other workspace (which
+              ;; won't work unless `exwm-layout-show-all-buffers' is set)
+              (unless exwm-layout-show-all-buffers
+                (selected-window)))))))))
+
+(defun exwm-config-ido ()
+  "Configure Ido to work with EXWM."
+  (ido-mode 1)
+  (add-hook 'exwm-init-hook #'exwm-config--fix/ido-buffer-window-other-frame))
+
+(defun exwm-config-misc ()
+  "Other configurations."
+  ;; Make more room
+  (menu-bar-mode -1)
+  (tool-bar-mode -1)
+  (scroll-bar-mode -1)
+  (fringe-mode 1))
+
+
+
+(provide 'exwm-config)
+
+;;; exwm-config.el ends here
diff --git a/exwm-core.el b/exwm-core.el
new file mode 100644
index 0000000000..e0d644d941
--- /dev/null
+++ b/exwm-core.el
@@ -0,0 +1,411 @@
+;;; exwm-core.el --- Core definitions  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2015-2024 Free Software Foundation, Inc.
+
+;; Author: Chris Feng <chris.w.feng@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module includes core definitions of variables, macros, functions, etc
+;; shared by various other modules.
+
+;;; Code:
+
+(require 'kmacro)
+
+(require 'xcb)
+(require 'xcb-icccm)
+(require 'xcb-ewmh)
+(require 'xcb-debug)
+
+(defgroup exwm-debug nil
+  "Debugging."
+  :group 'exwm)
+
+(defcustom exwm-debug-log-time-function #'exwm-debug-log-uptime
+  "Function used for generating timestamps in `exwm-debug' logs.
+
+Here are some predefined candidates:
+`exwm-debug-log-uptime': Display the uptime of this Emacs instance.
+`exwm-debug-log-time': Display time of day.
+`nil': Disable timestamp."
+  :type `(choice (const :tag "Emacs uptime" ,#'exwm-debug-log-uptime)
+                 (const :tag "Time of day" ,#'exwm-debug-log-time)
+                 (const :tag "Off" nil)
+                 (function :tag "Other"))
+  :set (lambda (symbol value)
+         (set-default symbol value)
+         ;; Also change the format for XELB to make logs consistent
+         ;; (as they share the same buffer).
+         (setq xcb-debug:log-time-function value)))
+
+(defalias 'exwm-debug-log-uptime 'xcb-debug:log-uptime
+  "Add uptime to `exwm-debug' logs.")
+
+(defalias 'exwm-debug-log-time 'xcb-debug:log-time
+  "Add time of day to `exwm-debug' logs.")
+
+(defvar exwm--connection nil "X connection.")
+
+(defvar exwm--terminal nil
+  "Terminal corresponding to `exwm--connection'.")
+
+(defvar exwm--wmsn-window nil
+  "An X window owning the WM_S0 selection.")
+
+(defvar exwm--wmsn-acquire-timeout 3
+  "Number of seconds to wait for other window managers to release the selection.")
+
+(defvar exwm--guide-window nil
+  "An X window separating workspaces and X windows.")
+
+(defvar exwm--id-buffer-alist nil "Alist of (<X window ID> . <Emacs buffer>).")
+
+(defvar exwm--root nil "Root window.")
+
+(defvar exwm-input--global-prefix-keys)
+(defvar exwm-input--simulation-keys)
+(defvar exwm-input-line-mode-passthrough)
+(defvar exwm-input-prefix-keys)
+(declare-function exwm-input--fake-key "exwm-input.el" (event))
+(declare-function exwm-input--on-KeyPress-line-mode "exwm-input.el"
+                  (key-press raw-data))
+(declare-function exwm-floating-hide "exwm-floating.el")
+(declare-function exwm-floating-toggle-floating "exwm-floating.el")
+(declare-function exwm-input-release-keyboard "exwm-input.el")
+(declare-function exwm-input-send-next-key "exwm-input.el" (times))
+(declare-function exwm-layout-set-fullscreen "exwm-layout.el" (&optional id))
+(declare-function exwm-layout-toggle-mode-line "exwm-layout.el")
+(declare-function exwm-manage--kill-buffer-query-function "exwm-manage.el")
+(declare-function exwm-workspace-move-window "exwm-workspace.el"
+                  (frame-or-index &optional id))
+
+(define-minor-mode exwm-debug
+  "Debug-logging enabled if non-nil."
+  :global t
+  :group 'exwm-debug)
+
+(defmacro exwm--debug (&rest forms)
+  "Evaluate FORMS if mode `exwm-debug' is active."
+  (when exwm-debug `(progn ,@forms)))
+
+(defmacro exwm--log (&optional format-string &rest objects)
+  "Emit a message prepending the name of the function being executed.
+
+FORMAT-STRING is a string specifying the message to output, as in
+`format'.  The OBJECTS arguments specify the substitutions."
+  (unless format-string (setq format-string ""))
+  `(when exwm-debug
+     (xcb-debug:message ,(concat "%s%s:\t" format-string "\n")
+                        (if exwm-debug-log-time-function
+                            (funcall exwm-debug-log-time-function)
+                          "")
+                        (xcb-debug:compile-time-function-name)
+                        ,@objects)
+     nil))
+
+(defsubst exwm--id->buffer (id)
+  "X window ID => Emacs buffer."
+  (declare (indent defun))
+  (cdr (assoc id exwm--id-buffer-alist)))
+
+(defsubst exwm--buffer->id (buffer)
+  "Emacs buffer BUFFER => X window ID."
+  (declare (indent defun))
+  (car (rassoc buffer exwm--id-buffer-alist)))
+
+(defun exwm--lock (&rest _args)
+  "Lock (disable all events)."
+  (exwm--log)
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ChangeWindowAttributes
+                     :window exwm--root
+                     :value-mask xcb:CW:EventMask
+                     :event-mask xcb:EventMask:NoEvent))
+  (xcb:flush exwm--connection))
+
+(defun exwm--unlock (&rest _args)
+  "Unlock (enable all events)."
+  (exwm--log)
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ChangeWindowAttributes
+                     :window exwm--root
+                     :value-mask xcb:CW:EventMask
+                     :event-mask (eval-when-compile
+                                   (logior xcb:EventMask:SubstructureRedirect
+                                           xcb:EventMask:StructureNotify))))
+  (xcb:flush exwm--connection))
+
+(defun exwm--set-geometry (xwin x y width height)
+  "Set the geometry of X window XWIN to WIDTHxHEIGHT+X+Y.
+
+Nil can be passed as placeholder."
+  (exwm--log "Setting #x%x to %sx%s+%s+%s" xwin width height x y)
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ConfigureWindow
+                     :window xwin
+                     :value-mask (logior (if x xcb:ConfigWindow:X 0)
+                                         (if y xcb:ConfigWindow:Y 0)
+                                         (if width xcb:ConfigWindow:Width 0)
+                                         (if height xcb:ConfigWindow:Height 0))
+                     :x x :y y :width width :height height)))
+
+(defun exwm--intern-atom (atom &optional conn)
+  "Intern X11 ATOM.
+If CONN is non-nil, use it instead of the value of the variable
+`exwm--connection'."
+  (slot-value (xcb:+request-unchecked+reply (or conn exwm--connection)
+                  (make-instance 'xcb:InternAtom
+                                 :only-if-exists 0
+                                 :name-len (length atom)
+                                 :name atom))
+              'atom))
+
+(defmacro exwm--defer (secs function &rest args)
+  "Defer the execution of FUNCTION.
+
+The action is to call FUNCTION with arguments ARGS.  If Emacs is not idle,
+defer the action until Emacs is idle.  Otherwise, defer the action until at
+least SECS seconds later."
+  `(run-with-idle-timer (+ (float-time (or (current-idle-time)
+                                           (seconds-to-time (- ,secs))))
+                           ,secs)
+                        nil
+                        ,function
+                        ,@args))
+
+(defsubst exwm--terminal-p (&optional frame)
+  "Return t when FRAME's terminal is EXWM's terminal.
+If FRAME is null, use selected frame."
+  (declare (indent defun))
+  (eq exwm--terminal (frame-terminal frame)))
+
+(defun exwm--get-client-event-mask ()
+  "Return event mask set on all managed windows."
+  (logior xcb:EventMask:StructureNotify
+          xcb:EventMask:PropertyChange
+          (if mouse-autoselect-window
+              xcb:EventMask:EnterWindow 0)))
+
+(defun exwm--color->pixel (color)
+  "Convert COLOR to PIXEL (index in TrueColor colormap)."
+  (when (and color
+             (eq (x-display-visual-class) 'true-color))
+    (let ((rgb (color-values color)))
+      (logior (ash (ash (pop rgb) -8) 16)
+              (ash (ash (pop rgb) -8) 8)
+              (ash (pop rgb) -8)))))
+
+(defun exwm--get-visual-depth-colormap (conn id)
+  "Get visual, depth and colormap from X window ID.
+Return a three element list with the respective results.
+
+If CONN is non-nil, use it instead of the value of the variable
+`exwm--connection'."
+  (let (ret-visual ret-depth ret-colormap)
+    (with-slots (visual colormap)
+        (xcb:+request-unchecked+reply conn
+            (make-instance 'xcb:GetWindowAttributes :window id))
+      (setq ret-visual visual)
+      (setq ret-colormap colormap))
+    (with-slots (depth)
+        (xcb:+request-unchecked+reply conn
+            (make-instance 'xcb:GetGeometry :drawable id))
+      (setq ret-depth depth))
+    (list ret-visual ret-depth ret-colormap)))
+
+;; Internal variables
+(defvar-local exwm--id nil)               ;window ID
+(defvar-local exwm--configurations nil)   ;initial configurations.
+(defvar-local exwm--frame nil)            ;workspace frame
+(defvar-local exwm--floating-frame nil)   ;floating frame
+(defvar-local exwm--mode-line-format nil) ;save mode-line-format
+(defvar-local exwm--floating-frame-position nil) ;set when hidden.
+(defvar-local exwm--fixed-size nil)              ;fixed size
+(defvar-local exwm--selected-input-mode 'line-mode
+  "Input mode as selected by the user.
+One of `line-mode' or `char-mode'.")
+(defvar-local exwm--input-mode 'line-mode
+  "Actual input mode, i.e. whether mouse and keyboard are grabbed.")
+;; Properties
+(defvar-local exwm--desktop nil "_NET_WM_DESKTOP.")
+(defvar-local exwm-window-type nil "_NET_WM_WINDOW_TYPE.")
+(defvar-local exwm--geometry nil)
+(defvar-local exwm-class-name nil "Class name in WM_CLASS.")
+(defvar-local exwm-instance-name nil "Instance name in WM_CLASS.")
+(defvar-local exwm-title nil "Window title (either _NET_WM_NAME or WM_NAME).")
+(defvar-local exwm--title-is-utf8 nil)
+(defvar-local exwm-transient-for nil "WM_TRANSIENT_FOR.")
+(defvar-local exwm--protocols nil)
+(defvar-local exwm-state xcb:icccm:WM_STATE:NormalState "WM_STATE.")
+(defvar-local exwm--ewmh-state nil "_NET_WM_STATE.")
+;; _NET_WM_NORMAL_HINTS
+(defvar-local exwm--normal-hints-x nil)
+(defvar-local exwm--normal-hints-y nil)
+(defvar-local exwm--normal-hints-width nil)
+(defvar-local exwm--normal-hints-height nil)
+(defvar-local exwm--normal-hints-min-width nil)
+(defvar-local exwm--normal-hints-min-height nil)
+(defvar-local exwm--normal-hints-max-width nil)
+(defvar-local exwm--normal-hints-max-height nil)
+;; (defvar-local exwm--normal-hints-win-gravity nil)
+;; WM_HINTS
+(defvar-local exwm--hints-input nil)
+(defvar-local exwm--hints-urgency nil)
+;; _MOTIF_WM_HINTS
+(defvar-local exwm--mwm-hints-decorations t)
+
+(defvar exwm-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "\C-c\C-d\C-l" #'xcb-debug:clear)
+    (define-key map "\C-c\C-d\C-m" #'xcb-debug:mark)
+    (define-key map "\C-c\C-d\C-t" #'exwm-debug)
+    (define-key map "\C-c\C-f" #'exwm-layout-set-fullscreen)
+    (define-key map "\C-c\C-h" #'exwm-floating-hide)
+    (define-key map "\C-c\C-k" #'exwm-input-release-keyboard)
+    (define-key map "\C-c\C-m" #'exwm-workspace-move-window)
+    (define-key map "\C-c\C-q" #'exwm-input-send-next-key)
+    (define-key map "\C-c\C-t\C-f" #'exwm-floating-toggle-floating)
+    (define-key map "\C-c\C-t\C-m" #'exwm-layout-toggle-mode-line)
+    map)
+  "Keymap for `exwm-mode'.")
+
+(defvar exwm--kmacro-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map [t]
+      (lambda ()
+        (interactive)
+        (cond
+         ((or exwm-input-line-mode-passthrough
+              ;; Do not test `exwm-input--during-command'.
+              (active-minibuffer-window)
+              (memq last-input-event exwm-input--global-prefix-keys)
+              (memq last-input-event exwm-input-prefix-keys)
+              (lookup-key exwm-mode-map (vector last-input-event))
+              (gethash last-input-event exwm-input--simulation-keys))
+          (set-transient-map (make-composed-keymap (list exwm-mode-map
+                                                         global-map)))
+          (push last-input-event unread-command-events))
+         (t
+          (exwm-input--fake-key last-input-event)))))
+    map)
+  "Keymap used when executing keyboard macros.")
+
+;; This menu mainly acts as an reminder for users.  Thus it should be as
+;; detailed as possible, even some entries do not make much sense here.
+;; Also, inactive entries should be disabled rather than hidden.
+(easy-menu-define exwm-mode-menu exwm-mode-map
+  "Menu for `exwm-mode'."
+  '("EXWM"
+    "---"
+    "*General*"
+    "---"
+    ["Toggle floating" exwm-floating-toggle-floating]
+    ["Toggle fullscreen mode" exwm-layout-toggle-fullscreen]
+    ["Hide window" exwm-floating-hide exwm--floating-frame]
+    ["Close window" (kill-buffer (current-buffer))]
+
+    "---"
+    "*Resizing*"
+    "---"
+    ["Toggle mode-line" exwm-layout-toggle-mode-line]
+    ["Enlarge window vertically" exwm-layout-enlarge-window]
+    ["Enlarge window horizontally" exwm-layout-enlarge-window-horizontally]
+    ["Shrink window vertically" exwm-layout-shrink-window]
+    ["Shrink window horizontally" exwm-layout-shrink-window-horizontally]
+
+    "---"
+    "*Keyboard*"
+    "---"
+    ["Toggle keyboard mode" exwm-input-toggle-keyboard]
+    ["Send key" exwm-input-send-next-key (eq exwm--input-mode 'line-mode)]
+    ;; This is merely a reference.
+    ("Send simulation key" :filter
+     (lambda (&rest _args)
+       (let (result)
+         (maphash
+          (lambda (key value)
+            (when (sequencep key)
+              (setq result (append result
+                                   `([
+                                      ,(format "Send '%s'"
+                                               (key-description value))
+                                      (lambda ()
+                                        (interactive)
+                                        (dolist (i ',value)
+                                          (exwm-input--fake-key i)))
+                                      :keys ,(key-description key)])))))
+          exwm-input--simulation-keys)
+         result)))
+
+    ["Define global binding" exwm-input-set-key]
+
+    "---"
+    "*Workspace*"
+    "---"
+    ["Add workspace" exwm-workspace-add]
+    ["Delete current workspace" exwm-workspace-delete]
+    ["Move workspace to" exwm-workspace-move]
+    ["Swap workspaces" exwm-workspace-swap]
+    ["Move X window to" exwm-workspace-move-window]
+    ["Move X window from" exwm-workspace-switch-to-buffer]
+    ["Toggle minibuffer" exwm-workspace-toggle-minibuffer]
+    ["Switch workspace" exwm-workspace-switch]
+    ;; Place this entry at bottom to avoid selecting others by accident.
+    ("Switch to" :filter
+     (lambda (&rest _args)
+       (mapcar (lambda (i)
+                 `[,(format "Workspace %d" i)
+                   (lambda ()
+                     (interactive)
+                     (exwm-workspace-switch ,i))
+                   (/= ,i exwm-workspace-current-index)])
+               (number-sequence 0 (1- (exwm-workspace--count))))))))
+
+(define-derived-mode exwm-mode nil "EXWM"
+  "Major mode for managing X windows.
+
+\\{exwm-mode-map}"
+  ;;
+  (setq mode-name
+        '(:eval (propertize "EXWM" 'face
+                            (when (cl-some (lambda (i)
+                                             (frame-parameter i 'exwm-urgency))
+                                           exwm-workspace--list)
+                              'font-lock-warning-face))))
+  ;; Change major-mode is not allowed
+  (add-hook 'change-major-mode-hook #'kill-buffer nil t)
+  ;; Kill buffer -> close window
+  (add-hook 'kill-buffer-query-functions
+            #'exwm-manage--kill-buffer-query-function nil t)
+  ;; Redirect events when executing keyboard macros.
+  (push `(executing-kbd-macro . ,exwm--kmacro-map)
+        minor-mode-overriding-map-alist)
+  (setq buffer-read-only t
+        cursor-type nil
+        left-margin-width nil
+        right-margin-width nil
+        left-fringe-width 0
+        right-fringe-width 0
+        vertical-scroll-bar nil))
+
+
+
+(provide 'exwm-core)
+
+;;; exwm-core.el ends here
diff --git a/exwm-floating.el b/exwm-floating.el
new file mode 100644
index 0000000000..34d06a30db
--- /dev/null
+++ b/exwm-floating.el
@@ -0,0 +1,780 @@
+;;; exwm-floating.el --- Floating Module for EXWM  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2015-2024 Free Software Foundation, Inc.
+
+;; Author: Chris Feng <chris.w.feng@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module deals with the conversion between floating and non-floating
+;; states and implements moving/resizing operations on floating windows.
+
+;;; Code:
+
+(require 'xcb-cursor)
+(require 'exwm-core)
+
+(defgroup exwm-floating nil
+  "Floating."
+  :group 'exwm)
+
+(defcustom exwm-floating-setup-hook nil
+  "Normal hook run when an X window has been made floating.
+This hook runs in the context of the corresponding buffer."
+  :type 'hook)
+
+(defcustom exwm-floating-exit-hook nil
+  "Normal hook run when an X window has exited floating state.
+This hook runs in the context of the corresponding buffer."
+  :type 'hook)
+
+(defcustom exwm-floating-border-color "navy"
+  "Border color of floating windows."
+  :type 'color
+  :initialize #'custom-initialize-default
+  :set (lambda (symbol value)
+         (set-default symbol value)
+         ;; Change border color for all floating X windows.
+         (when exwm--connection
+           (let ((border-pixel (exwm--color->pixel value)))
+             (when border-pixel
+               (dolist (pair exwm--id-buffer-alist)
+                 (with-current-buffer (cdr pair)
+                   (when exwm--floating-frame
+                     (xcb:+request exwm--connection
+                         (make-instance 'xcb:ChangeWindowAttributes
+                                        :window
+                                        (frame-parameter exwm--floating-frame
+                                                         'exwm-container)
+                                        :value-mask xcb:CW:BorderPixel
+                                        :border-pixel border-pixel)))))
+               (xcb:flush exwm--connection))))))
+
+(defcustom exwm-floating-border-width 1
+  "Border width of floating windows."
+  :type '(integer
+          :validate (lambda (widget)
+                      (when (< (widget-value widget) 0)
+                        (widget-put widget :error "Border width is at least 0")
+                        widget)))
+  :initialize #'custom-initialize-default
+  :set (lambda (symbol value)
+         (let ((delta (- value exwm-floating-border-width))
+               container)
+           (set-default symbol value)
+           ;; Change border width for all floating X windows.
+           (dolist (pair exwm--id-buffer-alist)
+             (with-current-buffer (cdr pair)
+               (when exwm--floating-frame
+                 (setq container (frame-parameter exwm--floating-frame
+                                                  'exwm-container))
+                 (with-slots (x y)
+                     (xcb:+request-unchecked+reply exwm--connection
+                         (make-instance 'xcb:GetGeometry
+                                        :drawable container))
+                   (xcb:+request exwm--connection
+                       (make-instance 'xcb:ConfigureWindow
+                                      :window container
+                                      :value-mask
+                                      (logior xcb:ConfigWindow:X
+                                              xcb:ConfigWindow:Y
+                                              xcb:ConfigWindow:BorderWidth)
+                                      :border-width value
+                                      :x (- x delta)
+                                      :y (- y delta)))))))
+           (when exwm--connection
+             (xcb:flush exwm--connection)))))
+
+;; Cursors for moving/resizing a window
+(defvar exwm-floating--cursor-move nil)
+(defvar exwm-floating--cursor-top-left nil)
+(defvar exwm-floating--cursor-top nil)
+(defvar exwm-floating--cursor-top-right nil)
+(defvar exwm-floating--cursor-right nil)
+(defvar exwm-floating--cursor-bottom-right nil)
+(defvar exwm-floating--cursor-bottom nil)
+(defvar exwm-floating--cursor-bottom-left nil)
+(defvar exwm-floating--cursor-left nil)
+
+(defvar exwm-floating--moveresize-calculate nil
+  "Calculate move/resize parameters [buffer event-mask x y width height].")
+
+(defvar exwm-workspace--current)
+(defvar exwm-workspace--frame-y-offset)
+(defvar exwm-workspace--window-y-offset)
+(declare-function exwm-layout--hide "exwm-layout.el" (id))
+(declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id))
+(declare-function exwm-layout--refresh "exwm-layout.el" ())
+(declare-function exwm-layout--show "exwm-layout.el" (id &optional window))
+(declare-function exwm-workspace--position "exwm-workspace.el" (frame))
+(declare-function exwm-workspace--update-offsets "exwm-workspace.el" ())
+(declare-function exwm-workspace--workarea "exwm-workspace.el" (frame))
+
+(defun exwm-floating--set-allowed-actions (id tilling)
+  "Set _NET_WM_ALLOWED_ACTIONS."
+  (exwm--log "#x%x" id)
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ewmh:set-_NET_WM_ALLOWED_ACTIONS
+                     :window id
+                     :data (if tilling
+                               (vector xcb:Atom:_NET_WM_ACTION_MINIMIZE
+                                       xcb:Atom:_NET_WM_ACTION_FULLSCREEN
+                                       xcb:Atom:_NET_WM_ACTION_CHANGE_DESKTOP
+                                       xcb:Atom:_NET_WM_ACTION_CLOSE)
+                             (vector xcb:Atom:_NET_WM_ACTION_MOVE
+                                     xcb:Atom:_NET_WM_ACTION_RESIZE
+                                     xcb:Atom:_NET_WM_ACTION_MINIMIZE
+                                     xcb:Atom:_NET_WM_ACTION_FULLSCREEN
+                                     xcb:Atom:_NET_WM_ACTION_CHANGE_DESKTOP
+                                     xcb:Atom:_NET_WM_ACTION_CLOSE)))))
+
+(defun exwm-floating--set-floating (id)
+  "Make window ID floating."
+  (let ((window (get-buffer-window (exwm--id->buffer id))))
+    (when window
+      ;; Hide the non-floating X window first.
+      (set-window-buffer window (other-buffer nil t))))
+  (let* ((original-frame (buffer-local-value 'exwm--frame
+                                             (exwm--id->buffer id)))
+         ;; Create new frame
+         (frame (with-current-buffer
+                    (or (get-buffer "*scratch*")
+                        (progn
+                          (set-buffer-major-mode
+                           (get-buffer-create "*scratch*"))
+                          (get-buffer "*scratch*")))
+                  (make-frame
+                   `((minibuffer . ,(minibuffer-window exwm--frame))
+                     (tab-bar-lines . 0)
+                     (tab-bar-lines-keep-state . t)
+                     (left . ,(* window-min-width -10000))
+                     (top . ,(* window-min-height -10000))
+                     (width . ,window-min-width)
+                     (height . ,window-min-height)
+                     (unsplittable . t))))) ;and fix the size later
+         (outer-id (string-to-number (frame-parameter frame 'outer-window-id)))
+         (window-id (string-to-number (frame-parameter frame 'window-id)))
+         (frame-container (xcb:generate-id exwm--connection))
+         (window (frame-first-window frame)) ;and it's the only window
+         (x (slot-value exwm--geometry 'x))
+         (y (slot-value exwm--geometry 'y))
+         (width (slot-value exwm--geometry 'width))
+         (height (slot-value exwm--geometry 'height)))
+    ;; Force drawing menu-bar & tool-bar.
+    (redisplay t)
+    (exwm-workspace--update-offsets)
+    (exwm--log "Floating geometry (original): %dx%d%+d%+d" width height x y)
+    ;; Save frame parameters.
+    (set-frame-parameter frame 'exwm-outer-id outer-id)
+    (set-frame-parameter frame 'exwm-id window-id)
+    (set-frame-parameter frame 'exwm-container frame-container)
+    ;; Fix illegal parameters
+    ;; FIXME: check normal hints restrictions
+    (with-slots ((x* x) (y* y) (width* width) (height* height))
+        (exwm-workspace--workarea original-frame)
+      ;; Center floating windows
+      (when (and (or (= x 0) (= x x*))
+                 (or (= y 0) (= y y*)))
+        (let ((buffer (exwm--id->buffer exwm-transient-for))
+              window edges)
+          (when (and buffer (setq window (get-buffer-window buffer)))
+            (setq edges (window-inside-absolute-pixel-edges window))
+            (unless (and (<= width (- (elt edges 2) (elt edges 0)))
+                         (<= height (- (elt edges 3) (elt edges 1))))
+              (setq edges nil)))
+          (if edges
+              ;; Put at the center of leading window
+              (setq x (+ x* (/ (- (elt edges 2) (elt edges 0) width) 2))
+                    y (+ y* (/ (- (elt edges 3) (elt edges 1) height) 2)))
+            ;; Put at the center of screen
+            (setq x (/ (- width* width) 2)
+                  y (/ (- height* height) 2)))))
+      (if (> width width*)
+          ;; Too wide
+          (progn (setq x x*
+                       width width*))
+        ;; Invalid width
+        (when (= 0 width) (setq width (/ width* 2)))
+        ;; Make sure at least half of the window is visible
+        (unless (< x* (+ x (/ width 2)) (+ x* width*))
+          (setq x (+ x* (/ (- width* width) 2)))))
+      (if (> height height*)
+          ;; Too tall
+          (setq y y*
+                height height*)
+        ;; Invalid height
+        (when (= 0 height) (setq height (/ height* 2)))
+        ;; Make sure at least half of the window is visible
+        (unless (< y* (+ y (/ height 2)) (+ y* height*))
+          (setq y (+ y* (/ (- height* height) 2)))))
+      ;; The geometry can be overridden by user options.
+      (let ((x** (plist-get exwm--configurations 'x))
+            (y** (plist-get exwm--configurations 'y))
+            (width** (plist-get exwm--configurations 'width))
+            (height** (plist-get exwm--configurations 'height)))
+        (if (integerp x**)
+            (setq x (+ x* x**))
+          (when (and (floatp x**)
+                     (>= 1 x** 0))
+            (setq x (+ x* (round (* x** width*))))))
+        (if (integerp y**)
+            (setq y (+ y* y**))
+          (when (and (floatp y**)
+                     (>= 1 y** 0))
+            (setq y (+ y* (round (* y** height*))))))
+        (if (integerp width**)
+            (setq width width**)
+          (when (and (floatp width**)
+                     (> 1 width** 0))
+            (setq width (max 1 (round (* width** width*))))))
+        (if (integerp height**)
+            (setq height height**)
+          (when (and (floatp height**)
+                     (> 1 height** 0))
+            (setq height (max 1 (round (* height** height*))))))))
+    (exwm--set-geometry id x y nil nil)
+    (xcb:flush exwm--connection)
+    (exwm--log "Floating geometry (corrected): %dx%d%+d%+d" width height x y)
+    ;; Fit frame to client
+    ;; It seems we have to make the frame invisible in order to resize it
+    ;; timely.
+    ;; The frame will be made visible by `select-frame-set-input-focus'.
+    (make-frame-invisible frame)
+    (let* ((edges (window-inside-pixel-edges window))
+           (frame-width (+ width (- (frame-pixel-width frame)
+                                    (- (elt edges 2) (elt edges 0)))))
+           (frame-height (+ height (- (frame-pixel-height frame)
+                                      (- (elt edges 3) (elt edges 1)))
+                            ;; Use `frame-outer-height' in the future.
+                            exwm-workspace--frame-y-offset))
+           (floating-mode-line (plist-get exwm--configurations
+                                          'floating-mode-line))
+           (floating-header-line (plist-get exwm--configurations
+                                            'floating-header-line))
+           (border-pixel (exwm--color->pixel exwm-floating-border-color)))
+      (if floating-mode-line
+          (setq exwm--mode-line-format (or exwm--mode-line-format
+                                           mode-line-format)
+                mode-line-format floating-mode-line)
+        (if (and (not (plist-member exwm--configurations 'floating-mode-line))
+                 exwm--mwm-hints-decorations)
+            (when exwm--mode-line-format
+              (setq mode-line-format exwm--mode-line-format))
+          ;; The mode-line need to be hidden in floating mode.
+          (setq frame-height (- frame-height (window-mode-line-height
+                                              (frame-root-window frame)))
+                exwm--mode-line-format (or exwm--mode-line-format
+                                           mode-line-format)
+                mode-line-format nil)))
+      (if floating-header-line
+          (setq header-line-format floating-header-line)
+        (if (and (not (plist-member exwm--configurations
+                                    'floating-header-line))
+                 exwm--mwm-hints-decorations)
+            (setq header-line-format nil)
+          ;; The header-line need to be hidden in floating mode.
+          (setq frame-height (- frame-height (window-header-line-height
+                                              (frame-root-window frame)))
+                header-line-format nil)))
+      (set-frame-size frame frame-width frame-height t)
+      ;; Create the frame container as the parent of the frame.
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:CreateWindow
+                         :depth 0
+                         :wid frame-container
+                         :parent exwm--root
+                         :x x
+                         :y (- y exwm-workspace--window-y-offset)
+                         :width width
+                         :height height
+                         :border-width
+                         (with-current-buffer (exwm--id->buffer id)
+                           (let ((border-witdh (plist-get exwm--configurations
+                                                          'border-width)))
+                             (if (and (integerp border-witdh)
+                                      (>= border-witdh 0))
+                                 border-witdh
+                               exwm-floating-border-width)))
+                         :class xcb:WindowClass:InputOutput
+                         :visual 0
+                         :value-mask (logior xcb:CW:BackPixmap
+                                             (if border-pixel
+                                                 xcb:CW:BorderPixel 0)
+                                             xcb:CW:OverrideRedirect)
+                         :background-pixmap xcb:BackPixmap:ParentRelative
+                         :border-pixel border-pixel
+                         :override-redirect 1))
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ewmh:set-_NET_WM_NAME
+                         :window frame-container
+                         :data
+                         (format "EXWM floating frame container for 0x%x" id)))
+      ;; Map it.
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:MapWindow :window frame-container))
+      ;; Put the X window right above this frame container.
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ConfigureWindow
+                         :window id
+                         :value-mask (logior xcb:ConfigWindow:Sibling
+                                             xcb:ConfigWindow:StackMode)
+                         :sibling frame-container
+                         :stack-mode xcb:StackMode:Above)))
+    ;; Reparent this frame to its container.
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ReparentWindow
+                       :window outer-id :parent frame-container :x 0 :y 0))
+    (exwm-floating--set-allowed-actions id nil)
+    (xcb:flush exwm--connection)
+    ;; Set window/buffer
+    (with-current-buffer (exwm--id->buffer id)
+      (setq window-size-fixed exwm--fixed-size
+            exwm--floating-frame frame)
+      ;; Do the refresh manually.
+      (remove-hook 'window-configuration-change-hook #'exwm-layout--refresh)
+      (set-window-buffer window (current-buffer)) ;this changes current buffer
+      (add-hook 'window-configuration-change-hook #'exwm-layout--refresh)
+      (set-window-dedicated-p window t)
+      (exwm-layout--show id window))
+    (with-current-buffer (exwm--id->buffer id)
+      (if (exwm-layout--iconic-state-p id)
+          ;; Hide iconic floating X windows.
+          (exwm-floating-hide)
+        (with-selected-frame exwm--frame
+          (exwm-layout--refresh)))
+      (select-frame-set-input-focus frame))
+    ;; FIXME: Strangely, the Emacs frame can move itself at this point
+    ;;        when there are left/top struts set.  Force resetting its
+    ;;        position seems working, but it'd better to figure out why.
+    ;; FIXME: This also happens in another case (#220) where the cause is
+    ;;        still unclear.
+    (exwm--set-geometry outer-id 0 0 nil nil)
+    (xcb:flush exwm--connection))
+  (with-current-buffer (exwm--id->buffer id)
+    (run-hooks 'exwm-floating-setup-hook))
+  ;; Redraw the frame.
+  (redisplay t))
+
+(defun exwm-floating--unset-floating (id)
+  "Make window ID non-floating."
+  (exwm--log "#x%x" id)
+  (let ((buffer (exwm--id->buffer id)))
+    (with-current-buffer buffer
+      (when exwm--floating-frame
+        ;; The X window is already mapped.
+        ;; Unmap the X window.
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:ChangeWindowAttributes
+                           :window id :value-mask xcb:CW:EventMask
+                           :event-mask xcb:EventMask:NoEvent))
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:UnmapWindow :window id))
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:ChangeWindowAttributes
+                           :window id :value-mask xcb:CW:EventMask
+                           :event-mask (exwm--get-client-event-mask)))
+        ;; Reparent the floating frame back to the root window.
+        (let ((frame-id (frame-parameter exwm--floating-frame 'exwm-outer-id))
+              (frame-container (frame-parameter exwm--floating-frame
+                                                'exwm-container)))
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:UnmapWindow :window frame-id))
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:ReparentWindow
+                             :window frame-id
+                             :parent exwm--root
+                             :x 0 :y 0))
+          ;; Also destroy its container.
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:DestroyWindow :window frame-container))))
+      ;; Place the X window just above the reference X window.
+      ;; (the stacking order won't change from now on).
+      ;; Also hide the possible floating border.
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ConfigureWindow
+                         :window id
+                         :value-mask (logior xcb:ConfigWindow:BorderWidth
+                                             xcb:ConfigWindow:Sibling
+                                             xcb:ConfigWindow:StackMode)
+                         :border-width 0
+                         :sibling exwm--guide-window
+                         :stack-mode xcb:StackMode:Above)))
+    (exwm-floating--set-allowed-actions id t)
+    (xcb:flush exwm--connection)
+    (with-current-buffer buffer
+      (when exwm--floating-frame        ;from floating to non-floating
+        (set-window-dedicated-p (frame-first-window exwm--floating-frame) nil)
+        ;; Select a tiling window and delete the old frame.
+        (select-window (frame-selected-window exwm-workspace--current))
+        (with-current-buffer buffer
+          (delete-frame exwm--floating-frame))))
+    (with-current-buffer buffer
+      (setq window-size-fixed nil
+            exwm--floating-frame nil)
+      (if (not (plist-member exwm--configurations 'tiling-mode-line))
+          (when exwm--mode-line-format
+            (setq mode-line-format exwm--mode-line-format))
+        (setq exwm--mode-line-format (or exwm--mode-line-format
+                                         mode-line-format)
+              mode-line-format (plist-get exwm--configurations
+                                          'tiling-mode-line)))
+      (if (not (plist-member exwm--configurations 'tiling-header-line))
+          (setq header-line-format nil)
+        (setq header-line-format (plist-get exwm--configurations
+                                            'tiling-header-line))))
+    ;; Only show X windows in normal state.
+    (unless (exwm-layout--iconic-state-p)
+      (pop-to-buffer-same-window buffer)))
+  (with-current-buffer (exwm--id->buffer id)
+    (run-hooks 'exwm-floating-exit-hook)))
+
+;;;###autoload
+(cl-defun exwm-floating-toggle-floating ()
+  "Toggle the current window between floating and non-floating states."
+  (interactive)
+  (exwm--log)
+  (unless (derived-mode-p 'exwm-mode)
+    (cl-return-from exwm-floating-toggle-floating))
+  (with-current-buffer (window-buffer)
+    (if exwm--floating-frame
+        (exwm-floating--unset-floating exwm--id)
+      (exwm-floating--set-floating exwm--id))))
+
+;;;###autoload
+(defun exwm-floating-hide ()
+  "Hide the current floating X window (which would show again when selected)."
+  (interactive)
+  (exwm--log)
+  (when (and (derived-mode-p 'exwm-mode)
+             exwm--floating-frame)
+    (exwm-layout--hide exwm--id)
+    (select-frame-set-input-focus exwm-workspace--current)))
+
+(defun exwm-floating--start-moveresize (id &optional type)
+  "Start move/resize."
+  (exwm--log "#x%x" id)
+  (let ((buffer-or-id (or (exwm--id->buffer id) id))
+        frame container-or-id x y width height cursor)
+    (if (bufferp buffer-or-id)
+        ;; Managed.
+        (with-current-buffer buffer-or-id
+          (setq frame exwm--floating-frame
+                container-or-id (frame-parameter exwm--floating-frame
+                                                 'exwm-container)))
+      ;; Unmanaged.
+      (setq container-or-id id))
+    (when (and container-or-id
+               ;; Test if the pointer can be grabbed
+               (= xcb:GrabStatus:Success
+                  (slot-value
+                   (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:GrabPointer
+                                      :owner-events 0
+                                      :grab-window container-or-id
+                                      :event-mask xcb:EventMask:NoEvent
+                                      :pointer-mode xcb:GrabMode:Async
+                                      :keyboard-mode xcb:GrabMode:Async
+                                      :confine-to xcb:Window:None
+                                      :cursor xcb:Cursor:None
+                                      :time xcb:Time:CurrentTime))
+                   'status)))
+      (with-slots (root-x root-y win-x win-y)
+          (xcb:+request-unchecked+reply exwm--connection
+              (make-instance 'xcb:QueryPointer :window id))
+        (if (not (bufferp buffer-or-id))
+            ;; Unmanaged.
+            (unless (eq type xcb:ewmh:_NET_WM_MOVERESIZE_MOVE)
+              (with-slots ((width* width)
+                           (height* height))
+                  (xcb:+request-unchecked+reply exwm--connection
+                      (make-instance 'xcb:GetGeometry :drawable id))
+                (setq width width*
+                      height height*)))
+          ;; Managed.
+          (select-window (frame-first-window frame)) ;transfer input focus
+          (setq width (frame-pixel-width frame)
+                height (frame-pixel-height frame))
+          (unless type
+            ;; Determine the resize type according to the pointer position
+            ;; Clicking the center 1/3 part to resize has no effect
+            (setq x (/ (* 3 win-x) (float width))
+                  y (/ (* 3 win-y) (float height))
+                  type (cond ((and (< x 1) (< y 1))
+                              xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOPLEFT)
+                             ((and (> x 2) (< y 1))
+                              xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOPRIGHT)
+                             ((and (> x 2) (> y 2))
+                              xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT)
+                             ((and (< x 1) (> y 2))
+                              xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT)
+                             ((> x 2) xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_RIGHT)
+                             ((> y 2) xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOM)
+                             ((< x 1) xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_LEFT)
+                             ((< y 1) xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOP)))))
+        (if (not type)
+            (exwm-floating--stop-moveresize)
+          (cond ((= type xcb:ewmh:_NET_WM_MOVERESIZE_MOVE)
+                 (setq cursor exwm-floating--cursor-move
+                       exwm-floating--moveresize-calculate
+                       (lambda (x y)
+                         (vector buffer-or-id
+                                 (eval-when-compile
+                                   (logior xcb:ConfigWindow:X
+                                           xcb:ConfigWindow:Y))
+                                 (- x win-x) (- y win-y) 0 0))))
+                ((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOPLEFT)
+                 (setq cursor exwm-floating--cursor-top-left
+                       exwm-floating--moveresize-calculate
+                       (lambda (x y)
+                         (vector buffer-or-id
+                                 (eval-when-compile
+                                   (logior xcb:ConfigWindow:X
+                                           xcb:ConfigWindow:Y
+                                           xcb:ConfigWindow:Width
+                                           xcb:ConfigWindow:Height))
+                                 (- x win-x) (- y win-y)
+                                 (- (+ root-x width) x)
+                                 (- (+ root-y height) y)))))
+                ((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOP)
+                 (setq cursor exwm-floating--cursor-top
+                       exwm-floating--moveresize-calculate
+                       (lambda (_x y)
+                         (vector buffer-or-id
+                                 (eval-when-compile
+                                   (logior xcb:ConfigWindow:Y
+                                           xcb:ConfigWindow:Height))
+                                 0 (- y win-y) 0 (- (+ root-y height) y)))))
+                ((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_TOPRIGHT)
+                 (setq cursor exwm-floating--cursor-top-right
+                       exwm-floating--moveresize-calculate
+                       (lambda (x y)
+                         (vector buffer-or-id
+                                 (eval-when-compile
+                                   (logior xcb:ConfigWindow:Y
+                                           xcb:ConfigWindow:Width
+                                           xcb:ConfigWindow:Height))
+                                 0 (- y win-y) (- x (- root-x width))
+                                 (- (+ root-y height) y)))))
+                ((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_RIGHT)
+                 (setq cursor exwm-floating--cursor-right
+                       exwm-floating--moveresize-calculate
+                       (lambda (x _y)
+                         (vector buffer-or-id
+                                 xcb:ConfigWindow:Width
+                                 0 0 (- x (- root-x width)) 0))))
+                ((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT)
+                 (setq cursor exwm-floating--cursor-bottom-right
+                       exwm-floating--moveresize-calculate
+                       (lambda (x y)
+                         (vector buffer-or-id
+                                 (eval-when-compile
+                                   (logior xcb:ConfigWindow:Width
+                                           xcb:ConfigWindow:Height))
+                                 0 0 (- x (- root-x width))
+                                 (- y (- root-y height))))))
+                ((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOM)
+                 (setq cursor exwm-floating--cursor-bottom
+                       exwm-floating--moveresize-calculate
+                       (lambda (_x y)
+                         (vector buffer-or-id
+                                 xcb:ConfigWindow:Height
+                                 0 0 0 (- y (- root-y height))))))
+                ((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT)
+                 (setq cursor exwm-floating--cursor-bottom-left
+                       exwm-floating--moveresize-calculate
+                       (lambda (x y)
+                         (vector buffer-or-id
+                                 (eval-when-compile
+                                   (logior xcb:ConfigWindow:X
+                                           xcb:ConfigWindow:Width
+                                           xcb:ConfigWindow:Height))
+                                 (- x win-x)
+                                 0
+                                 (- (+ root-x width) x)
+                                 (- y (- root-y height))))))
+                ((= type xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_LEFT)
+                 (setq cursor exwm-floating--cursor-left
+                       exwm-floating--moveresize-calculate
+                       (lambda (x _y)
+                         (vector buffer-or-id
+                                 (eval-when-compile
+                                   (logior xcb:ConfigWindow:X
+                                           xcb:ConfigWindow:Width))
+                                 (- x win-x) 0 (- (+ root-x width) x) 0)))))
+          ;; Select events and change cursor (should always succeed)
+          (xcb:+request-unchecked+reply exwm--connection
+              (make-instance 'xcb:GrabPointer
+                             :owner-events 0 :grab-window container-or-id
+                             :event-mask (eval-when-compile
+                                           (logior xcb:EventMask:ButtonRelease
+                                                   xcb:EventMask:ButtonMotion))
+                             :pointer-mode xcb:GrabMode:Async
+                             :keyboard-mode xcb:GrabMode:Async
+                             :confine-to xcb:Window:None
+                             :cursor cursor
+                             :time xcb:Time:CurrentTime)))))))
+
+(defun exwm-floating--stop-moveresize (&rest _args)
+  "Stop move/resize."
+  (exwm--log)
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:UngrabPointer :time xcb:Time:CurrentTime))
+  (when exwm-floating--moveresize-calculate
+    (let (result buffer-or-id outer-id container-id)
+      (setq result (funcall exwm-floating--moveresize-calculate 0 0)
+            buffer-or-id (aref result 0))
+      (when (bufferp buffer-or-id)
+        (with-current-buffer buffer-or-id
+          (setq outer-id (frame-parameter exwm--floating-frame 'exwm-outer-id)
+                container-id (frame-parameter exwm--floating-frame
+                                              'exwm-container))
+          (with-slots (x y width height border-width)
+              (xcb:+request-unchecked+reply exwm--connection
+                  (make-instance 'xcb:GetGeometry
+                                 :drawable container-id))
+            ;; Notify Emacs frame about this the position change.
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:SendEvent
+                               :propagate 0
+                               :destination outer-id
+                               :event-mask xcb:EventMask:StructureNotify
+                               :event
+                               (xcb:marshal
+                                (make-instance 'xcb:ConfigureNotify
+                                               :event outer-id
+                                               :window outer-id
+                                               :above-sibling xcb:Window:None
+                                               :x (+ x border-width)
+                                               :y (+ y border-width)
+                                               :width width
+                                               :height height
+                                               :border-width 0
+                                               :override-redirect 0)
+                                exwm--connection)))
+            (xcb:flush exwm--connection))
+          (exwm-layout--show exwm--id
+                             (frame-root-window exwm--floating-frame)))))
+    (setq exwm-floating--moveresize-calculate nil)))
+
+(defun exwm-floating--do-moveresize (data _synthetic)
+  "Perform move/resize."
+  (when exwm-floating--moveresize-calculate
+    (let* ((obj (make-instance 'xcb:MotionNotify))
+           result value-mask x y width height buffer-or-id container-or-id)
+      (xcb:unmarshal obj data)
+      (setq result (funcall exwm-floating--moveresize-calculate
+                            (slot-value obj 'root-x) (slot-value obj 'root-y))
+            buffer-or-id (aref result 0)
+            value-mask (aref result 1)
+            x (aref result 2)
+            y (aref result 3)
+            width (max 1 (aref result 4))
+            height (max 1 (aref result 5)))
+      (if (not (bufferp buffer-or-id))
+          ;; Unmanaged.
+          (setq container-or-id buffer-or-id)
+        ;; Managed.
+        (setq container-or-id
+              (with-current-buffer buffer-or-id
+                (frame-parameter exwm--floating-frame 'exwm-container))
+              x (- x exwm-floating-border-width)
+              ;; Use `frame-outer-height' in the future.
+              y (- y exwm-floating-border-width
+                   exwm-workspace--window-y-offset)
+              height (+ height exwm-workspace--window-y-offset)))
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ConfigureWindow
+                         :window container-or-id
+                         :value-mask (aref result 1)
+                         :x x
+                         :y y
+                         :width width
+                         :height height))
+      (when (bufferp buffer-or-id)
+        ;; Managed.
+        (setq value-mask (logand value-mask (logior xcb:ConfigWindow:Width
+                                                    xcb:ConfigWindow:Height)))
+        (when (/= 0 value-mask)
+          (with-current-buffer buffer-or-id
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:ConfigureWindow
+                               :window (frame-parameter exwm--floating-frame
+                                                        'exwm-outer-id)
+                               :value-mask value-mask
+                               :width width
+                               :height height)))))
+      (xcb:flush exwm--connection))))
+
+(defun exwm-floating-move (&optional delta-x delta-y)
+  "Move a floating window right by DELTA-X pixels and down by DELTA-Y pixels.
+
+Both DELTA-X and DELTA-Y default to 1.  This command should be bound locally."
+  (exwm--log "delta-x: %s, delta-y: %s" delta-x delta-y)
+  (unless (and (derived-mode-p 'exwm-mode) exwm--floating-frame)
+    (user-error "[EXWM] `exwm-floating-move' is only for floating X windows"))
+  (unless delta-x (setq delta-x 1))
+  (unless delta-y (setq delta-y 1))
+  (unless (and (= 0 delta-x) (= 0 delta-y))
+    (let* ((floating-container (frame-parameter exwm--floating-frame
+                                                'exwm-container))
+           (geometry (xcb:+request-unchecked+reply exwm--connection
+                         (make-instance 'xcb:GetGeometry
+                                        :drawable floating-container)))
+           (edges (window-inside-absolute-pixel-edges)))
+      (with-slots (x y) geometry
+        (exwm--set-geometry floating-container
+                            (+ x delta-x) (+ y delta-y) nil nil))
+      (exwm--set-geometry exwm--id
+                          (+ (pop edges) delta-x)
+                          (+ (pop edges) delta-y)
+                          nil nil))
+    (xcb:flush exwm--connection)))
+
+(defun exwm-floating--init ()
+  "Initialize floating module."
+  (exwm--log)
+  ;; Initialize cursors for moving/resizing a window
+  (xcb:cursor:init exwm--connection)
+  (setq exwm-floating--cursor-move
+        (xcb:cursor:load-cursor exwm--connection "fleur")
+        exwm-floating--cursor-top-left
+        (xcb:cursor:load-cursor exwm--connection "top_left_corner")
+        exwm-floating--cursor-top
+        (xcb:cursor:load-cursor exwm--connection "top_side")
+        exwm-floating--cursor-top-right
+        (xcb:cursor:load-cursor exwm--connection "top_right_corner")
+        exwm-floating--cursor-right
+        (xcb:cursor:load-cursor exwm--connection "right_side")
+        exwm-floating--cursor-bottom-right
+        (xcb:cursor:load-cursor exwm--connection "bottom_right_corner")
+        exwm-floating--cursor-bottom
+        (xcb:cursor:load-cursor exwm--connection "bottom_side")
+        exwm-floating--cursor-bottom-left
+        (xcb:cursor:load-cursor exwm--connection "bottom_left_corner")
+        exwm-floating--cursor-left
+        (xcb:cursor:load-cursor exwm--connection "left_side")))
+
+(defun exwm-floating--exit ()
+  "Exit the floating module."
+  (exwm--log))
+
+
+
+(provide 'exwm-floating)
+
+;;; exwm-floating.el ends here
diff --git a/exwm-input.el b/exwm-input.el
new file mode 100644
index 0000000000..f1f035c91a
--- /dev/null
+++ b/exwm-input.el
@@ -0,0 +1,1248 @@
+;;; exwm-input.el --- Input Module for EXWM  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2015-2024 Free Software Foundation, Inc.
+
+;; Author: Chris Feng <chris.w.feng@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module deals with key/mouse matters, including:
+;; + Input focus,
+;; + Key/Button event handling,
+;; + Key events filtering and simulation.
+
+;; Todo:
+;; + Pointer simulation mode (e.g. 'C-c 1'/'C-c 2' for single/double click,
+;;   move with arrow keys).
+;; + Simulation keys to mimic Emacs key bindings for text edit (redo, select,
+;;   cancel, clear, etc).  Some of them are not present on common keyboard
+;;   (keycode = 0).  May need to use XKB extension.
+
+;;; Code:
+
+(require 'xcb-keysyms)
+(require 'exwm-core)
+
+(defgroup exwm-input nil
+  "Input."
+  :group 'exwm)
+
+(defcustom exwm-input-prefix-keys
+  '(?\C-x ?\C-u ?\C-h ?\M-x ?\M-` ?\M-& ?\M-:)
+  "List of prefix keys EXWM should forward to Emacs when in `line-mode'.
+
+The point is to make keys like 'C-x C-f' forwarded to Emacs in `line-mode'.
+There is no need to add prefix keys for global/simulation keys or those
+defined in `exwm-mode-map' here."
+  :type '(repeat key-sequence)
+  :get (lambda (symbol)
+         (mapcar #'vector (default-value symbol)))
+  :set (lambda (symbol value)
+         (set symbol (mapcar (lambda (i)
+                               (if (sequencep i)
+                                   (aref i 0)
+                                 i))
+                             value))))
+
+(defcustom exwm-input-move-event 's-down-mouse-1
+  "Emacs event to start moving a window."
+  :type 'key-sequence
+  :get (lambda (symbol)
+         (let ((value (default-value symbol)))
+           (if (mouse-event-p value)
+               value
+             (vector value))))
+  :set (lambda (symbol value)
+         (set symbol (if (sequencep value)
+                         (aref value 0)
+                       value))))
+
+(defcustom exwm-input-resize-event 's-down-mouse-3
+  "Emacs event to start resizing a window."
+  :type 'key-sequence
+  :get (lambda (symbol)
+         (let ((value (default-value symbol)))
+           (if (mouse-event-p value)
+               value
+             (vector value))))
+  :set (lambda (symbol value)
+         (set symbol (if (sequencep value)
+                         (aref value 0)
+                       value))))
+
+(defcustom exwm-input-line-mode-passthrough nil
+  "Non-nil makes `line-mode' forward all events to Emacs."
+  :type 'boolean)
+
+;; Input focus update requests should be accumulated for a short time
+;; interval so that only the last one need to be processed.  This not
+;; improves the overall performance, but avoids the problem of input
+;; focus loop, which is a result of the interaction with Emacs frames.
+;;
+;; FIXME: The time interval is hard to decide and perhaps machine-dependent.
+;;        A value too small can cause redundant updates of input focus,
+;;        and even worse, dead loops.  OTOH a large value would bring
+;;        laggy experience.
+(defconst exwm-input--update-focus-interval 0.01
+  "Time interval (in seconds) for accumulating input focus update requests.")
+
+(defconst exwm-input--passthrough-functions '(read-char
+                                              read-char-exclusive
+                                              read-key-sequence-vector
+                                              read-key-sequence
+                                              read-event)
+  "Low-level read functions that must be exempted from EXWM input handling.")
+
+(defvar exwm-input--during-command nil
+  "Indicate whether between `pre-command-hook' and `post-command-hook'.")
+
+(defvar exwm-input--global-keys nil "Global key bindings.")
+
+(defvar exwm-input--global-prefix-keys nil
+  "List of prefix keys of global key bindings.")
+
+(defvar exwm-input--line-mode-cache nil "Cache for incomplete key sequence.")
+
+(defvar exwm-input--local-simulation-keys nil
+  "Whether simulation keys are local.")
+
+(defvar exwm-input--simulation-keys nil "Simulation keys in `line-mode'.")
+
+(defvar exwm-input--skip-buffer-list-update nil
+  "Skip the upcoming `buffer-list-update'.")
+
+(defvar exwm-input--temp-line-mode nil
+  "Non-nil indicates it's in temporary line-mode for `char-mode'.")
+
+(defvar exwm-input--timestamp-atom nil)
+
+(defvar exwm-input--timestamp-callback nil)
+
+(defvar exwm-input--timestamp-window nil)
+
+(defvar exwm-input--update-focus-timer nil
+  "Timer for deferring the update of input focus.")
+
+(defvar exwm-input--update-focus-lock nil
+  "Lock for solving input focus update contention.")
+
+(defvar exwm-input--update-focus-window nil "The (Emacs) window to be focused.
+This value should always be overwritten.")
+
+(defvar exwm-input--echo-area-timer nil "Timer for detecting echo area dirty.")
+
+(defvar exwm-input--event-hook nil
+  "Hook to run when EXWM receives an event.")
+
+(defvar exwm-input-input-mode-change-hook nil
+  "Hook to run when an input mode changes on an `exwm-mode' buffer.
+Current buffer will be the `exwm-mode' buffer when this hook runs.")
+
+(defvar exwm-workspace--current)
+(declare-function exwm-floating--do-moveresize "exwm-floating.el"
+                  (data _synthetic))
+(declare-function exwm-floating--start-moveresize "exwm-floating.el"
+                  (id &optional type))
+(declare-function exwm-floating--stop-moveresize "exwm-floating.el"
+                  (&rest _args))
+(declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id))
+(declare-function exwm-layout--show "exwm-layout.el" (id &optional window))
+(declare-function exwm-reset "exwm.el" ())
+(declare-function exwm-workspace--minibuffer-own-frame-p "exwm-workspace.el")
+(declare-function exwm-workspace--workspace-p "exwm-workspace.el" (workspace))
+(declare-function exwm-workspace-switch "exwm-workspace.el"
+                  (frame-or-index &optional force))
+
+(defun exwm-input--set-focus (id)
+  "Set input focus to window ID in a proper way."
+  (let ((from (slot-value (xcb:+request-unchecked+reply exwm--connection
+                              (make-instance 'xcb:GetInputFocus))
+                          'focus))
+        tree)
+    (if (or (exwm--id->buffer from)
+            (eq from id))
+        (exwm--log "#x%x => #x%x" (or from 0) (or id 0))
+      ;; Attempt to find the top-level X window for a 'focus proxy'.
+      (unless (= from xcb:Window:None)
+        (setq tree (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:QueryTree
+                                      :window from)))
+        (when tree
+          (setq from (slot-value tree 'parent))))
+      (exwm--log "#x%x (corrected) => #x%x" (or from 0) (or id 0)))
+    (when (and (exwm--id->buffer id)
+               ;; Avoid redundant input focus transfer.
+               (not (eq from id)))
+      (with-current-buffer (exwm--id->buffer id)
+        (exwm-input--update-timestamp
+         (lambda (timestamp id send-input-focus wm-take-focus)
+           (when send-input-focus
+             (xcb:+request exwm--connection
+                 (make-instance 'xcb:SetInputFocus
+                                :revert-to xcb:InputFocus:Parent
+                                :focus id
+                                :time timestamp)))
+           (when wm-take-focus
+             (let ((event (make-instance 'xcb:icccm:WM_TAKE_FOCUS
+                                         :window id
+                                         :time timestamp)))
+               (setq event (xcb:marshal event exwm--connection))
+               (xcb:+request exwm--connection
+                   (make-instance 'xcb:icccm:SendEvent
+                                  :destination id
+                                  :event event))))
+           (exwm-input--set-active-window id)
+           (xcb:flush exwm--connection))
+         id
+         (or exwm--hints-input
+             (not (memq xcb:Atom:WM_TAKE_FOCUS exwm--protocols)))
+         (memq xcb:Atom:WM_TAKE_FOCUS exwm--protocols))))))
+
+(defun exwm-input--update-timestamp (callback &rest args)
+  "Fetch the latest timestamp from the server and feed it to CALLBACK.
+
+ARGS are additional arguments to CALLBACK."
+  (setq exwm-input--timestamp-callback (cons callback args))
+  (exwm--log)
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ChangeProperty
+                     :mode xcb:PropMode:Replace
+                     :window exwm-input--timestamp-window
+                     :property exwm-input--timestamp-atom
+                     :type xcb:Atom:CARDINAL
+                     :format 32
+                     :data-len 0
+                     :data nil))
+  (xcb:flush exwm--connection))
+
+(defun exwm-input--on-PropertyNotify (data _synthetic)
+  "Handle PropertyNotify events."
+  (exwm--log)
+  (when exwm-input--timestamp-callback
+    (let ((obj (make-instance 'xcb:PropertyNotify)))
+      (xcb:unmarshal obj data)
+      (when (= exwm-input--timestamp-window
+               (slot-value obj 'window))
+        (apply (car exwm-input--timestamp-callback)
+               (slot-value obj 'time)
+               (cdr exwm-input--timestamp-callback))
+        (setq exwm-input--timestamp-callback nil)))))
+
+(defvar exwm-input--last-enter-notify-position nil)
+
+(defun exwm-input--on-EnterNotify (data _synthetic)
+  "Handle EnterNotify events."
+  (let ((evt (make-instance 'xcb:EnterNotify))
+        buffer window frame frame-xid edges fake-evt)
+    (xcb:unmarshal evt data)
+    (with-slots (time root event root-x root-y event-x event-y state) evt
+      (setq buffer (exwm--id->buffer event)
+            window (get-buffer-window buffer t))
+      (exwm--log "buffer=%s; window=%s" buffer window)
+      (when (and buffer window (not (eq window (selected-window)))
+                 (not (equal exwm-input--last-enter-notify-position
+                             (vector root-x root-y))))
+        (setq frame (window-frame window)
+              frame-xid (frame-parameter frame 'exwm-id))
+        (unless (eq frame exwm-workspace--current)
+          (if (exwm-workspace--workspace-p frame)
+              ;; The X window is on another workspace.
+              (exwm-workspace-switch frame)
+            (with-current-buffer buffer
+              (when (and (derived-mode-p 'exwm-mode)
+                         (not (eq exwm--frame exwm-workspace--current)))
+                ;; The floating X window is on another workspace.
+                (exwm-workspace-switch exwm--frame)))))
+        ;; Send a fake MotionNotify event to Emacs.
+        (setq edges (window-inside-pixel-edges window)
+              fake-evt (make-instance 'xcb:MotionNotify
+                                      :detail 0
+                                      :time time
+                                      :root root
+                                      :event frame-xid
+                                      :child xcb:Window:None
+                                      :root-x root-x
+                                      :root-y root-y
+                                      :event-x (+ event-x (elt edges 0))
+                                      :event-y (+ event-y (elt edges 1))
+                                      :state state
+                                      :same-screen 1))
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:SendEvent
+                           :propagate 0
+                           :destination frame-xid
+                           :event-mask xcb:EventMask:NoEvent
+                           :event (xcb:marshal fake-evt exwm--connection)))
+        (xcb:flush exwm--connection))
+      (setq exwm-input--last-enter-notify-position (vector root-x root-y)))))
+
+(defun exwm-input--on-keysyms-update ()
+  (exwm--log)
+  (let ((exwm-input--global-prefix-keys nil))
+    (exwm-input--update-global-prefix-keys)))
+
+(defun exwm-input--on-buffer-list-update ()
+  "Run in `buffer-list-update-hook' to track input focus."
+  (when (and          ; this hook is called incesantly; place cheap tests on top
+         (not exwm-input--skip-buffer-list-update)
+         (exwm--terminal-p) ; skip other terminals, e.g. TTY client frames
+         (not (frame-parameter nil 'no-accept-focus)))
+    (exwm--log "current-buffer=%S selected-window=%S"
+               (current-buffer) (selected-window))
+    (redirect-frame-focus (selected-frame) nil)
+    (setq exwm-input--update-focus-window (selected-window))
+    (exwm-input--update-focus-defer)))
+
+(defun exwm-input--update-focus-defer ()
+  "Schedule a deferred update to input focus.
+Instead of immediately focusing the current window, it defers the focus change
+until the selected window stops changing (debouncing input focus updates)."
+  (when exwm-input--update-focus-timer
+    (cancel-timer exwm-input--update-focus-timer))
+  (setq exwm-input--update-focus-timer
+        ;; Attempt to accumulate successive events close enough.
+        (run-with-timer exwm-input--update-focus-interval
+                        nil
+                        #'exwm-input--update-focus-commit)))
+
+(defun exwm-input--update-focus-commit ()
+  "Attempt to update the window focus.
+If we're currently updating the window focus, re-schedule a focus update
+attempt later."
+  (if exwm-input--update-focus-lock
+      (exwm-input--update-focus-defer)
+    (let ((exwm-input--update-focus-lock t))
+      (exwm-input--update-focus exwm-input--update-focus-window))))
+
+(defun exwm-input--update-focus (window)
+  "Update input focus to WINDOW."
+  (when (window-live-p window)
+    (exwm--log "focus-window=%s focus-buffer=%s" window (window-buffer window))
+    (with-current-buffer (window-buffer window)
+      (if (derived-mode-p 'exwm-mode)
+          (if (not (eq exwm--frame exwm-workspace--current))
+              (progn
+                (set-frame-parameter exwm--frame 'exwm-selected-window window)
+                (exwm--defer 0 #'exwm-workspace-switch exwm--frame))
+            (exwm--log "Set focus on #x%x" exwm--id)
+            (when exwm--floating-frame
+              ;; Adjust stacking orders of the floating X window.
+              (xcb:+request exwm--connection
+                  (make-instance 'xcb:ConfigureWindow
+                                 :window exwm--id
+                                 :value-mask xcb:ConfigWindow:StackMode
+                                 :stack-mode xcb:StackMode:TopIf))
+              (xcb:+request exwm--connection
+                  (make-instance 'xcb:ConfigureWindow
+                                 :window (frame-parameter exwm--floating-frame
+                                                          'exwm-container)
+                                 :value-mask (logior
+                                              xcb:ConfigWindow:Sibling
+                                              xcb:ConfigWindow:StackMode)
+                                 :sibling exwm--id
+                                 :stack-mode xcb:StackMode:Below))
+              ;; This floating X window might be hide by `exwm-floating-hide'.
+              (when (exwm-layout--iconic-state-p)
+                (exwm-layout--show exwm--id window))
+              (xcb:flush exwm--connection))
+            (exwm-input--set-focus exwm--id))
+        (when (eq (selected-window) window)
+          (exwm--log "Focus on %s" window)
+          (if (and (exwm-workspace--workspace-p (selected-frame))
+                   (not (eq (selected-frame) exwm-workspace--current)))
+              ;; The focus is on another workspace (e.g. it got clicked)
+              ;; so switch to it.
+              (progn
+                (exwm--log "Switching to %s's workspace %s (%s)"
+                           window
+                           (window-frame window)
+                           (selected-frame))
+                (set-frame-parameter (selected-frame) 'exwm-selected-window
+                                     window)
+                (exwm--defer 0 #'exwm-workspace-switch (selected-frame)))
+            ;; The focus is still on the current workspace.
+            (if (not (and (exwm-workspace--minibuffer-own-frame-p)
+                          (minibufferp)))
+                (x-focus-frame (window-frame window))
+              ;; X input focus should be set on the previously selected
+              ;; frame.
+              (x-focus-frame (window-frame (minibuffer-window))))
+            (exwm-input--set-active-window)
+            (xcb:flush exwm--connection)))))))
+
+(defun exwm-input--set-active-window (&optional id)
+  "Set _NET_ACTIVE_WINDOW."
+  (exwm--log)
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ewmh:set-_NET_ACTIVE_WINDOW
+                     :window exwm--root
+                     :data (or id xcb:Window:None))))
+
+(defun exwm-input--on-ButtonPress (data _synthetic)
+  "Handle ButtonPress event."
+  (let ((obj (make-instance 'xcb:ButtonPress))
+        (mode xcb:Allow:SyncPointer)
+        button-event window buffer frame fake-last-command)
+    (xcb:unmarshal obj data)
+    (exwm--log "major-mode=%s buffer=%s"
+               major-mode (buffer-name (current-buffer)))
+    (with-slots (detail event state) obj
+      (setq button-event (xcb:keysyms:keysym->event exwm--connection
+                                                    detail state)
+            buffer (exwm--id->buffer event)
+            window (get-buffer-window buffer t))
+      (cond ((and (eq button-event exwm-input-move-event)
+                  buffer
+                  ;; Either an undecorated or a floating X window.
+                  (with-current-buffer buffer
+                    (or (not (derived-mode-p 'exwm-mode))
+                        exwm--floating-frame)))
+             ;; Move
+             (exwm-floating--start-moveresize
+              event xcb:ewmh:_NET_WM_MOVERESIZE_MOVE))
+            ((and (eq button-event exwm-input-resize-event)
+                  buffer
+                  (with-current-buffer buffer
+                    (or (not (derived-mode-p 'exwm-mode))
+                        exwm--floating-frame)))
+             ;; Resize
+             (exwm-floating--start-moveresize event))
+            (buffer
+             ;; Click to focus
+             (setq fake-last-command t)
+             (unless (eq window (selected-window))
+               (setq frame (window-frame window))
+               (unless (eq frame exwm-workspace--current)
+                 (if (exwm-workspace--workspace-p frame)
+                     ;; The X window is on another workspace
+                     (exwm-workspace-switch frame)
+                   (with-current-buffer buffer
+                     (when (and (derived-mode-p 'exwm-mode)
+                                (not (eq exwm--frame
+                                         exwm-workspace--current)))
+                       ;; The floating X window is on another workspace
+                       (exwm-workspace-switch exwm--frame)))))
+               ;; It has been reported that the `window' may have be deleted
+               (if (window-live-p window)
+                   (select-window window)
+                 (setq window (get-buffer-window buffer t))
+                 (when window (select-window window))))
+             ;; Also process keybindings.
+             (with-current-buffer buffer
+               (when (derived-mode-p 'exwm-mode)
+                 (cl-case exwm--input-mode
+                   (line-mode
+                    (setq mode (exwm-input--on-ButtonPress-line-mode
+                                buffer button-event)))
+                   (char-mode
+                    (setq mode (exwm-input--on-ButtonPress-char-mode)))))))
+            (t
+             ;; Replay this event by default.
+             (setq fake-last-command t)
+             (setq mode xcb:Allow:ReplayPointer)))
+      (when fake-last-command
+        (if buffer
+            (with-current-buffer buffer
+              (exwm-input--fake-last-command))
+          (exwm-input--fake-last-command))))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:AllowEvents :mode mode :time xcb:Time:CurrentTime))
+    (xcb:flush exwm--connection))
+  (run-hooks 'exwm-input--event-hook))
+
+(defun exwm-input--on-KeyPress (data _synthetic)
+  "Handle KeyPress event."
+  (with-current-buffer (window-buffer (selected-window))
+    (let ((obj (make-instance 'xcb:KeyPress)))
+      (xcb:unmarshal obj data)
+      (exwm--log "major-mode=%s buffer=%s"
+                 major-mode (buffer-name (current-buffer)))
+      (if (derived-mode-p 'exwm-mode)
+          (cl-case exwm--input-mode
+            (line-mode
+             (exwm-input--on-KeyPress-line-mode obj data))
+            (char-mode
+             (exwm-input--on-KeyPress-char-mode obj data)))
+        (exwm-input--on-KeyPress-char-mode obj)))
+    (run-hooks 'exwm-input--event-hook)))
+
+(defun exwm-input--on-CreateNotify (data _synthetic)
+  "Handle CreateNotify events."
+  (exwm--log)
+  (let ((evt (make-instance 'xcb:CreateNotify)))
+    (xcb:unmarshal evt data)
+    (with-slots (window) evt
+      (exwm-input--grab-global-prefix-keys window))))
+
+(defun exwm-input--update-global-prefix-keys ()
+  "Update `exwm-input--global-prefix-keys'."
+  (exwm--log)
+  (when exwm--connection
+    (let ((original exwm-input--global-prefix-keys))
+      (setq exwm-input--global-prefix-keys nil)
+      (dolist (i exwm-input--global-keys)
+        (cl-pushnew (elt i 0) exwm-input--global-prefix-keys))
+      (unless (equal original exwm-input--global-prefix-keys)
+        (apply #'exwm-input--grab-global-prefix-keys
+               (slot-value (xcb:+request-unchecked+reply exwm--connection
+                               (make-instance 'xcb:QueryTree
+                                              :window exwm--root))
+                           'children))))))
+
+(defun exwm-input--grab-global-prefix-keys (&rest xwins)
+  (exwm--log)
+  (let ((req (make-instance 'xcb:GrabKey
+                            :owner-events 0
+                            :grab-window nil
+                            :modifiers nil
+                            :key nil
+                            :pointer-mode xcb:GrabMode:Async
+                            :keyboard-mode xcb:GrabMode:Async))
+        keysyms keycode alt-modifier)
+    (dolist (k exwm-input--global-prefix-keys)
+      (setq keysyms (xcb:keysyms:event->keysyms exwm--connection k))
+      (if (not keysyms)
+          (warn "Key unavailable: %s" (key-description (vector k)))
+        (setq keycode (xcb:keysyms:keysym->keycode exwm--connection
+                                                   (caar keysyms)))
+        (exwm--log "Grabbing key=%s (keysyms=%s keycode=%s)"
+                   (single-key-description k) keysyms keycode)
+        (dolist (keysym keysyms)
+          (setf (slot-value req 'modifiers) (cdr keysym)
+                (slot-value req 'key) keycode)
+          ;; Also grab this key with num-lock mask set.
+          (when (and (/= 0 xcb:keysyms:num-lock-mask)
+                     (= 0 (logand (cdr keysym) xcb:keysyms:num-lock-mask)))
+            (setf alt-modifier (logior (cdr keysym)
+                                       xcb:keysyms:num-lock-mask)))
+          (dolist (xwin xwins)
+            (setf (slot-value req 'grab-window) xwin)
+            (xcb:+request exwm--connection req)
+            (when alt-modifier
+              (setf (slot-value req 'modifiers) alt-modifier)
+              (xcb:+request exwm--connection req))))))
+    (xcb:flush exwm--connection)))
+
+(defun exwm-input--set-key (key command)
+  (exwm--log "key: %s, command: %s" key command)
+  (global-set-key key command)
+  (cl-pushnew key exwm-input--global-keys))
+
+(defcustom exwm-input-global-keys nil
+  "Global keys.
+
+It is an alist of the form (key . command), meaning giving KEY (a key
+sequence) a global binding as COMMAND.
+
+Notes:
+* Setting the value directly (rather than customizing it) after EXWM
+  finishes initialization has no effect."
+  :type '(alist :key-type key-sequence :value-type function)
+  :set (lambda (symbol value)
+         (when (boundp symbol)
+           (dolist (i (symbol-value symbol))
+             (global-unset-key (car i))))
+         (set symbol value)
+         (setq exwm-input--global-keys nil)
+         (dolist (i value)
+           (exwm-input--set-key (car i) (cdr i)))
+         (when exwm--connection
+           (exwm-input--update-global-prefix-keys))))
+
+;;;###autoload
+(defun exwm-input-set-key (key command)
+  "Set a global key binding.
+
+The new key binding only takes effect in real time when this command is
+called interactively, and is lost when this session ends unless it's
+specifically saved in the Customize interface for `exwm-input-global-keys'.
+
+In configuration you should customize or set `exwm-input-global-keys'
+instead."
+  (interactive "KSet key globally: \nCSet key %s to command: ")
+  (exwm--log)
+  (setq exwm-input-global-keys (append exwm-input-global-keys
+                                       (list (cons key command))))
+  (exwm-input--set-key key command)
+  (when (called-interactively-p 'any)
+    (exwm-input--update-global-prefix-keys)))
+
+(defsubst exwm-input--unread-event (event)
+  (declare (indent defun))
+  (setq unread-command-events
+        (append unread-command-events `((t . ,event)))))
+
+(defun exwm-input--mimic-read-event (event)
+  "Process EVENT as if it were returned by `read-event'."
+  (exwm--log)
+  (unless (eq 0 extra-keyboard-modifiers)
+    (setq event (event-convert-list (append (event-modifiers
+                                             extra-keyboard-modifiers)
+                                            event))))
+  (when (characterp event)
+    (let ((event* (when keyboard-translate-table
+                    (aref keyboard-translate-table event))))
+      (when event*
+        (setq event event*))))
+  event)
+
+(cl-defun exwm-input--translate (key)
+  (let (translation)
+    (dolist (map (list input-decode-map
+                       local-function-key-map
+                       key-translation-map))
+      (setq translation (lookup-key map key))
+      (if (functionp translation)
+          (cl-return-from exwm-input--translate (funcall translation nil))
+        (when (vectorp translation)
+          (cl-return-from exwm-input--translate translation)))))
+  key)
+
+(defun exwm-input--cache-event (event &optional temp-line-mode)
+  "Cache EVENT."
+  (exwm--log "%s" event)
+  (setq exwm-input--line-mode-cache
+        (vconcat exwm-input--line-mode-cache (vector event)))
+  ;; Attempt to translate this key sequence.
+  (setq exwm-input--line-mode-cache
+        (exwm-input--translate exwm-input--line-mode-cache))
+  ;; When the key sequence is complete (not a keymap).
+  ;; Note that `exwm-input--line-mode-cache' might get translated to nil, for
+  ;; example 'mouse--down-1-maybe-follows-link' does this.
+  (if (and exwm-input--line-mode-cache
+           (keymapp (key-binding exwm-input--line-mode-cache)))
+      ;; Grab keyboard temporarily to intercept the complete key sequence.
+      (when temp-line-mode
+        (setq exwm-input--temp-line-mode t)
+        (exwm-input--grab-keyboard))
+    (setq exwm-input--line-mode-cache nil)
+    (when exwm-input--temp-line-mode
+      (setq exwm-input--temp-line-mode nil)
+      (exwm-input--release-keyboard))))
+
+(defun exwm-input--event-passthrough-p (event)
+  "Whether EVENT should be passed to Emacs.
+Current buffer must be an `exwm-mode' buffer."
+  (or exwm-input-line-mode-passthrough
+      exwm-input--during-command
+      ;; Forward the event when there is an incomplete key
+      ;; sequence or when the minibuffer is active.
+      exwm-input--line-mode-cache
+      (eq (active-minibuffer-window) (selected-window))
+      ;;
+      (memq event exwm-input--global-prefix-keys)
+      (memq event exwm-input-prefix-keys)
+      (when overriding-terminal-local-map
+        (lookup-key overriding-terminal-local-map
+                    (vector event)))
+      (lookup-key (current-local-map) (vector event))
+      (gethash event exwm-input--simulation-keys)))
+
+(defun exwm-input--noop (&rest _args)
+  "A placeholder command."
+  (interactive))
+
+(defun exwm-input--fake-last-command ()
+  "Fool some packages into thinking there is a change in the buffer."
+  (setq last-command #'exwm-input--noop)
+  ;; The Emacs manual says:
+  ;; > Quitting is suppressed while running pre-command-hook and
+  ;; > post-command-hook. If an error happens while executing one of these
+  ;; > hooks, it does not terminate execution of the hook; instead the error is
+  ;; > silenced and the function in which the error occurred is removed from the
+  ;; > hook.
+  ;; We supress errors but neither continue execution nor we remove from the
+  ;; hook.
+  (condition-case err
+      (run-hooks 'pre-command-hook)
+    ((error)
+     (exwm--log "Error occurred while running pre-command-hook: %s"
+                (error-message-string err))
+     (xcb-debug:backtrace)))
+  (condition-case err
+      (run-hooks 'post-command-hook)
+    ((error)
+     (exwm--log "Error occurred while running post-command-hook: %s"
+                (error-message-string err))
+     (xcb-debug:backtrace))))
+
+(defun exwm-input--on-KeyPress-line-mode (key-press raw-data)
+  "Parse X KeyPress event to Emacs key event and then feed the command loop."
+  (with-slots (detail state) key-press
+    (let ((keysym (xcb:keysyms:keycode->keysym exwm--connection detail state))
+          event raw-event mode)
+      (exwm--log "%s" keysym)
+      (when (and (/= 0 (car keysym))
+                 (setq raw-event (xcb:keysyms:keysym->event
+                                  exwm--connection (car keysym)
+                                  (logand state (lognot (cdr keysym)))))
+                 (setq event (exwm-input--mimic-read-event raw-event))
+                 (exwm-input--event-passthrough-p event))
+        (setq mode xcb:Allow:AsyncKeyboard)
+        (exwm-input--cache-event event)
+        (exwm-input--unread-event raw-event))
+      (unless mode
+        (if (= 0 (logand #x6000 state)) ;Check the 13~14 bits.
+            ;; Not an XKB state; just replay it.
+            (setq mode xcb:Allow:ReplayKeyboard)
+          ;; An XKB state; sent it with SendEvent.
+          ;; FIXME: Can this also be replayed?
+          ;; FIXME: KeyRelease events are lost.
+          (setq mode xcb:Allow:AsyncKeyboard)
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:SendEvent
+                             :propagate 0
+                             :destination (slot-value key-press 'event)
+                             :event-mask xcb:EventMask:NoEvent
+                             :event raw-data)))
+        (when event
+          (if (not defining-kbd-macro)
+              (exwm-input--fake-last-command)
+            ;; Make Emacs aware of this event when defining keyboard macros.
+            (set-transient-map `(keymap (t . ,#'exwm-input--noop)))
+            (exwm-input--unread-event event))))
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:AllowEvents
+                         :mode mode
+                         :time xcb:Time:CurrentTime))
+      (xcb:flush exwm--connection))))
+
+(defun exwm-input--on-KeyPress-char-mode (key-press &optional _raw-data)
+  "Handle KeyPress event in `char-mode'."
+  (with-slots (detail state) key-press
+    (let ((keysym (xcb:keysyms:keycode->keysym exwm--connection detail state))
+          event raw-event)
+      (exwm--log "%s" keysym)
+      (when (and (/= 0 (car keysym))
+                 (setq raw-event (xcb:keysyms:keysym->event
+                                  exwm--connection (car keysym)
+                                  (logand state (lognot (cdr keysym)))))
+                 (setq event (exwm-input--mimic-read-event raw-event)))
+        (if (not (derived-mode-p 'exwm-mode))
+            (exwm-input--unread-event raw-event)
+          (exwm-input--cache-event event t)
+          (exwm-input--unread-event raw-event)))))
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:AllowEvents
+                     :mode xcb:Allow:AsyncKeyboard
+                     :time xcb:Time:CurrentTime))
+  (xcb:flush exwm--connection))
+
+(defun exwm-input--on-ButtonPress-line-mode (buffer button-event)
+  "Handle button events in line mode.
+BUFFER is the `exwm-mode' buffer the event was generated
+on.  BUTTON-EVENT is the X event converted into an Emacs event.
+
+The return value is used as event_mode to release the original
+button event."
+  (with-current-buffer buffer
+    (let ((read-event (exwm-input--mimic-read-event button-event)))
+      (exwm--log "%s" read-event)
+      (if (and read-event
+               (exwm-input--event-passthrough-p read-event))
+          ;; The event should be forwarded to emacs
+          (progn
+            (exwm-input--cache-event read-event)
+            (exwm-input--unread-event button-event)
+            xcb:Allow:SyncPointer)
+        ;; The event should be replayed
+        xcb:Allow:ReplayPointer))))
+
+(defun exwm-input--on-ButtonPress-char-mode ()
+  "Handle button events in `char-mode'.
+The return value is used as event_mode to release the original
+button event."
+  (exwm--log)
+  xcb:Allow:ReplayPointer)
+
+(defun exwm-input--update-mode-line (id)
+  "Update the propertized `mode-line-process' for window ID."
+  (exwm--log "#x%x" id)
+  (let (help-echo cmd mode)
+    (with-current-buffer (exwm--id->buffer id)
+      (cl-case exwm--input-mode
+        (line-mode
+         (setq mode "line"
+               help-echo "mouse-1: Switch to char-mode"
+               cmd (lambda ()
+                     (interactive)
+                     (exwm-input-release-keyboard id))))
+        (char-mode
+         (setq mode "char"
+               help-echo "mouse-1: Switch to line-mode"
+               cmd (lambda ()
+                     (interactive)
+                     (exwm-input-grab-keyboard id)))))
+      (setq mode-line-process
+            `(": "
+              (:propertize ,mode
+                           help-echo ,help-echo
+                           mouse-face mode-line-highlight
+                           local-map
+                           (keymap
+                            (mode-line
+                             keymap
+                             (down-mouse-1 . ,cmd))))))
+      (force-mode-line-update))))
+
+(defun exwm-input--grab-keyboard (&optional id)
+  "Grab all key events on window ID."
+  (unless id (setq id (exwm--buffer->id (window-buffer))))
+  (when id
+    (exwm--log "id=#x%x" id)
+    (when (xcb:+request-checked+request-check exwm--connection
+              (make-instance 'xcb:GrabKey
+                             :owner-events 0
+                             :grab-window id
+                             :modifiers xcb:ModMask:Any
+                             :key xcb:Grab:Any
+                             :pointer-mode xcb:GrabMode:Async
+                             :keyboard-mode xcb:GrabMode:Sync))
+      (exwm--log "Failed to grab keyboard for #x%x" id))
+    (let ((buffer (exwm--id->buffer id)))
+      (when buffer
+        (with-current-buffer buffer
+          (setq exwm--input-mode 'line-mode)
+          (run-hooks 'exwm-input-input-mode-change-hook))))))
+
+(defun exwm-input--release-keyboard (&optional id)
+  "Ungrab all key events on window ID."
+  (unless id (setq id (exwm--buffer->id (window-buffer))))
+  (when id
+    (exwm--log "id=#x%x" id)
+    (when (xcb:+request-checked+request-check exwm--connection
+              (make-instance 'xcb:UngrabKey
+                             :key xcb:Grab:Any
+                             :grab-window id
+                             :modifiers xcb:ModMask:Any))
+      (exwm--log "Failed to release keyboard for #x%x" id))
+    (exwm-input--grab-global-prefix-keys id)
+    (let ((buffer (exwm--id->buffer id)))
+      (when buffer
+        (with-current-buffer buffer
+          (setq exwm--input-mode 'char-mode)
+          (run-hooks 'exwm-input-input-mode-change-hook))))))
+
+;;;###autoload
+(defun exwm-input-grab-keyboard (&optional id)
+  "Switch to `line-mode'."
+  (interactive (list (when (derived-mode-p 'exwm-mode)
+                       (exwm--buffer->id (window-buffer)))))
+  (when id
+    (exwm--log "id=#x%x" id)
+    (setq exwm--selected-input-mode 'line-mode)
+    (exwm-input--grab-keyboard id)
+    (exwm-input--update-mode-line id)))
+
+;;;###autoload
+(defun exwm-input-release-keyboard (&optional id)
+  "Switch to `char-mode`."
+  (interactive (list (when (derived-mode-p 'exwm-mode)
+                       (exwm--buffer->id (window-buffer)))))
+  (when id
+    (exwm--log "id=#x%x" id)
+    (setq exwm--selected-input-mode  'char-mode)
+    (exwm-input--release-keyboard id)
+    (exwm-input--update-mode-line id)))
+
+;;;###autoload
+(defun exwm-input-toggle-keyboard (&optional id)
+  "Toggle between `line-mode' and `char-mode'."
+  (interactive (list (when (derived-mode-p 'exwm-mode)
+                       (exwm--buffer->id (window-buffer)))))
+  (when id
+    (exwm--log "id=#x%x" id)
+    (with-current-buffer (exwm--id->buffer id)
+      (cl-case exwm--input-mode
+        (line-mode
+         (exwm-input-release-keyboard id))
+        (char-mode
+         (exwm-reset))))))
+
+(defun exwm-input--fake-key (event)
+  "Fake a key event equivalent to Emacs event EVENT."
+  (let* ((keysyms (xcb:keysyms:event->keysyms exwm--connection event))
+         keycode id)
+    (when (= 0 (caar keysyms))
+      (user-error "[EXWM] Invalid key: %s" (single-key-description event)))
+    (setq keycode (xcb:keysyms:keysym->keycode exwm--connection
+                                               (caar keysyms)))
+    (when (/= 0 keycode)
+      (setq id (exwm--buffer->id (window-buffer (selected-window))))
+      (exwm--log "id=#x%x event=%s keycode" id event keycode)
+      (dolist (class '(xcb:KeyPress xcb:KeyRelease))
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:SendEvent
+                           :propagate 0 :destination id
+                           :event-mask xcb:EventMask:NoEvent
+                           :event (xcb:marshal
+                                   (make-instance class
+                                                  :detail keycode
+                                                  :time xcb:Time:CurrentTime
+                                                  :root exwm--root :event id
+                                                  :child 0
+                                                  :root-x 0 :root-y 0
+                                                  :event-x 0 :event-y 0
+                                                  :state (cdar keysyms)
+                                                  :same-screen 1)
+                                   exwm--connection)))))
+    (xcb:flush exwm--connection)))
+
+;;;###autoload
+(cl-defun exwm-input-send-next-key (times &optional end-key)
+  "Send next key to client window.
+
+EXWM will prompt for the key to send.  This command can be prefixed to send
+multiple keys.  If END-KEY is non-nil, stop sending keys if it's pressed."
+  (interactive "p")
+  (exwm--log)
+  (unless (derived-mode-p 'exwm-mode)
+    (cl-return-from exwm-input-send-next-key))
+  (when (> times 12) (setq times 12))
+  (let (key keys)
+    (dotimes (i times)
+      ;; Skip events not from keyboard
+      (let ((exwm-input-line-mode-passthrough t))
+        (catch 'break
+          (while t
+            (setq key (read-key (format "Send key: %s (%d/%d) %s"
+                                        (key-description keys)
+                                        (1+ i) times
+                                        (if end-key
+                                            (concat "To exit, press: "
+                                                    (key-description
+                                                     (list end-key)))
+                                          ""))))
+            (unless (listp key) (throw 'break nil)))))
+      (setq keys (vconcat keys (vector key)))
+      (when (eq key end-key) (cl-return-from exwm-input-send-next-key))
+      (exwm-input--fake-key key))))
+
+(defun exwm-input--set-simulation-keys (simulation-keys &optional no-refresh)
+  "Set simulation keys."
+  (exwm--log "%s" simulation-keys)
+  (unless no-refresh
+    ;; Unbind simulation keys.
+    (let ((hash (buffer-local-value 'exwm-input--simulation-keys
+                                    (current-buffer))))
+      (when (hash-table-p hash)
+        (maphash (lambda (key _value)
+                   (when (sequencep key)
+                     (if exwm-input--local-simulation-keys
+                         (local-unset-key key)
+                       (define-key exwm-mode-map key nil))))
+                 hash)))
+    ;; Abandon the old hash table.
+    (setq exwm-input--simulation-keys (make-hash-table :test #'equal)))
+  (dolist (i simulation-keys)
+    (let ((original (vconcat (car i)))
+          (simulated (cdr i)))
+      (setq simulated (if (sequencep simulated)
+                          (append simulated nil)
+                        (list simulated)))
+      ;; The key stored is a key sequence (vector).
+      ;; The value stored is a list of key events.
+      (puthash original simulated exwm-input--simulation-keys)
+      ;; Also mark the prefix key as used.
+      (puthash (aref original 0) t exwm-input--simulation-keys)))
+  ;; Update keymaps.
+  (maphash (lambda (key _value)
+             (when (sequencep key)
+               (if exwm-input--local-simulation-keys
+                   (local-set-key key #'exwm-input-send-simulation-key)
+                 (define-key exwm-mode-map key
+                   #'exwm-input-send-simulation-key))))
+           exwm-input--simulation-keys))
+
+(defcustom exwm-input-simulation-keys nil
+  "Simulation keys.
+
+It is an alist of the form (original-key . simulated-key), where both
+original-key and simulated-key are key sequences.  Original-key is what you
+type to an X window in `line-mode' which then gets translated to simulated-key
+by EXWM and forwarded to the X window.
+
+Notes:
+* Setting the value directly (rather than customizing it) after EXWM
+  finishes initialization has no effect.
+* Original-keys consist of multiple key events are only supported in Emacs
+  26.2 and later.
+* A minority of applications do not accept simulated keys by default.  It's
+  required to customize them to accept events sent by SendEvent.
+* The predefined examples in the Customize interface are not guaranteed to
+  work for all applications.  This can be tweaked on a per application basis
+  with `exwm-input-set-local-simulation-keys'."
+  :type '(alist :key-type (key-sequence :tag "Original")
+                :value-type (choice (key-sequence :tag "User-defined")
+                                    (key-sequence :tag "Move left" [left])
+                                    (key-sequence :tag "Move right" [right])
+                                    (key-sequence :tag "Move up" [up])
+                                    (key-sequence :tag "Move down" [down])
+                                    (key-sequence :tag "Move to BOL" [home])
+                                    (key-sequence :tag "Move to EOL" [end])
+                                    (key-sequence :tag "Page up" [prior])
+                                    (key-sequence :tag "Page down" [next])
+                                    (key-sequence :tag "Copy" [C-c])
+                                    (key-sequence :tag "Paste" [C-v])
+                                    (key-sequence :tag "Delete" [delete])
+                                    (key-sequence :tag "Delete to EOL"
+                                                  [S-end delete])))
+  :set (lambda (symbol value)
+         (set symbol value)
+         (exwm-input--set-simulation-keys value)))
+
+(defcustom exwm-input-pre-post-command-blacklist '(exit-minibuffer
+                                                   abort-recursive-edit
+                                                   minibuffer-keyboard-quit)
+  "Commands impossible to detect with `post-command-hook'."
+  :type '(repeat function))
+
+(cl-defun exwm-input--read-keys (prompt stop-key)
+  (let ((cursor-in-echo-area t)
+        keys key)
+    (while (not (eq key stop-key))
+      (setq key (read-key (format "%s (terminate with %s): %s"
+                                  prompt
+                                  (key-description (vector stop-key))
+                                  (key-description keys)))
+            keys (vconcat keys (vector key))))
+    (when (> (length keys) 1)
+      (substring keys 0 -1))))
+
+;;;###autoload
+(defun exwm-input-set-simulation-key (original-key simulated-key)
+  "Set a simulation key.
+
+The simulation key takes effect in real time, but is lost when this session
+ends unless it's specifically saved in the Customize interface for
+`exwm-input-simulation-keys'."
+  (interactive
+   (let (original simulated)
+     (setq original (exwm-input--read-keys "Translate from" ?\C-g))
+     (when original
+       (setq simulated (exwm-input--read-keys
+                        (format "Translate from %s to"
+                                (key-description original))
+                        ?\C-g)))
+     (list original simulated)))
+  (exwm--log "original: %s, simulated: %s" original-key simulated-key)
+  (when (and original-key simulated-key)
+    (let ((entry `((,original-key . ,simulated-key))))
+      (setq exwm-input-simulation-keys (append exwm-input-simulation-keys
+                                               entry))
+      (exwm-input--set-simulation-keys entry t))))
+
+(defun exwm-input--unset-simulation-keys ()
+  "Clear simulation keys and key bindings defined."
+  (exwm--log)
+  (when (hash-table-p exwm-input--simulation-keys)
+    (maphash (lambda (key _value)
+               (when (sequencep key)
+                 (define-key exwm-mode-map key nil)))
+             exwm-input--simulation-keys)
+    (clrhash exwm-input--simulation-keys)))
+
+(defun exwm-input-set-local-simulation-keys (simulation-keys)
+  "Set buffer-local simulation keys.
+
+SIMULATION-KEYS is an alist of the form (original-key . simulated-key),
+where both ORIGINAL-KEY and SIMULATED-KEY are key sequences."
+  (exwm--log)
+  (make-local-variable 'exwm-input--simulation-keys)
+  (use-local-map (copy-keymap exwm-mode-map))
+  (let ((exwm-input--local-simulation-keys t))
+    (exwm-input--set-simulation-keys simulation-keys)))
+
+;;;###autoload
+(cl-defun exwm-input-send-simulation-key (times)
+  "Fake a key event according to the last input key sequence."
+  (interactive "p")
+  (exwm--log)
+  (unless (derived-mode-p 'exwm-mode)
+    (cl-return-from exwm-input-send-simulation-key))
+  (let ((keys (gethash (this-single-command-keys)
+                       exwm-input--simulation-keys)))
+    (dotimes (_ times)
+      (dolist (key keys)
+        (exwm-input--fake-key key)))))
+
+;;;###autoload
+(defmacro exwm-input-invoke-factory (keys)
+  "Make a command that invokes KEYS when called.
+
+One use is to access the keymap bound to KEYS (as prefix keys) in `char-mode'."
+  (let* ((keys (kbd keys))
+         (description (key-description keys)))
+    `(defun ,(intern (concat "exwm-input--invoke--" description)) ()
+       ,(format "Invoke `%s'." description)
+       (interactive)
+       (mapc (lambda (key)
+               (exwm-input--cache-event key t)
+               (exwm-input--unread-event key))
+             ',(listify-key-sequence keys)))))
+
+(defun exwm-input--on-pre-command ()
+  "Run in `pre-command-hook'."
+  (unless (or (eq this-command #'exwm-input--noop)
+              (memq this-command exwm-input-pre-post-command-blacklist))
+    (setq exwm-input--during-command t)))
+
+(defun exwm-input--on-post-command ()
+  "Run in `post-command-hook'."
+  (unless (eq this-command #'exwm-input--noop)
+    (setq exwm-input--during-command nil)))
+
+(defun exwm-input--on-minibuffer-setup ()
+  "Run in `minibuffer-setup-hook' to grab keyboard if necessary."
+  (let* ((window (or (minibuffer-selected-window) ; minibuffer-setup-hook
+                     (selected-window)))          ; echo-area-clear-hook
+         (frame (window-frame window)))
+    (when (exwm--terminal-p frame)
+      (with-current-buffer (window-buffer window)
+        (when (and (derived-mode-p 'exwm-mode)
+                   (eq exwm--selected-input-mode 'char-mode))
+          (exwm--log "Grab #x%x window=%s frame=%s" exwm--id window frame)
+          (exwm-input--grab-keyboard exwm--id))))))
+
+(defun exwm-input--on-minibuffer-exit ()
+  "Run in `minibuffer-exit-hook' to release keyboard if necessary."
+  (let* ((window (or (minibuffer-selected-window) ; minibuffer-setup-hook
+                     (selected-window)))          ; echo-area-clear-hook
+         (frame (window-frame window)))
+    (when (exwm--terminal-p frame)
+      (with-current-buffer (window-buffer window)
+        (when (and (derived-mode-p 'exwm-mode)
+                   (eq exwm--selected-input-mode 'char-mode)
+                   (eq exwm--input-mode 'line-mode))
+          (exwm--log "Release #x%x window=%s frame=%s" exwm--id window frame)
+          (exwm-input--release-keyboard exwm--id))))))
+
+(defun exwm-input--on-echo-area-dirty ()
+  "Run when new message arrives to grab keyboard if necessary."
+  (when (and cursor-in-echo-area
+             (not (active-minibuffer-window)))
+    (exwm--log)
+    (exwm-input--on-minibuffer-setup)))
+
+(defun exwm-input--on-echo-area-clear ()
+  "Run in `echo-area-clear-hook' to release keyboard if necessary."
+  (unless (current-message)
+    (exwm--log)
+    (exwm-input--on-minibuffer-exit)))
+
+(defun exwm-input--call-with-passthrough (function &rest args)
+  "Bind `exwm-input-line-mode-passthrough' and call FUNCTION with ARGS."
+  (let ((exwm-input-line-mode-passthrough t))
+    (apply function args)))
+
+(defun exwm-input--init ()
+  "Initialize the keyboard module."
+  (exwm--log)
+  ;; Refresh keyboard mapping
+  (xcb:keysyms:init exwm--connection #'exwm-input--on-keysyms-update)
+  ;; Create the X window and intern the atom used to fetch timestamp.
+  (setq exwm-input--timestamp-window (xcb:generate-id exwm--connection))
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:CreateWindow
+                     :depth 0
+                     :wid exwm-input--timestamp-window
+                     :parent exwm--root
+                     :x -1
+                     :y -1
+                     :width 1
+                     :height 1
+                     :border-width 0
+                     :class xcb:WindowClass:CopyFromParent
+                     :visual 0
+                     :value-mask xcb:CW:EventMask
+                     :event-mask xcb:EventMask:PropertyChange))
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ewmh:set-_NET_WM_NAME
+                     :window exwm-input--timestamp-window
+                     :data "EXWM: exwm-input--timestamp-window"))
+  (setq exwm-input--timestamp-atom (exwm--intern-atom "_TIME"))
+  ;; Initialize global keys.
+  (dolist (i exwm-input-global-keys)
+    (exwm-input--set-key (car i) (cdr i)))
+  ;; Initialize simulation keys.
+  (when exwm-input-simulation-keys
+    (exwm-input--set-simulation-keys exwm-input-simulation-keys))
+  ;; Attach event listeners
+  (xcb:+event exwm--connection 'xcb:PropertyNotify
+              #'exwm-input--on-PropertyNotify)
+  (xcb:+event exwm--connection 'xcb:CreateNotify #'exwm-input--on-CreateNotify)
+  (xcb:+event exwm--connection 'xcb:KeyPress #'exwm-input--on-KeyPress)
+  (xcb:+event exwm--connection 'xcb:ButtonPress #'exwm-input--on-ButtonPress)
+  (xcb:+event exwm--connection 'xcb:ButtonRelease
+              #'exwm-floating--stop-moveresize)
+  (xcb:+event exwm--connection 'xcb:MotionNotify
+              #'exwm-floating--do-moveresize)
+  (when mouse-autoselect-window
+    (xcb:+event exwm--connection 'xcb:EnterNotify
+                #'exwm-input--on-EnterNotify))
+  ;; Control `exwm-input--during-command'
+  (add-hook 'pre-command-hook #'exwm-input--on-pre-command)
+  (add-hook 'post-command-hook #'exwm-input--on-post-command)
+  ;; Grab/Release keyboard when minibuffer/echo becomes active/inactive.
+  (add-hook 'minibuffer-setup-hook #'exwm-input--on-minibuffer-setup)
+  (add-hook 'minibuffer-exit-hook #'exwm-input--on-minibuffer-exit)
+  (setq exwm-input--echo-area-timer
+        (run-with-idle-timer 0 t #'exwm-input--on-echo-area-dirty))
+  (add-hook 'echo-area-clear-hook #'exwm-input--on-echo-area-clear)
+  ;; Update focus when buffer list updates
+  (add-hook 'buffer-list-update-hook #'exwm-input--on-buffer-list-update)
+
+  (dolist (fun exwm-input--passthrough-functions)
+    (advice-add fun :around #'exwm-input--call-with-passthrough)))
+
+(defun exwm-input--post-init ()
+  "The second stage in the initialization of the input module."
+  (exwm--log)
+  (exwm-input--update-global-prefix-keys))
+
+(defun exwm-input--exit ()
+  "Exit the input module."
+  (exwm--log)
+  (dolist (fun exwm-input--passthrough-functions)
+    (advice-remove fun #'exwm-input--call-with-passthrough))
+  (exwm-input--unset-simulation-keys)
+  (remove-hook 'pre-command-hook #'exwm-input--on-pre-command)
+  (remove-hook 'post-command-hook #'exwm-input--on-post-command)
+  (remove-hook 'minibuffer-setup-hook #'exwm-input--on-minibuffer-setup)
+  (remove-hook 'minibuffer-exit-hook #'exwm-input--on-minibuffer-exit)
+  (when exwm-input--echo-area-timer
+    (cancel-timer exwm-input--echo-area-timer)
+    (setq exwm-input--echo-area-timer nil))
+  (remove-hook 'echo-area-clear-hook #'exwm-input--on-echo-area-clear)
+  (remove-hook 'buffer-list-update-hook #'exwm-input--on-buffer-list-update)
+  (when exwm-input--update-focus-timer
+    (cancel-timer exwm-input--update-focus-timer))
+  ;; Make input focus working even without a WM.
+  (when (slot-value exwm--connection 'connected)
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:SetInputFocus
+                       :revert-to xcb:InputFocus:PointerRoot
+                       :focus exwm--root
+                       :time xcb:Time:CurrentTime))
+    (xcb:flush exwm--connection)))
+
+
+
+(provide 'exwm-input)
+
+;;; exwm-input.el ends here
diff --git a/exwm-layout.el b/exwm-layout.el
new file mode 100644
index 0000000000..8649c11ffd
--- /dev/null
+++ b/exwm-layout.el
@@ -0,0 +1,631 @@
+;;; exwm-layout.el --- Layout Module for EXWM  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2015-2024 Free Software Foundation, Inc.
+
+;; Author: Chris Feng <chris.w.feng@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module is responsible for keeping X client window properly displayed.
+
+;;; Code:
+
+(require 'exwm-core)
+
+(defgroup exwm-layout nil
+  "Layout."
+  :group 'exwm)
+
+(defcustom exwm-layout-auto-iconify t
+  "Non-nil to automatically iconify unused X windows when possible."
+  :type 'boolean)
+
+(defcustom exwm-layout-show-all-buffers nil
+  "Non-nil to allow switching to buffers on other workspaces."
+  :type 'boolean)
+
+(defconst exwm-layout--floating-hidden-position -101
+  "Where to place hidden floating X windows.")
+
+(defvar exwm-layout--other-buffer-exclude-buffers nil
+  "List of buffers that should not be selected by `other-buffer'.")
+
+(defvar exwm-layout--other-buffer-exclude-exwm-mode-buffers nil
+  "When non-nil, prevent EXWM buffers from being selected by `other-buffer'.")
+
+(defvar exwm-layout--timer nil "Timer used to track echo area changes.")
+
+(defvar exwm-workspace--current)
+(defvar exwm-workspace--frame-y-offset)
+(declare-function exwm-input--release-keyboard "exwm-input.el")
+(declare-function exwm-input--grab-keyboard "exwm-input.el")
+(declare-function exwm-input-grab-keyboard "exwm-input.el")
+(declare-function exwm-workspace--active-p "exwm-workspace.el" (frame))
+(declare-function exwm-workspace--get-geometry "exwm-workspace.el" (frame))
+(declare-function exwm-workspace--minibuffer-own-frame-p "exwm-workspace.el")
+(declare-function exwm-workspace--workspace-p "exwm-workspace.el"
+                  (workspace))
+(declare-function exwm-workspace-move-window "exwm-workspace.el"
+                  (frame-or-index &optional id))
+
+(defun exwm-layout--set-state (id state)
+  "Set WM_STATE of X window ID to STATE."
+  (exwm--log "id=#x%x" id)
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:icccm:set-WM_STATE
+                     :window id :state state :icon xcb:Window:None))
+  (with-current-buffer (exwm--id->buffer id)
+    (setq exwm-state state)))
+
+(defun exwm-layout--iconic-state-p (&optional id)
+  "Check whether X window ID is in iconic state."
+  (= xcb:icccm:WM_STATE:IconicState
+     (if id
+         (buffer-local-value 'exwm-state (exwm--id->buffer id))
+       exwm-state)))
+
+(defun exwm-layout--set-ewmh-state (id)
+  "Set _NET_WM_STATE of X window ID to the value of variable `exwm--ewmh-state'."
+  (with-current-buffer (exwm--id->buffer id)
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ewmh:set-_NET_WM_STATE
+                       :window exwm--id
+                       :data exwm--ewmh-state))))
+
+(defun exwm-layout--fullscreen-p ()
+  "Check whether current `exwm-mode' buffer is in fullscreen state."
+  (when (derived-mode-p 'exwm-mode)
+    (memq xcb:Atom:_NET_WM_STATE_FULLSCREEN exwm--ewmh-state)))
+
+(defun exwm-layout--auto-iconify ()
+  "Helper function to iconify unused X windows.
+See variable `exwm-layout-auto-iconify'."
+  (when (and exwm-layout-auto-iconify
+             (not exwm-transient-for))
+    (let ((xwin exwm--id)
+          (state exwm-state))
+      (dolist (pair exwm--id-buffer-alist)
+        (with-current-buffer (cdr pair)
+          (when (and exwm--floating-frame
+                     (eq exwm-transient-for xwin)
+                     (not (eq exwm-state state)))
+            (if (eq state xcb:icccm:WM_STATE:NormalState)
+                (exwm-layout--refresh-floating exwm--floating-frame)
+              (exwm-layout--hide exwm--id))))))))
+
+(defun exwm-layout--show (id &optional window)
+  "Show window ID exactly fit in the Emacs window WINDOW."
+  (exwm--log "Show #x%x in %s" id window)
+  (let* ((edges (window-inside-absolute-pixel-edges window))
+         (x (pop edges))
+         (y (pop edges))
+         (width (- (pop edges) x))
+         (height (- (pop edges) y))
+         frame-x frame-y frame-width frame-height)
+    (with-current-buffer (exwm--id->buffer id)
+      (when exwm--floating-frame
+        (setq frame-width (frame-pixel-width exwm--floating-frame)
+              frame-height (+ (frame-pixel-height exwm--floating-frame)
+                              ;; Use `frame-outer-height' in the future.
+                              exwm-workspace--frame-y-offset))
+        (when exwm--floating-frame-position
+          (setq frame-x (elt exwm--floating-frame-position 0)
+                frame-y (elt exwm--floating-frame-position 1)
+                x (+ x frame-x (- exwm-layout--floating-hidden-position))
+                y (+ y frame-y (- exwm-layout--floating-hidden-position)))
+          (setq exwm--floating-frame-position nil))
+        (exwm--set-geometry (frame-parameter exwm--floating-frame
+                                             'exwm-container)
+                            frame-x frame-y frame-width frame-height))
+      (when (exwm-layout--fullscreen-p)
+        (with-slots ((x* x)
+                     (y* y)
+                     (width* width)
+                     (height* height))
+            (exwm-workspace--get-geometry exwm--frame)
+          (setq x x*
+                y y*
+                width width*
+                height height*)))
+      (exwm--set-geometry id x y width height)
+      (xcb:+request exwm--connection (make-instance 'xcb:MapWindow :window id))
+      (exwm-layout--set-state id xcb:icccm:WM_STATE:NormalState)
+      (setq exwm--ewmh-state
+            (delq xcb:Atom:_NET_WM_STATE_HIDDEN exwm--ewmh-state))
+      (exwm-layout--set-ewmh-state id)
+      (exwm-layout--auto-iconify)))
+  (xcb:flush exwm--connection))
+
+(defun exwm-layout--hide (id)
+  "Hide window ID."
+  (with-current-buffer (exwm--id->buffer id)
+    (unless (or (exwm-layout--iconic-state-p)
+                (and exwm--floating-frame
+                     exwm--desktop
+                     (= 4294967295. exwm--desktop)))
+      (exwm--log "Hide #x%x" id)
+      (when exwm--floating-frame
+        (let* ((container (frame-parameter exwm--floating-frame
+                                           'exwm-container))
+               (geometry (xcb:+request-unchecked+reply exwm--connection
+                             (make-instance 'xcb:GetGeometry
+                                            :drawable container))))
+          (setq exwm--floating-frame-position
+                (vector (slot-value geometry 'x) (slot-value geometry 'y)))
+          (exwm--set-geometry container exwm-layout--floating-hidden-position
+                              exwm-layout--floating-hidden-position
+                              1
+                              1)))
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ChangeWindowAttributes
+                         :window id :value-mask xcb:CW:EventMask
+                         :event-mask xcb:EventMask:NoEvent))
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:UnmapWindow :window id))
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ChangeWindowAttributes
+                         :window id :value-mask xcb:CW:EventMask
+                         :event-mask (exwm--get-client-event-mask)))
+      (exwm-layout--set-state id xcb:icccm:WM_STATE:IconicState)
+      (cl-pushnew xcb:Atom:_NET_WM_STATE_HIDDEN exwm--ewmh-state)
+      (exwm-layout--set-ewmh-state id)
+      (exwm-layout--auto-iconify)
+      (xcb:flush exwm--connection))))
+
+;;;###autoload
+(cl-defun exwm-layout-set-fullscreen (&optional id)
+  "Make window ID fullscreen."
+  (interactive)
+  (exwm--log "id=#x%x" (or id 0))
+  (unless (and (or id (derived-mode-p 'exwm-mode))
+               (not (exwm-layout--fullscreen-p)))
+    (cl-return-from exwm-layout-set-fullscreen))
+  (with-current-buffer (if id (exwm--id->buffer id) (window-buffer))
+    ;; Expand the X window to fill the whole screen.
+    (with-slots (x y width height) (exwm-workspace--get-geometry exwm--frame)
+      (exwm--set-geometry exwm--id x y width height))
+    ;; Raise the X window.
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ConfigureWindow
+                       :window exwm--id
+                       :value-mask (logior xcb:ConfigWindow:BorderWidth
+                                           xcb:ConfigWindow:StackMode)
+                       :border-width 0
+                       :stack-mode xcb:StackMode:Above))
+    (cl-pushnew xcb:Atom:_NET_WM_STATE_FULLSCREEN exwm--ewmh-state)
+    (exwm-layout--set-ewmh-state exwm--id)
+    (xcb:flush exwm--connection)
+    (set-window-dedicated-p (get-buffer-window) t)
+    (exwm-input--release-keyboard exwm--id)))
+
+;;;###autoload
+(cl-defun exwm-layout-unset-fullscreen (&optional id)
+  "Restore X window ID from fullscreen state."
+  (interactive)
+  (exwm--log "id=#x%x" (or id 0))
+  (unless (and (or id (derived-mode-p 'exwm-mode))
+               (exwm-layout--fullscreen-p))
+    (cl-return-from exwm-layout-unset-fullscreen))
+  (with-current-buffer (if id (exwm--id->buffer id) (window-buffer))
+    ;; `exwm-layout--show' relies on `exwm--ewmh-state' to decide whether to
+    ;; fullscreen the window.
+    (setq exwm--ewmh-state
+          (delq xcb:Atom:_NET_WM_STATE_FULLSCREEN exwm--ewmh-state))
+    (exwm-layout--set-ewmh-state exwm--id)
+    (if exwm--floating-frame
+        (exwm-layout--show exwm--id (frame-root-window exwm--floating-frame))
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ConfigureWindow
+                         :window exwm--id
+                         :value-mask (logior xcb:ConfigWindow:Sibling
+                                             xcb:ConfigWindow:StackMode)
+                         :sibling exwm--guide-window
+                         :stack-mode xcb:StackMode:Above))
+      (let ((window (get-buffer-window nil t)))
+        (when window
+          (exwm-layout--show exwm--id window))))
+    (xcb:flush exwm--connection)
+    (set-window-dedicated-p (get-buffer-window) nil)
+    (when (eq 'line-mode exwm--selected-input-mode)
+      (exwm-input--grab-keyboard exwm--id))))
+
+;;;###autoload
+(cl-defun exwm-layout-toggle-fullscreen (&optional id)
+  "Toggle fullscreen mode of X window ID."
+  (interactive (list (exwm--buffer->id (window-buffer))))
+  (exwm--log "id=#x%x" (or id 0))
+  (unless (or id (derived-mode-p 'exwm-mode))
+    (cl-return-from exwm-layout-toggle-fullscreen))
+  (when id
+    (with-current-buffer (exwm--id->buffer id)
+      (if (exwm-layout--fullscreen-p)
+          (exwm-layout-unset-fullscreen id)
+        (exwm-layout-set-fullscreen id)))))
+
+(defun exwm-layout--other-buffer-predicate (buffer)
+  "Return non-nil when the BUFFER may be displayed in selected frame.
+
+Prevents EXWM-mode buffers already being displayed on some other window from
+being selected.
+
+Should be set as `buffer-predicate' frame parameter for all
+frames.  Used by `other-buffer'.
+
+When variable `exwm-layout--other-buffer-exclude-exwm-mode-buffers'
+is t EXWM buffers are never selected by `other-buffer'.
+
+When variable `exwm-layout--other-buffer-exclude-buffers' is a
+list of buffers, EXWM buffers belonging to that list are never
+selected by `other-buffer'."
+  (or (not (with-current-buffer buffer (derived-mode-p 'exwm-mode)))
+      (and (not exwm-layout--other-buffer-exclude-exwm-mode-buffers)
+           (not (memq buffer exwm-layout--other-buffer-exclude-buffers))
+           ;; Do not select if already shown in some window.
+           (not (get-buffer-window buffer t)))))
+
+(defun exwm-layout--set-client-list-stacking ()
+  "Set _NET_CLIENT_LIST_STACKING."
+  (exwm--log)
+  (let (id clients-floating clients clients-iconic clients-other)
+    (dolist (pair exwm--id-buffer-alist)
+      (setq id (car pair))
+      (with-current-buffer (cdr pair)
+        (if (eq exwm--frame exwm-workspace--current)
+            (if exwm--floating-frame
+                ;; A floating X window on the current workspace.
+                (setq clients-floating (cons id clients-floating))
+              (if (get-buffer-window (cdr pair) exwm-workspace--current)
+                  ;; A normal tilling X window on the current workspace.
+                  (setq clients (cons id clients))
+                ;; An iconic tilling X window on the current workspace.
+                (setq clients-iconic (cons id clients-iconic))))
+          ;; X window on other workspaces.
+          (setq clients-other (cons id clients-other)))))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ewmh:set-_NET_CLIENT_LIST_STACKING
+                       :window exwm--root
+                       :data (vconcat (append clients-other clients-iconic
+                                              clients clients-floating))))))
+
+(defun exwm-layout--refresh (&optional frame)
+  "Refresh layout of FRAME.
+If FRAME is nil, refresh layout of selected frame."
+  ;; `window-size-change-functions' sets this argument while
+  ;; `window-configuration-change-hook' makes the frame selected.
+  (unless frame
+    (setq frame (selected-frame)))
+  (exwm--log "frame=%s" frame)
+  (if (not (exwm-workspace--workspace-p frame))
+      (if (frame-parameter frame 'exwm-outer-id)
+          (exwm-layout--refresh-floating frame)
+        (exwm-layout--refresh-other frame))
+    (exwm-layout--refresh-workspace frame)))
+
+(defun exwm-layout--refresh-floating (frame)
+  "Refresh floating frame FRAME."
+  (exwm--log "Refresh floating %s" frame)
+  (let ((window (frame-first-window frame)))
+    (with-current-buffer (window-buffer window)
+      (when (and (derived-mode-p 'exwm-mode)
+                 ;; It may be a buffer waiting to be killed.
+                 (exwm--id->buffer exwm--id))
+        (exwm--log "Refresh floating window #x%x" exwm--id)
+        (if (exwm-workspace--active-p exwm--frame)
+            (exwm-layout--show exwm--id window)
+          (exwm-layout--hide exwm--id))))))
+
+(defun exwm-layout--refresh-other (frame)
+  "Refresh client or nox frame FRAME."
+  ;; Other frames (e.g. terminal/graphical frame of emacsclient)
+  ;; We shall bury all `exwm-mode' buffers in this case
+  (exwm--log "Refresh other %s" frame)
+  (let ((windows (window-list frame 'nomini)) ;exclude minibuffer
+        (exwm-layout--other-buffer-exclude-exwm-mode-buffers t))
+    (dolist (window windows)
+      (with-current-buffer (window-buffer window)
+        (when (derived-mode-p 'exwm-mode)
+          (if (window-prev-buffers window)
+              (switch-to-prev-buffer window)
+            (switch-to-next-buffer window)))))))
+
+(defun exwm-layout--refresh-workspace (frame)
+  "Refresh workspace frame FRAME."
+  (exwm--log "Refresh workspace %s" frame)
+  ;; Workspaces other than the active one can also be refreshed (RandR)
+  (let (covered-buffers   ;EXWM-buffers covered by a new X window.
+        vacated-windows)  ;Windows previously displaying EXWM-buffers.
+    (dolist (pair exwm--id-buffer-alist)
+      (with-current-buffer (cdr pair)
+        (when (and (not exwm--floating-frame) ;exclude floating X windows
+                   (or exwm-layout-show-all-buffers
+                       ;; Exclude X windows on other workspaces
+                       (eq frame exwm--frame)))
+          (let (;; List of windows in current frame displaying the `exwm-mode'
+                ;; buffers.
+                (windows (get-buffer-window-list (current-buffer) 'nomini
+                                                 frame)))
+            (if (not windows)
+                (when (eq frame exwm--frame)
+                  ;; Hide it if it was being shown in this workspace.
+                  (exwm-layout--hide exwm--id))
+              (let ((window (car windows)))
+                (if (eq frame exwm--frame)
+                    ;; Show it if `frame' is active, hide otherwise.
+                    (if (exwm-workspace--active-p frame)
+                        (exwm-layout--show exwm--id window)
+                      (exwm-layout--hide exwm--id))
+                  ;; It was last shown in other workspace; move it here.
+                  (exwm-workspace-move-window frame exwm--id))
+                ;; Vacate any other windows (in any workspace) showing this
+                ;; `exwm-mode' buffer.
+                (setq vacated-windows
+                      (append vacated-windows (remove
+                                               window
+                                               (get-buffer-window-list
+                                                (current-buffer) 'nomini t))))
+                ;; Note any `exwm-mode' buffer is being covered by another
+                ;; `exwm-mode' buffer.  We want to avoid that `exwm-mode'
+                ;; buffer to be reappear in any of the vacated windows.
+                (let ((prev-buffer (car-safe
+                                    (car-safe (window-prev-buffers window)))))
+                  (and
+                   prev-buffer
+                   (with-current-buffer prev-buffer
+                     (derived-mode-p 'exwm-mode))
+                   (push prev-buffer covered-buffers)))))))))
+    ;; Set some sensible buffer to vacated windows.
+    (let ((exwm-layout--other-buffer-exclude-buffers covered-buffers))
+      (dolist (window vacated-windows)
+        (if (window-prev-buffers window)
+            (switch-to-prev-buffer window)
+          (switch-to-next-buffer window))))
+    ;; Make sure windows floating / on other workspaces are excluded
+    (let ((exwm-layout--other-buffer-exclude-exwm-mode-buffers t))
+      (dolist (window (window-list frame 'nomini))
+        (with-current-buffer (window-buffer window)
+          (when (and (derived-mode-p 'exwm-mode)
+                     (or exwm--floating-frame (not (eq frame exwm--frame))))
+            (if (window-prev-buffers window)
+                (switch-to-prev-buffer window)
+              (switch-to-next-buffer window))))))
+    (exwm-layout--set-client-list-stacking)
+    (xcb:flush exwm--connection)))
+
+(defun exwm-layout--on-minibuffer-setup ()
+  "Refresh layout when minibuffer grows."
+  (exwm--log)
+  ;; Only when active minibuffer's frame is an EXWM frame.
+  (let* ((mini-window (active-minibuffer-window))
+         (frame (window-frame mini-window)))
+    (when (exwm-workspace--workspace-p frame)
+      (exwm--defer 0 (lambda ()
+                       (when (< 1 (window-height mini-window))
+                         (exwm-layout--refresh frame)))))))
+
+(defun exwm-layout--on-echo-area-change (&optional dirty)
+  "Run when message arrives or in `echo-area-clear-hook' to refresh layout.
+If DIRTY is non-nil, refresh layout immediately."
+  (let ((frame (window-frame (active-minibuffer-window)))
+        (msg (current-message)))
+    ;; Check whether the frame where current window's minibuffer resides (not
+    ;; current window's frame for floating windows!) must be adjusted.
+    (when (and msg
+               (exwm-workspace--workspace-p frame)
+               (or (cl-position ?\n msg)
+                   (> (length msg) (frame-width frame))))
+      (exwm--log)
+      (if dirty
+          (exwm-layout--refresh exwm-workspace--current)
+        (exwm--defer 0 #'exwm-layout--refresh exwm-workspace--current)))))
+
+;;;###autoload
+(defun exwm-layout-enlarge-window (delta &optional horizontal)
+  "Make the selected window DELTA pixels taller.
+
+If no argument is given, make the selected window one pixel taller.  If the
+optional argument HORIZONTAL is non-nil, make selected window DELTA pixels
+wider.  If DELTA is negative, shrink selected window by -DELTA pixels.
+
+Normal hints are checked and regarded if the selected window is displaying an
+`exwm-mode' buffer.  However, this may violate the normal hints set on other X
+windows."
+  (interactive "p")
+  (exwm--log)
+  (cond
+   ((zerop delta))                     ;no operation
+   ((window-minibuffer-p))             ;avoid resize minibuffer-window
+   ((not (and (derived-mode-p 'exwm-mode) exwm--floating-frame))
+    ;; Resize on tiling layout
+    (unless (= 0 (window-resizable nil delta horizontal nil t)) ;not resizable
+      (let ((window-resize-pixelwise t))
+        (window-resize nil delta horizontal nil t))))
+   ;; Resize on floating layout
+   (exwm--fixed-size)                   ;fixed size
+   (horizontal
+    (let* ((width (frame-pixel-width))
+           (edges (window-inside-pixel-edges))
+           (inner-width (- (elt edges 2) (elt edges 0)))
+           (margin (- width inner-width)))
+      (if (> delta 0)
+          (if (not exwm--normal-hints-max-width)
+              (cl-incf width delta)
+            (if (>= inner-width exwm--normal-hints-max-width)
+                (setq width nil)
+              (setq width (min (+ exwm--normal-hints-max-width margin)
+                               (+ width delta)))))
+        (if (not exwm--normal-hints-min-width)
+            (cl-incf width delta)
+          (if (<= inner-width exwm--normal-hints-min-width)
+              (setq width nil)
+            (setq width (max (+ exwm--normal-hints-min-width margin)
+                             (+ width delta))))))
+      (when (and width (> width 0))
+        (setf (slot-value exwm--geometry 'width) width)
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:ConfigureWindow
+                           :window (frame-parameter exwm--floating-frame
+                                                    'exwm-outer-id)
+                           :value-mask xcb:ConfigWindow:Width
+                           :width width))
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:ConfigureWindow
+                           :window (frame-parameter exwm--floating-frame
+                                                    'exwm-container)
+                           :value-mask xcb:ConfigWindow:Width
+                           :width width))
+        (xcb:flush exwm--connection))))
+   (t
+    (let* ((height (+ (frame-pixel-height) exwm-workspace--frame-y-offset))
+           (edges (window-inside-pixel-edges))
+           (inner-height (- (elt edges 3) (elt edges 1)))
+           (margin (- height inner-height)))
+      (if (> delta 0)
+          (if (not exwm--normal-hints-max-height)
+              (cl-incf height delta)
+            (if (>= inner-height exwm--normal-hints-max-height)
+                (setq height nil)
+              (setq height (min (+ exwm--normal-hints-max-height margin)
+                                (+ height delta)))))
+        (if (not exwm--normal-hints-min-height)
+            (cl-incf height delta)
+          (if (<= inner-height exwm--normal-hints-min-height)
+              (setq height nil)
+            (setq height (max (+ exwm--normal-hints-min-height margin)
+                              (+ height delta))))))
+      (when (and height (> height 0))
+        (setf (slot-value exwm--geometry 'height) height)
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:ConfigureWindow
+                           :window (frame-parameter exwm--floating-frame
+                                                    'exwm-outer-id)
+                           :value-mask xcb:ConfigWindow:Height
+                           :height height))
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:ConfigureWindow
+                           :window (frame-parameter exwm--floating-frame
+                                                    'exwm-container)
+                           :value-mask xcb:ConfigWindow:Height
+                           :height height))
+        (xcb:flush exwm--connection))))))
+
+;;;###autoload
+(defun exwm-layout-enlarge-window-horizontally (delta)
+  "Make the selected window DELTA pixels wider.
+
+See also `exwm-layout-enlarge-window'."
+  (interactive "p")
+  (exwm--log "%s" delta)
+  (exwm-layout-enlarge-window delta t))
+
+;;;###autoload
+(defun exwm-layout-shrink-window (delta)
+  "Make the selected window DELTA pixels lower.
+
+See also `exwm-layout-enlarge-window'."
+  (interactive "p")
+  (exwm--log "%s" delta)
+  (exwm-layout-enlarge-window (- delta)))
+
+;;;###autoload
+(defun exwm-layout-shrink-window-horizontally (delta)
+  "Make the selected window DELTA pixels narrower.
+
+See also `exwm-layout-enlarge-window'."
+  (interactive "p")
+  (exwm--log "%s" delta)
+  (exwm-layout-enlarge-window (- delta) t))
+
+;;;###autoload
+(defun exwm-layout-hide-mode-line ()
+  "Hide mode-line."
+  (interactive)
+  (exwm--log)
+  (when (and (derived-mode-p 'exwm-mode) mode-line-format)
+    (let (mode-line-height)
+      (when exwm--floating-frame
+        (setq mode-line-height (window-mode-line-height
+                                (frame-root-window exwm--floating-frame))))
+      (setq exwm--mode-line-format mode-line-format
+            mode-line-format nil)
+      (if (not exwm--floating-frame)
+          (exwm-layout--show exwm--id)
+        (set-frame-height exwm--floating-frame
+                          (- (frame-pixel-height exwm--floating-frame)
+                             mode-line-height)
+                          nil t)))))
+
+;;;###autoload
+(defun exwm-layout-show-mode-line ()
+  "Show mode-line."
+  (interactive)
+  (exwm--log)
+  (when (and (derived-mode-p 'exwm-mode) (not mode-line-format))
+    (setq mode-line-format exwm--mode-line-format
+          exwm--mode-line-format nil)
+    (if (not exwm--floating-frame)
+        (exwm-layout--show exwm--id)
+      (set-frame-height exwm--floating-frame
+                        (+ (frame-pixel-height exwm--floating-frame)
+                           (window-mode-line-height (frame-root-window
+                                                     exwm--floating-frame)))
+                        nil t)
+      (call-interactively #'exwm-input-grab-keyboard))
+    (force-mode-line-update)))
+
+;;;###autoload
+(defun exwm-layout-toggle-mode-line ()
+  "Toggle the display of mode-line."
+  (interactive)
+  (exwm--log)
+  (when (derived-mode-p 'exwm-mode)
+    (if mode-line-format
+        (exwm-layout-hide-mode-line)
+      (exwm-layout-show-mode-line))))
+
+(defun exwm-layout--init ()
+  "Initialize layout module."
+  ;; Auto refresh layout
+  (exwm--log)
+  (add-hook 'window-configuration-change-hook #'exwm-layout--refresh)
+  ;; The behavior of `window-configuration-change-hook' will be changed.
+  (when (fboundp 'window-pixel-width-before-size-change)
+    (add-hook 'window-size-change-functions #'exwm-layout--refresh))
+  (unless (exwm-workspace--minibuffer-own-frame-p)
+    ;; Refresh when minibuffer grows
+    (add-hook 'minibuffer-setup-hook #'exwm-layout--on-minibuffer-setup t)
+    (setq exwm-layout--timer
+          (run-with-idle-timer 0 t #'exwm-layout--on-echo-area-change t))
+    (add-hook 'echo-area-clear-hook #'exwm-layout--on-echo-area-change)))
+
+(defun exwm-layout--exit ()
+  "Exit the layout module."
+  (exwm--log)
+  (remove-hook 'window-configuration-change-hook #'exwm-layout--refresh)
+  (when (fboundp 'window-pixel-width-before-size-change)
+    (remove-hook 'window-size-change-functions #'exwm-layout--refresh))
+  (remove-hook 'minibuffer-setup-hook #'exwm-layout--on-minibuffer-setup)
+  (when exwm-layout--timer
+    (cancel-timer exwm-layout--timer)
+    (setq exwm-layout--timer nil))
+  (remove-hook 'echo-area-clear-hook #'exwm-layout--on-echo-area-change))
+
+
+
+(provide 'exwm-layout)
+
+;;; exwm-layout.el ends here
diff --git a/exwm-manage.el b/exwm-manage.el
new file mode 100644
index 0000000000..ab66e298ac
--- /dev/null
+++ b/exwm-manage.el
@@ -0,0 +1,833 @@
+;;; exwm-manage.el --- Window Management Module for  -*- lexical-binding: t -*-
+;;;                    EXWM
+
+;; Copyright (C) 2015-2024 Free Software Foundation, Inc.
+
+;; Author: Chris Feng <chris.w.feng@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This is the fundamental module of EXWM that deals with window management.
+
+;;; Code:
+
+(require 'exwm-core)
+
+(defgroup exwm-manage nil
+  "Manage."
+  :group 'exwm)
+
+(defcustom exwm-manage-finish-hook nil
+  "Normal hook run after a window is just managed.
+This hook runs in the context of the corresponding `exwm-mode' buffer."
+  :type 'hook)
+
+(defcustom exwm-manage-force-tiling nil
+  "Non-nil to force managing all X windows in tiling layout.
+You can still make the X windows floating afterwards."
+  :type 'boolean)
+
+(defcustom exwm-manage-ping-timeout 3
+  "Seconds to wait before killing a client."
+  :type 'integer)
+
+(defcustom exwm-manage-configurations nil
+  "Per-application configurations.
+
+Configuration options allow to override various default behaviors of EXWM
+and only take effect when they are present.  Note for certain options
+specifying nil is not exactly the same as leaving them out.  Currently
+possible choices:
+* floating: Force floating (non-nil) or tiling (nil) on startup.
+* x/y/width/height: Override the initial geometry (floating X window only).
+* border-width: Override the border width (only visible when floating).
+* fullscreen: Force full screen (non-nil) on startup.
+* floating-mode-line: `mode-line-format' used when floating.
+* tiling-mode-line: `mode-line-format' used when tiling.
+* floating-header-line: `header-line-format' used when floating.
+* tiling-header-line: `header-line-format' used when tiling.
+* char-mode: Force char-mode (non-nil) on startup.
+* prefix-keys: `exwm-input-prefix-keys' local to this X window.
+* simulation-keys: `exwm-input-simulation-keys' local to this X window.
+* workspace: The initial workspace.
+* managed: Force to manage (non-nil) or not manage (nil) the X window.
+
+For each X window managed for the first time, matching criteria (sexps) are
+evaluated sequentially and the first configuration with a non-nil matching
+criterion would be applied.  Apart from generic forms, one would typically
+want to match against EXWM internal variables such as `exwm-title',
+`exwm-class-name' and `exwm-instance-name'."
+  :type '(alist :key-type (sexp :tag "Matching criterion" nil)
+                :value-type
+                (plist :tag "Configurations"
+                       :options
+                       (((const :tag "Floating" floating) boolean)
+                        ((const :tag "X" x) number)
+                        ((const :tag "Y" y) number)
+                        ((const :tag "Width" width) number)
+                        ((const :tag "Height" height) number)
+                        ((const :tag "Border width" border-width) integer)
+                        ((const :tag "Fullscreen" fullscreen) boolean)
+                        ((const :tag "Floating mode-line" floating-mode-line)
+                         sexp)
+                        ((const :tag "Tiling mode-line" tiling-mode-line) sexp)
+                        ((const :tag "Floating header-line"
+                                floating-header-line)
+                         sexp)
+                        ((const :tag "Tiling header-line" tiling-header-line)
+                         sexp)
+                        ((const :tag "Char-mode" char-mode) boolean)
+                        ((const :tag "Prefix keys" prefix-keys)
+                         (repeat key-sequence))
+                        ((const :tag "Simulation keys" simulation-keys)
+                         (alist :key-type (key-sequence :tag "From")
+                                :value-type (key-sequence :tag "To")))
+                        ((const :tag "Workspace" workspace) integer)
+                        ((const :tag "Managed" managed) boolean)
+                        ;; For forward compatibility.
+                        ((other) sexp))))
+  ;; TODO: This is admittedly ugly.  We'd be better off with an event type.
+  :get (lambda (symbol)
+         (mapcar (lambda (pair)
+                   (let* ((match (car pair))
+                          (config (cdr pair))
+                          (prefix-keys (plist-get config 'prefix-keys)))
+                     (when prefix-keys
+                       (setq config (copy-tree config)
+                             config (plist-put config 'prefix-keys
+                                               (mapcar (lambda (i)
+                                                         (if (sequencep i)
+                                                             i
+                                                           (vector i)))
+                                                       prefix-keys))))
+                     (cons match config)))
+                 (default-value symbol)))
+  :set (lambda (symbol value)
+         (set symbol
+              (mapcar (lambda (pair)
+                        (let* ((match (car pair))
+                               (config (cdr pair))
+                               (prefix-keys (plist-get config 'prefix-keys)))
+                          (when prefix-keys
+                            (setq config (copy-tree config)
+                                  config (plist-put config 'prefix-keys
+                                                    (mapcar (lambda (i)
+                                                              (if (sequencep i)
+                                                                  (aref i 0)
+                                                                i))
+                                                            prefix-keys))))
+                          (cons match config)))
+                      value))))
+
+;; FIXME: Make the following values as small as possible.
+(defconst exwm-manage--height-delta-min 5)
+(defconst exwm-manage--width-delta-min 5)
+
+;; The _MOTIF_WM_HINTS atom (see <Xm/MwmUtil.h> for more details)
+;; It's currently only used in 'exwm-manage' module
+(defvar exwm-manage--_MOTIF_WM_HINTS nil "_MOTIF_WM_HINTS atom.")
+
+(defvar exwm-manage--desktop nil "The desktop X window.")
+
+(defvar exwm-manage--frame-outer-id-list nil
+  "List of window-outer-id's of all frames.")
+
+(defvar exwm-manage--ping-lock nil
+  "Non-nil indicates EXWM is pinging a window.")
+
+(defvar exwm-input--skip-buffer-list-update)
+(defvar exwm-input-prefix-keys)
+(defvar exwm-workspace--current)
+(defvar exwm-workspace--id-struts-alist)
+(defvar exwm-workspace--list)
+(defvar exwm-workspace--switch-history-outdated)
+(defvar exwm-workspace-current-index)
+(declare-function exwm--update-class "exwm.el" (id &optional force))
+(declare-function exwm--update-hints "exwm.el" (id &optional force))
+(declare-function exwm--update-normal-hints "exwm.el" (id &optional force))
+(declare-function exwm--update-protocols "exwm.el" (id &optional force))
+(declare-function exwm--update-struts "exwm.el" (id))
+(declare-function exwm--update-title "exwm.el" (id))
+(declare-function exwm--update-transient-for "exwm.el" (id &optional force))
+(declare-function exwm--update-desktop "exwm.el" (id &optional force))
+(declare-function exwm--update-window-type "exwm.el" (id &optional force))
+(declare-function exwm-floating--set-floating "exwm-floating.el" (id))
+(declare-function exwm-floating--unset-floating "exwm-floating.el" (id))
+(declare-function exwm-input-grab-keyboard "exwm-input.el" (&optional id))
+(declare-function exwm-input-release-keyboard "exwm-input.el" (&optional id))
+(declare-function exwm-input-set-local-simulation-keys "exwm-input.el")
+(declare-function exwm-layout--fullscreen-p "exwm-layout.el" ())
+(declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id))
+(declare-function exwm-layout-set-fullscreen "exwm-layout.el" (&optional id))
+(declare-function exwm-workspace--get-geometry "exwm-workspace.el" (frame))
+(declare-function exwm-workspace--position "exwm-workspace.el" (frame))
+(declare-function exwm-workspace--set-fullscreen "exwm-workspace.el" (frame))
+(declare-function exwm-workspace--update-struts "exwm-workspace.el" ())
+(declare-function exwm-workspace--update-workareas "exwm-workspace.el" ())
+(declare-function exwm-workspace--workarea "exwm-workspace.el" (frame))
+
+(defun exwm-manage--update-geometry (id &optional force)
+  "Update geometry of X window ID.
+Override current geometry if FORCE is non-nil."
+  (exwm--log "id=#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (unless (and exwm--geometry (not force))
+      (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:GetGeometry :drawable id))))
+        (setq exwm--geometry
+              (or reply
+                  ;; Provide a reasonable fallback value.
+                  (make-instance 'xcb:RECTANGLE
+                                 :x 0
+                                 :y 0
+                                 :width (/ (x-display-pixel-width) 2)
+                                 :height (/ (x-display-pixel-height) 2))))))))
+
+(defun exwm-manage--update-ewmh-state (id)
+  "Update _NET_WM_STATE of X window ID."
+  (exwm--log "id=#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (unless exwm--ewmh-state
+      (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:ewmh:get-_NET_WM_STATE
+                                      :window id))))
+        (when reply
+          (setq exwm--ewmh-state (append (slot-value reply 'value) nil)))))))
+
+(defun exwm-manage--update-mwm-hints (id &optional force)
+  "Update _MOTIF_WM_HINTS of X window ID.
+Override current hinds if FORCE is non-nil."
+  (exwm--log "id=#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (unless (and (not exwm--mwm-hints-decorations) (not force))
+      (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:icccm:-GetProperty
+                                      :window id
+                                      :property exwm-manage--_MOTIF_WM_HINTS
+                                      :type exwm-manage--_MOTIF_WM_HINTS
+                                      :long-length 5))))
+        (when reply
+          ;; Check MotifWmHints.decorations.
+          (with-slots (value) reply
+            (setq value (append value nil))
+            (when (and value
+                       ;; See <Xm/MwmUtil.h> for fields definitions.
+                       (/= 0 (logand
+                              (elt value 0) ;MotifWmHints.flags
+                              2))           ;MWM_HINTS_DECORATIONS
+                       (= 0
+                          (elt value 2))) ;MotifWmHints.decorations
+              (setq exwm--mwm-hints-decorations nil))))))))
+
+(defun exwm-manage--update-default-directory (id)
+  "Update the `default-directory' of X window ID.
+Sets the `default-directory' of the EXWM buffer associated with X window to
+match its current working directory.
+
+This only works when procfs is mounted, which may not be the case on some BSDs."
+  (with-current-buffer (exwm--id->buffer id)
+    (if-let* ((response (xcb:+request-unchecked+reply exwm--connection
+                            (make-instance 'xcb:ewmh:get-_NET_WM_PID
+                                           :window id)))
+              (pid (slot-value response 'value))
+              (cwd (file-symlink-p (format "/proc/%d/cwd" pid)))
+              ((file-accessible-directory-p cwd)))
+        (setq default-directory (file-name-as-directory cwd))
+      (setq default-directory (expand-file-name "~/")))))
+
+
+(defun exwm-manage--set-client-list ()
+  "Set _NET_CLIENT_LIST."
+  (exwm--log)
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ewmh:set-_NET_CLIENT_LIST
+                     :window exwm--root
+                     :data (vconcat (mapcar #'car exwm--id-buffer-alist)))))
+
+(cl-defun exwm-manage--get-configurations ()
+  "Retrieve configurations for this buffer."
+  (exwm--log)
+  (when (derived-mode-p 'exwm-mode)
+    (dolist (i exwm-manage-configurations)
+      (save-current-buffer
+        (when (with-demoted-errors "Problematic configuration: %S"
+                (eval (car i) t))
+          (cl-return-from exwm-manage--get-configurations (cdr i)))))))
+
+(defun exwm-manage--manage-window (id)
+  "Manage window ID."
+  (exwm--log "Try to manage #x%x" id)
+  (catch 'return
+    ;; Ensure it's alive
+    (when (xcb:+request-checked+request-check exwm--connection
+              (make-instance 'xcb:ChangeWindowAttributes
+                             :window id :value-mask xcb:CW:EventMask
+                             :event-mask (exwm--get-client-event-mask)))
+      (throw 'return 'dead))
+    ;; Add this X window to save-set.
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ChangeSaveSet
+                       :mode xcb:SetMode:Insert
+                       :window id))
+    (with-current-buffer (let ((exwm-input--skip-buffer-list-update t))
+                           (generate-new-buffer "*EXWM*"))
+      ;; Keep the oldest X window first.
+      (setq exwm--id-buffer-alist
+            (nconc exwm--id-buffer-alist `((,id . ,(current-buffer)))))
+      (exwm-mode)
+      (setq exwm--id id
+            exwm--frame exwm-workspace--current)
+      (exwm--update-window-type id)
+      (exwm--update-class id)
+      (exwm--update-transient-for id)
+      (exwm--update-normal-hints id)
+      (exwm--update-hints id)
+      (exwm-manage--update-geometry id)
+      (exwm-manage--update-mwm-hints id)
+      (exwm--update-title id)
+      (exwm--update-protocols id)
+      (setq exwm--configurations (exwm-manage--get-configurations))
+      ;; OverrideRedirect is not checked here.
+      (when (and
+             ;; The user has specified to manage it.
+             (not (plist-get exwm--configurations 'managed))
+             (or
+              ;; The user has specified not to manage it.
+              (plist-member exwm--configurations 'managed)
+              ;; This is not a type of X window we can manage.
+              (and exwm-window-type
+                   (not (cl-intersection
+                         exwm-window-type
+                         (list xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY
+                               xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG
+                               xcb:Atom:_NET_WM_WINDOW_TYPE_NORMAL))))
+              ;; Check the _MOTIF_WM_HINTS property to not manage floating X
+              ;; windows without decoration.
+              (and (not exwm--mwm-hints-decorations)
+                   (not exwm--hints-input)
+                   ;; Floating windows only
+                   (or exwm-transient-for exwm--fixed-size
+                       (memq xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY
+                             exwm-window-type)
+                       (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG
+                             exwm-window-type)))))
+        (exwm--log "No need to manage #x%x" id)
+        ;; Update struts.
+        (when (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DOCK exwm-window-type)
+          (exwm--update-struts id))
+        ;; Remove all events
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:ChangeWindowAttributes
+                           :window id :value-mask xcb:CW:EventMask
+                           :event-mask
+                           (if (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DOCK
+                                     exwm-window-type)
+                               ;; Listen for PropertyChange (struts) and
+                               ;; UnmapNotify/DestroyNotify event of the dock.
+                               (exwm--get-client-event-mask)
+                             xcb:EventMask:NoEvent)))
+        ;; The window needs to be mapped
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:MapWindow :window id))
+        (with-slots (x y width height) exwm--geometry
+          ;; Center window of type _NET_WM_WINDOW_TYPE_SPLASH
+          (when (memq xcb:Atom:_NET_WM_WINDOW_TYPE_SPLASH exwm-window-type)
+            (with-slots ((x* x) (y* y) (width* width) (height* height))
+                (exwm-workspace--workarea exwm--frame)
+              (exwm--set-geometry id
+                                  (+ x* (/ (- width* width) 2))
+                                  (+ y* (/ (- height* height) 2))
+                                  nil
+                                  nil))))
+        ;; Check for desktop.
+        (when (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DESKTOP exwm-window-type)
+          ;; There should be only one desktop X window.
+          (setq exwm-manage--desktop id)
+          ;; Put it at bottom.
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:ConfigureWindow
+                             :window id
+                             :value-mask xcb:ConfigWindow:StackMode
+                             :stack-mode xcb:StackMode:Below)))
+        (xcb:flush exwm--connection)
+        (setq exwm--id-buffer-alist (assq-delete-all id exwm--id-buffer-alist))
+        (let ((kill-buffer-query-functions nil)
+              (exwm-input--skip-buffer-list-update t))
+          (kill-buffer (current-buffer)))
+        (throw 'return 'ignored))
+      (let ((index (plist-get exwm--configurations 'workspace)))
+        (when (and index (< index (length exwm-workspace--list)))
+          (setq exwm--frame (elt exwm-workspace--list index))))
+      ;; Manage the window
+      (exwm--log "Manage #x%x" id)
+      (xcb:+request exwm--connection    ;remove border
+          (make-instance 'xcb:ConfigureWindow
+                         :window id :value-mask xcb:ConfigWindow:BorderWidth
+                         :border-width 0))
+      (dolist (button       ;grab buttons to set focus / move / resize
+               (list xcb:ButtonIndex:1 xcb:ButtonIndex:2 xcb:ButtonIndex:3))
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:GrabButton
+                           :owner-events 0 :grab-window id
+                           :event-mask xcb:EventMask:ButtonPress
+                           :pointer-mode xcb:GrabMode:Sync
+                           :keyboard-mode xcb:GrabMode:Async
+                           :confine-to xcb:Window:None :cursor xcb:Cursor:None
+                           :button button :modifiers xcb:ModMask:Any)))
+      (exwm-manage--set-client-list)
+      (xcb:flush exwm--connection)
+      (if (plist-member exwm--configurations 'floating)
+          ;; User has specified whether it should be floating.
+          (if (plist-get exwm--configurations 'floating)
+              (exwm-floating--set-floating id)
+            (with-selected-window (frame-selected-window exwm--frame)
+              (exwm-floating--unset-floating id)))
+        ;; Try to determine if it should be floating.
+        (if (and (not exwm-manage-force-tiling)
+                 (or exwm-transient-for exwm--fixed-size
+                     (memq xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY
+                           exwm-window-type)
+                     (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG
+                           exwm-window-type)))
+            (exwm-floating--set-floating id)
+          (with-selected-window (frame-selected-window exwm--frame)
+            (exwm-floating--unset-floating id))))
+      (if (plist-get exwm--configurations 'char-mode)
+          (exwm-input-release-keyboard id)
+        (exwm-input-grab-keyboard id))
+      (when-let ((simulation-keys (plist-get exwm--configurations 'simulation-keys)))
+        (exwm-input-set-local-simulation-keys simulation-keys))
+      (when-let ((prefix-keys (plist-get exwm--configurations 'prefix-keys)))
+        (setq-local exwm-input-prefix-keys prefix-keys))
+      (setq exwm-workspace--switch-history-outdated t)
+      (exwm--update-desktop id)
+      (exwm-manage--update-ewmh-state id)
+      (exwm-manage--update-default-directory id)
+      (when (or (plist-get exwm--configurations 'fullscreen)
+                (exwm-layout--fullscreen-p))
+        (setq exwm--ewmh-state (delq xcb:Atom:_NET_WM_STATE_FULLSCREEN
+                                     exwm--ewmh-state))
+        (exwm-layout-set-fullscreen id))
+      (run-hooks 'exwm-manage-finish-hook))))
+
+(defun exwm-manage--unmanage-window (id &optional withdraw-only)
+  "Unmanage window ID.
+
+If WITHDRAW-ONLY is non-nil, the X window will be properly placed back to the
+root window.  Set WITHDRAW-ONLY to `quit' if this functions is used when window
+manager is shutting down."
+  (let ((buffer (exwm--id->buffer id)))
+    (exwm--log "Unmanage #x%x (buffer: %s, widthdraw: %s)"
+               id buffer withdraw-only)
+    (setq exwm--id-buffer-alist (assq-delete-all id exwm--id-buffer-alist))
+    ;; Update workspaces when a dock is destroyed.
+    (when (and (null withdraw-only)
+               (assq id exwm-workspace--id-struts-alist))
+      (setq exwm-workspace--id-struts-alist
+            (assq-delete-all id exwm-workspace--id-struts-alist))
+      (exwm-workspace--update-struts)
+      (exwm-workspace--update-workareas)
+      (dolist (f exwm-workspace--list)
+        (exwm-workspace--set-fullscreen f)))
+    (when (and (buffer-live-p buffer)
+               ;; Invoked from `exwm-manage--exit' upon disconnection.
+               (slot-value exwm--connection 'connected))
+      (with-current-buffer buffer
+        ;; Unmap the X window.
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:UnmapWindow :window id))
+        ;;
+        (setq exwm-workspace--switch-history-outdated t)
+        ;;
+        (when withdraw-only
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:ChangeWindowAttributes
+                             :window id :value-mask xcb:CW:EventMask
+                             :event-mask xcb:EventMask:NoEvent))
+          ;; Delete WM_STATE property
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:DeleteProperty
+                             :window id :property xcb:Atom:WM_STATE))
+          (cond
+           ((eq withdraw-only 'quit)
+            ;; Remap the window when exiting.
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:MapWindow :window id)))
+           (t
+            ;; Remove _NET_WM_DESKTOP.
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:DeleteProperty
+                               :window id
+                               :property xcb:Atom:_NET_WM_DESKTOP)))))
+        (when exwm--floating-frame
+          ;; Unmap the floating frame before destroying its container.
+          (let ((window (frame-parameter exwm--floating-frame 'exwm-outer-id))
+                (container (frame-parameter exwm--floating-frame
+                                            'exwm-container)))
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:UnmapWindow :window window))
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:ReparentWindow
+                               :window window :parent exwm--root :x 0 :y 0))
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:DestroyWindow :window container))))
+        (when (exwm-layout--fullscreen-p)
+          (let ((window (get-buffer-window)))
+            (when window
+              (set-window-dedicated-p window nil))))
+        (exwm-manage--set-client-list)
+        (xcb:flush exwm--connection))
+      (let ((kill-buffer-func
+             (lambda (buffer)
+               (when (buffer-local-value 'exwm--floating-frame buffer)
+                 (select-window
+                  (frame-selected-window exwm-workspace--current)))
+               (with-current-buffer buffer
+                 (let ((kill-buffer-query-functions nil))
+                   (kill-buffer buffer))))))
+        (exwm--defer 0 kill-buffer-func buffer)
+        (when (active-minibuffer-window)
+          (exit-minibuffer))))))
+
+(defun exwm-manage--scan ()
+  "Search for existing windows and try to manage them."
+  (exwm--log)
+  (let* ((tree (xcb:+request-unchecked+reply exwm--connection
+                   (make-instance 'xcb:QueryTree
+                                  :window exwm--root)))
+         reply)
+    (dolist (i (slot-value tree 'children))
+      (setq reply (xcb:+request-unchecked+reply exwm--connection
+                      (make-instance 'xcb:GetWindowAttributes
+                                     :window i)))
+      ;; It's possible the X window has been destroyed.
+      (when reply
+        (with-slots (override-redirect map-state) reply
+          (when (and (= 0 override-redirect)
+                     (= xcb:MapState:Viewable map-state))
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:UnmapWindow
+                               :window i))
+            (xcb:flush exwm--connection)
+            (exwm-manage--manage-window i)))))))
+
+(defun exwm-manage--kill-buffer-query-function ()
+  "Run in `kill-buffer-query-functions'."
+  (exwm--log "id=#x%x; buffer=%s" (or exwm--id 0) (current-buffer))
+  (catch 'return
+    (when (or (not exwm--connection)
+              (not (slot-value exwm--connection 'connected)))
+      (throw 'return t))
+    (when (or (not exwm--id)
+              (xcb:+request-checked+request-check exwm--connection
+                  (make-instance 'xcb:ChangeWindowAttributes
+                                 :window exwm--id
+                                 :value-mask xcb:CW:EventMask
+                                 :event-mask (exwm--get-client-event-mask))))
+      ;; The X window is no longer alive so just close the buffer.
+      (when exwm--floating-frame
+        (let ((window (frame-parameter exwm--floating-frame 'exwm-outer-id))
+              (container (frame-parameter exwm--floating-frame
+                                          'exwm-container)))
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:UnmapWindow :window window))
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:ReparentWindow
+                             :window window
+                             :parent exwm--root
+                             :x 0 :y 0))
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:DestroyWindow
+                             :window container))))
+      (xcb:flush exwm--connection)
+      (throw 'return t))
+    (unless (memq xcb:Atom:WM_DELETE_WINDOW exwm--protocols)
+      ;; The X window does not support WM_DELETE_WINDOW; destroy it.
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:DestroyWindow :window exwm--id))
+      (xcb:flush exwm--connection)
+      ;; Wait for DestroyNotify event.
+      (throw 'return nil))
+    (let ((id exwm--id))
+      ;; Try to close the X window with WM_DELETE_WINDOW client message.
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:icccm:SendEvent
+                         :destination id
+                         :event (xcb:marshal
+                                 (make-instance 'xcb:icccm:WM_DELETE_WINDOW
+                                                :window id)
+                                 exwm--connection)))
+      (xcb:flush exwm--connection)
+      ;;
+      (unless (memq xcb:Atom:_NET_WM_PING exwm--protocols)
+        ;; For X windows without _NET_WM_PING support, we'd better just
+        ;; wait for DestroyNotify events.
+        (throw 'return nil))
+      ;; Try to determine if the X window is dead with _NET_WM_PING.
+      (setq exwm-manage--ping-lock t)
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:SendEvent
+                         :propagate 0
+                         :destination id
+                         :event-mask xcb:EventMask:NoEvent
+                         :event (xcb:marshal
+                                 (make-instance 'xcb:ewmh:_NET_WM_PING
+                                                :window id
+                                                :timestamp 0
+                                                :client-window id)
+                                 exwm--connection)))
+      (xcb:flush exwm--connection)
+      (with-timeout (exwm-manage-ping-timeout
+                     (if (y-or-n-p (format "'%s' is not responding.  \
+Would you like to kill it? "
+                                              (buffer-name)))
+                         (progn (exwm-manage--kill-client id)
+                                ;; Kill the unresponsive X window and
+                                ;; wait for DestroyNotify event.
+                                (throw 'return nil))
+                       ;; Give up.
+                       (throw 'return nil)))
+        (while (and exwm-manage--ping-lock
+                    (exwm--id->buffer id)) ;may have been destroyed.
+          (accept-process-output nil 0.1))
+        ;; Give up.
+        (throw 'return nil)))))
+
+(defun exwm-manage--kill-client (&optional id)
+  "Kill X client ID.
+If ID is nil, kill X window corresponding to current buffer."
+  (unless id (setq id (exwm--buffer->id (current-buffer))))
+  (exwm--log "id=#x%x" id)
+  (let* ((response (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:ewmh:get-_NET_WM_PID :window id)))
+         (pid (and response (slot-value response 'value)))
+         (request (make-instance 'xcb:KillClient :resource id)))
+    (if (not pid)
+        (xcb:+request exwm--connection request)
+      ;; What if the PID is fake/wrong?
+      (signal-process pid 'SIGKILL)
+      ;; Ensure it's dead
+      (run-with-timer exwm-manage-ping-timeout nil
+                      (lambda ()
+                        (xcb:+request exwm--connection request))))
+    (xcb:flush exwm--connection)))
+
+(defun exwm-manage--add-frame (frame)
+  "Run in `after-make-frame-functions'.
+FRAME is the newly created frame."
+  (exwm--log "frame=%s" frame)
+  (when (display-graphic-p frame)
+    (push (string-to-number (frame-parameter frame 'outer-window-id))
+          exwm-manage--frame-outer-id-list)))
+
+(defun exwm-manage--remove-frame (frame)
+  "Run in `delete-frame-functions'.
+FRAME is the frame to be deleted."
+  (exwm--log "frame=%s" frame)
+  (when (display-graphic-p frame)
+    (setq exwm-manage--frame-outer-id-list
+          (delq (string-to-number (frame-parameter frame 'outer-window-id))
+                exwm-manage--frame-outer-id-list))))
+
+(defun exwm-manage--on-ConfigureRequest (data _synthetic)
+  "Handle ConfigureRequest event.
+DATA contains unmarshalled ConfigureRequest event data."
+  (exwm--log)
+  (let ((obj (make-instance 'xcb:ConfigureRequest))
+        buffer edges width-delta height-delta)
+    (xcb:unmarshal obj data)
+    (with-slots (window x y width height
+                        border-width sibling stack-mode value-mask)
+        obj
+      (exwm--log "#x%x (#x%x) @%dx%d%+d%+d; \
+border-width: %d; sibling: #x%x; stack-mode: %d"
+                 window value-mask width height x y
+                 border-width sibling stack-mode)
+      (if (and (setq buffer (exwm--id->buffer window))
+               (with-current-buffer buffer
+                 (or (exwm-layout--fullscreen-p)
+                     ;; Make sure it's a floating X window wanting to resize
+                     ;; itself.
+                     (or (not exwm--floating-frame)
+                         (progn
+                           (setq edges
+                                 (window-inside-pixel-edges
+                                  (get-buffer-window buffer t))
+                                 width-delta (- width (- (elt edges 2)
+                                                         (elt edges 0)))
+                                 height-delta (- height (- (elt edges 3)
+                                                           (elt edges 1))))
+                           ;; We cannot do resizing precisely for now.
+                           (and (if (= 0 (logand value-mask
+                                                 xcb:ConfigWindow:Width))
+                                    t
+                                  (< (abs width-delta)
+                                     exwm-manage--width-delta-min))
+                                (if (= 0 (logand value-mask
+                                                 xcb:ConfigWindow:Height))
+                                    t
+                                  (< (abs height-delta)
+                                     exwm-manage--height-delta-min))))))))
+          ;; Send client message for managed windows
+          (with-current-buffer buffer
+            (setq edges
+                  (if (exwm-layout--fullscreen-p)
+                      (with-slots (x y width height)
+                          (exwm-workspace--get-geometry exwm--frame)
+                        (list x y width height))
+                    (window-inside-absolute-pixel-edges
+                     (get-buffer-window buffer t))))
+            (exwm--log "Reply with ConfigureNotify (edges): %s" edges)
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:SendEvent
+                               :propagate 0 :destination window
+                               :event-mask xcb:EventMask:StructureNotify
+                               :event (xcb:marshal
+                                       (make-instance
+                                        'xcb:ConfigureNotify
+                                        :event window :window window
+                                        :above-sibling xcb:Window:None
+                                        :x (elt edges 0) :y (elt edges 1)
+                                        :width (- (elt edges 2) (elt edges 0))
+                                        :height (- (elt edges 3) (elt edges 1))
+                                        :border-width 0 :override-redirect 0)
+                                       exwm--connection))))
+        (if buffer
+            (with-current-buffer buffer
+              (exwm--log "ConfigureWindow (resize floating X window)")
+              (exwm--set-geometry (frame-parameter exwm--floating-frame
+                                                   'exwm-outer-id)
+                                  nil
+                                  nil
+                                  (+ (frame-pixel-width exwm--floating-frame)
+                                     width-delta)
+                                  (+ (frame-pixel-height exwm--floating-frame)
+                                     height-delta)))
+          (exwm--log "ConfigureWindow (preserve geometry)")
+          ;; Configure the unmanaged window.
+          ;; But Emacs frames should be excluded.  Generally we don't
+          ;; receive ConfigureRequest events from Emacs frames since we
+          ;; have set OverrideRedirect on them, but this is not true for
+          ;; Lucid build (as of 25.1).
+          (unless (memq window exwm-manage--frame-outer-id-list)
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:ConfigureWindow
+                               :window window
+                               :value-mask value-mask
+                               :x x :y y :width width :height height
+                               :border-width border-width
+                               :sibling sibling
+                               :stack-mode stack-mode)))))))
+  (xcb:flush exwm--connection))
+
+(defun exwm-manage--on-MapRequest (data _synthetic)
+  "Handle MapRequest event.
+DATA contains unmarshalled MapRequest event data."
+  (let ((obj (make-instance 'xcb:MapRequest)))
+    (xcb:unmarshal obj data)
+    (with-slots (parent window) obj
+      (exwm--log "id=#x%x parent=#x%x" window parent)
+      (if (assoc window exwm--id-buffer-alist)
+          (with-current-buffer (exwm--id->buffer window)
+            (if (exwm-layout--iconic-state-p)
+                ;; State change: iconic => normal.
+                (when (eq exwm--frame exwm-workspace--current)
+                  (pop-to-buffer-same-window (current-buffer)))
+              (exwm--log "#x%x is already managed" window)))
+        (if (/= exwm--root parent)
+            (progn (xcb:+request exwm--connection
+                       (make-instance 'xcb:MapWindow :window window))
+                   (xcb:flush exwm--connection))
+          (exwm--log "#x%x" window)
+          (exwm-manage--manage-window window))))))
+
+(defun exwm-manage--on-UnmapNotify (data _synthetic)
+  "Handle UnmapNotify event.
+DATA contains unmarshalled UnmapNotify event data."
+  (let ((obj (make-instance 'xcb:UnmapNotify)))
+    (xcb:unmarshal obj data)
+    (with-slots (window) obj
+      (exwm--log "id=#x%x" window)
+      (exwm-manage--unmanage-window window t))))
+
+(defun exwm-manage--on-MapNotify (data _synthetic)
+  "Handle MapNotify event.
+DATA contains unmarshalled MapNotify event data."
+  (let ((obj (make-instance 'xcb:MapNotify)))
+    (xcb:unmarshal obj data)
+    (with-slots (window) obj
+      (when (assoc window exwm--id-buffer-alist)
+        (exwm--log "id=#x%x" window)
+        ;; With this we ensure that a "window hierarchy change" happens after
+        ;; mapping the window, as some servers (XQuartz) do not generate it.
+        (with-current-buffer (exwm--id->buffer window)
+          (if exwm--floating-frame
+              (xcb:+request exwm--connection
+                  (make-instance 'xcb:ConfigureWindow
+                                 :window window
+                                 :value-mask xcb:ConfigWindow:StackMode
+                                 :stack-mode xcb:StackMode:Above))
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:ConfigureWindow
+                               :window window
+                               :value-mask (logior xcb:ConfigWindow:Sibling
+                                                   xcb:ConfigWindow:StackMode)
+                               :sibling exwm--guide-window
+                               :stack-mode xcb:StackMode:Above))))
+        (xcb:flush exwm--connection)))))
+
+(defun exwm-manage--on-DestroyNotify (data synthetic)
+  "Handle DestroyNotify event.
+DATA contains unmarshalled DestroyNotify event data.
+SYNTHETIC indicates whether the event is a synthetic event."
+  (unless synthetic
+    (exwm--log)
+    (let ((obj (make-instance 'xcb:DestroyNotify)))
+      (xcb:unmarshal obj data)
+      (exwm--log "#x%x" (slot-value obj 'window))
+      (exwm-manage--unmanage-window (slot-value obj 'window)))))
+
+(defun exwm-manage--init ()
+  "Initialize manage module."
+  ;; Intern _MOTIF_WM_HINTS
+  (exwm--log)
+  (setq exwm-manage--_MOTIF_WM_HINTS (exwm--intern-atom "_MOTIF_WM_HINTS"))
+  (add-hook 'after-make-frame-functions #'exwm-manage--add-frame)
+  (add-hook 'delete-frame-functions #'exwm-manage--remove-frame)
+  (xcb:+event exwm--connection 'xcb:ConfigureRequest
+              #'exwm-manage--on-ConfigureRequest)
+  (xcb:+event exwm--connection 'xcb:MapRequest #'exwm-manage--on-MapRequest)
+  (xcb:+event exwm--connection 'xcb:UnmapNotify #'exwm-manage--on-UnmapNotify)
+  (xcb:+event exwm--connection 'xcb:MapNotify #'exwm-manage--on-MapNotify)
+  (xcb:+event exwm--connection 'xcb:DestroyNotify
+              #'exwm-manage--on-DestroyNotify))
+
+(defun exwm-manage--exit ()
+  "Exit the manage module."
+  (exwm--log)
+  (dolist (pair exwm--id-buffer-alist)
+    (exwm-manage--unmanage-window (car pair) 'quit))
+  (remove-hook 'after-make-frame-functions #'exwm-manage--add-frame)
+  (remove-hook 'delete-frame-functions #'exwm-manage--remove-frame)
+  (setq exwm-manage--_MOTIF_WM_HINTS nil))
+
+
+
+(provide 'exwm-manage)
+
+;;; exwm-manage.el ends here
diff --git a/exwm-randr.el b/exwm-randr.el
new file mode 100644
index 0000000000..7f0e50559b
--- /dev/null
+++ b/exwm-randr.el
@@ -0,0 +1,369 @@
+;;; exwm-randr.el --- RandR Module for EXWM  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2015-2024 Free Software Foundation, Inc.
+
+;; Author: Chris Feng <chris.w.feng@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module adds RandR support for EXWM.  Currently it requires external
+;; tools such as xrandr(1) to properly configure RandR first.  This
+;; dependency may be removed in the future, but more work is needed before
+;; that.
+
+;; To use this module, load, enable it and configure
+;; `exwm-randr-workspace-monitor-plist' and `exwm-randr-screen-change-hook'
+;; as follows:
+;;
+;;   (require 'exwm-randr)
+;;   (setq exwm-randr-workspace-monitor-plist '(0 "VGA1"))
+;;   (add-hook 'exwm-randr-screen-change-hook
+;;             (lambda ()
+;;               (start-process-shell-command
+;;                "xrandr" nil "xrandr --output VGA1 --left-of LVDS1 --auto")))
+;;   (exwm-randr-enable)
+;;
+;; With above lines, workspace 0 should be assigned to the output named "VGA1",
+;; staying at the left of other workspaces on the output "LVDS1".  Please refer
+;; to xrandr(1) for the configuration of RandR.
+
+;; References:
+;; + RandR (http://www.x.org/archive/X11R7.7/doc/randrproto/randrproto.txt)
+
+;;; Code:
+
+(require 'xcb-randr)
+
+(require 'exwm-core)
+(require 'exwm-workspace)
+
+(declare-function x-get-atom-name "C source code" (VALUE &optional FRAME))
+
+(defgroup exwm-randr nil
+  "RandR."
+  :group 'exwm)
+
+(defcustom exwm-randr-refresh-hook nil
+  "Normal hook run when the RandR module just refreshed."
+  :type 'hook)
+
+(defcustom exwm-randr-screen-change-hook nil
+  "Normal hook run when screen changes."
+  :type 'hook)
+
+(defcustom exwm-randr-workspace-monitor-plist nil
+  "Plist mapping workspaces to monitors.
+
+In RandR 1.5 a monitor is a rectangle region decoupled from the physical
+size of screens, and can be identified with `xrandr --listmonitors' (name of
+the primary monitor is prefixed with an `*').  When no monitor is created it
+automatically fallback to RandR 1.2 output which represents the physical
+screen size.  RandR 1.5 monitors can be created with `xrandr --setmonitor'.
+For example, to split an output (`LVDS-1') of size 1280x800 into two
+side-by-side monitors one could invoke (the digits after `/' are size in mm)
+
+    xrandr --setmonitor *LVDS-1-L 640/135x800/163+0+0 LVDS-1
+    xrandr --setmonitor LVDS-1-R 640/135x800/163+640+0 none
+
+If a monitor is not active, the workspaces mapped to it are displayed on the
+primary monitor until it becomes active (if ever).  Unspecified workspaces
+are all mapped to the primary monitor.  For example, with the following
+setting workspace other than 1 and 3 would always be displayed on the
+primary monitor where workspace 1 and 3 would be displayed on their
+corresponding monitors whenever the monitors are active.
+
+  \\='(1 \"HDMI-1\" 3 \"DP-1\")"
+  :type '(plist :key-type integer :value-type string))
+
+(defvar exwm-randr--last-timestamp 0 "Used for debouncing events.")
+
+(defvar exwm-randr--prev-screen-change-seqnum nil
+  "The most recent ScreenChangeNotify sequence number.")
+
+(defvar exwm-randr--compatibility-mode nil
+  "Non-nil when the server does not support RandR 1.5 protocol.")
+
+(defun exwm-randr--get-monitors ()
+  "Get RandR 1.5 monitors."
+  (exwm--log)
+  (let (monitor-name geometry monitor-geometry-alist primary-monitor)
+    (with-slots (timestamp monitors)
+        (xcb:+request-unchecked+reply exwm--connection
+            (make-instance 'xcb:randr:GetMonitors
+                           :window exwm--root
+                           :get-active 1))
+      (when (> timestamp exwm-randr--last-timestamp)
+        (setq exwm-randr--last-timestamp timestamp))
+      (dolist (monitor monitors)
+        (with-slots (name primary x y width height) monitor
+          (setq monitor-name (x-get-atom-name name)
+                geometry (make-instance 'xcb:RECTANGLE
+                                        :x x
+                                        :y y
+                                        :width width
+                                        :height height)
+                monitor-geometry-alist (cons (cons monitor-name geometry)
+                                             monitor-geometry-alist))
+          (exwm--log "%s: %sx%s+%s+%s" monitor-name x y width height)
+          ;; Save primary monitor when available (fallback to the first one).
+          (when (or (/= 0 primary)
+                    (not primary-monitor))
+            (setq primary-monitor monitor-name)))))
+    (exwm--log "Primary monitor: %s" primary-monitor)
+    (list primary-monitor monitor-geometry-alist
+          (exwm-randr--get-monitor-alias primary-monitor
+                                         monitor-geometry-alist))))
+
+(defun exwm-randr--get-outputs ()
+  "Get RandR 1.2 outputs.
+
+Only used when RandR 1.5 is not supported by the server."
+  (exwm--log)
+  (let (output-name geometry output-geometry-alist primary-output)
+    (with-slots (config-timestamp outputs)
+        (xcb:+request-unchecked+reply exwm--connection
+            (make-instance 'xcb:randr:GetScreenResourcesCurrent
+                           :window exwm--root))
+      (when (> config-timestamp exwm-randr--last-timestamp)
+        (setq exwm-randr--last-timestamp config-timestamp))
+      (dolist (output outputs)
+        (with-slots (crtc connection name)
+            (xcb:+request-unchecked+reply exwm--connection
+                (make-instance 'xcb:randr:GetOutputInfo
+                               :output output
+                               :config-timestamp config-timestamp))
+          (when (and (= connection xcb:randr:Connection:Connected)
+                     (/= crtc 0))
+            (with-slots (x y width height)
+                (xcb:+request-unchecked+reply exwm--connection
+                    (make-instance 'xcb:randr:GetCrtcInfo
+                                   :crtc crtc
+                                   :config-timestamp config-timestamp))
+              (setq output-name (decode-coding-string
+                                 (apply #'unibyte-string name) 'utf-8)
+                    geometry (make-instance 'xcb:RECTANGLE
+                                            :x x
+                                            :y y
+                                            :width width
+                                            :height height)
+                    output-geometry-alist (cons (cons output-name geometry)
+                                                output-geometry-alist))
+              (exwm--log "%s: %sx%s+%s+%s" output-name x y width height)
+              ;; The primary output is the first one.
+              (unless primary-output
+                (setq primary-output output-name)))))))
+    (exwm--log "Primary output: %s" primary-output)
+    (list primary-output output-geometry-alist
+          (exwm-randr--get-monitor-alias primary-output
+                                         output-geometry-alist))))
+
+(defun exwm-randr--get-monitor-alias (primary-monitor monitor-geometry-alist)
+  "Generate monitor aliases using PRIMARY-MONITOR MONITOR-GEOMETRY-ALIST.
+
+In a mirroring setup some monitors overlap and should be treated as one."
+  (let (monitor-position-alist monitor-alias-alist monitor-name geometry)
+    (setq monitor-position-alist (with-slots (x y)
+                                     (cdr (assoc primary-monitor
+                                                 monitor-geometry-alist))
+                                   (list (cons primary-monitor (vector x y)))))
+    (setq monitor-alias-alist (list (cons primary-monitor primary-monitor)))
+    (dolist (pair monitor-geometry-alist)
+      (setq monitor-name (car pair)
+            geometry (cdr pair))
+      (unless (assoc monitor-name monitor-alias-alist)
+        (let* ((position (vector (slot-value geometry 'x)
+                                 (slot-value geometry 'y)))
+               (alias (car (rassoc position monitor-position-alist))))
+          (if alias
+              (setq monitor-alias-alist (cons (cons monitor-name alias)
+                                              monitor-alias-alist))
+            (setq monitor-position-alist (cons (cons monitor-name position)
+                                               monitor-position-alist)
+                  monitor-alias-alist (cons (cons monitor-name monitor-name)
+                                            monitor-alias-alist))))))
+    monitor-alias-alist))
+
+;;;###autoload
+(defun exwm-randr-refresh ()
+  "Refresh workspaces according to the updated RandR info."
+  (interactive)
+  (exwm--log)
+  (let* ((result (if exwm-randr--compatibility-mode
+                     (exwm-randr--get-outputs)
+                   (exwm-randr--get-monitors)))
+         (primary-monitor (elt result 0))
+         (monitor-geometry-alist (elt result 1))
+         (monitor-alias-alist (elt result 2))
+         container-monitor-alist container-frame-alist)
+    (when (and primary-monitor monitor-geometry-alist)
+      (when exwm-workspace--fullscreen-frame-count
+        ;; Not all workspaces are fullscreen; reset this counter.
+        (setq exwm-workspace--fullscreen-frame-count 0))
+      (dotimes (i (exwm-workspace--count))
+        (let* ((monitor (plist-get exwm-randr-workspace-monitor-plist i))
+               (geometry (cdr (assoc monitor monitor-geometry-alist)))
+               (frame (elt exwm-workspace--list i))
+               (container (frame-parameter frame 'exwm-container)))
+          (if geometry
+              ;; Unify monitor names in case it's a mirroring setup.
+              (setq monitor (cdr (assoc monitor monitor-alias-alist)))
+            ;; Missing monitors fallback to the primary one.
+            (setq monitor primary-monitor
+                  geometry (cdr (assoc primary-monitor
+                                       monitor-geometry-alist))))
+          (setq container-monitor-alist (nconc
+                                         `((,container . ,(intern monitor)))
+                                         container-monitor-alist)
+                container-frame-alist (nconc `((,container . ,frame))
+                                             container-frame-alist))
+          (set-frame-parameter frame 'exwm-randr-monitor monitor)
+          (set-frame-parameter frame 'exwm-geometry geometry)))
+      ;; Update workareas.
+      (exwm-workspace--update-workareas)
+      ;; Resize workspace.
+      (dolist (f exwm-workspace--list)
+        (exwm-workspace--set-fullscreen f))
+      (xcb:flush exwm--connection)
+      ;; Raise the minibuffer if it's active.
+      (when (and (active-minibuffer-window)
+                 (exwm-workspace--minibuffer-own-frame-p))
+        (exwm-workspace--show-minibuffer))
+      ;; Set _NET_DESKTOP_GEOMETRY.
+      (exwm-workspace--set-desktop-geometry)
+      ;; Update active/inactive workspaces.
+      (dolist (w exwm-workspace--list)
+        (exwm-workspace--set-active w nil))
+      ;; Mark the workspace on the top of each monitor as active.
+      (dolist (xwin
+               (reverse
+                (slot-value (xcb:+request-unchecked+reply exwm--connection
+                                (make-instance 'xcb:QueryTree
+                                               :window exwm--root))
+                            'children)))
+        (let ((monitor (cdr (assq xwin container-monitor-alist))))
+          (when monitor
+            (setq container-monitor-alist
+                  (rassq-delete-all monitor container-monitor-alist))
+            (exwm-workspace--set-active (cdr (assq xwin container-frame-alist))
+                                        t))))
+      (xcb:flush exwm--connection)
+      (run-hooks 'exwm-randr-refresh-hook))))
+
+(defun exwm-randr--on-ScreenChangeNotify (data _synthetic)
+  "Handle `ScreenChangeNotify' event.
+
+Run `exwm-randr-screen-change-hook' (usually user scripts to configure RandR)."
+  (exwm--log)
+  (let ((evt (make-instance 'xcb:randr:ScreenChangeNotify)))
+    (xcb:unmarshal evt data)
+    (let ((seqnum (slot-value evt '~sequence)))
+      (unless (equal seqnum exwm-randr--prev-screen-change-seqnum)
+        (setq exwm-randr--prev-screen-change-seqnum seqnum)
+        (run-hooks 'exwm-randr-screen-change-hook)))))
+
+(defun exwm-randr--on-Notify (data _synthetic)
+  "Handle `CrtcChangeNotify' and `OutputChangeNotify' events.
+
+Refresh when any CRTC/output changes."
+  (exwm--log)
+  (let ((evt (make-instance 'xcb:randr:Notify))
+        notify)
+    (xcb:unmarshal evt data)
+    (with-slots (subCode u) evt
+      (cl-case subCode
+        (xcb:randr:Notify:CrtcChange
+         (setq notify (slot-value u 'cc)))
+        (xcb:randr:Notify:OutputChange
+         (setq notify (slot-value u 'oc))))
+      (when notify
+        (with-slots (timestamp) notify
+          (when (> timestamp exwm-randr--last-timestamp)
+            (exwm-randr-refresh)
+            (setq exwm-randr--last-timestamp timestamp)))))))
+
+(defun exwm-randr--on-ConfigureNotify (data _synthetic)
+  "Handle `ConfigureNotify' event.
+
+Refresh when any RandR 1.5 monitor changes."
+  (exwm--log)
+  (let ((evt (make-instance 'xcb:ConfigureNotify)))
+    (xcb:unmarshal evt data)
+    (with-slots (window) evt
+      (when (eq window exwm--root)
+        (exwm-randr-refresh)))))
+
+(defun exwm-randr--init ()
+  "Initialize RandR extension and EXWM RandR module."
+  (exwm--log)
+  (when (= 0 (slot-value (xcb:get-extension-data exwm--connection 'xcb:randr)
+                         'present))
+    (error "[EXWM] RandR extension is not supported by the server"))
+  (with-slots (major-version minor-version)
+      (xcb:+request-unchecked+reply exwm--connection
+          (make-instance 'xcb:randr:QueryVersion
+                         :major-version 1 :minor-version 5))
+    (cond ((and (= major-version 1) (= minor-version 5))
+           (setq exwm-randr--compatibility-mode nil))
+          ((and (= major-version 1) (>= minor-version 2))
+           (setq exwm-randr--compatibility-mode t))
+          (t
+           (error "[EXWM] The server only support RandR version up to %d.%d"
+                  major-version minor-version)))
+    ;; External monitor(s) may already be connected.
+    (run-hooks 'exwm-randr-screen-change-hook)
+    (exwm-randr-refresh)
+    ;; Listen for `ScreenChangeNotify' to notify external tools to
+    ;; configure RandR and `CrtcChangeNotify/OutputChangeNotify' to
+    ;; refresh the workspace layout.
+    (xcb:+event exwm--connection 'xcb:randr:ScreenChangeNotify
+                #'exwm-randr--on-ScreenChangeNotify)
+    (xcb:+event exwm--connection 'xcb:randr:Notify
+                #'exwm-randr--on-Notify)
+    (xcb:+event exwm--connection 'xcb:ConfigureNotify
+                #'exwm-randr--on-ConfigureNotify)
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:randr:SelectInput
+                       :window exwm--root
+                       :enable (logior
+                                xcb:randr:NotifyMask:ScreenChange
+                                xcb:randr:NotifyMask:CrtcChange
+                                xcb:randr:NotifyMask:OutputChange)))
+    (xcb:flush exwm--connection)
+    (add-hook 'exwm-workspace-list-change-hook #'exwm-randr-refresh))
+  ;; Prevent frame parameters introduced by this module from being
+  ;; saved/restored.
+  (dolist (i '(exwm-randr-monitor))
+    (unless (assq i frameset-filter-alist)
+      (push (cons i :never) frameset-filter-alist))))
+
+(defun exwm-randr--exit ()
+  "Exit the RandR module."
+  (exwm--log)
+  (remove-hook 'exwm-workspace-list-change-hook #'exwm-randr-refresh))
+
+(defun exwm-randr-enable ()
+  "Enable RandR support for EXWM."
+  (exwm--log)
+  (add-hook 'exwm-init-hook #'exwm-randr--init)
+  (add-hook 'exwm-exit-hook #'exwm-randr--exit))
+
+
+
+(provide 'exwm-randr)
+
+;;; exwm-randr.el ends here
diff --git a/exwm-systemtray.el b/exwm-systemtray.el
new file mode 100644
index 0000000000..9e57dae4eb
--- /dev/null
+++ b/exwm-systemtray.el
@@ -0,0 +1,701 @@
+;;; exwm-systemtray.el --- System Tray Module for  -*- lexical-binding: t -*-
+;;;                        EXWM
+
+;; Copyright (C) 2016-2024 Free Software Foundation, Inc.
+
+;; Author: Chris Feng <chris.w.feng@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module adds system tray support for EXWM.
+
+;; To use this module, load and enable it as follows:
+;;   (require 'exwm-systemtray)
+;;   (exwm-systemtray-enable)
+
+;;; Code:
+
+(require 'xcb-ewmh)
+(require 'xcb-icccm)
+(require 'xcb-xembed)
+(require 'xcb-systemtray)
+
+(require 'exwm-core)
+(require 'exwm-workspace)
+
+(declare-function exwm-workspace--workarea "exwm-workspace.el" (frame))
+
+(defclass exwm-systemtray--icon ()
+  ((width :initarg :width)
+   (height :initarg :height)
+   (visible :initarg :visible))
+  :documentation "Attributes of a system tray icon.")
+
+(defclass xcb:systemtray:-ClientMessage
+  (xcb:icccm:--ClientMessage xcb:ClientMessage)
+  ((format :initform 32)
+   (type :initform 'xcb:Atom:MANAGER)
+   (time :initarg :time :type xcb:TIMESTAMP)      ;new slot
+   (selection :initarg :selection :type xcb:ATOM) ;new slot
+   (owner :initarg :owner :type xcb:WINDOW))      ;new slot
+  :documentation "A systemtray client message.")
+
+(defgroup exwm-systemtray nil
+  "System tray."
+  :group 'exwm)
+
+(defcustom exwm-systemtray-height nil
+  "System tray height.
+
+You shall use the default value if using auto-hide minibuffer."
+  :type 'integer)
+
+(defcustom exwm-systemtray-icon-gap 2
+  "Gap between icons."
+  :type 'integer)
+
+(defvar exwm-systemtray--connection nil "The X connection.")
+
+(defvar exwm-systemtray--embedder-window nil "The embedder window.")
+(defvar exwm-systemtray--embedder-window-depth nil
+  "The embedder window's depth.")
+
+(defcustom exwm-systemtray-background-color 'workspace-background
+  "Background color of systemtray.
+This should be a color, the symbol `workspace-background' for the background
+color of current workspace frame, or the symbol `transparent' for transparent
+background.
+
+Transparent background is not yet supported when Emacs uses 32-bit depth
+visual, as reported by `x-display-planes'.  The X resource \"Emacs.visualClass:
+TrueColor-24\" can be used to force Emacs to use 24-bit depth."
+  :type '(choice (const :tag "Transparent" transparent)
+                 (const :tag "Frame background" workspace-background)
+                 (color :tag "Color"))
+  :initialize #'custom-initialize-default
+  :set (lambda (symbol value)
+         (when (and (eq value 'transparent)
+                    (not (exwm-systemtray--transparency-supported-p)))
+           (display-warning 'exwm-systemtray
+                            "Transparent background is not supported yet when \
+using 32-bit depth.  Using `workspace-background' instead.")
+           (setq value 'workspace-background))
+         (set-default symbol value)
+         (when (and exwm-systemtray--connection
+                    exwm-systemtray--embedder-window)
+           ;; Change the background color for embedder.
+           (exwm-systemtray--set-background-color)
+           ;; Unmap & map to take effect immediately.
+           (xcb:+request exwm-systemtray--connection
+                         (make-instance 'xcb:UnmapWindow
+                                        :window exwm-systemtray--embedder-window))
+           (xcb:+request exwm-systemtray--connection
+                         (make-instance 'xcb:MapWindow
+                                        :window exwm-systemtray--embedder-window))
+           (xcb:flush exwm-systemtray--connection))))
+
+;; GTK icons require at least 16 pixels to show normally.
+(defconst exwm-systemtray--icon-min-size 16 "Minimum icon size.")
+
+(defvar exwm-systemtray--list nil "The icon list.")
+
+(defvar exwm-systemtray--selection-owner-window nil
+  "The selection owner window.")
+
+(defvar xcb:Atom:_NET_SYSTEM_TRAY_S0)
+
+(defun exwm-systemtray--embed (icon)
+  "Embed an ICON."
+  (exwm--log "Try to embed #x%x" icon)
+  (let ((info (xcb:+request-unchecked+reply exwm-systemtray--connection
+                  (make-instance 'xcb:xembed:get-_XEMBED_INFO
+                                 :window icon)))
+        width* height* visible)
+    (when info
+      (exwm--log "Embed #x%x" icon)
+      (with-slots (width height)
+          (xcb:+request-unchecked+reply exwm-systemtray--connection
+              (make-instance 'xcb:GetGeometry :drawable icon))
+        (setq height* exwm-systemtray-height
+              width* (round (* width (/ (float height*) height))))
+        (when (< width* exwm-systemtray--icon-min-size)
+          (setq width* exwm-systemtray--icon-min-size
+                height* (round (* height (/ (float width*) width)))))
+        (exwm--log "Resize from %dx%d to %dx%d"
+                   width height width* height*))
+      ;; Add this icon to save-set.
+      (xcb:+request exwm-systemtray--connection
+          (make-instance 'xcb:ChangeSaveSet
+                         :mode xcb:SetMode:Insert
+                         :window icon))
+      ;; Reparent to the embedder.
+      (xcb:+request exwm-systemtray--connection
+          (make-instance 'xcb:ReparentWindow
+                         :window icon
+                         :parent exwm-systemtray--embedder-window
+                         :x 0
+                         ;; Vertically centered.
+                         :y (/ (- exwm-systemtray-height height*) 2)))
+      ;; Resize the icon.
+      (xcb:+request exwm-systemtray--connection
+          (make-instance 'xcb:ConfigureWindow
+                         :window icon
+                         :value-mask (logior xcb:ConfigWindow:Width
+                                             xcb:ConfigWindow:Height
+                                             xcb:ConfigWindow:BorderWidth)
+                         :width width*
+                         :height height*
+                         :border-width 0))
+      ;; Set event mask.
+      (xcb:+request exwm-systemtray--connection
+          (make-instance 'xcb:ChangeWindowAttributes
+                         :window icon
+                         :value-mask xcb:CW:EventMask
+                         :event-mask (logior xcb:EventMask:ResizeRedirect
+                                             xcb:EventMask:KeyPress
+                                             xcb:EventMask:PropertyChange)))
+      ;; Grab all keys and forward them to Emacs frame.
+      (unless (exwm-workspace--minibuffer-own-frame-p)
+        (xcb:+request exwm-systemtray--connection
+            (make-instance 'xcb:GrabKey
+                           :owner-events 0
+                           :grab-window icon
+                           :modifiers xcb:ModMask:Any
+                           :key xcb:Grab:Any
+                           :pointer-mode xcb:GrabMode:Async
+                           :keyboard-mode xcb:GrabMode:Async)))
+      (setq visible (slot-value info 'flags))
+      (if visible
+          (setq visible
+                (/= 0 (logand (slot-value info 'flags) xcb:xembed:MAPPED)))
+        ;; Default to visible.
+        (setq visible t))
+      (when visible
+        (exwm--log "Map the window")
+        (xcb:+request exwm-systemtray--connection
+            (make-instance 'xcb:MapWindow :window icon)))
+      (xcb:+request exwm-systemtray--connection
+          (make-instance 'xcb:xembed:SendEvent
+                         :destination icon
+                         :event
+                         (xcb:marshal
+                          (make-instance 'xcb:xembed:EMBEDDED-NOTIFY
+                                         :window icon
+                                         :time xcb:Time:CurrentTime
+                                         :embedder
+                                         exwm-systemtray--embedder-window
+                                         :version 0)
+                          exwm-systemtray--connection)))
+      (push `(,icon . ,(make-instance 'exwm-systemtray--icon
+                                      :width width*
+                                      :height height*
+                                      :visible visible))
+            exwm-systemtray--list)
+      (exwm-systemtray--refresh))))
+
+(defun exwm-systemtray--unembed (icon)
+  "Unembed an ICON."
+  (exwm--log "Unembed #x%x" icon)
+  (xcb:+request exwm-systemtray--connection
+      (make-instance 'xcb:UnmapWindow :window icon))
+  (xcb:+request exwm-systemtray--connection
+      (make-instance 'xcb:ReparentWindow
+                     :window icon
+                     :parent exwm--root
+                     :x 0 :y 0))
+  (setq exwm-systemtray--list
+        (assq-delete-all icon exwm-systemtray--list))
+  (exwm-systemtray--refresh))
+
+(defun exwm-systemtray--refresh ()
+  "Refresh the system tray."
+  (exwm--log)
+  ;; Make sure to redraw the embedder.
+  (xcb:+request exwm-systemtray--connection
+      (make-instance 'xcb:UnmapWindow
+                     :window exwm-systemtray--embedder-window))
+  (let ((x exwm-systemtray-icon-gap)
+        map)
+    (dolist (pair exwm-systemtray--list)
+      (when (slot-value (cdr pair) 'visible)
+        (xcb:+request exwm-systemtray--connection
+            (make-instance 'xcb:ConfigureWindow
+                           :window (car pair)
+                           :value-mask xcb:ConfigWindow:X
+                           :x x))
+        (setq x (+ x (slot-value (cdr pair) 'width)
+                   exwm-systemtray-icon-gap))
+        (setq map t)))
+    (let ((workarea (exwm-workspace--workarea exwm-workspace-current-index)))
+      (xcb:+request exwm-systemtray--connection
+          (make-instance 'xcb:ConfigureWindow
+                         :window exwm-systemtray--embedder-window
+                         :value-mask (logior xcb:ConfigWindow:X
+                                             xcb:ConfigWindow:Width)
+                         :x (- (slot-value workarea 'width) x)
+                         :width x)))
+    (when map
+      (xcb:+request exwm-systemtray--connection
+          (make-instance 'xcb:MapWindow
+                         :window exwm-systemtray--embedder-window))))
+  (xcb:flush exwm-systemtray--connection))
+
+(defun exwm-systemtray--refresh-background-color (&optional remap)
+  "Refresh background color after theme change or workspace switch.
+If REMAP is not nil, map and unmap the embedder window so that the background is
+redrawn."
+  ;; Only `workspace-background' is dependent on current theme and workspace.
+  (when (eq 'workspace-background exwm-systemtray-background-color)
+    (exwm-systemtray--set-background-color)
+    (when remap
+      (xcb:+request exwm-systemtray--connection
+                    (make-instance 'xcb:UnmapWindow
+                                   :window exwm-systemtray--embedder-window))
+      (xcb:+request exwm-systemtray--connection
+                    (make-instance 'xcb:MapWindow
+                                   :window exwm-systemtray--embedder-window))
+      (xcb:flush exwm-systemtray--connection))))
+
+(defun exwm-systemtray--set-background-color ()
+  "Change the background color of the embedder.
+The color is set according to `exwm-systemtray-background-color'.
+
+Note that this function does not change the current contents of the embedder
+window; unmap & map are necessary for the background color to take effect."
+  (when (and exwm-systemtray--connection
+             exwm-systemtray--embedder-window)
+    (let* ((color (cl-case exwm-systemtray-background-color
+                    ((transparent nil) ; nil means transparent as well
+                     (if (exwm-systemtray--transparency-supported-p)
+                         nil
+                       (message "%s" "[EXWM] system tray does not support \
+`transparent' background; using `workspace-background' instead")
+                       (face-background 'default exwm-workspace--current)))
+                    (workspace-background
+                     (face-background 'default exwm-workspace--current))
+                    (t exwm-systemtray-background-color)))
+           (background-pixel (exwm--color->pixel color)))
+      (xcb:+request exwm-systemtray--connection
+                    (make-instance 'xcb:ChangeWindowAttributes
+                                   :window exwm-systemtray--embedder-window
+                                   ;; Either-or.  A `background-pixel' of nil
+                                   ;; means simulate transparency.  We use
+                                   ;; `xcb:CW:BackPixmap' together with
+                                   ;; `xcb:BackPixmap:ParentRelative' do that,
+                                   ;; but this only works when the parent
+                                   ;; window's visual (Emacs') has the same
+                                   ;; visual depth.
+                                   :value-mask (if background-pixel
+                                                   xcb:CW:BackPixel
+                                                 xcb:CW:BackPixmap)
+                                   ;; Due to the :value-mask above,
+                                   ;; :background-pixmap only takes effect when
+                                   ;; `transparent' is requested and supported
+                                   ;; (visual depth of Emacs and of system tray
+                                   ;; are equal).  Setting
+                                   ;; `xcb:BackPixmap:ParentRelative' when
+                                   ;; that's not the case would produce an
+                                   ;; `xcb:Match' error.
+                                   :background-pixmap xcb:BackPixmap:ParentRelative
+                                   :background-pixel background-pixel)))))
+
+(defun exwm-systemtray--transparency-supported-p ()
+  "Check whether transparent background is supported.
+EXWM system tray supports transparency when the visual depth of the system tray
+window matches that of Emacs.  The visual depth of the system tray window is the
+default visual depth of the display.
+
+Sections \"Visual and background pixmap handling\" and
+\"_NET_SYSTEM_TRAY_VISUAL\" of the System Tray Protocol Specification
+\(https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-latest.html#visuals)
+indicate how to support actual transparency."
+  (let ((planes (x-display-planes)))
+    (if exwm-systemtray--embedder-window-depth
+        (= planes exwm-systemtray--embedder-window-depth)
+      (<= planes 24))))
+
+(defun exwm-systemtray--on-DestroyNotify (data _synthetic)
+  "Unembed icons on DestroyNotify.
+Argument DATA contains the raw event data."
+  (exwm--log)
+  (let ((obj (make-instance 'xcb:DestroyNotify)))
+    (xcb:unmarshal obj data)
+    (with-slots (window) obj
+      (when (assoc window exwm-systemtray--list)
+        (exwm-systemtray--unembed window)))))
+
+(defun exwm-systemtray--on-ReparentNotify (data _synthetic)
+  "Unembed icons on ReparentNotify.
+Argument DATA contains the raw event data."
+  (exwm--log)
+  (let ((obj (make-instance 'xcb:ReparentNotify)))
+    (xcb:unmarshal obj data)
+    (with-slots (window parent) obj
+      (when (and (/= parent exwm-systemtray--embedder-window)
+                 (assoc window exwm-systemtray--list))
+        (exwm-systemtray--unembed window)))))
+
+(defun exwm-systemtray--on-ResizeRequest (data _synthetic)
+  "Resize the tray icon on ResizeRequest.
+Argument DATA contains the raw event data."
+  (exwm--log)
+  (let ((obj (make-instance 'xcb:ResizeRequest))
+        attr)
+    (xcb:unmarshal obj data)
+    (with-slots (window width height) obj
+      (when (setq attr (cdr (assoc window exwm-systemtray--list)))
+        (with-slots ((width* width)
+                     (height* height))
+            attr
+          (setq height* exwm-systemtray-height
+                width* (round (* width (/ (float height*) height))))
+          (when (< width* exwm-systemtray--icon-min-size)
+            (setq width* exwm-systemtray--icon-min-size
+                  height* (round (* height (/ (float width*) width)))))
+          (xcb:+request exwm-systemtray--connection
+              (make-instance 'xcb:ConfigureWindow
+                             :window window
+                             :value-mask (logior xcb:ConfigWindow:Y
+                                                 xcb:ConfigWindow:Width
+                                                 xcb:ConfigWindow:Height)
+                             ;; Vertically centered.
+                             :y (/ (- exwm-systemtray-height height*) 2)
+                             :width width*
+                             :height height*)))
+        (exwm-systemtray--refresh)))))
+
+(defun exwm-systemtray--on-PropertyNotify (data _synthetic)
+  "Map/Unmap the tray icon on PropertyNotify.
+Argument DATA contains the raw event data."
+  (exwm--log)
+  (let ((obj (make-instance 'xcb:PropertyNotify))
+        attr info visible)
+    (xcb:unmarshal obj data)
+    (with-slots (window atom state) obj
+      (when (and (eq state xcb:Property:NewValue)
+                 (eq atom xcb:Atom:_XEMBED_INFO)
+                 (setq attr (cdr (assoc window exwm-systemtray--list))))
+        (setq info (xcb:+request-unchecked+reply exwm-systemtray--connection
+                       (make-instance 'xcb:xembed:get-_XEMBED_INFO
+                                      :window window)))
+        (when info
+          (setq visible (/= 0 (logand (slot-value info 'flags)
+                                      xcb:xembed:MAPPED)))
+          (exwm--log "#x%x visible? %s" window visible)
+          (if visible
+              (xcb:+request exwm-systemtray--connection
+                  (make-instance 'xcb:MapWindow :window window))
+            (xcb:+request exwm-systemtray--connection
+                (make-instance 'xcb:UnmapWindow :window window)))
+          (setf (slot-value attr 'visible) visible)
+          (exwm-systemtray--refresh))))))
+
+(defun exwm-systemtray--on-ClientMessage (data _synthetic)
+  "Handle client messages.
+Argument DATA contains the raw event data."
+  (let ((obj (make-instance 'xcb:ClientMessage))
+        opcode data32)
+    (xcb:unmarshal obj data)
+    (with-slots (window type data) obj
+      (when (eq type xcb:Atom:_NET_SYSTEM_TRAY_OPCODE)
+        (setq data32 (slot-value data 'data32)
+              opcode (elt data32 1))
+        (exwm--log "opcode: %s" opcode)
+        (cond ((= opcode xcb:systemtray:opcode:REQUEST-DOCK)
+               (unless (assoc (elt data32 2) exwm-systemtray--list)
+                 (exwm-systemtray--embed (elt data32 2))))
+              ;; Not implemented (rarely used nowadays).
+              ((or (= opcode xcb:systemtray:opcode:BEGIN-MESSAGE)
+                   (= opcode xcb:systemtray:opcode:CANCEL-MESSAGE)))
+              (t
+               (exwm--log "Unknown opcode message: %s" obj)))))))
+
+(defun exwm-systemtray--on-KeyPress (data _synthetic)
+  "Forward all KeyPress events to Emacs frame.
+Argument DATA contains the raw event data."
+  (exwm--log)
+  ;; This function is only executed when there's no autohide minibuffer,
+  ;; a workspace frame has the input focus and the pointer is over a
+  ;; tray icon.
+  (let ((dest (frame-parameter (selected-frame) 'exwm-outer-id))
+        (obj (make-instance 'xcb:KeyPress)))
+    (xcb:unmarshal obj data)
+    (setf (slot-value obj 'event) dest)
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:SendEvent
+                       :propagate 0
+                       :destination dest
+                       :event-mask xcb:EventMask:NoEvent
+                       :event (xcb:marshal obj exwm-systemtray--connection))))
+  (xcb:flush exwm-systemtray--connection))
+
+(defun exwm-systemtray--on-workspace-switch ()
+  "Reparent/Refresh the system tray in `exwm-workspace-switch-hook'."
+  (exwm--log)
+  (unless (exwm-workspace--minibuffer-own-frame-p)
+    (exwm-workspace--update-offsets)
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:ReparentWindow
+                       :window exwm-systemtray--embedder-window
+                       :parent (string-to-number
+                                (frame-parameter exwm-workspace--current
+                                                 'window-id))
+                       :x 0
+                       :y (- (slot-value (exwm-workspace--workarea
+                                           exwm-workspace-current-index)
+                                         'height)
+                             exwm-workspace--frame-y-offset
+                             exwm-systemtray-height))))
+  (exwm-systemtray--refresh-background-color)
+  (exwm-systemtray--refresh))
+
+(defun exwm-systemtray--on-theme-change (_theme)
+  "Refresh system tray upon theme change."
+  (exwm-systemtray--refresh-background-color 'remap))
+
+(defun exwm-systemtray--refresh-all ()
+  "Reposition/Refresh the system tray."
+  (exwm--log)
+  (unless (exwm-workspace--minibuffer-own-frame-p)
+    (exwm-workspace--update-offsets)
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:ConfigureWindow
+                       :window exwm-systemtray--embedder-window
+                       :value-mask xcb:ConfigWindow:Y
+                       :y (- (slot-value (exwm-workspace--workarea
+                                           exwm-workspace-current-index)
+                                         'height)
+                             exwm-workspace--frame-y-offset
+                             exwm-systemtray-height))))
+  (exwm-systemtray--refresh))
+
+(cl-defun exwm-systemtray--init ()
+  "Initialize system tray module."
+  (exwm--log)
+  (cl-assert (not exwm-systemtray--connection))
+  (cl-assert (not exwm-systemtray--list))
+  (cl-assert (not exwm-systemtray--selection-owner-window))
+  (cl-assert (not exwm-systemtray--embedder-window))
+  (unless exwm-systemtray-height
+    (setq exwm-systemtray-height (max exwm-systemtray--icon-min-size
+                                      (with-selected-window (minibuffer-window)
+                                        (line-pixel-height)))))
+  ;; Create a new connection.
+  (setq exwm-systemtray--connection (xcb:connect))
+  (set-process-query-on-exit-flag (slot-value exwm-systemtray--connection
+                                              'process)
+                                  nil)
+  ;; Initialize XELB modules.
+  (xcb:xembed:init exwm-systemtray--connection t)
+  (xcb:systemtray:init exwm-systemtray--connection t)
+  ;; Acquire the manager selection _NET_SYSTEM_TRAY_S0.
+  (with-slots (owner)
+      (xcb:+request-unchecked+reply exwm-systemtray--connection
+          (make-instance 'xcb:GetSelectionOwner
+                         :selection xcb:Atom:_NET_SYSTEM_TRAY_S0))
+    (when (/= owner xcb:Window:None)
+      (xcb:disconnect exwm-systemtray--connection)
+      (setq exwm-systemtray--connection nil)
+      (warn "[EXWM] Other system tray detected")
+      (cl-return-from exwm-systemtray--init)))
+  (let ((id (xcb:generate-id exwm-systemtray--connection)))
+    (setq exwm-systemtray--selection-owner-window id)
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:CreateWindow
+                       :depth 0
+                       :wid id
+                       :parent exwm--root
+                       :x 0
+                       :y 0
+                       :width 1
+                       :height 1
+                       :border-width 0
+                       :class xcb:WindowClass:InputOnly
+                       :visual 0
+                       :value-mask xcb:CW:OverrideRedirect
+                       :override-redirect 1))
+    ;; Get the selection ownership.
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:SetSelectionOwner
+                       :owner id
+                       :selection xcb:Atom:_NET_SYSTEM_TRAY_S0
+                       :time xcb:Time:CurrentTime))
+    ;; Send a client message to announce the selection.
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:SendEvent
+                       :propagate 0
+                       :destination exwm--root
+                       :event-mask xcb:EventMask:StructureNotify
+                       :event (xcb:marshal
+                               (make-instance 'xcb:systemtray:-ClientMessage
+                                              :window exwm--root
+                                              :time xcb:Time:CurrentTime
+                                              :selection
+                                              xcb:Atom:_NET_SYSTEM_TRAY_S0
+                                              :owner id)
+                               exwm-systemtray--connection)))
+    ;; Set _NET_WM_NAME.
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:ewmh:set-_NET_WM_NAME
+                       :window id
+                       :data "EXWM: exwm-systemtray--selection-owner-window"))
+    ;; Set the _NET_SYSTEM_TRAY_ORIENTATION property.
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:xembed:set-_NET_SYSTEM_TRAY_ORIENTATION
+                       :window id
+                       :data xcb:systemtray:ORIENTATION:HORZ)))
+  ;; Create the embedder.
+  (let ((id (xcb:generate-id exwm-systemtray--connection))
+        frame parent embedder-depth embedder-visual embedder-colormap y)
+    (setq exwm-systemtray--embedder-window id)
+    (if (exwm-workspace--minibuffer-own-frame-p)
+        (setq frame exwm-workspace--minibuffer
+              y (if (>= (line-pixel-height) exwm-systemtray-height)
+                    ;; Bottom aligned.
+                    (- (line-pixel-height) exwm-systemtray-height)
+                  ;; Vertically centered.
+                  (/ (- (line-pixel-height) exwm-systemtray-height) 2)))
+      (exwm-workspace--update-offsets)
+      (setq frame exwm-workspace--current
+            ;; Bottom aligned.
+            y (- (slot-value (exwm-workspace--workarea
+                               exwm-workspace-current-index)
+                             'height)
+                 exwm-workspace--frame-y-offset
+                 exwm-systemtray-height)))
+    (setq parent (string-to-number (frame-parameter frame 'window-id)))
+    ;; Use default depth, visual and colormap (from root window), instead of
+    ;; Emacs frame's.  See Section "Visual and background pixmap handling" in
+    ;; "System Tray Protocol Specification 0.3".
+    (let* ((vdc (exwm--get-visual-depth-colormap exwm-systemtray--connection
+                                                 exwm--root)))
+      (setq embedder-visual (car vdc))
+      (setq embedder-depth (cadr vdc))
+      (setq embedder-colormap (caddr vdc)))
+    ;; Note down the embedder window's depth.  It will be used to check whether
+    ;; we can use xcb:BackPixmap:ParentRelative to emulate transparency.
+    (setq exwm-systemtray--embedder-window-depth embedder-depth)
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:CreateWindow
+                       :depth embedder-depth
+                       :wid id
+                       :parent parent
+                       :x 0
+                       :y y
+                       :width 1
+                       :height exwm-systemtray-height
+                       :border-width 0
+                       :class xcb:WindowClass:InputOutput
+                       :visual embedder-visual
+                       :colormap embedder-colormap
+                       :value-mask (logior xcb:CW:BorderPixel
+                                           xcb:CW:Colormap
+                                           xcb:CW:EventMask)
+                       :border-pixel 0
+                       :event-mask xcb:EventMask:SubstructureNotify))
+    (exwm-systemtray--set-background-color)
+    ;; Set _NET_WM_NAME.
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:ewmh:set-_NET_WM_NAME
+                       :window id
+                       :data "EXWM: exwm-systemtray--embedder-window"))
+    ;; Set _NET_WM_WINDOW_TYPE.
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:ewmh:set-_NET_WM_WINDOW_TYPE
+                       :window id
+                       :data (vector xcb:Atom:_NET_WM_WINDOW_TYPE_DOCK)))
+    ;; Set _NET_SYSTEM_TRAY_VISUAL.
+    (xcb:+request exwm-systemtray--connection
+        (make-instance 'xcb:xembed:set-_NET_SYSTEM_TRAY_VISUAL
+                       :window exwm-systemtray--selection-owner-window
+                       :data embedder-visual)))
+  (xcb:flush exwm-systemtray--connection)
+  ;; Attach event listeners.
+  (xcb:+event exwm-systemtray--connection 'xcb:DestroyNotify
+              #'exwm-systemtray--on-DestroyNotify)
+  (xcb:+event exwm-systemtray--connection 'xcb:ReparentNotify
+              #'exwm-systemtray--on-ReparentNotify)
+  (xcb:+event exwm-systemtray--connection 'xcb:ResizeRequest
+              #'exwm-systemtray--on-ResizeRequest)
+  (xcb:+event exwm-systemtray--connection 'xcb:PropertyNotify
+              #'exwm-systemtray--on-PropertyNotify)
+  (xcb:+event exwm-systemtray--connection 'xcb:ClientMessage
+              #'exwm-systemtray--on-ClientMessage)
+  (unless (exwm-workspace--minibuffer-own-frame-p)
+    (xcb:+event exwm-systemtray--connection 'xcb:KeyPress
+                #'exwm-systemtray--on-KeyPress))
+  ;; Add hook to move/reparent the embedder.
+  (add-hook 'exwm-workspace-switch-hook #'exwm-systemtray--on-workspace-switch)
+  (add-hook 'exwm-workspace--update-workareas-hook
+            #'exwm-systemtray--refresh-all)
+  ;; Add hook to update background colors.
+  (add-hook 'enable-theme-functions #'exwm-systemtray--on-theme-change)
+  (add-hook 'disable-theme-functions #'exwm-systemtray--on-theme-change)
+  (add-hook 'menu-bar-mode-hook #'exwm-systemtray--refresh-all)
+  (add-hook 'tool-bar-mode-hook #'exwm-systemtray--refresh-all)
+  (when (boundp 'exwm-randr-refresh-hook)
+    (add-hook 'exwm-randr-refresh-hook #'exwm-systemtray--refresh-all))
+  ;; The struts can be updated already.
+  (when exwm-workspace--workareas
+    (exwm-systemtray--refresh-all)))
+
+(defun exwm-systemtray--exit ()
+  "Exit the systemtray module."
+  (exwm--log)
+  (when exwm-systemtray--connection
+    (when (slot-value exwm-systemtray--connection 'connected)
+      ;; Hide & reparent out the embedder before disconnection to prevent
+      ;; embedded icons from being reparented to an Emacs frame (which is the
+      ;; parent of the embedder).
+      (xcb:+request exwm-systemtray--connection
+          (make-instance 'xcb:UnmapWindow
+                         :window exwm-systemtray--embedder-window))
+      (xcb:+request exwm-systemtray--connection
+          (make-instance 'xcb:ReparentWindow
+                         :window exwm-systemtray--embedder-window
+                         :parent exwm--root
+                         :x 0
+                         :y 0))
+      (xcb:disconnect exwm-systemtray--connection))
+    (setq exwm-systemtray--connection nil
+          exwm-systemtray--list nil
+          exwm-systemtray--selection-owner-window nil
+          exwm-systemtray--embedder-window nil
+          exwm-systemtray--embedder-window-depth nil)
+    (remove-hook 'exwm-workspace-switch-hook
+                 #'exwm-systemtray--on-workspace-switch)
+    (remove-hook 'exwm-workspace--update-workareas-hook
+                 #'exwm-systemtray--refresh-all)
+    (remove-hook 'enable-theme-functions #'exwm-systemtray--on-theme-change)
+    (remove-hook 'disable-theme-functions #'exwm-systemtray--on-theme-change)
+    (remove-hook 'menu-bar-mode-hook #'exwm-systemtray--refresh-all)
+    (remove-hook 'tool-bar-mode-hook #'exwm-systemtray--refresh-all)
+    (when (boundp 'exwm-randr-refresh-hook)
+      (remove-hook 'exwm-randr-refresh-hook #'exwm-systemtray--refresh-all))))
+
+(defun exwm-systemtray-enable ()
+  "Enable system tray support for EXWM."
+  (exwm--log)
+  (add-hook 'exwm-init-hook #'exwm-systemtray--init)
+  (add-hook 'exwm-exit-hook #'exwm-systemtray--exit))
+
+
+
+(provide 'exwm-systemtray)
+
+;;; exwm-systemtray.el ends here
diff --git a/exwm-workspace.el b/exwm-workspace.el
new file mode 100644
index 0000000000..89be697159
--- /dev/null
+++ b/exwm-workspace.el
@@ -0,0 +1,1768 @@
+;;; exwm-workspace.el --- Workspace Module for EXWM  -*- lexical-binding: t -*-
+
+;; Copyright (C) 1015-2024 Free Software Foundation, Inc.
+
+;; Author: Chris Feng <chris.w.feng@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module adds workspace support for EXWM.
+
+;;; Code:
+
+(require 'server)
+
+(require 'exwm-core)
+
+(defgroup exwm-workspace nil
+  "Workspace."
+  :group 'exwm)
+
+(defcustom exwm-workspace-switch-hook nil
+  "Normal hook run after switching workspace."
+  :type 'hook)
+
+(defcustom exwm-workspace-list-change-hook nil
+  "Normal hook run when the workspace list is changed.
+This happens when a workspace is added, deleted, moved, etc."
+  :type 'hook)
+
+(defcustom exwm-workspace-show-all-buffers nil
+  "Non-nil to show buffers on other workspaces."
+  :type 'boolean)
+
+(defcustom exwm-workspace-warp-cursor nil
+  "Non-nil to warp cursor automatically after workspace switch."
+  :type 'boolean)
+
+(defcustom exwm-workspace-number 1
+  "Initial number of workspaces."
+  :type 'integer)
+
+(defcustom exwm-workspace-index-map #'number-to-string
+  "Function for mapping a workspace index to a string for display.
+
+By default `number-to-string' is applied which yields 0 1 2 ... ."
+  :type 'function)
+
+(defcustom exwm-workspace-minibuffer-position nil
+  "Position of the minibuffer frame.
+
+A restart is required for this change to take effect."
+  :type '(choice (const :tag "Bottom (fixed)" nil)
+                 (const :tag "Bottom (auto-hide)" bottom)
+                 (const :tag "Top (auto-hide)" top)))
+
+(defcustom exwm-workspace-display-echo-area-timeout 1
+  "Timeout for displaying echo area."
+  :type 'integer)
+
+(defcustom exwm-workspace-switch-create-limit 10
+  "Number of workspaces `exwm-workspace-switch-create' is allowed to create."
+  :type 'integer)
+
+(defvar exwm-workspace-current-index 0 "Index of current active workspace.")
+
+(defvar exwm-workspace--attached-minibuffer-height 0
+  "Height (in pixel) of the attached minibuffer.
+
+If the minibuffer is detached, this value is 0.")
+
+(defvar exwm-workspace--create-silently nil
+  "When non-nil workspaces are created in the background (not switched to).
+
+Please manually run the hook `exwm-workspace-list-change-hook' afterwards.")
+
+(defvar exwm-workspace--current nil "Current active workspace.")
+
+(defvar exwm-workspace--display-echo-area-timer nil
+  "Timer for auto-hiding echo area.")
+
+(defvar exwm-workspace--id-struts-alist nil "Alist of X window and struts.")
+
+(defvar exwm-workspace--fullscreen-frame-count 0
+  "Count the fullscreen workspace frames.")
+
+(defvar exwm-workspace--list nil "List of all workspaces (Emacs frames).")
+
+(defvar exwm-workspace--minibuffer nil
+  "The minibuffer frame shared among all frames.")
+
+(defvar exwm-workspace--original-handle-focus-in
+  (symbol-function #'handle-focus-in))
+(defvar exwm-workspace--original-handle-focus-out
+  (symbol-function #'handle-focus-out))
+
+(defvar exwm-workspace--prompt-add-allowed nil
+  "Non-nil to allow adding workspace from the prompt.")
+
+(defvar exwm-workspace--prompt-delete-allowed nil
+  "Non-nil to allow deleting workspace from the prompt.")
+
+(defvar exwm-workspace--struts nil "Areas occupied by struts.")
+
+(defvar exwm-workspace--switch-history nil
+  "History for `read-from-minibuffer' to interactively switch workspace.")
+
+(defvar exwm-workspace--switch-history-outdated nil
+  "Non-nil to indicate `exwm-workspace--switch-history' is outdated.")
+
+(defvar exwm-workspace--timer nil "Timer used to track echo area changes.")
+
+(defvar exwm-workspace--update-workareas-hook nil
+  "Normal hook run when workareas get updated.")
+
+(defvar exwm-workspace--workareas nil "Workareas (struts excluded).")
+
+(defvar exwm-workspace--frame-y-offset 0
+  "Offset between Emacs inner & outer frame in Y.")
+(defvar exwm-workspace--window-y-offset 0
+  "Offset between Emacs first window & outer frame in Y.")
+
+(defvar exwm-input--during-command)
+(defvar exwm-input--event-hook)
+(defvar exwm-layout-show-all-buffers)
+(defvar exwm-manage--desktop)
+(declare-function exwm-input--on-buffer-list-update "exwm-input.el" ())
+(declare-function exwm-layout--fullscreen-p "exwm-layout.el" ())
+(declare-function exwm-layout--hide "exwm-layout.el" (id))
+(declare-function exwm-layout--other-buffer-predicate "exwm-layout.el"
+                  (buffer))
+(declare-function exwm-layout--refresh "exwm-layout.el")
+(declare-function exwm-layout--show "exwm-layout.el" (id &optional window))
+
+(defsubst exwm-workspace--position (frame)
+  "Retrieve index of given FRAME in workspace list.
+NIL if FRAME is not a workspace."
+  (declare (indent defun))
+  (cl-position frame exwm-workspace--list))
+
+(defsubst exwm-workspace--count ()
+  "Retrieve total number of workspaces."
+  (length exwm-workspace--list))
+
+(defsubst exwm-workspace--workspace-p (frame)
+  "Return t if FRAME is a workspace."
+  (declare (indent defun))
+  (memq frame exwm-workspace--list))
+
+(defsubst exwm-workspace--workarea (frame)
+  "Return workarea corresponding to FRAME.
+FRAME may be either a workspace frame or a workspace position."
+  (declare (indent defun))
+  (elt exwm-workspace--workareas
+       (if (integerp frame)
+           frame
+         (exwm-workspace--position frame))))
+
+(defvar exwm-workspace--switch-map nil
+  "Keymap used for interactively selecting workspace.")
+
+(defun exwm-workspace--init-switch-map ()
+  "Initialize variable `exwm-workspace--switch-map'."
+  (let ((map (make-sparse-keymap)))
+    (define-key map [t] (lambda () (interactive)))
+    (define-key map "+" #'exwm-workspace--prompt-add)
+    (define-key map "-" #'exwm-workspace--prompt-delete)
+    (dotimes (i 10)
+      (define-key map (int-to-string i)
+        #'exwm-workspace--switch-map-nth-prefix))
+    (unless (eq exwm-workspace-index-map #'number-to-string)
+      ;; Add extra (and possibly override) keys for selecting workspace.
+      (dotimes (i 10)
+        (let ((key (funcall exwm-workspace-index-map i)))
+          (when (and (stringp key)
+                     (= (length key) 1)
+                     (<= 0 (elt key 0) 127))
+            (define-key map key
+              (lambda ()
+                (interactive)
+                (exwm-workspace--switch-map-select-nth i)))))))
+    (define-key map "\C-a" (lambda () (interactive) (goto-history-element 1)))
+    (define-key map "\C-e" (lambda ()
+                             (interactive)
+                             (goto-history-element (exwm-workspace--count))))
+    (define-key map "\C-g" #'abort-recursive-edit)
+    (define-key map "\C-]" #'abort-recursive-edit)
+    (define-key map "\C-j" #'exit-minibuffer)
+    ;; (define-key map "\C-m" #'exit-minibuffer) ;not working
+    (define-key map [return] #'exit-minibuffer)
+    (define-key map " " #'exit-minibuffer)
+    (define-key map "\C-f" #'previous-history-element)
+    (define-key map "\C-b" #'next-history-element)
+    ;; Alternative keys
+    (define-key map [right] #'previous-history-element)
+    (define-key map [left] #'next-history-element)
+    (setq exwm-workspace--switch-map map)))
+
+(defun exwm-workspace--workspace-from-frame-or-index (frame-or-index)
+  "Retrieve the workspace frame from FRAME-OR-INDEX."
+  (cond
+   ((framep frame-or-index)
+    (unless (exwm-workspace--position frame-or-index)
+      (user-error "[EXWM] Frame is not a workspace %S" frame-or-index))
+    frame-or-index)
+   ((integerp frame-or-index)
+    (unless (and (<= 0 frame-or-index)
+                 (< frame-or-index (exwm-workspace--count)))
+      (user-error "[EXWM] Workspace index out of range: %d" frame-or-index))
+    (elt exwm-workspace--list frame-or-index))
+   (t (user-error "[EXWM] Invalid workspace: %s" frame-or-index))))
+
+(defun exwm-workspace--prompt-for-workspace (&optional prompt)
+  "Prompt for a workspace, returning the workspace frame.
+Show PROMPT to the user if non-nil."
+  (exwm-workspace--update-switch-history)
+  (let* ((current-idx (exwm-workspace--position exwm-workspace--current))
+         (history-add-new-input nil)  ;prevent modifying history
+         (history-idx (read-from-minibuffer
+                       (or prompt "Workspace: ")
+                       (elt exwm-workspace--switch-history current-idx)
+                       exwm-workspace--switch-map nil
+                       `(exwm-workspace--switch-history . ,(1+ current-idx))))
+         (workspace-idx (cl-position history-idx exwm-workspace--switch-history
+                                     :test #'equal)))
+    (elt exwm-workspace--list workspace-idx)))
+
+(defun exwm-workspace--prompt-add ()
+  "Add workspace from the prompt."
+  (interactive)
+  (when exwm-workspace--prompt-add-allowed
+    (let ((exwm-workspace--create-silently t))
+      (make-frame)
+      (run-hooks 'exwm-workspace-list-change-hook))
+    (exwm-workspace--update-switch-history)
+    (goto-history-element minibuffer-history-position)))
+
+(defun exwm-workspace--prompt-delete ()
+  "Delete workspace from the prompt."
+  (interactive)
+  (when (and exwm-workspace--prompt-delete-allowed
+             (< 1 (exwm-workspace--count)))
+    (let ((frame (elt exwm-workspace--list (1- minibuffer-history-position))))
+      (if (eq frame exwm-workspace--current)
+          ;; Abort the recursive minibuffer if deleting the current workspace.
+          (progn
+            (exwm--defer 0 #'delete-frame frame)
+            (abort-recursive-edit))
+        (delete-frame frame)
+        (exwm-workspace--update-switch-history)
+        (goto-history-element (min minibuffer-history-position
+                                   (exwm-workspace--count)))))))
+
+(defun exwm-workspace--update-switch-history ()
+  "Update the history for switching workspace to reflect the latest status."
+  (when exwm-workspace--switch-history-outdated
+    (setq exwm-workspace--switch-history-outdated nil)
+    (let* ((num (exwm-workspace--count))
+           (sequence (number-sequence 0 (1- num)))
+           (not-empty (make-vector num nil)))
+      (dolist (i exwm--id-buffer-alist)
+        (with-current-buffer (cdr i)
+          (when exwm--frame
+            (setf (aref not-empty
+                        (exwm-workspace--position exwm--frame))
+                  t))))
+      (setq exwm-workspace--switch-history
+            (mapcar
+             (lambda (i)
+               (mapconcat
+                (lambda (j)
+                  (format (if (= i j) "[%s]" " %s ")
+                          (propertize
+                           (apply exwm-workspace-index-map (list j))
+                           'face
+                           (cond ((frame-parameter (elt exwm-workspace--list j)
+                                                   'exwm-urgency)
+                                  '(:foreground "orange"))
+                                 ((aref not-empty j) '(:foreground "green"))
+                                 (t nil)))))
+                sequence ""))
+             sequence)))))
+
+;;;###autoload
+(defun exwm-workspace--get-geometry (frame)
+  "Return the geometry of frame FRAME."
+  (or (frame-parameter frame 'exwm-geometry)
+      (make-instance 'xcb:RECTANGLE
+                     :x 0
+                     :y 0
+                     :width (x-display-pixel-width)
+                     :height (x-display-pixel-height))))
+
+;;;###autoload
+(defun exwm-workspace--current-height ()
+  "Return the height of current workspace."
+  (let ((geometry (frame-parameter exwm-workspace--current 'exwm-geometry)))
+    (if geometry
+        (slot-value geometry 'height)
+      (x-display-pixel-height))))
+
+;;;###autoload
+(defun exwm-workspace--minibuffer-own-frame-p ()
+  "Reports whether the minibuffer is displayed in its own frame."
+  (memq exwm-workspace-minibuffer-position '(top bottom)))
+
+(defun exwm-workspace--update-struts ()
+  "Update `exwm-workspace--struts'."
+  (setq exwm-workspace--struts nil)
+  (let (struts struts*)
+    (dolist (pair exwm-workspace--id-struts-alist)
+      (setq struts (cdr pair))
+      (when struts
+        (dotimes (i 4)
+          (when (/= 0 (aref struts i))
+            (setq struts*
+                  (vector (aref [left right top bottom] i)
+                          (aref struts i)
+                          (when (= 12 (length struts))
+                            (substring struts (+ 4 (* i 2)) (+ 6 (* i 2))))))
+            (if (= 0 (mod i 2))
+                ;; Make left/top processed first.
+                (push struts* exwm-workspace--struts)
+              (setq exwm-workspace--struts
+                    (append exwm-workspace--struts (list struts*))))))))
+    (exwm--log "%s" exwm-workspace--struts)))
+
+(defun exwm-workspace--update-workareas ()
+  "Update `exwm-workspace--workareas'."
+  (let* ((root-width (x-display-pixel-width))
+         (root-height (x-display-pixel-height))
+         ;; Get workareas prior to struts.
+         (workareas (mapcar
+                     (lambda (frame)
+                       (if-let (rect (frame-parameter frame 'exwm-geometry))
+                           ;; Use the 'exwm-geometry' frame parameter if it
+                           ;; exists.  Make sure to clone it, will be modified
+                           ;; below!
+                           (clone rect)
+                         ;; Fall back to use the screen size.
+                         (make-instance 'xcb:RECTANGLE
+                                        :x 0
+                                        :y 0
+                                        :width root-width
+                                        :height root-height)))
+                     exwm-workspace--list)))
+    ;; Exclude areas occupied by struts.
+    (dolist (struts exwm-workspace--struts)
+      (let* ((edge (aref struts 0))
+             (size (aref struts 1))
+             (position (aref struts 2))
+             (beg (and position (aref position 0)))
+             (end (and position (aref position 1)))
+             delta)
+        (dolist (w workareas)
+          (with-slots (x y width height) w
+            (pcase edge
+              ;; Left and top are always processed first.
+              ('left
+               (setq delta (- size x))
+               (when (and (< 0 delta)
+                          (< delta width)
+                          (or (not position)
+                              (< (max beg y)
+                                 (min end (+ y height)))))
+                 (cl-decf width delta)
+                 (setf x size)))
+              ('right
+               (setq delta (- size (- root-width x width)))
+               (when (and (< 0 delta)
+                          (< delta width)
+                          (or (not position)
+                              (< (max beg y)
+                                 (min end (+ y height)))))
+                 (cl-decf width delta)))
+              ('top
+               (setq delta (- size y))
+               (when (and (< 0 delta)
+                          (< delta height)
+                          (or (not position)
+                              (< (max beg x)
+                                 (min end (+ x width)))))
+                 (cl-decf height delta)
+                 (setf y size)))
+              ('bottom
+               (setq delta (- size (- root-height y height)))
+               (when (and (< 0 delta)
+                          (< delta height)
+                          (or (not position)
+                              (< (max beg x)
+                                 (min end (+ x width)))))
+                 (cl-decf height delta))))))))
+    ;; Save the result.
+    (setq exwm-workspace--workareas workareas)
+    (xcb:flush exwm--connection))
+  (exwm--log "%s" exwm-workspace--workareas)
+  (run-hooks 'exwm-workspace--update-workareas-hook))
+
+(defun exwm-workspace--update-offsets ()
+  "Update `exwm-workspace--frame-y-offset'/`exwm-workspace--window-y-offset'."
+  (exwm--log)
+  (if (not (and exwm-workspace--list
+                (or menu-bar-mode tool-bar-mode)))
+      (setq exwm-workspace--frame-y-offset 0
+            exwm-workspace--window-y-offset 0)
+    (redisplay t)
+    (let* ((frame (elt exwm-workspace--list 0))
+           (edges (window-inside-absolute-pixel-edges (frame-first-window
+                                                       frame))))
+      (with-slots (y)
+          (xcb:+request-unchecked+reply exwm--connection
+              (make-instance 'xcb:GetGeometry
+                             :drawable (frame-parameter frame
+                                                        'exwm-container)))
+        (with-slots ((y* y))
+            (xcb:+request-unchecked+reply exwm--connection
+                (make-instance 'xcb:GetGeometry
+                               :drawable (frame-parameter frame
+                                                          'exwm-outer-id)))
+          (with-slots ((y** y))
+              (xcb:+request-unchecked+reply exwm--connection
+                  (make-instance 'xcb:GetGeometry
+                                 :drawable (frame-parameter frame 'exwm-id)))
+            (setq exwm-workspace--frame-y-offset (- y** y*)
+                  exwm-workspace--window-y-offset (- (elt edges 1) y))))))))
+
+(defun exwm-workspace--set-active (frame active)
+  "Make frame FRAME active on its monitor.
+ACTIVE indicates whether to set the frame active or inactive."
+  (exwm--log "active=%s; frame=%s" active frame)
+  (set-frame-parameter frame 'exwm-active active)
+  (if active
+      (exwm-workspace--set-fullscreen frame)
+    (exwm--set-geometry (frame-parameter frame 'exwm-container) nil nil 1 1))
+  (exwm-layout--refresh frame)
+  (xcb:flush exwm--connection))
+
+(defun exwm-workspace--active-p (frame)
+  "Return non-nil if FRAME is active."
+  (frame-parameter frame 'exwm-active))
+
+(defun exwm-workspace--set-fullscreen (frame)
+  "Make frame FRAME fullscreen according to `exwm-workspace--workareas'."
+  (exwm--log "frame=%s" frame)
+  (let ((id (frame-parameter frame 'exwm-outer-id))
+        (container (frame-parameter frame 'exwm-container)))
+    (with-slots (x y width height)
+        (exwm-workspace--workarea frame)
+      (exwm--log "x=%s; y=%s; w=%s; h=%s" x y width height)
+      (when (and (eq frame exwm-workspace--current)
+                 (exwm-workspace--minibuffer-own-frame-p))
+        (exwm-workspace--resize-minibuffer-frame))
+      (if (exwm-workspace--active-p frame)
+          (exwm--set-geometry container x y width height)
+        (exwm--set-geometry container x y 1 1))
+      (exwm--set-geometry id nil nil width height)
+      (xcb:flush exwm--connection)))
+  ;; This is only used for workspace initialization.
+  (when exwm-workspace--fullscreen-frame-count
+    (cl-incf exwm-workspace--fullscreen-frame-count)))
+
+(defun exwm-workspace--resize-minibuffer-frame ()
+  "Resize minibuffer (and its container) to fit the size of workspace."
+  (cl-assert (exwm-workspace--minibuffer-own-frame-p))
+  (let ((workarea (exwm-workspace--workarea exwm-workspace-current-index))
+        (container (frame-parameter exwm-workspace--minibuffer
+                                    'exwm-container))
+        y width)
+    (setq y (if (eq exwm-workspace-minibuffer-position 'top)
+                (- (slot-value workarea 'y)
+                   exwm-workspace--attached-minibuffer-height)
+              ;; Reset the frame size.
+              (set-frame-height exwm-workspace--minibuffer 1)
+              (redisplay)               ;FIXME.
+              (+ (slot-value workarea 'y) (slot-value workarea 'height)
+                 (- (frame-pixel-height exwm-workspace--minibuffer))
+                 exwm-workspace--attached-minibuffer-height))
+          width (slot-value workarea 'width))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ConfigureWindow
+                       :window container
+                       :value-mask (logior xcb:ConfigWindow:X
+                                           xcb:ConfigWindow:Y
+                                           xcb:ConfigWindow:Width
+                                           (if exwm-manage--desktop
+                                               xcb:ConfigWindow:Sibling
+                                             0)
+                                           xcb:ConfigWindow:StackMode)
+                       :x (slot-value workarea 'x)
+                       :y y
+                       :width width
+                       :sibling exwm-manage--desktop
+                       :stack-mode (if exwm-manage--desktop
+                                       xcb:StackMode:Above
+                                     xcb:StackMode:Below)))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ConfigureWindow
+                       :window (frame-parameter exwm-workspace--minibuffer
+                                                'exwm-outer-id)
+                       :value-mask xcb:ConfigWindow:Width
+                       :width width))
+    (exwm--log "y: %s, width: %s" y width)))
+
+(defun exwm-workspace--switch-map-nth-prefix (&optional prefix-digits)
+  "Allow selecting a workspace by number.
+
+PREFIX-DIGITS is a list of the digits introduced so far."
+  (interactive)
+  (let* ((k (aref (substring (this-command-keys-vector) -1) 0))
+         (d (- k ?0))
+         ;; Convert prefix-digits to number.  For example, '(2 1) to 120.
+         (o 1)
+         (pn (apply #'+ (mapcar (lambda (x)
+                                  (setq o (* 10 o))
+                                  (* o x))
+                                prefix-digits)))
+         (n (+ pn d))
+         prefix-length index-max index-length)
+    (if (or (= n 0)
+            (> n
+               (setq index-max (1- (exwm-workspace--count))))
+            (>= (setq prefix-length (length prefix-digits))
+                (setq index-length (floor (log index-max 10))))
+            ;; Check if it's still possible to do a match.
+            (> (* n (expt 10 (- index-length prefix-length)))
+               index-max))
+        (exwm-workspace--switch-map-select-nth n)
+      ;; Go ahead if there are enough digits to select any workspace.
+      (set-transient-map
+       (let ((map (make-sparse-keymap))
+             (cmd (let ((digits (cons d prefix-digits)))
+                    (lambda ()
+                     (interactive)
+                     (exwm-workspace--switch-map-nth-prefix digits)))))
+         (dotimes (i 10)
+           (define-key map (int-to-string i) cmd))
+         ;; Accept
+         (define-key map [return]
+           (lambda ()
+             (interactive)
+             (exwm-workspace--switch-map-select-nth n)))
+         map)))))
+
+(defun exwm-workspace--switch-map-select-nth (n)
+  "Select Nth workspace."
+  (interactive)
+  (goto-history-element (1+ n))
+  (exit-minibuffer))
+
+;;;###autoload
+(defun exwm-workspace-switch (frame-or-index &optional force)
+  "Switch to workspace FRAME-OR-INDEX (0-based).
+
+Query for the index if not specified when called interactively.  Passing a
+workspace frame as the first option or making use of the rest options are
+for internal use only.
+
+When FORCE is true, allow switching to current workspace."
+  (interactive
+   (list
+    (cond
+     ((null current-prefix-arg)
+      (unless (and (derived-mode-p 'exwm-mode)
+                   ;; The prompt is invisible in fullscreen mode.
+                   (exwm-layout--fullscreen-p))
+        (let ((exwm-workspace--prompt-add-allowed t)
+              (exwm-workspace--prompt-delete-allowed t))
+          (exwm-workspace--prompt-for-workspace "Switch to [+/-]: "))))
+     ((and (integerp current-prefix-arg)
+           (<= 0 current-prefix-arg (exwm-workspace--count)))
+      current-prefix-arg)
+     (t 0))))
+  (exwm--log)
+  (let* ((frame (exwm-workspace--workspace-from-frame-or-index frame-or-index))
+         (old-frame exwm-workspace--current)
+         (index (exwm-workspace--position frame))
+         (window (frame-parameter frame 'exwm-selected-window)))
+    (when (or force (not (eq frame exwm-workspace--current)))
+      (unless (window-live-p window)
+        (setq window (frame-selected-window frame)))
+    (when (and (not (eq frame old-frame))
+               (frame-live-p old-frame))
+      (with-selected-frame old-frame
+        (funcall exwm-workspace--original-handle-focus-out
+                 (list 'focus-out frame))))
+      ;; Raise this frame.
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ConfigureWindow
+                         :window (frame-parameter frame 'exwm-container)
+                         :value-mask (logior xcb:ConfigWindow:Sibling
+                                             xcb:ConfigWindow:StackMode)
+                         :sibling exwm--guide-window
+                         :stack-mode xcb:StackMode:Below))
+      (setq exwm-workspace--current frame
+            exwm-workspace-current-index index)
+      (unless (exwm-workspace--workspace-p (selected-frame))
+        ;; Save the floating frame window selected on the previous workspace.
+        (set-frame-parameter (buffer-local-value 'exwm--frame (window-buffer))
+                             'exwm-selected-window (selected-window)))
+      ;; Show/Hide X windows.
+      (let ((monitor-old (frame-parameter old-frame 'exwm-randr-monitor))
+            (monitor-new (frame-parameter frame 'exwm-randr-monitor))
+            (active-old (exwm-workspace--active-p old-frame))
+            (active-new (exwm-workspace--active-p frame))
+            workspaces-to-hide)
+        (cond
+         ((not active-old)
+          (exwm-workspace--set-active frame t))
+         ((equal monitor-old monitor-new)
+          (exwm-workspace--set-active frame t)
+          (unless (eq frame old-frame)
+            (exwm-workspace--set-active old-frame nil)
+            (setq workspaces-to-hide (list old-frame))))
+         (active-new)
+         (t
+          (dolist (w exwm-workspace--list)
+            (when (and (exwm-workspace--active-p w)
+                       (equal monitor-new
+                              (frame-parameter w 'exwm-randr-monitor)))
+              (exwm-workspace--set-active w nil)
+              (setq workspaces-to-hide (append workspaces-to-hide (list w)))))
+          (exwm-workspace--set-active frame t)))
+        (dolist (i exwm--id-buffer-alist)
+          (with-current-buffer (cdr i)
+            (if (memq exwm--frame workspaces-to-hide)
+                (exwm-layout--hide exwm--id)
+              (when (eq frame exwm--frame)
+                (let ((window (get-buffer-window nil t)))
+                  (when window
+                    (exwm-layout--show exwm--id window))))))))
+      (select-window window)
+      (x-focus-frame (window-frame window)) ;The real input focus.
+      (set-frame-parameter frame 'exwm-selected-window nil)
+      (if (exwm-workspace--minibuffer-own-frame-p)
+          ;; Resize the minibuffer frame.
+          (exwm-workspace--resize-minibuffer-frame)
+        ;; Set a default minibuffer frame.
+        (setq default-minibuffer-frame frame))
+      ;; Hide windows in other workspaces by preprending a space
+      (unless exwm-workspace-show-all-buffers
+        (dolist (i exwm--id-buffer-alist)
+          (with-current-buffer (cdr i)
+            (let ((name (replace-regexp-in-string "^\\s-*" ""
+                                                  (buffer-name))))
+              (exwm-workspace-rename-buffer (if (eq frame exwm--frame)
+                                                name
+                                              (concat " " name)))))))
+      ;; Update demands attention flag
+      (set-frame-parameter frame 'exwm-urgency nil)
+      ;; Update switch workspace history
+      (setq exwm-workspace--switch-history-outdated t)
+      ;; Set _NET_CURRENT_DESKTOP
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ewmh:set-_NET_CURRENT_DESKTOP
+                         :window exwm--root :data index))
+      (xcb:flush exwm--connection))
+    (when exwm-workspace-warp-cursor
+      (with-slots (win-x win-y)
+          (xcb:+request-unchecked+reply exwm--connection
+              (make-instance 'xcb:QueryPointer
+                             :window (frame-parameter frame
+                                                      'exwm-outer-id)))
+        (when (or (< win-x 0)
+                  (< win-y 0)
+                  (> win-x (frame-pixel-width frame))
+                  (> win-y (frame-pixel-height frame)))
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:WarpPointer
+                             :src-window xcb:Window:None
+                             :dst-window (frame-parameter frame
+                                                          'exwm-outer-id)
+                             :src-x 0
+                             :src-y 0
+                             :src-width 0
+                             :src-height 0
+                             :dst-x (/ (frame-pixel-width frame) 2)
+                             :dst-y (/ (frame-pixel-height frame) 2)))
+          (xcb:flush exwm--connection))))
+    (funcall exwm-workspace--original-handle-focus-in (list 'focus-in frame))
+    (run-hooks 'exwm-workspace-switch-hook)))
+
+;;;###autoload
+(defun exwm-workspace-switch-create (frame-or-index)
+  "Switch to workspace FRAME-OR-INDEX creating it first non-existent.
+
+Passing a workspace frame as the first option is for internal use only."
+  (interactive
+   (list
+    (cond
+     ((integerp current-prefix-arg)
+      current-prefix-arg)
+     (t 0))))
+  (unless frame-or-index
+    (setq frame-or-index 0))
+  (exwm--log "%s" frame-or-index)
+  (if (or (framep frame-or-index)
+          (< frame-or-index (exwm-workspace--count)))
+      (exwm-workspace-switch frame-or-index)
+    (let ((exwm-workspace--create-silently t))
+      (dotimes (_ (min exwm-workspace-switch-create-limit
+                       (1+ (- frame-or-index
+                              (exwm-workspace--count)))))
+        (make-frame))
+      (run-hooks 'exwm-workspace-list-change-hook))
+    (exwm-workspace-switch frame-or-index)))
+
+;;;###autoload
+(defun exwm-workspace-swap (workspace1 workspace2)
+  "Interchange position of WORKSPACE1 with that of WORKSPACE2."
+  (interactive
+   (unless (and (derived-mode-p 'exwm-mode)
+                ;; The prompt is invisible in fullscreen mode.
+                (exwm-layout--fullscreen-p))
+     (let (w1 w2)
+       (let ((exwm-workspace--prompt-add-allowed t)
+             (exwm-workspace--prompt-delete-allowed t))
+         (setq w1 (exwm-workspace--prompt-for-workspace
+                   "Pick a workspace [+/-]: ")))
+       (setq w2 (exwm-workspace--prompt-for-workspace
+                 (format "Swap workspace %d with: "
+                         (exwm-workspace--position w1))))
+       (list w1 w2))))
+  (exwm--log)
+  (let ((pos1 (exwm-workspace--position workspace1))
+        (pos2 (exwm-workspace--position workspace2)))
+    (if (or (not pos1) (not pos2) (= pos1 pos2))
+        (user-error "[EXWM] Cannot swap %s and %s" workspace1 workspace2)
+      (setf (elt exwm-workspace--list pos1) workspace2)
+      (setf (elt exwm-workspace--list pos2) workspace1)
+      ;; Update the _NET_WM_DESKTOP property of each X window affected.
+      (dolist (pair exwm--id-buffer-alist)
+        (when (memq (buffer-local-value 'exwm--frame (cdr pair))
+                    (list workspace1 workspace2))
+          (exwm-workspace--set-desktop (car pair))))
+      (xcb:flush exwm--connection)
+      (when (memq exwm-workspace--current (list workspace1 workspace2))
+        ;; With the current workspace involved, lots of stuffs need refresh.
+        (set-frame-parameter exwm-workspace--current 'exwm-selected-window
+                             (selected-window))
+        (exwm-workspace-switch exwm-workspace--current t))
+      (run-hooks 'exwm-workspace-list-change-hook))))
+
+;;;###autoload
+(defun exwm-workspace-move (workspace nth)
+  "Move WORKSPACE to the NTH position.
+
+When called interactively, prompt for a workspace and move current one just
+before it."
+  (interactive
+   (cond
+    ((null current-prefix-arg)
+     (unless (and (derived-mode-p 'exwm-mode)
+                  ;; The prompt is invisible in fullscreen mode.
+                  (exwm-layout--fullscreen-p))
+       (list exwm-workspace--current
+             (exwm-workspace--position
+              (exwm-workspace--prompt-for-workspace "Move workspace to: ")))))
+    ((and (integerp current-prefix-arg)
+          (<= 0 current-prefix-arg (exwm-workspace--count)))
+     (list exwm-workspace--current current-prefix-arg))
+    (t (list exwm-workspace--current 0))))
+  (exwm--log)
+  (let ((pos (exwm-workspace--position workspace))
+        flag start end index)
+    (if (= nth pos)
+        (user-error "[EXWM] Cannot move to same position")
+      ;; Set if the current workspace is involved.
+      (setq flag (or (eq workspace exwm-workspace--current)
+                     (eq (elt exwm-workspace--list nth)
+                         exwm-workspace--current)))
+      ;; Do the move.
+      (with-no-warnings                 ;For Emacs 24.
+        (pop (nthcdr pos exwm-workspace--list)))
+      (push workspace (nthcdr nth exwm-workspace--list))
+      ;; Update the _NET_WM_DESKTOP property of each X window affected.
+      (setq start (min pos nth)
+            end (max pos nth))
+      (dolist (pair exwm--id-buffer-alist)
+        (setq index (exwm-workspace--position
+                     (buffer-local-value 'exwm--frame (cdr pair))))
+        (unless (or (< index start) (> index end))
+          (exwm-workspace--set-desktop (car pair))))
+      (when flag
+        ;; With the current workspace involved, lots of stuffs need refresh.
+        (set-frame-parameter exwm-workspace--current 'exwm-selected-window
+                             (selected-window))
+        (exwm-workspace-switch exwm-workspace--current t))
+      (run-hooks 'exwm-workspace-list-change-hook))))
+
+;;;###autoload
+(defun exwm-workspace-add (&optional index)
+  "Add a workspace as the INDEX-th workspace, or the last one if INDEX is nil.
+
+INDEX must not exceed the current number of workspaces."
+  (interactive)
+  (exwm--log "%s" index)
+  (if (and index
+           ;; No need to move if it's the last one.
+           (< index (exwm-workspace--count)))
+      (exwm-workspace-move (make-frame) index)
+    (make-frame)))
+
+;;;###autoload
+(defun exwm-workspace-delete (&optional frame-or-index)
+  "Delete the workspace FRAME-OR-INDEX."
+  (interactive)
+  (exwm--log "%s" frame-or-index)
+  (when (< 1 (exwm-workspace--count))
+    (let ((frame (if frame-or-index
+                     (exwm-workspace--workspace-from-frame-or-index
+                      frame-or-index)
+                   exwm-workspace--current)))
+      (delete-frame frame))))
+
+(defun exwm-workspace--set-desktop (id)
+  "Set _NET_WM_DESKTOP for X window ID."
+  (exwm--log "#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (let ((desktop (exwm-workspace--position exwm--frame)))
+      (setq exwm--desktop desktop)
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ewmh:set-_NET_WM_DESKTOP
+                         :window id
+                         :data desktop)))))
+
+;;;###autoload
+(cl-defun exwm-workspace-move-window (frame-or-index &optional id)
+  "Move window ID to workspace FRAME-OR-INDEX."
+  (interactive (list
+                (cond
+                 ((null current-prefix-arg)
+                  (let ((exwm-workspace--prompt-add-allowed t)
+                        (exwm-workspace--prompt-delete-allowed t))
+                    (exwm-workspace--prompt-for-workspace "Move to [+/-]: ")))
+                 ((and (integerp current-prefix-arg)
+                       (<= 0 current-prefix-arg (exwm-workspace--count)))
+                  current-prefix-arg)
+                 (t 0))))
+  (let ((frame (exwm-workspace--workspace-from-frame-or-index frame-or-index))
+        old-frame container)
+    (unless id (setq id (exwm--buffer->id (window-buffer))))
+    (unless id
+      (cl-return-from exwm-workspace-move-window))
+    (exwm--log "Moving #x%x to %s" id frame-or-index)
+    (with-current-buffer (exwm--id->buffer id)
+      (unless (eq exwm--frame frame)
+        (unless exwm-workspace-show-all-buffers
+          (let ((name (replace-regexp-in-string "^\\s-*" "" (buffer-name))))
+            (exwm-workspace-rename-buffer
+             (if (eq frame exwm-workspace--current)
+                 name
+               (concat " " name)))))
+        (setq old-frame exwm--frame
+              exwm--frame frame)
+        (if (not exwm--floating-frame)
+            ;; Tiling.
+            (if (get-buffer-window nil frame)
+                (when (eq frame exwm-workspace--current)
+                  (exwm-layout--refresh frame))
+              (set-window-buffer (get-buffer-window nil t)
+                                 (other-buffer nil t))
+              (unless (eq frame exwm-workspace--current)
+                ;; Clear the 'exwm-selected-window' frame parameter.
+                (set-frame-parameter frame 'exwm-selected-window nil))
+              (set-window-buffer (frame-selected-window frame)
+                                 (exwm--id->buffer id))
+              (if (eq frame exwm-workspace--current)
+                  (select-window (frame-selected-window frame))
+                (unless (exwm-workspace--active-p frame)
+                  (exwm-layout--hide id))))
+          ;; Floating.
+          (setq container (frame-parameter exwm--floating-frame
+                                           'exwm-container))
+          (unless (equal (frame-parameter old-frame 'exwm-randr-monitor)
+                         (frame-parameter frame 'exwm-randr-monitor))
+            (with-slots (x y)
+                (xcb:+request-unchecked+reply exwm--connection
+                    (make-instance 'xcb:GetGeometry
+                                   :drawable container))
+              (with-slots ((x1 x)
+                           (y1 y))
+                  (exwm-workspace--get-geometry old-frame)
+                (with-slots ((x2 x)
+                             (y2 y))
+                    (exwm-workspace--get-geometry frame)
+                  (setq x (+ x (- x2 x1))
+                        y (+ y (- y2 y1)))))
+              (exwm--set-geometry id x y nil nil)
+              (exwm--set-geometry container x y nil nil)))
+          (if (exwm-workspace--minibuffer-own-frame-p)
+              (if (eq frame exwm-workspace--current)
+                  (select-window (frame-root-window exwm--floating-frame))
+                (select-window (frame-selected-window exwm-workspace--current))
+                (unless (exwm-workspace--active-p frame)
+                  (exwm-layout--hide id)))
+            ;; The frame needs to be recreated since it won't use the
+            ;; minibuffer on the new workspace.
+            ;; The code is mostly copied from `exwm-floating--set-floating'.
+            (let* ((old-frame exwm--floating-frame)
+                   (new-frame
+                    (with-current-buffer
+                        (or (get-buffer "*scratch*")
+                            (progn
+                              (set-buffer-major-mode
+                               (get-buffer-create "*scratch*"))
+                              (get-buffer "*scratch*")))
+                      (make-frame
+                       `((minibuffer . ,(minibuffer-window frame))
+                         (left . ,(* window-min-width -100))
+                         (top . ,(* window-min-height -100))
+                         (width . ,window-min-width)
+                         (height . ,window-min-height)
+                         (unsplittable . t)))))
+                   (outer-id (string-to-number
+                              (frame-parameter new-frame
+                                               'outer-window-id)))
+                   (window-id (string-to-number
+                               (frame-parameter new-frame 'window-id)))
+                   (window (frame-root-window new-frame)))
+              (set-frame-parameter new-frame 'exwm-outer-id outer-id)
+              (set-frame-parameter new-frame 'exwm-id window-id)
+              (set-frame-parameter new-frame 'exwm-container container)
+              (make-frame-invisible new-frame)
+              (set-frame-size new-frame
+                              (frame-pixel-width old-frame)
+                              (frame-pixel-height old-frame)
+                              t)
+              (xcb:+request exwm--connection
+                  (make-instance 'xcb:ReparentWindow
+                                 :window outer-id
+                                 :parent container
+                                 :x 0 :y 0))
+              (xcb:flush exwm--connection)
+              (with-current-buffer (exwm--id->buffer id)
+                (setq window-size-fixed nil
+                      exwm--floating-frame new-frame)
+                (set-window-dedicated-p (frame-root-window old-frame) nil)
+                (remove-hook 'window-configuration-change-hook
+                             #'exwm-layout--refresh)
+                (set-window-buffer window (current-buffer))
+                (add-hook 'window-configuration-change-hook
+                          #'exwm-layout--refresh)
+                (set-window-dedicated-p window t))
+              ;; Select a tiling window and delete the old frame.
+              (select-window (frame-selected-window exwm-workspace--current))
+              (delete-frame old-frame)
+              ;; The rest is the same.
+              (make-frame-visible new-frame)
+              (exwm--set-geometry outer-id 0 0 nil nil)
+              (xcb:flush exwm--connection)
+              (redisplay)
+              (if (eq frame exwm-workspace--current)
+                  (with-current-buffer (exwm--id->buffer id)
+                    (select-window (frame-root-window exwm--floating-frame)))
+                (unless (exwm-workspace--active-p frame)
+                  (exwm-layout--hide id)))))
+          ;; Update the 'exwm-selected-window' frame parameter.
+          (when (not (eq frame exwm-workspace--current))
+            (with-current-buffer (exwm--id->buffer id)
+              (set-frame-parameter frame 'exwm-selected-window
+                                   (frame-root-window
+                                    exwm--floating-frame)))))
+        ;; Set _NET_WM_DESKTOP.
+        (exwm-workspace--set-desktop id)
+        (xcb:flush exwm--connection)))
+    (setq exwm-workspace--switch-history-outdated t)))
+
+;;;###autoload
+(defun exwm-workspace-switch-to-buffer (buffer-or-name)
+  "Make selected window display BUFFER-OR-NAME."
+  (interactive
+   (let ((inhibit-quit t))
+     ;; Show all buffers
+     (unless exwm-workspace-show-all-buffers
+       (dolist (pair exwm--id-buffer-alist)
+         (with-current-buffer (cdr pair)
+           (when (= ?\s (aref (buffer-name) 0))
+             (let ((buffer-list-update-hook
+                    (remq #'exwm-input--on-buffer-list-update
+                          buffer-list-update-hook)))
+               (rename-buffer (substring (buffer-name) 1)))))))
+     (prog1
+         (with-local-quit
+           (list (get-buffer (read-buffer-to-switch "Switch to buffer: "))))
+       ;; Hide buffers on other workspaces
+       (unless exwm-workspace-show-all-buffers
+         (dolist (pair exwm--id-buffer-alist)
+           (with-current-buffer (cdr pair)
+             (unless (or (eq exwm--frame exwm-workspace--current)
+                         (= ?\s (aref (buffer-name) 0)))
+               (let ((buffer-list-update-hook
+                      (remq #'exwm-input--on-buffer-list-update
+                            buffer-list-update-hook)))
+                 (rename-buffer (concat " " (buffer-name)))))))))))
+  (exwm--log)
+  (when buffer-or-name
+    (with-current-buffer buffer-or-name
+      (if (derived-mode-p 'exwm-mode)
+          ;; EXWM buffer.
+          (if (eq exwm--frame exwm-workspace--current)
+              ;; On the current workspace.
+              (if (not exwm--floating-frame)
+                  (switch-to-buffer buffer-or-name)
+                ;; Select the floating frame.
+                (select-frame-set-input-focus exwm--floating-frame)
+                (select-window (frame-root-window exwm--floating-frame)))
+            ;; On another workspace.
+            (if exwm-layout-show-all-buffers
+                (exwm-workspace-move-window exwm-workspace--current
+                                            exwm--id)
+              (let ((window (get-buffer-window buffer-or-name exwm--frame)))
+                (if window
+                    (set-frame-parameter exwm--frame
+                                         'exwm-selected-window window)
+                  (set-window-buffer (frame-selected-window exwm--frame)
+                                     buffer-or-name)))
+              (exwm-workspace-switch exwm--frame)))
+        ;; Ordinary buffer.
+        (switch-to-buffer buffer-or-name)))))
+
+(defun exwm-workspace-rename-buffer (newname)
+  "Rename current buffer to NEWNAME."
+  (let ((hidden (= ?\s (aref newname 0)))
+        (basename (replace-regexp-in-string "<[0-9]+>$" "" newname))
+        (counter 1)
+        tmp)
+    (when hidden (setq basename (substring basename 1)))
+    (setq newname basename)
+    (while (and (setq tmp (or (get-buffer newname)
+                              (get-buffer (concat " " newname))))
+                (not (eq tmp (current-buffer))))
+      (setq newname (format "%s<%d>" basename (cl-incf counter))))
+    (let ((buffer-list-update-hook
+           (remq #'exwm-input--on-buffer-list-update
+                 buffer-list-update-hook)))
+      (rename-buffer (concat (and hidden " ") newname)))))
+
+(defun exwm-workspace--x-create-frame (orig-x-create-frame params)
+  "Set override-redirect on the frame created by `x-create-frame'.
+ORIG-X-CREATE-FRAME is the advised function `x-create-frame'.
+PARAMS are the original arguments."
+  (exwm--log)
+  (let ((frame (funcall orig-x-create-frame params)))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ChangeWindowAttributes
+                       :window (string-to-number
+                                (frame-parameter frame 'outer-window-id))
+                       :value-mask xcb:CW:OverrideRedirect
+                       :override-redirect 1))
+    (xcb:flush exwm--connection)
+    frame))
+
+(defsubst exwm-workspace--minibuffer-attached-p ()
+  "Return non-nil if the minibuffer is attached.
+
+Please check `exwm-workspace--minibuffer-own-frame-p' first."
+  (assq (frame-parameter exwm-workspace--minibuffer 'exwm-container)
+        exwm-workspace--id-struts-alist))
+
+;;;###autoload
+(defun exwm-workspace-attach-minibuffer ()
+  "Attach the minibuffer making it always visible."
+  (interactive)
+  (exwm--log)
+  (when (and (exwm-workspace--minibuffer-own-frame-p)
+             (not (exwm-workspace--minibuffer-attached-p)))
+    ;; Reset the frame size.
+    (set-frame-height exwm-workspace--minibuffer 1)
+    (redisplay)                       ;FIXME.
+    (setq exwm-workspace--attached-minibuffer-height
+          (frame-pixel-height exwm-workspace--minibuffer))
+    (exwm-workspace--show-minibuffer)
+    (let ((container (frame-parameter exwm-workspace--minibuffer
+                                      'exwm-container)))
+      (push (cons container
+                  (if (eq exwm-workspace-minibuffer-position 'top)
+                      (vector 0 0 exwm-workspace--attached-minibuffer-height 0)
+                    (vector 0 0 0 exwm-workspace--attached-minibuffer-height)))
+            exwm-workspace--id-struts-alist)
+      (exwm-workspace--update-struts)
+      (exwm-workspace--update-workareas)
+      (dolist (f exwm-workspace--list)
+        (exwm-workspace--set-fullscreen f)))))
+
+;;;###autoload
+(defun exwm-workspace-detach-minibuffer ()
+  "Detach the minibuffer so that it automatically hides."
+  (interactive)
+  (exwm--log)
+  (when (and (exwm-workspace--minibuffer-own-frame-p)
+             (exwm-workspace--minibuffer-attached-p))
+    (setq exwm-workspace--attached-minibuffer-height 0)
+    (let ((container (frame-parameter exwm-workspace--minibuffer
+                                      'exwm-container)))
+      (setq exwm-workspace--id-struts-alist
+            (assq-delete-all container exwm-workspace--id-struts-alist))
+      (exwm-workspace--update-struts)
+      (exwm-workspace--update-workareas)
+      (dolist (f exwm-workspace--list)
+        (exwm-workspace--set-fullscreen f))
+      (exwm-workspace--hide-minibuffer))))
+
+;;;###autoload
+(defun exwm-workspace-toggle-minibuffer ()
+  "Attach the minibuffer if it's detached, or detach it if it's attached."
+  (interactive)
+  (exwm--log)
+  (when (exwm-workspace--minibuffer-own-frame-p)
+    (if (exwm-workspace--minibuffer-attached-p)
+        (exwm-workspace-detach-minibuffer)
+      (exwm-workspace-attach-minibuffer))))
+
+(defun exwm-workspace--update-minibuffer-height (&optional echo-area)
+  "Update the minibuffer frame height.
+When ECHO-AREA is non-nil, take the size of the echo area into
+account when calculating the height."
+  (when (exwm--terminal-p)
+    (let ((height
+           (with-current-buffer
+               (window-buffer (minibuffer-window exwm-workspace--minibuffer))
+             (max 1
+                  (if echo-area
+                      (let ((width (frame-width exwm-workspace--minibuffer))
+                            (result 0))
+                        (mapc (lambda (i)
+                                (setq result
+                                      (+ result
+                                         (ceiling (1+ (length i)) width))))
+                              (split-string (or (current-message) "") "\n"))
+                        result)
+                    (count-screen-lines))))))
+      (when (and (integerp max-mini-window-height)
+                 (> height max-mini-window-height))
+        (setq height max-mini-window-height))
+      (exwm--log "%s" height)
+      (set-frame-height exwm-workspace--minibuffer height))))
+
+(defun exwm-workspace--on-ConfigureNotify (data _synthetic)
+  "Adjust the container to fit the minibuffer frame.
+DATA contains unmarshalled ConfigureNotify event data."
+  (let ((obj (make-instance 'xcb:ConfigureNotify)) y)
+    (xcb:unmarshal obj data)
+    (with-slots (window height) obj
+      (when (eq (frame-parameter exwm-workspace--minibuffer 'exwm-outer-id)
+                window)
+        (exwm--log)
+        (when (and (floatp max-mini-window-height)
+                   (> height (* max-mini-window-height
+                                (exwm-workspace--current-height))))
+          (setq height (floor
+                        (* max-mini-window-height
+                           (exwm-workspace--current-height))))
+          (xcb:+request exwm--connection
+              (make-instance 'xcb:ConfigureWindow
+                             :window window
+                             :value-mask xcb:ConfigWindow:Height
+                             :height height)))
+        (when (/= (exwm-workspace--count) (length exwm-workspace--workareas))
+          ;; There is a chance the workareas are not updated timely.
+          (exwm-workspace--update-workareas))
+        (with-slots ((y* y) (height* height))
+            (exwm-workspace--workarea exwm-workspace-current-index)
+          (setq y (if (eq exwm-workspace-minibuffer-position 'top)
+                      (- y*
+                         exwm-workspace--attached-minibuffer-height)
+                    (+ y* height* (- height)
+                       exwm-workspace--attached-minibuffer-height))))
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:ConfigureWindow
+                           :window (frame-parameter exwm-workspace--minibuffer
+                                                    'exwm-container)
+                           :value-mask (logior xcb:ConfigWindow:Y
+                                               xcb:ConfigWindow:Height)
+                           :y y
+                           :height height))
+        (xcb:flush exwm--connection)))))
+
+(defun exwm-workspace--display-buffer (buffer alist)
+  "Display BUFFER as if the current workspace were selected.
+ALIST is an action alist, as accepted by function `display-buffer'."
+  ;; Only when the floating minibuffer frame is selected.
+  ;; This also protect this functions from being recursively called.
+  (when (eq (selected-frame) exwm-workspace--minibuffer)
+    (with-selected-frame exwm-workspace--current
+      (display-buffer buffer alist))))
+
+(defun exwm-workspace--show-minibuffer ()
+  "Show the minibuffer frame."
+  (exwm--log)
+  ;; Cancel pending timer.
+  (when exwm-workspace--display-echo-area-timer
+    (cancel-timer exwm-workspace--display-echo-area-timer)
+    (setq exwm-workspace--display-echo-area-timer nil))
+  ;; Show the minibuffer frame.
+  (unless (exwm-workspace--minibuffer-attached-p)
+    (exwm--set-geometry (frame-parameter exwm-workspace--minibuffer
+                                         'exwm-container)
+                        nil nil
+                        (frame-pixel-width exwm-workspace--minibuffer)
+                        (frame-pixel-height exwm-workspace--minibuffer)))
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ConfigureWindow
+                     :window (frame-parameter exwm-workspace--minibuffer
+                                              'exwm-container)
+                     :value-mask xcb:ConfigWindow:StackMode
+                     :stack-mode xcb:StackMode:Above))
+  (xcb:flush exwm--connection))
+
+(defun exwm-workspace--hide-minibuffer ()
+  "Hide the minibuffer frame."
+  (exwm--log)
+  ;; Hide the minibuffer frame.
+  (if (exwm-workspace--minibuffer-attached-p)
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ConfigureWindow
+                         :window (frame-parameter exwm-workspace--minibuffer
+                                                  'exwm-container)
+                         :value-mask (logior (if exwm-manage--desktop
+                                                 xcb:ConfigWindow:Sibling
+                                               0)
+                                             xcb:ConfigWindow:StackMode)
+                         :sibling exwm-manage--desktop
+                         :stack-mode (if exwm-manage--desktop
+                                         xcb:StackMode:Above
+                                       xcb:StackMode:Below)))
+    (exwm--set-geometry (frame-parameter exwm-workspace--minibuffer
+                                         'exwm-container)
+                        nil nil 1 1))
+  (xcb:flush exwm--connection))
+
+(defun exwm-workspace--on-minibuffer-setup ()
+  "Run in `minibuffer-setup-hook' to show the minibuffer and its container."
+  (exwm--log)
+  (when (and (= 1 (minibuffer-depth))
+             (exwm--terminal-p))
+    (add-hook 'post-command-hook #'exwm-workspace--update-minibuffer-height)
+    (exwm-workspace--show-minibuffer))
+  ;; FIXME: This is a temporary fix for the *Completions* buffer not
+  ;;        being correctly fitted by its displaying window.  As with
+  ;;        `exwm-workspace--display-buffer', the problem is caused by
+  ;;        the fact that the minibuffer (rather than the workspace)
+  ;;        frame is the 'selected frame'.  `get-buffer-window' will
+  ;;        fail to retrieve the correct window.  It's likely there are
+  ;;        other related issues.
+  ;; This is not required by Emacs 24.
+  (when (fboundp 'window-preserve-size)
+    (let ((window (get-buffer-window "*Completions*"
+                                     exwm-workspace--current)))
+      (when window
+        (fit-window-to-buffer window)
+        (window-preserve-size window)))))
+
+(defun exwm-workspace--on-minibuffer-exit ()
+  "Run in `minibuffer-exit-hook' to hide the minibuffer container."
+  (exwm--log)
+  (when (and (= 1 (minibuffer-depth))
+             (exwm--terminal-p))
+    (remove-hook 'post-command-hook #'exwm-workspace--update-minibuffer-height)
+    (exwm-workspace--hide-minibuffer)))
+
+(defun exwm-workspace--on-echo-area-dirty ()
+  "Run when new message arrives to show the echo area and its container."
+  (when (and (not (active-minibuffer-window))
+             (or (current-message)
+                 cursor-in-echo-area)
+             (exwm--terminal-p))
+    (exwm-workspace--update-minibuffer-height t)
+    (exwm-workspace--show-minibuffer)
+    (unless (or (not exwm-workspace-display-echo-area-timeout)
+                exwm-input--during-command ;e.g. read-event
+                input-method-use-echo-area)
+      (setq exwm-workspace--display-echo-area-timer
+            (run-with-timer exwm-workspace-display-echo-area-timeout nil
+                            #'exwm-workspace--echo-area-maybe-clear)))))
+
+(defun exwm-workspace--echo-area-maybe-clear ()
+  "Eventually clear the echo area container."
+  (exwm--log)
+  (if (not (current-message))
+      (exwm-workspace--on-echo-area-clear)
+    ;; Reschedule.
+    (cancel-timer exwm-workspace--display-echo-area-timer)
+    (setq exwm-workspace--display-echo-area-timer
+          (run-with-timer exwm-workspace-display-echo-area-timeout nil
+                          #'exwm-workspace--echo-area-maybe-clear))))
+
+(defun exwm-workspace--on-echo-area-clear ()
+  "Run in `echo-area-clear-hook' to hide echo area container."
+  (when (exwm--terminal-p)
+    (unless (active-minibuffer-window)
+      (exwm-workspace--hide-minibuffer))
+    (when exwm-workspace--display-echo-area-timer
+      (cancel-timer exwm-workspace--display-echo-area-timer)
+      (setq exwm-workspace--display-echo-area-timer nil))))
+
+(defun exwm-workspace--set-desktop-geometry ()
+  "Set _NET_DESKTOP_GEOMETRY."
+  (exwm--log)
+  ;; We don't support large desktop so it's the same with screen size.
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ewmh:set-_NET_DESKTOP_GEOMETRY
+                     :window exwm--root
+                     :width (x-display-pixel-width)
+                     :height (x-display-pixel-height))))
+
+(defun exwm-workspace--add-frame-as-workspace (frame)
+  "Configure frame FRAME to be treated as a workspace."
+  (exwm--log "%s" frame)
+  (setq exwm-workspace--list (nconc exwm-workspace--list (list frame)))
+  (let ((outer-id (string-to-number (frame-parameter frame
+                                                     'outer-window-id)))
+        (window-id (string-to-number (frame-parameter frame 'window-id)))
+        (container (xcb:generate-id exwm--connection))
+        frame-colormap frame-visual frame-depth)
+    ;; Save window IDs
+    (set-frame-parameter frame 'exwm-outer-id outer-id)
+    (set-frame-parameter frame 'exwm-id window-id)
+    (set-frame-parameter frame 'exwm-container container)
+    ;; Copy RandR frame parameters from the first workspace to
+    ;; prevent potential problems.  The values do not matter here as
+    ;; they'll be updated by the RandR module later.
+    (let ((w (car exwm-workspace--list)))
+      (dolist (param '(exwm-randr-monitor
+                       exwm-geometry))
+        (set-frame-parameter frame param (frame-parameter w param))))
+    ;; Support transparency on the container X window when the Emacs frame
+    ;; does.  Note that in addition to setting the visual, colormap and depth
+    ;; we must also reset the `:border-pixmap', as its default value is
+    ;; relative to the parent window, which might have a different depth.
+    (let* ((vdc (exwm--get-visual-depth-colormap exwm--connection outer-id)))
+      (setq frame-visual (car vdc))
+      (setq frame-depth (cadr vdc))
+      (setq frame-colormap (caddr vdc)))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:CreateWindow
+                       :depth frame-depth
+                       :wid container
+                       :parent exwm--root
+                       :x -1
+                       :y -1
+                       :width 1
+                       :height 1
+                       :border-width 0
+                       :class xcb:WindowClass:InputOutput
+                       :visual frame-visual
+                       :value-mask (logior xcb:CW:BackPixmap
+                                           xcb:CW:BorderPixel
+                                           xcb:CW:Colormap
+                                           xcb:CW:OverrideRedirect)
+                       :background-pixmap xcb:BackPixmap:None
+                       :border-pixel 0
+                       :colormap frame-colormap
+                       :override-redirect 1))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ConfigureWindow
+                       :window container
+                       :value-mask xcb:ConfigWindow:StackMode
+                       :stack-mode xcb:StackMode:Below))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ewmh:set-_NET_WM_NAME
+                       :window container
+                       :data
+                       (format "EXWM workspace %d frame container"
+                               (exwm-workspace--position frame))))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ReparentWindow
+                       :window outer-id :parent container :x 0 :y 0))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:icccm:set-WM_STATE
+                       :window outer-id
+                       :state xcb:icccm:WM_STATE:NormalState
+                       :icon xcb:Window:None))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:MapWindow :window container)))
+  (xcb:flush exwm--connection)
+  ;; Delay making the workspace fullscreen until Emacs becomes idle
+  (exwm--defer 0 #'exwm-workspace--fullscreen-workspace frame)
+  ;; Update EWMH properties.
+  (exwm-workspace--update-ewmh-props)
+  (if exwm-workspace--create-silently
+      (setq exwm-workspace--switch-history-outdated t)
+    (let ((original-index exwm-workspace-current-index))
+      (exwm-workspace-switch frame t)
+      (message "Created %s as workspace %d; switched from %d"
+               frame exwm-workspace-current-index original-index))
+    (run-hooks 'exwm-workspace-list-change-hook)))
+
+(defun exwm-workspace--get-next-workspace (frame)
+  "Return the next workspace if workspace FRAME were removed.
+Return nil if FRAME is the only workspace."
+  (let* ((index (exwm-workspace--position frame))
+         (lastp (= index (1- (exwm-workspace--count))))
+         (nextw (elt exwm-workspace--list (+ index (if lastp -1 +1)))))
+    (unless (eq frame nextw)
+      nextw)))
+
+(defun exwm-workspace--remove-frame-as-workspace (frame &optional quit)
+  "Stop treating FRAME as a workspace.
+When QUIT is non-nil cleanup avoid communicating with the X server."
+  ;; TODO: restore all frame parameters (e.g. exwm-workspace, buffer-predicate,
+  ;; etc)
+  (exwm--log "Removing frame `%s' as workspace" frame)
+  (unless quit
+    (let* ((next-frame (exwm-workspace--get-next-workspace frame))
+           (following-frames (cdr (memq frame exwm-workspace--list))))
+      ;; Need to remove the workspace from the list for the correct calculation of
+      ;; indexes below.
+      (setq exwm-workspace--list (delete frame exwm-workspace--list))
+      ;; Move the windows to the next workspace and switch to it.
+      (unless next-frame
+        ;; The user managed to delete the last workspace, so create a new one.
+        (exwm--log "Last workspace deleted; create a new one")
+        (let ((exwm-workspace--create-silently t))
+          (setq next-frame (make-frame))))
+      (dolist (pair exwm--id-buffer-alist)
+        (let ((other-frame (buffer-local-value 'exwm--frame (cdr pair))))
+          ;; Move X windows to next-frame.
+          (when (eq other-frame frame)
+            (exwm-workspace-move-window next-frame (car pair)))
+          ;; Update the _NET_WM_DESKTOP property of each following X window.
+          (when (memq other-frame following-frames)
+            (exwm-workspace--set-desktop (car pair)))))
+      ;; If the current workspace is deleted, switch to next one.
+      (when (eq frame exwm-workspace--current)
+        (exwm-workspace-switch next-frame))))
+  ;; Reparent out the frame.
+  (let ((outer-id (frame-parameter frame 'exwm-outer-id)))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:UnmapWindow
+                       :window outer-id))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ReparentWindow
+                       :window outer-id
+                       :parent exwm--root
+                       :x 0
+                       :y 0))
+    ;; Reset the override-redirect.
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ChangeWindowAttributes
+                       :window outer-id
+                       :value-mask xcb:CW:OverrideRedirect
+                       :override-redirect 0))
+    ;; Remove fullscreen state.
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ewmh:set-_NET_WM_STATE
+                       :window outer-id
+                       :data nil))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:MapWindow
+                       :window outer-id)))
+  ;; Destroy the container.
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:DestroyWindow
+                     :window (frame-parameter frame 'exwm-container)))
+  (xcb:flush exwm--connection)
+  ;; Update EWMH properties.
+  (exwm-workspace--update-ewmh-props)
+  ;; Update switch history.
+  (unless quit
+    (setq exwm-workspace--switch-history-outdated t)
+    (run-hooks 'exwm-workspace-list-change-hook)))
+
+(defun exwm-workspace--on-delete-frame (frame)
+  "Hook run upon `delete-frame' removing FRAME as a workspace."
+  (cond
+   ((not (exwm-workspace--workspace-p frame))
+    (exwm--log "Frame `%s' is not a workspace" frame))
+   (t
+    (exwm-workspace--remove-frame-as-workspace frame))))
+
+(defun exwm-workspace--fullscreen-workspace (frame)
+  "Make workspace FRAME fullscreen.
+Called from a timer."
+  (when (frame-live-p frame)
+    (set-frame-parameter frame 'fullscreen 'fullboth)))
+
+(defun exwm-workspace--on-after-make-frame (frame)
+  "Hook run upon `make-frame' that configures FRAME as a workspace."
+  (cond
+   ((exwm-workspace--workspace-p frame)
+    (exwm--log "Frame `%s' is already a workspace" frame))
+   ((not (display-graphic-p frame))
+    (exwm--log "Frame `%s' is not graphical" frame))
+   ((not (eq (frame-terminal) exwm--terminal))
+    (exwm--log "Frame `%s' is on a different terminal (%S instead of %S)"
+               frame
+               (frame-terminal frame)
+               exwm--terminal))
+   ((not (string-equal
+          (replace-regexp-in-string "\\.0$" ""
+                                    (slot-value exwm--connection 'display))
+          (replace-regexp-in-string "\\.0$" ""
+                                    (frame-parameter frame 'display))))
+    (exwm--log "Frame `%s' is on a different DISPLAY (%S instead of %S)"
+               frame
+               (frame-parameter frame 'display)
+               (slot-value exwm--connection 'display)))
+   ((frame-parameter frame 'unsplittable)
+    ;; We create floating frames with the "unsplittable" parameter set.
+    ;; Though it may not be a floating frame, we won't treat an
+    ;; unsplittable frame as a workspace anyway.
+    (exwm--log "Frame `%s' is floating" frame))
+   (t
+    (exwm--log "Adding frame `%s' as workspace" frame)
+    (exwm-workspace--add-frame-as-workspace frame))))
+
+(defun exwm-workspace--update-ewmh-props ()
+  "Update EWMH properties to match the workspace list."
+  (exwm--log)
+  (let ((num-workspaces (exwm-workspace--count)))
+    ;; Avoid setting 0 desktops.
+    (when (= 0 num-workspaces)
+      (setq num-workspaces 1))
+    ;; Set _NET_NUMBER_OF_DESKTOPS.
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ewmh:set-_NET_NUMBER_OF_DESKTOPS
+                       :window exwm--root :data num-workspaces))
+    ;; Set _NET_DESKTOP_GEOMETRY.
+    (exwm-workspace--set-desktop-geometry)
+    ;; Update workareas.
+    (exwm-workspace--update-workareas))
+  (xcb:flush exwm--connection))
+
+(defun exwm-workspace--modify-all-x-frames-parameters (new-x-parameters)
+  "Modifies `window-system-default-frame-alist' for the X Window System.
+NEW-X-PARAMETERS is an alist of frame parameters, merged into current
+`window-system-default-frame-alist' for the X Window System.  The parameters are
+applied to all subsequently created X frames."
+  (exwm--log)
+  ;; The parameters are modified in place; take current
+  ;; ones or insert a new X-specific list.
+  (let ((x-parameters (or (assq 'x window-system-default-frame-alist)
+                          (let ((new-x-parameters '(x)))
+                            (push new-x-parameters
+                                  window-system-default-frame-alist)
+                            new-x-parameters))))
+    (setf (cdr x-parameters)
+          (append new-x-parameters (cdr x-parameters)))))
+
+(defun exwm-workspace--handle-focus-in (_orig-func _event)
+  "Replacement for `handle-focus-in'."
+  (interactive "e"))
+
+(defun exwm-workspace--handle-focus-out (_orig-func _event)
+  "Replacement for `handle-focus-out'."
+  (interactive "e"))
+
+(defun exwm-workspace--init-minibuffer-frame ()
+  "Initialize minibuffer-only frame."
+  (exwm--log)
+  ;; Initialize workspaces without minibuffers.
+  (setq exwm-workspace--minibuffer
+        (make-frame '((window-system . x) (minibuffer . only)
+                      (left . 10000) (right . 10000)
+                      (width . 1) (height . 1))))
+  ;; This is the only usable minibuffer frame.
+  (setq default-minibuffer-frame exwm-workspace--minibuffer)
+  (exwm-workspace--modify-all-x-frames-parameters
+   '((minibuffer . nil)))
+  (let ((outer-id (string-to-number
+                   (frame-parameter exwm-workspace--minibuffer
+                                    'outer-window-id)))
+        (window-id (string-to-number
+                    (frame-parameter exwm-workspace--minibuffer
+                                     'window-id)))
+        (container (xcb:generate-id exwm--connection)))
+    (set-frame-parameter exwm-workspace--minibuffer
+                         'exwm-outer-id outer-id)
+    (set-frame-parameter exwm-workspace--minibuffer 'exwm-id window-id)
+    (set-frame-parameter exwm-workspace--minibuffer 'exwm-container
+                         container)
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:CreateWindow
+                       :depth 0
+                       :wid container
+                       :parent exwm--root
+                       :x 0
+                       :y 0
+                       :width 1
+                       :height 1
+                       :border-width 0
+                       :class xcb:WindowClass:InputOutput
+                       :visual 0
+                       :value-mask (logior xcb:CW:BackPixmap
+                                           xcb:CW:OverrideRedirect)
+                       :background-pixmap xcb:BackPixmap:ParentRelative
+                       :override-redirect 1))
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ewmh:set-_NET_WM_NAME
+                       :window container
+                       :data "EXWM minibuffer container"))
+    ;; Reparent the minibuffer frame to the container.
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ReparentWindow
+                       :window outer-id :parent container :x 0 :y 0))
+    ;; Map the container.
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:MapWindow
+                       :window container))
+    ;; Attach event listener for monitoring the frame
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ChangeWindowAttributes
+                       :window outer-id
+                       :value-mask xcb:CW:EventMask
+                       :event-mask xcb:EventMask:StructureNotify))
+    (xcb:+event exwm--connection 'xcb:ConfigureNotify
+                #'exwm-workspace--on-ConfigureNotify))
+  ;; Show/hide minibuffer / echo area when they're active/inactive.
+  (add-hook 'minibuffer-setup-hook #'exwm-workspace--on-minibuffer-setup)
+  (add-hook 'minibuffer-exit-hook #'exwm-workspace--on-minibuffer-exit)
+  (setq exwm-workspace--timer
+        (run-with-idle-timer 0 t #'exwm-workspace--on-echo-area-dirty))
+  (add-hook 'echo-area-clear-hook #'exwm-workspace--on-echo-area-clear)
+  ;; The default behavior of `display-buffer' (indirectly called by
+  ;; `minibuffer-completion-help') is not correct here.
+  (cl-pushnew '(exwm-workspace--display-buffer) display-buffer-alist
+              :test #'equal))
+
+(defun exwm-workspace--exit-minibuffer-frame ()
+  "Cleanup minibuffer-only frame."
+  (exwm--log)
+  ;; Only on minibuffer-frame.
+  (remove-hook 'minibuffer-setup-hook #'exwm-workspace--on-minibuffer-setup)
+  (remove-hook 'minibuffer-exit-hook #'exwm-workspace--on-minibuffer-exit)
+  (remove-hook 'echo-area-clear-hook #'exwm-workspace--on-echo-area-clear)
+  (when exwm-workspace--display-echo-area-timer
+    (cancel-timer exwm-workspace--display-echo-area-timer))
+  (when exwm-workspace--timer
+    (cancel-timer exwm-workspace--timer)
+    (setq exwm-workspace--timer nil))
+  (setq display-buffer-alist
+        (cl-delete '(exwm-workspace--display-buffer) display-buffer-alist
+                   :test #'equal))
+  (setq default-minibuffer-frame nil)
+  (when (frame-live-p exwm-workspace--minibuffer) ; might be already dead
+    (let ((id (frame-parameter exwm-workspace--minibuffer 'exwm-outer-id)))
+      (when (and exwm-workspace--minibuffer id
+                 ;; Invoked from `exwm-manage--exit' upon disconnection.
+                 (slot-value exwm--connection 'connected))
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:ReparentWindow
+                           :window id
+                           :parent exwm--root
+                           :x 0
+                           :y 0)))
+      (setq exwm-workspace--minibuffer nil))))
+
+(defun exwm-workspace--init ()
+  "Initialize workspace module."
+  (exwm--log)
+  (exwm-workspace--init-switch-map)
+  ;; Prevent unexpected exit
+  (setq exwm-workspace--fullscreen-frame-count 0)
+  (exwm-workspace--modify-all-x-frames-parameters
+   '((internal-border-width . 0)))
+  (let ((initial-workspaces (frame-list)))
+    (if (not (exwm-workspace--minibuffer-own-frame-p))
+        ;; Initialize workspaces with minibuffers.
+        (when (< 1 (length initial-workspaces))
+          ;; Exclude the initial frame.
+          (dolist (i initial-workspaces)
+            (unless (frame-parameter i 'window-id)
+              (setq initial-workspaces (delq i initial-workspaces))))
+          (let ((f (car initial-workspaces)))
+            ;; Remove the possible internal border.
+            (set-frame-parameter f 'internal-border-width 0)))
+      (exwm-workspace--init-minibuffer-frame)
+      ;; Remove/hide existing frames.
+      (dolist (f initial-workspaces)
+        (when (eq 'x (framep f))        ;do not delete the initial frame.
+          (delete-frame f)))
+      ;; Recreate one frame with the external minibuffer set.
+      (setq initial-workspaces (list (make-frame '((window-system . x))))))
+    ;; Prevent `other-buffer' from selecting already displayed EXWM buffers.
+    (modify-all-frames-parameters
+     '((buffer-predicate . exwm-layout--other-buffer-predicate)))
+    ;; Create remaining workspaces.
+    (dotimes (_ (- exwm-workspace-number (length initial-workspaces)))
+      (nconc initial-workspaces (list (make-frame '((window-system . x))))))
+    ;; Configure workspaces
+    (let ((exwm-workspace--create-silently t))
+      (dolist (i initial-workspaces)
+        (exwm-workspace--add-frame-as-workspace i))))
+  (xcb:flush exwm--connection)
+  ;; We have to advice `x-create-frame' or every call to it would hang EXWM
+  (advice-add 'x-create-frame :around #'exwm-workspace--x-create-frame)
+  ;; We have to manually handle focus-in and focus-out events for Emacs
+  ;; frames.
+  (advice-add 'handle-focus-in :around #'exwm-workspace--handle-focus-in)
+  (advice-add 'handle-focus-out :around #'exwm-workspace--handle-focus-out)
+  ;; Make new frames create new workspaces.
+  (add-hook 'after-make-frame-functions
+            #'exwm-workspace--on-after-make-frame)
+  (add-hook 'delete-frame-functions #'exwm-workspace--on-delete-frame)
+  (when (exwm-workspace--minibuffer-own-frame-p)
+    (add-hook 'exwm-input--event-hook
+              #'exwm-workspace--on-echo-area-clear))
+  ;; Switch to the first workspace
+  (exwm-workspace-switch 0 t)
+  ;; Prevent frame parameters introduced by this module from being
+  ;; saved/restored.
+  (dolist (i '(exwm-active exwm-outer-id exwm-id exwm-container exwm-geometry
+                           exwm-selected-window exwm-urgency fullscreen))
+    (unless (assq i frameset-filter-alist)
+      (push (cons i :never) frameset-filter-alist))))
+
+(defun exwm-workspace--exit ()
+  "Exit the workspace module."
+  (exwm--log)
+  (when (exwm-workspace--minibuffer-own-frame-p)
+    (exwm-workspace--exit-minibuffer-frame))
+  (advice-remove 'x-create-frame #'exwm-workspace--x-create-frame)
+  (advice-remove 'handle-focus-in #'exwm-workspace--handle-focus-in)
+  (advice-remove 'handle-focus-out #'exwm-workspace--handle-focus-out)
+  (remove-hook 'after-make-frame-functions
+               #'exwm-workspace--on-after-make-frame)
+  (remove-hook 'delete-frame-functions
+               #'exwm-workspace--on-delete-frame)
+  (when (exwm-workspace--minibuffer-own-frame-p)
+    (remove-hook 'exwm-input--event-hook
+                 #'exwm-workspace--on-echo-area-clear))
+  ;; Hide & reparent out all frames (save-set can't be used here since
+  ;; X windows will be re-mapped).
+  (when (slot-value exwm--connection 'connected)
+    (dolist (i exwm-workspace--list)
+      (when (frame-live-p i)                    ; might be already dead
+        (exwm-workspace--remove-frame-as-workspace i 'quit)
+        (modify-frame-parameters i '((exwm-selected-window . nil)
+                                     (exwm-urgency . nil)
+                                     (exwm-outer-id . nil)
+                                     (exwm-id . nil)
+                                     (exwm-container . nil)
+                                     ;; (internal-border-width . nil) ; integerp
+                                     (fullscreen . nil)
+                                     (buffer-predicate . nil))))))
+  ;; Don't let dead frames linger.
+  (setq exwm-workspace--current nil)
+  (setq exwm-workspace-current-index 0)
+  (setq exwm-workspace--list nil))
+
+(defun exwm-workspace--post-init ()
+  "The second stage in the initialization of the workspace module."
+  (exwm--log)
+  ;; Wait until all workspace frames are resized.
+  (with-timeout (1)
+    (while (< exwm-workspace--fullscreen-frame-count (exwm-workspace--count))
+      (accept-process-output nil 0.1)))
+  (setq exwm-workspace--fullscreen-frame-count nil))
+
+
+
+(provide 'exwm-workspace)
+
+;;; exwm-workspace.el ends here
diff --git a/exwm-xim.el b/exwm-xim.el
new file mode 100644
index 0000000000..1f0c9c460b
--- /dev/null
+++ b/exwm-xim.el
@@ -0,0 +1,810 @@
+;;; exwm-xim.el --- XIM Module for EXWM  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2019-2024 Free Software Foundation, Inc.
+
+;; Author: Chris Feng <chris.w.feng@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This module adds XIM support for EXWM and allows sending characters
+;; generated by any Emacs's builtin input method (info node `Input Methods')
+;; to X windows.
+
+;; This module is essentially an X input method server utilizing Emacs as
+;; its backend.  It talks with X windows through the XIM protocol.  The XIM
+;; protocol is quite flexible by itself, stating that an implementation can
+;; create network connections of various types as well as make use of an
+;; existing X connection for communication, and that an IM server may
+;; support multiple transport versions, various input styles and several
+;; event flow modals, etc.  Here we only make choices that are most popular
+;; among other IM servers and more importantly, practical for Emacs to act
+;; as an IM server:
+;;
+;; + Packets are transported on top of an X connection like most IMEs.
+;; + Only transport version 0.0 (i.e. only-CM & Property-with-CM) is
+;;   supported (same as "IM Server Developers Kit", adopted by most IMEs).
+;; + Only support static event flow, on-demand-synchronous method.
+;; + Only "root-window" input style is supported.
+
+;; To use this module, first load and enable it as follows:
+;;
+;;    (require 'exwm-xim)
+;;    (exwm-xim-enable)
+;;
+;; A keybinding for `toggle-input-method' is probably required to turn on &
+;; off an input method (default to `default-input-method').  It's bound to
+;; 'C-\' by default and can be made reachable when working with X windows:
+;;
+;;    (push ?\C-\\ exwm-input-prefix-keys)
+;;
+;; It's also required (and error-prone) to setup environment variables to
+;; make applications actually use this input method.  Typically the
+;; following lines should be inserted into '~/.xinitrc'.
+;;
+;;    export XMODIFIERS=@im=exwm-xim
+;;    export GTK_IM_MODULE=xim
+;;    export QT_IM_MODULE=xim
+;;    export CLUTTER_IM_MODULE=xim
+
+;; References:
+;; + XIM (http://www.x.org/releases/X11R7.6/doc/libX11/specs/XIM/xim.html)
+;; + IMdkit (http://xorg.freedesktop.org/archive/unsupported/lib/IMdkit/)
+;; + UIM (https://github.com/uim/uim)
+
+;;; Code:
+
+(require 'cl-lib)
+
+(require 'xcb-keysyms)
+(require 'xcb-xim)
+
+(require 'exwm-core)
+(require 'exwm-input)
+
+(defconst exwm-xim--locales
+  "@locale=\
+aa,af,ak,am,an,anp,ar,as,ast,ayc,az,be,bem,ber,bg,bhb,bho,bn,bo,br,brx,bs,byn,\
+ca,ce,cmn,crh,cs,csb,cv,cy,da,de,doi,dv,dz,el,en,es,et,eu,fa,ff,fi,fil,fo,fr,\
+fur,fy,ga,gd,gez,gl,gu,gv,ha,hak,he,hi,hne,hr,hsb,ht,hu,hy,ia,id,ig,ik,is,it,\
+iu,iw,ja,ka,kk,kl,km,kn,ko,kok,ks,ku,kw,ky,lb,lg,li,li,lij,lo,lt,lv,lzh,mag,\
+mai,mg,mhr,mi,mk,ml,mn,mni,mr,ms,mt,my,nan,nb,nds,ne,nhn,niu,nl,nn,nr,nso,oc,\
+om,or,os,pa,pa,pap,pl,ps,pt,quz,raj,ro,ru,rw,sa,sat,sc,sd,se,shs,si,sid,sk,sl,\
+so,sq,sr,ss,st,sv,sw,szl,ta,tcy,te,tg,th,the,ti,tig,tk,tl,tn,tr,ts,tt,ug,uk,\
+unm,ur,uz,ve,vi,wa,wae,wal,wo,xh,yi,yo,yue,zh,zu,\
+C,no"
+  "All supported locales (stolen from glibc).")
+
+(defconst exwm-xim--default-error
+  (make-instance 'xim:error
+                 :im-id 0
+                 :ic-id 0
+                 :flag xim:error-flag:invalid-both
+                 :error-code xim:error-code:bad-something
+                 :length 0
+                 :type 0
+                 :detail nil)
+  "Default error returned to clients.")
+
+(defconst exwm-xim--default-im-attrs
+  (list (make-instance 'xim:XIMATTR
+                       :id 0
+                       :type xim:ATTRIBUTE-VALUE-TYPE:xim-styles
+                       :length (length xlib:XNQueryInputStyle)
+                       :attribute xlib:XNQueryInputStyle))
+  "Default IM attrs returned to clients.")
+
+(defconst exwm-xim--default-ic-attrs
+  (list (make-instance 'xim:XICATTR
+                       :id 0
+                       :type xim:ATTRIBUTE-VALUE-TYPE:long-data
+                       :length (length xlib:XNInputStyle)
+                       :attribute xlib:XNInputStyle)
+        (make-instance 'xim:XICATTR
+                       :id 1
+                       :type xim:ATTRIBUTE-VALUE-TYPE:window
+                       :length (length xlib:XNClientWindow)
+                       :attribute xlib:XNClientWindow)
+        ;; Required by e.g. xterm.
+        (make-instance 'xim:XICATTR
+                       :id 2
+                       :type xim:ATTRIBUTE-VALUE-TYPE:window
+                       :length (length xlib:XNFocusWindow)
+                       :attribute xlib:XNFocusWindow))
+  "Default IC attrs returned to clients.")
+
+(defconst exwm-xim--default-styles
+  (make-instance 'xim:XIMStyles
+                 :number nil
+                 :styles (list (logior xlib:XIMPreeditNothing
+                                       xlib:XIMStatusNothing)))
+  "Default styles: root-window, i.e. no preediting or status display support.")
+
+(defconst exwm-xim--default-attributes
+  (list (make-instance 'xim:XIMATTRIBUTE
+                       :id 0
+                       :length nil
+                       :value exwm-xim--default-styles))
+  "Default IM/IC attributes returned to clients.")
+
+(defvar exwm-xim--conn nil
+  "The X connection for initiating other XIM connections.")
+(defvar exwm-xim--event-xwin nil
+  "X window for initiating new XIM connections.")
+(defvar exwm-xim--server-client-plist '(nil nil)
+  "Plist mapping server window to [X connection, client window, byte-order].")
+(defvar exwm-xim--client-server-plist '(nil nil)
+  "Plist mapping client window to server window.")
+(defvar exwm-xim--property-index 0 "For generating a unique property name.")
+(defvar exwm-xim--im-id 0 "Last IM ID.")
+(defvar exwm-xim--ic-id 0 "Last IC ID.")
+
+;; X11 atoms.
+(defvar exwm-xim--@server nil)
+(defvar exwm-xim--LOCALES nil)
+(defvar exwm-xim--TRANSPORT nil)
+(defvar exwm-xim--XIM_SERVERS nil)
+(defvar exwm-xim--_XIM_PROTOCOL nil)
+(defvar exwm-xim--_XIM_XCONNECT nil)
+
+(defvar exwm-xim-buffer-p nil
+  "Whether current buffer is used by exwm-xim.")
+(make-variable-buffer-local 'exwm-xim-buffer-p)
+
+(defun exwm-xim--on-SelectionRequest (data _synthetic)
+  "Handle SelectionRequest events on IMS window.
+DATA contains unmarshalled SelectionRequest event data.
+
+Such events would be received when clients query for LOCALES or TRANSPORT."
+  (exwm--log)
+  (let ((evt (make-instance 'xcb:SelectionRequest))
+        value fake-event)
+    (xcb:unmarshal evt data)
+    (with-slots (time requestor selection target property) evt
+      (setq value (cond ((= target exwm-xim--LOCALES)
+                         ;; Return supported locales.
+                         exwm-xim--locales)
+                        ((= target exwm-xim--TRANSPORT)
+                         ;; Use XIM over an X connection.
+                         "@transport=X/")))
+      (when value
+        ;; Change the property.
+        (xcb:+request exwm-xim--conn
+            (make-instance 'xcb:ChangeProperty
+                           :mode xcb:PropMode:Replace
+                           :window requestor
+                           :property property
+                           :type target
+                           :format 8
+                           :data-len (length value)
+                           :data value))
+        ;; Send a SelectionNotify event.
+        (setq fake-event (make-instance 'xcb:SelectionNotify
+                                        :time time
+                                        :requestor requestor
+                                        :selection selection
+                                        :target target
+                                        :property property))
+        (xcb:+request exwm-xim--conn
+            (make-instance 'xcb:SendEvent
+                           :propagate 0
+                           :destination requestor
+                           :event-mask xcb:EventMask:NoEvent
+                           :event (xcb:marshal fake-event exwm-xim--conn)))
+        (xcb:flush exwm-xim--conn)))))
+
+(cl-defun exwm-xim--on-ClientMessage-0 (data _synthetic)
+  "Handle ClientMessage event on IMS window (new connection).
+
+Such events would be received when clients request for _XIM_XCONNECT.
+A new X connection and server window would be created to communicate with
+this client."
+  (exwm--log)
+  (let ((evt (make-instance 'xcb:ClientMessage))
+        conn client-xwin server-xwin)
+    (xcb:unmarshal evt data)
+    (with-slots (window type data) evt
+      (unless (= type exwm-xim--_XIM_XCONNECT)
+        ;; Only handle _XIM_XCONNECT.
+        (exwm--log "Ignore ClientMessage %s" type)
+        (cl-return-from exwm-xim--on-ClientMessage-0))
+      (setq client-xwin (elt (slot-value data 'data32) 0)
+            ;; Create a new X connection and a new server window.
+            conn (xcb:connect)
+            server-xwin (xcb:generate-id conn))
+      (set-process-query-on-exit-flag (slot-value conn 'process) nil)
+      ;; Store this client.
+      (plist-put exwm-xim--server-client-plist server-xwin
+                 `[,conn ,client-xwin nil])
+      (plist-put exwm-xim--client-server-plist client-xwin server-xwin)
+      ;; Select DestroyNotify events on this client window.
+      (xcb:+request exwm-xim--conn
+          (make-instance 'xcb:ChangeWindowAttributes
+                         :window client-xwin
+                         :value-mask xcb:CW:EventMask
+                         :event-mask xcb:EventMask:StructureNotify))
+      (xcb:flush exwm-xim--conn)
+      ;; Handle ClientMessage events from this new connection.
+      (xcb:+event conn 'xcb:ClientMessage #'exwm-xim--on-ClientMessage)
+      ;; Create a communication window.
+      (xcb:+request conn
+          (make-instance 'xcb:CreateWindow
+                         :depth 0
+                         :wid server-xwin
+                         :parent exwm--root
+                         :x 0
+                         :y 0
+                         :width 1
+                         :height 1
+                         :border-width 0
+                         :class xcb:WindowClass:InputOutput
+                         :visual 0
+                         :value-mask xcb:CW:OverrideRedirect
+                         :override-redirect 1))
+      (xcb:flush conn)
+      ;; Send connection establishment ClientMessage.
+      (setf window client-xwin
+            (slot-value data 'data32) `(,server-xwin 0 0 0 0))
+      (slot-makeunbound data 'data8)
+      (slot-makeunbound data 'data16)
+      (xcb:+request exwm-xim--conn
+          (make-instance 'xcb:SendEvent
+                         :propagate 0
+                         :destination client-xwin
+                         :event-mask xcb:EventMask:NoEvent
+                         :event (xcb:marshal evt exwm-xim--conn)))
+      (xcb:flush exwm-xim--conn))))
+
+(cl-defun exwm-xim--on-ClientMessage (data _synthetic)
+  "Handle ClientMessage event on IMS communication window (request).
+
+Such events would be received when clients request for _XIM_PROTOCOL.
+The actual XIM request is in client message data or a property."
+  (exwm--log)
+  (let ((evt (make-instance 'xcb:ClientMessage))
+        conn client-xwin server-xwin)
+    (xcb:unmarshal evt data)
+    (with-slots (format window type data) evt
+      (unless (= type exwm-xim--_XIM_PROTOCOL)
+        (exwm--log "Ignore ClientMessage %s" type)
+        (cl-return-from exwm-xim--on-ClientMessage))
+      (setq server-xwin window
+            conn (plist-get exwm-xim--server-client-plist server-xwin)
+            client-xwin (elt conn 1)
+            conn (elt conn 0))
+      (cond ((= format 8)
+             ;; Data.
+             (exwm-xim--on-request (vconcat (slot-value data 'data8))
+                                   conn client-xwin server-xwin))
+            ((= format 32)
+             ;; Atom.
+             (with-slots (data32) data
+               (with-slots (value)
+                   (xcb:+request-unchecked+reply conn
+                       (make-instance 'xcb:GetProperty
+                                      :delete 1
+                                      :window server-xwin
+                                      :property (elt data32 1)
+                                      :type xcb:GetPropertyType:Any
+                                      :long-offset 0
+                                      :long-length (elt data32 0)))
+                 (when (> (length value) 0)
+                   (exwm-xim--on-request value conn client-xwin
+                                         server-xwin)))))))))
+
+(defun exwm-xim--on-request (data conn client-xwin server-xwin)
+  "Handle an XIM reuqest."
+  (exwm--log)
+  (let ((opcode (elt data 0))
+        ;; Let-bind `xim:lsb' to make pack/unpack functions work correctly.
+        (xim:lsb (elt (plist-get exwm-xim--server-client-plist server-xwin) 2))
+        req replies)
+    (cond ((= opcode xim:opcode:error)
+           (exwm--log "ERROR: %s" data))
+          ((= opcode xim:opcode:connect)
+           (exwm--log "CONNECT")
+           (setq xim:lsb (= (elt data 4) xim:connect-byte-order:lsb-first))
+           ;; Store byte-order.
+           (setf (elt (plist-get exwm-xim--server-client-plist server-xwin) 2)
+                 xim:lsb)
+           (setq req (make-instance 'xim:connect))
+           (xcb:unmarshal req data)
+           (if (and (= (slot-value req 'major-version) 1)
+                    (= (slot-value req 'minor-version) 0)
+                    ;; Do not support authentication.
+                    (= (slot-value req 'number) 0))
+               ;; Accept the connection.
+               (push (make-instance 'xim:connect-reply) replies)
+             ;; Deny it.
+             (push exwm-xim--default-error replies)))
+          ((memq opcode (list xim:opcode:auth-required
+                              xim:opcode:auth-reply
+                              xim:opcode:auth-next
+                              xim:opcode:auth-ng))
+           (exwm--log "AUTH: %d" opcode)
+           ;; Deny any attempt to make authentication.
+           (push exwm-xim--default-error replies))
+          ((= opcode xim:opcode:disconnect)
+           (exwm--log "DISCONNECT")
+           ;; Gracefully disconnect from the client.
+           (exwm-xim--make-request (make-instance 'xim:disconnect-reply)
+                                   conn client-xwin)
+           ;; Destroy the communication window & connection.
+           (xcb:+request conn
+               (make-instance 'xcb:DestroyWindow
+                              :window server-xwin))
+           (xcb:disconnect conn)
+           ;; Clean up cache.
+           (cl-remf exwm-xim--server-client-plist server-xwin)
+           (cl-remf exwm-xim--client-server-plist client-xwin))
+          ((= opcode xim:opcode:open)
+           (exwm--log "OPEN")
+           ;; Note: We make no check here.
+           (setq exwm-xim--im-id (if (< exwm-xim--im-id #xffff)
+                                     (1+ exwm-xim--im-id)
+                                   1))
+           (setq replies
+                 (list
+                  (make-instance 'xim:open-reply
+                                 :im-id exwm-xim--im-id
+                                 :im-attrs-length nil
+                                 :im-attrs exwm-xim--default-im-attrs
+                                 :ic-attrs-length nil
+                                 :ic-attrs exwm-xim--default-ic-attrs)
+                  (make-instance 'xim:set-event-mask
+                                 :im-id exwm-xim--im-id
+                                 :ic-id 0
+                                 ;; Static event flow.
+                                 :forward-event-mask xcb:EventMask:KeyPress
+                                 ;; on-demand-synchronous method.
+                                 :synchronous-event-mask
+                                 xcb:EventMask:NoEvent))))
+          ((= opcode xim:opcode:close)
+           (exwm--log "CLOSE")
+           (setq req (make-instance 'xim:close))
+           (xcb:unmarshal req data)
+           (push (make-instance 'xim:close-reply
+                                :im-id (slot-value req 'im-id))
+                 replies))
+          ((= opcode xim:opcode:trigger-notify)
+           (exwm--log "TRIGGER-NOTIFY")
+           ;; Only static event flow modal is supported.
+           (push exwm-xim--default-error replies))
+          ((= opcode xim:opcode:encoding-negotiation)
+           (exwm--log "ENCODING-NEGOTIATION")
+           (setq req (make-instance 'xim:encoding-negotiation))
+           (xcb:unmarshal req data)
+           (let ((index (cl-position "COMPOUND_TEXT"
+                                     (mapcar (lambda (i) (slot-value i 'name))
+                                             (slot-value req 'names))
+                                     :test #'equal)))
+             (unless index
+               ;; Fallback to portable character encoding (a subset of ASCII).
+               (setq index -1))
+             (push (make-instance 'xim:encoding-negotiation-reply
+                                  :im-id (slot-value req 'im-id)
+                                  :category
+                                  xim:encoding-negotiation-reply-category:name
+                                  :index index)
+                   replies)))
+          ((= opcode xim:opcode:query-extension)
+           (exwm--log "QUERY-EXTENSION")
+           (setq req (make-instance 'xim:query-extension))
+           (xcb:unmarshal req data)
+           (push (make-instance 'xim:query-extension-reply
+                                :im-id (slot-value req 'im-id)
+                                ;; No extension support.
+                                :length 0
+                                :extensions nil)
+                 replies))
+          ((= opcode xim:opcode:set-im-values)
+           (exwm--log "SET-IM-VALUES")
+           ;; There's only one possible input method attribute.
+           (setq req (make-instance 'xim:set-im-values))
+           (xcb:unmarshal req data)
+           (push (make-instance 'xim:set-im-values-reply
+                                :im-id (slot-value req 'im-id))
+                 replies))
+          ((= opcode xim:opcode:get-im-values)
+           (exwm--log "GET-IM-VALUES")
+           (setq req (make-instance 'xim:get-im-values))
+           (let (im-attributes-id)
+             (xcb:unmarshal req data)
+             (setq im-attributes-id (slot-value req 'im-attributes-id))
+             (if (cl-notevery (lambda (i) (= i 0)) im-attributes-id)
+                 ;; Only support one IM attributes.
+                 (push (make-instance 'xim:error
+                                      :im-id (slot-value req 'im-id)
+                                      :ic-id 0
+                                      :flag xim:error-flag:invalid-ic-id
+                                      :error-code xim:error-code:bad-something
+                                      :length 0
+                                      :type 0
+                                      :detail nil)
+                       replies)
+               (push
+                (make-instance 'xim:get-im-values-reply
+                               :im-id (slot-value req 'im-id)
+                               :length nil
+                               :im-attributes exwm-xim--default-attributes)
+                replies))))
+          ((= opcode xim:opcode:create-ic)
+           (exwm--log "CREATE-IC")
+           (setq req (make-instance 'xim:create-ic))
+           (xcb:unmarshal req data)
+           ;; Note: The ic-attributes slot is ignored.
+           (setq exwm-xim--ic-id (if (< exwm-xim--ic-id #xffff)
+                                     (1+ exwm-xim--ic-id)
+                                   1))
+           (push (make-instance 'xim:create-ic-reply
+                                :im-id (slot-value req 'im-id)
+                                :ic-id exwm-xim--ic-id)
+                 replies))
+          ((= opcode xim:opcode:destroy-ic)
+           (exwm--log "DESTROY-IC")
+           (setq req (make-instance 'xim:destroy-ic))
+           (xcb:unmarshal req data)
+           (push (make-instance 'xim:destroy-ic-reply
+                                :im-id (slot-value req 'im-id)
+                                :ic-id (slot-value req 'ic-id))
+                 replies))
+          ((= opcode xim:opcode:set-ic-values)
+           (exwm--log "SET-IC-VALUES")
+           (setq req (make-instance 'xim:set-ic-values))
+           (xcb:unmarshal req data)
+           ;; We don't distinguish between input contexts.
+           (push (make-instance 'xim:set-ic-values-reply
+                                :im-id (slot-value req 'im-id)
+                                :ic-id (slot-value req 'ic-id))
+                 replies))
+          ((= opcode xim:opcode:get-ic-values)
+           (exwm--log "GET-IC-VALUES")
+           (setq req (make-instance 'xim:get-ic-values))
+           (xcb:unmarshal req data)
+           (push (make-instance 'xim:get-ic-values-reply
+                                :im-id (slot-value req 'im-id)
+                                :ic-id (slot-value req 'ic-id)
+                                :length nil
+                                :ic-attributes exwm-xim--default-attributes)
+                 replies))
+          ((= opcode xim:opcode:set-ic-focus)
+           (exwm--log "SET-IC-FOCUS")
+           ;; All input contexts are the same.
+           )
+          ((= opcode xim:opcode:unset-ic-focus)
+           (exwm--log "UNSET-IC-FOCUS")
+           ;; All input contexts are the same.
+           )
+          ((= opcode xim:opcode:forward-event)
+           (exwm--log "FORWARD-EVENT")
+           (setq req (make-instance 'xim:forward-event))
+           (xcb:unmarshal req data)
+           (exwm-xim--handle-forward-event-request req xim:lsb conn
+                                                   client-xwin))
+          ((= opcode xim:opcode:sync)
+           (exwm--log "SYNC")
+           (setq req (make-instance 'xim:sync))
+           (xcb:unmarshal req data)
+           (push (make-instance 'xim:sync-reply
+                                :im-id (slot-value req 'im-id)
+                                :ic-id (slot-value req 'ic-id))
+                 replies))
+          ((= opcode xim:opcode:sync-reply)
+           (exwm--log "SYNC-REPLY"))
+          ((= opcode xim:opcode:reset-ic)
+           (exwm--log "RESET-IC")
+           ;; No context-specific data saved.
+           (setq req (make-instance 'xim:reset-ic))
+           (xcb:unmarshal req data)
+           (push (make-instance 'xim:reset-ic-reply
+                                :im-id (slot-value req 'im-id)
+                                :ic-id (slot-value req 'ic-id)
+                                :length 0
+                                :string "")
+                 replies))
+          ((memq opcode (list xim:opcode:str-conversion-reply
+                              xim:opcode:preedit-start-reply
+                              xim:opcode:preedit-caret-reply))
+           (exwm--log "PREEDIT: %d" opcode)
+           ;; No preedit support.
+           (push exwm-xim--default-error replies))
+          (t
+           (exwm--log "Bad protocol")
+           (push exwm-xim--default-error replies)))
+    ;; Actually send the replies.
+    (when replies
+      (mapc (lambda (reply)
+              (exwm-xim--make-request reply conn client-xwin))
+            replies)
+      (xcb:flush conn))))
+
+(defun exwm-xim--handle-forward-event-request (req lsb conn client-xwin)
+  (let ((im-func (with-current-buffer (window-buffer)
+                   input-method-function))
+        key-event keysym keysyms event result)
+    ;; Note: The flag slot is ignored.
+    ;; Do conversion in client's byte-order.
+    (let ((xcb:lsb lsb))
+      (setq key-event (make-instance 'xcb:KeyPress))
+      (xcb:unmarshal key-event (slot-value req 'event)))
+    (with-slots (detail state) key-event
+      (setq keysym (xcb:keysyms:keycode->keysym exwm-xim--conn detail
+                                                state))
+      (when (/= (car keysym) 0)
+        (setq event (xcb:keysyms:keysym->event
+                     exwm-xim--conn
+                     (car keysym)
+                     (logand state (lognot (cdr keysym)))))))
+    (while (or (slot-value req 'event) unread-command-events)
+      (unless (slot-value req 'event)
+        (setq event (pop unread-command-events))
+        ;; Handle events in (t . EVENT) format.
+        (when (and (consp event)
+                   (eq (car event) t))
+          (setq event (cdr event))))
+      (if (or (not im-func)
+              ;; `list' is the default method.
+              (eq im-func #'list)
+              (not event)
+              ;; Select only printable keys.
+              (not (integerp event)) (> #x20 event) (< #x7e event))
+          ;; Either there is no active input method, or invalid key
+          ;; is detected.
+          (with-slots ((raw-event event)
+                       im-id ic-id serial-number)
+              req
+            (if raw-event
+                (setq event raw-event)
+              (setq keysyms (xcb:keysyms:event->keysyms exwm-xim--conn event))
+              (with-slots (detail state) key-event
+                (setf detail (xcb:keysyms:keysym->keycode exwm-xim--conn
+                                                          (caar keysyms))
+                      state (cdar keysyms)))
+              (setq event (let ((xcb:lsb lsb))
+                            (xcb:marshal key-event conn))))
+            (when event
+              (exwm-xim--make-request
+               (make-instance 'xim:forward-event
+                              :im-id im-id
+                              :ic-id ic-id
+                              :flag xim:commit-flag:synchronous
+                              :serial-number serial-number
+                              :event event)
+               conn client-xwin)))
+        (when (eq exwm--selected-input-mode 'char-mode)
+          ;; Grab keyboard temporarily for char-mode.
+          (exwm-input--grab-keyboard))
+        (unwind-protect
+            (with-temp-buffer
+              ;; This variable is used to test whether exwm-xim is enabled.
+              ;; Used by e.g. pyim-probe.
+              (setq-local exwm-xim-buffer-p t)
+              ;; Always show key strokes.
+              (let ((input-method-use-echo-area t)
+                    (exwm-input-line-mode-passthrough t))
+                (setq result (funcall im-func event))
+                ;; Clear echo area for the input method.
+                (message nil)
+                ;; This also works for portable character encoding.
+                (setq result
+                      (encode-coding-string (concat result)
+                                            'compound-text-with-extensions))
+                (exwm-xim--make-request
+                 (make-instance 'xim:commit-x-lookup-chars
+                                :im-id (slot-value req 'im-id)
+                                :ic-id (slot-value req 'ic-id)
+                                :flag (logior xim:commit-flag:synchronous
+                                              xim:commit-flag:x-lookup-chars)
+                                :length (length result)
+                                :string result)
+                 conn client-xwin)))
+          (when (eq exwm--selected-input-mode 'char-mode)
+            (exwm-input--release-keyboard))))
+      (xcb:flush conn)
+      (setf event nil
+            (slot-value req 'event) nil))))
+
+(defun exwm-xim--make-request (req conn client-xwin)
+  "Make an XIM request REQ via connection CONN.
+
+CLIENT-XWIN would receive a ClientMessage event either telling the client
+the request data or where to fetch the data."
+  (exwm--log)
+  (let ((data (xcb:marshal req))
+        property format client-message-data client-message)
+    (if (<= (length data) 20)
+        ;; Send short requests directly with client messages.
+        (setq format 8
+              ;; Pad to 20 bytes.
+              data (append data (make-list (- 20 (length data)) 0))
+              client-message-data (make-instance 'xcb:ClientMessageData
+                                                 :data8 data))
+      ;; Send long requests with properties.
+      (setq property (exwm--intern-atom (format "_EXWM_XIM_%x"
+                                                exwm-xim--property-index)))
+      (cl-incf exwm-xim--property-index)
+      (xcb:+request conn
+          (make-instance 'xcb:ChangeProperty
+                         :mode xcb:PropMode:Append
+                         :window client-xwin
+                         :property property
+                         :type xcb:Atom:STRING
+                         :format 8
+                         :data-len (length data)
+                         :data data))
+      ;; Also send a client message to notify the client about this property.
+      (setq format 32
+            client-message-data (make-instance 'xcb:ClientMessageData
+                                               :data32 `(,(length data)
+                                                         ,property
+                                                         ;; Pad to 20 bytes.
+                                                         0 0 0))))
+    ;; Send the client message.
+    (setq client-message (make-instance 'xcb:ClientMessage
+                                        :format format
+                                        :window client-xwin
+                                        :type exwm-xim--_XIM_PROTOCOL
+                                        :data client-message-data))
+    (xcb:+request conn
+        (make-instance 'xcb:SendEvent
+                       :propagate 0
+                       :destination client-xwin
+                       :event-mask xcb:EventMask:NoEvent
+                       :event (xcb:marshal client-message conn)))))
+
+(defun exwm-xim--on-DestroyNotify (data synthetic)
+  "Do cleanups on receiving DestroyNotify event.
+
+Such event would be received when the client window is destroyed."
+  (exwm--log)
+  (unless synthetic
+    (let ((evt (make-instance 'xcb:DestroyNotify))
+          conn client-xwin server-xwin)
+      (xcb:unmarshal evt data)
+      (setq client-xwin (slot-value evt 'window)
+            server-xwin (plist-get exwm-xim--client-server-plist client-xwin))
+      (when server-xwin
+        (setq conn (aref (plist-get exwm-xim--server-client-plist server-xwin)
+                         0))
+        (cl-remf exwm-xim--server-client-plist server-xwin)
+        (cl-remf exwm-xim--client-server-plist client-xwin)
+        ;; Destroy the communication window & connection.
+        (xcb:+request conn
+            (make-instance 'xcb:DestroyWindow
+                           :window server-xwin))
+        (xcb:disconnect conn)))))
+
+(cl-defun exwm-xim--init ()
+  "Initialize the XIM module."
+  (exwm--log)
+  (when exwm-xim--conn
+    (cl-return-from exwm-xim--init))
+  ;; Initialize atoms.
+  (setq exwm-xim--@server (exwm--intern-atom "@server=exwm-xim")
+        exwm-xim--LOCALES (exwm--intern-atom "LOCALES")
+        exwm-xim--TRANSPORT (exwm--intern-atom "TRANSPORT")
+        exwm-xim--XIM_SERVERS (exwm--intern-atom "XIM_SERVERS")
+        exwm-xim--_XIM_PROTOCOL (exwm--intern-atom "_XIM_PROTOCOL")
+        exwm-xim--_XIM_XCONNECT (exwm--intern-atom "_XIM_XCONNECT"))
+  ;; Create a new connection and event window.
+  (setq exwm-xim--conn (xcb:connect)
+        exwm-xim--event-xwin (xcb:generate-id exwm-xim--conn))
+  (set-process-query-on-exit-flag (slot-value exwm-xim--conn 'process) nil)
+  ;; Initialize xcb:keysyms module.
+  (xcb:keysyms:init exwm-xim--conn)
+  ;; Listen to SelectionRequest event for connection establishment.
+  (xcb:+event exwm-xim--conn 'xcb:SelectionRequest
+              #'exwm-xim--on-SelectionRequest)
+  ;; Listen to ClientMessage event on IMS window for new XIM connection.
+  (xcb:+event exwm-xim--conn 'xcb:ClientMessage #'exwm-xim--on-ClientMessage-0)
+  ;; Listen to DestroyNotify event to do cleanups.
+  (xcb:+event exwm-xim--conn 'xcb:DestroyNotify #'exwm-xim--on-DestroyNotify)
+  ;; Create the event window.
+  (xcb:+request exwm-xim--conn
+      (make-instance 'xcb:CreateWindow
+                     :depth 0
+                     :wid exwm-xim--event-xwin
+                     :parent exwm--root
+                     :x 0
+                     :y 0
+                     :width 1
+                     :height 1
+                     :border-width 0
+                     :class xcb:WindowClass:InputOutput
+                     :visual 0
+                     :value-mask xcb:CW:OverrideRedirect
+                     :override-redirect 1))
+  ;; Set the selection owner.
+  (xcb:+request exwm-xim--conn
+      (make-instance 'xcb:SetSelectionOwner
+                     :owner exwm-xim--event-xwin
+                     :selection exwm-xim--@server
+                     :time xcb:Time:CurrentTime))
+  ;; Set XIM_SERVERS property on the root window.
+  (xcb:+request exwm-xim--conn
+      (make-instance 'xcb:ChangeProperty
+                     :mode xcb:PropMode:Prepend
+                     :window exwm--root
+                     :property exwm-xim--XIM_SERVERS
+                     :type xcb:Atom:ATOM
+                     :format 32
+                     :data-len 1
+                     :data (funcall (if xcb:lsb
+                                        #'xcb:-pack-u4-lsb
+                                      #'xcb:-pack-u4)
+                                    exwm-xim--@server)))
+  (xcb:flush exwm-xim--conn))
+
+(cl-defun exwm-xim--exit ()
+  "Exit the XIM module."
+  (exwm--log)
+  ;; Close IMS communication connections.
+  (mapc (lambda (i)
+          (when (vectorp i)
+            (when (slot-value (elt i 0) 'connected)
+              (xcb:disconnect (elt i 0)))))
+        exwm-xim--server-client-plist)
+  ;; Close the IMS connection.
+  (unless (and exwm-xim--conn
+               (slot-value exwm-xim--conn 'connected))
+    (cl-return-from exwm-xim--exit))
+  ;; Remove exwm-xim from XIM_SERVERS.
+  (let ((reply (xcb:+request-unchecked+reply exwm-xim--conn
+                   (make-instance 'xcb:GetProperty
+                                  :delete 1
+                                  :window exwm--root
+                                  :property exwm-xim--XIM_SERVERS
+                                  :type xcb:Atom:ATOM
+                                  :long-offset 0
+                                  :long-length 1000)))
+        unpacked-reply pack unpack)
+    (unless reply
+      (cl-return-from exwm-xim--exit))
+    (setq reply (slot-value reply 'value))
+    (unless (> (length reply) 4)
+      (cl-return-from exwm-xim--exit))
+    (setq reply (vconcat reply)
+          pack (if xcb:lsb #'xcb:-pack-u4-lsb #'xcb:-pack-u4)
+          unpack (if xcb:lsb #'xcb:-unpack-u4-lsb #'xcb:-unpack-u4))
+    (dotimes (i (/ (length reply) 4))
+      (push (funcall unpack reply (* i 4)) unpacked-reply))
+    (setq unpacked-reply (delq exwm-xim--@server unpacked-reply)
+          reply (mapcar pack unpacked-reply))
+    (xcb:+request exwm-xim--conn
+        (make-instance 'xcb:ChangeProperty
+                       :mode xcb:PropMode:Replace
+                       :window exwm--root
+                       :property exwm-xim--XIM_SERVERS
+                       :type xcb:Atom:ATOM
+                       :format 32
+                       :data-len (length reply)
+                       :data reply))
+    (xcb:flush exwm-xim--conn))
+  (xcb:disconnect exwm-xim--conn)
+  (setq exwm-xim--conn nil))
+
+(defun exwm-xim-enable ()
+  "Enable XIM support for EXWM."
+  (exwm--log)
+  (add-hook 'exwm-init-hook #'exwm-xim--init)
+  (add-hook 'exwm-exit-hook #'exwm-xim--exit))
+
+
+
+(provide 'exwm-xim)
+
+;;; exwm-xim.el ends here
diff --git a/exwm-xsettings.el b/exwm-xsettings.el
new file mode 100644
index 0000000000..99d6b9c4ac
--- /dev/null
+++ b/exwm-xsettings.el
@@ -0,0 +1,336 @@
+;;; exwm-xsettings.el --- XSETTINGS Module for EXWM -*- lexical-binding: t -*-
+
+;; Copyright (C) 2022-2024 Free Software Foundation, Inc.
+
+;; Author: Steven Allen <steven@stebalien.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Implements the XSETTINGS protocol, allowing Emacs to manage the system theme,
+;; fonts, icons, etc.
+;;
+;; This package can be configured as follows:
+;;
+;;   (require 'exwm-xsettings)
+;;   (setq exwm-xsettings-theme '("Adwaita" . "Adwaita-dark") ;; light/dark
+;;         exwm-xsettings `(("Xft/HintStyle" . "hintslight")
+;;                          ("Xft/RGBA" . "rgb")
+;;                          ("Xft/lcdfilter" . "lcddefault")
+;;                          ("Xft/Antialias" . 1)
+;;                          ;; DPI is in 1024ths of an inch, so this is a DPI of
+;;                          ;; 144, equivalent to ;; a scaling factor of 1.5
+;;                          ;; (144 = 1.5 * 96).
+;;                          ("Xft/DPI" . ,(* 144 1024))
+;;                          ("Xft/Hinting" . 1)))
+;;   (exwm-xsettings-enable)
+;;
+;; To modify these settings at runtime, customize them with
+;; `custom-set-variables' or `setopt' (Emacs 29+).  E.g., the following will
+;; immediately change the icon theme to "Papirus" at runtime, even in running
+;; applications:
+;;
+;;   (setopt exwm-xsettings-icon-theme "Papirus")
+
+;;; Code:
+
+(require 'xcb-ewmh)
+(require 'xcb-xsettings)
+(require 'exwm-core)
+
+(defvar exwm-xsettings--connection nil)
+(defvar exwm-xsettings--XSETTINGS_SETTINGS-atom nil)
+(defvar exwm-xsettings--XSETTINGS_S0-atom nil)
+(defvar exwm-xsettings--selection-owner-window nil)
+(defvar exwm-xsettings--serial 0)
+
+(defun exwm-xsettings--rgba-match (_widget value)
+  "Return t if VALUE is a valid RGBA color."
+  (and (numberp value) (<= 0 value 1)))
+
+(defun exwm-xsettings--custom-set (symbol value)
+  "Setter used by `exwm-xsettings' customization options.
+
+SYMBOL is the setting being updated and VALUE is the new value."
+  (set-default-toplevel-value symbol value)
+  (exwm-xsettings--update-settings))
+
+(defgroup exwm-xsettings nil
+  "XSETTINGS."
+  :group 'exwm)
+
+(defcustom exwm-xsettings nil
+  "Alist of custom XSETTINGS.
+These settings take precedence over `exwm-xsettings-theme' and
+`exwm-xsettings-icon-theme'."
+  :type '(alist :key-type (string :tag "Name")
+                :value-type (choice :tag "Value"
+                              (string :tag "String")
+                              (integer :tag "Integer")
+                              (list :tag "Color"
+                                (number :tag "Red"
+                                        :type-error
+                                        "This field should contain a number between 0 and 1."
+                                       :match exwm-xsettings--rgba-match)
+                                (number :tag "Green"
+                                        :type-error
+                                        "This field should contain a number between 0 and 1."
+                                       :match exwm-xsettings--rgba-match)
+                                (number :tag "Blue"
+                                        :type-error
+                                        "This field should contain a number between 0 and 1."
+                                       :match exwm-xsettings--rgba-match)
+                                (number :tag "Alpha"
+                                        :type-error
+                                        "This field should contain a number between 0 and 1."
+                                       :match exwm-xsettings--rgba-match
+                                       :value 1.0))))
+  :initialize #'custom-initialize-default
+  :set #'exwm-xsettings--custom-set)
+
+(defcustom exwm-xsettings-theme nil
+  "The system-wide theme."
+  :type '(choice (string :tag "Theme")
+                 (cons (string :tag "Light Theme")
+                       (string :tag "Dark Theme")))
+  :initialize #'custom-initialize-default
+  :set #'exwm-xsettings--custom-set)
+
+(defcustom exwm-xsettings-icon-theme nil
+  "The system-wide icon theme."
+  :type '(choice (string :tag "Icon Theme")
+                 (cons (string :tag "Light Icon Theme")
+                       (string :tag "Dark Icon Theme")))
+  :initialize #'custom-initialize-default
+  :set #'exwm-xsettings--custom-set)
+
+(defalias 'exwm-xsettings--color-dark-p
+  (if (eval-when-compile (< emacs-major-version 29))
+      ;; Borrowed from Emacs 29.
+      (lambda (rgb)
+        "Whether RGB is more readable against white than black."
+        (unless (<= 0 (apply #'min rgb) (apply #'max rgb) 1)
+          (error "RGB components %S not in [0,1]" rgb))
+        (let* ((r (expt (nth 0 rgb) 2.2))
+               (g (expt (nth 1 rgb) 2.2))
+               (b (expt (nth 2 rgb) 2.2))
+               (y (+ (* r 0.2126) (* g 0.7152) (* b 0.0722))))
+          (< y 0.325)))
+    'color-dark-p))
+
+(defun exwm-xsettings--pick-theme (theme)
+  "Pick a light or dark theme from the given THEME.
+If THEME is a string, it's returned directly.
+If THEME is a cons of (LIGHT . DARK), the appropriate theme is picked based on
+the default face's background color."
+  (pcase theme
+    ((cl-type string) theme)
+    (`(,(cl-type string) . ,(cl-type string))
+     (if (exwm-xsettings--color-dark-p (color-name-to-rgb (face-background 'default)))
+         (cdr theme) (car theme)))
+    (_ (error "Expected theme to be a string or a pair of strings"))))
+
+(defun exwm-xsettings--get-settings ()
+  "Get the current settings.
+Combines `exwm-xsettings', `exwm-xsettings-theme' (if set), and
+`exwm-xsettings-icon-theme' (if set)."
+  (cl-remove-duplicates
+   (append
+    exwm-xsettings
+    (when exwm-xsettings-theme
+      (list (cons "Net/ThemeName" (exwm-xsettings--pick-theme exwm-xsettings-theme))))
+    (when exwm-xsettings-icon-theme
+      (list (cons "Net/IconThemeName" (exwm-xsettings--pick-theme exwm-xsettings-icon-theme)))))
+   :key 'car
+   :test 'string=))
+
+(defun exwm-xsettings--make-settings (settings serial)
+  "Construct a new settings object.
+SETTINGS is an alist of key/value pairs.
+SERIAL is a sequence number."
+  (make-instance 'xcb:xsettings:-Settings
+                 :byte-order (if xcb:lsb 0 1)
+                 :serial serial
+                 :settings-len (length settings)
+                 :settings
+                 (mapcar
+                  (lambda (prop)
+                    (let* ((name (car prop))
+                           (value (cdr prop))
+                           (common (list :name name
+                                         :name-len (length name)
+                                         :last-change-serial serial)))
+                      (pcase value
+                        ((cl-type string)
+                         (apply #'make-instance 'xcb:xsettings:-SETTING_STRING
+                                :value-len (length value)
+                                :value value
+                                common))
+                        ((cl-type integer)
+                         (apply #'make-instance 'xcb:xsettings:-SETTING_INTEGER
+                                :value value common))
+                        ((and (cl-type list) (app length (or 3 4)))
+                         ;; Convert from RGB(A) to 16bit integers.
+                         (setq value (mapcar (lambda (x) (round (* x #xffff))) value))
+                         (apply #'make-instance 'xcb:xsettings:-SETTING_COLOR
+                                :red (pop value)
+                                :green (pop value)
+                                :blue (pop value)
+                                :alpha (or (pop value) #xffff)))
+                        (_ (error "Setting value must be a string, integer, or length 3-4 list")))))
+                  settings)))
+
+(defun exwm-xsettings--update-settings ()
+  "Update the xsettings."
+  (when exwm-xsettings--connection
+    (setq exwm-xsettings--serial (1+ exwm-xsettings--serial))
+    (let* ((settings (exwm-xsettings--get-settings))
+           (bytes (xcb:marshal (exwm-xsettings--make-settings settings exwm-xsettings--serial))))
+      (xcb:+request exwm-xsettings--connection
+          (make-instance 'xcb:ChangeProperty
+                         :mode xcb:PropMode:Replace
+                         :window exwm-xsettings--selection-owner-window
+                         :property exwm-xsettings--XSETTINGS_SETTINGS-atom
+                         :type exwm-xsettings--XSETTINGS_SETTINGS-atom
+                         :format 8
+                         :data-len (length bytes)
+                         :data bytes)))
+    (xcb:flush exwm-xsettings--connection)))
+
+(defun exwm-xsettings--on-theme-change (&rest _)
+  "Called when the Emacs theme is changed."
+  ;; We only bother updating the xsettings if changing the theme could effect
+  ;; the settings.
+  (when (or (consp exwm-xsettings-theme) (consp exwm-xsettings-icon-theme))
+    (exwm-xsettings--update-settings)))
+
+(defun exwm-xsettings--on-SelectionClear (_data _synthetic)
+  "Called when another xsettings daemon takes over."
+  (exwm--log "XSETTINGS manager has been replaced.")
+  (exwm-xsettings--exit))
+
+(cl-defun exwm-xsettings--init ()
+  "Initialize the XSETTINGS module."
+  (exwm--log)
+
+  (cl-assert (not exwm-xsettings--connection))
+
+  ;; Connect
+  (setq exwm-xsettings--connection (xcb:connect))
+  (set-process-query-on-exit-flag (slot-value exwm-xsettings--connection
+                                              'process)
+                                  nil)
+
+  ;; Intern the atoms.
+  (setq exwm-xsettings--XSETTINGS_SETTINGS-atom
+        (exwm--intern-atom "_XSETTINGS_SETTINGS" exwm-xsettings--connection)
+
+        exwm-xsettings--XSETTINGS_S0-atom
+        (exwm--intern-atom "_XSETTINGS_S0" exwm-xsettings--connection))
+
+  ;; Detect running XSETTINGS managers.
+  (with-slots (owner)
+      (xcb:+request-unchecked+reply exwm-xsettings--connection
+          (make-instance 'xcb:GetSelectionOwner
+                         :selection exwm-xsettings--XSETTINGS_S0-atom))
+    (when (/= owner xcb:Window:None)
+      (xcb:disconnect exwm-xsettings--connection)
+      (setq exwm-xsettings--connection nil)
+      (warn "[EXWM] Other XSETTINGS manager detected")
+      (cl-return-from exwm-xsettings--init)))
+
+  (let ((id(xcb:generate-id exwm-xsettings--connection)))
+    (setq exwm-xsettings--selection-owner-window id)
+
+    ;; Create a settings window.
+    (xcb:+request exwm-xsettings--connection
+        (make-instance 'xcb:CreateWindow
+                       :wid id
+                       :parent exwm--root
+                       :class xcb:WindowClass:InputOnly
+                       :x 0
+                       :y 0
+                       :width 1
+                       :height 1
+                       :border-width 0
+                       :depth 0
+                       :visual 0
+                       :value-mask xcb:CW:OverrideRedirect
+                       :override-redirect 1))
+
+    ;; Set _NET_WM_NAME.
+    (xcb:+request exwm-xsettings--connection
+        (make-instance 'xcb:ewmh:set-_NET_WM_NAME
+                       :window id
+                       :data "EXWM: exwm-xsettings--selection-owner-window"))
+
+    ;; Apply the XSETTINGS properties.
+    (exwm-xsettings--update-settings)
+
+    ;; Take ownership and notify.
+    (xcb:+request exwm-xsettings--connection
+        (make-instance 'xcb:SetSelectionOwner
+                       :owner id
+                       :selection exwm-xsettings--XSETTINGS_S0-atom
+                       :time xcb:Time:CurrentTime))
+    (xcb:+request exwm-xsettings--connection
+        (make-instance 'xcb:SendEvent
+                       :propagate 0
+                       :destination exwm--root
+                       :event-mask xcb:EventMask:StructureNotify
+                       :event (xcb:marshal
+                               (make-instance 'xcb:xsettings:-ClientMessage
+                                              :window exwm--root
+                                              :time xcb:Time:CurrentTime
+                                              :selection exwm-xsettings--XSETTINGS_S0-atom
+                                              :owner id)
+                               exwm-xsettings--connection)))
+
+    ;; Detect loss of XSETTINGS ownership.
+    (xcb:+event exwm-xsettings--connection 'xcb:SelectionClear
+                #'exwm-xsettings--on-SelectionClear)
+
+    (xcb:flush exwm-xsettings--connection))
+
+  ;; Update the xsettings if/when the theme changes.
+  (add-hook 'enable-theme-functions #'exwm-xsettings--on-theme-change)
+  (add-hook 'disable-theme-functions #'exwm-xsettings--on-theme-change))
+
+(defun exwm-xsettings--exit ()
+  "Exit the XSETTINGS module."
+  (exwm--log)
+
+  (when exwm-xsettings--connection
+    (remove-hook 'enable-theme-functions #'exwm-xsettings--on-theme-change)
+    (remove-hook 'disable-theme-functions #'exwm-xsettings--on-theme-change)
+
+    (xcb:disconnect exwm-xsettings--connection)
+
+    (setq exwm-xsettings--connection nil
+          exwm-xsettings--XSETTINGS_SETTINGS-atom nil
+          exwm-xsettings--XSETTINGS_S0-atom nil
+          exwm-xsettings--selection-owner-window nil)))
+
+(defun exwm-xsettings-enable ()
+  "Enable xsettings support for EXWM."
+  (exwm--log)
+  (add-hook 'exwm-init-hook #'exwm-xsettings--init)
+  (add-hook 'exwm-exit-hook #'exwm-xsettings--exit))
+
+(provide 'exwm-xsettings)
+
+;;; exwm-xsettings.el ends here
diff --git a/exwm.el b/exwm.el
new file mode 100644
index 0000000000..c4900eab48
--- /dev/null
+++ b/exwm.el
@@ -0,0 +1,1113 @@
+;;; exwm.el --- Emacs X Window Manager  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2015-2024 Free Software Foundation, Inc.
+
+;; Author: Chris Feng <chris.w.feng@gmail.com>
+;; Maintainer: Adrián Medraño Calvo <adrian@medranocalvo.com>, Steven Allen <steven@stebalien.com>, Daniel Mendler <mail@daniel-mendler.de>
+;; Version: 0.28
+;; Package-Requires: ((emacs "27.1") (xelb "0.18"))
+;; Keywords: unix
+;; URL: https://github.com/emacs-exwm/exwm
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Overview
+;; --------
+;; EXWM (Emacs X Window Manager) is a full-featured tiling X window manager
+;; for Emacs built on top of [XELB](https://github.com/emacs-exwm/xelb).
+;; It features:
+;; + Fully keyboard-driven operations
+;; + Hybrid layout modes (tiling & stacking)
+;; + Dynamic workspace support
+;; + ICCCM/EWMH compliance
+;; Optional features:
+;; + RandR (multi-monitor) support
+;; + System tray
+;; + Input method
+;; + Background setting support
+;; + XSETTINGS server
+
+;; Installation & configuration
+;; ----------------------------
+;; Here are the minimal steps to get EXWM working:
+;; 1. Install XELB and EXWM, and make sure they are in `load-path'.
+;; 2. In '~/.emacs', add following lines (please modify accordingly):
+;;
+;;    (require 'exwm)
+;;    (require 'exwm-config)
+;;    (exwm-config-example)
+;;
+;; 3. Link or copy the file 'xinitrc' to '~/.xinitrc'.
+;; 4. Launch EXWM in a console (e.g. tty1) with
+;;
+;;    xinit -- vt01
+;;
+;; You should additionally hide the menu-bar, tool-bar, etc to increase the
+;; usable space.  Please check the wiki (https://github.com/emacs-exwm/exwm/wiki)
+;; for more detailed instructions on installation, configuration, usage, etc.
+
+;; References:
+;; + dwm (http://dwm.suckless.org/)
+;; + i3 wm (https://i3wm.org/)
+;; + Also see references within each required library.
+
+;;; Code:
+
+(require 'server)
+(require 'exwm-core)
+(require 'exwm-workspace)
+(require 'exwm-layout)
+(require 'exwm-floating)
+(require 'exwm-manage)
+(require 'exwm-input)
+
+(declare-function x-get-atom-name "C source code" (VALUE &optional FRAME))
+
+(defgroup exwm nil
+  "Emacs X Window Manager."
+  :tag "EXWM"
+  :group 'applications
+  :prefix "exwm-")
+
+(defcustom exwm-init-hook nil
+  "Normal hook run when EXWM has just finished initialization."
+  :type 'hook)
+
+(defcustom exwm-exit-hook nil
+  "Normal hook run just before EXWM exits."
+  :type 'hook)
+
+(defcustom exwm-update-class-hook nil
+  "Normal hook run when window class is updated."
+  :type 'hook)
+
+(defcustom exwm-update-title-hook nil
+  "Normal hook run when window title is updated."
+  :type 'hook)
+
+(defcustom exwm-blocking-subrs
+  ;; `x-file-dialog' and `x-select-font' are missing on some Emacs builds, for
+  ;; example on the X11 Lucid build.
+  '(x-file-dialog x-popup-dialog x-select-font message-box message-or-box)
+  "Subrs (primitives) that would normally block EXWM."
+  :type '(repeat function))
+
+(defcustom exwm-replace 'ask
+  "Whether to replace existing window manager."
+  :type '(radio (const :tag "Ask" ask)
+                (const :tag "Replace by default" t)
+                (const :tag "Do not replace" nil)))
+
+(defconst exwm--server-name "server-exwm"
+  "Name of the subordinate Emacs server.")
+
+(defvar exwm--server-timeout 1
+  "Number of seconds to wait for the subordinate Emacs server to exit.
+After this time, the server will be killed.")
+
+(defvar exwm--server-process nil "Process of the subordinate Emacs server.")
+
+(defun exwm-reset ()
+  "Reset the state of the selected window (non-fullscreen, line-mode, etc)."
+  (interactive)
+  (exwm--log)
+  (with-current-buffer (window-buffer)
+    (when (derived-mode-p 'exwm-mode)
+      (when (exwm-layout--fullscreen-p)
+        (exwm-layout-unset-fullscreen))
+      ;; Force refresh
+      (exwm-layout--refresh)
+      (call-interactively #'exwm-input-grab-keyboard))))
+
+;;;###autoload
+(defun exwm-restart ()
+  "Restart EXWM."
+  (interactive)
+  (exwm--log)
+  (when (exwm--confirm-kill-emacs "Restart?" 'no-check)
+    (let* ((attr (process-attributes (emacs-pid)))
+           (args (cdr (assq 'args attr)))
+           (ppid (cdr (assq 'ppid attr)))
+           (pargs (cdr (assq 'args (process-attributes ppid)))))
+      (cond
+       ((= ppid 1)
+        ;; The parent is the init process.  This probably means this
+        ;; instance is an emacsclient.  Anyway, start a control instance
+        ;; to manage the subsequent ones.
+        (call-process (car command-line-args))
+        (kill-emacs))
+       ((string= args pargs)
+        ;; This is a subordinate instance.  Return a magic number to
+        ;; inform the parent (control instance) to start another one.
+        (kill-emacs ?R))
+       (t
+        ;; This is the control instance.  Keep starting subordinate
+        ;; instances until told to exit.
+        ;; Run `server-force-stop' if it exists.
+        (run-hooks 'kill-emacs-hook)
+        (with-temp-buffer
+          (while (= ?R (shell-command-on-region (point) (point) args))))
+        (kill-emacs))))))
+
+(defun exwm--update-desktop (xwin)
+  "Update _NET_WM_DESKTOP.
+Argument XWIN contains the X window of the `exwm-mode' buffer."
+  (exwm--log "#x%x" xwin)
+  (with-current-buffer (exwm--id->buffer xwin)
+    (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                     (make-instance 'xcb:ewmh:get-_NET_WM_DESKTOP
+                                    :window xwin)))
+          desktop)
+      (when reply
+        (setq desktop (slot-value reply 'value))
+        (cond
+         ((and desktop (= desktop 4294967295.))
+          (unless (or (not exwm--floating-frame)
+                      (eq exwm--frame exwm-workspace--current)
+                      (and exwm--desktop
+                           (= desktop exwm--desktop)))
+            (exwm-layout--show xwin (frame-root-window exwm--floating-frame)))
+          (setq exwm--desktop desktop))
+         ((and desktop
+               (< desktop (exwm-workspace--count))
+               (if exwm--desktop
+                   (/= desktop exwm--desktop)
+                 (/= desktop (exwm-workspace--position exwm--frame))))
+          (exwm-workspace-move-window desktop xwin))
+         (t
+          (exwm-workspace--set-desktop xwin)))))))
+
+(defun exwm--update-window-type (id &optional force)
+  "Update `exwm-window-type' from _NET_WM_WINDOW_TYPE.
+Argument ID contains the X window of the `exwm-mode' buffer.
+
+When FORCE is nil the update only takes place if
+`exwm-window-type' is unset."
+  (exwm--log "#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (unless (and exwm-window-type (not force))
+      (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:ewmh:get-_NET_WM_WINDOW_TYPE
+                                      :window id))))
+        (when reply                     ;nil when destroyed
+          (setq exwm-window-type (append (slot-value reply 'value) nil)))))))
+
+(defun exwm--update-class (id &optional force)
+  "Update `exwm-instance-name' and `exwm-class' from WM_CLASS.
+Argument ID contains the X window of the `exwm-mode' buffer.
+
+When FORCE is nil the update only takes place if any of
+`exwm-instance-name' or `exwm-class' is unset."
+  (exwm--log "#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (unless (and exwm-instance-name exwm-class-name (not force))
+      (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:icccm:get-WM_CLASS :window id))))
+        (when reply                     ;nil when destroyed
+          (setq exwm-instance-name (slot-value reply 'instance-name)
+                exwm-class-name (slot-value reply 'class-name))
+          (when (and exwm-instance-name exwm-class-name)
+            (run-hooks 'exwm-update-class-hook)))))))
+
+(defun exwm--update-utf8-title (id &optional force)
+  "Update `exwm-title' from _NET_WM_NAME.
+Argument ID contains the X window of the `exwm-mode' buffer.
+
+When FORCE is nil the update only takes place if `exwm-title' is
+unset."
+  (exwm--log "#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (when (or force (not exwm-title))
+      (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:ewmh:get-_NET_WM_NAME :window id))))
+        (when reply                     ;nil when destroyed
+          (setq exwm-title (slot-value reply 'value))
+          (when exwm-title
+            (setq exwm--title-is-utf8 t)
+            (run-hooks 'exwm-update-title-hook)))))))
+
+(defun exwm--update-ctext-title (id &optional force)
+  "Update `exwm-title' from WM_NAME.
+Argument ID contains the X window of the `exwm-mode' buffer.
+
+When FORCE is nil the update only takes place if `exwm-title' is
+unset."
+  (exwm--log "#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (unless (or exwm--title-is-utf8
+                (and exwm-title (not force)))
+      (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:icccm:get-WM_NAME :window id))))
+        (when reply                     ;nil when destroyed
+          (setq exwm-title (slot-value reply 'value))
+          (when exwm-title
+            (run-hooks 'exwm-update-title-hook)))))))
+
+(defun exwm--update-title (id)
+  "Update _NET_WM_NAME or WM_NAME.
+Argument ID contains the X window of the `exwm-mode' buffer."
+  (exwm--log "#x%x" id)
+  (exwm--update-utf8-title id)
+  (exwm--update-ctext-title id))
+
+(defun exwm--update-transient-for (id &optional force)
+  "Update `exwm-transient-for' from WM_TRANSIENT_FOR.
+Argument ID contains the X window of the `exwm-mode' buffer.
+
+When FORCE is nil the update only takes place if `exwm-title' is
+unset."
+  (exwm--log "#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (unless (and exwm-transient-for (not force))
+      (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:icccm:get-WM_TRANSIENT_FOR
+                                      :window id))))
+        (when reply                     ;nil when destroyed
+          (setq exwm-transient-for (slot-value reply 'value)))))))
+
+(defun exwm--update-normal-hints (id &optional force)
+  "Update normal hints from WM_NORMAL_HINTS.
+Argument ID contains the X window of the `exwm-mode' buffer.
+
+When FORCE is nil the update only takes place all of
+`exwm--normal-hints-x exwm--normal-hints-y',
+`exwm--normal-hints-width exwm--normal-hints-height',
+`exwm--normal-hints-min-width exwm--normal-hints-min-height' and
+`exwm--normal-hints-max-width exwm--normal-hints-max-height' are
+unset."
+  (exwm--log "#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (unless (and (not force)
+                 (or exwm--normal-hints-x exwm--normal-hints-y
+                     exwm--normal-hints-width exwm--normal-hints-height
+                     exwm--normal-hints-min-width exwm--normal-hints-min-height
+                     exwm--normal-hints-max-width exwm--normal-hints-max-height
+                     ;; FIXME: other fields
+                     ))
+      (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:icccm:get-WM_NORMAL_HINTS
+                                      :window id))))
+        (when (and reply (slot-value reply 'flags)) ;nil when destroyed
+          (with-slots (flags x y width height min-width min-height max-width
+                             max-height base-width base-height ;; win-gravity
+                             )
+              reply
+            (unless (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:USPosition))
+              (setq exwm--normal-hints-x x exwm--normal-hints-y y))
+            (unless (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:USSize))
+              (setq exwm--normal-hints-width width
+                    exwm--normal-hints-height height))
+            (unless (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:PMinSize))
+              (setq exwm--normal-hints-min-width min-width
+                    exwm--normal-hints-min-height min-height))
+            (unless (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:PMaxSize))
+              (setq exwm--normal-hints-max-width max-width
+                    exwm--normal-hints-max-height max-height))
+            (unless (or exwm--normal-hints-min-width
+                        (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:PBaseSize)))
+              (setq exwm--normal-hints-min-width base-width
+                    exwm--normal-hints-min-height base-height))
+            ;; (unless (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:PWinGravity))
+            ;;   (setq exwm--normal-hints-win-gravity win-gravity))
+            (setq exwm--fixed-size
+                  (and exwm--normal-hints-min-width
+                       exwm--normal-hints-min-height
+                       exwm--normal-hints-max-width
+                       exwm--normal-hints-max-height
+                       (/= 0 exwm--normal-hints-min-width)
+                       (/= 0 exwm--normal-hints-min-height)
+                       (= exwm--normal-hints-min-width
+                          exwm--normal-hints-max-width)
+                       (= exwm--normal-hints-min-height
+                          exwm--normal-hints-max-height)))))))))
+
+(defun exwm--update-hints (id &optional force)
+  "Update hints from WM_HINTS.
+Argument ID contains the X window of the `exwm-mode' buffer.
+
+When FORCE is nil the update only takes place if both of
+`exwm--hints-input' and `exwm--hints-urgency' are unset."
+  (exwm--log "#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (unless (and (not force) exwm--hints-input exwm--hints-urgency)
+      (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:icccm:get-WM_HINTS :window id))))
+        (when (and reply (slot-value reply 'flags)) ;nil when destroyed
+          (with-slots (flags input initial-state) reply
+            (when flags
+              (unless (= 0 (logand flags xcb:icccm:WM_HINTS:InputHint))
+                (setq exwm--hints-input (when input (= 1 input))))
+              (unless (= 0 (logand flags xcb:icccm:WM_HINTS:StateHint))
+                (setq exwm-state initial-state))
+              (unless (= 0 (logand flags xcb:icccm:WM_HINTS:UrgencyHint))
+                (setq exwm--hints-urgency t))))
+          (when (and exwm--hints-urgency
+                     (not (eq exwm--frame exwm-workspace--current)))
+            (unless (frame-parameter exwm--frame 'exwm-urgency)
+              (set-frame-parameter exwm--frame 'exwm-urgency t)
+              (setq exwm-workspace--switch-history-outdated t))))))))
+
+(defun exwm--update-protocols (id &optional force)
+  "Update `exwm--protocols' from WM_PROTOCOLS.
+Argument ID contains the X window of the `exwm-mode' buffer.
+
+When FORCE is nil the update only takes place if `exwm--protocols'
+is unset."
+  (exwm--log "#x%x" id)
+  (with-current-buffer (exwm--id->buffer id)
+    (unless (and exwm--protocols (not force))
+      (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                       (make-instance 'xcb:icccm:get-WM_PROTOCOLS
+                                      :window id))))
+        (when reply                     ;nil when destroyed
+          (setq exwm--protocols (append (slot-value reply 'value) nil)))))))
+
+(defun exwm--update-struts-legacy (id)
+  "Update struts of X window ID from _NET_WM_STRUT."
+  (exwm--log "#x%x" id)
+  (let ((pair (assq id exwm-workspace--id-struts-alist))
+        reply struts)
+    (unless (and pair (< 4 (length (cdr pair))))
+      (setq reply (xcb:+request-unchecked+reply exwm--connection
+                      (make-instance 'xcb:ewmh:get-_NET_WM_STRUT
+                                     :window id)))
+      (when reply
+        (setq struts (slot-value reply 'value))
+        (if pair
+            (setcdr pair struts)
+          (push (cons id struts) exwm-workspace--id-struts-alist))
+        (exwm-workspace--update-struts))
+      ;; Update workareas.
+      (exwm-workspace--update-workareas)
+      ;; Update workspaces.
+      (dolist (f exwm-workspace--list)
+        (exwm-workspace--set-fullscreen f)))))
+
+(defun exwm--update-struts-partial (id)
+  "Update struts of X window ID from _NET_WM_STRUT_PARTIAL."
+  (exwm--log "#x%x" id)
+  (let ((reply (xcb:+request-unchecked+reply exwm--connection
+                   (make-instance 'xcb:ewmh:get-_NET_WM_STRUT_PARTIAL
+                                  :window id)))
+        struts pair)
+    (when reply
+      (setq struts (slot-value reply 'value)
+            pair (assq id exwm-workspace--id-struts-alist))
+      (if pair
+          (setcdr pair struts)
+        (push (cons id struts) exwm-workspace--id-struts-alist))
+      (exwm-workspace--update-struts))
+    ;; Update workareas.
+    (exwm-workspace--update-workareas)
+    ;; Update workspaces.
+    (dolist (f exwm-workspace--list)
+      (exwm-workspace--set-fullscreen f))))
+
+(defun exwm--update-struts (id)
+  "Update struts of X window ID from _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT."
+  (exwm--log "#x%x" id)
+  (exwm--update-struts-partial id)
+  (exwm--update-struts-legacy id))
+
+(defun exwm--on-PropertyNotify (data _synthetic)
+  "Handle PropertyNotify event.
+DATA contains unmarshalled PropertyNotify event data."
+  (let ((obj (make-instance 'xcb:PropertyNotify))
+        atom id buffer)
+    (xcb:unmarshal obj data)
+    (setq id (slot-value obj 'window)
+          atom (slot-value obj 'atom))
+    (exwm--log "atom=%s(%s)" (x-get-atom-name atom exwm-workspace--current) atom)
+    (setq buffer (exwm--id->buffer id))
+    (if (not (buffer-live-p buffer))
+        ;; Properties of unmanaged X windows.
+        (cond ((= atom xcb:Atom:_NET_WM_STRUT)
+               (exwm--update-struts-legacy id))
+              ((= atom xcb:Atom:_NET_WM_STRUT_PARTIAL)
+               (exwm--update-struts-partial id)))
+      (with-current-buffer buffer
+        (cond ((= atom xcb:Atom:_NET_WM_WINDOW_TYPE)
+               (exwm--update-window-type id t))
+              ((= atom xcb:Atom:WM_CLASS)
+               (exwm--update-class id t))
+              ((= atom xcb:Atom:_NET_WM_NAME)
+               (exwm--update-utf8-title id t))
+              ((= atom xcb:Atom:WM_NAME)
+               (exwm--update-ctext-title id t))
+              ((= atom xcb:Atom:WM_TRANSIENT_FOR)
+               (exwm--update-transient-for id t))
+              ((= atom xcb:Atom:WM_NORMAL_HINTS)
+               (exwm--update-normal-hints id t))
+              ((= atom xcb:Atom:WM_HINTS)
+               (exwm--update-hints id t))
+              ((= atom xcb:Atom:WM_PROTOCOLS)
+               (exwm--update-protocols id t))
+              ((= atom xcb:Atom:_NET_WM_USER_TIME)) ;ignored
+              (t
+               (exwm--log "Unhandled: %s(%d)"
+                          (x-get-atom-name atom exwm-workspace--current)
+                          atom)))))))
+
+(defun exwm--on-ClientMessage (raw-data _synthetic)
+  "Handle ClientMessage event.
+RAW-DATA contains unmarshalled ClientMessage event data."
+  (let ((obj (make-instance 'xcb:ClientMessage))
+        type id data)
+    (xcb:unmarshal obj raw-data)
+    (setq type (slot-value obj 'type)
+          id (slot-value obj 'window)
+          data (slot-value (slot-value obj 'data) 'data32))
+    (exwm--log "atom=%s(%s) id=#x%x data=%s" (x-get-atom-name type exwm-workspace--current)
+               type (or id 0) data)
+    (cond
+     ;; _NET_NUMBER_OF_DESKTOPS.
+     ((= type xcb:Atom:_NET_NUMBER_OF_DESKTOPS)
+      (let ((current (exwm-workspace--count))
+            (requested (elt data 0)))
+        ;; Only allow increasing/decreasing the workspace number by 1.
+        (cond
+         ((< current requested)
+          (make-frame))
+         ((and (> current requested)
+               (> current 1))
+          (let ((frame (car (last exwm-workspace--list))))
+            (delete-frame frame))))))
+     ;; _NET_CURRENT_DESKTOP.
+     ((= type xcb:Atom:_NET_CURRENT_DESKTOP)
+      (exwm-workspace-switch (elt data 0)))
+     ;; _NET_ACTIVE_WINDOW.
+     ((= type xcb:Atom:_NET_ACTIVE_WINDOW)
+      (let ((buffer (exwm--id->buffer id))
+            iconic window)
+        (if (buffer-live-p buffer)
+          ;; Either an `exwm-mode' buffer (an X window) or a floating frame.
+          (with-current-buffer buffer
+            (when (eq exwm--frame exwm-workspace--current)
+              (if exwm--floating-frame
+                  (select-frame exwm--floating-frame)
+                (setq iconic (exwm-layout--iconic-state-p))
+                (when iconic
+                  ;; State change: iconic => normal.
+                  (set-window-buffer (frame-selected-window exwm--frame)
+                                     (current-buffer)))
+                ;; Focus transfer.
+                (setq window (get-buffer-window nil t))
+                (when (or iconic
+                          (not (eq window (selected-window))))
+                  (select-window window)))))
+          ;; A workspace.
+          (dolist (f exwm-workspace--list)
+            (when (eq id (frame-parameter f 'exwm-outer-id))
+              (x-focus-frame f t))))))
+     ;; _NET_CLOSE_WINDOW.
+     ((= type xcb:Atom:_NET_CLOSE_WINDOW)
+      (let ((buffer (exwm--id->buffer id)))
+        (when (buffer-live-p buffer)
+          (exwm--defer 0 #'kill-buffer buffer))))
+     ;; _NET_WM_MOVERESIZE
+     ((= type xcb:Atom:_NET_WM_MOVERESIZE)
+      (let ((direction (elt data 2))
+            (buffer (exwm--id->buffer id)))
+        (unless (and buffer
+                     (not (buffer-local-value 'exwm--floating-frame buffer)))
+          (cond ((= direction
+                    xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_KEYBOARD)
+                 ;; FIXME
+                 )
+                ((= direction
+                    xcb:ewmh:_NET_WM_MOVERESIZE_MOVE_KEYBOARD)
+                 ;; FIXME
+                 )
+                ((= direction xcb:ewmh:_NET_WM_MOVERESIZE_CANCEL)
+                 (exwm-floating--stop-moveresize))
+                ;; In case it's a workspace frame.
+                ((and (not buffer)
+                      (catch 'break
+                        (dolist (f exwm-workspace--list)
+                          (when (or (eq id (frame-parameter f 'exwm-outer-id))
+                                    (eq id (frame-parameter f 'exwm-id)))
+                            (throw 'break t)))
+                        nil)))
+                (t
+                 ;; In case it's a floating frame,
+                 ;; move the corresponding X window instead.
+                 (unless buffer
+                   (catch 'break
+                     (dolist (pair exwm--id-buffer-alist)
+                       (with-current-buffer (cdr pair)
+                         (when
+                             (and exwm--floating-frame
+                                  (or (eq id
+                                          (frame-parameter exwm--floating-frame
+                                                           'exwm-outer-id))
+                                      (eq id
+                                          (frame-parameter exwm--floating-frame
+                                                           'exwm-id))))
+                           (setq id exwm--id)
+                           (throw 'break nil))))))
+                 ;; Start to move it.
+                 (exwm-floating--start-moveresize id direction))))))
+     ;; _NET_REQUEST_FRAME_EXTENTS
+     ((= type xcb:Atom:_NET_REQUEST_FRAME_EXTENTS)
+      (let ((buffer (exwm--id->buffer id))
+            top btm)
+        (if (or (not buffer)
+                (not (buffer-local-value 'exwm--floating-frame buffer)))
+            (setq top 0
+                  btm 0)
+          (setq top (window-header-line-height)
+                btm (window-mode-line-height)))
+        (xcb:+request exwm--connection
+            (make-instance 'xcb:ewmh:set-_NET_FRAME_EXTENTS
+                           :window id
+                           :left 0
+                           :right 0
+                           :top top
+                           :bottom btm)))
+      (xcb:flush exwm--connection))
+     ;; _NET_WM_DESKTOP.
+     ((= type xcb:Atom:_NET_WM_DESKTOP)
+      (let ((buffer (exwm--id->buffer id)))
+        (when (buffer-live-p buffer)
+          (exwm-workspace-move-window (elt data 0) id))))
+     ;; _NET_WM_STATE
+     ((= type xcb:Atom:_NET_WM_STATE)
+      (let ((action (elt data 0))
+            (props (list (elt data 1) (elt data 2)))
+            (buffer (exwm--id->buffer id))
+            props-new)
+        ;; only support _NET_WM_STATE_FULLSCREEN / _NET_WM_STATE_ADD for frames
+        (when (and (not buffer)
+                   (memq xcb:Atom:_NET_WM_STATE_FULLSCREEN props)
+                   (= action xcb:ewmh:_NET_WM_STATE_ADD))
+          (xcb:+request
+              exwm--connection
+              (make-instance 'xcb:ewmh:set-_NET_WM_STATE
+                             :window id
+                             :data (vector xcb:Atom:_NET_WM_STATE_FULLSCREEN)))
+          (xcb:flush exwm--connection))
+        (when buffer                    ;ensure it's managed
+          (with-current-buffer buffer
+            ;; _NET_WM_STATE_FULLSCREEN
+            (when (or (memq xcb:Atom:_NET_WM_STATE_FULLSCREEN props)
+                      (memq xcb:Atom:_NET_WM_STATE_ABOVE props))
+              (cond ((= action xcb:ewmh:_NET_WM_STATE_ADD)
+                     (unless (exwm-layout--fullscreen-p)
+                       (exwm-layout-set-fullscreen id))
+                     (push xcb:Atom:_NET_WM_STATE_FULLSCREEN props-new))
+                    ((= action xcb:ewmh:_NET_WM_STATE_REMOVE)
+                     (when (exwm-layout--fullscreen-p)
+                       (exwm-layout-unset-fullscreen id)))
+                    ((= action xcb:ewmh:_NET_WM_STATE_TOGGLE)
+                     (if (exwm-layout--fullscreen-p)
+                         (exwm-layout-unset-fullscreen id)
+                       (exwm-layout-set-fullscreen id)
+                       (push xcb:Atom:_NET_WM_STATE_FULLSCREEN props-new)))))
+            ;; _NET_WM_STATE_DEMANDS_ATTENTION
+            ;; FIXME: check (may require other properties set)
+            (when (memq xcb:Atom:_NET_WM_STATE_DEMANDS_ATTENTION props)
+              (when (= action xcb:ewmh:_NET_WM_STATE_ADD)
+                (unless (eq exwm--frame exwm-workspace--current)
+                  (set-frame-parameter exwm--frame 'exwm-urgency t)
+                  (setq exwm-workspace--switch-history-outdated t)))
+              ;; xcb:ewmh:_NET_WM_STATE_REMOVE?
+              ;; xcb:ewmh:_NET_WM_STATE_TOGGLE?
+              )
+            (xcb:+request exwm--connection
+                (make-instance 'xcb:ewmh:set-_NET_WM_STATE
+                               :window id :data (vconcat props-new)))
+            (xcb:flush exwm--connection)))))
+     ((= type xcb:Atom:WM_PROTOCOLS)
+      (let ((type (elt data 0)))
+        (cond ((= type xcb:Atom:_NET_WM_PING)
+               (setq exwm-manage--ping-lock nil))
+              (t (exwm--log "Unhandled WM_PROTOCOLS of type: %d" type)))))
+     ((= type xcb:Atom:WM_CHANGE_STATE)
+      (let ((buffer (exwm--id->buffer id)))
+        (when (and (buffer-live-p buffer)
+                   (= (elt data 0) xcb:icccm:WM_STATE:IconicState))
+          (with-current-buffer buffer
+            (if exwm--floating-frame
+                (call-interactively #'exwm-floating-hide)
+              (bury-buffer))))))
+     (t
+      (exwm--log "Unhandled: %s(%d)"
+                 (x-get-atom-name type exwm-workspace--current) type)))))
+
+(defun exwm--on-SelectionClear (data _synthetic)
+  "Handle SelectionClear events.
+DATA contains unmarshalled SelectionClear event data."
+  (exwm--log)
+  (let ((obj (make-instance 'xcb:SelectionClear))
+        owner selection)
+    (xcb:unmarshal obj data)
+    (setq owner (slot-value obj 'owner)
+          selection (slot-value obj 'selection))
+    (when (and (eq owner exwm--wmsn-window)
+               (eq selection xcb:Atom:WM_S0))
+      (exwm-exit))))
+
+(defun exwm--on-delete-terminal (terminal)
+  "Handle terminal being deleted without Emacs being killed.
+This function is Hooked to `delete-terminal-functions'.
+
+TERMINAL is the terminal being (or that has been) deleted.
+
+This may happen when invoking `save-buffers-kill-terminal' within an emacsclient
+session."
+  (when (eq terminal exwm--terminal)
+    (exwm-exit)))
+
+(defun exwm--init-icccm-ewmh ()
+  "Initialize ICCCM/EWMH support."
+  (exwm--log)
+  ;; Handle PropertyNotify event
+  (xcb:+event exwm--connection 'xcb:PropertyNotify #'exwm--on-PropertyNotify)
+  ;; Handle relevant client messages
+  (xcb:+event exwm--connection 'xcb:ClientMessage #'exwm--on-ClientMessage)
+  ;; Handle SelectionClear
+  (xcb:+event exwm--connection 'xcb:SelectionClear #'exwm--on-SelectionClear)
+  ;; Set _NET_SUPPORTED
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ewmh:set-_NET_SUPPORTED
+                     :window exwm--root
+                     :data (vector
+                            ;; Root windows properties.
+                            xcb:Atom:_NET_SUPPORTED
+                            xcb:Atom:_NET_CLIENT_LIST
+                            xcb:Atom:_NET_CLIENT_LIST_STACKING
+                            xcb:Atom:_NET_NUMBER_OF_DESKTOPS
+                            xcb:Atom:_NET_DESKTOP_GEOMETRY
+                            xcb:Atom:_NET_DESKTOP_VIEWPORT
+                            xcb:Atom:_NET_CURRENT_DESKTOP
+                            ;; xcb:Atom:_NET_DESKTOP_NAMES
+                            xcb:Atom:_NET_ACTIVE_WINDOW
+                            ;; xcb:Atom:_NET_WORKAREA
+                            xcb:Atom:_NET_SUPPORTING_WM_CHECK
+                            ;; xcb:Atom:_NET_VIRTUAL_ROOTS
+                            ;; xcb:Atom:_NET_DESKTOP_LAYOUT
+                            ;; xcb:Atom:_NET_SHOWING_DESKTOP
+
+                            ;; Other root window messages.
+                            xcb:Atom:_NET_CLOSE_WINDOW
+                            ;; xcb:Atom:_NET_MOVERESIZE_WINDOW
+                            xcb:Atom:_NET_WM_MOVERESIZE
+                            ;; xcb:Atom:_NET_RESTACK_WINDOW
+                            xcb:Atom:_NET_REQUEST_FRAME_EXTENTS
+
+                            ;; Application window properties.
+                            xcb:Atom:_NET_WM_NAME
+                            ;; xcb:Atom:_NET_WM_VISIBLE_NAME
+                            ;; xcb:Atom:_NET_WM_ICON_NAME
+                            ;; xcb:Atom:_NET_WM_VISIBLE_ICON_NAME
+                            xcb:Atom:_NET_WM_DESKTOP
+                            ;;
+                            xcb:Atom:_NET_WM_WINDOW_TYPE
+                            ;; xcb:Atom:_NET_WM_WINDOW_TYPE_DESKTOP
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_DOCK
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_TOOLBAR
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_MENU
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_SPLASH
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_DROPDOWN_MENU
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_POPUP_MENU
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_TOOLTIP
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_NOTIFICATION
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_COMBO
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_DND
+                            xcb:Atom:_NET_WM_WINDOW_TYPE_NORMAL
+                            ;;
+                            xcb:Atom:_NET_WM_STATE
+                            ;; xcb:Atom:_NET_WM_STATE_MODAL
+                            ;; xcb:Atom:_NET_WM_STATE_STICKY
+                            ;; xcb:Atom:_NET_WM_STATE_MAXIMIZED_VERT
+                            ;; xcb:Atom:_NET_WM_STATE_MAXIMIZED_HORZ
+                            ;; xcb:Atom:_NET_WM_STATE_SHADED
+                            ;; xcb:Atom:_NET_WM_STATE_SKIP_TASKBAR
+                            ;; xcb:Atom:_NET_WM_STATE_SKIP_PAGER
+                            xcb:Atom:_NET_WM_STATE_HIDDEN
+                            xcb:Atom:_NET_WM_STATE_FULLSCREEN
+                            ;; xcb:Atom:_NET_WM_STATE_ABOVE
+                            ;; xcb:Atom:_NET_WM_STATE_BELOW
+                            xcb:Atom:_NET_WM_STATE_DEMANDS_ATTENTION
+                            ;; xcb:Atom:_NET_WM_STATE_FOCUSED
+                            ;;
+                            xcb:Atom:_NET_WM_ALLOWED_ACTIONS
+                            xcb:Atom:_NET_WM_ACTION_MOVE
+                            xcb:Atom:_NET_WM_ACTION_RESIZE
+                            xcb:Atom:_NET_WM_ACTION_MINIMIZE
+                            ;; xcb:Atom:_NET_WM_ACTION_SHADE
+                            ;; xcb:Atom:_NET_WM_ACTION_STICK
+                            ;; xcb:Atom:_NET_WM_ACTION_MAXIMIZE_HORZ
+                            ;; xcb:Atom:_NET_WM_ACTION_MAXIMIZE_VERT
+                            xcb:Atom:_NET_WM_ACTION_FULLSCREEN
+                            xcb:Atom:_NET_WM_ACTION_CHANGE_DESKTOP
+                            xcb:Atom:_NET_WM_ACTION_CLOSE
+                            ;; xcb:Atom:_NET_WM_ACTION_ABOVE
+                            ;; xcb:Atom:_NET_WM_ACTION_BELOW
+                            ;;
+                            xcb:Atom:_NET_WM_STRUT
+                            xcb:Atom:_NET_WM_STRUT_PARTIAL
+                            ;; xcb:Atom:_NET_WM_ICON_GEOMETRY
+                            ;; xcb:Atom:_NET_WM_ICON
+                            xcb:Atom:_NET_WM_PID
+                            ;; xcb:Atom:_NET_WM_HANDLED_ICONS
+                            ;; xcb:Atom:_NET_WM_USER_TIME
+                            ;; xcb:Atom:_NET_WM_USER_TIME_WINDOW
+                            xcb:Atom:_NET_FRAME_EXTENTS
+                            ;; xcb:Atom:_NET_WM_OPAQUE_REGION
+                            ;; xcb:Atom:_NET_WM_BYPASS_COMPOSITOR
+
+                            ;; Window manager protocols.
+                            xcb:Atom:_NET_WM_PING
+                            ;; xcb:Atom:_NET_WM_SYNC_REQUEST
+                            ;; xcb:Atom:_NET_WM_FULLSCREEN_MONITORS
+
+                            ;; Other properties.
+                            xcb:Atom:_NET_WM_FULL_PLACEMENT)))
+  ;; Create a child window for setting _NET_SUPPORTING_WM_CHECK
+  (let ((new-id (xcb:generate-id exwm--connection)))
+    (setq exwm--guide-window new-id)
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:CreateWindow
+                       :depth 0
+                       :wid new-id
+                       :parent exwm--root
+                       :x -1
+                       :y -1
+                       :width 1
+                       :height 1
+                       :border-width 0
+                       :class xcb:WindowClass:InputOnly
+                       :visual 0
+                       :value-mask xcb:CW:OverrideRedirect
+                       :override-redirect 1))
+    ;; Set _NET_WM_NAME.  Must be set to the name of the window manager, as
+    ;; required by wm-spec.
+    (xcb:+request exwm--connection
+        (make-instance 'xcb:ewmh:set-_NET_WM_NAME
+                       :window new-id :data "EXWM"))
+    (dolist (i (list exwm--root new-id))
+      ;; Set _NET_SUPPORTING_WM_CHECK
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ewmh:set-_NET_SUPPORTING_WM_CHECK
+                         :window i :data new-id))))
+  ;; Set _NET_DESKTOP_VIEWPORT (we don't support large desktop).
+  (xcb:+request exwm--connection
+      (make-instance 'xcb:ewmh:set-_NET_DESKTOP_VIEWPORT
+                     :window exwm--root
+                     :data [0 0]))
+  (xcb:flush exwm--connection))
+
+(defun exwm--wmsn-acquire (replace)
+  "Acquire the WM_Sn selection.
+
+REPLACE specifies what to do in case there already is a window
+manager.  If t, replace it, if nil, abort and ask the user if `ask'."
+  (exwm--log "%s" replace)
+  (with-slots (owner)
+      (xcb:+request-unchecked+reply exwm--connection
+          (make-instance 'xcb:GetSelectionOwner
+                         :selection xcb:Atom:WM_S0))
+    (when (/= owner xcb:Window:None)
+      (when (eq replace 'ask)
+        (setq replace (yes-or-no-p "Replace existing window manager? ")))
+      (when (not replace)
+        (user-error "Other window manager detected")))
+    (let ((new-owner (xcb:generate-id exwm--connection)))
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:CreateWindow
+                         :depth 0
+                         :wid new-owner
+                         :parent exwm--root
+                         :x -1
+                         :y -1
+                         :width 1
+                         :height 1
+                         :border-width 0
+                         :class xcb:WindowClass:CopyFromParent
+                         :visual 0
+                         :value-mask 0
+                         :override-redirect 0))
+      (xcb:+request exwm--connection
+          (make-instance 'xcb:ewmh:set-_NET_WM_NAME
+                         :window new-owner :data "EXWM: exwm--wmsn-window"))
+      (xcb:+request-checked+request-check exwm--connection
+          (make-instance 'xcb:SetSelectionOwner
+                         :selection xcb:Atom:WM_S0
+                         :owner new-owner
+                         :time xcb:Time:CurrentTime))
+      (with-slots (owner)
+          (xcb:+request-unchecked+reply exwm--connection
+              (make-instance 'xcb:GetSelectionOwner
+                             :selection xcb:Atom:WM_S0))
+        (unless (eq owner new-owner)
+          (error "Could not acquire ownership of WM selection")))
+      ;; Wait for the other window manager to terminate.
+      (when (/= owner xcb:Window:None)
+        (let (reply)
+          (cl-dotimes (i exwm--wmsn-acquire-timeout)
+            (setq reply (xcb:+request-unchecked+reply exwm--connection
+                            (make-instance 'xcb:GetGeometry :drawable owner)))
+            (when (not reply)
+              (cl-return))
+            (message "Waiting for other window manager to quit... %ds" i)
+            (sleep-for 1))
+          (when reply
+            (error "Other window manager did not release selection in time"))))
+      ;; announce
+      (let* ((cmd (make-instance 'xcb:ClientMessageData
+                                 :data32 (vector xcb:Time:CurrentTime
+                                                 xcb:Atom:WM_S0
+                                                 new-owner
+                                                 0
+                                                 0)))
+             (cm (make-instance 'xcb:ClientMessage
+                                               :window exwm--root
+                                               :format 32
+                                               :type xcb:Atom:MANAGER
+                                               :data cmd))
+             (se (make-instance 'xcb:SendEvent
+                         :propagate 0
+                         :destination exwm--root
+                         :event-mask xcb:EventMask:NoEvent
+                         :event (xcb:marshal cm exwm--connection))))
+        (xcb:+request exwm--connection se))
+      (setq exwm--wmsn-window new-owner))))
+
+;;;###autoload
+(cl-defun exwm-init (&optional frame)
+  "Initialize EXWM.
+FRAME, if given, indicates the X display EXWM should manage."
+  (interactive)
+  (exwm--log "%s" frame)
+  (if frame
+      ;; The frame might not be selected if it's created by emacslicnet.
+      (select-frame-set-input-focus frame)
+    (setq frame (selected-frame)))
+  (when (not (eq 'x (framep frame)))
+    (message "[EXWM] Not running under X environment")
+    (cl-return-from exwm-init))
+  (when exwm--connection
+    (exwm--log "EXWM already running")
+    (cl-return-from exwm-init))
+  (condition-case err
+      (progn
+        (exwm-enable 'undo)               ;never initialize again
+        (setq exwm--terminal (frame-terminal frame))
+        (setq exwm--connection (xcb:connect))
+        (set-process-query-on-exit-flag (slot-value exwm--connection 'process)
+                                        nil) ;prevent query message on exit
+        (setq exwm--root
+              (slot-value (car (slot-value
+                                (xcb:get-setup exwm--connection) 'roots))
+                          'root))
+        ;; Initialize ICCCM/EWMH support
+        (xcb:icccm:init exwm--connection t)
+        (xcb:ewmh:init exwm--connection t)
+        ;; Try to register window manager selection.
+        (exwm--wmsn-acquire exwm-replace)
+        (when (xcb:+request-checked+request-check exwm--connection
+                  (make-instance 'xcb:ChangeWindowAttributes
+                                 :window exwm--root
+                                 :value-mask xcb:CW:EventMask
+                                 :event-mask
+                                 xcb:EventMask:SubstructureRedirect))
+          (error "Other window manager is running"))
+        ;; Disable some features not working well with EXWM
+        (setq use-dialog-box nil
+              confirm-kill-emacs #'exwm--confirm-kill-emacs)
+        (advice-add 'save-buffers-kill-terminal
+                    :before-while #'exwm--confirm-kill-terminal)
+        ;; Clean up if the terminal is deleted.
+        (add-hook 'delete-terminal-functions 'exwm--on-delete-terminal)
+        (exwm--lock)
+        (exwm--init-icccm-ewmh)
+        (exwm-layout--init)
+        (exwm-floating--init)
+        (exwm-manage--init)
+        (exwm-workspace--init)
+        (exwm-input--init)
+        (exwm--unlock)
+        (exwm-workspace--post-init)
+        (exwm-input--post-init)
+        (run-hooks 'exwm-init-hook)
+        ;; Manage existing windows
+        (exwm-manage--scan))
+    (user-error)
+    ((quit error)
+     (exwm-exit)
+     ;; Rethrow error
+     (warn "[EXWM] EXWM fails to start (%s: %s)" (car err) (cdr err)))))
+
+
+;;;###autoload
+(defun exwm-exit ()
+  "Exit EXWM."
+  (interactive)
+  (exwm--log)
+  (run-hooks 'exwm-exit-hook)
+  (setq confirm-kill-emacs nil)
+  ;; Exit modules.
+  (when exwm--connection
+    (exwm-input--exit)
+    (exwm-manage--exit)
+    (exwm-workspace--exit)
+    (exwm-floating--exit)
+    (exwm-layout--exit)
+    (xcb:flush exwm--connection)
+    (xcb:disconnect exwm--connection))
+  (setq exwm--connection nil)
+  (setq exwm--terminal nil)
+  (exwm--log "Exited"))
+
+;;;###autoload
+(defun exwm-enable (&optional undo)
+  "Enable/Disable EXWM."
+  (exwm--log "%s" undo)
+  (pcase undo
+    (`undo                              ;prevent reinitialization
+     (remove-hook 'window-setup-hook #'exwm-init)
+     (remove-hook 'after-make-frame-functions #'exwm-init))
+    (`undo-all                          ;attempt to revert everything
+     (remove-hook 'window-setup-hook #'exwm-init)
+     (remove-hook 'after-make-frame-functions #'exwm-init)
+     (remove-hook 'kill-emacs-hook #'exwm--server-stop)
+     (dolist (i exwm-blocking-subrs)
+       (advice-remove i #'exwm--server-eval-at)))
+    (_                                  ;enable EXWM
+     (setq frame-resize-pixelwise t     ;mandatory; before init
+           window-resize-pixelwise t)
+     ;; Ignore unrecognized command line arguments.  This can be helpful
+     ;; when EXWM is launched by some session manager.
+     (push #'vector command-line-functions)
+     ;; In case EXWM is to be started from a graphical Emacs instance.
+     (add-hook 'window-setup-hook #'exwm-init t)
+     ;; In case EXWM is to be started with emacsclient.
+     (add-hook 'after-make-frame-functions #'exwm-init t)
+     ;; Manage the subordinate Emacs server.
+     (add-hook 'kill-emacs-hook #'exwm--server-stop)
+     (dolist (i exwm-blocking-subrs)
+       (advice-add i :around #'exwm--server-eval-at)))))
+
+(defun exwm--server-stop ()
+  "Stop the subordinate Emacs server."
+  (exwm--log)
+  (when exwm--server-process
+    (when (process-live-p exwm--server-process)
+      (cl-loop
+       initially (signal-process exwm--server-process 'TERM)
+       while     (process-live-p exwm--server-process)
+       repeat    (* 10 exwm--server-timeout)
+       do        (sit-for 0.1)))
+    (delete-process exwm--server-process)
+    (setq exwm--server-process nil)))
+
+(defun exwm--server-eval-at (function &rest args)
+  "Wrapper of `server-eval-at' used to advice subrs.
+FUNCTION is the function to be evaluated, ARGS are the arguments."
+  ;; Start the subordinate Emacs server if it's not alive
+  (exwm--log "%s %s" function args)
+  (unless (server-running-p exwm--server-name)
+    (when exwm--server-process (delete-process exwm--server-process))
+    (setq exwm--server-process
+          (start-process exwm--server-name
+                         nil
+                         (car command-line-args) ;The executable file
+                         "-d" (frame-parameter nil 'display)
+                         "-Q"
+                         (concat "--fg-daemon=" exwm--server-name)
+                         "--eval"
+                         ;; Create an invisible frame
+                         "(make-frame '((window-system . x) (visibility)))"))
+    (while (not (server-running-p exwm--server-name))
+      (sit-for 0.001)))
+  (server-eval-at
+   exwm--server-name
+   `(progn (select-frame (car (frame-list)))
+           (let ((result ,(nconc (list (make-symbol (subr-name function)))
+                                 args)))
+             (pcase (type-of result)
+               ;; Return the name of a buffer
+               (`buffer (buffer-name result))
+               ;; We blindly convert all font objects to their XLFD names. This
+               ;; might cause problems of course, but it still has a chance to
+               ;; work (whereas directly passing font objects would merely
+               ;; raise errors).
+               ((or `font-entity `font-object `font-spec)
+                (font-xlfd-name result))
+               ;; Passing following types makes little sense
+               ((or `compiled-function `finalizer `frame `hash-table `marker
+                    `overlay `process `window `window-configuration))
+               ;; Passing the name of a subr
+               (`subr (make-symbol (subr-name result)))
+               ;; For other types, return the value as-is.
+               (t result))))))
+
+(defun exwm--confirm-kill-terminal (&optional _)
+  "Confirm before killing terminal."
+  ;; This is invoked instead of `save-buffers-kill-emacs' (C-x C-c) on client
+  ;; frames.
+  (if (exwm--terminal-p)
+      (exwm--confirm-kill-emacs "Kill terminal?")
+    t))
+
+(defun exwm--confirm-kill-emacs (prompt &optional force)
+  "Confirm before exiting Emacs.
+PROMPT a reason to present to the user.
+If FORCE is nil, ask the user for confirmation.
+If FORCE is the symbol `no-check', ask if there are unsaved buffers.
+If FORCE is any other non-nil value, force killing of Emacs."
+  (exwm--log)
+  (when (cond
+         ((and force (not (eq force 'no-check)))
+          ;; Force killing Emacs.
+          t)
+         ((or (eq force 'no-check) (not exwm--id-buffer-alist))
+          ;; Check if there's any unsaved file.
+          (pcase (catch 'break
+                   (let ((kill-emacs-query-functions
+                          (append kill-emacs-query-functions
+                                  (list (lambda ()
+                                          (throw 'break 'break))))))
+                     (save-buffers-kill-emacs)))
+            (`break (y-or-n-p prompt))
+            (x x)))
+         (t
+          (yes-or-no-p (format "[EXWM] %d X window(s) will be destroyed.  %s"
+                               (length exwm--id-buffer-alist) prompt))))
+    ;; Run `kill-emacs-hook' (`server-force-stop' excluded) before Emacs
+    ;; frames are unmapped so that errors (if any) can be visible.
+    (if (memq #'server-force-stop kill-emacs-hook)
+        (progn
+          (setq kill-emacs-hook (delq #'server-force-stop kill-emacs-hook))
+          (run-hooks 'kill-emacs-hook)
+          (setq kill-emacs-hook (list #'server-force-stop)))
+      (run-hooks 'kill-emacs-hook)
+      (setq kill-emacs-hook nil))
+    ;; Exit each module, destroying all resources created by this connection.
+    (exwm-exit)
+    ;; Set the return value.
+    t))
+
+
+
+(provide 'exwm)
+
+;;; exwm.el ends here
diff --git a/favicon.ico b/favicon.ico
deleted file mode 100644
index 56ff59384f..0000000000
--- a/favicon.ico
+++ /dev/null
Binary files differdiff --git a/filter.c b/filter.c
deleted file mode 100644
index 70f5b74998..0000000000
--- a/filter.c
+++ /dev/null
@@ -1,457 +0,0 @@
-/* filter.c: filter framework functions
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "html.h"
-#ifndef NO_LUA
-#include <dlfcn.h>
-#include <lua.h>
-#include <lualib.h>
-#include <lauxlib.h>
-#endif
-
-static inline void reap_filter(struct cgit_filter *filter)
-{
-	if (filter && filter->cleanup)
-		filter->cleanup(filter);
-}
-
-void cgit_cleanup_filters(void)
-{
-	int i;
-	reap_filter(ctx.cfg.about_filter);
-	reap_filter(ctx.cfg.commit_filter);
-	reap_filter(ctx.cfg.source_filter);
-	reap_filter(ctx.cfg.email_filter);
-	reap_filter(ctx.cfg.owner_filter);
-	reap_filter(ctx.cfg.auth_filter);
-	for (i = 0; i < cgit_repolist.count; ++i) {
-		reap_filter(cgit_repolist.repos[i].about_filter);
-		reap_filter(cgit_repolist.repos[i].commit_filter);
-		reap_filter(cgit_repolist.repos[i].source_filter);
-		reap_filter(cgit_repolist.repos[i].email_filter);
-		reap_filter(cgit_repolist.repos[i].owner_filter);
-	}
-}
-
-static int open_exec_filter(struct cgit_filter *base, va_list ap)
-{
-	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
-	int pipe_fh[2];
-	int i;
-
-	for (i = 0; i < filter->base.argument_count; i++)
-		filter->argv[i + 1] = va_arg(ap, char *);
-
-	filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
-		"Unable to duplicate STDOUT");
-	chk_zero(pipe(pipe_fh), "Unable to create pipe to subprocess");
-	filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
-	if (filter->pid == 0) {
-		close(pipe_fh[1]);
-		chk_non_negative(dup2(pipe_fh[0], STDIN_FILENO),
-			"Unable to use pipe as STDIN");
-		execvp(filter->cmd, filter->argv);
-		die_errno("Unable to exec subprocess %s", filter->cmd);
-	}
-	close(pipe_fh[0]);
-	chk_non_negative(dup2(pipe_fh[1], STDOUT_FILENO),
-		"Unable to use pipe as STDOUT");
-	close(pipe_fh[1]);
-	return 0;
-}
-
-static int close_exec_filter(struct cgit_filter *base)
-{
-	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
-	int i, exit_status = 0;
-
-	chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
-		"Unable to restore STDOUT");
-	close(filter->old_stdout);
-	if (filter->pid < 0)
-		goto done;
-	waitpid(filter->pid, &exit_status, 0);
-	if (WIFEXITED(exit_status))
-		goto done;
-	die("Subprocess %s exited abnormally", filter->cmd);
-
-done:
-	for (i = 0; i < filter->base.argument_count; i++)
-		filter->argv[i + 1] = NULL;
-	return WEXITSTATUS(exit_status);
-
-}
-
-static void fprintf_exec_filter(struct cgit_filter *base, FILE *f, const char *prefix)
-{
-	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
-	fprintf(f, "%sexec:%s\n", prefix, filter->cmd);
-}
-
-static void cleanup_exec_filter(struct cgit_filter *base)
-{
-	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
-	if (filter->argv) {
-		free(filter->argv);
-		filter->argv = NULL;
-	}
-	if (filter->cmd) {
-		free(filter->cmd);
-		filter->cmd = NULL;
-	}
-}
-
-static struct cgit_filter *new_exec_filter(const char *cmd, int argument_count)
-{
-	struct cgit_exec_filter *f;
-	int args_size = 0;
-
-	f = xmalloc(sizeof(*f));
-	/* We leave argv for now and assign it below. */
-	cgit_exec_filter_init(f, xstrdup(cmd), NULL);
-	f->base.argument_count = argument_count;
-	args_size = (2 + argument_count) * sizeof(char *);
-	f->argv = xmalloc(args_size);
-	memset(f->argv, 0, args_size);
-	f->argv[0] = f->cmd;
-	return &f->base;
-}
-
-void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv)
-{
-	memset(filter, 0, sizeof(*filter));
-	filter->base.open = open_exec_filter;
-	filter->base.close = close_exec_filter;
-	filter->base.fprintf = fprintf_exec_filter;
-	filter->base.cleanup = cleanup_exec_filter;
-	filter->cmd = cmd;
-	filter->argv = argv;
-	/* The argument count for open_filter is zero by default, unless called from new_filter, above. */
-	filter->base.argument_count = 0;
-}
-
-#ifdef NO_LUA
-void cgit_init_filters(void)
-{
-}
-#endif
-
-#ifndef NO_LUA
-static ssize_t (*libc_write)(int fd, const void *buf, size_t count);
-static ssize_t (*filter_write)(struct cgit_filter *base, const void *buf, size_t count) = NULL;
-static struct cgit_filter *current_write_filter = NULL;
-
-void cgit_init_filters(void)
-{
-	libc_write = dlsym(RTLD_NEXT, "write");
-	if (!libc_write)
-		die("Could not locate libc's write function");
-}
-
-ssize_t write(int fd, const void *buf, size_t count)
-{
-	if (fd != STDOUT_FILENO || !filter_write)
-		return libc_write(fd, buf, count);
-	return filter_write(current_write_filter, buf, count);
-}
-
-static inline void hook_write(struct cgit_filter *filter, ssize_t (*new_write)(struct cgit_filter *base, const void *buf, size_t count))
-{
-	/* We want to avoid buggy nested patterns. */
-	assert(filter_write == NULL);
-	assert(current_write_filter == NULL);
-	current_write_filter = filter;
-	filter_write = new_write;
-}
-
-static inline void unhook_write(void)
-{
-	assert(filter_write != NULL);
-	assert(current_write_filter != NULL);
-	filter_write = NULL;
-	current_write_filter = NULL;
-}
-
-struct lua_filter {
-	struct cgit_filter base;
-	char *script_file;
-	lua_State *lua_state;
-};
-
-static void error_lua_filter(struct lua_filter *filter)
-{
-	die("Lua error in %s: %s", filter->script_file, lua_tostring(filter->lua_state, -1));
-	lua_pop(filter->lua_state, 1);
-}
-
-static ssize_t write_lua_filter(struct cgit_filter *base, const void *buf, size_t count)
-{
-	struct lua_filter *filter = (struct lua_filter *)base;
-
-	lua_getglobal(filter->lua_state, "filter_write");
-	lua_pushlstring(filter->lua_state, buf, count);
-	if (lua_pcall(filter->lua_state, 1, 0, 0)) {
-		error_lua_filter(filter);
-		errno = EIO;
-		return -1;
-	}
-	return count;
-}
-
-static inline int hook_lua_filter(lua_State *lua_state, void (*fn)(const char *txt))
-{
-	const char *str;
-	ssize_t (*save_filter_write)(struct cgit_filter *base, const void *buf, size_t count);
-	struct cgit_filter *save_filter;
-
-	str = lua_tostring(lua_state, 1);
-	if (!str)
-		return 0;
-
-	save_filter_write = filter_write;
-	save_filter = current_write_filter;
-	unhook_write();
-	fn(str);
-	hook_write(save_filter, save_filter_write);
-
-	return 0;
-}
-
-static int html_lua_filter(lua_State *lua_state)
-{
-	return hook_lua_filter(lua_state, html);
-}
-
-static int html_txt_lua_filter(lua_State *lua_state)
-{
-	return hook_lua_filter(lua_state, html_txt);
-}
-
-static int html_attr_lua_filter(lua_State *lua_state)
-{
-	return hook_lua_filter(lua_state, html_attr);
-}
-
-static int html_url_path_lua_filter(lua_State *lua_state)
-{
-	return hook_lua_filter(lua_state, html_url_path);
-}
-
-static int html_url_arg_lua_filter(lua_State *lua_state)
-{
-	return hook_lua_filter(lua_state, html_url_arg);
-}
-
-static int html_include_lua_filter(lua_State *lua_state)
-{
-	return hook_lua_filter(lua_state, (void (*)(const char *))html_include);
-}
-
-static void cleanup_lua_filter(struct cgit_filter *base)
-{
-	struct lua_filter *filter = (struct lua_filter *)base;
-
-	if (!filter->lua_state)
-		return;
-
-	lua_close(filter->lua_state);
-	filter->lua_state = NULL;
-	if (filter->script_file) {
-		free(filter->script_file);
-		filter->script_file = NULL;
-	}
-}
-
-static int init_lua_filter(struct lua_filter *filter)
-{
-	if (filter->lua_state)
-		return 0;
-
-	if (!(filter->lua_state = luaL_newstate()))
-		return 1;
-
-	luaL_openlibs(filter->lua_state);
-
-	lua_pushcfunction(filter->lua_state, html_lua_filter);
-	lua_setglobal(filter->lua_state, "html");
-	lua_pushcfunction(filter->lua_state, html_txt_lua_filter);
-	lua_setglobal(filter->lua_state, "html_txt");
-	lua_pushcfunction(filter->lua_state, html_attr_lua_filter);
-	lua_setglobal(filter->lua_state, "html_attr");
-	lua_pushcfunction(filter->lua_state, html_url_path_lua_filter);
-	lua_setglobal(filter->lua_state, "html_url_path");
-	lua_pushcfunction(filter->lua_state, html_url_arg_lua_filter);
-	lua_setglobal(filter->lua_state, "html_url_arg");
-	lua_pushcfunction(filter->lua_state, html_include_lua_filter);
-	lua_setglobal(filter->lua_state, "html_include");
-
-	if (luaL_dofile(filter->lua_state, filter->script_file)) {
-		error_lua_filter(filter);
-		lua_close(filter->lua_state);
-		filter->lua_state = NULL;
-		return 1;
-	}
-	return 0;
-}
-
-static int open_lua_filter(struct cgit_filter *base, va_list ap)
-{
-	struct lua_filter *filter = (struct lua_filter *)base;
-	int i;
-
-	if (init_lua_filter(filter))
-		return 1;
-
-	hook_write(base, write_lua_filter);
-
-	lua_getglobal(filter->lua_state, "filter_open");
-	for (i = 0; i < filter->base.argument_count; ++i)
-		lua_pushstring(filter->lua_state, va_arg(ap, char *));
-	if (lua_pcall(filter->lua_state, filter->base.argument_count, 0, 0)) {
-		error_lua_filter(filter);
-		return 1;
-	}
-	return 0;
-}
-
-static int close_lua_filter(struct cgit_filter *base)
-{
-	struct lua_filter *filter = (struct lua_filter *)base;
-	int ret = 0;
-
-	lua_getglobal(filter->lua_state, "filter_close");
-	if (lua_pcall(filter->lua_state, 0, 1, 0)) {
-		error_lua_filter(filter);
-		ret = -1;
-	} else {
-		ret = lua_tonumber(filter->lua_state, -1);
-		lua_pop(filter->lua_state, 1);
-	}
-
-	unhook_write();
-	return ret;
-}
-
-static void fprintf_lua_filter(struct cgit_filter *base, FILE *f, const char *prefix)
-{
-	struct lua_filter *filter = (struct lua_filter *)base;
-	fprintf(f, "%slua:%s\n", prefix, filter->script_file);
-}
-
-
-static struct cgit_filter *new_lua_filter(const char *cmd, int argument_count)
-{
-	struct lua_filter *filter;
-
-	filter = xmalloc(sizeof(*filter));
-	memset(filter, 0, sizeof(*filter));
-	filter->base.open = open_lua_filter;
-	filter->base.close = close_lua_filter;
-	filter->base.fprintf = fprintf_lua_filter;
-	filter->base.cleanup = cleanup_lua_filter;
-	filter->base.argument_count = argument_count;
-	filter->script_file = xstrdup(cmd);
-
-	return &filter->base;
-}
-
-#endif
-
-
-int cgit_open_filter(struct cgit_filter *filter, ...)
-{
-	int result;
-	va_list ap;
-	if (!filter)
-		return 0;
-	va_start(ap, filter);
-	result = filter->open(filter, ap);
-	va_end(ap);
-	return result;
-}
-
-int cgit_close_filter(struct cgit_filter *filter)
-{
-	if (!filter)
-		return 0;
-	return filter->close(filter);
-}
-
-void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix)
-{
-	filter->fprintf(filter, f, prefix);
-}
-
-
-
-static const struct {
-	const char *prefix;
-	struct cgit_filter *(*ctor)(const char *cmd, int argument_count);
-} filter_specs[] = {
-	{ "exec", new_exec_filter },
-#ifndef NO_LUA
-	{ "lua", new_lua_filter },
-#endif
-};
-
-struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
-{
-	char *colon;
-	int i;
-	size_t len;
-	int argument_count;
-
-	if (!cmd || !cmd[0])
-		return NULL;
-
-	colon = strchr(cmd, ':');
-	len = colon - cmd;
-	/*
-	 * In case we're running on Windows, don't allow a single letter before
-	 * the colon.
-	 */
-	if (len == 1)
-		colon = NULL;
-
-	switch (filtertype) {
-		case AUTH:
-			argument_count = 12;
-			break;
-
-		case EMAIL:
-			argument_count = 2;
-			break;
-
-		case OWNER:
-			argument_count = 0;
-			break;
-
-		case SOURCE:
-		case ABOUT:
-			argument_count = 1;
-			break;
-
-		case COMMIT:
-		default:
-			argument_count = 0;
-			break;
-	}
-
-	/* If no prefix is given, exec filter is the default. */
-	if (!colon)
-		return new_exec_filter(cmd, argument_count);
-
-	for (i = 0; i < ARRAY_SIZE(filter_specs); i++) {
-		if (len == strlen(filter_specs[i].prefix) &&
-		    !strncmp(filter_specs[i].prefix, cmd, len))
-			return filter_specs[i].ctor(colon + 1, argument_count);
-	}
-
-	die("Invalid filter type: %.*s", (int) len, cmd);
-}
diff --git a/filters/about-formatting.sh b/filters/about-formatting.sh
deleted file mode 100755
index 85daf9c26b..0000000000
--- a/filters/about-formatting.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/sh
-
-# This may be used with the about-filter or repo.about-filter setting in cgitrc.
-# It passes formatting of about pages to differing programs, depending on the usage.
-
-# Markdown support requires python and markdown-python.
-# RestructuredText support requires python and docutils.
-# Man page support requires groff.
-
-# The following environment variables can be used to retrieve the configuration
-# of the repository for which this script is called:
-# CGIT_REPO_URL        ( = repo.url       setting )
-# CGIT_REPO_NAME       ( = repo.name      setting )
-# CGIT_REPO_PATH       ( = repo.path      setting )
-# CGIT_REPO_OWNER      ( = repo.owner     setting )
-# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
-# CGIT_REPO_SECTION    ( = section        setting )
-# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )
-
-cd "$(dirname $0)/html-converters/"
-case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
-	*.markdown|*.mdown|*.md|*.mkd) exec ./md2html; ;;
-	*.rst) exec ./rst2html; ;;
-	*.[1-9]) exec ./man2html; ;;
-	*.htm|*.html) exec cat; ;;
-	*.txt|*) exec ./txt2html; ;;
-esac
diff --git a/filters/commit-links.sh b/filters/commit-links.sh
deleted file mode 100755
index 58819524ce..0000000000
--- a/filters/commit-links.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-# This script can be used to generate links in commit messages.
-#
-# To use this script, refer to this file with either the commit-filter or the
-# repo.commit-filter options in cgitrc.
-#
-# The following environment variables can be used to retrieve the configuration
-# of the repository for which this script is called:
-# CGIT_REPO_URL        ( = repo.url       setting )
-# CGIT_REPO_NAME       ( = repo.name      setting )
-# CGIT_REPO_PATH       ( = repo.path      setting )
-# CGIT_REPO_OWNER      ( = repo.owner     setting )
-# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
-# CGIT_REPO_SECTION    ( = section        setting )
-# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )
-#
-
-regex=''
-
-# This expression generates links to commits referenced by their SHA1.
-regex=$regex'
-s|\b([0-9a-fA-F]{7,40})\b|<a href="./?id=\1">\1</a>|g'
-
-# This expression generates links to a fictional bugtracker.
-regex=$regex'
-s|#([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g'
-
-sed -re "$regex"
diff --git a/filters/email-gravatar.lua b/filters/email-gravatar.lua
deleted file mode 100644
index c39b490d6b..0000000000
--- a/filters/email-gravatar.lua
+++ /dev/null
@@ -1,35 +0,0 @@
--- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
--- It adds gravatar icons to author names. It is designed to be used with the lua:
--- prefix in filters. It is much faster than the corresponding python script.
---
--- Requirements:
--- 	luaossl
--- 	<http://25thandclement.com/~william/projects/luaossl.html>
---
-
-local digest = require("openssl.digest")
-
-function md5_hex(input)
-	local b = digest.new("md5"):final(input)
-	local x = ""
-	for i = 1, #b do
-		x = x .. string.format("%.2x", string.byte(b, i))
-	end
-	return x
-end
-
-function filter_open(email, page)
-	buffer = ""
-	md5 = md5_hex(email:sub(2, -2):lower())
-end
-
-function filter_close()
-	html("<img src='//www.gravatar.com/avatar/" .. md5 .. "?s=13&amp;d=retro' width='13' height='13' alt='Gravatar' /> " .. buffer)
-	return 0
-end
-
-function filter_write(str)
-	buffer = buffer .. str
-end
-
-
diff --git a/filters/email-gravatar.py b/filters/email-gravatar.py
deleted file mode 100755
index d70440ea54..0000000000
--- a/filters/email-gravatar.py
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/usr/bin/env python3
-
-# Please prefer the email-gravatar.lua using lua: as a prefix over this script. This
-# script is very slow, in comparison.
-#
-# This script may be used with the email-filter or repo.email-filter settings in cgitrc.
-#
-# The following environment variables can be used to retrieve the configuration
-# of the repository for which this script is called:
-# CGIT_REPO_URL        ( = repo.url       setting )
-# CGIT_REPO_NAME       ( = repo.name      setting )
-# CGIT_REPO_PATH       ( = repo.path      setting )
-# CGIT_REPO_OWNER      ( = repo.owner     setting )
-# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
-# CGIT_REPO_SECTION    ( = section        setting )
-# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )
-#
-# It receives an email address on argv[1] and text on stdin. It prints
-# to stdout that text prepended by a gravatar at 10pt.
-
-import sys
-import hashlib
-import codecs
-
-email = sys.argv[1].lower().strip()
-if email[0] == '<':
-        email = email[1:]
-if email[-1] == '>':
-        email = email[0:-1]
-
-page = sys.argv[2]
-
-sys.stdin = codecs.getreader("utf-8")(sys.stdin.detach())
-sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
-
-md5 = hashlib.md5(email.encode()).hexdigest()
-text = sys.stdin.read().strip()
-
-print("<img src='//www.gravatar.com/avatar/" + md5 + "?s=13&amp;d=retro' width='13' height='13' alt='Gravatar' /> " + text)
diff --git a/filters/email-libravatar.lua b/filters/email-libravatar.lua
deleted file mode 100644
index 7336baf830..0000000000
--- a/filters/email-libravatar.lua
+++ /dev/null
@@ -1,36 +0,0 @@
--- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
--- It adds libravatar icons to author names. It is designed to be used with the lua:
--- prefix in filters.
---
--- Requirements:
--- 	luaossl
--- 	<http://25thandclement.com/~william/projects/luaossl.html>
---
-
-local digest = require("openssl.digest")
-
-function md5_hex(input)
-	local b = digest.new("md5"):final(input)
-	local x = ""
-	for i = 1, #b do
-		x = x .. string.format("%.2x", string.byte(b, i))
-	end
-	return x
-end
-
-function filter_open(email, page)
-	buffer = ""
-	md5 = md5_hex(email:sub(2, -2):lower())
-end
-
-function filter_close()
-	baseurl = os.getenv("HTTPS") and "https://seccdn.libravatar.org/" or "http://cdn.libravatar.org/"
-	html("<img src='" .. baseurl .. "avatar/" .. md5 .. "?s=13&amp;d=retro' width='13' height='13' alt='Libravatar' /> " .. buffer)
-	return 0
-end
-
-function filter_write(str)
-	buffer = buffer .. str
-end
-
-
diff --git a/filters/file-authentication.lua b/filters/file-authentication.lua
deleted file mode 100644
index 024880463c..0000000000
--- a/filters/file-authentication.lua
+++ /dev/null
@@ -1,359 +0,0 @@
--- This script may be used with the auth-filter.
---
--- Requirements:
--- 	luaossl
--- 	<http://25thandclement.com/~william/projects/luaossl.html>
--- 	luaposix
--- 	<https://github.com/luaposix/luaposix>
---
-local sysstat = require("posix.sys.stat")
-local unistd = require("posix.unistd")
-local rand = require("openssl.rand")
-local hmac = require("openssl.hmac")
-
--- This file should contain a series of lines in the form of:
---	username1:hash1
---	username2:hash2
---	username3:hash3
---	...
--- Hashes can be generated using something like `mkpasswd -m sha-512 -R 300000`.
--- This file should not be world-readable.
-local users_filename = "/etc/cgit-auth/users"
-
--- This file should contain a series of lines in the form of:
--- 	groupname1:username1,username2,username3,...
---	...
-local groups_filename = "/etc/cgit-auth/groups"
-
--- This file should contain a series of lines in the form of:
--- 	reponame1:groupname1,groupname2,groupname3,...
---	...
-local repos_filename = "/etc/cgit-auth/repos"
-
--- Set this to a path this script can write to for storing a persistent
--- cookie secret, which should not be world-readable.
-local secret_filename = "/var/cache/cgit/auth-secret"
-
---
---
--- Authentication functions follow below. Swap these out if you want different authentication semantics.
---
---
-
--- Looks up a hash for a given user.
-function lookup_hash(user)
-	local line
-	for line in io.lines(users_filename) do
-		local u, h = string.match(line, "(.-):(.+)")
-		if u:lower() == user:lower() then
-			return h
-		end
-	end
-	return nil
-end
-
--- Looks up users for a given repo.
-function lookup_users(repo)
-	local users = nil
-	local groups = nil
-	local line, group, user
-	for line in io.lines(repos_filename) do
-		local r, g = string.match(line, "(.-):(.+)")
-		if r == repo then
-			groups = { }
-			for group in string.gmatch(g, "([^,]+)") do
-				groups[group:lower()] = true
-			end
-			break
-		end
-	end
-	if groups == nil then
-		return nil
-	end
-	for line in io.lines(groups_filename) do
-		local g, u = string.match(line, "(.-):(.+)")
-		if groups[g:lower()] then
-			if users == nil then
-				users = { }
-			end
-			for user in string.gmatch(u, "([^,]+)") do
-				users[user:lower()] = true
-			end
-		end
-	end
-	return users
-end
-
-
--- Sets HTTP cookie headers based on post and sets up redirection.
-function authenticate_post()
-	local hash = lookup_hash(post["username"])
-	local redirect = validate_value("redirect", post["redirect"])
-
-	if redirect == nil then
-		not_found()
-		return 0
-	end
-
-	redirect_to(redirect)
-
-	if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
-		set_cookie("cgitauth", "")
-	else
-		-- One week expiration time
-		local username = secure_value("username", post["username"], os.time() + 604800)
-		set_cookie("cgitauth", username)
-	end
-
-	html("\n")
-	return 0
-end
-
-
--- Returns 1 if the cookie is valid and 0 if it is not.
-function authenticate_cookie()
-	accepted_users = lookup_users(cgit["repo"])
-	if accepted_users == nil then
-		-- We return as valid if the repo is not protected.
-		return 1
-	end
-
-	local username = validate_value("username", get_cookie(http["cookie"], "cgitauth"))
-	if username == nil or not accepted_users[username:lower()] then
-		return 0
-	else
-		return 1
-	end
-end
-
--- Prints the html for the login form.
-function body()
-	html("<h2>Authentication Required</h2>")
-	html("<form method='post' action='")
-	html_attr(cgit["login"])
-	html("'>")
-	html("<input type='hidden' name='redirect' value='")
-	html_attr(secure_value("redirect", cgit["url"], 0))
-	html("' />")
-	html("<table>")
-	html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
-	html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
-	html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
-	html("</table></form>")
-
-	return 0
-end
-
-
-
---
---
--- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
---
---
-
-local actions = {}
-actions["authenticate-post"] = authenticate_post
-actions["authenticate-cookie"] = authenticate_cookie
-actions["body"] = body
-
-function filter_open(...)
-	action = actions[select(1, ...)]
-
-	http = {}
-	http["cookie"] = select(2, ...)
-	http["method"] = select(3, ...)
-	http["query"] = select(4, ...)
-	http["referer"] = select(5, ...)
-	http["path"] = select(6, ...)
-	http["host"] = select(7, ...)
-	http["https"] = select(8, ...)
-
-	cgit = {}
-	cgit["repo"] = select(9, ...)
-	cgit["page"] = select(10, ...)
-	cgit["url"] = select(11, ...)
-	cgit["login"] = select(12, ...)
-
-end
-
-function filter_close()
-	return action()
-end
-
-function filter_write(str)
-	post = parse_qs(str)
-end
-
-
---
---
--- Utility functions based on keplerproject/wsapi.
---
---
-
-function url_decode(str)
-	if not str then
-		return ""
-	end
-	str = string.gsub(str, "+", " ")
-	str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
-	str = string.gsub(str, "\r\n", "\n")
-	return str
-end
-
-function url_encode(str)
-	if not str then
-		return ""
-	end
-	str = string.gsub(str, "\n", "\r\n")
-	str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
-	str = string.gsub(str, " ", "+")
-	return str
-end
-
-function parse_qs(qs)
-	local tab = {}
-	for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
-		tab[url_decode(key)] = url_decode(val)
-	end
-	return tab
-end
-
-function get_cookie(cookies, name)
-	cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
-	return url_decode(string.match(cookies, ";" .. name .. "=(.-);"))
-end
-
-function tohex(b)
-	local x = ""
-	for i = 1, #b do
-		x = x .. string.format("%.2x", string.byte(b, i))
-	end
-	return x
-end
-
---
---
--- Cookie construction and validation helpers.
---
---
-
-local secret = nil
-
--- Loads a secret from a file, creates a secret, or returns one from memory.
-function get_secret()
-	if secret ~= nil then
-		return secret
-	end
-	local secret_file = io.open(secret_filename, "r")
-	if secret_file == nil then
-		local old_umask = sysstat.umask(63)
-		local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
-		local temporary_file = io.open(temporary_filename, "w")
-		if temporary_file == nil then
-			os.exit(177)
-		end
-		temporary_file:write(tohex(rand.bytes(32)))
-		temporary_file:close()
-		unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
-		unistd.unlink(temporary_filename)
-		sysstat.umask(old_umask)
-		secret_file = io.open(secret_filename, "r")
-	end
-	if secret_file == nil then
-		os.exit(177)
-	end
-	secret = secret_file:read()
-	secret_file:close()
-	if secret:len() ~= 64 then
-		os.exit(177)
-	end
-	return secret
-end
-
--- Returns value of cookie if cookie is valid. Otherwise returns nil.
-function validate_value(expected_field, cookie)
-	local i = 0
-	local value = ""
-	local field = ""
-	local expiration = 0
-	local salt = ""
-	local chmac = ""
-
-	if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
-		return nil
-	end
-
-	for component in string.gmatch(cookie, "[^|]+") do
-		if i == 0 then
-			field = component
-		elseif i == 1 then
-			value = component
-		elseif i == 2 then
-			expiration = tonumber(component)
-			if expiration == nil then
-				expiration = -1
-			end
-		elseif i == 3 then
-			salt = component
-		elseif i == 4 then
-			chmac = component
-		else
-			break
-		end
-		i = i + 1
-	end
-
-	if chmac == nil or chmac:len() == 0 then
-		return nil
-	end
-
-	-- Lua hashes strings, so these comparisons are time invariant.
-	if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
-		return nil
-	end
-
-	if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
-		return nil
-	end
-
-	if url_decode(field) ~= expected_field then
-		return nil
-	end
-
-	return url_decode(value)
-end
-
-function secure_value(field, value, expiration)
-	if value == nil or value:len() <= 0 then
-		return ""
-	end
-
-	local authstr = ""
-	local salt = tohex(rand.bytes(16))
-	value = url_encode(value)
-	field = url_encode(field)
-	authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
-	authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
-	return authstr
-end
-
-function set_cookie(cookie, value)
-	html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
-	if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
-		html("; secure")
-	end
-	html("\n")
-end
-
-function redirect_to(url)
-	html("Status: 302 Redirect\n")
-	html("Cache-Control: no-cache, no-store\n")
-	html("Location: " .. url .. "\n")
-end
-
-function not_found()
-	html("Status: 404 Not Found\n")
-	html("Cache-Control: no-cache, no-store\n\n")
-end
diff --git a/filters/gentoo-ldap-authentication.lua b/filters/gentoo-ldap-authentication.lua
deleted file mode 100644
index 673c88d102..0000000000
--- a/filters/gentoo-ldap-authentication.lua
+++ /dev/null
@@ -1,360 +0,0 @@
--- This script may be used with the auth-filter. Be sure to configure it as you wish.
---
--- Requirements:
--- 	luaossl
--- 	<http://25thandclement.com/~william/projects/luaossl.html>
--- 	lualdap >= 1.2
--- 	<https://git.zx2c4.com/lualdap/about/>
--- 	luaposix
--- 	<https://github.com/luaposix/luaposix>
---
-local sysstat = require("posix.sys.stat")
-local unistd = require("posix.unistd")
-local lualdap = require("lualdap")
-local rand = require("openssl.rand")
-local hmac = require("openssl.hmac")
-
---
---
--- Configure these variables for your settings.
---
---
-
--- A list of password protected repositories, with which gentooAccess
--- group is allowed to access each one.
-local protected_repos = {
-	glouglou = "infra",
-	portage = "dev"
-}
-
--- Set this to a path this script can write to for storing a persistent
--- cookie secret, which should be guarded.
-local secret_filename = "/var/cache/cgit/auth-secret"
-
-
---
---
--- Authentication functions follow below. Swap these out if you want different authentication semantics.
---
---
-
--- Sets HTTP cookie headers based on post and sets up redirection.
-function authenticate_post()
-	local redirect = validate_value("redirect", post["redirect"])
-
-	if redirect == nil then
-		not_found()
-		return 0
-	end
-
-	redirect_to(redirect)
-	
-	local groups = gentoo_ldap_user_groups(post["username"], post["password"])
-	if groups == nil then
-		set_cookie("cgitauth", "")
-	else
-		-- One week expiration time
-		set_cookie("cgitauth", secure_value("gentoogroups", table.concat(groups, ","), os.time() + 604800))
-	end
-
-	html("\n")
-	return 0
-end
-
-
--- Returns 1 if the cookie is valid and 0 if it is not.
-function authenticate_cookie()
-	local required_group = protected_repos[cgit["repo"]]
-	if required_group == nil then
-		-- We return as valid if the repo is not protected.
-		return 1
-	end
-	
-	local user_groups = validate_value("gentoogroups", get_cookie(http["cookie"], "cgitauth"))
-	if user_groups == nil or user_groups == "" then
-		return 0
-	end
-	for group in string.gmatch(user_groups, "[^,]+") do
-		if group == required_group then
-			return 1
-		end
-	end
-	return 0
-end
-
--- Prints the html for the login form.
-function body()
-	html("<h2>Gentoo LDAP Authentication Required</h2>")
-	html("<form method='post' action='")
-	html_attr(cgit["login"])
-	html("'>")
-	html("<input type='hidden' name='redirect' value='")
-	html_attr(secure_value("redirect", cgit["url"], 0))
-	html("' />")
-	html("<table>")
-	html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
-	html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
-	html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
-	html("</table></form>")
-
-	return 0
-end
-
---
---
--- Gentoo LDAP support.
---
---
-
-function gentoo_ldap_user_groups(username, password)
-	-- Ensure the user is alphanumeric
-	if username == nil or username:match("%W") then
-		return nil
-	end
-
-	local who = "uid=" .. username .. ",ou=devs,dc=gentoo,dc=org"
-
-	local ldap, err = lualdap.open_simple {
-		uri = "ldap://ldap1.gentoo.org",
-		who = who,
-		password = password,
-		starttls = true,
-		certfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.crt",
-		keyfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.key",
-		cacertfile = "/var/www/uwsgi/cgit/gentoo-ldap/ca.pem"
-	}
-	if ldap == nil then
-		return nil
-	end
-
-	local group_suffix = ".group"
-	local group_suffix_len = group_suffix:len()
-	local groups = {}
-	for dn, attribs in ldap:search { base = who, scope = "subtree" } do
-		local access = attribs["gentooAccess"]
-		if dn == who and access ~= nil then
-			for i, v in ipairs(access) do
-				local vlen = v:len()
-				if vlen > group_suffix_len and v:sub(-group_suffix_len) == group_suffix then
-					table.insert(groups, v:sub(1, vlen - group_suffix_len))
-				end
-			end
-		end
-	end
-
-	ldap:close()
-
-	return groups
-end
-
---
---
--- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
---
---
-
-local actions = {}
-actions["authenticate-post"] = authenticate_post
-actions["authenticate-cookie"] = authenticate_cookie
-actions["body"] = body
-
-function filter_open(...)
-	action = actions[select(1, ...)]
-
-	http = {}
-	http["cookie"] = select(2, ...)
-	http["method"] = select(3, ...)
-	http["query"] = select(4, ...)
-	http["referer"] = select(5, ...)
-	http["path"] = select(6, ...)
-	http["host"] = select(7, ...)
-	http["https"] = select(8, ...)
-
-	cgit = {}
-	cgit["repo"] = select(9, ...)
-	cgit["page"] = select(10, ...)
-	cgit["url"] = select(11, ...)
-	cgit["login"] = select(12, ...)
-
-end
-
-function filter_close()
-	return action()
-end
-
-function filter_write(str)
-	post = parse_qs(str)
-end
-
-
---
---
--- Utility functions based on keplerproject/wsapi.
---
---
-
-function url_decode(str)
-	if not str then
-		return ""
-	end
-	str = string.gsub(str, "+", " ")
-	str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
-	str = string.gsub(str, "\r\n", "\n")
-	return str
-end
-
-function url_encode(str)
-	if not str then
-		return ""
-	end
-	str = string.gsub(str, "\n", "\r\n")
-	str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
-	str = string.gsub(str, " ", "+")
-	return str
-end
-
-function parse_qs(qs)
-	local tab = {}
-	for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
-		tab[url_decode(key)] = url_decode(val)
-	end
-	return tab
-end
-
-function get_cookie(cookies, name)
-	cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
-	return string.match(cookies, ";" .. name .. "=(.-);")
-end
-
-function tohex(b)
-	local x = ""
-	for i = 1, #b do
-		x = x .. string.format("%.2x", string.byte(b, i))
-	end
-	return x
-end
-
---
---
--- Cookie construction and validation helpers.
---
---
-
-local secret = nil
-
--- Loads a secret from a file, creates a secret, or returns one from memory.
-function get_secret()
-	if secret ~= nil then
-		return secret
-	end
-	local secret_file = io.open(secret_filename, "r")
-	if secret_file == nil then
-		local old_umask = sysstat.umask(63)
-		local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
-		local temporary_file = io.open(temporary_filename, "w")
-		if temporary_file == nil then
-			os.exit(177)
-		end
-		temporary_file:write(tohex(rand.bytes(32)))
-		temporary_file:close()
-		unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
-		unistd.unlink(temporary_filename)
-		sysstat.umask(old_umask)
-		secret_file = io.open(secret_filename, "r")
-	end
-	if secret_file == nil then
-		os.exit(177)
-	end
-	secret = secret_file:read()
-	secret_file:close()
-	if secret:len() ~= 64 then
-		os.exit(177)
-	end
-	return secret
-end
-
--- Returns value of cookie if cookie is valid. Otherwise returns nil.
-function validate_value(expected_field, cookie)
-	local i = 0
-	local value = ""
-	local field = ""
-	local expiration = 0
-	local salt = ""
-	local chmac = ""
-
-	if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
-		return nil
-	end
-
-	for component in string.gmatch(cookie, "[^|]+") do
-		if i == 0 then
-			field = component
-		elseif i == 1 then
-			value = component
-		elseif i == 2 then
-			expiration = tonumber(component)
-			if expiration == nil then
-				expiration = -1
-			end
-		elseif i == 3 then
-			salt = component
-		elseif i == 4 then
-			chmac = component
-		else
-			break
-		end
-		i = i + 1
-	end
-
-	if chmac == nil or chmac:len() == 0 then
-		return nil
-	end
-
-	-- Lua hashes strings, so these comparisons are time invariant.
-	if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
-		return nil
-	end
-
-	if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
-		return nil
-	end
-
-	if url_decode(field) ~= expected_field then
-		return nil
-	end
-
-	return url_decode(value)
-end
-
-function secure_value(field, value, expiration)
-	if value == nil or value:len() <= 0 then
-		return ""
-	end
-
-	local authstr = ""
-	local salt = tohex(rand.bytes(16))
-	value = url_encode(value)
-	field = url_encode(field)
-	authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
-	authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
-	return authstr
-end
-
-function set_cookie(cookie, value)
-	html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
-	if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
-		html("; secure")
-	end
-	html("\n")
-end
-
-function redirect_to(url)
-	html("Status: 302 Redirect\n")
-	html("Cache-Control: no-cache, no-store\n")
-	html("Location: " .. url .. "\n")
-end
-
-function not_found()
-	html("Status: 404 Not Found\n")
-	html("Cache-Control: no-cache, no-store\n\n")
-end
diff --git a/filters/html-converters/man2html b/filters/html-converters/man2html
deleted file mode 100755
index 0ef7884181..0000000000
--- a/filters/html-converters/man2html
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-echo "<div style=\"font-family: monospace\">"
-groff -mandoc -T html -P -r -P -l | egrep -v '(<html>|<head>|<meta|<title>|</title>|</head>|<body>|</body>|</html>|<!DOCTYPE|"http://www.w3.org)'
-echo "</div>"
diff --git a/filters/html-converters/md2html b/filters/html-converters/md2html
deleted file mode 100755
index dc20f42a05..0000000000
--- a/filters/html-converters/md2html
+++ /dev/null
@@ -1,307 +0,0 @@
-#!/usr/bin/env python3
-import markdown
-import sys
-import io
-from pygments.formatters import HtmlFormatter
-from markdown.extensions.toc import TocExtension
-sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
-sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
-sys.stdout.write('''
-<style>
-.markdown-body {
-    font-size: 14px;
-    line-height: 1.6;
-    overflow: hidden;
-}
-.markdown-body>*:first-child {
-    margin-top: 0 !important;
-}
-.markdown-body>*:last-child {
-    margin-bottom: 0 !important;
-}
-.markdown-body a.absent {
-    color: #c00;
-}
-.markdown-body a.anchor {
-    display: block;
-    padding-left: 30px;
-    margin-left: -30px;
-    cursor: pointer;
-    position: absolute;
-    top: 0;
-    left: 0;
-    bottom: 0;
-}
-.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
-    margin: 20px 0 10px;
-    padding: 0;
-    font-weight: bold;
-    -webkit-font-smoothing: antialiased;
-    cursor: text;
-    position: relative;
-}
-.markdown-body h1 .mini-icon-link, .markdown-body h2 .mini-icon-link, .markdown-body h3 .mini-icon-link, .markdown-body h4 .mini-icon-link, .markdown-body h5 .mini-icon-link, .markdown-body h6 .mini-icon-link {
-    display: none;
-    color: #000;
-}
-.markdown-body h1:hover a.anchor, .markdown-body h2:hover a.anchor, .markdown-body h3:hover a.anchor, .markdown-body h4:hover a.anchor, .markdown-body h5:hover a.anchor, .markdown-body h6:hover a.anchor {
-    text-decoration: none;
-    line-height: 1;
-    padding-left: 0;
-    margin-left: -22px;
-    top: 15%;
-}
-.markdown-body h1:hover a.anchor .mini-icon-link, .markdown-body h2:hover a.anchor .mini-icon-link, .markdown-body h3:hover a.anchor .mini-icon-link, .markdown-body h4:hover a.anchor .mini-icon-link, .markdown-body h5:hover a.anchor .mini-icon-link, .markdown-body h6:hover a.anchor .mini-icon-link {
-    display: inline-block;
-}
-div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#cgit .markdown-body h3 a.toclink, div#cgit .markdown-body h4 a.toclink, div#cgit .markdown-body h5 a.toclink, div#cgit .markdown-body h6 a.toclink {
-    color: black;
-}
-.markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code {
-    font-size: inherit;
-}
-.markdown-body h1 {
-    font-size: 28px;
-    color: #000;
-}
-.markdown-body h2 {
-    font-size: 24px;
-    border-bottom: 1px solid #ccc;
-    color: #000;
-}
-.markdown-body h3 {
-    font-size: 18px;
-}
-.markdown-body h4 {
-    font-size: 16px;
-}
-.markdown-body h5 {
-    font-size: 14px;
-}
-.markdown-body h6 {
-    color: #777;
-    font-size: 14px;
-}
-.markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre {
-    margin: 15px 0;
-}
-.markdown-body hr {
-    background: transparent url("/dirty-shade.png") repeat-x 0 0;
-    border: 0 none;
-    color: #ccc;
-    height: 4px;
-    padding: 0;
-}
-.markdown-body>h2:first-child, .markdown-body>h1:first-child, .markdown-body>h1:first-child+h2, .markdown-body>h3:first-child, .markdown-body>h4:first-child, .markdown-body>h5:first-child, .markdown-body>h6:first-child {
-    margin-top: 0;
-    padding-top: 0;
-}
-.markdown-body a:first-child h1, .markdown-body a:first-child h2, .markdown-body a:first-child h3, .markdown-body a:first-child h4, .markdown-body a:first-child h5, .markdown-body a:first-child h6 {
-    margin-top: 0;
-    padding-top: 0;
-}
-.markdown-body h1+p, .markdown-body h2+p, .markdown-body h3+p, .markdown-body h4+p, .markdown-body h5+p, .markdown-body h6+p {
-    margin-top: 0;
-}
-.markdown-body li p.first {
-    display: inline-block;
-}
-.markdown-body ul, .markdown-body ol {
-    padding-left: 30px;
-}
-.markdown-body ul.no-list, .markdown-body ol.no-list {
-    list-style-type: none;
-    padding: 0;
-}
-.markdown-body ul li>:first-child, .markdown-body ul li ul:first-of-type, .markdown-body ul li ol:first-of-type, .markdown-body ol li>:first-child, .markdown-body ol li ul:first-of-type, .markdown-body ol li ol:first-of-type {
-    margin-top: 0px;
-}
-.markdown-body ul li p:last-of-type, .markdown-body ol li p:last-of-type {
-    margin-bottom: 0;
-}
-.markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul {
-    margin-bottom: 0;
-}
-.markdown-body dl {
-    padding: 0;
-}
-.markdown-body dl dt {
-    font-size: 14px;
-    font-weight: bold;
-    font-style: italic;
-    padding: 0;
-    margin: 15px 0 5px;
-}
-.markdown-body dl dt:first-child {
-    padding: 0;
-}
-.markdown-body dl dt>:first-child {
-    margin-top: 0px;
-}
-.markdown-body dl dt>:last-child {
-    margin-bottom: 0px;
-}
-.markdown-body dl dd {
-    margin: 0 0 15px;
-    padding: 0 15px;
-}
-.markdown-body dl dd>:first-child {
-    margin-top: 0px;
-}
-.markdown-body dl dd>:last-child {
-    margin-bottom: 0px;
-}
-.markdown-body blockquote {
-    border-left: 4px solid #DDD;
-    padding: 0 15px;
-    color: #777;
-}
-.markdown-body blockquote>:first-child {
-    margin-top: 0px;
-}
-.markdown-body blockquote>:last-child {
-    margin-bottom: 0px;
-}
-.markdown-body table th {
-    font-weight: bold;
-}
-.markdown-body table th, .markdown-body table td {
-    border: 1px solid #ccc;
-    padding: 6px 13px;
-}
-.markdown-body table tr {
-    border-top: 1px solid #ccc;
-    background-color: #fff;
-}
-.markdown-body table tr:nth-child(2n) {
-    background-color: #f8f8f8;
-}
-.markdown-body img {
-    max-width: 100%;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-}
-.markdown-body span.frame {
-    display: block;
-    overflow: hidden;
-}
-.markdown-body span.frame>span {
-    border: 1px solid #ddd;
-    display: block;
-    float: left;
-    overflow: hidden;
-    margin: 13px 0 0;
-    padding: 7px;
-    width: auto;
-}
-.markdown-body span.frame span img {
-    display: block;
-    float: left;
-}
-.markdown-body span.frame span span {
-    clear: both;
-    color: #333;
-    display: block;
-    padding: 5px 0 0;
-}
-.markdown-body span.align-center {
-    display: block;
-    overflow: hidden;
-    clear: both;
-}
-.markdown-body span.align-center>span {
-    display: block;
-    overflow: hidden;
-    margin: 13px auto 0;
-    text-align: center;
-}
-.markdown-body span.align-center span img {
-    margin: 0 auto;
-    text-align: center;
-}
-.markdown-body span.align-right {
-    display: block;
-    overflow: hidden;
-    clear: both;
-}
-.markdown-body span.align-right>span {
-    display: block;
-    overflow: hidden;
-    margin: 13px 0 0;
-    text-align: right;
-}
-.markdown-body span.align-right span img {
-    margin: 0;
-    text-align: right;
-}
-.markdown-body span.float-left {
-    display: block;
-    margin-right: 13px;
-    overflow: hidden;
-    float: left;
-}
-.markdown-body span.float-left span {
-    margin: 13px 0 0;
-}
-.markdown-body span.float-right {
-    display: block;
-    margin-left: 13px;
-    overflow: hidden;
-    float: right;
-}
-.markdown-body span.float-right>span {
-    display: block;
-    overflow: hidden;
-    margin: 13px auto 0;
-    text-align: right;
-}
-.markdown-body code, .markdown-body tt {
-    margin: 0 2px;
-    padding: 0px 5px;
-    border: 1px solid #eaeaea;
-    background-color: #f8f8f8;
-    border-radius: 3px;
-}
-.markdown-body code {
-    white-space: nowrap;
-}
-.markdown-body pre>code {
-    margin: 0;
-    padding: 0;
-    white-space: pre;
-    border: none;
-    background: transparent;
-}
-.markdown-body .highlight pre, .markdown-body pre {
-    background-color: #f8f8f8;
-    border: 1px solid #ccc;
-    font-size: 13px;
-    line-height: 19px;
-    overflow: auto;
-    padding: 6px 10px;
-    border-radius: 3px;
-}
-.markdown-body pre code, .markdown-body pre tt {
-    margin: 0;
-    padding: 0;
-    background-color: transparent;
-    border: none;
-}
-''')
-sys.stdout.write(HtmlFormatter(style='pastie').get_style_defs('.highlight'))
-sys.stdout.write('''
-</style>   
-''')
-sys.stdout.write("<div class='markdown-body'>")
-sys.stdout.flush()
-# Note: you may want to run this through bleach for sanitization
-markdown.markdownFromFile(
-	output_format="html5",
-	extensions=[
-		"markdown.extensions.fenced_code",
-		"markdown.extensions.codehilite",
-		"markdown.extensions.tables",
-		TocExtension(anchorlink=True)],
-	extension_configs={
-		"markdown.extensions.codehilite":{"css_class":"highlight"}})
-sys.stdout.write("</div>")
diff --git a/filters/html-converters/rst2html b/filters/html-converters/rst2html
deleted file mode 100755
index 02d90f81c9..0000000000
--- a/filters/html-converters/rst2html
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-exec rst2html.py --template <(echo -e "%(stylesheet)s\n%(body_pre_docinfo)s\n%(docinfo)s\n%(body)s")
diff --git a/filters/html-converters/txt2html b/filters/html-converters/txt2html
deleted file mode 100755
index 495eeceb27..0000000000
--- a/filters/html-converters/txt2html
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-echo "<pre>"
-sed "s|&|\\&amp;|g;s|'|\\&apos;|g;s|\"|\\&quot;|g;s|<|\\&lt;|g;s|>|\\&gt;|g"
-echo "</pre>"
diff --git a/filters/owner-example.lua b/filters/owner-example.lua
deleted file mode 100644
index 50fc25a8e5..0000000000
--- a/filters/owner-example.lua
+++ /dev/null
@@ -1,17 +0,0 @@
--- This script is an example of an owner-filter.  It replaces the
--- usual query link with one to a fictional homepage.  This script may
--- be used with the owner-filter or repo.owner-filter settings in
--- cgitrc with the `lua:` prefix.
-
-function filter_open()
-	buffer = ""
-end
-
-function filter_close()
-	html(string.format("<a href=\"%s\">%s</a>", "http://wiki.example.com/about/" .. buffer, buffer))
-	return 0
-end
-
-function filter_write(str)
-	buffer = buffer .. str
-end
diff --git a/filters/simple-authentication.lua b/filters/simple-authentication.lua
deleted file mode 100644
index 23d345763b..0000000000
--- a/filters/simple-authentication.lua
+++ /dev/null
@@ -1,314 +0,0 @@
--- This script may be used with the auth-filter. Be sure to configure it as you wish.
---
--- Requirements:
--- 	luaossl
--- 	<http://25thandclement.com/~william/projects/luaossl.html>
--- 	luaposix
--- 	<https://github.com/luaposix/luaposix>
---
-local sysstat = require("posix.sys.stat")
-local unistd = require("posix.unistd")
-local rand = require("openssl.rand")
-local hmac = require("openssl.hmac")
-
---
---
--- Configure these variables for your settings.
---
---
-
--- A list of password protected repositories along with the users who can access them.
-local protected_repos = {
-	glouglou	= { laurent = true, jason = true },
-	qt		= { jason = true, bob = true }
-}
-
--- A list of users and hashes, generated with `mkpasswd -m sha-512 -R 300000`.
-local users = {
-	jason		= "$6$rounds=300000$YYJct3n/o.ruYK$HhpSeuCuW1fJkpvMZOZzVizeLsBKcGA/aF2UPuV5v60JyH2MVSG6P511UMTj2F3H75.IT2HIlnvXzNb60FcZH1",
-	laurent		= "$6$rounds=300000$dP0KNHwYb3JKigT$pN/LG7rWxQ4HniFtx5wKyJXBJUKP7R01zTNZ0qSK/aivw8ywGAOdfYiIQFqFhZFtVGvr11/7an.nesvm8iJUi.",
-	bob		= "$6$rounds=300000$jCLCCt6LUpTz$PI1vvd1yaVYcCzqH8QAJFcJ60b6W/6sjcOsU7mAkNo7IE8FRGW1vkjF8I/T5jt/auv5ODLb1L4S2s.CAyZyUC"
-}
-
--- Set this to a path this script can write to for storing a persistent
--- cookie secret, which should be guarded.
-local secret_filename = "/var/cache/cgit/auth-secret"
-
---
---
--- Authentication functions follow below. Swap these out if you want different authentication semantics.
---
---
-
--- Sets HTTP cookie headers based on post and sets up redirection.
-function authenticate_post()
-	local hash = users[post["username"]]
-	local redirect = validate_value("redirect", post["redirect"])
-
-	if redirect == nil then
-		not_found()
-		return 0
-	end
-
-	redirect_to(redirect)
-
-	if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
-		set_cookie("cgitauth", "")
-	else
-		-- One week expiration time
-		local username = secure_value("username", post["username"], os.time() + 604800)
-		set_cookie("cgitauth", username)
-	end
-
-	html("\n")
-	return 0
-end
-
-
--- Returns 1 if the cookie is valid and 0 if it is not.
-function authenticate_cookie()
-	accepted_users = protected_repos[cgit["repo"]]
-	if accepted_users == nil then
-		-- We return as valid if the repo is not protected.
-		return 1
-	end
-
-	local username = validate_value("username", get_cookie(http["cookie"], "cgitauth"))
-	if username == nil or not accepted_users[username:lower()] then
-		return 0
-	else
-		return 1
-	end
-end
-
--- Prints the html for the login form.
-function body()
-	html("<h2>Authentication Required</h2>")
-	html("<form method='post' action='")
-	html_attr(cgit["login"])
-	html("'>")
-	html("<input type='hidden' name='redirect' value='")
-	html_attr(secure_value("redirect", cgit["url"], 0))
-	html("' />")
-	html("<table>")
-	html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
-	html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
-	html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
-	html("</table></form>")
-
-	return 0
-end
-
-
-
---
---
--- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
---
---
-
-local actions = {}
-actions["authenticate-post"] = authenticate_post
-actions["authenticate-cookie"] = authenticate_cookie
-actions["body"] = body
-
-function filter_open(...)
-	action = actions[select(1, ...)]
-
-	http = {}
-	http["cookie"] = select(2, ...)
-	http["method"] = select(3, ...)
-	http["query"] = select(4, ...)
-	http["referer"] = select(5, ...)
-	http["path"] = select(6, ...)
-	http["host"] = select(7, ...)
-	http["https"] = select(8, ...)
-
-	cgit = {}
-	cgit["repo"] = select(9, ...)
-	cgit["page"] = select(10, ...)
-	cgit["url"] = select(11, ...)
-	cgit["login"] = select(12, ...)
-
-end
-
-function filter_close()
-	return action()
-end
-
-function filter_write(str)
-	post = parse_qs(str)
-end
-
-
---
---
--- Utility functions based on keplerproject/wsapi.
---
---
-
-function url_decode(str)
-	if not str then
-		return ""
-	end
-	str = string.gsub(str, "+", " ")
-	str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
-	str = string.gsub(str, "\r\n", "\n")
-	return str
-end
-
-function url_encode(str)
-	if not str then
-		return ""
-	end
-	str = string.gsub(str, "\n", "\r\n")
-	str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
-	str = string.gsub(str, " ", "+")
-	return str
-end
-
-function parse_qs(qs)
-	local tab = {}
-	for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
-		tab[url_decode(key)] = url_decode(val)
-	end
-	return tab
-end
-
-function get_cookie(cookies, name)
-	cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
-	return url_decode(string.match(cookies, ";" .. name .. "=(.-);"))
-end
-
-function tohex(b)
-	local x = ""
-	for i = 1, #b do
-		x = x .. string.format("%.2x", string.byte(b, i))
-	end
-	return x
-end
-
---
---
--- Cookie construction and validation helpers.
---
---
-
-local secret = nil
-
--- Loads a secret from a file, creates a secret, or returns one from memory.
-function get_secret()
-	if secret ~= nil then
-		return secret
-	end
-	local secret_file = io.open(secret_filename, "r")
-	if secret_file == nil then
-		local old_umask = sysstat.umask(63)
-		local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
-		local temporary_file = io.open(temporary_filename, "w")
-		if temporary_file == nil then
-			os.exit(177)
-		end
-		temporary_file:write(tohex(rand.bytes(32)))
-		temporary_file:close()
-		unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
-		unistd.unlink(temporary_filename)
-		sysstat.umask(old_umask)
-		secret_file = io.open(secret_filename, "r")
-	end
-	if secret_file == nil then
-		os.exit(177)
-	end
-	secret = secret_file:read()
-	secret_file:close()
-	if secret:len() ~= 64 then
-		os.exit(177)
-	end
-	return secret
-end
-
--- Returns value of cookie if cookie is valid. Otherwise returns nil.
-function validate_value(expected_field, cookie)
-	local i = 0
-	local value = ""
-	local field = ""
-	local expiration = 0
-	local salt = ""
-	local chmac = ""
-
-	if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
-		return nil
-	end
-
-	for component in string.gmatch(cookie, "[^|]+") do
-		if i == 0 then
-			field = component
-		elseif i == 1 then
-			value = component
-		elseif i == 2 then
-			expiration = tonumber(component)
-			if expiration == nil then
-				expiration = -1
-			end
-		elseif i == 3 then
-			salt = component
-		elseif i == 4 then
-			chmac = component
-		else
-			break
-		end
-		i = i + 1
-	end
-
-	if chmac == nil or chmac:len() == 0 then
-		return nil
-	end
-
-	-- Lua hashes strings, so these comparisons are time invariant.
-	if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
-		return nil
-	end
-
-	if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
-		return nil
-	end
-
-	if url_decode(field) ~= expected_field then
-		return nil
-	end
-
-	return url_decode(value)
-end
-
-function secure_value(field, value, expiration)
-	if value == nil or value:len() <= 0 then
-		return ""
-	end
-
-	local authstr = ""
-	local salt = tohex(rand.bytes(16))
-	value = url_encode(value)
-	field = url_encode(field)
-	authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
-	authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
-	return authstr
-end
-
-function set_cookie(cookie, value)
-	html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
-	if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
-		html("; secure")
-	end
-	html("\n")
-end
-
-function redirect_to(url)
-	html("Status: 302 Redirect\n")
-	html("Cache-Control: no-cache, no-store\n")
-	html("Location: " .. url .. "\n")
-end
-
-function not_found()
-	html("Status: 404 Not Found\n")
-	html("Cache-Control: no-cache, no-store\n\n")
-end
diff --git a/filters/syntax-highlighting.py b/filters/syntax-highlighting.py
deleted file mode 100755
index e912594c48..0000000000
--- a/filters/syntax-highlighting.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env python3
-
-# This script uses Pygments and Python3. You must have both installed
-# for this to work.
-#
-# http://pygments.org/
-# http://python.org/
-#
-# It may be used with the source-filter or repo.source-filter settings
-# in cgitrc.
-#
-# The following environment variables can be used to retrieve the
-# configuration of the repository for which this script is called:
-# CGIT_REPO_URL        ( = repo.url       setting )
-# CGIT_REPO_NAME       ( = repo.name      setting )
-# CGIT_REPO_PATH       ( = repo.path      setting )
-# CGIT_REPO_OWNER      ( = repo.owner     setting )
-# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
-# CGIT_REPO_SECTION    ( = section        setting )
-# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )
-
-
-import sys
-import io
-from pygments import highlight
-from pygments.util import ClassNotFound
-from pygments.lexers import TextLexer
-from pygments.lexers import guess_lexer
-from pygments.lexers import guess_lexer_for_filename
-from pygments.formatters import HtmlFormatter
-
-
-sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8', errors='replace')
-sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
-data = sys.stdin.read()
-filename = sys.argv[1]
-formatter = HtmlFormatter(style='pastie', nobackground=True)
-
-try:
-	lexer = guess_lexer_for_filename(filename, data)
-except ClassNotFound:
-	# check if there is any shebang
-	if data[0:2] == '#!':
-		lexer = guess_lexer(data)
-	else:
-		lexer = TextLexer()
-except TypeError:
-	lexer = TextLexer()
-
-# highlight! :-)
-# printout pygments' css definitions as well
-sys.stdout.write('<style>')
-sys.stdout.write(formatter.get_style_defs('.highlight'))
-sys.stdout.write('</style>')
-sys.stdout.write(highlight(data, lexer, formatter, outfile=None))
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
deleted file mode 100755
index 840bc34fff..0000000000
--- a/filters/syntax-highlighting.sh
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/bin/sh
-# This script can be used to implement syntax highlighting in the cgit
-# tree-view by referring to this file with the source-filter or repo.source-
-# filter options in cgitrc.
-#
-# This script requires a shell supporting the ${var##pattern} syntax.
-# It is supported by at least dash and bash, however busybox environments
-# might have to use an external call to sed instead.
-#
-# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
-# highlighting, so you'll probably want something like the following included
-# in your css file:
-#
-# Style definition file generated by highlight 2.4.8, http://www.andre-simon.de/
-#
-# table.blob .num  { color:#2928ff; }
-# table.blob .esc  { color:#ff00ff; }
-# table.blob .str  { color:#ff0000; }
-# table.blob .dstr { color:#818100; }
-# table.blob .slc  { color:#838183; font-style:italic; }
-# table.blob .com  { color:#838183; font-style:italic; }
-# table.blob .dir  { color:#008200; }
-# table.blob .sym  { color:#000000; }
-# table.blob .kwa  { color:#000000; font-weight:bold; }
-# table.blob .kwb  { color:#830000; }
-# table.blob .kwc  { color:#000000; font-weight:bold; }
-# table.blob .kwd  { color:#010181; }
-#
-#
-# Style definition file generated by highlight 2.6.14, http://www.andre-simon.de/
-#
-# body.hl  { background-color:#ffffff; }
-# pre.hl   { color:#000000; background-color:#ffffff; font-size:10pt; font-family:'Courier New';}
-# .hl.num  { color:#2928ff; }
-# .hl.esc  { color:#ff00ff; }
-# .hl.str  { color:#ff0000; }
-# .hl.dstr { color:#818100; }
-# .hl.slc  { color:#838183; font-style:italic; }
-# .hl.com  { color:#838183; font-style:italic; }
-# .hl.dir  { color:#008200; }
-# .hl.sym  { color:#000000; }
-# .hl.line { color:#555555; }
-# .hl.mark { background-color:#ffffbb;}
-# .hl.kwa  { color:#000000; font-weight:bold; }
-# .hl.kwb  { color:#830000; }
-# .hl.kwc  { color:#000000; font-weight:bold; }
-# .hl.kwd  { color:#010181; }
-#
-#
-# Style definition file generated by highlight 3.8, http://www.andre-simon.de/
-#
-# body.hl { background-color:#e0eaee; }
-# pre.hl  { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New';}
-# .hl.num { color:#b07e00; }
-# .hl.esc { color:#ff00ff; }
-# .hl.str { color:#bf0303; }
-# .hl.pps { color:#818100; }
-# .hl.slc { color:#838183; font-style:italic; }
-# .hl.com { color:#838183; font-style:italic; }
-# .hl.ppc { color:#008200; }
-# .hl.opt { color:#000000; }
-# .hl.lin { color:#555555; }
-# .hl.kwa { color:#000000; font-weight:bold; }
-# .hl.kwb { color:#0057ae; }
-# .hl.kwc { color:#000000; font-weight:bold; }
-# .hl.kwd { color:#010181; }
-#
-#
-# Style definition file generated by highlight 3.13, http://www.andre-simon.de/
-#
-# body.hl { background-color:#e0eaee; }
-# pre.hl  { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New',monospace;}
-# .hl.num { color:#b07e00; }
-# .hl.esc { color:#ff00ff; }
-# .hl.str { color:#bf0303; }
-# .hl.pps { color:#818100; }
-# .hl.slc { color:#838183; font-style:italic; }
-# .hl.com { color:#838183; font-style:italic; }
-# .hl.ppc { color:#008200; }
-# .hl.opt { color:#000000; }
-# .hl.ipl { color:#0057ae; }
-# .hl.lin { color:#555555; }
-# .hl.kwa { color:#000000; font-weight:bold; }
-# .hl.kwb { color:#0057ae; }
-# .hl.kwc { color:#000000; font-weight:bold; }
-# .hl.kwd { color:#010181; }
-#
-#
-# The following environment variables can be used to retrieve the configuration
-# of the repository for which this script is called:
-# CGIT_REPO_URL        ( = repo.url       setting )
-# CGIT_REPO_NAME       ( = repo.name      setting )
-# CGIT_REPO_PATH       ( = repo.path      setting )
-# CGIT_REPO_OWNER      ( = repo.owner     setting )
-# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
-# CGIT_REPO_SECTION    ( = section        setting )
-# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )
-#
-
-# store filename and extension in local vars
-BASENAME="$1"
-EXTENSION="${BASENAME##*.}"
-
-[ "${BASENAME}" = "${EXTENSION}" ] && EXTENSION=txt
-[ -z "${EXTENSION}" ] && EXTENSION=txt
-
-# map Makefile and Makefile.* to .mk
-[ "${BASENAME%%.*}" = "Makefile" ] && EXTENSION=mk
-
-# highlight versions 2 and 3 have different commandline options. Specifically,
-# the -X option that is used for version 2 is replaced by the -O xhtml option
-# for version 3.
-#
-# Version 2 can be found (for example) on EPEL 5, while version 3 can be
-# found (for example) on EPEL 6.
-#
-# This is for version 2
-exec highlight --force -f -I -X -S "$EXTENSION" 2>/dev/null
-
-# This is for version 3
-#exec highlight --force -f -I -O xhtml -S "$EXTENSION" 2>/dev/null
diff --git a/gen-version.sh b/gen-version.sh
deleted file mode 100755
index 80cf49af48..0000000000
--- a/gen-version.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-
-# Get version-info specified in Makefile
-V=$1
-
-# Use `git describe` to get current version if we're inside a git repo
-if test "$(git rev-parse --git-dir 2>/dev/null)" = '.git'
-then
-	V=$(git describe --abbrev=4 HEAD 2>/dev/null)
-fi
-
-new="CGIT_VERSION = $V"
-old=$(cat VERSION 2>/dev/null)
-
-# Exit if VERSION is uptodate
-test "$old" = "$new" && exit 0
-
-# Update VERSION with new version-info
-echo "$new" > VERSION
-cat VERSION
diff --git a/git b/git
deleted file mode 160000
-Subproject 5fa0f5238b0cd46cfe7f6fa76c3f526ea98148d
diff --git a/html.c b/html.c
deleted file mode 100644
index 7f81965fdd..0000000000
--- a/html.c
+++ /dev/null
@@ -1,344 +0,0 @@
-/* html.c: helper functions for html output
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "html.h"
-#include "url.h"
-
-/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */
-static const char* url_escape_table[256] = {
-	"%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
-	"%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
-	"%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
-	"%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
-	"%20", NULL,  "%22", "%23", NULL,  "%25", "%26", "%27",
-	NULL,  NULL,  NULL,  "%2b", NULL,  NULL,  NULL,  NULL,
-	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
-	NULL,  NULL,  NULL,  NULL,  "%3c", "%3d", "%3e", "%3f",
-	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
-	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
-	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
-	NULL,  NULL,  NULL,  NULL,  "%5c", NULL,  "%5e", NULL,
-	"%60", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
-	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
-	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
-	NULL,  NULL,  NULL,  "%7b", "%7c", "%7d", NULL,  "%7f",
-	"%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
-	"%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
-	"%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
-	"%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
-	"%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
-	"%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
-	"%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
-	"%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
-	"%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
-	"%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
-	"%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
-	"%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
-	"%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
-	"%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
-	"%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
-	"%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
-};
-
-char *fmt(const char *format, ...)
-{
-	static char buf[8][1024];
-	static int bufidx;
-	int len;
-	va_list args;
-
-	bufidx++;
-	bufidx &= 7;
-
-	va_start(args, format);
-	len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args);
-	va_end(args);
-	if (len > sizeof(buf[bufidx])) {
-		fprintf(stderr, "[html.c] string truncated: %s\n", format);
-		exit(1);
-	}
-	return buf[bufidx];
-}
-
-char *fmtalloc(const char *format, ...)
-{
-	struct strbuf sb = STRBUF_INIT;
-	va_list args;
-
-	va_start(args, format);
-	strbuf_vaddf(&sb, format, args);
-	va_end(args);
-
-	return strbuf_detach(&sb, NULL);
-}
-
-void html_raw(const char *data, size_t size)
-{
-	if (write(STDOUT_FILENO, data, size) != size)
-		die_errno("write error on html output");
-}
-
-void html(const char *txt)
-{
-	html_raw(txt, strlen(txt));
-}
-
-void htmlf(const char *format, ...)
-{
-	va_list args;
-	struct strbuf buf = STRBUF_INIT;
-
-	va_start(args, format);
-	strbuf_vaddf(&buf, format, args);
-	va_end(args);
-	html(buf.buf);
-	strbuf_release(&buf);
-}
-
-void html_txtf(const char *format, ...)
-{
-	va_list args;
-
-	va_start(args, format);
-	html_vtxtf(format, args);
-	va_end(args);
-}
-
-void html_vtxtf(const char *format, va_list ap)
-{
-	va_list cp;
-	struct strbuf buf = STRBUF_INIT;
-
-	va_copy(cp, ap);
-	strbuf_vaddf(&buf, format, cp);
-	va_end(cp);
-	html_txt(buf.buf);
-	strbuf_release(&buf);
-}
-
-void html_txt(const char *txt)
-{
-	if (txt)
-		html_ntxt(txt, strlen(txt));
-}
-
-ssize_t html_ntxt(const char *txt, size_t len)
-{
-	const char *t = txt;
-	ssize_t slen;
-
-	if (len > SSIZE_MAX)
-		return -1;
-
-	slen = (ssize_t) len;
-	while (t && *t && slen--) {
-		int c = *t;
-		if (c == '<' || c == '>' || c == '&') {
-			html_raw(txt, t - txt);
-			if (c == '>')
-				html("&gt;");
-			else if (c == '<')
-				html("&lt;");
-			else if (c == '&')
-				html("&amp;");
-			txt = t + 1;
-		}
-		t++;
-	}
-	if (t != txt)
-		html_raw(txt, t - txt);
-	return slen;
-}
-
-void html_attrf(const char *fmt, ...)
-{
-	va_list ap;
-	struct strbuf sb = STRBUF_INIT;
-
-	va_start(ap, fmt);
-	strbuf_vaddf(&sb, fmt, ap);
-	va_end(ap);
-
-	html_attr(sb.buf);
-	strbuf_release(&sb);
-}
-
-void html_attr(const char *txt)
-{
-	const char *t = txt;
-	while (t && *t) {
-		int c = *t;
-		if (c == '<' || c == '>' || c == '\'' || c == '\"' || c == '&') {
-			html_raw(txt, t - txt);
-			if (c == '>')
-				html("&gt;");
-			else if (c == '<')
-				html("&lt;");
-			else if (c == '\'')
-				html("&#x27;");
-			else if (c == '"')
-				html("&quot;");
-			else if (c == '&')
-				html("&amp;");
-			txt = t + 1;
-		}
-		t++;
-	}
-	if (t != txt)
-		html(txt);
-}
-
-void html_url_path(const char *txt)
-{
-	const char *t = txt;
-	while (t && *t) {
-		unsigned char c = *t;
-		const char *e = url_escape_table[c];
-		if (e && c != '+' && c != '&') {
-			html_raw(txt, t - txt);
-			html(e);
-			txt = t + 1;
-		}
-		t++;
-	}
-	if (t != txt)
-		html(txt);
-}
-
-void html_url_arg(const char *txt)
-{
-	const char *t = txt;
-	while (t && *t) {
-		unsigned char c = *t;
-		const char *e = url_escape_table[c];
-		if (c == ' ')
-			e = "+";
-		if (e) {
-			html_raw(txt, t - txt);
-			html(e);
-			txt = t + 1;
-		}
-		t++;
-	}
-	if (t != txt)
-		html(txt);
-}
-
-void html_header_arg_in_quotes(const char *txt)
-{
-	const char *t = txt;
-	while (t && *t) {
-		unsigned char c = *t;
-		const char *e = NULL;
-		if (c == '\\')
-			e = "\\\\";
-		else if (c == '\r')
-			e = "\\r";
-		else if (c == '\n')
-			e = "\\n";
-		else if (c == '"')
-			e = "\\\"";
-		if (e) {
-			html_raw(txt, t - txt);
-			html(e);
-			txt = t + 1;
-		}
-		t++;
-	}
-	if (t != txt)
-		html(txt);
-
-}
-
-void html_hidden(const char *name, const char *value)
-{
-	html("<input type='hidden' name='");
-	html_attr(name);
-	html("' value='");
-	html_attr(value);
-	html("'/>");
-}
-
-void html_option(const char *value, const char *text, const char *selected_value)
-{
-	html("<option value='");
-	html_attr(value);
-	html("'");
-	if (selected_value && !strcmp(selected_value, value))
-		html(" selected='selected'");
-	html(">");
-	html_txt(text);
-	html("</option>\n");
-}
-
-void html_intoption(int value, const char *text, int selected_value)
-{
-	htmlf("<option value='%d'%s>", value,
-	      value == selected_value ? " selected='selected'" : "");
-	html_txt(text);
-	html("</option>");
-}
-
-void html_link_open(const char *url, const char *title, const char *class)
-{
-	html("<a href='");
-	html_attr(url);
-	if (title) {
-		html("' title='");
-		html_attr(title);
-	}
-	if (class) {
-		html("' class='");
-		html_attr(class);
-	}
-	html("'>");
-}
-
-void html_link_close(void)
-{
-	html("</a>");
-}
-
-void html_fileperm(unsigned short mode)
-{
-	htmlf("%c%c%c", (mode & 4 ? 'r' : '-'),
-	      (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-'));
-}
-
-int html_include(const char *filename)
-{
-	FILE *f;
-	char buf[4096];
-	size_t len;
-
-	if (!(f = fopen(filename, "r"))) {
-		fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n",
-			filename, strerror(errno), errno);
-		return -1;
-	}
-	while ((len = fread(buf, 1, 4096, f)) > 0)
-		html_raw(buf, len);
-	fclose(f);
-	return 0;
-}
-
-void http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value))
-{
-	const char *t = txt;
-
-	while (t && *t) {
-		char *name = url_decode_parameter_name(&t);
-		if (*name) {
-			char *value = url_decode_parameter_value(&t);
-			fn(name, value);
-			free(value);
-		}
-		free(name);
-	}
-}
diff --git a/html.h b/html.h
deleted file mode 100644
index fa4de77587..0000000000
--- a/html.h
+++ /dev/null
@@ -1,37 +0,0 @@
-#ifndef HTML_H
-#define HTML_H
-
-#include "cgit.h"
-
-extern void html_raw(const char *txt, size_t size);
-extern void html(const char *txt);
-
-__attribute__((format (printf,1,2)))
-extern void htmlf(const char *format,...);
-
-__attribute__((format (printf,1,2)))
-extern void html_txtf(const char *format,...);
-
-__attribute__((format (printf,1,0)))
-extern void html_vtxtf(const char *format, va_list ap);
-
-__attribute__((format (printf,1,2)))
-extern void html_attrf(const char *format,...);
-
-extern void html_txt(const char *txt);
-extern ssize_t html_ntxt(const char *txt, size_t len);
-extern void html_attr(const char *txt);
-extern void html_url_path(const char *txt);
-extern void html_url_arg(const char *txt);
-extern void html_header_arg_in_quotes(const char *txt);
-extern void html_hidden(const char *name, const char *value);
-extern void html_option(const char *value, const char *text, const char *selected_value);
-extern void html_intoption(int value, const char *text, int selected_value);
-extern void html_link_open(const char *url, const char *title, const char *class);
-extern void html_link_close(void);
-extern void html_fileperm(unsigned short mode);
-extern int html_include(const char *filename);
-
-extern void http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value));
-
-#endif /* HTML_H */
diff --git a/parsing.c b/parsing.c
deleted file mode 100644
index 7b3980e6b1..0000000000
--- a/parsing.c
+++ /dev/null
@@ -1,224 +0,0 @@
-/* parsing.c: parsing of config files
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-
-/*
- * url syntax: [repo ['/' cmd [ '/' path]]]
- *   repo: any valid repo url, may contain '/'
- *   cmd:  log | commit | diff | tree | view | blob | snapshot
- *   path: any valid path, may contain '/'
- *
- */
-void cgit_parse_url(const char *url)
-{
-	char *c, *cmd, *p;
-	struct cgit_repo *repo;
-
-	if (!url || url[0] == '\0')
-		return;
-
-	ctx.qry.page = NULL;
-	ctx.repo = cgit_get_repoinfo(url);
-	if (ctx.repo) {
-		ctx.qry.repo = ctx.repo->url;
-		return;
-	}
-
-	cmd = NULL;
-	c = strchr(url, '/');
-	while (c) {
-		c[0] = '\0';
-		repo = cgit_get_repoinfo(url);
-		if (repo) {
-			ctx.repo = repo;
-			cmd = c;
-		}
-		c[0] = '/';
-		c = strchr(c + 1, '/');
-	}
-
-	if (ctx.repo) {
-		ctx.qry.repo = ctx.repo->url;
-		p = strchr(cmd + 1, '/');
-		if (p) {
-			p[0] = '\0';
-			if (p[1])
-				ctx.qry.path = trim_end(p + 1, '/');
-		}
-		if (cmd[1])
-			ctx.qry.page = xstrdup(cmd + 1);
-	}
-}
-
-static char *substr(const char *head, const char *tail)
-{
-	char *buf;
-
-	if (tail < head)
-		return xstrdup("");
-	buf = xmalloc(tail - head + 1);
-	strlcpy(buf, head, tail - head + 1);
-	return buf;
-}
-
-static void parse_user(const char *t, char **name, char **email, unsigned long *date, int *tz)
-{
-	struct ident_split ident;
-	unsigned email_len;
-
-	if (!split_ident_line(&ident, t, strchrnul(t, '\n') - t)) {
-		*name = substr(ident.name_begin, ident.name_end);
-
-		email_len = ident.mail_end - ident.mail_begin;
-		*email = xmalloc(strlen("<") + email_len + strlen(">") + 1);
-		xsnprintf(*email, email_len + 3, "<%.*s>", email_len, ident.mail_begin);
-
-		if (ident.date_begin)
-			*date = strtoul(ident.date_begin, NULL, 10);
-		if (ident.tz_begin)
-			*tz = atoi(ident.tz_begin);
-	}
-}
-
-#ifdef NO_ICONV
-#define reencode(a, b, c)
-#else
-static const char *reencode(char **txt, const char *src_enc, const char *dst_enc)
-{
-	char *tmp;
-
-	if (!txt)
-		return NULL;
-
-	if (!*txt || !src_enc || !dst_enc)
-		return *txt;
-
-	/* no encoding needed if src_enc equals dst_enc */
-	if (!strcasecmp(src_enc, dst_enc))
-		return *txt;
-
-	tmp = reencode_string(*txt, dst_enc, src_enc);
-	if (tmp) {
-		free(*txt);
-		*txt = tmp;
-	}
-	return *txt;
-}
-#endif
-
-static const char *next_header_line(const char *p)
-{
-	p = strchr(p, '\n');
-	if (!p)
-		return NULL;
-	return p + 1;
-}
-
-static int end_of_header(const char *p)
-{
-	return !p || (*p == '\n');
-}
-
-struct commitinfo *cgit_parse_commit(struct commit *commit)
-{
-	const int sha1hex_len = 40;
-	struct commitinfo *ret;
-	const char *p = get_cached_commit_buffer(the_repository, commit, NULL);
-	const char *t;
-
-	ret = xcalloc(1, sizeof(struct commitinfo));
-	ret->commit = commit;
-
-	if (!p)
-		return ret;
-
-	if (!skip_prefix(p, "tree ", &p))
-		die("Bad commit: %s", oid_to_hex(&commit->object.oid));
-	p += sha1hex_len + 1;
-
-	while (skip_prefix(p, "parent ", &p))
-		p += sha1hex_len + 1;
-
-	if (p && skip_prefix(p, "author ", &p)) {
-		parse_user(p, &ret->author, &ret->author_email,
-			&ret->author_date, &ret->author_tz);
-		p = next_header_line(p);
-	}
-
-	if (p && skip_prefix(p, "committer ", &p)) {
-		parse_user(p, &ret->committer, &ret->committer_email,
-			&ret->committer_date, &ret->committer_tz);
-		p = next_header_line(p);
-	}
-
-	if (p && skip_prefix(p, "encoding ", &p)) {
-		t = strchr(p, '\n');
-		if (t) {
-			ret->msg_encoding = substr(p, t + 1);
-			p = t + 1;
-		}
-	}
-
-	if (!ret->msg_encoding)
-		ret->msg_encoding = xstrdup("UTF-8");
-
-	while (!end_of_header(p))
-		p = next_header_line(p);
-	while (p && *p == '\n')
-		p++;
-	if (!p)
-		return ret;
-
-	t = strchrnul(p, '\n');
-	ret->subject = substr(p, t);
-	while (*t == '\n')
-		t++;
-	ret->msg = xstrdup(t);
-
-	reencode(&ret->author, ret->msg_encoding, PAGE_ENCODING);
-	reencode(&ret->author_email, ret->msg_encoding, PAGE_ENCODING);
-	reencode(&ret->committer, ret->msg_encoding, PAGE_ENCODING);
-	reencode(&ret->committer_email, ret->msg_encoding, PAGE_ENCODING);
-	reencode(&ret->subject, ret->msg_encoding, PAGE_ENCODING);
-	reencode(&ret->msg, ret->msg_encoding, PAGE_ENCODING);
-
-	return ret;
-}
-
-struct taginfo *cgit_parse_tag(struct tag *tag)
-{
-	void *data;
-	enum object_type type;
-	unsigned long size;
-	const char *p;
-	struct taginfo *ret = NULL;
-
-	data = read_object_file(&tag->object.oid, &type, &size);
-	if (!data || type != OBJ_TAG)
-		goto cleanup;
-
-	ret = xcalloc(1, sizeof(struct taginfo));
-
-	for (p = data; !end_of_header(p); p = next_header_line(p)) {
-		if (skip_prefix(p, "tagger ", &p)) {
-			parse_user(p, &ret->tagger, &ret->tagger_email,
-				&ret->tagger_date, &ret->tagger_tz);
-		}
-	}
-
-	while (p && *p == '\n')
-		p++;
-
-	if (p && *p)
-		ret->msg = xstrdup(p);
-
-cleanup:
-	free(data);
-	return ret;
-}
diff --git a/robots.txt b/robots.txt
deleted file mode 100644
index 4ce948fec2..0000000000
--- a/robots.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-User-agent: *
-Disallow: /*/snapshot/*
-Allow: /
diff --git a/scan-tree.c b/scan-tree.c
deleted file mode 100644
index 6a2f65a86b..0000000000
--- a/scan-tree.c
+++ /dev/null
@@ -1,270 +0,0 @@
-/* scan-tree.c
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "scan-tree.h"
-#include "configfile.h"
-#include "html.h"
-#include <config.h>
-
-/* return 1 if path contains a objects/ directory and a HEAD file */
-static int is_git_dir(const char *path)
-{
-	struct stat st;
-	struct strbuf pathbuf = STRBUF_INIT;
-	int result = 0;
-
-	strbuf_addf(&pathbuf, "%s/objects", path);
-	if (stat(pathbuf.buf, &st)) {
-		if (errno != ENOENT)
-			fprintf(stderr, "Error checking path %s: %s (%d)\n",
-				path, strerror(errno), errno);
-		goto out;
-	}
-	if (!S_ISDIR(st.st_mode))
-		goto out;
-
-	strbuf_reset(&pathbuf);
-	strbuf_addf(&pathbuf, "%s/HEAD", path);
-	if (stat(pathbuf.buf, &st)) {
-		if (errno != ENOENT)
-			fprintf(stderr, "Error checking path %s: %s (%d)\n",
-				path, strerror(errno), errno);
-		goto out;
-	}
-	if (!S_ISREG(st.st_mode))
-		goto out;
-
-	result = 1;
-out:
-	strbuf_release(&pathbuf);
-	return result;
-}
-
-static struct cgit_repo *repo;
-static repo_config_fn config_fn;
-
-static void scan_tree_repo_config(const char *name, const char *value)
-{
-	config_fn(repo, name, value);
-}
-
-static int gitconfig_config(const char *key, const char *value, void *cb)
-{
-	const char *name;
-
-	if (!strcmp(key, "gitweb.owner"))
-		config_fn(repo, "owner", value);
-	else if (!strcmp(key, "gitweb.description"))
-		config_fn(repo, "desc", value);
-	else if (!strcmp(key, "gitweb.category"))
-		config_fn(repo, "section", value);
-	else if (!strcmp(key, "gitweb.homepage"))
-		config_fn(repo, "homepage", value);
-	else if (skip_prefix(key, "cgit.", &name))
-		config_fn(repo, name, value);
-
-	return 0;
-}
-
-static char *xstrrchr(char *s, char *from, int c)
-{
-	while (from >= s && *from != c)
-		from--;
-	return from < s ? NULL : from;
-}
-
-static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn)
-{
-	struct stat st;
-	struct passwd *pwd;
-	size_t pathlen;
-	struct strbuf rel = STRBUF_INIT;
-	char *p, *slash;
-	int n;
-	size_t size;
-
-	if (stat(path->buf, &st)) {
-		fprintf(stderr, "Error accessing %s: %s (%d)\n",
-			path->buf, strerror(errno), errno);
-		return;
-	}
-
-	strbuf_addch(path, '/');
-	pathlen = path->len;
-
-	if (ctx.cfg.strict_export) {
-		strbuf_addstr(path, ctx.cfg.strict_export);
-		if(stat(path->buf, &st))
-			return;
-		strbuf_setlen(path, pathlen);
-	}
-
-	strbuf_addstr(path, "noweb");
-	if (!stat(path->buf, &st))
-		return;
-	strbuf_setlen(path, pathlen);
-
-	if (!starts_with(path->buf, base))
-		strbuf_addbuf(&rel, path);
-	else
-		strbuf_addstr(&rel, path->buf + strlen(base) + 1);
-
-	if (!strcmp(rel.buf + rel.len - 5, "/.git"))
-		strbuf_setlen(&rel, rel.len - 5);
-	else if (rel.len && rel.buf[rel.len - 1] == '/')
-		strbuf_setlen(&rel, rel.len - 1);
-
-	repo = cgit_add_repo(rel.buf);
-	config_fn = fn;
-	if (ctx.cfg.enable_git_config) {
-		strbuf_addstr(path, "config");
-		git_config_from_file(gitconfig_config, path->buf, NULL);
-		strbuf_setlen(path, pathlen);
-	}
-
-	if (ctx.cfg.remove_suffix) {
-		size_t urllen;
-		strip_suffix(repo->url, ".git", &urllen);
-		strip_suffix_mem(repo->url, &urllen, "/");
-		repo->url[urllen] = '\0';
-	}
-	repo->path = xstrdup(path->buf);
-	while (!repo->owner) {
-		if ((pwd = getpwuid(st.st_uid)) == NULL) {
-			fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
-				path->buf, strerror(errno), errno);
-			break;
-		}
-		if (pwd->pw_gecos)
-			if ((p = strchr(pwd->pw_gecos, ',')))
-				*p = '\0';
-		repo->owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name);
-	}
-
-	if (repo->desc == cgit_default_repo_desc || !repo->desc) {
-		strbuf_addstr(path, "description");
-		if (!stat(path->buf, &st))
-			readfile(path->buf, &repo->desc, &size);
-		strbuf_setlen(path, pathlen);
-	}
-
-	if (ctx.cfg.section_from_path) {
-		n = ctx.cfg.section_from_path;
-		if (n > 0) {
-			slash = rel.buf - 1;
-			while (slash && n && (slash = strchr(slash + 1, '/')))
-				n--;
-		} else {
-			slash = rel.buf + rel.len;
-			while (slash && n && (slash = xstrrchr(rel.buf, slash - 1, '/')))
-				n++;
-		}
-		if (slash && !n) {
-			*slash = '\0';
-			repo->section = xstrdup(rel.buf);
-			*slash = '/';
-			if (starts_with(repo->name, repo->section)) {
-				repo->name += strlen(repo->section);
-				if (*repo->name == '/')
-					repo->name++;
-			}
-		}
-	}
-
-	strbuf_addstr(path, "cgitrc");
-	if (!stat(path->buf, &st))
-		parse_configfile(path->buf, &scan_tree_repo_config);
-
-	strbuf_release(&rel);
-}
-
-static void scan_path(const char *base, const char *path, repo_config_fn fn)
-{
-	DIR *dir = opendir(path);
-	struct dirent *ent;
-	struct strbuf pathbuf = STRBUF_INIT;
-	size_t pathlen = strlen(path);
-	struct stat st;
-
-	if (!dir) {
-		fprintf(stderr, "Error opening directory %s: %s (%d)\n",
-			path, strerror(errno), errno);
-		return;
-	}
-
-	strbuf_add(&pathbuf, path, strlen(path));
-	if (is_git_dir(pathbuf.buf)) {
-		add_repo(base, &pathbuf, fn);
-		goto end;
-	}
-	strbuf_addstr(&pathbuf, "/.git");
-	if (is_git_dir(pathbuf.buf)) {
-		add_repo(base, &pathbuf, fn);
-		goto end;
-	}
-	/*
-	 * Add one because we don't want to lose the trailing '/' when we
-	 * reset the length of pathbuf in the loop below.
-	 */
-	pathlen++;
-	while ((ent = readdir(dir)) != NULL) {
-		if (ent->d_name[0] == '.') {
-			if (ent->d_name[1] == '\0')
-				continue;
-			if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
-				continue;
-			if (!ctx.cfg.scan_hidden_path)
-				continue;
-		}
-		strbuf_setlen(&pathbuf, pathlen);
-		strbuf_addstr(&pathbuf, ent->d_name);
-		if (stat(pathbuf.buf, &st)) {
-			fprintf(stderr, "Error checking path %s: %s (%d)\n",
-				pathbuf.buf, strerror(errno), errno);
-			continue;
-		}
-		if (S_ISDIR(st.st_mode))
-			scan_path(base, pathbuf.buf, fn);
-	}
-end:
-	strbuf_release(&pathbuf);
-	closedir(dir);
-}
-
-void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn)
-{
-	struct strbuf line = STRBUF_INIT;
-	FILE *projects;
-	int err;
-
-	projects = fopen(projectsfile, "r");
-	if (!projects) {
-		fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n",
-			projectsfile, strerror(errno), errno);
-		return;
-	}
-	while (strbuf_getline(&line, projects) != EOF) {
-		if (!line.len)
-			continue;
-		strbuf_insert(&line, 0, "/", 1);
-		strbuf_insert(&line, 0, path, strlen(path));
-		scan_path(path, line.buf, fn);
-	}
-	if ((err = ferror(projects))) {
-		fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
-			projectsfile, strerror(err), err);
-	}
-	fclose(projects);
-	strbuf_release(&line);
-}
-
-void scan_tree(const char *path, repo_config_fn fn)
-{
-	scan_path(path, path, fn);
-}
diff --git a/scan-tree.h b/scan-tree.h
deleted file mode 100644
index 1afbd4bbcd..0000000000
--- a/scan-tree.h
+++ /dev/null
@@ -1,2 +0,0 @@
-extern void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn);
-extern void scan_tree(const char *path, repo_config_fn fn);
diff --git a/shared.c b/shared.c
deleted file mode 100644
index 8115469a7c..0000000000
--- a/shared.c
+++ /dev/null
@@ -1,579 +0,0 @@
-/* shared.c: global vars + some callback functions
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-
-struct cgit_repolist cgit_repolist;
-struct cgit_context ctx;
-
-int chk_zero(int result, char *msg)
-{
-	if (result != 0)
-		die_errno("%s", msg);
-	return result;
-}
-
-int chk_positive(int result, char *msg)
-{
-	if (result <= 0)
-		die_errno("%s", msg);
-	return result;
-}
-
-int chk_non_negative(int result, char *msg)
-{
-	if (result < 0)
-		die_errno("%s", msg);
-	return result;
-}
-
-char *cgit_default_repo_desc = "[no description]";
-struct cgit_repo *cgit_add_repo(const char *url)
-{
-	struct cgit_repo *ret;
-
-	if (++cgit_repolist.count > cgit_repolist.length) {
-		if (cgit_repolist.length == 0)
-			cgit_repolist.length = 8;
-		else
-			cgit_repolist.length *= 2;
-		cgit_repolist.repos = xrealloc(cgit_repolist.repos,
-					       cgit_repolist.length *
-					       sizeof(struct cgit_repo));
-	}
-
-	ret = &cgit_repolist.repos[cgit_repolist.count-1];
-	memset(ret, 0, sizeof(struct cgit_repo));
-	ret->url = trim_end(url, '/');
-	ret->name = ret->url;
-	ret->path = NULL;
-	ret->desc = cgit_default_repo_desc;
-	ret->extra_head_content = NULL;
-	ret->owner = NULL;
-	ret->homepage = NULL;
-	ret->section = ctx.cfg.section;
-	ret->snapshots = ctx.cfg.snapshots;
-	ret->enable_blame = ctx.cfg.enable_blame;
-	ret->enable_commit_graph = ctx.cfg.enable_commit_graph;
-	ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
-	ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
-	ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
-	ret->enable_subject_links = ctx.cfg.enable_subject_links;
-	ret->enable_html_serving = ctx.cfg.enable_html_serving;
-	ret->max_stats = ctx.cfg.max_stats;
-	ret->branch_sort = ctx.cfg.branch_sort;
-	ret->commit_sort = ctx.cfg.commit_sort;
-	ret->module_link = ctx.cfg.module_link;
-	ret->readme = ctx.cfg.readme;
-	ret->mtime = -1;
-	ret->about_filter = ctx.cfg.about_filter;
-	ret->commit_filter = ctx.cfg.commit_filter;
-	ret->source_filter = ctx.cfg.source_filter;
-	ret->email_filter = ctx.cfg.email_filter;
-	ret->owner_filter = ctx.cfg.owner_filter;
-	ret->clone_url = ctx.cfg.clone_url;
-	ret->submodules.strdup_strings = 1;
-	ret->hide = ret->ignore = 0;
-	return ret;
-}
-
-struct cgit_repo *cgit_get_repoinfo(const char *url)
-{
-	int i;
-	struct cgit_repo *repo;
-
-	for (i = 0; i < cgit_repolist.count; i++) {
-		repo = &cgit_repolist.repos[i];
-		if (repo->ignore)
-			continue;
-		if (!strcmp(repo->url, url))
-			return repo;
-	}
-	return NULL;
-}
-
-void cgit_free_commitinfo(struct commitinfo *info)
-{
-	free(info->author);
-	free(info->author_email);
-	free(info->committer);
-	free(info->committer_email);
-	free(info->subject);
-	free(info->msg);
-	free(info->msg_encoding);
-	free(info);
-}
-
-char *trim_end(const char *str, char c)
-{
-	int len;
-
-	if (str == NULL)
-		return NULL;
-	len = strlen(str);
-	while (len > 0 && str[len - 1] == c)
-		len--;
-	if (len == 0)
-		return NULL;
-	return xstrndup(str, len);
-}
-
-char *ensure_end(const char *str, char c)
-{
-	size_t len = strlen(str);
-	char *result;
-
-	if (len && str[len - 1] == c)
-		return xstrndup(str, len);
-
-	result = xmalloc(len + 2);
-	memcpy(result, str, len);
-	result[len] = '/';
-	result[len + 1] = '\0';
-	return result;
-}
-
-void strbuf_ensure_end(struct strbuf *sb, char c)
-{
-	if (!sb->len || sb->buf[sb->len - 1] != c)
-		strbuf_addch(sb, c);
-}
-
-void cgit_add_ref(struct reflist *list, struct refinfo *ref)
-{
-	size_t size;
-
-	if (list->count >= list->alloc) {
-		list->alloc += (list->alloc ? list->alloc : 4);
-		size = list->alloc * sizeof(struct refinfo *);
-		list->refs = xrealloc(list->refs, size);
-	}
-	list->refs[list->count++] = ref;
-}
-
-static struct refinfo *cgit_mk_refinfo(const char *refname, const struct object_id *oid)
-{
-	struct refinfo *ref;
-
-	ref = xmalloc(sizeof (struct refinfo));
-	ref->refname = xstrdup(refname);
-	ref->object = parse_object(the_repository, oid);
-	switch (ref->object->type) {
-	case OBJ_TAG:
-		ref->tag = cgit_parse_tag((struct tag *)ref->object);
-		break;
-	case OBJ_COMMIT:
-		ref->commit = cgit_parse_commit((struct commit *)ref->object);
-		break;
-	}
-	return ref;
-}
-
-void cgit_free_taginfo(struct taginfo *tag)
-{
-	if (tag->tagger)
-		free(tag->tagger);
-	if (tag->tagger_email)
-		free(tag->tagger_email);
-	if (tag->msg)
-		free(tag->msg);
-	free(tag);
-}
-
-static void cgit_free_refinfo(struct refinfo *ref)
-{
-	if (ref->refname)
-		free((char *)ref->refname);
-	switch (ref->object->type) {
-	case OBJ_TAG:
-		cgit_free_taginfo(ref->tag);
-		break;
-	case OBJ_COMMIT:
-		cgit_free_commitinfo(ref->commit);
-		break;
-	}
-	free(ref);
-}
-
-void cgit_free_reflist_inner(struct reflist *list)
-{
-	int i;
-
-	for (i = 0; i < list->count; i++) {
-		cgit_free_refinfo(list->refs[i]);
-	}
-	free(list->refs);
-}
-
-int cgit_refs_cb(const char *refname, const struct object_id *oid, int flags,
-		  void *cb_data)
-{
-	struct reflist *list = (struct reflist *)cb_data;
-	struct refinfo *info = cgit_mk_refinfo(refname, oid);
-
-	if (info)
-		cgit_add_ref(list, info);
-	return 0;
-}
-
-void cgit_diff_tree_cb(struct diff_queue_struct *q,
-		       struct diff_options *options, void *data)
-{
-	int i;
-
-	for (i = 0; i < q->nr; i++) {
-		if (q->queue[i]->status == 'U')
-			continue;
-		((filepair_fn)data)(q->queue[i]);
-	}
-}
-
-static int load_mmfile(mmfile_t *file, const struct object_id *oid)
-{
-	enum object_type type;
-
-	if (is_null_oid(oid)) {
-		file->ptr = (char *)"";
-		file->size = 0;
-	} else {
-		file->ptr = read_object_file(oid, &type,
-		                           (unsigned long *)&file->size);
-	}
-	return 1;
-}
-
-/*
- * Receive diff-buffers from xdiff and concatenate them as
- * needed across multiple callbacks.
- *
- * This is basically a copy of xdiff-interface.c/xdiff_outf(),
- * ripped from git and modified to use globals instead of
- * a special callback-struct.
- */
-static char *diffbuf = NULL;
-static int buflen = 0;
-
-static int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
-{
-	int i;
-
-	for (i = 0; i < nbuf; i++) {
-		if (mb[i].ptr[mb[i].size-1] != '\n') {
-			/* Incomplete line */
-			diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
-			memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
-			buflen += mb[i].size;
-			continue;
-		}
-
-		/* we have a complete line */
-		if (!diffbuf) {
-			((linediff_fn)priv)(mb[i].ptr, mb[i].size);
-			continue;
-		}
-		diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
-		memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
-		((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
-		free(diffbuf);
-		diffbuf = NULL;
-		buflen = 0;
-	}
-	if (diffbuf) {
-		((linediff_fn)priv)(diffbuf, buflen);
-		free(diffbuf);
-		diffbuf = NULL;
-		buflen = 0;
-	}
-	return 0;
-}
-
-int cgit_diff_files(const struct object_id *old_oid,
-		    const struct object_id *new_oid, unsigned long *old_size,
-		    unsigned long *new_size, int *binary, int context,
-		    int ignorews, linediff_fn fn)
-{
-	mmfile_t file1, file2;
-	xpparam_t diff_params;
-	xdemitconf_t emit_params;
-	xdemitcb_t emit_cb;
-
-	if (!load_mmfile(&file1, old_oid) || !load_mmfile(&file2, new_oid))
-		return 1;
-
-	*old_size = file1.size;
-	*new_size = file2.size;
-
-	if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
-	    (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
-		*binary = 1;
-		if (file1.size)
-			free(file1.ptr);
-		if (file2.size)
-			free(file2.ptr);
-		return 0;
-	}
-
-	memset(&diff_params, 0, sizeof(diff_params));
-	memset(&emit_params, 0, sizeof(emit_params));
-	memset(&emit_cb, 0, sizeof(emit_cb));
-	diff_params.flags = XDF_NEED_MINIMAL;
-	if (ignorews)
-		diff_params.flags |= XDF_IGNORE_WHITESPACE;
-	emit_params.ctxlen = context > 0 ? context : 3;
-	emit_params.flags = XDL_EMIT_FUNCNAMES;
-	emit_cb.out_line = filediff_cb;
-	emit_cb.priv = fn;
-	xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
-	if (file1.size)
-		free(file1.ptr);
-	if (file2.size)
-		free(file2.ptr);
-	return 0;
-}
-
-void cgit_diff_tree(const struct object_id *old_oid,
-		    const struct object_id *new_oid,
-		    filepair_fn fn, const char *prefix, int ignorews)
-{
-	struct diff_options opt;
-	struct pathspec_item item;
-
-	memset(&item, 0, sizeof(item));
-	diff_setup(&opt);
-	opt.output_format = DIFF_FORMAT_CALLBACK;
-	opt.detect_rename = 1;
-	opt.rename_limit = ctx.cfg.renamelimit;
-	opt.flags.recursive = 1;
-	if (ignorews)
-		DIFF_XDL_SET(&opt, IGNORE_WHITESPACE);
-	opt.format_callback = cgit_diff_tree_cb;
-	opt.format_callback_data = fn;
-	if (prefix) {
-		item.match = xstrdup(prefix);
-		item.len = strlen(prefix);
-		opt.pathspec.nr = 1;
-		opt.pathspec.items = &item;
-	}
-	diff_setup_done(&opt);
-
-	if (old_oid && !is_null_oid(old_oid))
-		diff_tree_oid(old_oid, new_oid, "", &opt);
-	else
-		diff_root_tree_oid(new_oid, "", &opt);
-	diffcore_std(&opt);
-	diff_flush(&opt);
-
-	free(item.match);
-}
-
-void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix)
-{
-	const struct object_id *old_oid = NULL;
-
-	if (commit->parents)
-		old_oid = &commit->parents->item->object.oid;
-	cgit_diff_tree(old_oid, &commit->object.oid, fn, prefix,
-		       ctx.qry.ignorews);
-}
-
-int cgit_parse_snapshots_mask(const char *str)
-{
-	struct string_list tokens = STRING_LIST_INIT_DUP;
-	struct string_list_item *item;
-	const struct cgit_snapshot_format *f;
-	int rv = 0;
-
-	/* favor legacy setting */
-	if (atoi(str))
-		return 1;
-
-	if (strcmp(str, "all") == 0)
-		return INT_MAX;
-
-	string_list_split(&tokens, str, ' ', -1);
-	string_list_remove_empty_items(&tokens, 0);
-
-	for_each_string_list_item(item, &tokens) {
-		for (f = cgit_snapshot_formats; f->suffix; f++) {
-			if (!strcmp(item->string, f->suffix) ||
-			    !strcmp(item->string, f->suffix + 1)) {
-				rv |= cgit_snapshot_format_bit(f);
-				break;
-			}
-		}
-	}
-
-	string_list_clear(&tokens, 0);
-	return rv;
-}
-
-typedef struct {
-	char * name;
-	char * value;
-} cgit_env_var;
-
-void cgit_prepare_repo_env(struct cgit_repo * repo)
-{
-	cgit_env_var env_vars[] = {
-		{ .name = "CGIT_REPO_URL", .value = repo->url },
-		{ .name = "CGIT_REPO_NAME", .value = repo->name },
-		{ .name = "CGIT_REPO_PATH", .value = repo->path },
-		{ .name = "CGIT_REPO_OWNER", .value = repo->owner },
-		{ .name = "CGIT_REPO_DEFBRANCH", .value = repo->defbranch },
-		{ .name = "CGIT_REPO_SECTION", .value = repo->section },
-		{ .name = "CGIT_REPO_CLONE_URL", .value = repo->clone_url }
-	};
-	int env_var_count = ARRAY_SIZE(env_vars);
-	cgit_env_var *p, *q;
-	static char *warn = "cgit warning: failed to set env: %s=%s\n";
-
-	p = env_vars;
-	q = p + env_var_count;
-	for (; p < q; p++)
-		if (p->value && setenv(p->name, p->value, 1))
-			fprintf(stderr, warn, p->name, p->value);
-}
-
-/* Read the content of the specified file into a newly allocated buffer,
- * zeroterminate the buffer and return 0 on success, errno otherwise.
- */
-int readfile(const char *path, char **buf, size_t *size)
-{
-	int fd, e;
-	struct stat st;
-
-	fd = open(path, O_RDONLY);
-	if (fd == -1)
-		return errno;
-	if (fstat(fd, &st)) {
-		e = errno;
-		close(fd);
-		return e;
-	}
-	if (!S_ISREG(st.st_mode)) {
-		close(fd);
-		return EISDIR;
-	}
-	*buf = xmalloc(st.st_size + 1);
-	*size = read_in_full(fd, *buf, st.st_size);
-	e = errno;
-	(*buf)[*size] = '\0';
-	close(fd);
-	return (*size == st.st_size ? 0 : e);
-}
-
-static int is_token_char(char c)
-{
-	return isalnum(c) || c == '_';
-}
-
-/* Replace name with getenv(name), return pointer to zero-terminating char
- */
-static char *expand_macro(char *name, int maxlength)
-{
-	char *value;
-	size_t len;
-
-	len = 0;
-	value = getenv(name);
-	if (value) {
-		len = strlen(value) + 1;
-		if (len > maxlength)
-			len = maxlength;
-		strlcpy(name, value, len);
-		--len;
-	}
-	return name + len;
-}
-
-#define EXPBUFSIZE (1024 * 8)
-
-/* Replace all tokens prefixed by '$' in the specified text with the
- * value of the named environment variable.
- * NB: the return value is a static buffer, i.e. it must be strdup'd
- * by the caller.
- */
-char *expand_macros(const char *txt)
-{
-	static char result[EXPBUFSIZE];
-	char *p, *start;
-	int len;
-
-	p = result;
-	start = NULL;
-	while (p < result + EXPBUFSIZE - 1 && txt && *txt) {
-		*p = *txt;
-		if (start) {
-			if (!is_token_char(*txt)) {
-				if (p - start > 0) {
-					*p = '\0';
-					len = result + EXPBUFSIZE - start - 1;
-					p = expand_macro(start, len) - 1;
-				}
-				start = NULL;
-				txt--;
-			}
-			p++;
-			txt++;
-			continue;
-		}
-		if (*txt == '$') {
-			start = p;
-			txt++;
-			continue;
-		}
-		p++;
-		txt++;
-	}
-	*p = '\0';
-	if (start && p - start > 0) {
-		len = result + EXPBUFSIZE - start - 1;
-		p = expand_macro(start, len);
-		*p = '\0';
-	}
-	return result;
-}
-
-char *get_mimetype_for_filename(const char *filename)
-{
-	char *ext, *mimetype, *token, line[1024], *saveptr;
-	FILE *file;
-	struct string_list_item *mime;
-
-	if (!filename)
-		return NULL;
-
-	ext = strrchr(filename, '.');
-	if (!ext)
-		return NULL;
-	++ext;
-	if (!ext[0])
-		return NULL;
-	mime = string_list_lookup(&ctx.cfg.mimetypes, ext);
-	if (mime)
-		return xstrdup(mime->util);
-
-	if (!ctx.cfg.mimetype_file)
-		return NULL;
-	file = fopen(ctx.cfg.mimetype_file, "r");
-	if (!file)
-		return NULL;
-	while (fgets(line, sizeof(line), file)) {
-		if (!line[0] || line[0] == '#')
-			continue;
-		mimetype = strtok_r(line, " \t\r\n", &saveptr);
-		while ((token = strtok_r(NULL, " \t\r\n", &saveptr))) {
-			if (!strcasecmp(ext, token)) {
-				fclose(file);
-				return xstrdup(mimetype);
-			}
-		}
-	}
-	fclose(file);
-	return NULL;
-}
diff --git a/tests/.gitignore b/tests/.gitignore
deleted file mode 100644
index 3fd2e965c8..0000000000
--- a/tests/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-trash\ directory.t*
-test-results
diff --git a/tests/Makefile b/tests/Makefile
deleted file mode 100644
index 65e1117338..0000000000
--- a/tests/Makefile
+++ /dev/null
@@ -1,17 +0,0 @@
-include ../git/config.mak.uname
--include ../cgit.conf
-
-SHELL_PATH ?= $(SHELL)
-SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
-
-T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
-
-all: $(T)
-
-$(T):
-	@'$(SHELL_PATH_SQ)' $@ $(CGIT_TEST_OPTS)
-
-clean:
-	$(RM) -rf trash
-
-.PHONY: $(T) clean
diff --git a/tests/filters/dump.lua b/tests/filters/dump.lua
deleted file mode 100644
index 1f15c93105..0000000000
--- a/tests/filters/dump.lua
+++ /dev/null
@@ -1,17 +0,0 @@
-function filter_open(...)
-	buffer = ""
-	for i = 1, select("#", ...) do
-		buffer = buffer .. select(i, ...) .. " "
-	end
-end
-
-function filter_close()
-	html(buffer)
-	return 0
-end
-
-function filter_write(str)
-	buffer = buffer .. string.upper(str)
-end
-
-
diff --git a/tests/filters/dump.sh b/tests/filters/dump.sh
deleted file mode 100755
index da6f7a1b18..0000000000
--- a/tests/filters/dump.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-
-[ "$#" -gt 0 ] && printf "%s " "$*"
-tr '[:lower:]' '[:upper:]'
diff --git a/tests/setup.sh b/tests/setup.sh
deleted file mode 100755
index 7590f04944..0000000000
--- a/tests/setup.sh
+++ /dev/null
@@ -1,176 +0,0 @@
-# This file should be sourced by all test-scripts
-#
-# Main functions:
-#   prepare_tests(description) - setup for testing, i.e. create repos+config
-#   run_test(description, script) - run one test, i.e. eval script
-#
-# Helper functions
-#   cgit_query(querystring) - call cgit with the specified querystring
-#   cgit_url(url) - call cgit with the specified virtual url
-#
-# Example script:
-#
-# . setup.sh
-# prepare_tests "html validation"
-# run_test 'repo index' 'cgit_url "/" | tidy -e'
-# run_test 'repo summary' 'cgit_url "/foo" | tidy -e'
-
-# We don't want to run Git commands through Valgrind, so we filter out the
-# --valgrind option here and handle it ourselves.  We copy the arguments
-# assuming that none contain a newline, although other whitespace is
-# preserved.
-LF='
-'
-test_argv=
-
-while test $# != 0
-do
-	case "$1" in
-	--va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
-		cgit_valgrind=t
-		test_argv="$test_argv${LF}--verbose"
-		;;
-	*)
-		test_argv="$test_argv$LF$1"
-		;;
-	esac
-	shift
-done
-
-OLDIFS=$IFS
-IFS=$LF
-set -- $test_argv
-IFS=$OLDIFS
-
-: ${TEST_DIRECTORY=$(pwd)/../git/t}
-: ${TEST_OUTPUT_DIRECTORY=$(pwd)}
-TEST_NO_CREATE_REPO=YesPlease
-. "$TEST_DIRECTORY"/test-lib.sh
-
-# Prepend the directory containing cgit to PATH.
-if test -n "$cgit_valgrind"
-then
-	GIT_VALGRIND="$TEST_DIRECTORY/valgrind"
-	CGIT_VALGRIND=$(cd ../valgrind && pwd)
-	PATH="$CGIT_VALGRIND/bin:$PATH"
-	export GIT_VALGRIND CGIT_VALGRIND
-else
-	PATH="$(pwd)/../..:$PATH"
-fi
-
-FILTER_DIRECTORY=$(cd ../filters && pwd)
-
-if cgit --version | grep -F -q "[+] Lua scripting"; then
-	export CGIT_HAS_LUA=1
-else
-	export CGIT_HAS_LUA=0
-fi
-
-mkrepo() {
-	name=$1
-	count=$2
-	test_create_repo "$name"
-	(
-		cd "$name"
-		n=1
-		while test $n -le $count
-		do
-			echo $n >file-$n
-			git add file-$n
-			git commit -m "commit $n"
-			n=$(expr $n + 1)
-		done
-		if test "$3" = "testplus"
-		then
-			echo "hello" >a+b
-			git add a+b
-			git commit -m "add a+b"
-			git branch "1+2"
-		fi
-	)
-}
-
-setup_repos()
-{
-	rm -rf cache
-	mkdir -p cache
-	mkrepo repos/foo 5 >/dev/null
-	mkrepo repos/bar 50 >/dev/null
-	mkrepo repos/foo+bar 10 testplus >/dev/null
-	mkrepo "repos/with space" 2 >/dev/null
-	mkrepo repos/filter 5 testplus >/dev/null
-	cat >cgitrc <<EOF
-virtual-root=/
-cache-root=$PWD/cache
-
-cache-size=1021
-snapshots=tar.gz tar.bz zip
-enable-log-filecount=1
-enable-log-linecount=1
-summary-log=5
-summary-branches=5
-summary-tags=5
-clone-url=git://example.org/\$CGIT_REPO_URL.git
-enable-filter-overrides=1
-
-repo.url=foo
-repo.path=$PWD/repos/foo/.git
-# Do not specify a description for this repo, as it then will be assigned
-# the constant value "[no description]" (which actually used to cause a
-# segfault).
-
-repo.url=bar
-repo.path=$PWD/repos/bar/.git
-repo.desc=the bar repo
-
-repo.url=foo+bar
-repo.path=$PWD/repos/foo+bar/.git
-repo.desc=the foo+bar repo
-
-repo.url=with space
-repo.path=$PWD/repos/with space/.git
-repo.desc=spaced repo
-
-repo.url=filter-exec
-repo.path=$PWD/repos/filter/.git
-repo.desc=filtered repo
-repo.about-filter=exec:$FILTER_DIRECTORY/dump.sh
-repo.commit-filter=exec:$FILTER_DIRECTORY/dump.sh
-repo.email-filter=exec:$FILTER_DIRECTORY/dump.sh
-repo.source-filter=exec:$FILTER_DIRECTORY/dump.sh
-repo.readme=master:a+b
-EOF
-
-	if [ $CGIT_HAS_LUA -eq 1 ]; then
-		cat >>cgitrc <<EOF
-repo.url=filter-lua
-repo.path=$PWD/repos/filter/.git
-repo.desc=filtered repo
-repo.about-filter=lua:$FILTER_DIRECTORY/dump.lua
-repo.commit-filter=lua:$FILTER_DIRECTORY/dump.lua
-repo.email-filter=lua:$FILTER_DIRECTORY/dump.lua
-repo.source-filter=lua:$FILTER_DIRECTORY/dump.lua
-repo.readme=master:a+b
-EOF
-	fi
-}
-
-cgit_query()
-{
-	CGIT_CONFIG="$PWD/cgitrc" QUERY_STRING="$1" cgit
-}
-
-cgit_url()
-{
-	CGIT_CONFIG="$PWD/cgitrc" QUERY_STRING="url=$1" cgit
-}
-
-strip_headers() {
-	while read -r line
-	do
-		test -z "$line" && break
-	done
-	cat
-}
-
-test -z "$CGIT_TEST_NO_CREATE_REPOS" && setup_repos
diff --git a/tests/t0001-validate-git-versions.sh b/tests/t0001-validate-git-versions.sh
deleted file mode 100755
index 3200f31101..0000000000
--- a/tests/t0001-validate-git-versions.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/sh
-
-test_description='Check Git version is correct'
-CGIT_TEST_NO_CREATE_REPOS=YesPlease
-. ./setup.sh
-
-test_expect_success 'extract Git version from Makefile' '
-	sed -n -e "/^GIT_VER[ 	]*=/ {
-		s/^GIT_VER[ 	]*=[ 	]*//
-		p
-	}" ../../Makefile >makefile_version
-'
-
-# Note that Git's GIT-VERSION-GEN script applies "s/-/./g" to the version
-# string to produce the internal version in the GIT-VERSION-FILE, so we
-# must apply the same transformation to the version in the Makefile before
-# comparing them.
-test_expect_success 'test Git version matches Makefile' '
-	( cat ../../git/GIT-VERSION-FILE || echo "No GIT-VERSION-FILE" ) |
-	sed -e "s/GIT_VERSION[ 	]*=[ 	]*//" -e "s/\\.dirty$//" >git_version &&
-	sed -e "s/-/./g" makefile_version >makefile_git_version &&
-	test_cmp git_version makefile_git_version
-'
-
-test_expect_success 'test submodule version matches Makefile' '
-	if ! test -e ../../git/.git
-	then
-		echo "git/ is not a Git repository" >&2
-	else
-		(
-			cd ../.. &&
-			sm_sha1=$(git ls-files --stage -- git |
-				sed -e "s/^[0-9]* \\([0-9a-f]*\\) [0-9]	.*$/\\1/") &&
-			cd git &&
-			git describe --match "v[0-9]*" $sm_sha1
-		) | sed -e "s/^v//" -e "s/-/./" >sm_version &&
-		test_cmp sm_version makefile_version
-	fi
-'
-
-test_done
diff --git a/tests/t0010-validate-html.sh b/tests/t0010-validate-html.sh
deleted file mode 100755
index ca08d69d14..0000000000
--- a/tests/t0010-validate-html.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/bin/sh
-
-test_description='Validate html with tidy'
-. ./setup.sh
-
-
-test_url()
-{
-	tidy_opt="-eq"
-	test -z "$NO_TIDY_WARNINGS" || tidy_opt+=" --show-warnings no"
-	cgit_url "$1" >tidy-$test_count.tmp || return
-	sed -e "1,4d" tidy-$test_count.tmp >tidy-$test_count || return
-	"$tidy" $tidy_opt tidy-$test_count
-	rc=$?
-
-	# tidy returns with exitcode 1 on warnings, 2 on error
-	if test $rc = 2
-	then
-		false
-	else
-		:
-	fi
-}
-
-tidy=`which tidy 2>/dev/null`
-test -n "$tidy" || {
-	skip_all='Skipping html validation tests: tidy not found'
-	test_done
-	exit
-}
-
-test_expect_success 'index page' 'test_url ""'
-test_expect_success 'foo' 'test_url "foo"'
-test_expect_success 'foo/log' 'test_url "foo/log"'
-test_expect_success 'foo/tree' 'test_url "foo/tree"'
-test_expect_success 'foo/tree/file-1' 'test_url "foo/tree/file-1"'
-test_expect_success 'foo/commit' 'test_url "foo/commit"'
-test_expect_success 'foo/diff' 'test_url "foo/diff"'
-
-test_done
diff --git a/tests/t0020-validate-cache.sh b/tests/t0020-validate-cache.sh
deleted file mode 100755
index 657765d897..0000000000
--- a/tests/t0020-validate-cache.sh
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/sh
-
-test_description='Validate cache'
-. ./setup.sh
-
-test_expect_success 'verify cache-size=0' '
-
-	rm -f cache/* &&
-	sed -e "s/cache-size=1021$/cache-size=0/" cgitrc >cgitrc.tmp &&
-	mv -f cgitrc.tmp cgitrc &&
-	cgit_url "" &&
-	cgit_url "foo" &&
-	cgit_url "foo/refs" &&
-	cgit_url "foo/tree" &&
-	cgit_url "foo/log" &&
-	cgit_url "foo/diff" &&
-	cgit_url "foo/patch" &&
-	cgit_url "bar" &&
-	cgit_url "bar/refs" &&
-	cgit_url "bar/tree" &&
-	cgit_url "bar/log" &&
-	cgit_url "bar/diff" &&
-	cgit_url "bar/patch" &&
-	ls cache >output &&
-	test_line_count = 0 output
-'
-
-test_expect_success 'verify cache-size=1' '
-
-	rm -f cache/* &&
-	sed -e "s/cache-size=0$/cache-size=1/" cgitrc >cgitrc.tmp &&
-	mv -f cgitrc.tmp cgitrc &&
-	cgit_url "" &&
-	cgit_url "foo" &&
-	cgit_url "foo/refs" &&
-	cgit_url "foo/tree" &&
-	cgit_url "foo/log" &&
-	cgit_url "foo/diff" &&
-	cgit_url "foo/patch" &&
-	cgit_url "bar" &&
-	cgit_url "bar/refs" &&
-	cgit_url "bar/tree" &&
-	cgit_url "bar/log" &&
-	cgit_url "bar/diff" &&
-	cgit_url "bar/patch" &&
-	ls cache >output &&
-	test_line_count = 1 output
-'
-
-test_expect_success 'verify cache-size=1021' '
-
-	rm -f cache/* &&
-	sed -e "s/cache-size=1$/cache-size=1021/" cgitrc >cgitrc.tmp &&
-	mv -f cgitrc.tmp cgitrc &&
-	cgit_url "" &&
-	cgit_url "foo" &&
-	cgit_url "foo/refs" &&
-	cgit_url "foo/tree" &&
-	cgit_url "foo/log" &&
-	cgit_url "foo/diff" &&
-	cgit_url "foo/patch" &&
-	cgit_url "bar" &&
-	cgit_url "bar/refs" &&
-	cgit_url "bar/tree" &&
-	cgit_url "bar/log" &&
-	cgit_url "bar/diff" &&
-	cgit_url "bar/patch" &&
-	ls cache >output &&
-	test_line_count = 13 output &&
-	cgit_url "foo/ls_cache" >output.full &&
-	strip_headers <output.full >output &&
-	test_line_count = 13 output &&
-	# Check that ls_cache output is cached correctly
-	cgit_url "foo/ls_cache" >output.second &&
-	test_cmp output.full output.second
-'
-
-test_done
diff --git a/tests/t0101-index.sh b/tests/t0101-index.sh
deleted file mode 100755
index 82ef9b04e5..0000000000
--- a/tests/t0101-index.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/sh
-
-test_description='Check content on index page'
-. ./setup.sh
-
-test_expect_success 'generate index page' 'cgit_url "" >tmp'
-test_expect_success 'find foo repo' 'grep "foo" tmp'
-test_expect_success 'find foo description' 'grep "\[no description\]" tmp'
-test_expect_success 'find bar repo' 'grep "bar" tmp'
-test_expect_success 'find bar description' 'grep "the bar repo" tmp'
-test_expect_success 'find foo+bar repo' 'grep ">foo+bar<" tmp'
-test_expect_success 'verify foo+bar link' 'grep "/foo+bar/" tmp'
-test_expect_success 'verify "with%20space" link' 'grep "/with%20space/" tmp'
-test_expect_success 'no tree-link' '! grep "foo/tree" tmp'
-test_expect_success 'no log-link' '! grep "foo/log" tmp'
-
-test_done
diff --git a/tests/t0102-summary.sh b/tests/t0102-summary.sh
deleted file mode 100755
index b8864cb187..0000000000
--- a/tests/t0102-summary.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/sh
-
-test_description='Check content on summary page'
-. ./setup.sh
-
-test_expect_success 'generate foo summary' 'cgit_url "foo" >tmp'
-test_expect_success 'find commit 1' 'grep "commit 1" tmp'
-test_expect_success 'find commit 5' 'grep "commit 5" tmp'
-test_expect_success 'find branch master' 'grep "master" tmp'
-test_expect_success 'no tags' '! grep "tags" tmp'
-test_expect_success 'clone-url expanded correctly' '
-	grep "git://example.org/foo.git" tmp
-'
-
-test_expect_success 'generate bar summary' 'cgit_url "bar" >tmp'
-test_expect_success 'no commit 45' '! grep "commit 45" tmp'
-test_expect_success 'find commit 46' 'grep "commit 46" tmp'
-test_expect_success 'find commit 50' 'grep "commit 50" tmp'
-test_expect_success 'find branch master' 'grep "master" tmp'
-test_expect_success 'no tags' '! grep "tags" tmp'
-test_expect_success 'clone-url expanded correctly' '
-	grep "git://example.org/bar.git" tmp
-'
-
-test_done
diff --git a/tests/t0103-log.sh b/tests/t0103-log.sh
deleted file mode 100755
index bdf1435a16..0000000000
--- a/tests/t0103-log.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/sh
-
-test_description='Check content on log page'
-. ./setup.sh
-
-test_expect_success 'generate foo/log' 'cgit_url "foo/log" >tmp'
-test_expect_success 'find commit 1' 'grep "commit 1" tmp'
-test_expect_success 'find commit 5' 'grep "commit 5" tmp'
-
-test_expect_success 'generate bar/log' 'cgit_url "bar/log" >tmp'
-test_expect_success 'find commit 1' 'grep "commit 1" tmp'
-test_expect_success 'find commit 50' 'grep "commit 50" tmp'
-
-test_expect_success 'generate "with%20space/log?qt=grep&q=commit+1"' '
-	cgit_url "with+space/log&qt=grep&q=commit+1" >tmp
-'
-test_expect_success 'find commit 1' 'grep "commit 1" tmp'
-test_expect_success 'find link with %20 in path' 'grep "/with%20space/log/?qt=grep" tmp'
-test_expect_success 'find link with + in arg' 'grep "/log/?qt=grep&amp;q=commit+1" tmp'
-test_expect_success 'no links with space in path' '! grep "href=./with space/" tmp'
-test_expect_success 'no links with space in arg' '! grep "q=commit 1" tmp'
-test_expect_success 'commit 2 is not visible' '! grep "commit 2" tmp'
-
-test_done
diff --git a/tests/t0104-tree.sh b/tests/t0104-tree.sh
deleted file mode 100755
index 2e140f5939..0000000000
--- a/tests/t0104-tree.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-test_description='Check content on tree page'
-. ./setup.sh
-
-test_expect_success 'generate bar/tree' 'cgit_url "bar/tree" >tmp'
-test_expect_success 'find file-1' 'grep "file-1" tmp'
-test_expect_success 'find file-50' 'grep "file-50" tmp'
-
-test_expect_success 'generate bar/tree/file-50' 'cgit_url "bar/tree/file-50" >tmp'
-
-test_expect_success 'find line 1' '
-	grep "<a id=.n1. href=.#n1.>1</a>" tmp
-'
-
-test_expect_success 'no line 2' '
-	! grep "<a id=.n2. href=.#n2.>2</a>" tmp
-'
-
-test_expect_success 'generate foo+bar/tree' 'cgit_url "foo%2bbar/tree" >tmp'
-
-test_expect_success 'verify a+b link' '
-	grep "/foo+bar/tree/a+b" tmp
-'
-
-test_expect_success 'generate foo+bar/tree?h=1+2' 'cgit_url "foo%2bbar/tree&h=1%2b2" >tmp'
-
-test_expect_success 'verify a+b?h=1+2 link' '
-	grep "/foo+bar/tree/a+b?h=1%2b2" tmp
-'
-
-test_done
diff --git a/tests/t0105-commit.sh b/tests/t0105-commit.sh
deleted file mode 100755
index 9cdf55c025..0000000000
--- a/tests/t0105-commit.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/sh
-
-test_description='Check content on commit page'
-. ./setup.sh
-
-test_expect_success 'generate foo/commit' 'cgit_url "foo/commit" >tmp'
-test_expect_success 'find tree link' 'grep "<a href=./foo/tree/.>" tmp'
-test_expect_success 'find parent link' 'grep -E "<a href=./foo/commit/\?id=.+>" tmp'
-
-test_expect_success 'find commit subject' '
-	grep "<div class=.commit-subject.>commit 5<" tmp
-'
-
-test_expect_success 'find commit msg' 'grep "<div class=.commit-msg.></div>" tmp'
-test_expect_success 'find diffstat' 'grep "<table summary=.diffstat. class=.diffstat.>" tmp'
-
-test_expect_success 'find diff summary' '
-	grep "1 files changed, 1 insertions, 0 deletions" tmp
-'
-
-test_expect_success 'get root commit' '
-	root=$(cd repos/foo && git rev-list --reverse HEAD | head -1) &&
-	cgit_url "foo/commit&id=$root" >tmp &&
-	grep "</html>" tmp
-'
-
-test_expect_success 'root commit contains diffstat' '
-	grep "<a href=./foo/diff/file-1.id=[0-9a-f]\{40\}.>file-1</a>" tmp
-'
-
-test_expect_success 'root commit contains diff' '
-	grep ">diff --git a/file-1 b/file-1<" tmp &&
-	grep "<div class=.add.>+1</div>" tmp
-'
-
-test_done
diff --git a/tests/t0106-diff.sh b/tests/t0106-diff.sh
deleted file mode 100755
index 82b645ec72..0000000000
--- a/tests/t0106-diff.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/sh
-
-test_description='Check content on diff page'
-. ./setup.sh
-
-test_expect_success 'generate foo/diff' 'cgit_url "foo/diff" >tmp'
-test_expect_success 'find diff header' 'grep "a/file-5 b/file-5" tmp'
-test_expect_success 'find blob link' 'grep "<a href=./foo/tree/file-5?id=" tmp'
-test_expect_success 'find added file' 'grep "new file mode 100644" tmp'
-
-test_expect_success 'find hunk header' '
-	grep "<div class=.hunk.>@@ -0,0 +1 @@</div>" tmp
-'
-
-test_expect_success 'find added line' '
-	grep "<div class=.add.>+5</div>" tmp
-'
-
-test_done
diff --git a/tests/t0107-snapshot.sh b/tests/t0107-snapshot.sh
deleted file mode 100755
index 6cf7aaa6fc..0000000000
--- a/tests/t0107-snapshot.sh
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/bin/sh
-
-test_description='Verify snapshot'
-. ./setup.sh
-
-test_expect_success 'get foo/snapshot/master.tar.gz' '
-	cgit_url "foo/snapshot/master.tar.gz" >tmp
-'
-
-test_expect_success 'check html headers' '
-	head -n 1 tmp |
-	grep "Content-Type: application/x-gzip" &&
-
-	head -n 2 tmp |
-	grep "Content-Disposition: inline; filename=.master.tar.gz."
-'
-
-test_expect_success 'strip off the header lines' '
-	strip_headers <tmp >master.tar.gz
-'
-
-test_expect_success 'verify gzip format' '
-	gunzip --test master.tar.gz
-'
-
-test_expect_success 'untar' '
-	rm -rf master &&
-	tar -xzf master.tar.gz
-'
-
-test_expect_success 'count files' '
-	ls master/ >output &&
-	test_line_count = 5 output
-'
-
-test_expect_success 'verify untarred file-5' '
-	grep "^5$" master/file-5 &&
-	test_line_count = 1 master/file-5
-'
-
-test_expect_success 'get foo/snapshot/master.zip' '
-	cgit_url "foo/snapshot/master.zip" >tmp
-'
-
-test_expect_success 'check HTML headers (zip)' '
-	head -n 1 tmp |
-	grep "Content-Type: application/x-zip" &&
-
-	head -n 2 tmp |
-	grep "Content-Disposition: inline; filename=.master.zip."
-'
-
-test_expect_success 'strip off the header lines (zip)' '
-	strip_headers <tmp >master.zip
-'
-
-if test -n "$(which unzip 2>/dev/null)"; then
-	test_set_prereq UNZIP
-else
-	say 'Skipping ZIP validation tests: unzip not found'
-fi
-
-test_expect_success UNZIP 'verify zip format' '
-	unzip -t master.zip
-'
-
-test_expect_success UNZIP 'unzip' '
-	rm -rf master &&
-	unzip master.zip
-'
-
-test_expect_success UNZIP 'count files (zip)' '
-	ls master/ >output &&
-	test_line_count = 5 output
-'
-
-test_expect_success UNZIP 'verify unzipped file-5' '
-	grep "^5$" master/file-5 &&
-	test_line_count = 1 master/file-5
-'
-
-test_done
diff --git a/tests/t0108-patch.sh b/tests/t0108-patch.sh
deleted file mode 100755
index 013d68024d..0000000000
--- a/tests/t0108-patch.sh
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/bin/sh
-
-test_description='Check content on patch page'
-. ./setup.sh
-
-test_expect_success 'generate foo/patch' '
-	cgit_query "url=foo/patch" >tmp
-'
-
-test_expect_success 'find `From:` line' '
-	grep "^From: " tmp
-'
-
-test_expect_success 'find `Date:` line' '
-	grep "^Date: " tmp
-'
-
-test_expect_success 'find `Subject:` line' '
-	grep "^Subject: commit 5" tmp
-'
-
-test_expect_success 'find `cgit` signature' '
-	tail -2 tmp | head -1 | grep "^cgit"
-'
-
-test_expect_success 'compare with output of git-format-patch(1)' '
-	CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../VERSION) &&
-	git --git-dir="$PWD/repos/foo/.git" format-patch --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD^ >tmp2 &&
-	strip_headers <tmp >tmp_ &&
-	test_cmp tmp_ tmp2
-'
-
-test_expect_success 'find initial commit' '
-	root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD)
-'
-
-test_expect_success 'generate patch for initial commit' '
-	cgit_query "url=foo/patch&id=$root" >tmp
-'
-
-test_expect_success 'find `cgit` signature' '
-	tail -2 tmp | head -1 | grep "^cgit"
-'
-
-test_expect_success 'generate patches for multiple commits' '
-	id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) &&
-	id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) &&
-	cgit_query "url=foo/patch&id=$id&id2=$id2" >tmp
-'
-
-test_expect_success 'find `cgit` signature' '
-	tail -2 tmp | head -1 | grep "^cgit"
-'
-
-test_expect_success 'compare with output of git-format-patch(1)' '
-	CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../VERSION) &&
-	git --git-dir="$PWD/repos/foo/.git" format-patch -N --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD~3..HEAD >tmp2 &&
-	strip_headers <tmp >tmp_ &&
-	test_cmp tmp_ tmp2
-'
-
-test_done
diff --git a/tests/t0109-gitconfig.sh b/tests/t0109-gitconfig.sh
deleted file mode 100755
index 3ba668490d..0000000000
--- a/tests/t0109-gitconfig.sh
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/sh
-
-test_description='Ensure that git does not access $HOME'
-. ./setup.sh
-
-test -n "$(which strace 2>/dev/null)" || {
-	skip_all='Skipping access validation tests: strace not found'
-	test_done
-	exit
-}
-
-test_no_home_access () {
-	non_existent_path="/path/to/some/place/that/does/not/possibly/exist"
-	while test -d "$non_existent_path"; do
-		non_existent_path="$non_existent_path/$(date +%N)"
-	done &&
-	strace \
-		-E HOME="$non_existent_path" \
-		-E CGIT_CONFIG="$PWD/cgitrc" \
-		-E QUERY_STRING="url=$1" \
-		-e access -f -o strace.out cgit &&
-	test_must_fail grep "$non_existent_path" strace.out
-}
-
-test_no_home_access_success() {
-	test_expect_success "do not access \$HOME: $1" "
-		test_no_home_access '$1'
-	"
-}
-
-test_no_home_access_success
-test_no_home_access_success foo
-test_no_home_access_success foo/refs
-test_no_home_access_success foo/log
-test_no_home_access_success foo/tree
-test_no_home_access_success foo/tree/file-1
-test_no_home_access_success foo/commit
-test_no_home_access_success foo/diff
-test_no_home_access_success foo/patch
-test_no_home_access_success foo/snapshot/master.tar.gz
-
-test_done
diff --git a/tests/t0110-rawdiff.sh b/tests/t0110-rawdiff.sh
deleted file mode 100755
index 66fa7d5d37..0000000000
--- a/tests/t0110-rawdiff.sh
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/sh
-
-test_description='Check content on rawdiff page'
-. ./setup.sh
-
-test_expect_success 'generate foo/rawdiff' '
-	cgit_query "url=foo/rawdiff" >tmp
-'
-
-test_expect_success 'compare with output of git-diff(1)' '
-	git --git-dir="$PWD/repos/foo/.git" diff HEAD^.. >tmp2 &&
-	sed "1,4d" tmp >tmp_ &&
-	cmp tmp_ tmp2
-'
-
-test_expect_success 'find initial commit' '
-	root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD)
-'
-
-test_expect_success 'generate diff for initial commit' '
-	cgit_query "url=foo/rawdiff&id=$root" >tmp
-'
-
-test_expect_success 'compare with output of git-diff-tree(1)' '
-	git --git-dir="$PWD/repos/foo/.git" diff-tree -p --no-commit-id --root "$root" >tmp2 &&
-	sed "1,4d" tmp >tmp_ &&
-	cmp tmp_ tmp2
-'
-
-test_expect_success 'generate diff for multiple commits' '
-	id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) &&
-	id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) &&
-	cgit_query "url=foo/rawdiff&id=$id&id2=$id2" >tmp
-'
-
-test_expect_success 'compare with output of git-diff(1)' '
-	git --git-dir="$PWD/repos/foo/.git" diff HEAD~3..HEAD >tmp2 &&
-	sed "1,4d" tmp >tmp_ &&
-	cmp tmp_ tmp2
-'
-
-test_done
diff --git a/tests/t0111-filter.sh b/tests/t0111-filter.sh
deleted file mode 100755
index 2fdc3669f4..0000000000
--- a/tests/t0111-filter.sh
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/bin/sh
-
-test_description='Check filtered content'
-. ./setup.sh
-
-prefixes="exec"
-if [ $CGIT_HAS_LUA -eq 1 ]; then
-	prefixes="$prefixes lua"
-fi
-
-for prefix in $prefixes
-do
-	test_expect_success "generate filter-$prefix/tree/a%2bb" "
-		cgit_url 'filter-$prefix/tree/a%2bb' >tmp
-	"
-
-	test_expect_success "check whether the $prefix source filter works" '
-		grep "<code>a+b HELLO$" tmp
-	'
-
-	test_expect_success "generate filter-$prefix/about/" "
-		cgit_url 'filter-$prefix/about/' >tmp
-	"
-
-	test_expect_success "check whether the $prefix about filter works" '
-		grep "<div id='"'"'summary'"'"'>a+b HELLO$" tmp
-	'
-
-	test_expect_success "generate filter-$prefix/commit/" "
-		cgit_url 'filter-$prefix/commit/' >tmp
-	"
-
-	test_expect_success "check whether the $prefix commit filter works" '
-		grep "<div class='"'"'commit-subject'"'"'>ADD A+B" tmp
-	'
-
-	test_expect_success "check whether the $prefix email filter works for authors" '
-		grep "<author@example.com> commit A U THOR &LT;AUTHOR@EXAMPLE.COM&GT;" tmp
-	'
-
-	test_expect_success "check whether the $prefix email filter works for committers" '
-		grep "<committer@example.com> commit C O MITTER &LT;COMMITTER@EXAMPLE.COM&GT;" tmp
-	'
-done
-
-test_done
diff --git a/tests/valgrind/bin/cgit b/tests/valgrind/bin/cgit
deleted file mode 100755
index dcdfbe5320..0000000000
--- a/tests/valgrind/bin/cgit
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-
-# Note that we currently use Git's suppression file and there are variables
-# $GIT_VALGRIND and $CGIT_VALGRIND which point to different places.
-exec valgrind -q --error-exitcode=126 \
-	--suppressions="$GIT_VALGRIND/default.supp" \
-	--gen-suppressions=all \
-	--leak-check=no \
-	--track-origins=yes \
-	--log-fd=4 \
-	--input-fd=4 \
-	"$CGIT_VALGRIND/../../cgit" "$@"
diff --git a/ui-atom.c b/ui-atom.c
deleted file mode 100644
index 1056f36397..0000000000
--- a/ui-atom.c
+++ /dev/null
@@ -1,149 +0,0 @@
-/* ui-atom.c: functions for atom feeds
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-atom.h"
-#include "html.h"
-#include "ui-shared.h"
-
-static void add_entry(struct commit *commit, const char *host)
-{
-	char delim = '&';
-	char *hex;
-	char *mail, *t, *t2;
-	struct commitinfo *info;
-
-	info = cgit_parse_commit(commit);
-	hex = oid_to_hex(&commit->object.oid);
-	html("<entry>\n");
-	html("<title>");
-	html_txt(info->subject);
-	html("</title>\n");
-	html("<updated>");
-	html_txt(show_date(info->committer_date, 0,
-                    date_mode_from_type(DATE_ISO8601_STRICT)));
-	html("</updated>\n");
-	html("<author>\n");
-	if (info->author) {
-		html("<name>");
-		html_txt(info->author);
-		html("</name>\n");
-	}
-	if (info->author_email && !ctx.cfg.noplainemail) {
-		mail = xstrdup(info->author_email);
-		t = strchr(mail, '<');
-		if (t)
-			t++;
-		else
-			t = mail;
-		t2 = strchr(t, '>');
-		if (t2)
-			*t2 = '\0';
-		html("<email>");
-		html_txt(t);
-		html("</email>\n");
-		free(mail);
-	}
-	html("</author>\n");
-	html("<published>");
-	html_txt(show_date(info->author_date, 0,
-                    date_mode_from_type(DATE_ISO8601_STRICT)));
-	html("</published>\n");
-	if (host) {
-		char *pageurl;
-		html("<link rel='alternate' type='text/html' href='");
-		html(cgit_httpscheme());
-		html_attr(host);
-		pageurl = cgit_pageurl(ctx.repo->url, "commit", NULL);
-		html_attr(pageurl);
-		if (ctx.cfg.virtual_root)
-			delim = '?';
-		html_attrf("%cid=%s", delim, hex);
-		html("'/>\n");
-		free(pageurl);
-	}
-	htmlf("<id>%s</id>\n", hex);
-	html("<content type='text'>\n");
-	html_txt(info->msg);
-	html("</content>\n");
-	html("<content type='xhtml'>\n");
-	html("<div xmlns='http://www.w3.org/1999/xhtml'>\n");
-	html("<pre>\n");
-	html_txt(info->msg);
-	html("</pre>\n");
-	html("</div>\n");
-	html("</content>\n");
-	html("</entry>\n");
-	cgit_free_commitinfo(info);
-}
-
-
-void cgit_print_atom(char *tip, const char *path, int max_count)
-{
-	char *host;
-	const char *argv[] = {NULL, tip, NULL, NULL, NULL};
-	struct commit *commit;
-	struct rev_info rev;
-	int argc = 2;
-
-	if (ctx.qry.show_all)
-		argv[1] = "--all";
-	else if (!tip)
-		argv[1] = ctx.qry.head;
-
-	if (path) {
-		argv[argc++] = "--";
-		argv[argc++] = path;
-	}
-
-	init_revisions(&rev, NULL);
-	rev.abbrev = DEFAULT_ABBREV;
-	rev.commit_format = CMIT_FMT_DEFAULT;
-	rev.verbose_header = 1;
-	rev.show_root_diff = 0;
-	rev.max_count = max_count;
-	setup_revisions(argc, argv, &rev, NULL);
-	prepare_revision_walk(&rev);
-
-	host = cgit_hosturl();
-	ctx.page.mimetype = "text/xml";
-	ctx.page.charset = "utf-8";
-	cgit_print_http_headers();
-	html("<feed xmlns='http://www.w3.org/2005/Atom'>\n");
-	html("<title>");
-	html_txt(ctx.repo->name);
-	if (path) {
-		html("/");
-		html_txt(path);
-	}
-	if (tip && !ctx.qry.show_all) {
-		html(", branch ");
-		html_txt(tip);
-	}
-	html("</title>\n");
-	html("<subtitle>");
-	html_txt(ctx.repo->desc);
-	html("</subtitle>\n");
-	if (host) {
-		char *repourl = cgit_repourl(ctx.repo->url);
-		html("<link rel='alternate' type='text/html' href='");
-		html(cgit_httpscheme());
-		html_attr(host);
-		html_attr(repourl);
-		html("'/>\n");
-		free(repourl);
-	}
-	while ((commit = get_revision(&rev)) != NULL) {
-		add_entry(commit, host);
-		free_commit_buffer(the_repository->parsed_objects, commit);
-		free_commit_list(commit->parents);
-		commit->parents = NULL;
-	}
-	html("</feed>\n");
-	free(host);
-}
diff --git a/ui-atom.h b/ui-atom.h
deleted file mode 100644
index dda953bbf4..0000000000
--- a/ui-atom.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef UI_ATOM_H
-#define UI_ATOM_H
-
-extern void cgit_print_atom(char *tip, const char *path, int max_count);
-
-#endif
diff --git a/ui-blame.c b/ui-blame.c
deleted file mode 100644
index 644c30ad28..0000000000
--- a/ui-blame.c
+++ /dev/null
@@ -1,302 +0,0 @@
-/* ui-blame.c: functions for blame output
- *
- * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-blame.h"
-#include "html.h"
-#include "ui-shared.h"
-#include "argv-array.h"
-#include "blame.h"
-
-
-static char *emit_suspect_detail(struct blame_origin *suspect)
-{
-	struct commitinfo *info;
-	struct strbuf detail = STRBUF_INIT;
-
-	info = cgit_parse_commit(suspect->commit);
-
-	strbuf_addf(&detail, "author  %s", info->author);
-	if (!ctx.cfg.noplainemail)
-		strbuf_addf(&detail, " %s", info->author_email);
-	strbuf_addf(&detail, "  %s\n",
-		    show_date(info->author_date, info->author_tz,
-				    cgit_date_mode(DATE_ISO8601)));
-
-	strbuf_addf(&detail, "committer  %s", info->committer);
-	if (!ctx.cfg.noplainemail)
-		strbuf_addf(&detail, " %s", info->committer_email);
-	strbuf_addf(&detail, "  %s\n\n",
-		    show_date(info->committer_date, info->committer_tz,
-				    cgit_date_mode(DATE_ISO8601)));
-
-	strbuf_addstr(&detail, info->subject);
-
-	cgit_free_commitinfo(info);
-	return strbuf_detach(&detail, NULL);
-}
-
-static void emit_blame_entry_hash(struct blame_entry *ent)
-{
-	struct blame_origin *suspect = ent->suspect;
-	struct object_id *oid = &suspect->commit->object.oid;
-	unsigned long line = 0;
-
-	char *detail = emit_suspect_detail(suspect);
-	html("<span class='sha1'>");
-	cgit_commit_link(find_unique_abbrev(oid, DEFAULT_ABBREV), detail,
-			 NULL, ctx.qry.head, oid_to_hex(oid), suspect->path);
-	html("</span>");
-	free(detail);
-
-	while (line++ < ent->num_lines)
-		html("\n");
-}
-
-static void emit_blame_entry_linenumber(struct blame_entry *ent)
-{
-	const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n";
-
-	unsigned long lineno = ent->lno;
-	while (lineno < ent->lno + ent->num_lines)
-		htmlf(numberfmt, ++lineno);
-}
-
-static void emit_blame_entry_line_background(struct blame_scoreboard *sb,
-					     struct blame_entry *ent)
-{
-	unsigned long line;
-	size_t len, maxlen = 2;
-	const char* pos, *endpos;
-
-	for (line = ent->lno; line < ent->lno + ent->num_lines; line++) {
-		html("\n");
-		pos = blame_nth_line(sb, line);
-		endpos = blame_nth_line(sb, line + 1);
-		len = 0;
-		while (pos < endpos) {
-			len++;
-			if (*pos++ == '\t')
-				len = (len + 7) & ~7;
-		}
-		if (len > maxlen)
-			maxlen = len;
-	}
-
-	for (len = 0; len < maxlen - 1; len++)
-		html(" ");
-}
-
-struct walk_tree_context {
-	char *curr_rev;
-	int match_baselen;
-	int state;
-};
-
-static void print_object(const struct object_id *oid, const char *path,
-			 const char *basename, const char *rev)
-{
-	enum object_type type;
-	char *buf;
-	unsigned long size;
-	struct argv_array rev_argv = ARGV_ARRAY_INIT;
-	struct rev_info revs;
-	struct blame_scoreboard sb;
-	struct blame_origin *o;
-	struct blame_entry *ent = NULL;
-
-	type = oid_object_info(the_repository, oid, &size);
-	if (type == OBJ_BAD) {
-		cgit_print_error_page(404, "Not found", "Bad object name: %s",
-				      oid_to_hex(oid));
-		return;
-	}
-
-	buf = read_object_file(oid, &type, &size);
-	if (!buf) {
-		cgit_print_error_page(500, "Internal server error",
-			"Error reading object %s", oid_to_hex(oid));
-		return;
-	}
-
-	argv_array_push(&rev_argv, "blame");
-	argv_array_push(&rev_argv, rev);
-	init_revisions(&revs, NULL);
-	revs.diffopt.flags.allow_textconv = 1;
-	setup_revisions(rev_argv.argc, rev_argv.argv, &revs, NULL);
-	init_scoreboard(&sb);
-	sb.revs = &revs;
-	sb.repo = the_repository;
-	setup_scoreboard(&sb, path, &o);
-	o->suspects = blame_entry_prepend(NULL, 0, sb.num_lines, o);
-	prio_queue_put(&sb.commits, o->commit);
-	blame_origin_decref(o);
-	sb.ent = NULL;
-	sb.path = path;
-	assign_blame(&sb, 0);
-	blame_sort_final(&sb);
-	blame_coalesce(&sb);
-
-	cgit_set_title_from_path(path);
-
-	cgit_print_layout_start();
-	htmlf("blob: %s (", oid_to_hex(oid));
-	cgit_plain_link("plain", NULL, NULL, ctx.qry.head, rev, path);
-	html(") (");
-	cgit_tree_link("tree", NULL, NULL, ctx.qry.head, rev, path);
-	html(")\n");
-
-	if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
-		htmlf("<div class='error'>blob size (%ldKB)"
-		      " exceeds display size limit (%dKB).</div>",
-		      size / 1024, ctx.cfg.max_blob_size);
-		goto cleanup;
-	}
-
-	html("<table class='blame blob'>\n<tr>\n");
-
-	/* Commit hashes */
-	html("<td class='hashes'>");
-	for (ent = sb.ent; ent; ent = ent->next) {
-		html("<div class='alt'><pre>");
-		emit_blame_entry_hash(ent);
-		html("</pre></div>");
-	}
-	html("</td>\n");
-
-	/* Line numbers */
-	if (ctx.cfg.enable_tree_linenumbers) {
-		html("<td class='linenumbers'>");
-		for (ent = sb.ent; ent; ent = ent->next) {
-			html("<div class='alt'><pre>");
-			emit_blame_entry_linenumber(ent);
-			html("</pre></div>");
-		}
-		html("</td>\n");
-	}
-
-	html("<td class='lines'><div>");
-
-	/* Colored bars behind lines */
-	html("<div>");
-	for (ent = sb.ent; ent; ) {
-		struct blame_entry *e = ent->next;
-		html("<div class='alt'><pre>");
-		emit_blame_entry_line_background(&sb, ent);
-		html("</pre></div>");
-		free(ent);
-		ent = e;
-	}
-	html("</div>");
-
-	free((void *)sb.final_buf);
-
-	/* Lines */
-	html("<pre><code>");
-	if (ctx.repo->source_filter) {
-		char *filter_arg = xstrdup(basename);
-		cgit_open_filter(ctx.repo->source_filter, filter_arg);
-		html_raw(buf, size);
-		cgit_close_filter(ctx.repo->source_filter);
-		free(filter_arg);
-	} else {
-		html_txt(buf);
-	}
-	html("</code></pre>");
-
-	html("</div></td>\n");
-
-	html("</tr>\n</table>\n");
-
-	cgit_print_layout_end();
-
-cleanup:
-	free(buf);
-}
-
-static int walk_tree(const struct object_id *oid, struct strbuf *base,
-		     const char *pathname, unsigned mode, int stage,
-		     void *cbdata)
-{
-	struct walk_tree_context *walk_tree_ctx = cbdata;
-
-	if (base->len == walk_tree_ctx->match_baselen) {
-		if (S_ISREG(mode)) {
-			struct strbuf buffer = STRBUF_INIT;
-			strbuf_addbuf(&buffer, base);
-			strbuf_addstr(&buffer, pathname);
-			print_object(oid, buffer.buf, pathname,
-				     walk_tree_ctx->curr_rev);
-			strbuf_release(&buffer);
-			walk_tree_ctx->state = 1;
-		} else if (S_ISDIR(mode)) {
-			walk_tree_ctx->state = 2;
-		}
-	} else if (base->len < INT_MAX
-			&& (int)base->len > walk_tree_ctx->match_baselen) {
-		walk_tree_ctx->state = 2;
-	} else if (S_ISDIR(mode)) {
-		return READ_TREE_RECURSIVE;
-	}
-	return 0;
-}
-
-static int basedir_len(const char *path)
-{
-	char *p = strrchr(path, '/');
-	if (p)
-		return p - path + 1;
-	return 0;
-}
-
-void cgit_print_blame(void)
-{
-	const char *rev = ctx.qry.sha1;
-	struct object_id oid;
-	struct commit *commit;
-	struct pathspec_item path_items = {
-		.match = ctx.qry.path,
-		.len = ctx.qry.path ? strlen(ctx.qry.path) : 0
-	};
-	struct pathspec paths = {
-		.nr = 1,
-		.items = &path_items
-	};
-	struct walk_tree_context walk_tree_ctx = {
-		.state = 0
-	};
-
-	if (!rev)
-		rev = ctx.qry.head;
-
-	if (get_oid(rev, &oid)) {
-		cgit_print_error_page(404, "Not found",
-			"Invalid revision name: %s", rev);
-		return;
-	}
-	commit = lookup_commit_reference(the_repository, &oid);
-	if (!commit || parse_commit(commit)) {
-		cgit_print_error_page(404, "Not found",
-			"Invalid commit reference: %s", rev);
-		return;
-	}
-
-	walk_tree_ctx.curr_rev = xstrdup(rev);
-	walk_tree_ctx.match_baselen = (path_items.match) ?
-				       basedir_len(path_items.match) : -1;
-
-	read_tree_recursive(the_repository, commit->maybe_tree, "", 0, 0,
-		&paths, walk_tree, &walk_tree_ctx);
-	if (!walk_tree_ctx.state)
-		cgit_print_error_page(404, "Not found", "Not found");
-	else if (walk_tree_ctx.state == 2)
-		cgit_print_error_page(404, "No blame for folders",
-			"Blame is not available for folders.");
-
-	free(walk_tree_ctx.curr_rev);
-}
diff --git a/ui-blame.h b/ui-blame.h
deleted file mode 100644
index 5b97e03591..0000000000
--- a/ui-blame.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef UI_BLAME_H
-#define UI_BLAME_H
-
-extern void cgit_print_blame(void);
-
-#endif /* UI_BLAME_H */
diff --git a/ui-blob.c b/ui-blob.c
deleted file mode 100644
index 30e2d4bf5f..0000000000
--- a/ui-blob.c
+++ /dev/null
@@ -1,181 +0,0 @@
-/* ui-blob.c: show blob content
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-blob.h"
-#include "html.h"
-#include "ui-shared.h"
-
-struct walk_tree_context {
-	const char *match_path;
-	struct object_id *matched_oid;
-	unsigned int found_path:1;
-	unsigned int file_only:1;
-};
-
-static int walk_tree(const struct object_id *oid, struct strbuf *base,
-		const char *pathname, unsigned mode, int stage, void *cbdata)
-{
-	struct walk_tree_context *walk_tree_ctx = cbdata;
-
-	if (walk_tree_ctx->file_only && !S_ISREG(mode))
-		return READ_TREE_RECURSIVE;
-	if (strncmp(base->buf, walk_tree_ctx->match_path, base->len)
-		|| strcmp(walk_tree_ctx->match_path + base->len, pathname))
-		return READ_TREE_RECURSIVE;
-	oidcpy(walk_tree_ctx->matched_oid, oid);
-	walk_tree_ctx->found_path = 1;
-	return 0;
-}
-
-int cgit_ref_path_exists(const char *path, const char *ref, int file_only)
-{
-	struct object_id oid;
-	unsigned long size;
-	struct pathspec_item path_items = {
-		.match = xstrdup(path),
-		.len = strlen(path)
-	};
-	struct pathspec paths = {
-		.nr = 1,
-		.items = &path_items
-	};
-	struct walk_tree_context walk_tree_ctx = {
-		.match_path = path,
-		.matched_oid = &oid,
-		.found_path = 0,
-		.file_only = file_only
-	};
-
-	if (get_oid(ref, &oid))
-		goto done;
-	if (oid_object_info(the_repository, &oid, &size) != OBJ_COMMIT)
-		goto done;
-	read_tree_recursive(the_repository, lookup_commit_reference(the_repository, &oid)->maybe_tree,
-		"", 0, 0, &paths, walk_tree, &walk_tree_ctx);
-
-done:
-	free(path_items.match);
-	return walk_tree_ctx.found_path;
-}
-
-int cgit_print_file(char *path, const char *head, int file_only)
-{
-	struct object_id oid;
-	enum object_type type;
-	char *buf;
-	unsigned long size;
-	struct commit *commit;
-	struct pathspec_item path_items = {
-		.match = path,
-		.len = strlen(path)
-	};
-	struct pathspec paths = {
-		.nr = 1,
-		.items = &path_items
-	};
-	struct walk_tree_context walk_tree_ctx = {
-		.match_path = path,
-		.matched_oid = &oid,
-		.found_path = 0,
-		.file_only = file_only
-	};
-
-	if (get_oid(head, &oid))
-		return -1;
-	type = oid_object_info(the_repository, &oid, &size);
-	if (type == OBJ_COMMIT) {
-		commit = lookup_commit_reference(the_repository, &oid);
-		read_tree_recursive(the_repository, commit->maybe_tree,
-			"", 0, 0, &paths, walk_tree, &walk_tree_ctx);
-		if (!walk_tree_ctx.found_path)
-			return -1;
-		type = oid_object_info(the_repository, &oid, &size);
-	}
-	if (type == OBJ_BAD)
-		return -1;
-	buf = read_object_file(&oid, &type, &size);
-	if (!buf)
-		return -1;
-	buf[size] = '\0';
-	html_raw(buf, size);
-	free(buf);
-	return 0;
-}
-
-void cgit_print_blob(const char *hex, char *path, const char *head, int file_only)
-{
-	struct object_id oid;
-	enum object_type type;
-	char *buf;
-	unsigned long size;
-	struct commit *commit;
-	struct pathspec_item path_items = {
-		.match = path,
-		.len = path ? strlen(path) : 0
-	};
-	struct pathspec paths = {
-		.nr = 1,
-		.items = &path_items
-	};
-	struct walk_tree_context walk_tree_ctx = {
-		.match_path = path,
-		.matched_oid = &oid,
-		.found_path = 0,
-		.file_only = file_only
-	};
-
-	if (hex) {
-		if (get_oid_hex(hex, &oid)) {
-			cgit_print_error_page(400, "Bad request",
-					"Bad hex value: %s", hex);
-			return;
-		}
-	} else {
-		if (get_oid(head, &oid)) {
-			cgit_print_error_page(404, "Not found",
-					"Bad ref: %s", head);
-			return;
-		}
-	}
-
-	type = oid_object_info(the_repository, &oid, &size);
-
-	if ((!hex) && type == OBJ_COMMIT && path) {
-		commit = lookup_commit_reference(the_repository, &oid);
-		read_tree_recursive(the_repository, commit->maybe_tree,
-			"", 0, 0, &paths, walk_tree, &walk_tree_ctx);
-		type = oid_object_info(the_repository, &oid, &size);
-	}
-
-	if (type == OBJ_BAD) {
-		cgit_print_error_page(404, "Not found",
-				"Bad object name: %s", hex);
-		return;
-	}
-
-	buf = read_object_file(&oid, &type, &size);
-	if (!buf) {
-		cgit_print_error_page(500, "Internal server error",
-				"Error reading object %s", hex);
-		return;
-	}
-
-	buf[size] = '\0';
-	if (buffer_is_binary(buf, size))
-		ctx.page.mimetype = "application/octet-stream";
-	else
-		ctx.page.mimetype = "text/plain";
-	ctx.page.filename = path;
-
-	html("X-Content-Type-Options: nosniff\n");
-	html("Content-Security-Policy: default-src 'none'\n");
-	cgit_print_http_headers();
-	html_raw(buf, size);
-	free(buf);
-}
diff --git a/ui-blob.h b/ui-blob.h
deleted file mode 100644
index 16847b20b1..0000000000
--- a/ui-blob.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#ifndef UI_BLOB_H
-#define UI_BLOB_H
-
-extern int cgit_ref_path_exists(const char *path, const char *ref, int file_only);
-extern int cgit_print_file(char *path, const char *head, int file_only);
-extern void cgit_print_blob(const char *hex, char *path, const char *head, int file_only);
-
-#endif /* UI_BLOB_H */
diff --git a/ui-clone.c b/ui-clone.c
deleted file mode 100644
index 5dccb63976..0000000000
--- a/ui-clone.c
+++ /dev/null
@@ -1,126 +0,0 @@
-/* ui-clone.c: functions for http cloning, based on
- * git's http-backend.c by Shawn O. Pearce
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-clone.h"
-#include "html.h"
-#include "ui-shared.h"
-#include "packfile.h"
-#include "object-store.h"
-
-static int print_ref_info(const char *refname, const struct object_id *oid,
-                          int flags, void *cb_data)
-{
-	struct object *obj;
-
-	if (!(obj = parse_object(the_repository, oid)))
-		return 0;
-
-	htmlf("%s\t%s\n", oid_to_hex(oid), refname);
-	if (obj->type == OBJ_TAG) {
-		if (!(obj = deref_tag(the_repository, obj, refname, 0)))
-			return 0;
-		htmlf("%s\t%s^{}\n", oid_to_hex(&obj->oid), refname);
-	}
-	return 0;
-}
-
-static void print_pack_info(void)
-{
-	struct packed_git *pack;
-	char *offset;
-
-	ctx.page.mimetype = "text/plain";
-	ctx.page.filename = "objects/info/packs";
-	cgit_print_http_headers();
-	reprepare_packed_git(the_repository);
-	for (pack = get_packed_git(the_repository); pack; pack = pack->next) {
-		if (pack->pack_local) {
-			offset = strrchr(pack->pack_name, '/');
-			if (offset && offset[1] != '\0')
-				++offset;
-			else
-				offset = pack->pack_name;
-			htmlf("P %s\n", offset);
-		}
-	}
-}
-
-static void send_file(const char *path)
-{
-	struct stat st;
-
-	if (stat(path, &st)) {
-		switch (errno) {
-		case ENOENT:
-			cgit_print_error_page(404, "Not found", "Not found");
-			break;
-		case EACCES:
-			cgit_print_error_page(403, "Forbidden", "Forbidden");
-			break;
-		default:
-			cgit_print_error_page(400, "Bad request", "Bad request");
-		}
-		return;
-	}
-	ctx.page.mimetype = "application/octet-stream";
-	ctx.page.filename = path;
-	skip_prefix(path, ctx.repo->path, &ctx.page.filename);
-	skip_prefix(ctx.page.filename, "/", &ctx.page.filename);
-	cgit_print_http_headers();
-	html_include(path);
-}
-
-void cgit_clone_info(void)
-{
-	if (!ctx.qry.path || strcmp(ctx.qry.path, "refs")) {
-		cgit_print_error_page(400, "Bad request", "Bad request");
-		return;
-	}
-
-	ctx.page.mimetype = "text/plain";
-	ctx.page.filename = "info/refs";
-	cgit_print_http_headers();
-	for_each_ref(print_ref_info, NULL);
-}
-
-void cgit_clone_objects(void)
-{
-	char *p;
-
-	if (!ctx.qry.path)
-		goto err;
-
-	if (!strcmp(ctx.qry.path, "info/packs")) {
-		print_pack_info();
-		return;
-	}
-
-	/* Avoid directory traversal by forbidding "..", but also work around
-	 * other funny business by just specifying a fairly strict format. For
-	 * example, now we don't have to stress out about the Cygwin port.
-	 */
-	for (p = ctx.qry.path; *p; ++p) {
-		if (*p == '.' && *(p + 1) == '.')
-			goto err;
-		if (!isalnum(*p) && *p != '/' && *p != '.' && *p != '-')
-			goto err;
-	}
-
-	send_file(git_path("objects/%s", ctx.qry.path));
-	return;
-
-err:
-	cgit_print_error_page(400, "Bad request", "Bad request");
-}
-
-void cgit_clone_head(void)
-{
-	send_file(git_path("%s", "HEAD"));
-}
diff --git a/ui-clone.h b/ui-clone.h
deleted file mode 100644
index 3e460a3dbc..0000000000
--- a/ui-clone.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#ifndef UI_CLONE_H
-#define UI_CLONE_H
-
-void cgit_clone_info(void);
-void cgit_clone_objects(void);
-void cgit_clone_head(void);
-
-#endif /* UI_CLONE_H */
diff --git a/ui-commit.c b/ui-commit.c
deleted file mode 100644
index 9a47b54c56..0000000000
--- a/ui-commit.c
+++ /dev/null
@@ -1,147 +0,0 @@
-/* ui-commit.c: generate commit view
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-commit.h"
-#include "html.h"
-#include "ui-shared.h"
-#include "ui-diff.h"
-#include "ui-log.h"
-
-void cgit_print_commit(char *hex, const char *prefix)
-{
-	struct commit *commit, *parent;
-	struct commitinfo *info, *parent_info;
-	struct commit_list *p;
-	struct strbuf notes = STRBUF_INIT;
-	struct object_id oid;
-	char *tmp, *tmp2;
-	int parents = 0;
-
-	if (!hex)
-		hex = ctx.qry.head;
-
-	if (get_oid(hex, &oid)) {
-		cgit_print_error_page(400, "Bad request",
-				"Bad object id: %s", hex);
-		return;
-	}
-	commit = lookup_commit_reference(the_repository, &oid);
-	if (!commit) {
-		cgit_print_error_page(404, "Not found",
-				"Bad commit reference: %s", hex);
-		return;
-	}
-	info = cgit_parse_commit(commit);
-
-	format_display_notes(&oid, &notes, PAGE_ENCODING, 0);
-
-	load_ref_decorations(NULL, DECORATE_FULL_REFS);
-
-	cgit_print_layout_start();
-	cgit_print_diff_ctrls();
-	html("<table summary='commit info' class='commit-info'>\n");
-	html("<tr><th>author</th><td>");
-	cgit_open_filter(ctx.repo->email_filter, info->author_email, "commit");
-	html_txt(info->author);
-	if (!ctx.cfg.noplainemail) {
-		html(" ");
-		html_txt(info->author_email);
-	}
-	cgit_close_filter(ctx.repo->email_filter);
-	html("</td><td class='right'>");
-	html_txt(show_date(info->author_date, info->author_tz,
-				cgit_date_mode(DATE_ISO8601)));
-	html("</td></tr>\n");
-	html("<tr><th>committer</th><td>");
-	cgit_open_filter(ctx.repo->email_filter, info->committer_email, "commit");
-	html_txt(info->committer);
-	if (!ctx.cfg.noplainemail) {
-		html(" ");
-		html_txt(info->committer_email);
-	}
-	cgit_close_filter(ctx.repo->email_filter);
-	html("</td><td class='right'>");
-	html_txt(show_date(info->committer_date, info->committer_tz,
-				cgit_date_mode(DATE_ISO8601)));
-	html("</td></tr>\n");
-	html("<tr><th>commit</th><td colspan='2' class='sha1'>");
-	tmp = oid_to_hex(&commit->object.oid);
-	cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix);
-	html(" (");
-	cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix);
-	html(")</td></tr>\n");
-	html("<tr><th>tree</th><td colspan='2' class='sha1'>");
-	tmp = xstrdup(hex);
-	cgit_tree_link(oid_to_hex(&commit->maybe_tree->object.oid), NULL, NULL,
-		       ctx.qry.head, tmp, NULL);
-	if (prefix) {
-		html(" /");
-		cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix);
-	}
-	free(tmp);
-	html("</td></tr>\n");
-	for (p = commit->parents; p; p = p->next) {
-		parent = lookup_commit_reference(the_repository, &p->item->object.oid);
-		if (!parent) {
-			html("<tr><td colspan='3'>");
-			cgit_print_error("Error reading parent commit");
-			html("</td></tr>");
-			continue;
-		}
-		html("<tr><th>parent</th>"
-		     "<td colspan='2' class='sha1'>");
-		tmp = tmp2 = oid_to_hex(&p->item->object.oid);
-		if (ctx.repo->enable_subject_links) {
-			parent_info = cgit_parse_commit(parent);
-			tmp2 = parent_info->subject;
-		}
-		cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix);
-		html(" (");
-		cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
-			       oid_to_hex(&p->item->object.oid), prefix);
-		html(")</td></tr>");
-		parents++;
-	}
-	if (ctx.repo->snapshots) {
-		html("<tr><th>download</th><td colspan='2' class='sha1'>");
-		cgit_print_snapshot_links(ctx.repo, hex, "<br/>");
-		html("</td></tr>");
-	}
-	html("</table>\n");
-	html("<div class='commit-subject'>");
-	cgit_open_filter(ctx.repo->commit_filter);
-	html_txt(info->subject);
-	cgit_close_filter(ctx.repo->commit_filter);
-	show_commit_decorations(commit);
-	html("</div>");
-	html("<div class='commit-msg'>");
-	cgit_open_filter(ctx.repo->commit_filter);
-	html_txt(info->msg);
-	cgit_close_filter(ctx.repo->commit_filter);
-	html("</div>");
-	if (notes.len != 0) {
-		html("<div class='notes-header'>Notes</div>");
-		html("<div class='notes'>");
-		cgit_open_filter(ctx.repo->commit_filter);
-		html_txt(notes.buf);
-		cgit_close_filter(ctx.repo->commit_filter);
-		html("</div>");
-		html("<div class='notes-footer'></div>");
-	}
-	if (parents < 3) {
-		if (parents)
-			tmp = oid_to_hex(&commit->parents->item->object.oid);
-		else
-			tmp = NULL;
-		cgit_print_diff(ctx.qry.sha1, tmp, prefix, 0, 0);
-	}
-	strbuf_release(&notes);
-	cgit_free_commitinfo(info);
-	cgit_print_layout_end();
-}
diff --git a/ui-commit.h b/ui-commit.h
deleted file mode 100644
index 8198b4bacc..0000000000
--- a/ui-commit.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef UI_COMMIT_H
-#define UI_COMMIT_H
-
-extern void cgit_print_commit(char *hex, const char *prefix);
-
-#endif /* UI_COMMIT_H */
diff --git a/ui-diff.c b/ui-diff.c
deleted file mode 100644
index c60aefd1d6..0000000000
--- a/ui-diff.c
+++ /dev/null
@@ -1,501 +0,0 @@
-/* ui-diff.c: show diff between two blobs
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-diff.h"
-#include "html.h"
-#include "ui-shared.h"
-#include "ui-ssdiff.h"
-
-struct object_id old_rev_oid[1];
-struct object_id new_rev_oid[1];
-
-static int files, slots;
-static int total_adds, total_rems, max_changes;
-static int lines_added, lines_removed;
-
-static struct fileinfo {
-	char status;
-	struct object_id old_oid[1];
-	struct object_id new_oid[1];
-	unsigned short old_mode;
-	unsigned short new_mode;
-	char *old_path;
-	char *new_path;
-	unsigned int added;
-	unsigned int removed;
-	unsigned long old_size;
-	unsigned long new_size;
-	unsigned int binary:1;
-} *items;
-
-static int use_ssdiff = 0;
-static struct diff_filepair *current_filepair;
-static const char *current_prefix;
-
-struct diff_filespec *cgit_get_current_old_file(void)
-{
-	return current_filepair->one;
-}
-
-struct diff_filespec *cgit_get_current_new_file(void)
-{
-	return current_filepair->two;
-}
-
-static void print_fileinfo(struct fileinfo *info)
-{
-	char *class;
-
-	switch (info->status) {
-	case DIFF_STATUS_ADDED:
-		class = "add";
-		break;
-	case DIFF_STATUS_COPIED:
-		class = "cpy";
-		break;
-	case DIFF_STATUS_DELETED:
-		class = "del";
-		break;
-	case DIFF_STATUS_MODIFIED:
-		class = "upd";
-		break;
-	case DIFF_STATUS_RENAMED:
-		class = "mov";
-		break;
-	case DIFF_STATUS_TYPE_CHANGED:
-		class = "typ";
-		break;
-	case DIFF_STATUS_UNKNOWN:
-		class = "unk";
-		break;
-	case DIFF_STATUS_UNMERGED:
-		class = "stg";
-		break;
-	default:
-		die("bug: unhandled diff status %c", info->status);
-	}
-
-	html("<tr>");
-	html("<td class='mode'>");
-	if (is_null_oid(info->new_oid)) {
-		cgit_print_filemode(info->old_mode);
-	} else {
-		cgit_print_filemode(info->new_mode);
-	}
-
-	if (info->old_mode != info->new_mode &&
-	    !is_null_oid(info->old_oid) &&
-	    !is_null_oid(info->new_oid)) {
-		html("<span class='modechange'>[");
-		cgit_print_filemode(info->old_mode);
-		html("]</span>");
-	}
-	htmlf("</td><td class='%s'>", class);
-	cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
-		       ctx.qry.sha2, info->new_path);
-	if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) {
-		htmlf(" (%s from ",
-		      info->status == DIFF_STATUS_COPIED ? "copied" : "renamed");
-		html_txt(info->old_path);
-		html(")");
-	}
-	html("</td><td class='right'>");
-	if (info->binary) {
-		htmlf("bin</td><td class='graph'>%ld -> %ld bytes",
-		      info->old_size, info->new_size);
-		return;
-	}
-	htmlf("%d", info->added + info->removed);
-	html("</td><td class='graph'>");
-	htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
-	htmlf("<td class='add' style='width: %.1f%%;'/>",
-	      info->added * 100.0 / max_changes);
-	htmlf("<td class='rem' style='width: %.1f%%;'/>",
-	      info->removed * 100.0 / max_changes);
-	htmlf("<td class='none' style='width: %.1f%%;'/>",
-	      (max_changes - info->removed - info->added) * 100.0 / max_changes);
-	html("</tr></table></td></tr>\n");
-}
-
-static void count_diff_lines(char *line, int len)
-{
-	if (line && (len > 0)) {
-		if (line[0] == '+')
-			lines_added++;
-		else if (line[0] == '-')
-			lines_removed++;
-	}
-}
-
-static int show_filepair(struct diff_filepair *pair)
-{
-	/* Always show if we have no limiting prefix. */
-	if (!current_prefix)
-		return 1;
-
-	/* Show if either path in the pair begins with the prefix. */
-	if (starts_with(pair->one->path, current_prefix) ||
-	    starts_with(pair->two->path, current_prefix))
-		return 1;
-
-	/* Otherwise we don't want to show this filepair. */
-	return 0;
-}
-
-static void inspect_filepair(struct diff_filepair *pair)
-{
-	int binary = 0;
-	unsigned long old_size = 0;
-	unsigned long new_size = 0;
-
-	if (!show_filepair(pair))
-		return;
-
-	files++;
-	lines_added = 0;
-	lines_removed = 0;
-	cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size, &new_size,
-			&binary, 0, ctx.qry.ignorews, count_diff_lines);
-	if (files >= slots) {
-		if (slots == 0)
-			slots = 4;
-		else
-			slots = slots * 2;
-		items = xrealloc(items, slots * sizeof(struct fileinfo));
-	}
-	items[files-1].status = pair->status;
-	oidcpy(items[files-1].old_oid, &pair->one->oid);
-	oidcpy(items[files-1].new_oid, &pair->two->oid);
-	items[files-1].old_mode = pair->one->mode;
-	items[files-1].new_mode = pair->two->mode;
-	items[files-1].old_path = xstrdup(pair->one->path);
-	items[files-1].new_path = xstrdup(pair->two->path);
-	items[files-1].added = lines_added;
-	items[files-1].removed = lines_removed;
-	items[files-1].old_size = old_size;
-	items[files-1].new_size = new_size;
-	items[files-1].binary = binary;
-	if (lines_added + lines_removed > max_changes)
-		max_changes = lines_added + lines_removed;
-	total_adds += lines_added;
-	total_rems += lines_removed;
-}
-
-static void cgit_print_diffstat(const struct object_id *old_oid,
-				const struct object_id *new_oid,
-				const char *prefix)
-{
-	int i;
-
-	html("<div class='diffstat-header'>");
-	cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
-		       ctx.qry.sha2, NULL);
-	if (prefix) {
-		html(" (limited to '");
-		html_txt(prefix);
-		html("')");
-	}
-	html("</div>");
-	html("<table summary='diffstat' class='diffstat'>");
-	max_changes = 0;
-	cgit_diff_tree(old_oid, new_oid, inspect_filepair, prefix,
-		       ctx.qry.ignorews);
-	for (i = 0; i<files; i++)
-		print_fileinfo(&items[i]);
-	html("</table>");
-	html("<div class='diffstat-summary'>");
-	htmlf("%d files changed, %d insertions, %d deletions",
-	      files, total_adds, total_rems);
-	html("</div>");
-}
-
-
-/*
- * print a single line returned from xdiff
- */
-static void print_line(char *line, int len)
-{
-	char *class = "ctx";
-	char c = line[len-1];
-
-	if (line[0] == '+')
-		class = "add";
-	else if (line[0] == '-')
-		class = "del";
-	else if (line[0] == '@')
-		class = "hunk";
-
-	htmlf("<div class='%s'>", class);
-	line[len-1] = '\0';
-	html_txt(line);
-	html("</div>");
-	line[len-1] = c;
-}
-
-static void header(const struct object_id *oid1, char *path1, int mode1,
-		   const struct object_id *oid2, char *path2, int mode2)
-{
-	char *abbrev1, *abbrev2;
-	int subproject;
-
-	subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
-	html("<div class='head'>");
-	html("diff --git a/");
-	html_txt(path1);
-	html(" b/");
-	html_txt(path2);
-
-	if (mode1 == 0)
-		htmlf("<br/>new file mode %.6o", mode2);
-
-	if (mode2 == 0)
-		htmlf("<br/>deleted file mode %.6o", mode1);
-
-	if (!subproject) {
-		abbrev1 = xstrdup(find_unique_abbrev(oid1, DEFAULT_ABBREV));
-		abbrev2 = xstrdup(find_unique_abbrev(oid2, DEFAULT_ABBREV));
-		htmlf("<br/>index %s..%s", abbrev1, abbrev2);
-		free(abbrev1);
-		free(abbrev2);
-		if (mode1 != 0 && mode2 != 0) {
-			htmlf(" %.6o", mode1);
-			if (mode2 != mode1)
-				htmlf("..%.6o", mode2);
-		}
-		if (is_null_oid(oid1)) {
-			path1 = "dev/null";
-			html("<br/>--- /");
-		} else
-			html("<br/>--- a/");
-		if (mode1 != 0)
-			cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
-				       oid_to_hex(old_rev_oid), path1);
-		else
-			html_txt(path1);
-		if (is_null_oid(oid2)) {
-			path2 = "dev/null";
-			html("<br/>+++ /");
-		} else
-			html("<br/>+++ b/");
-		if (mode2 != 0)
-			cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
-				       oid_to_hex(new_rev_oid), path2);
-		else
-			html_txt(path2);
-	}
-	html("</div>");
-}
-
-static void filepair_cb(struct diff_filepair *pair)
-{
-	unsigned long old_size = 0;
-	unsigned long new_size = 0;
-	int binary = 0;
-	linediff_fn print_line_fn = print_line;
-
-	if (!show_filepair(pair))
-		return;
-
-	current_filepair = pair;
-	if (use_ssdiff) {
-		cgit_ssdiff_header_begin();
-		print_line_fn = cgit_ssdiff_line_cb;
-	}
-	header(&pair->one->oid, pair->one->path, pair->one->mode,
-	       &pair->two->oid, pair->two->path, pair->two->mode);
-	if (use_ssdiff)
-		cgit_ssdiff_header_end();
-	if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
-		if (S_ISGITLINK(pair->one->mode))
-			print_line_fn(fmt("-Subproject %s", oid_to_hex(&pair->one->oid)), 52);
-		if (S_ISGITLINK(pair->two->mode))
-			print_line_fn(fmt("+Subproject %s", oid_to_hex(&pair->two->oid)), 52);
-		if (use_ssdiff)
-			cgit_ssdiff_footer();
-		return;
-	}
-	if (cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size,
-			    &new_size, &binary, ctx.qry.context,
-			    ctx.qry.ignorews, print_line_fn))
-		cgit_print_error("Error running diff");
-	if (binary) {
-		if (use_ssdiff)
-			html("<tr><td colspan='4'>Binary files differ</td></tr>");
-		else
-			html("Binary files differ");
-	}
-	if (use_ssdiff)
-		cgit_ssdiff_footer();
-}
-
-void cgit_print_diff_ctrls(void)
-{
-	int i, curr;
-
-	html("<div class='cgit-panel'>");
-	html("<b>diff options</b>");
-	html("<form method='get'>");
-	cgit_add_hidden_formfields(1, 0, ctx.qry.page);
-	html("<table>");
-	html("<tr><td colspan='2'/></tr>");
-	html("<tr>");
-	html("<td class='label'>context:</td>");
-	html("<td class='ctrl'>");
-	html("<select name='context' onchange='this.form.submit();'>");
-	curr = ctx.qry.context;
-	if (!curr)
-		curr = 3;
-	for (i = 1; i <= 10; i++)
-		html_intoption(i, fmt("%d", i), curr);
-	for (i = 15; i <= 40; i += 5)
-		html_intoption(i, fmt("%d", i), curr);
-	html("</select>");
-	html("</td>");
-	html("</tr><tr>");
-	html("<td class='label'>space:</td>");
-	html("<td class='ctrl'>");
-	html("<select name='ignorews' onchange='this.form.submit();'>");
-	html_intoption(0, "include", ctx.qry.ignorews);
-	html_intoption(1, "ignore", ctx.qry.ignorews);
-	html("</select>");
-	html("</td>");
-	html("</tr><tr>");
-	html("<td class='label'>mode:</td>");
-	html("<td class='ctrl'>");
-	html("<select name='dt' onchange='this.form.submit();'>");
-	curr = ctx.qry.has_difftype ? ctx.qry.difftype : ctx.cfg.difftype;
-	html_intoption(0, "unified", curr);
-	html_intoption(1, "ssdiff", curr);
-	html_intoption(2, "stat only", curr);
-	html("</select></td></tr>");
-	html("<tr><td/><td class='ctrl'>");
-	html("<noscript><input type='submit' value='reload'/></noscript>");
-	html("</td></tr></table>");
-	html("</form>");
-	html("</div>");
-}
-
-void cgit_print_diff(const char *new_rev, const char *old_rev,
-		     const char *prefix, int show_ctrls, int raw)
-{
-	struct commit *commit, *commit2;
-	const struct object_id *old_tree_oid, *new_tree_oid;
-	diff_type difftype;
-
-	/*
-	 * If "follow" is set then the diff machinery needs to examine the
-	 * entire commit to detect renames so we must limit the paths in our
-	 * own callbacks and not pass the prefix to the diff machinery.
-	 */
-	if (ctx.qry.follow && ctx.cfg.enable_follow_links) {
-		current_prefix = prefix;
-		prefix = "";
-	} else {
-		current_prefix = NULL;
-	}
-
-	if (!new_rev)
-		new_rev = ctx.qry.head;
-	if (get_oid(new_rev, new_rev_oid)) {
-		cgit_print_error_page(404, "Not found",
-			"Bad object name: %s", new_rev);
-		return;
-	}
-	commit = lookup_commit_reference(the_repository, new_rev_oid);
-	if (!commit || parse_commit(commit)) {
-		cgit_print_error_page(404, "Not found",
-			"Bad commit: %s", oid_to_hex(new_rev_oid));
-		return;
-	}
-	new_tree_oid = &commit->maybe_tree->object.oid;
-
-	if (old_rev) {
-		if (get_oid(old_rev, old_rev_oid)) {
-			cgit_print_error_page(404, "Not found",
-				"Bad object name: %s", old_rev);
-			return;
-		}
-	} else if (commit->parents && commit->parents->item) {
-		oidcpy(old_rev_oid, &commit->parents->item->object.oid);
-	} else {
-		oidclr(old_rev_oid);
-	}
-
-	if (!is_null_oid(old_rev_oid)) {
-		commit2 = lookup_commit_reference(the_repository, old_rev_oid);
-		if (!commit2 || parse_commit(commit2)) {
-			cgit_print_error_page(404, "Not found",
-				"Bad commit: %s", oid_to_hex(old_rev_oid));
-			return;
-		}
-		old_tree_oid = &commit2->maybe_tree->object.oid;
-	} else {
-		old_tree_oid = NULL;
-	}
-
-	if (raw) {
-		struct diff_options diffopt;
-
-		diff_setup(&diffopt);
-		diffopt.output_format = DIFF_FORMAT_PATCH;
-		diffopt.flags.recursive = 1;
-		diff_setup_done(&diffopt);
-
-		ctx.page.mimetype = "text/plain";
-		cgit_print_http_headers();
-		if (old_tree_oid) {
-			diff_tree_oid(old_tree_oid, new_tree_oid, "",
-				       &diffopt);
-		} else {
-			diff_root_tree_oid(new_tree_oid, "", &diffopt);
-		}
-		diffcore_std(&diffopt);
-		diff_flush(&diffopt);
-
-		return;
-	}
-
-	difftype = ctx.qry.has_difftype ? ctx.qry.difftype : ctx.cfg.difftype;
-	use_ssdiff = difftype == DIFF_SSDIFF;
-
-	if (show_ctrls) {
-		cgit_print_layout_start();
-		cgit_print_diff_ctrls();
-	}
-
-	/*
-	 * Clicking on a link to a file in the diff stat should show a diff
-	 * of the file, showing the diff stat limited to a single file is
-	 * pretty useless.  All links from this point on will be to
-	 * individual files, so we simply reset the difftype in the query
-	 * here to avoid propagating DIFF_STATONLY to the individual files.
-	 */
-	if (difftype == DIFF_STATONLY)
-		ctx.qry.difftype = ctx.cfg.difftype;
-
-	cgit_print_diffstat(old_rev_oid, new_rev_oid, prefix);
-
-	if (difftype == DIFF_STATONLY)
-		return;
-
-	if (use_ssdiff) {
-		html("<table summary='ssdiff' class='ssdiff'>");
-	} else {
-		html("<table summary='diff' class='diff'>");
-		html("<tr><td>");
-	}
-	cgit_diff_tree(old_rev_oid, new_rev_oid, filepair_cb, prefix,
-		       ctx.qry.ignorews);
-	if (!use_ssdiff)
-		html("</td></tr>");
-	html("</table>");
-
-	if (show_ctrls)
-		cgit_print_layout_end();
-}
diff --git a/ui-diff.h b/ui-diff.h
deleted file mode 100644
index 39264a164f..0000000000
--- a/ui-diff.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef UI_DIFF_H
-#define UI_DIFF_H
-
-extern void cgit_print_diff_ctrls(void);
-
-extern void cgit_print_diff(const char *new_hex, const char *old_hex,
-			    const char *prefix, int show_ctrls, int raw);
-
-extern struct diff_filespec *cgit_get_current_old_file(void);
-extern struct diff_filespec *cgit_get_current_new_file(void);
-
-extern struct object_id old_rev_oid[1];
-extern struct object_id new_rev_oid[1];
-
-#endif /* UI_DIFF_H */
diff --git a/ui-log.c b/ui-log.c
deleted file mode 100644
index dc5cb1eb6a..0000000000
--- a/ui-log.c
+++ /dev/null
@@ -1,550 +0,0 @@
-/* ui-log.c: functions for log output
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-log.h"
-#include "html.h"
-#include "ui-shared.h"
-#include "argv-array.h"
-
-static int files, add_lines, rem_lines, lines_counted;
-
-/*
- * The list of available column colors in the commit graph.
- */
-static const char *column_colors_html[] = {
-	"<span class='column1'>",
-	"<span class='column2'>",
-	"<span class='column3'>",
-	"<span class='column4'>",
-	"<span class='column5'>",
-	"<span class='column6'>",
-	"</span>",
-};
-
-#define COLUMN_COLORS_HTML_MAX (ARRAY_SIZE(column_colors_html) - 1)
-
-static void count_lines(char *line, int size)
-{
-	if (size <= 0)
-		return;
-
-	if (line[0] == '+')
-		add_lines++;
-
-	else if (line[0] == '-')
-		rem_lines++;
-}
-
-static void inspect_files(struct diff_filepair *pair)
-{
-	unsigned long old_size = 0;
-	unsigned long new_size = 0;
-	int binary = 0;
-
-	files++;
-	if (ctx.repo->enable_log_linecount)
-		cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size,
-				&new_size, &binary, 0, ctx.qry.ignorews,
-				count_lines);
-}
-
-void show_commit_decorations(struct commit *commit)
-{
-	const struct name_decoration *deco;
-	static char buf[1024];
-
-	buf[sizeof(buf) - 1] = 0;
-	deco = get_name_decoration(&commit->object);
-	if (!deco)
-		return;
-	html("<span class='decoration'>");
-	while (deco) {
-		struct object_id peeled;
-		int is_annotated = 0;
-		strlcpy(buf, prettify_refname(deco->name), sizeof(buf));
-		switch(deco->type) {
-		case DECORATION_NONE:
-			/* If the git-core doesn't recognize it,
-			 * don't display anything. */
-			break;
-		case DECORATION_REF_LOCAL:
-			cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
-				ctx.qry.vpath, 0, NULL, NULL,
-				ctx.qry.showmsg, 0);
-			break;
-		case DECORATION_REF_TAG:
-			if (!peel_ref(deco->name, &peeled))
-				is_annotated = !oidcmp(&commit->object.oid, &peeled);
-			cgit_tag_link(buf, NULL, is_annotated ? "tag-annotated-deco" : "tag-deco", buf);
-			break;
-		case DECORATION_REF_REMOTE:
-			if (!ctx.repo->enable_remote_branches)
-				break;
-			cgit_log_link(buf, NULL, "remote-deco", NULL,
-				oid_to_hex(&commit->object.oid),
-				ctx.qry.vpath, 0, NULL, NULL,
-				ctx.qry.showmsg, 0);
-			break;
-		default:
-			cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
-					oid_to_hex(&commit->object.oid),
-					ctx.qry.vpath);
-			break;
-		}
-		deco = deco->next;
-	}
-	html("</span>");
-}
-
-static void handle_rename(struct diff_filepair *pair)
-{
-	/*
-	 * After we have seen a rename, we generate links to the previous
-	 * name of the file so that commit & diff views get fed the path
-	 * that is correct for the commit they are showing, avoiding the
-	 * need to walk the entire history leading back to every commit we
-	 * show in order detect renames.
-	 */
-	if (0 != strcmp(ctx.qry.vpath, pair->two->path)) {
-		free(ctx.qry.vpath);
-		ctx.qry.vpath = xstrdup(pair->two->path);
-	}
-	inspect_files(pair);
-}
-
-static int show_commit(struct commit *commit, struct rev_info *revs)
-{
-	struct commit_list *parents = commit->parents;
-	struct commit *parent;
-	int found = 0, saved_fmt;
-	struct diff_flags saved_flags = revs->diffopt.flags;
-
-	/* Always show if we're not in "follow" mode with a single file. */
-	if (!ctx.qry.follow)
-		return 1;
-
-	/*
-	 * In "follow" mode, we don't show merges.  This is consistent with
-	 * "git log --follow -- <file>".
-	 */
-	if (parents && parents->next)
-		return 0;
-
-	/*
-	 * If this is the root commit, do what rev_info tells us.
-	 */
-	if (!parents)
-		return revs->show_root_diff;
-
-	/* When we get here we have precisely one parent. */
-	parent = parents->item;
-	/* If we can't parse the commit, let print_commit() report an error. */
-	if (parse_commit(parent))
-		return 1;
-
-	files = 0;
-	add_lines = 0;
-	rem_lines = 0;
-
-	revs->diffopt.flags.recursive = 1;
-	diff_tree_oid(&parent->maybe_tree->object.oid,
-		      &commit->maybe_tree->object.oid,
-		      "", &revs->diffopt);
-	diffcore_std(&revs->diffopt);
-
-	found = !diff_queue_is_empty();
-	saved_fmt = revs->diffopt.output_format;
-	revs->diffopt.output_format = DIFF_FORMAT_CALLBACK;
-	revs->diffopt.format_callback = cgit_diff_tree_cb;
-	revs->diffopt.format_callback_data = handle_rename;
-	diff_flush(&revs->diffopt);
-	revs->diffopt.output_format = saved_fmt;
-	revs->diffopt.flags = saved_flags;
-
-	lines_counted = 1;
-	return found;
-}
-
-static void print_commit(struct commit *commit, struct rev_info *revs)
-{
-	struct commitinfo *info;
-	int columns = revs->graph ? 4 : 3;
-	struct strbuf graphbuf = STRBUF_INIT;
-	struct strbuf msgbuf = STRBUF_INIT;
-
-	if (ctx.repo->enable_log_filecount)
-		columns++;
-	if (ctx.repo->enable_log_linecount)
-		columns++;
-
-	if (revs->graph) {
-		/* Advance graph until current commit */
-		while (!graph_next_line(revs->graph, &graphbuf)) {
-			/* Print graph segment in otherwise empty table row */
-			html("<tr class='nohover'><td class='commitgraph'>");
-			html(graphbuf.buf);
-			htmlf("</td><td colspan='%d' /></tr>\n", columns);
-			strbuf_setlen(&graphbuf, 0);
-		}
-		/* Current commit's graph segment is now ready in graphbuf */
-	}
-
-	info = cgit_parse_commit(commit);
-	htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : "");
-
-	if (revs->graph) {
-		/* Print graph segment for current commit */
-		html("<td class='commitgraph'>");
-		html(graphbuf.buf);
-		html("</td>");
-		strbuf_setlen(&graphbuf, 0);
-	}
-	else {
-		html("<td>");
-		cgit_print_age(info->committer_date, info->committer_tz, TM_WEEK * 2);
-		html("</td>");
-	}
-
-	htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : "");
-	if (ctx.qry.showmsg) {
-		/* line-wrap long commit subjects instead of truncating them */
-		size_t subject_len = strlen(info->subject);
-
-		if (subject_len > ctx.cfg.max_msg_len &&
-		    ctx.cfg.max_msg_len >= 15) {
-			/* symbol for signaling line-wrap (in PAGE_ENCODING) */
-			const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 };
-			int i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
-
-			/* Rewind i to preceding space character */
-			while (i > 0 && !isspace(info->subject[i]))
-				--i;
-			if (!i) /* Oops, zero spaces. Reset i */
-				i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
-
-			/* add remainder starting at i to msgbuf */
-			strbuf_add(&msgbuf, info->subject + i, subject_len - i);
-			strbuf_trim(&msgbuf);
-			strbuf_add(&msgbuf, "\n\n", 2);
-
-			/* Place wrap_symbol at position i in info->subject */
-			strlcpy(info->subject + i, wrap_symbol, subject_len - i + 1);
-		}
-	}
-	cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
-			 oid_to_hex(&commit->object.oid), ctx.qry.vpath);
-	show_commit_decorations(commit);
-	html("</td><td>");
-	cgit_open_filter(ctx.repo->email_filter, info->author_email, "log");
-	html_txt(info->author);
-	cgit_close_filter(ctx.repo->email_filter);
-
-	if (revs->graph) {
-		html("</td><td>");
-		cgit_print_age(info->committer_date, info->committer_tz, TM_WEEK * 2);
-	}
-
-	if (!lines_counted && (ctx.repo->enable_log_filecount ||
-			       ctx.repo->enable_log_linecount)) {
-		files = 0;
-		add_lines = 0;
-		rem_lines = 0;
-		cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
-	}
-
-	if (ctx.repo->enable_log_filecount)
-		htmlf("</td><td>%d", files);
-	if (ctx.repo->enable_log_linecount)
-		htmlf("</td><td><span class='deletions'>-%d</span>/"
-			"<span class='insertions'>+%d</span>", rem_lines, add_lines);
-
-	html("</td></tr>\n");
-
-	if ((revs->graph && !graph_is_commit_finished(revs->graph))
-			|| ctx.qry.showmsg) { /* Print a second table row */
-		html("<tr class='nohover-highlight'>");
-
-		if (ctx.qry.showmsg) {
-			/* Concatenate commit message + notes in msgbuf */
-			if (info->msg && *(info->msg)) {
-				strbuf_addstr(&msgbuf, info->msg);
-				strbuf_addch(&msgbuf, '\n');
-			}
-			format_display_notes(&commit->object.oid,
-					     &msgbuf, PAGE_ENCODING, 0);
-			strbuf_addch(&msgbuf, '\n');
-			strbuf_ltrim(&msgbuf);
-		}
-
-		if (revs->graph) {
-			int lines = 0;
-
-			/* Calculate graph padding */
-			if (ctx.qry.showmsg) {
-				/* Count #lines in commit message + notes */
-				const char *p = msgbuf.buf;
-				lines = 1;
-				while ((p = strchr(p, '\n'))) {
-					p++;
-					lines++;
-				}
-			}
-
-			/* Print graph padding */
-			html("<td class='commitgraph'>");
-			while (lines > 0 || !graph_is_commit_finished(revs->graph)) {
-				if (graphbuf.len)
-					html("\n");
-				strbuf_setlen(&graphbuf, 0);
-				graph_next_line(revs->graph, &graphbuf);
-				html(graphbuf.buf);
-				lines--;
-			}
-			html("</td>\n");
-		}
-		else
-			html("<td/>"); /* Empty 'Age' column */
-
-		/* Print msgbuf into remainder of table row */
-		htmlf("<td colspan='%d'%s>\n", columns - (revs->graph ? 1 : 0),
-			ctx.qry.showmsg ? " class='logmsg'" : "");
-		html_txt(msgbuf.buf);
-		html("</td></tr>\n");
-	}
-
-	strbuf_release(&msgbuf);
-	strbuf_release(&graphbuf);
-	cgit_free_commitinfo(info);
-}
-
-static const char *disambiguate_ref(const char *ref, int *must_free_result)
-{
-	struct object_id oid;
-	struct strbuf longref = STRBUF_INIT;
-
-	strbuf_addf(&longref, "refs/heads/%s", ref);
-	if (get_oid(longref.buf, &oid) == 0) {
-		*must_free_result = 1;
-		return strbuf_detach(&longref, NULL);
-	}
-
-	*must_free_result = 0;
-	strbuf_release(&longref);
-	return ref;
-}
-
-static char *next_token(char **src)
-{
-	char *result;
-
-	if (!src || !*src)
-		return NULL;
-	while (isspace(**src))
-		(*src)++;
-	if (!**src)
-		return NULL;
-	result = *src;
-	while (**src) {
-		if (isspace(**src)) {
-			**src = '\0';
-			(*src)++;
-			break;
-		}
-		(*src)++;
-	}
-	return result;
-}
-
-void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
-		    const char *path, int pager, int commit_graph, int commit_sort)
-{
-	struct rev_info rev;
-	struct commit *commit;
-	struct argv_array rev_argv = ARGV_ARRAY_INIT;
-	int i, columns = commit_graph ? 4 : 3;
-	int must_free_tip = 0;
-
-	/* rev_argv.argv[0] will be ignored by setup_revisions */
-	argv_array_push(&rev_argv, "log_rev_setup");
-
-	if (!tip)
-		tip = ctx.qry.head;
-	tip = disambiguate_ref(tip, &must_free_tip);
-	argv_array_push(&rev_argv, tip);
-
-	if (grep && pattern && *pattern) {
-		pattern = xstrdup(pattern);
-		if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
-		    !strcmp(grep, "committer")) {
-			argv_array_pushf(&rev_argv, "--%s=%s", grep, pattern);
-		} else if (!strcmp(grep, "range")) {
-			char *arg;
-			/* Split the pattern at whitespace and add each token
-			 * as a revision expression. Do not accept other
-			 * rev-list options. Also, replace the previously
-			 * pushed tip (it's no longer relevant).
-			 */
-			argv_array_pop(&rev_argv);
-			while ((arg = next_token(&pattern))) {
-				if (*arg == '-') {
-					fprintf(stderr, "Bad range expr: %s\n",
-						arg);
-					break;
-				}
-				argv_array_push(&rev_argv, arg);
-			}
-		}
-	}
-
-	if (!path || !ctx.cfg.enable_follow_links) {
-		/*
-		 * If we don't have a path, "follow" is a no-op so make sure
-		 * the variable is set to false to avoid needing to check
-		 * both this and whether we have a path everywhere.
-		 */
-		ctx.qry.follow = 0;
-	}
-
-	if (commit_graph && !ctx.qry.follow) {
-		argv_array_push(&rev_argv, "--graph");
-		argv_array_push(&rev_argv, "--color");
-		graph_set_column_colors(column_colors_html,
-					COLUMN_COLORS_HTML_MAX);
-	}
-
-	if (commit_sort == 1)
-		argv_array_push(&rev_argv, "--date-order");
-	else if (commit_sort == 2)
-		argv_array_push(&rev_argv, "--topo-order");
-
-	if (path && ctx.qry.follow)
-		argv_array_push(&rev_argv, "--follow");
-	argv_array_push(&rev_argv, "--");
-	if (path)
-		argv_array_push(&rev_argv, path);
-
-	init_revisions(&rev, NULL);
-	rev.abbrev = DEFAULT_ABBREV;
-	rev.commit_format = CMIT_FMT_DEFAULT;
-	rev.verbose_header = 1;
-	rev.show_root_diff = 0;
-	rev.ignore_missing = 1;
-	rev.simplify_history = 1;
-	setup_revisions(rev_argv.argc, rev_argv.argv, &rev, NULL);
-	load_ref_decorations(NULL, DECORATE_FULL_REFS);
-	rev.show_decorations = 1;
-	rev.grep_filter.ignore_case = 1;
-
-	rev.diffopt.detect_rename = 1;
-	rev.diffopt.rename_limit = ctx.cfg.renamelimit;
-	if (ctx.qry.ignorews)
-		DIFF_XDL_SET(&rev.diffopt, IGNORE_WHITESPACE);
-
-	compile_grep_patterns(&rev.grep_filter);
-	prepare_revision_walk(&rev);
-
-	if (pager) {
-		cgit_print_layout_start();
-		html("<table class='list nowrap'>");
-	}
-
-	html("<tr class='nohover'>");
-	if (commit_graph)
-		html("<th></th>");
-	else
-		html("<th class='left'>Age</th>");
-	html("<th class='left'>Commit message");
-	if (pager) {
-		html(" (");
-		cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
-			      NULL, ctx.qry.head, ctx.qry.sha1,
-			      ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
-			      ctx.qry.search, ctx.qry.showmsg ? 0 : 1,
-			      ctx.qry.follow);
-		html(")");
-	}
-	html("</th><th class='left'>Author</th>");
-	if (rev.graph)
-		html("<th class='left'>Age</th>");
-	if (ctx.repo->enable_log_filecount) {
-		html("<th class='left'>Files</th>");
-		columns++;
-	}
-	if (ctx.repo->enable_log_linecount) {
-		html("<th class='left'>Lines</th>");
-		columns++;
-	}
-	html("</tr>\n");
-
-	if (ofs<0)
-		ofs = 0;
-
-	for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; /* nop */) {
-		if (show_commit(commit, &rev))
-			i++;
-		free_commit_buffer(the_repository->parsed_objects, commit);
-		free_commit_list(commit->parents);
-		commit->parents = NULL;
-	}
-
-	for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; /* nop */) {
-		/*
-		 * In "follow" mode, we must count the files and lines the
-		 * first time we invoke diff on a given commit, and we need
-		 * to do that to see if the commit touches the path we care
-		 * about, so we do it in show_commit.  Hence we must clear
-		 * lines_counted here.
-		 *
-		 * This has the side effect of avoiding running diff twice
-		 * when we are both following renames and showing file
-		 * and/or line counts.
-		 */
-		lines_counted = 0;
-		if (show_commit(commit, &rev)) {
-			i++;
-			print_commit(commit, &rev);
-		}
-		free_commit_buffer(the_repository->parsed_objects, commit);
-		free_commit_list(commit->parents);
-		commit->parents = NULL;
-	}
-	if (pager) {
-		html("</table><ul class='pager'>");
-		if (ofs > 0) {
-			html("<li>");
-			cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
-				      ctx.qry.sha1, ctx.qry.vpath,
-				      ofs - cnt, ctx.qry.grep,
-				      ctx.qry.search, ctx.qry.showmsg,
-				      ctx.qry.follow);
-			html("</li>");
-		}
-		if ((commit = get_revision(&rev)) != NULL) {
-			html("<li>");
-			cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
-				      ctx.qry.sha1, ctx.qry.vpath,
-				      ofs + cnt, ctx.qry.grep,
-				      ctx.qry.search, ctx.qry.showmsg,
-				      ctx.qry.follow);
-			html("</li>");
-		}
-		html("</ul>");
-		cgit_print_layout_end();
-	} else if ((commit = get_revision(&rev)) != NULL) {
-		htmlf("<tr class='nohover'><td colspan='%d'>", columns);
-		cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
-			      ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg,
-			      ctx.qry.follow);
-		html("</td></tr>\n");
-	}
-
-	/* If we allocated tip then it is safe to cast away const. */
-	if (must_free_tip)
-		free((char*) tip);
-}
diff --git a/ui-log.h b/ui-log.h
deleted file mode 100644
index 325607cdab..0000000000
--- a/ui-log.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#ifndef UI_LOG_H
-#define UI_LOG_H
-
-extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep,
-			   char *pattern, const char *path, int pager,
-			   int commit_graph, int commit_sort);
-extern void show_commit_decorations(struct commit *commit);
-
-#endif /* UI_LOG_H */
diff --git a/ui-patch.c b/ui-patch.c
deleted file mode 100644
index 5a964108e5..0000000000
--- a/ui-patch.c
+++ /dev/null
@@ -1,98 +0,0 @@
-/* ui-patch.c: generate patch view
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-patch.h"
-#include "html.h"
-#include "ui-shared.h"
-
-/* two commit hashes with two dots in between and termination */
-#define REV_RANGE_LEN 2 * GIT_MAX_HEXSZ + 3
-
-void cgit_print_patch(const char *new_rev, const char *old_rev,
-		      const char *prefix)
-{
-	struct rev_info rev;
-	struct commit *commit;
-	struct object_id new_rev_oid, old_rev_oid;
-	char rev_range[REV_RANGE_LEN];
-	const char *rev_argv[] = { NULL, "--reverse", "--format=email", rev_range, "--", prefix, NULL };
-	int rev_argc = ARRAY_SIZE(rev_argv) - 1;
-	char *patchname;
-
-	if (!prefix)
-		rev_argc--;
-
-	if (!new_rev)
-		new_rev = ctx.qry.head;
-
-	if (get_oid(new_rev, &new_rev_oid)) {
-		cgit_print_error_page(404, "Not found",
-				"Bad object id: %s", new_rev);
-		return;
-	}
-	commit = lookup_commit_reference(the_repository, &new_rev_oid);
-	if (!commit) {
-		cgit_print_error_page(404, "Not found",
-				"Bad commit reference: %s", new_rev);
-		return;
-	}
-
-	if (old_rev) {
-		if (get_oid(old_rev, &old_rev_oid)) {
-			cgit_print_error_page(404, "Not found",
-					"Bad object id: %s", old_rev);
-			return;
-		}
-		if (!lookup_commit_reference(the_repository, &old_rev_oid)) {
-			cgit_print_error_page(404, "Not found",
-					"Bad commit reference: %s", old_rev);
-			return;
-		}
-	} else if (commit->parents && commit->parents->item) {
-		oidcpy(&old_rev_oid, &commit->parents->item->object.oid);
-	} else {
-		oidclr(&old_rev_oid);
-	}
-
-	if (is_null_oid(&old_rev_oid)) {
-		memcpy(rev_range, oid_to_hex(&new_rev_oid), GIT_SHA1_HEXSZ + 1);
-	} else {
-		xsnprintf(rev_range, REV_RANGE_LEN, "%s..%s", oid_to_hex(&old_rev_oid),
-			oid_to_hex(&new_rev_oid));
-	}
-
-	patchname = fmt("%s.patch", rev_range);
-	ctx.page.mimetype = "text/plain";
-	ctx.page.filename = patchname;
-	cgit_print_http_headers();
-
-	if (ctx.cfg.noplainemail) {
-		rev_argv[2] = "--format=format:From %H Mon Sep 17 00:00:00 "
-			      "2001%nFrom: %an%nDate: %aD%n%w(78,0,1)Subject: "
-			      "%s%n%n%w(0)%b";
-	}
-
-	init_revisions(&rev, NULL);
-	rev.abbrev = DEFAULT_ABBREV;
-	rev.verbose_header = 1;
-	rev.diff = 1;
-	rev.show_root_diff = 1;
-	rev.max_parents = 1;
-	rev.diffopt.output_format |= DIFF_FORMAT_DIFFSTAT |
-			DIFF_FORMAT_PATCH | DIFF_FORMAT_SUMMARY;
-	if (prefix)
-		rev.diffopt.stat_sep = fmt("(limited to '%s')\n\n", prefix);
-	setup_revisions(rev_argc, rev_argv, &rev, NULL);
-	prepare_revision_walk(&rev);
-
-	while ((commit = get_revision(&rev)) != NULL) {
-		log_tree_commit(&rev, commit);
-		printf("-- \ncgit %s\n\n", cgit_version);
-	}
-}
diff --git a/ui-patch.h b/ui-patch.h
deleted file mode 100644
index 7a6cacd5ac..0000000000
--- a/ui-patch.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#ifndef UI_PATCH_H
-#define UI_PATCH_H
-
-extern void cgit_print_patch(const char *new_rev, const char *old_rev,
-			     const char *prefix);
-
-#endif /* UI_PATCH_H */
diff --git a/ui-plain.c b/ui-plain.c
deleted file mode 100644
index b73c1cfed1..0000000000
--- a/ui-plain.c
+++ /dev/null
@@ -1,207 +0,0 @@
-/* ui-plain.c: functions for output of plain blobs by path
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-plain.h"
-#include "html.h"
-#include "ui-shared.h"
-
-struct walk_tree_context {
-	int match_baselen;
-	int match;
-};
-
-static int print_object(const struct object_id *oid, const char *path)
-{
-	enum object_type type;
-	char *buf, *mimetype;
-	unsigned long size;
-
-	type = oid_object_info(the_repository, oid, &size);
-	if (type == OBJ_BAD) {
-		cgit_print_error_page(404, "Not found", "Not found");
-		return 0;
-	}
-
-	buf = read_object_file(oid, &type, &size);
-	if (!buf) {
-		cgit_print_error_page(404, "Not found", "Not found");
-		return 0;
-	}
-
-	mimetype = get_mimetype_for_filename(path);
-	ctx.page.mimetype = mimetype;
-
-	if (!ctx.repo->enable_html_serving) {
-		html("X-Content-Type-Options: nosniff\n");
-		html("Content-Security-Policy: default-src 'none'\n");
-		if (mimetype) {
-			/* Built-in white list allows PDF and everything that isn't text/ and application/ */
-			if ((!strncmp(mimetype, "text/", 5) || !strncmp(mimetype, "application/", 12)) && strcmp(mimetype, "application/pdf"))
-				ctx.page.mimetype = NULL;
-		}
-	}
-
-	if (!ctx.page.mimetype) {
-		if (buffer_is_binary(buf, size)) {
-			ctx.page.mimetype = "application/octet-stream";
-			ctx.page.charset = NULL;
-		} else {
-			ctx.page.mimetype = "text/plain";
-		}
-	}
-	ctx.page.filename = path;
-	ctx.page.size = size;
-	ctx.page.etag = oid_to_hex(oid);
-	cgit_print_http_headers();
-	html_raw(buf, size);
-	free(mimetype);
-	free(buf);
-	return 1;
-}
-
-static char *buildpath(const char *base, int baselen, const char *path)
-{
-	if (path[0])
-		return fmtalloc("%.*s%s/", baselen, base, path);
-	else
-		return fmtalloc("%.*s/", baselen, base);
-}
-
-static void print_dir(const struct object_id *oid, const char *base,
-		      int baselen, const char *path)
-{
-	char *fullpath, *slash;
-	size_t len;
-
-	fullpath = buildpath(base, baselen, path);
-	slash = (fullpath[0] == '/' ? "" : "/");
-	ctx.page.etag = oid_to_hex(oid);
-	cgit_print_http_headers();
-	htmlf("<html><head><title>%s", slash);
-	html_txt(fullpath);
-	htmlf("</title></head>\n<body>\n<h2>%s", slash);
-	html_txt(fullpath);
-	html("</h2>\n<ul>\n");
-	len = strlen(fullpath);
-	if (len > 1) {
-		fullpath[len - 1] = 0;
-		slash = strrchr(fullpath, '/');
-		if (slash)
-			*(slash + 1) = 0;
-		else {
-			free(fullpath);
-			fullpath = NULL;
-		}
-		html("<li>");
-		cgit_plain_link("../", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
-				fullpath);
-		html("</li>\n");
-	}
-	free(fullpath);
-}
-
-static void print_dir_entry(const struct object_id *oid, const char *base,
-			    int baselen, const char *path, unsigned mode)
-{
-	char *fullpath;
-
-	fullpath = buildpath(base, baselen, path);
-	if (!S_ISDIR(mode) && !S_ISGITLINK(mode))
-		fullpath[strlen(fullpath) - 1] = 0;
-	html("  <li>");
-	if (S_ISGITLINK(mode)) {
-		cgit_submodule_link(NULL, fullpath, oid_to_hex(oid));
-	} else
-		cgit_plain_link(path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
-				fullpath);
-	html("</li>\n");
-	free(fullpath);
-}
-
-static void print_dir_tail(void)
-{
-	html(" </ul>\n</body></html>\n");
-}
-
-static int walk_tree(const struct object_id *oid, struct strbuf *base,
-		const char *pathname, unsigned mode, int stage, void *cbdata)
-{
-	struct walk_tree_context *walk_tree_ctx = cbdata;
-
-	if (base->len == walk_tree_ctx->match_baselen) {
-		if (S_ISREG(mode) || S_ISLNK(mode)) {
-			if (print_object(oid, pathname))
-				walk_tree_ctx->match = 1;
-		} else if (S_ISDIR(mode)) {
-			print_dir(oid, base->buf, base->len, pathname);
-			walk_tree_ctx->match = 2;
-			return READ_TREE_RECURSIVE;
-		}
-	} else if (base->len < INT_MAX && (int)base->len > walk_tree_ctx->match_baselen) {
-		print_dir_entry(oid, base->buf, base->len, pathname, mode);
-		walk_tree_ctx->match = 2;
-	} else if (S_ISDIR(mode)) {
-		return READ_TREE_RECURSIVE;
-	}
-
-	return 0;
-}
-
-static int basedir_len(const char *path)
-{
-	char *p = strrchr(path, '/');
-	if (p)
-		return p - path + 1;
-	return 0;
-}
-
-void cgit_print_plain(void)
-{
-	const char *rev = ctx.qry.sha1;
-	struct object_id oid;
-	struct commit *commit;
-	struct pathspec_item path_items = {
-		.match = ctx.qry.path,
-		.len = ctx.qry.path ? strlen(ctx.qry.path) : 0
-	};
-	struct pathspec paths = {
-		.nr = 1,
-		.items = &path_items
-	};
-	struct walk_tree_context walk_tree_ctx = {
-		.match = 0
-	};
-
-	if (!rev)
-		rev = ctx.qry.head;
-
-	if (get_oid(rev, &oid)) {
-		cgit_print_error_page(404, "Not found", "Not found");
-		return;
-	}
-	commit = lookup_commit_reference(the_repository, &oid);
-	if (!commit || parse_commit(commit)) {
-		cgit_print_error_page(404, "Not found", "Not found");
-		return;
-	}
-	if (!path_items.match) {
-		path_items.match = "";
-		walk_tree_ctx.match_baselen = -1;
-		print_dir(&commit->maybe_tree->object.oid, "", 0, "");
-		walk_tree_ctx.match = 2;
-	}
-	else
-		walk_tree_ctx.match_baselen = basedir_len(path_items.match);
-	read_tree_recursive(the_repository, commit->maybe_tree,
-		"", 0, 0, &paths, walk_tree, &walk_tree_ctx);
-	if (!walk_tree_ctx.match)
-		cgit_print_error_page(404, "Not found", "Not found");
-	else if (walk_tree_ctx.match == 2)
-		print_dir_tail();
-}
diff --git a/ui-plain.h b/ui-plain.h
deleted file mode 100644
index 5bff07b83b..0000000000
--- a/ui-plain.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef UI_PLAIN_H
-#define UI_PLAIN_H
-
-extern void cgit_print_plain(void);
-
-#endif /* UI_PLAIN_H */
diff --git a/ui-refs.c b/ui-refs.c
deleted file mode 100644
index 456f610df4..0000000000
--- a/ui-refs.c
+++ /dev/null
@@ -1,219 +0,0 @@
-/* ui-refs.c: browse symbolic refs
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-refs.h"
-#include "html.h"
-#include "ui-shared.h"
-
-static inline int cmp_age(int age1, int age2)
-{
-	/* age1 and age2 are assumed to be non-negative */
-	return age2 - age1;
-}
-
-static int cmp_ref_name(const void *a, const void *b)
-{
-	struct refinfo *r1 = *(struct refinfo **)a;
-	struct refinfo *r2 = *(struct refinfo **)b;
-
-	return strcmp(r1->refname, r2->refname);
-}
-
-static int cmp_branch_age(const void *a, const void *b)
-{
-	struct refinfo *r1 = *(struct refinfo **)a;
-	struct refinfo *r2 = *(struct refinfo **)b;
-
-	return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
-}
-
-static int get_ref_age(struct refinfo *ref)
-{
-	if (!ref->object)
-		return 0;
-	switch (ref->object->type) {
-	case OBJ_TAG:
-		return ref->tag ? ref->tag->tagger_date : 0;
-	case OBJ_COMMIT:
-		return ref->commit ? ref->commit->committer_date : 0;
-	}
-	return 0;
-}
-
-static int cmp_tag_age(const void *a, const void *b)
-{
-	struct refinfo *r1 = *(struct refinfo **)a;
-	struct refinfo *r2 = *(struct refinfo **)b;
-
-	return cmp_age(get_ref_age(r1), get_ref_age(r2));
-}
-
-static int print_branch(struct refinfo *ref)
-{
-	struct commitinfo *info = ref->commit;
-	char *name = (char *)ref->refname;
-
-	if (!info)
-		return 1;
-	html("<tr><td>");
-	cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
-		      ctx.qry.showmsg, 0);
-	html("</td><td>");
-
-	if (ref->object->type == OBJ_COMMIT) {
-		cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL);
-		html("</td><td>");
-		cgit_open_filter(ctx.repo->email_filter, info->author_email, "refs");
-		html_txt(info->author);
-		cgit_close_filter(ctx.repo->email_filter);
-		html("</td><td colspan='2'>");
-		cgit_print_age(info->committer_date, info->committer_tz, -1);
-	} else {
-		html("</td><td></td><td>");
-		cgit_object_link(ref->object);
-	}
-	html("</td></tr>\n");
-	return 0;
-}
-
-static void print_tag_header(void)
-{
-	html("<tr class='nohover'><th class='left'>Tag</th>"
-	     "<th class='left'>Download</th>"
-	     "<th class='left'>Author</th>"
-	     "<th class='left' colspan='2'>Age</th></tr>\n");
-}
-
-static int print_tag(struct refinfo *ref)
-{
-	struct tag *tag = NULL;
-	struct taginfo *info = NULL;
-	char *name = (char *)ref->refname;
-	struct object *obj = ref->object;
-
-	if (obj->type == OBJ_TAG) {
-		tag = (struct tag *)obj;
-		obj = tag->tagged;
-		info = ref->tag;
-		if (!info)
-			return 1;
-	}
-
-	html("<tr><td>");
-	cgit_tag_link(name, NULL, NULL, name);
-	html("</td><td>");
-	if (ctx.repo->snapshots && (obj->type == OBJ_COMMIT))
-		cgit_print_snapshot_links(ctx.repo, name, "&nbsp;&nbsp;");
-	else
-		cgit_object_link(obj);
-	html("</td><td>");
-	if (info) {
-		if (info->tagger) {
-			cgit_open_filter(ctx.repo->email_filter, info->tagger_email, "refs");
-			html_txt(info->tagger);
-			cgit_close_filter(ctx.repo->email_filter);
-		}
-	} else if (ref->object->type == OBJ_COMMIT) {
-		cgit_open_filter(ctx.repo->email_filter, ref->commit->author_email, "refs");
-		html_txt(ref->commit->author);
-		cgit_close_filter(ctx.repo->email_filter);
-	}
-	html("</td><td colspan='2'>");
-	if (info) {
-		if (info->tagger_date > 0)
-			cgit_print_age(info->tagger_date, info->tagger_tz, -1);
-	} else if (ref->object->type == OBJ_COMMIT) {
-		cgit_print_age(ref->commit->commit->date, 0, -1);
-	}
-	html("</td></tr>\n");
-
-	return 0;
-}
-
-static void print_refs_link(const char *path)
-{
-	html("<tr class='nohover'><td colspan='5'>");
-	cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
-	html("</td></tr>");
-}
-
-void cgit_print_branches(int maxcount)
-{
-	struct reflist list;
-	int i;
-
-	html("<tr class='nohover'><th class='left'>Branch</th>"
-	     "<th class='left'>Commit message</th>"
-	     "<th class='left'>Author</th>"
-	     "<th class='left' colspan='2'>Age</th></tr>\n");
-
-	list.refs = NULL;
-	list.alloc = list.count = 0;
-	for_each_branch_ref(cgit_refs_cb, &list);
-	if (ctx.repo->enable_remote_branches)
-		for_each_remote_ref(cgit_refs_cb, &list);
-
-	if (maxcount == 0 || maxcount > list.count)
-		maxcount = list.count;
-
-	qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
-	if (ctx.repo->branch_sort == 0)
-		qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
-
-	for (i = 0; i < maxcount; i++)
-		print_branch(list.refs[i]);
-
-	if (maxcount < list.count)
-		print_refs_link("heads");
-
-	cgit_free_reflist_inner(&list);
-}
-
-void cgit_print_tags(int maxcount)
-{
-	struct reflist list;
-	int i;
-
-	list.refs = NULL;
-	list.alloc = list.count = 0;
-	for_each_tag_ref(cgit_refs_cb, &list);
-	if (list.count == 0)
-		return;
-	qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age);
-	if (!maxcount)
-		maxcount = list.count;
-	else if (maxcount > list.count)
-		maxcount = list.count;
-	print_tag_header();
-	for (i = 0; i < maxcount; i++)
-		print_tag(list.refs[i]);
-
-	if (maxcount < list.count)
-		print_refs_link("tags");
-
-	cgit_free_reflist_inner(&list);
-}
-
-void cgit_print_refs(void)
-{
-	cgit_print_layout_start();
-	html("<table class='list nowrap'>");
-
-	if (ctx.qry.path && starts_with(ctx.qry.path, "heads"))
-		cgit_print_branches(0);
-	else if (ctx.qry.path && starts_with(ctx.qry.path, "tags"))
-		cgit_print_tags(0);
-	else {
-		cgit_print_branches(0);
-		html("<tr class='nohover'><td colspan='5'>&nbsp;</td></tr>");
-		cgit_print_tags(0);
-	}
-	html("</table>");
-	cgit_print_layout_end();
-}
diff --git a/ui-refs.h b/ui-refs.h
deleted file mode 100644
index 1d4a54a2c8..0000000000
--- a/ui-refs.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#ifndef UI_REFS_H
-#define UI_REFS_H
-
-extern void cgit_print_branches(int maxcount);
-extern void cgit_print_tags(int maxcount);
-extern void cgit_print_refs(void);
-
-#endif /* UI_REFS_H */
diff --git a/ui-repolist.c b/ui-repolist.c
deleted file mode 100644
index 7cf763891f..0000000000
--- a/ui-repolist.c
+++ /dev/null
@@ -1,379 +0,0 @@
-/* ui-repolist.c: functions for generating the repolist page
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-repolist.h"
-#include "html.h"
-#include "ui-shared.h"
-
-static time_t read_agefile(const char *path)
-{
-	time_t result;
-	size_t size;
-	char *buf = NULL;
-	struct strbuf date_buf = STRBUF_INIT;
-
-	if (readfile(path, &buf, &size)) {
-		free(buf);
-		return -1;
-	}
-
-	if (parse_date(buf, &date_buf) == 0)
-		result = strtoul(date_buf.buf, NULL, 10);
-	else
-		result = 0;
-	free(buf);
-	strbuf_release(&date_buf);
-	return result;
-}
-
-static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
-{
-	struct strbuf path = STRBUF_INIT;
-	struct stat s;
-	struct cgit_repo *r = (struct cgit_repo *)repo;
-
-	if (repo->mtime != -1) {
-		*mtime = repo->mtime;
-		return 1;
-	}
-	strbuf_addf(&path, "%s/%s", repo->path, ctx.cfg.agefile);
-	if (stat(path.buf, &s) == 0) {
-		*mtime = read_agefile(path.buf);
-		if (*mtime) {
-			r->mtime = *mtime;
-			goto end;
-		}
-	}
-
-	strbuf_reset(&path);
-	strbuf_addf(&path, "%s/refs/heads/%s", repo->path,
-		    repo->defbranch ? repo->defbranch : "master");
-	if (stat(path.buf, &s) == 0) {
-		*mtime = s.st_mtime;
-		r->mtime = *mtime;
-		goto end;
-	}
-
-	strbuf_reset(&path);
-	strbuf_addf(&path, "%s/%s", repo->path, "packed-refs");
-	if (stat(path.buf, &s) == 0) {
-		*mtime = s.st_mtime;
-		r->mtime = *mtime;
-		goto end;
-	}
-
-	*mtime = 0;
-	r->mtime = *mtime;
-end:
-	strbuf_release(&path);
-	return (r->mtime != 0);
-}
-
-static void print_modtime(struct cgit_repo *repo)
-{
-	time_t t;
-	if (get_repo_modtime(repo, &t))
-		cgit_print_age(t, 0, -1);
-}
-
-static int is_match(struct cgit_repo *repo)
-{
-	if (!ctx.qry.search)
-		return 1;
-	if (repo->url && strcasestr(repo->url, ctx.qry.search))
-		return 1;
-	if (repo->name && strcasestr(repo->name, ctx.qry.search))
-		return 1;
-	if (repo->desc && strcasestr(repo->desc, ctx.qry.search))
-		return 1;
-	if (repo->owner && strcasestr(repo->owner, ctx.qry.search))
-		return 1;
-	return 0;
-}
-
-static int is_in_url(struct cgit_repo *repo)
-{
-	if (!ctx.qry.url)
-		return 1;
-	if (repo->url && starts_with(repo->url, ctx.qry.url))
-		return 1;
-	return 0;
-}
-
-static int is_visible(struct cgit_repo *repo)
-{
-	if (repo->hide || repo->ignore)
-		return 0;
-	if (!(is_match(repo) && is_in_url(repo)))
-		return 0;
-	return 1;
-}
-
-static int any_repos_visible(void)
-{
-	int i;
-
-	for (i = 0; i < cgit_repolist.count; i++) {
-		if (is_visible(&cgit_repolist.repos[i]))
-			return 1;
-	}
-	return 0;
-}
-
-static void print_sort_header(const char *title, const char *sort)
-{
-	char *currenturl = cgit_currenturl();
-	html("<th class='left'><a href='");
-	html_attr(currenturl);
-	htmlf("?s=%s", sort);
-	if (ctx.qry.search) {
-		html("&amp;q=");
-		html_url_arg(ctx.qry.search);
-	}
-	htmlf("'>%s</a></th>", title);
-	free(currenturl);
-}
-
-static void print_header(void)
-{
-	html("<tr class='nohover'>");
-	print_sort_header("Name", "name");
-	print_sort_header("Description", "desc");
-	if (ctx.cfg.enable_index_owner)
-		print_sort_header("Owner", "owner");
-	print_sort_header("Idle", "idle");
-	if (ctx.cfg.enable_index_links)
-		html("<th class='left'>Links</th>");
-	html("</tr>\n");
-}
-
-
-static void print_pager(int items, int pagelen, char *search, char *sort)
-{
-	int i, ofs;
-	char *class = NULL;
-	html("<ul class='pager'>");
-	for (i = 0, ofs = 0; ofs < items; i++, ofs = i * pagelen) {
-		class = (ctx.qry.ofs == ofs) ? "current" : NULL;
-		html("<li>");
-		cgit_index_link(fmt("[%d]", i + 1), fmt("Page %d", i + 1),
-				class, search, sort, ofs, 0);
-		html("</li>");
-	}
-	html("</ul>");
-}
-
-static int cmp(const char *s1, const char *s2)
-{
-	if (s1 && s2) {
-		if (ctx.cfg.case_sensitive_sort)
-			return strcmp(s1, s2);
-		else
-			return strcasecmp(s1, s2);
-	}
-	if (s1 && !s2)
-		return -1;
-	if (s2 && !s1)
-		return 1;
-	return 0;
-}
-
-static int sort_name(const void *a, const void *b)
-{
-	const struct cgit_repo *r1 = a;
-	const struct cgit_repo *r2 = b;
-
-	return cmp(r1->name, r2->name);
-}
-
-static int sort_desc(const void *a, const void *b)
-{
-	const struct cgit_repo *r1 = a;
-	const struct cgit_repo *r2 = b;
-
-	return cmp(r1->desc, r2->desc);
-}
-
-static int sort_owner(const void *a, const void *b)
-{
-	const struct cgit_repo *r1 = a;
-	const struct cgit_repo *r2 = b;
-
-	return cmp(r1->owner, r2->owner);
-}
-
-static int sort_idle(const void *a, const void *b)
-{
-	const struct cgit_repo *r1 = a;
-	const struct cgit_repo *r2 = b;
-	time_t t1, t2;
-
-	t1 = t2 = 0;
-	get_repo_modtime(r1, &t1);
-	get_repo_modtime(r2, &t2);
-	return t2 - t1;
-}
-
-static int sort_section(const void *a, const void *b)
-{
-	const struct cgit_repo *r1 = a;
-	const struct cgit_repo *r2 = b;
-	int result;
-
-	result = cmp(r1->section, r2->section);
-	if (!result) {
-		if (!strcmp(ctx.cfg.repository_sort, "age"))
-			result = sort_idle(r1, r2);
-		if (!result)
-			result = cmp(r1->name, r2->name);
-	}
-	return result;
-}
-
-struct sortcolumn {
-	const char *name;
-	int (*fn)(const void *a, const void *b);
-};
-
-static const struct sortcolumn sortcolumn[] = {
-	{"section", sort_section},
-	{"name", sort_name},
-	{"desc", sort_desc},
-	{"owner", sort_owner},
-	{"idle", sort_idle},
-	{NULL, NULL}
-};
-
-static int sort_repolist(char *field)
-{
-	const struct sortcolumn *column;
-
-	for (column = &sortcolumn[0]; column->name; column++) {
-		if (strcmp(field, column->name))
-			continue;
-		qsort(cgit_repolist.repos, cgit_repolist.count,
-			sizeof(struct cgit_repo), column->fn);
-		return 1;
-	}
-	return 0;
-}
-
-
-void cgit_print_repolist(void)
-{
-	int i, columns = 3, hits = 0, header = 0;
-	char *last_section = NULL;
-	char *section;
-	char *repourl;
-	int sorted = 0;
-
-	if (!any_repos_visible()) {
-		cgit_print_error_page(404, "Not found", "No repositories found");
-		return;
-	}
-
-	if (ctx.cfg.enable_index_links)
-		++columns;
-	if (ctx.cfg.enable_index_owner)
-		++columns;
-
-	ctx.page.title = ctx.cfg.root_title;
-	cgit_print_http_headers();
-	cgit_print_docstart();
-	cgit_print_pageheader();
-
-	if (ctx.qry.sort)
-		sorted = sort_repolist(ctx.qry.sort);
-	else if (ctx.cfg.section_sort)
-		sort_repolist("section");
-
-	html("<table summary='repository list' class='list nowrap'>");
-	for (i = 0; i < cgit_repolist.count; i++) {
-		ctx.repo = &cgit_repolist.repos[i];
-		if (!is_visible(ctx.repo))
-			continue;
-		hits++;
-		if (hits <= ctx.qry.ofs)
-			continue;
-		if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count)
-			continue;
-		if (!header++)
-			print_header();
-		section = ctx.repo->section;
-		if (section && !strcmp(section, ""))
-			section = NULL;
-		if (!sorted &&
-		    ((last_section == NULL && section != NULL) ||
-		    (last_section != NULL && section == NULL) ||
-		    (last_section != NULL && section != NULL &&
-		     strcmp(section, last_section)))) {
-			htmlf("<tr class='nohover-highlight'><td colspan='%d' class='reposection'>",
-			      columns);
-			html_txt(section);
-			html("</td></tr>");
-			last_section = section;
-		}
-		htmlf("<tr><td class='%s'>",
-		      !sorted && section ? "sublevel-repo" : "toplevel-repo");
-		cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
-		html("</td><td>");
-		repourl = cgit_repourl(ctx.repo->url);
-		html_link_open(repourl, NULL, NULL);
-		free(repourl);
-		if (html_ntxt(ctx.repo->desc, ctx.cfg.max_repodesc_len) < 0)
-			html("...");
-		html_link_close();
-		html("</td><td>");
-		if (ctx.cfg.enable_index_owner) {
-			if (ctx.repo->owner_filter) {
-				cgit_open_filter(ctx.repo->owner_filter);
-				html_txt(ctx.repo->owner);
-				cgit_close_filter(ctx.repo->owner_filter);
-			} else {
-				char *currenturl = cgit_currenturl();
-				html("<a href='");
-				html_attr(currenturl);
-				html("?q=");
-				html_url_arg(ctx.repo->owner);
-				html("'>");
-				html_txt(ctx.repo->owner);
-				html("</a>");
-				free(currenturl);
-			}
-			html("</td><td>");
-		}
-		print_modtime(ctx.repo);
-		html("</td>");
-		if (ctx.cfg.enable_index_links) {
-			html("<td>");
-			cgit_summary_link("summary", NULL, "button", NULL);
-			cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
-				      0, NULL, NULL, ctx.qry.showmsg, 0);
-			cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
-			html("</td>");
-		}
-		html("</tr>\n");
-	}
-	html("</table>");
-	if (hits > ctx.cfg.max_repo_count)
-		print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search, ctx.qry.sort);
-	cgit_print_docend();
-}
-
-void cgit_print_site_readme(void)
-{
-	cgit_print_layout_start();
-	if (!ctx.cfg.root_readme)
-		goto done;
-	cgit_open_filter(ctx.cfg.about_filter, ctx.cfg.root_readme);
-	html_include(ctx.cfg.root_readme);
-	cgit_close_filter(ctx.cfg.about_filter);
-done:
-	cgit_print_layout_end();
-}
diff --git a/ui-repolist.h b/ui-repolist.h
deleted file mode 100644
index 1b6b3227db..0000000000
--- a/ui-repolist.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#ifndef UI_REPOLIST_H
-#define UI_REPOLIST_H
-
-extern void cgit_print_repolist(void);
-extern void cgit_print_site_readme(void);
-
-#endif /* UI_REPOLIST_H */
diff --git a/ui-shared.c b/ui-shared.c
deleted file mode 100644
index d2358f2928..0000000000
--- a/ui-shared.c
+++ /dev/null
@@ -1,1210 +0,0 @@
-/* ui-shared.c: common web output functions
- *
- * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-shared.h"
-#include "cmd.h"
-#include "html.h"
-#include "version.h"
-
-static const char cgit_doctype[] =
-"<!DOCTYPE html>\n";
-
-static char *http_date(time_t t)
-{
-	static char day[][4] =
-		{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
-	static char month[][4] =
-		{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
-		 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
-	struct tm *tm = gmtime(&t);
-	return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
-		   tm->tm_mday, month[tm->tm_mon], 1900 + tm->tm_year,
-		   tm->tm_hour, tm->tm_min, tm->tm_sec);
-}
-
-void cgit_print_error(const char *fmt, ...)
-{
-	va_list ap;
-	va_start(ap, fmt);
-	cgit_vprint_error(fmt, ap);
-	va_end(ap);
-}
-
-void cgit_vprint_error(const char *fmt, va_list ap)
-{
-	va_list cp;
-	html("<div class='error'>");
-	va_copy(cp, ap);
-	html_vtxtf(fmt, cp);
-	va_end(cp);
-	html("</div>\n");
-}
-
-const char *cgit_httpscheme(void)
-{
-	if (ctx.env.https && !strcmp(ctx.env.https, "on"))
-		return "https://";
-	else
-		return "http://";
-}
-
-char *cgit_hosturl(void)
-{
-	if (ctx.env.http_host)
-		return xstrdup(ctx.env.http_host);
-	if (!ctx.env.server_name)
-		return NULL;
-	if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
-		return xstrdup(ctx.env.server_name);
-	return fmtalloc("%s:%s", ctx.env.server_name, ctx.env.server_port);
-}
-
-char *cgit_currenturl(void)
-{
-	const char *root = cgit_rooturl();
-
-	if (!ctx.qry.url)
-		return xstrdup(root);
-	if (root[0] && root[strlen(root) - 1] == '/')
-		return fmtalloc("%s%s", root, ctx.qry.url);
-	return fmtalloc("%s/%s", root, ctx.qry.url);
-}
-
-char *cgit_currentfullurl(void)
-{
-	const char *root = cgit_rooturl();
-	const char *orig_query = ctx.env.query_string ? ctx.env.query_string : "";
-	size_t len = strlen(orig_query);
-	char *query = xmalloc(len + 2), *start_url, *ret;
-
-	/* Remove all url=... parts from query string */
-	memcpy(query + 1, orig_query, len + 1);
-	query[0] = '?';
-	start_url = query;
-	while ((start_url = strstr(start_url, "url=")) != NULL) {
-		if (start_url[-1] == '?' || start_url[-1] == '&') {
-			const char *end_url = strchr(start_url, '&');
-			if (end_url)
-				memmove(start_url, end_url + 1, strlen(end_url));
-			else
-				start_url[0] = '\0';
-		} else
-			++start_url;
-	}
-	if (!query[1])
-		query[0] = '\0';
-
-	if (!ctx.qry.url)
-		ret = fmtalloc("%s%s", root, query);
-	else if (root[0] && root[strlen(root) - 1] == '/')
-		ret = fmtalloc("%s%s%s", root, ctx.qry.url, query);
-	else
-		ret = fmtalloc("%s/%s%s", root, ctx.qry.url, query);
-	free(query);
-	return ret;
-}
-
-const char *cgit_rooturl(void)
-{
-	if (ctx.cfg.virtual_root)
-		return ctx.cfg.virtual_root;
-	else
-		return ctx.cfg.script_name;
-}
-
-const char *cgit_loginurl(void)
-{
-	static const char *login_url;
-	if (!login_url)
-		login_url = fmtalloc("%s?p=login", cgit_rooturl());
-	return login_url;
-}
-
-char *cgit_repourl(const char *reponame)
-{
-	if (ctx.cfg.virtual_root)
-		return fmtalloc("%s%s/", ctx.cfg.virtual_root, reponame);
-	else
-		return fmtalloc("?r=%s", reponame);
-}
-
-char *cgit_fileurl(const char *reponame, const char *pagename,
-		   const char *filename, const char *query)
-{
-	struct strbuf sb = STRBUF_INIT;
-	char *delim;
-
-	if (ctx.cfg.virtual_root) {
-		strbuf_addf(&sb, "%s%s/%s/%s", ctx.cfg.virtual_root, reponame,
-			    pagename, (filename ? filename:""));
-		delim = "?";
-	} else {
-		strbuf_addf(&sb, "?url=%s/%s/%s", reponame, pagename,
-			    (filename ? filename : ""));
-		delim = "&amp;";
-	}
-	if (query)
-		strbuf_addf(&sb, "%s%s", delim, query);
-	return strbuf_detach(&sb, NULL);
-}
-
-char *cgit_pageurl(const char *reponame, const char *pagename,
-		   const char *query)
-{
-	return cgit_fileurl(reponame, pagename, NULL, query);
-}
-
-const char *cgit_repobasename(const char *reponame)
-{
-	/* I assume we don't need to store more than one repo basename */
-	static char rvbuf[1024];
-	int p;
-	const char *rv;
-	size_t len;
-
-	len = strlcpy(rvbuf, reponame, sizeof(rvbuf));
-	if (len >= sizeof(rvbuf))
-		die("cgit_repobasename: truncated repository name '%s'", reponame);
-	p = len - 1;
-	/* strip trailing slashes */
-	while (p && rvbuf[p] == '/')
-		rvbuf[p--] = '\0';
-	/* strip trailing .git */
-	if (p >= 3 && starts_with(&rvbuf[p-3], ".git")) {
-		p -= 3;
-		rvbuf[p--] = '\0';
-	}
-	/* strip more trailing slashes if any */
-	while (p && rvbuf[p] == '/')
-		rvbuf[p--] = '\0';
-	/* find last slash in the remaining string */
-	rv = strrchr(rvbuf, '/');
-	if (rv)
-		return ++rv;
-	return rvbuf;
-}
-
-const char *cgit_snapshot_prefix(const struct cgit_repo *repo)
-{
-	if (repo->snapshot_prefix)
-		return repo->snapshot_prefix;
-
-	return cgit_repobasename(repo->url);
-}
-
-static void site_url(const char *page, const char *search, const char *sort, int ofs, int always_root)
-{
-	char *delim = "?";
-
-	if (always_root || page)
-		html_attr(cgit_rooturl());
-	else {
-		char *currenturl = cgit_currenturl();
-		html_attr(currenturl);
-		free(currenturl);
-	}
-
-	if (page) {
-		htmlf("?p=%s", page);
-		delim = "&amp;";
-	}
-	if (search) {
-		html(delim);
-		html("q=");
-		html_attr(search);
-		delim = "&amp;";
-	}
-	if (sort) {
-		html(delim);
-		html("s=");
-		html_attr(sort);
-		delim = "&amp;";
-	}
-	if (ofs) {
-		html(delim);
-		htmlf("ofs=%d", ofs);
-	}
-}
-
-static void site_link(const char *page, const char *name, const char *title,
-		      const char *class, const char *search, const char *sort, int ofs, int always_root)
-{
-	html("<a");
-	if (title) {
-		html(" title='");
-		html_attr(title);
-		html("'");
-	}
-	if (class) {
-		html(" class='");
-		html_attr(class);
-		html("'");
-	}
-	html(" href='");
-	site_url(page, search, sort, ofs, always_root);
-	html("'>");
-	html_txt(name);
-	html("</a>");
-}
-
-void cgit_index_link(const char *name, const char *title, const char *class,
-		     const char *pattern, const char *sort, int ofs, int always_root)
-{
-	site_link(NULL, name, title, class, pattern, sort, ofs, always_root);
-}
-
-static char *repolink(const char *title, const char *class, const char *page,
-		      const char *head, const char *path)
-{
-	char *delim = "?";
-
-	html("<a");
-	if (title) {
-		html(" title='");
-		html_attr(title);
-		html("'");
-	}
-	if (class) {
-		html(" class='");
-		html_attr(class);
-		html("'");
-	}
-	html(" href='");
-	if (ctx.cfg.virtual_root) {
-		html_url_path(ctx.cfg.virtual_root);
-		html_url_path(ctx.repo->url);
-		if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
-			html("/");
-		if (page) {
-			html_url_path(page);
-			html("/");
-			if (path)
-				html_url_path(path);
-		}
-	} else {
-		html_url_path(ctx.cfg.script_name);
-		html("?url=");
-		html_url_arg(ctx.repo->url);
-		if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
-			html("/");
-		if (page) {
-			html_url_arg(page);
-			html("/");
-			if (path)
-				html_url_arg(path);
-		}
-		delim = "&amp;";
-	}
-	if (head && ctx.repo->defbranch && strcmp(head, ctx.repo->defbranch)) {
-		html(delim);
-		html("h=");
-		html_url_arg(head);
-		delim = "&amp;";
-	}
-	return fmt("%s", delim);
-}
-
-static void reporevlink(const char *page, const char *name, const char *title,
-			const char *class, const char *head, const char *rev,
-			const char *path)
-{
-	char *delim;
-
-	delim = repolink(title, class, page, head, path);
-	if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) {
-		html(delim);
-		html("id=");
-		html_url_arg(rev);
-	}
-	html("'>");
-	html_txt(name);
-	html("</a>");
-}
-
-void cgit_summary_link(const char *name, const char *title, const char *class,
-		       const char *head)
-{
-	reporevlink(NULL, name, title, class, head, NULL, NULL);
-}
-
-void cgit_tag_link(const char *name, const char *title, const char *class,
-		   const char *tag)
-{
-	reporevlink("tag", name, title, class, tag, NULL, NULL);
-}
-
-void cgit_tree_link(const char *name, const char *title, const char *class,
-		    const char *head, const char *rev, const char *path)
-{
-	reporevlink("tree", name, title, class, head, rev, path);
-}
-
-void cgit_plain_link(const char *name, const char *title, const char *class,
-		     const char *head, const char *rev, const char *path)
-{
-	reporevlink("plain", name, title, class, head, rev, path);
-}
-
-void cgit_blame_link(const char *name, const char *title, const char *class,
-		     const char *head, const char *rev, const char *path)
-{
-	reporevlink("blame", name, title, class, head, rev, path);
-}
-
-void cgit_log_link(const char *name, const char *title, const char *class,
-		   const char *head, const char *rev, const char *path,
-		   int ofs, const char *grep, const char *pattern, int showmsg,
-		   int follow)
-{
-	char *delim;
-
-	delim = repolink(title, class, "log", head, path);
-	if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) {
-		html(delim);
-		html("id=");
-		html_url_arg(rev);
-		delim = "&amp;";
-	}
-	if (grep && pattern) {
-		html(delim);
-		html("qt=");
-		html_url_arg(grep);
-		delim = "&amp;";
-		html(delim);
-		html("q=");
-		html_url_arg(pattern);
-	}
-	if (ofs > 0) {
-		html(delim);
-		html("ofs=");
-		htmlf("%d", ofs);
-		delim = "&amp;";
-	}
-	if (showmsg) {
-		html(delim);
-		html("showmsg=1");
-		delim = "&amp;";
-	}
-	if (follow) {
-		html(delim);
-		html("follow=1");
-	}
-	html("'>");
-	html_txt(name);
-	html("</a>");
-}
-
-void cgit_commit_link(const char *name, const char *title, const char *class,
-		      const char *head, const char *rev, const char *path)
-{
-	char *delim;
-
-	delim = repolink(title, class, "commit", head, path);
-	if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) {
-		html(delim);
-		html("id=");
-		html_url_arg(rev);
-		delim = "&amp;";
-	}
-	if (ctx.qry.difftype) {
-		html(delim);
-		htmlf("dt=%d", ctx.qry.difftype);
-		delim = "&amp;";
-	}
-	if (ctx.qry.context > 0 && ctx.qry.context != 3) {
-		html(delim);
-		html("context=");
-		htmlf("%d", ctx.qry.context);
-		delim = "&amp;";
-	}
-	if (ctx.qry.ignorews) {
-		html(delim);
-		html("ignorews=1");
-		delim = "&amp;";
-	}
-	if (ctx.qry.follow) {
-		html(delim);
-		html("follow=1");
-	}
-	html("'>");
-	if (name[0] != '\0') {
-		if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
-			html_ntxt(name, ctx.cfg.max_msg_len - 3);
-			html("...");
-		} else
-			html_txt(name);
-	} else
-		html_txt("(no commit message)");
-	html("</a>");
-}
-
-void cgit_refs_link(const char *name, const char *title, const char *class,
-		    const char *head, const char *rev, const char *path)
-{
-	reporevlink("refs", name, title, class, head, rev, path);
-}
-
-void cgit_snapshot_link(const char *name, const char *title, const char *class,
-			const char *head, const char *rev,
-			const char *archivename)
-{
-	reporevlink("snapshot", name, title, class, head, rev, archivename);
-}
-
-void cgit_diff_link(const char *name, const char *title, const char *class,
-		    const char *head, const char *new_rev, const char *old_rev,
-		    const char *path)
-{
-	char *delim;
-
-	delim = repolink(title, class, "diff", head, path);
-	if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
-		html(delim);
-		html("id=");
-		html_url_arg(new_rev);
-		delim = "&amp;";
-	}
-	if (old_rev) {
-		html(delim);
-		html("id2=");
-		html_url_arg(old_rev);
-		delim = "&amp;";
-	}
-	if (ctx.qry.difftype) {
-		html(delim);
-		htmlf("dt=%d", ctx.qry.difftype);
-		delim = "&amp;";
-	}
-	if (ctx.qry.context > 0 && ctx.qry.context != 3) {
-		html(delim);
-		html("context=");
-		htmlf("%d", ctx.qry.context);
-		delim = "&amp;";
-	}
-	if (ctx.qry.ignorews) {
-		html(delim);
-		html("ignorews=1");
-		delim = "&amp;";
-	}
-	if (ctx.qry.follow) {
-		html(delim);
-		html("follow=1");
-	}
-	html("'>");
-	html_txt(name);
-	html("</a>");
-}
-
-void cgit_patch_link(const char *name, const char *title, const char *class,
-		     const char *head, const char *rev, const char *path)
-{
-	reporevlink("patch", name, title, class, head, rev, path);
-}
-
-void cgit_stats_link(const char *name, const char *title, const char *class,
-		     const char *head, const char *path)
-{
-	reporevlink("stats", name, title, class, head, NULL, path);
-}
-
-static void cgit_self_link(char *name, const char *title, const char *class)
-{
-	if (!strcmp(ctx.qry.page, "repolist"))
-		cgit_index_link(name, title, class, ctx.qry.search, ctx.qry.sort,
-				ctx.qry.ofs, 1);
-	else if (!strcmp(ctx.qry.page, "summary"))
-		cgit_summary_link(name, title, class, ctx.qry.head);
-	else if (!strcmp(ctx.qry.page, "tag"))
-		cgit_tag_link(name, title, class, ctx.qry.has_sha1 ?
-			       ctx.qry.sha1 : ctx.qry.head);
-	else if (!strcmp(ctx.qry.page, "tree"))
-		cgit_tree_link(name, title, class, ctx.qry.head,
-			       ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
-			       ctx.qry.path);
-	else if (!strcmp(ctx.qry.page, "plain"))
-		cgit_plain_link(name, title, class, ctx.qry.head,
-				ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
-				ctx.qry.path);
-	else if (!strcmp(ctx.qry.page, "blame"))
-		cgit_blame_link(name, title, class, ctx.qry.head,
-				ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
-				ctx.qry.path);
-	else if (!strcmp(ctx.qry.page, "log"))
-		cgit_log_link(name, title, class, ctx.qry.head,
-			      ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
-			      ctx.qry.path, ctx.qry.ofs,
-			      ctx.qry.grep, ctx.qry.search,
-			      ctx.qry.showmsg, ctx.qry.follow);
-	else if (!strcmp(ctx.qry.page, "commit"))
-		cgit_commit_link(name, title, class, ctx.qry.head,
-				 ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
-				 ctx.qry.path);
-	else if (!strcmp(ctx.qry.page, "patch"))
-		cgit_patch_link(name, title, class, ctx.qry.head,
-				ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
-				ctx.qry.path);
-	else if (!strcmp(ctx.qry.page, "refs"))
-		cgit_refs_link(name, title, class, ctx.qry.head,
-			       ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
-			       ctx.qry.path);
-	else if (!strcmp(ctx.qry.page, "snapshot"))
-		cgit_snapshot_link(name, title, class, ctx.qry.head,
-				   ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
-				   ctx.qry.path);
-	else if (!strcmp(ctx.qry.page, "diff"))
-		cgit_diff_link(name, title, class, ctx.qry.head,
-			       ctx.qry.sha1, ctx.qry.sha2,
-			       ctx.qry.path);
-	else if (!strcmp(ctx.qry.page, "stats"))
-		cgit_stats_link(name, title, class, ctx.qry.head,
-				ctx.qry.path);
-	else {
-		/* Don't known how to make link for this page */
-		repolink(title, class, ctx.qry.page, ctx.qry.head, ctx.qry.path);
-		html("><!-- cgit_self_link() doesn't know how to make link for page '");
-		html_txt(ctx.qry.page);
-		html("' -->");
-		html_txt(name);
-		html("</a>");
-	}
-}
-
-void cgit_object_link(struct object *obj)
-{
-	char *page, *shortrev, *fullrev, *name;
-
-	fullrev = oid_to_hex(&obj->oid);
-	shortrev = xstrdup(fullrev);
-	shortrev[10] = '\0';
-	if (obj->type == OBJ_COMMIT) {
-		cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
-				 ctx.qry.head, fullrev, NULL);
-		return;
-	} else if (obj->type == OBJ_TREE)
-		page = "tree";
-	else if (obj->type == OBJ_TAG)
-		page = "tag";
-	else
-		page = "blob";
-	name = fmt("%s %s...", type_name(obj->type), shortrev);
-	reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
-}
-
-static struct string_list_item *lookup_path(struct string_list *list,
-					    const char *path)
-{
-	struct string_list_item *item;
-
-	while (path && path[0]) {
-		if ((item = string_list_lookup(list, path)))
-			return item;
-		if (!(path = strchr(path, '/')))
-			break;
-		path++;
-	}
-	return NULL;
-}
-
-void cgit_submodule_link(const char *class, char *path, const char *rev)
-{
-	struct string_list *list;
-	struct string_list_item *item;
-	char tail, *dir;
-	size_t len;
-
-	len = 0;
-	tail = 0;
-	list = &ctx.repo->submodules;
-	item = lookup_path(list, path);
-	if (!item) {
-		len = strlen(path);
-		tail = path[len - 1];
-		if (tail == '/') {
-			path[len - 1] = 0;
-			item = lookup_path(list, path);
-		}
-	}
-	if (item || ctx.repo->module_link) {
-		html("<a ");
-		if (class)
-			htmlf("class='%s' ", class);
-		html("href='");
-		if (item) {
-			html_attrf(item->util, rev);
-		} else {
-			dir = strrchr(path, '/');
-			if (dir)
-				dir++;
-			else
-				dir = path;
-			html_attrf(ctx.repo->module_link, dir, rev);
-		}
-		html("'>");
-		html_txt(path);
-		html("</a>");
-	} else {
-		html("<span");
-		if (class)
-			htmlf(" class='%s'", class);
-		html(">");
-		html_txt(path);
-		html("</span>");
-	}
-	html_txtf(" @ %.7s", rev);
-	if (item && tail)
-		path[len - 1] = tail;
-}
-
-const struct date_mode *cgit_date_mode(enum date_mode_type type)
-{
-	static struct date_mode mode;
-	mode.type = type;
-	mode.local = ctx.cfg.local_time;
-	return &mode;
-}
-
-static void print_rel_date(time_t t, int tz, double value,
-	const char *class, const char *suffix)
-{
-	htmlf("<span class='%s' title='", class);
-	html_attr(show_date(t, tz, cgit_date_mode(DATE_ISO8601)));
-	htmlf("'>%.0f %s</span>", value, suffix);
-}
-
-void cgit_print_age(time_t t, int tz, time_t max_relative)
-{
-	time_t now, secs;
-
-	if (!t)
-		return;
-	time(&now);
-	secs = now - t;
-	if (secs < 0)
-		secs = 0;
-
-	if (secs > max_relative && max_relative >= 0) {
-		html("<span title='");
-		html_attr(show_date(t, tz, cgit_date_mode(DATE_ISO8601)));
-		html("'>");
-		html_txt(show_date(t, tz, cgit_date_mode(DATE_SHORT)));
-		html("</span>");
-		return;
-	}
-
-	if (secs < TM_HOUR * 2) {
-		print_rel_date(t, tz, secs * 1.0 / TM_MIN, "age-mins", "min.");
-		return;
-	}
-	if (secs < TM_DAY * 2) {
-		print_rel_date(t, tz, secs * 1.0 / TM_HOUR, "age-hours", "hours");
-		return;
-	}
-	if (secs < TM_WEEK * 2) {
-		print_rel_date(t, tz, secs * 1.0 / TM_DAY, "age-days", "days");
-		return;
-	}
-	if (secs < TM_MONTH * 2) {
-		print_rel_date(t, tz, secs * 1.0 / TM_WEEK, "age-weeks", "weeks");
-		return;
-	}
-	if (secs < TM_YEAR * 2) {
-		print_rel_date(t, tz, secs * 1.0 / TM_MONTH, "age-months", "months");
-		return;
-	}
-	print_rel_date(t, tz, secs * 1.0 / TM_YEAR, "age-years", "years");
-}
-
-void cgit_print_http_headers(void)
-{
-	if (ctx.env.no_http && !strcmp(ctx.env.no_http, "1"))
-		return;
-
-	if (ctx.page.status)
-		htmlf("Status: %d %s\n", ctx.page.status, ctx.page.statusmsg);
-	if (ctx.page.mimetype && ctx.page.charset)
-		htmlf("Content-Type: %s; charset=%s\n", ctx.page.mimetype,
-		      ctx.page.charset);
-	else if (ctx.page.mimetype)
-		htmlf("Content-Type: %s\n", ctx.page.mimetype);
-	if (ctx.page.size)
-		htmlf("Content-Length: %zd\n", ctx.page.size);
-	if (ctx.page.filename) {
-		html("Content-Disposition: inline; filename=\"");
-		html_header_arg_in_quotes(ctx.page.filename);
-		html("\"\n");
-	}
-	if (!ctx.env.authenticated)
-		html("Cache-Control: no-cache, no-store\n");
-	htmlf("Last-Modified: %s\n", http_date(ctx.page.modified));
-	htmlf("Expires: %s\n", http_date(ctx.page.expires));
-	if (ctx.page.etag)
-		htmlf("ETag: \"%s\"\n", ctx.page.etag);
-	html("\n");
-	if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
-		exit(0);
-}
-
-void cgit_redirect(const char *url, bool permanent)
-{
-	htmlf("Status: %d %s\n", permanent ? 301 : 302, permanent ? "Moved" : "Found");
-	html("Location: ");
-	html_url_path(url);
-	html("\n\n");
-}
-
-static void print_rel_vcs_link(const char *url)
-{
-	html("<link rel='vcs-git' href='");
-	html_attr(url);
-	html("' title='");
-	html_attr(ctx.repo->name);
-	html(" Git repository'/>\n");
-}
-
-void cgit_print_docstart(void)
-{
-	char *host = cgit_hosturl();
-
-	if (ctx.cfg.embedded) {
-		if (ctx.cfg.header)
-			html_include(ctx.cfg.header);
-		return;
-	}
-
-	html(cgit_doctype);
-	html("<html lang='en'>\n");
-	html("<head>\n");
-	html("<title>");
-	html_txt(ctx.page.title);
-	html("</title>\n");
-	htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
-	if (ctx.cfg.robots && *ctx.cfg.robots)
-		htmlf("<meta name='robots' content='%s'/>\n", ctx.cfg.robots);
-	html("<link rel='stylesheet' type='text/css' href='");
-	html_attr(ctx.cfg.css);
-	html("'/>\n");
-	if (ctx.cfg.favicon) {
-		html("<link rel='shortcut icon' href='");
-		html_attr(ctx.cfg.favicon);
-		html("'/>\n");
-	}
-	if (host && ctx.repo && ctx.qry.head) {
-		char *fileurl;
-		struct strbuf sb = STRBUF_INIT;
-		strbuf_addf(&sb, "h=%s", ctx.qry.head);
-
-		html("<link rel='alternate' title='Atom feed' href='");
-		html(cgit_httpscheme());
-		html_attr(host);
-		fileurl = cgit_fileurl(ctx.repo->url, "atom", ctx.qry.vpath,
-				       sb.buf);
-		html_attr(fileurl);
-		html("' type='application/atom+xml'/>\n");
-		strbuf_release(&sb);
-		free(fileurl);
-	}
-	if (ctx.repo)
-		cgit_add_clone_urls(print_rel_vcs_link);
-	if (ctx.cfg.head_include)
-		html_include(ctx.cfg.head_include);
-	if (ctx.repo && ctx.repo->extra_head_content)
-		html(ctx.repo->extra_head_content);
-	html("</head>\n");
-	html("<body>\n");
-	if (ctx.cfg.header)
-		html_include(ctx.cfg.header);
-	free(host);
-}
-
-void cgit_print_docend(void)
-{
-	html("</div> <!-- class=content -->\n");
-	if (ctx.cfg.embedded) {
-		html("</div> <!-- id=cgit -->\n");
-		if (ctx.cfg.footer)
-			html_include(ctx.cfg.footer);
-		return;
-	}
-	if (ctx.cfg.footer)
-		html_include(ctx.cfg.footer);
-	else {
-		htmlf("<div class='footer'>generated by <a href='https://git.zx2c4.com/cgit/about/'>cgit %s</a> "
-			"(<a href='https://git-scm.com/'>git %s</a>) at ", cgit_version, git_version_string);
-		html_txt(show_date(time(NULL), 0, cgit_date_mode(DATE_ISO8601)));
-		html("</div>\n");
-	}
-	html("</div> <!-- id=cgit -->\n");
-	html("</body>\n</html>\n");
-}
-
-void cgit_print_error_page(int code, const char *msg, const char *fmt, ...)
-{
-	va_list ap;
-	ctx.page.expires = ctx.cfg.cache_dynamic_ttl;
-	ctx.page.status = code;
-	ctx.page.statusmsg = msg;
-	cgit_print_layout_start();
-	va_start(ap, fmt);
-	cgit_vprint_error(fmt, ap);
-	va_end(ap);
-	cgit_print_layout_end();
-}
-
-void cgit_print_layout_start(void)
-{
-	cgit_print_http_headers();
-	cgit_print_docstart();
-	cgit_print_pageheader();
-}
-
-void cgit_print_layout_end(void)
-{
-	cgit_print_docend();
-}
-
-static void add_clone_urls(void (*fn)(const char *), char *txt, char *suffix)
-{
-	struct strbuf **url_list = strbuf_split_str(txt, ' ', 0);
-	int i;
-
-	for (i = 0; url_list[i]; i++) {
-		strbuf_rtrim(url_list[i]);
-		if (url_list[i]->len == 0)
-			continue;
-		if (suffix && *suffix)
-			strbuf_addf(url_list[i], "/%s", suffix);
-		fn(url_list[i]->buf);
-	}
-
-	strbuf_list_free(url_list);
-}
-
-void cgit_add_clone_urls(void (*fn)(const char *))
-{
-	if (ctx.repo->clone_url)
-		add_clone_urls(fn, expand_macros(ctx.repo->clone_url), NULL);
-	else if (ctx.cfg.clone_prefix)
-		add_clone_urls(fn, ctx.cfg.clone_prefix, ctx.repo->url);
-}
-
-static int print_branch_option(const char *refname, const struct object_id *oid,
-			       int flags, void *cb_data)
-{
-	char *name = (char *)refname;
-	html_option(name, name, ctx.qry.head);
-	return 0;
-}
-
-void cgit_add_hidden_formfields(int incl_head, int incl_search,
-				const char *page)
-{
-	if (!ctx.cfg.virtual_root) {
-		struct strbuf url = STRBUF_INIT;
-
-		strbuf_addf(&url, "%s/%s", ctx.qry.repo, page);
-		if (ctx.qry.vpath)
-			strbuf_addf(&url, "/%s", ctx.qry.vpath);
-		html_hidden("url", url.buf);
-		strbuf_release(&url);
-	}
-
-	if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
-	    strcmp(ctx.qry.head, ctx.repo->defbranch))
-		html_hidden("h", ctx.qry.head);
-
-	if (ctx.qry.sha1)
-		html_hidden("id", ctx.qry.sha1);
-	if (ctx.qry.sha2)
-		html_hidden("id2", ctx.qry.sha2);
-	if (ctx.qry.showmsg)
-		html_hidden("showmsg", "1");
-
-	if (incl_search) {
-		if (ctx.qry.grep)
-			html_hidden("qt", ctx.qry.grep);
-		if (ctx.qry.search)
-			html_hidden("q", ctx.qry.search);
-	}
-}
-
-static const char *hc(const char *page)
-{
-	if (!ctx.qry.page)
-		return NULL;
-
-	return strcmp(ctx.qry.page, page) ? NULL : "active";
-}
-
-static void cgit_print_path_crumbs(char *path)
-{
-	char *old_path = ctx.qry.path;
-	char *p = path, *q, *end = path + strlen(path);
-	int levels = 0;
-
-	ctx.qry.path = NULL;
-	cgit_self_link("root", NULL, NULL);
-	ctx.qry.path = p = path;
-	while (p < end) {
-		if (!(q = strchr(p, '/')) || levels > 15)
-			q = end;
-		*q = '\0';
-		html_txt("/");
-		cgit_self_link(p, NULL, NULL);
-		if (q < end)
-			*q = '/';
-		p = q + 1;
-		++levels;
-	}
-	ctx.qry.path = old_path;
-}
-
-static void print_header(void)
-{
-	char *logo = NULL, *logo_link = NULL;
-
-	html("<table id='header'>\n");
-	html("<tr>\n");
-
-	if (ctx.repo && ctx.repo->logo && *ctx.repo->logo)
-		logo = ctx.repo->logo;
-	else
-		logo = ctx.cfg.logo;
-	if (ctx.repo && ctx.repo->logo_link && *ctx.repo->logo_link)
-		logo_link = ctx.repo->logo_link;
-	else
-		logo_link = ctx.cfg.logo_link;
-	if (logo && *logo) {
-		html("<td class='logo' rowspan='2'><a href='");
-		if (logo_link && *logo_link)
-			html_attr(logo_link);
-		else
-			html_attr(cgit_rooturl());
-		html("'><img src='");
-		html_attr(logo);
-		html("' alt='cgit logo'/></a></td>\n");
-	}
-
-	html("<td class='main'>");
-	if (ctx.repo) {
-		cgit_index_link("index", NULL, NULL, NULL, NULL, 0, 1);
-		html(" : ");
-		cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
-		if (ctx.env.authenticated) {
-			html("</td><td class='form'>");
-			html("<form method='get'>\n");
-			cgit_add_hidden_formfields(0, 1, ctx.qry.page);
-			html("<select name='h' onchange='this.form.submit();'>\n");
-			for_each_branch_ref(print_branch_option, ctx.qry.head);
-			if (ctx.repo->enable_remote_branches)
-				for_each_remote_ref(print_branch_option, ctx.qry.head);
-			html("</select> ");
-			html("<input type='submit' value='switch'/>");
-			html("</form>");
-		}
-	} else
-		html_txt(ctx.cfg.root_title);
-	html("</td></tr>\n");
-
-	html("<tr><td class='sub'>");
-	if (ctx.repo) {
-		html_txt(ctx.repo->desc);
-		html("</td><td class='sub right'>");
-		html_txt(ctx.repo->owner);
-	} else {
-		if (ctx.cfg.root_desc)
-			html_txt(ctx.cfg.root_desc);
-	}
-	html("</td></tr></table>\n");
-}
-
-void cgit_print_pageheader(void)
-{
-	html("<div id='cgit'>");
-	if (!ctx.env.authenticated || !ctx.cfg.noheader)
-		print_header();
-
-	html("<table class='tabs'><tr><td>\n");
-	if (ctx.env.authenticated && ctx.repo) {
-		if (ctx.repo->readme.nr)
-			reporevlink("about", "about", NULL,
-				    hc("about"), ctx.qry.head, NULL,
-				    NULL);
-		cgit_summary_link("summary", NULL, hc("summary"),
-				  ctx.qry.head);
-		cgit_refs_link("refs", NULL, hc("refs"), ctx.qry.head,
-			       ctx.qry.sha1, NULL);
-		cgit_log_link("log", NULL, hc("log"), ctx.qry.head,
-			      NULL, ctx.qry.vpath, 0, NULL, NULL,
-			      ctx.qry.showmsg, ctx.qry.follow);
-		if (ctx.qry.page && !strcmp(ctx.qry.page, "blame"))
-			cgit_blame_link("blame", NULL, hc("blame"), ctx.qry.head,
-				        ctx.qry.sha1, ctx.qry.vpath);
-		else
-			cgit_tree_link("tree", NULL, hc("tree"), ctx.qry.head,
-				       ctx.qry.sha1, ctx.qry.vpath);
-		cgit_commit_link("commit", NULL, hc("commit"),
-				 ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath);
-		cgit_diff_link("diff", NULL, hc("diff"), ctx.qry.head,
-			       ctx.qry.sha1, ctx.qry.sha2, ctx.qry.vpath);
-		if (ctx.repo->max_stats)
-			cgit_stats_link("stats", NULL, hc("stats"),
-					ctx.qry.head, ctx.qry.vpath);
-		if (ctx.repo->homepage) {
-			html("<a href='");
-			html_attr(ctx.repo->homepage);
-			html("'>homepage</a>");
-		}
-		html("</td><td class='form'>");
-		html("<form class='right' method='get' action='");
-		if (ctx.cfg.virtual_root) {
-			char *fileurl = cgit_fileurl(ctx.qry.repo, "log",
-						   ctx.qry.vpath, NULL);
-			html_url_path(fileurl);
-			free(fileurl);
-		}
-		html("'>\n");
-		cgit_add_hidden_formfields(1, 0, "log");
-		html("<select name='qt'>\n");
-		html_option("grep", "log msg", ctx.qry.grep);
-		html_option("author", "author", ctx.qry.grep);
-		html_option("committer", "committer", ctx.qry.grep);
-		html_option("range", "range", ctx.qry.grep);
-		html("</select>\n");
-		html("<input class='txt' type='search' size='10' name='q' value='");
-		html_attr(ctx.qry.search);
-		html("'/>\n");
-		html("<input type='submit' value='search'/>\n");
-		html("</form>\n");
-	} else if (ctx.env.authenticated) {
-		char *currenturl = cgit_currenturl();
-		site_link(NULL, "index", NULL, hc("repolist"), NULL, NULL, 0, 1);
-		if (ctx.cfg.root_readme)
-			site_link("about", "about", NULL, hc("about"),
-				  NULL, NULL, 0, 1);
-		html("</td><td class='form'>");
-		html("<form method='get' action='");
-		html_attr(currenturl);
-		html("'>\n");
-		html("<input type='search' name='q' size='10' value='");
-		html_attr(ctx.qry.search);
-		html("'/>\n");
-		html("<input type='submit' value='search'/>\n");
-		html("</form>");
-		free(currenturl);
-	}
-	html("</td></tr></table>\n");
-	if (ctx.env.authenticated && ctx.repo && ctx.qry.vpath) {
-		html("<div class='path'>");
-		html("path: ");
-		cgit_print_path_crumbs(ctx.qry.vpath);
-		if (ctx.cfg.enable_follow_links && !strcmp(ctx.qry.page, "log")) {
-			html(" (");
-			ctx.qry.follow = !ctx.qry.follow;
-			cgit_self_link(ctx.qry.follow ? "follow" : "unfollow",
-					NULL, NULL);
-			ctx.qry.follow = !ctx.qry.follow;
-			html(")");
-		}
-		html("</div>");
-	}
-	html("<div class='content'>");
-}
-
-void cgit_print_filemode(unsigned short mode)
-{
-	if (S_ISDIR(mode))
-		html("d");
-	else if (S_ISLNK(mode))
-		html("l");
-	else if (S_ISGITLINK(mode))
-		html("m");
-	else
-		html("-");
-	html_fileperm(mode >> 6);
-	html_fileperm(mode >> 3);
-	html_fileperm(mode);
-}
-
-void cgit_compose_snapshot_prefix(struct strbuf *filename, const char *base,
-				  const char *ref)
-{
-	struct object_id oid;
-
-	/*
-	 * Prettify snapshot names by stripping leading "v" or "V" if the tag
-	 * name starts with {v,V}[0-9] and the prettify mapping is injective,
-	 * i.e. each stripped tag can be inverted without ambiguities.
-	 */
-	if (get_oid(fmt("refs/tags/%s", ref), &oid) == 0 &&
-	    (ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]) &&
-	    ((get_oid(fmt("refs/tags/%s", ref + 1), &oid) == 0) +
-	     (get_oid(fmt("refs/tags/v%s", ref + 1), &oid) == 0) +
-	     (get_oid(fmt("refs/tags/V%s", ref + 1), &oid) == 0) == 1))
-		ref++;
-
-	strbuf_addf(filename, "%s-%s", base, ref);
-}
-
-void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref,
-			       const char *separator)
-{
-	const struct cgit_snapshot_format *f;
-	struct strbuf filename = STRBUF_INIT;
-	const char *basename;
-	size_t prefixlen;
-
-	basename = cgit_snapshot_prefix(repo);
-	if (starts_with(ref, basename))
-		strbuf_addstr(&filename, ref);
-	else
-		cgit_compose_snapshot_prefix(&filename, basename, ref);
-
-	prefixlen = filename.len;
-	for (f = cgit_snapshot_formats; f->suffix; f++) {
-		if (!(repo->snapshots & cgit_snapshot_format_bit(f)))
-			continue;
-		strbuf_setlen(&filename, prefixlen);
-		strbuf_addstr(&filename, f->suffix);
-		cgit_snapshot_link(filename.buf, NULL, NULL, NULL, NULL,
-				   filename.buf);
-		if (cgit_snapshot_get_sig(ref, f)) {
-			strbuf_addstr(&filename, ".asc");
-			html(" (");
-			cgit_snapshot_link("sig", NULL, NULL, NULL, NULL,
-					   filename.buf);
-			html(")");
-		} else if (starts_with(f->suffix, ".tar") && cgit_snapshot_get_sig(ref, &cgit_snapshot_formats[0])) {
-			strbuf_setlen(&filename, strlen(filename.buf) - strlen(f->suffix));
-			strbuf_addstr(&filename, ".tar.asc");
-			html(" (");
-			cgit_snapshot_link("sig", NULL, NULL, NULL, NULL,
-					   filename.buf);
-			html(")");
-		}
-		html(separator);
-	}
-	strbuf_release(&filename);
-}
-
-void cgit_set_title_from_path(const char *path)
-{
-	struct strbuf sb = STRBUF_INIT;
-	const char *slash, *last_slash;
-
-	if (!path)
-		return;
-
-	for (last_slash = path + strlen(path); (slash = memrchr(path, '/', last_slash - path)) != NULL; last_slash = slash) {
-		strbuf_add(&sb, slash + 1, last_slash - slash - 1);
-		strbuf_addstr(&sb, " \xc2\xab ");
-	}
-	strbuf_add(&sb, path, last_slash - path);
-	strbuf_addf(&sb, " - %s", ctx.page.title);
-	ctx.page.title = strbuf_detach(&sb, NULL);
-}
diff --git a/ui-shared.h b/ui-shared.h
deleted file mode 100644
index 6964873a63..0000000000
--- a/ui-shared.h
+++ /dev/null
@@ -1,87 +0,0 @@
-#ifndef UI_SHARED_H
-#define UI_SHARED_H
-
-extern const char *cgit_httpscheme(void);
-extern char *cgit_hosturl(void);
-extern const char *cgit_rooturl(void);
-extern char *cgit_currenturl(void);
-extern char *cgit_currentfullurl(void);
-extern const char *cgit_loginurl(void);
-extern char *cgit_repourl(const char *reponame);
-extern char *cgit_fileurl(const char *reponame, const char *pagename,
-			  const char *filename, const char *query);
-extern char *cgit_pageurl(const char *reponame, const char *pagename,
-			  const char *query);
-
-extern void cgit_add_clone_urls(void (*fn)(const char *));
-
-extern void cgit_index_link(const char *name, const char *title,
-			    const char *class, const char *pattern, const char *sort, int ofs, int always_root);
-extern void cgit_summary_link(const char *name, const char *title,
-			      const char *class, const char *head);
-extern void cgit_tag_link(const char *name, const char *title,
-			  const char *class, const char *tag);
-extern void cgit_tree_link(const char *name, const char *title,
-			   const char *class, const char *head,
-			   const char *rev, const char *path);
-extern void cgit_plain_link(const char *name, const char *title,
-			    const char *class, const char *head,
-			    const char *rev, const char *path);
-extern void cgit_blame_link(const char *name, const char *title,
-			    const char *class, const char *head,
-			    const char *rev, const char *path);
-extern void cgit_log_link(const char *name, const char *title,
-			  const char *class, const char *head, const char *rev,
-			  const char *path, int ofs, const char *grep,
-			  const char *pattern, int showmsg, int follow);
-extern void cgit_commit_link(const char *name, const char *title,
-			     const char *class, const char *head,
-			     const char *rev, const char *path);
-extern void cgit_patch_link(const char *name, const char *title,
-			    const char *class, const char *head,
-			    const char *rev, const char *path);
-extern void cgit_refs_link(const char *name, const char *title,
-			   const char *class, const char *head,
-			   const char *rev, const char *path);
-extern void cgit_snapshot_link(const char *name, const char *title,
-			       const char *class, const char *head,
-			       const char *rev, const char *archivename);
-extern void cgit_diff_link(const char *name, const char *title,
-			   const char *class, const char *head,
-			   const char *new_rev, const char *old_rev,
-			   const char *path);
-extern void cgit_stats_link(const char *name, const char *title,
-			    const char *class, const char *head,
-			    const char *path);
-extern void cgit_object_link(struct object *obj);
-
-extern void cgit_submodule_link(const char *class, char *path,
-				const char *rev);
-
-extern void cgit_print_layout_start(void);
-extern void cgit_print_layout_end(void);
-
-__attribute__((format (printf,1,2)))
-extern void cgit_print_error(const char *fmt, ...);
-__attribute__((format (printf,1,0)))
-extern void cgit_vprint_error(const char *fmt, va_list ap);
-extern const struct date_mode *cgit_date_mode(enum date_mode_type type);
-extern void cgit_print_age(time_t t, int tz, time_t max_relative);
-extern void cgit_print_http_headers(void);
-extern void cgit_redirect(const char *url, bool permanent);
-extern void cgit_print_docstart(void);
-extern void cgit_print_docend(void);
-__attribute__((format (printf,3,4)))
-extern void cgit_print_error_page(int code, const char *msg, const char *fmt, ...);
-extern void cgit_print_pageheader(void);
-extern void cgit_print_filemode(unsigned short mode);
-extern void cgit_compose_snapshot_prefix(struct strbuf *filename,
-					 const char *base, const char *ref);
-extern void cgit_print_snapshot_links(const struct cgit_repo *repo,
-				      const char *ref, const char *separator);
-extern const char *cgit_snapshot_prefix(const struct cgit_repo *repo);
-extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
-				       const char *page);
-
-extern void cgit_set_title_from_path(const char *path);
-#endif /* UI_SHARED_H */
diff --git a/ui-snapshot.c b/ui-snapshot.c
deleted file mode 100644
index 9461d51a59..0000000000
--- a/ui-snapshot.c
+++ /dev/null
@@ -1,302 +0,0 @@
-/* ui-snapshot.c: generate snapshot of a commit
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-snapshot.h"
-#include "html.h"
-#include "ui-shared.h"
-
-static int write_archive_type(const char *format, const char *hex, const char *prefix)
-{
-	struct argv_array argv = ARGV_ARRAY_INIT;
-	const char **nargv;
-	int result;
-	argv_array_push(&argv, "snapshot");
-	argv_array_push(&argv, format);
-	if (prefix) {
-		struct strbuf buf = STRBUF_INIT;
-		strbuf_addstr(&buf, prefix);
-		strbuf_addch(&buf, '/');
-		argv_array_push(&argv, "--prefix");
-		argv_array_push(&argv, buf.buf);
-		strbuf_release(&buf);
-	}
-	argv_array_push(&argv, hex);
-	/*
-	 * Now we need to copy the pointers to arguments into a new
-	 * structure because write_archive will rearrange its arguments
-	 * which may result in duplicated/missing entries causing leaks
-	 * or double-frees in argv_array_clear.
-	 */
-	nargv = xmalloc(sizeof(char *) * (argv.argc + 1));
-	/* argv_array guarantees a trailing NULL entry. */
-	memcpy(nargv, argv.argv, sizeof(char *) * (argv.argc + 1));
-
-	result = write_archive(argv.argc, nargv, NULL, the_repository, NULL, 0);
-	argv_array_clear(&argv);
-	free(nargv);
-	return result;
-}
-
-static int write_tar_archive(const char *hex, const char *prefix)
-{
-	return write_archive_type("--format=tar", hex, prefix);
-}
-
-static int write_zip_archive(const char *hex, const char *prefix)
-{
-	return write_archive_type("--format=zip", hex, prefix);
-}
-
-static int write_compressed_tar_archive(const char *hex,
-					const char *prefix,
-					char *filter_argv[])
-{
-	int rv;
-	struct cgit_exec_filter f;
-	cgit_exec_filter_init(&f, filter_argv[0], filter_argv);
-
-	cgit_open_filter(&f.base);
-	rv = write_tar_archive(hex, prefix);
-	cgit_close_filter(&f.base);
-	return rv;
-}
-
-static int write_tar_gzip_archive(const char *hex, const char *prefix)
-{
-	char *argv[] = { "gzip", "-n", NULL };
-	return write_compressed_tar_archive(hex, prefix, argv);
-}
-
-static int write_tar_bzip2_archive(const char *hex, const char *prefix)
-{
-	char *argv[] = { "bzip2", NULL };
-	return write_compressed_tar_archive(hex, prefix, argv);
-}
-
-static int write_tar_xz_archive(const char *hex, const char *prefix)
-{
-	char *argv[] = { "xz", NULL };
-	return write_compressed_tar_archive(hex, prefix, argv);
-}
-
-const struct cgit_snapshot_format cgit_snapshot_formats[] = {
-	/* .tar must remain the 0 index */
-	{ ".tar",	"application/x-tar",	write_tar_archive	},
-	{ ".tar.gz",	"application/x-gzip",	write_tar_gzip_archive	},
-	{ ".tar.bz2",	"application/x-bzip2",	write_tar_bzip2_archive	},
-	{ ".tar.xz",	"application/x-xz",	write_tar_xz_archive	},
-	{ ".zip",	"application/x-zip",	write_zip_archive	},
-	{ NULL }
-};
-
-static struct notes_tree snapshot_sig_notes[ARRAY_SIZE(cgit_snapshot_formats)];
-
-const struct object_id *cgit_snapshot_get_sig(const char *ref,
-					      const struct cgit_snapshot_format *f)
-{
-	struct notes_tree *tree;
-	struct object_id oid;
-
-	if (get_oid(ref, &oid))
-		return NULL;
-
-	tree = &snapshot_sig_notes[f - &cgit_snapshot_formats[0]];
-	if (!tree->initialized) {
-		struct strbuf notes_ref = STRBUF_INIT;
-
-		strbuf_addf(&notes_ref, "refs/notes/signatures/%s",
-			    f->suffix + 1);
-
-		init_notes(tree, notes_ref.buf, combine_notes_ignore, 0);
-		strbuf_release(&notes_ref);
-	}
-
-	return get_note(tree, &oid);
-}
-
-static const struct cgit_snapshot_format *get_format(const char *filename)
-{
-	const struct cgit_snapshot_format *fmt;
-
-	for (fmt = cgit_snapshot_formats; fmt->suffix; fmt++) {
-		if (ends_with(filename, fmt->suffix))
-			return fmt;
-	}
-	return NULL;
-}
-
-const unsigned cgit_snapshot_format_bit(const struct cgit_snapshot_format *f)
-{
-	return BIT(f - &cgit_snapshot_formats[0]);
-}
-
-static int make_snapshot(const struct cgit_snapshot_format *format,
-			 const char *hex, const char *prefix,
-			 const char *filename)
-{
-	struct object_id oid;
-
-	if (get_oid(hex, &oid)) {
-		cgit_print_error_page(404, "Not found",
-				"Bad object id: %s", hex);
-		return 1;
-	}
-	if (!lookup_commit_reference(the_repository, &oid)) {
-		cgit_print_error_page(400, "Bad request",
-				"Not a commit reference: %s", hex);
-		return 1;
-	}
-	ctx.page.etag = oid_to_hex(&oid);
-	ctx.page.mimetype = xstrdup(format->mimetype);
-	ctx.page.filename = xstrdup(filename);
-	cgit_print_http_headers();
-	init_archivers();
-	format->write_func(hex, prefix);
-	return 0;
-}
-
-static int write_sig(const struct cgit_snapshot_format *format,
-		     const char *hex, const char *archive,
-		     const char *filename)
-{
-	const struct object_id *note = cgit_snapshot_get_sig(hex, format);
-	enum object_type type;
-	unsigned long size;
-	char *buf;
-
-	if (!note) {
-		cgit_print_error_page(404, "Not found",
-				"No signature for %s", archive);
-		return 0;
-	}
-
-	buf = read_object_file(note, &type, &size);
-	if (!buf) {
-		cgit_print_error_page(404, "Not found", "Not found");
-		return 0;
-	}
-
-	html("X-Content-Type-Options: nosniff\n");
-	html("Content-Security-Policy: default-src 'none'\n");
-	ctx.page.etag = oid_to_hex(note);
-	ctx.page.mimetype = xstrdup("application/pgp-signature");
-	ctx.page.filename = xstrdup(filename);
-	cgit_print_http_headers();
-
-	html_raw(buf, size);
-	free(buf);
-	return 0;
-}
-
-/* Try to guess the requested revision from the requested snapshot name.
- * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become
- * "cgit-0.7.2". If this is a valid commit object name we've got a winner.
- * Otherwise, if the snapshot name has a prefix matching the result from
- * repo_basename(), we strip the basename and any following '-' and '_'
- * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once
- * more. If this still isn't a valid commit object name, we check if pre-
- * pending a 'v' or a 'V' to the remaining snapshot name ("0.7.2" ->
- * "v0.7.2") gives us something valid.
- */
-static const char *get_ref_from_filename(const struct cgit_repo *repo,
-					 const char *filename,
-					 const struct cgit_snapshot_format *format)
-{
-	const char *reponame;
-	struct object_id oid;
-	struct strbuf snapshot = STRBUF_INIT;
-	int result = 1;
-
-	strbuf_addstr(&snapshot, filename);
-	strbuf_setlen(&snapshot, snapshot.len - strlen(format->suffix));
-
-	if (get_oid(snapshot.buf, &oid) == 0)
-		goto out;
-
-	reponame = cgit_snapshot_prefix(repo);
-	if (starts_with(snapshot.buf, reponame)) {
-		const char *new_start = snapshot.buf;
-		new_start += strlen(reponame);
-		while (new_start && (*new_start == '-' || *new_start == '_'))
-			new_start++;
-		strbuf_splice(&snapshot, 0, new_start - snapshot.buf, "", 0);
-	}
-
-	if (get_oid(snapshot.buf, &oid) == 0)
-		goto out;
-
-	strbuf_insert(&snapshot, 0, "v", 1);
-	if (get_oid(snapshot.buf, &oid) == 0)
-		goto out;
-
-	strbuf_splice(&snapshot, 0, 1, "V", 1);
-	if (get_oid(snapshot.buf, &oid) == 0)
-		goto out;
-
-	result = 0;
-	strbuf_release(&snapshot);
-
-out:
-	return result ? strbuf_detach(&snapshot, NULL) : NULL;
-}
-
-void cgit_print_snapshot(const char *head, const char *hex,
-			 const char *filename, int dwim)
-{
-	const struct cgit_snapshot_format* f;
-	const char *sig_filename = NULL;
-	char *adj_filename = NULL;
-	char *prefix = NULL;
-
-	if (!filename) {
-		cgit_print_error_page(400, "Bad request",
-				"No snapshot name specified");
-		return;
-	}
-
-	if (ends_with(filename, ".asc")) {
-		sig_filename = filename;
-
-		/* Strip ".asc" from filename for common format processing */
-		adj_filename = xstrdup(filename);
-		adj_filename[strlen(adj_filename) - 4] = '\0';
-		filename = adj_filename;
-	}
-
-	f = get_format(filename);
-	if (!f || (!sig_filename && !(ctx.repo->snapshots & cgit_snapshot_format_bit(f)))) {
-		cgit_print_error_page(400, "Bad request",
-				"Unsupported snapshot format: %s", filename);
-		return;
-	}
-
-	if (!hex && dwim) {
-		hex = get_ref_from_filename(ctx.repo, filename, f);
-		if (hex == NULL) {
-			cgit_print_error_page(404, "Not found", "Not found");
-			return;
-		}
-		prefix = xstrdup(filename);
-		prefix[strlen(filename) - strlen(f->suffix)] = '\0';
-	}
-
-	if (!hex)
-		hex = head;
-
-	if (!prefix)
-		prefix = xstrdup(cgit_snapshot_prefix(ctx.repo));
-
-	if (sig_filename)
-		write_sig(f, hex, filename, sig_filename);
-	else
-		make_snapshot(f, hex, prefix, filename);
-
-	free(prefix);
-	free(adj_filename);
-}
diff --git a/ui-snapshot.h b/ui-snapshot.h
deleted file mode 100644
index a8deec3697..0000000000
--- a/ui-snapshot.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#ifndef UI_SNAPSHOT_H
-#define UI_SNAPSHOT_H
-
-extern void cgit_print_snapshot(const char *head, const char *hex,
-				const char *filename, int dwim);
-
-#endif /* UI_SNAPSHOT_H */
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
deleted file mode 100644
index af8bc9e065..0000000000
--- a/ui-ssdiff.c
+++ /dev/null
@@ -1,420 +0,0 @@
-#include "cgit.h"
-#include "ui-ssdiff.h"
-#include "html.h"
-#include "ui-shared.h"
-#include "ui-diff.h"
-
-extern int use_ssdiff;
-
-static int current_old_line, current_new_line;
-static int **L = NULL;
-
-struct deferred_lines {
-	int line_no;
-	char *line;
-	struct deferred_lines *next;
-};
-
-static struct deferred_lines *deferred_old, *deferred_old_last;
-static struct deferred_lines *deferred_new, *deferred_new_last;
-
-static void create_or_reset_lcs_table(void)
-{
-	int i;
-
-	if (L != NULL) {
-		memset(*L, 0, sizeof(int) * MAX_SSDIFF_SIZE);
-		return;
-	}
-
-	// xcalloc will die if we ran out of memory;
-	// not very helpful for debugging
-	L = (int**)xcalloc(MAX_SSDIFF_M, sizeof(int *));
-	*L = (int*)xcalloc(MAX_SSDIFF_SIZE, sizeof(int));
-
-	for (i = 1; i < MAX_SSDIFF_M; i++) {
-		L[i] = *L + i * MAX_SSDIFF_N;
-	}
-}
-
-static char *longest_common_subsequence(char *A, char *B)
-{
-	int i, j, ri;
-	int m = strlen(A);
-	int n = strlen(B);
-	int tmp1, tmp2;
-	int lcs_length;
-	char *result;
-
-	// We bail if the lines are too long
-	if (m >= MAX_SSDIFF_M || n >= MAX_SSDIFF_N)
-		return NULL;
-
-	create_or_reset_lcs_table();
-
-	for (i = m; i >= 0; i--) {
-		for (j = n; j >= 0; j--) {
-			if (A[i] == '\0' || B[j] == '\0') {
-				L[i][j] = 0;
-			} else if (A[i] == B[j]) {
-				L[i][j] = 1 + L[i + 1][j + 1];
-			} else {
-				tmp1 = L[i + 1][j];
-				tmp2 = L[i][j + 1];
-				L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
-			}
-		}
-	}
-
-	lcs_length = L[0][0];
-	result = xmalloc(lcs_length + 2);
-	memset(result, 0, sizeof(*result) * (lcs_length + 2));
-
-	ri = 0;
-	i = 0;
-	j = 0;
-	while (i < m && j < n) {
-		if (A[i] == B[j]) {
-			result[ri] = A[i];
-			ri += 1;
-			i += 1;
-			j += 1;
-		} else if (L[i + 1][j] >= L[i][j + 1]) {
-			i += 1;
-		} else {
-			j += 1;
-		}
-	}
-
-	return result;
-}
-
-static int line_from_hunk(char *line, char type)
-{
-	char *buf1, *buf2;
-	int len, res;
-
-	buf1 = strchr(line, type);
-	if (buf1 == NULL)
-		return 0;
-	buf1 += 1;
-	buf2 = strchr(buf1, ',');
-	if (buf2 == NULL)
-		return 0;
-	len = buf2 - buf1;
-	buf2 = xmalloc(len + 1);
-	strlcpy(buf2, buf1, len + 1);
-	res = atoi(buf2);
-	free(buf2);
-	return res;
-}
-
-static char *replace_tabs(char *line)
-{
-	char *prev_buf = line;
-	char *cur_buf;
-	size_t linelen = strlen(line);
-	int n_tabs = 0;
-	int i;
-	char *result;
-	size_t result_len;
-
-	if (linelen == 0) {
-		result = xmalloc(1);
-		result[0] = '\0';
-		return result;
-	}
-
-	for (i = 0; i < linelen; i++) {
-		if (line[i] == '\t')
-			n_tabs += 1;
-	}
-	result_len = linelen + n_tabs * 8;
-	result = xmalloc(result_len + 1);
-	result[0] = '\0';
-
-	for (;;) {
-		cur_buf = strchr(prev_buf, '\t');
-		if (!cur_buf) {
-			linelen = strlen(result);
-			strlcpy(&result[linelen], prev_buf, result_len - linelen + 1);
-			break;
-		} else {
-			linelen = strlen(result);
-			strlcpy(&result[linelen], prev_buf, cur_buf - prev_buf + 1);
-			linelen = strlen(result);
-			memset(&result[linelen], ' ', 8 - (linelen % 8));
-			result[linelen + 8 - (linelen % 8)] = '\0';
-		}
-		prev_buf = cur_buf + 1;
-	}
-	return result;
-}
-
-static int calc_deferred_lines(struct deferred_lines *start)
-{
-	struct deferred_lines *item = start;
-	int result = 0;
-	while (item) {
-		result += 1;
-		item = item->next;
-	}
-	return result;
-}
-
-static void deferred_old_add(char *line, int line_no)
-{
-	struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
-	item->line = xstrdup(line);
-	item->line_no = line_no;
-	item->next = NULL;
-	if (deferred_old) {
-		deferred_old_last->next = item;
-		deferred_old_last = item;
-	} else {
-		deferred_old = deferred_old_last = item;
-	}
-}
-
-static void deferred_new_add(char *line, int line_no)
-{
-	struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
-	item->line = xstrdup(line);
-	item->line_no = line_no;
-	item->next = NULL;
-	if (deferred_new) {
-		deferred_new_last->next = item;
-		deferred_new_last = item;
-	} else {
-		deferred_new = deferred_new_last = item;
-	}
-}
-
-static void print_part_with_lcs(char *class, char *line, char *lcs)
-{
-	int line_len = strlen(line);
-	int i, j;
-	char c[2] = " ";
-	int same = 1;
-
-	j = 0;
-	for (i = 0; i < line_len; i++) {
-		c[0] = line[i];
-		if (same) {
-			if (line[i] == lcs[j])
-				j += 1;
-			else {
-				same = 0;
-				htmlf("<span class='%s'>", class);
-			}
-		} else if (line[i] == lcs[j]) {
-			same = 1;
-			html("</span>");
-			j += 1;
-		}
-		html_txt(c);
-	}
-	if (!same)
-		html("</span>");
-}
-
-static void print_ssdiff_line(char *class,
-			      int old_line_no,
-			      char *old_line,
-			      int new_line_no,
-			      char *new_line, int individual_chars)
-{
-	char *lcs = NULL;
-
-	if (old_line)
-		old_line = replace_tabs(old_line + 1);
-	if (new_line)
-		new_line = replace_tabs(new_line + 1);
-	if (individual_chars && old_line && new_line)
-		lcs = longest_common_subsequence(old_line, new_line);
-	html("<tr>\n");
-	if (old_line_no > 0) {
-		struct diff_filespec *old_file = cgit_get_current_old_file();
-		char *lineno_str = fmt("n%d", old_line_no);
-		char *id_str = fmt("id=%s#%s", is_null_oid(&old_file->oid)?"HEAD":oid_to_hex(old_rev_oid), lineno_str);
-		char *fileurl = cgit_fileurl(ctx.repo->url, "tree", old_file->path, id_str);
-		html("<td class='lineno'><a href='");
-		html(fileurl);
-		htmlf("'>%s</a>", lineno_str + 1);
-		html("</td>");
-		htmlf("<td class='%s'>", class);
-		free(fileurl);
-	} else if (old_line)
-		htmlf("<td class='lineno'></td><td class='%s'>", class);
-	else
-		htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
-	if (old_line) {
-		if (lcs)
-			print_part_with_lcs("del", old_line, lcs);
-		else
-			html_txt(old_line);
-	}
-
-	html("</td>\n");
-	if (new_line_no > 0) {
-		struct diff_filespec *new_file = cgit_get_current_new_file();
-		char *lineno_str = fmt("n%d", new_line_no);
-		char *id_str = fmt("id=%s#%s", is_null_oid(&new_file->oid)?"HEAD":oid_to_hex(new_rev_oid), lineno_str);
-		char *fileurl = cgit_fileurl(ctx.repo->url, "tree", new_file->path, id_str);
-		html("<td class='lineno'><a href='");
-		html(fileurl);
-		htmlf("'>%s</a>", lineno_str + 1);
-		html("</td>");
-		htmlf("<td class='%s'>", class);
-		free(fileurl);
-	} else if (new_line)
-		htmlf("<td class='lineno'></td><td class='%s'>", class);
-	else
-		htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
-	if (new_line) {
-		if (lcs)
-			print_part_with_lcs("add", new_line, lcs);
-		else
-			html_txt(new_line);
-	}
-
-	html("</td></tr>");
-	if (lcs)
-		free(lcs);
-	if (new_line)
-		free(new_line);
-	if (old_line)
-		free(old_line);
-}
-
-static void print_deferred_old_lines(void)
-{
-	struct deferred_lines *iter_old, *tmp;
-	iter_old = deferred_old;
-	while (iter_old) {
-		print_ssdiff_line("del", iter_old->line_no,
-				  iter_old->line, -1, NULL, 0);
-		tmp = iter_old->next;
-		free(iter_old);
-		iter_old = tmp;
-	}
-}
-
-static void print_deferred_new_lines(void)
-{
-	struct deferred_lines *iter_new, *tmp;
-	iter_new = deferred_new;
-	while (iter_new) {
-		print_ssdiff_line("add", -1, NULL,
-				  iter_new->line_no, iter_new->line, 0);
-		tmp = iter_new->next;
-		free(iter_new);
-		iter_new = tmp;
-	}
-}
-
-static void print_deferred_changed_lines(void)
-{
-	struct deferred_lines *iter_old, *iter_new, *tmp;
-	int n_old_lines = calc_deferred_lines(deferred_old);
-	int n_new_lines = calc_deferred_lines(deferred_new);
-	int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
-
-	iter_old = deferred_old;
-	iter_new = deferred_new;
-	while (iter_old || iter_new) {
-		if (iter_old && iter_new)
-			print_ssdiff_line("changed", iter_old->line_no,
-					  iter_old->line,
-					  iter_new->line_no, iter_new->line,
-					  individual_chars);
-		else if (iter_old)
-			print_ssdiff_line("changed", iter_old->line_no,
-					  iter_old->line, -1, NULL, 0);
-		else if (iter_new)
-			print_ssdiff_line("changed", -1, NULL,
-					  iter_new->line_no, iter_new->line, 0);
-		if (iter_old) {
-			tmp = iter_old->next;
-			free(iter_old);
-			iter_old = tmp;
-		}
-
-		if (iter_new) {
-			tmp = iter_new->next;
-			free(iter_new);
-			iter_new = tmp;
-		}
-	}
-}
-
-void cgit_ssdiff_print_deferred_lines(void)
-{
-	if (!deferred_old && !deferred_new)
-		return;
-	if (deferred_old && !deferred_new)
-		print_deferred_old_lines();
-	else if (!deferred_old && deferred_new)
-		print_deferred_new_lines();
-	else
-		print_deferred_changed_lines();
-	deferred_old = deferred_old_last = NULL;
-	deferred_new = deferred_new_last = NULL;
-}
-
-/*
- * print a single line returned from xdiff
- */
-void cgit_ssdiff_line_cb(char *line, int len)
-{
-	char c = line[len - 1];
-	line[len - 1] = '\0';
-	if (line[0] == '@') {
-		current_old_line = line_from_hunk(line, '-');
-		current_new_line = line_from_hunk(line, '+');
-	}
-
-	if (line[0] == ' ') {
-		if (deferred_old || deferred_new)
-			cgit_ssdiff_print_deferred_lines();
-		print_ssdiff_line("ctx", current_old_line, line,
-				  current_new_line, line, 0);
-		current_old_line += 1;
-		current_new_line += 1;
-	} else if (line[0] == '+') {
-		deferred_new_add(line, current_new_line);
-		current_new_line += 1;
-	} else if (line[0] == '-') {
-		deferred_old_add(line, current_old_line);
-		current_old_line += 1;
-	} else if (line[0] == '@') {
-		html("<tr><td colspan='4' class='hunk'>");
-		html_txt(line);
-		html("</td></tr>");
-	} else {
-		html("<tr><td colspan='4' class='ctx'>");
-		html_txt(line);
-		html("</td></tr>");
-	}
-	line[len - 1] = c;
-}
-
-void cgit_ssdiff_header_begin(void)
-{
-	current_old_line = -1;
-	current_new_line = -1;
-	html("<tr><td class='space' colspan='4'><div></div></td></tr>");
-	html("<tr><td class='head' colspan='4'>");
-}
-
-void cgit_ssdiff_header_end(void)
-{
-	html("</td></tr>");
-}
-
-void cgit_ssdiff_footer(void)
-{
-	if (deferred_old || deferred_new)
-		cgit_ssdiff_print_deferred_lines();
-	html("<tr><td class='foot' colspan='4'></td></tr>");
-}
diff --git a/ui-ssdiff.h b/ui-ssdiff.h
deleted file mode 100644
index 11f2714407..0000000000
--- a/ui-ssdiff.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#ifndef UI_SSDIFF_H
-#define UI_SSDIFF_H
-
-/*
- * ssdiff line limits
- */
-#ifndef MAX_SSDIFF_M
-#define MAX_SSDIFF_M 128
-#endif
-
-#ifndef MAX_SSDIFF_N
-#define MAX_SSDIFF_N 128
-#endif
-#define MAX_SSDIFF_SIZE ((MAX_SSDIFF_M) * (MAX_SSDIFF_N))
-
-extern void cgit_ssdiff_print_deferred_lines(void);
-
-extern void cgit_ssdiff_line_cb(char *line, int len);
-
-extern void cgit_ssdiff_header_begin(void);
-extern void cgit_ssdiff_header_end(void);
-
-extern void cgit_ssdiff_footer(void);
-
-#endif /* UI_SSDIFF_H */
diff --git a/ui-stats.c b/ui-stats.c
deleted file mode 100644
index 7272a61a2f..0000000000
--- a/ui-stats.c
+++ /dev/null
@@ -1,426 +0,0 @@
-#include "cgit.h"
-#include "ui-stats.h"
-#include "html.h"
-#include "ui-shared.h"
-
-struct authorstat {
-	long total;
-	struct string_list list;
-};
-
-#define DAY_SECS (60 * 60 * 24)
-#define WEEK_SECS (DAY_SECS * 7)
-
-static void trunc_week(struct tm *tm)
-{
-	time_t t = timegm(tm);
-	t -= ((tm->tm_wday + 6) % 7) * DAY_SECS;
-	gmtime_r(&t, tm);
-}
-
-static void dec_week(struct tm *tm)
-{
-	time_t t = timegm(tm);
-	t -= WEEK_SECS;
-	gmtime_r(&t, tm);
-}
-
-static void inc_week(struct tm *tm)
-{
-	time_t t = timegm(tm);
-	t += WEEK_SECS;
-	gmtime_r(&t, tm);
-}
-
-static char *pretty_week(struct tm *tm)
-{
-	static char buf[10];
-
-	strftime(buf, sizeof(buf), "W%V %G", tm);
-	return buf;
-}
-
-static void trunc_month(struct tm *tm)
-{
-	tm->tm_mday = 1;
-}
-
-static void dec_month(struct tm *tm)
-{
-	tm->tm_mon--;
-	if (tm->tm_mon < 0) {
-		tm->tm_year--;
-		tm->tm_mon = 11;
-	}
-}
-
-static void inc_month(struct tm *tm)
-{
-	tm->tm_mon++;
-	if (tm->tm_mon > 11) {
-		tm->tm_year++;
-		tm->tm_mon = 0;
-	}
-}
-
-static char *pretty_month(struct tm *tm)
-{
-	static const char *months[] = {
-		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
-		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-	};
-	return fmt("%s %d", months[tm->tm_mon], tm->tm_year + 1900);
-}
-
-static void trunc_quarter(struct tm *tm)
-{
-	trunc_month(tm);
-	while (tm->tm_mon % 3 != 0)
-		dec_month(tm);
-}
-
-static void dec_quarter(struct tm *tm)
-{
-	dec_month(tm);
-	dec_month(tm);
-	dec_month(tm);
-}
-
-static void inc_quarter(struct tm *tm)
-{
-	inc_month(tm);
-	inc_month(tm);
-	inc_month(tm);
-}
-
-static char *pretty_quarter(struct tm *tm)
-{
-	return fmt("Q%d %d", tm->tm_mon / 3 + 1, tm->tm_year + 1900);
-}
-
-static void trunc_year(struct tm *tm)
-{
-	trunc_month(tm);
-	tm->tm_mon = 0;
-}
-
-static void dec_year(struct tm *tm)
-{
-	tm->tm_year--;
-}
-
-static void inc_year(struct tm *tm)
-{
-	tm->tm_year++;
-}
-
-static char *pretty_year(struct tm *tm)
-{
-	return fmt("%d", tm->tm_year + 1900);
-}
-
-static const struct cgit_period periods[] = {
-	{'w', "week", 12, 4, trunc_week, dec_week, inc_week, pretty_week},
-	{'m', "month", 12, 4, trunc_month, dec_month, inc_month, pretty_month},
-	{'q', "quarter", 12, 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter},
-	{'y', "year", 12, 4, trunc_year, dec_year, inc_year, pretty_year},
-};
-
-/* Given a period code or name, return a period index (1, 2, 3 or 4)
- * and update the period pointer to the correcsponding struct.
- * If no matching code is found, return 0.
- */
-int cgit_find_stats_period(const char *expr, const struct cgit_period **period)
-{
-	int i;
-	char code = '\0';
-
-	if (!expr)
-		return 0;
-
-	if (strlen(expr) == 1)
-		code = expr[0];
-
-	for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++)
-		if (periods[i].code == code || !strcmp(periods[i].name, expr)) {
-			if (period)
-				*period = &periods[i];
-			return i + 1;
-		}
-	return 0;
-}
-
-const char *cgit_find_stats_periodname(int idx)
-{
-	if (idx > 0 && idx < 4)
-		return periods[idx - 1].name;
-	else
-		return "";
-}
-
-static void add_commit(struct string_list *authors, struct commit *commit,
-	const struct cgit_period *period)
-{
-	struct commitinfo *info;
-	struct string_list_item *author, *item;
-	struct authorstat *authorstat;
-	struct string_list *items;
-	char *tmp;
-	struct tm *date;
-	time_t t;
-	uintptr_t *counter;
-
-	info = cgit_parse_commit(commit);
-	tmp = xstrdup(info->author);
-	author = string_list_insert(authors, tmp);
-	if (!author->util)
-		author->util = xcalloc(1, sizeof(struct authorstat));
-	else
-		free(tmp);
-	authorstat = author->util;
-	items = &authorstat->list;
-	t = info->committer_date;
-	date = gmtime(&t);
-	period->trunc(date);
-	tmp = xstrdup(period->pretty(date));
-	item = string_list_insert(items, tmp);
-	counter = (uintptr_t *)&item->util;
-	if (*counter)
-		free(tmp);
-	(*counter)++;
-
-	authorstat->total++;
-	cgit_free_commitinfo(info);
-}
-
-static int cmp_total_commits(const void *a1, const void *a2)
-{
-	const struct string_list_item *i1 = a1;
-	const struct string_list_item *i2 = a2;
-	const struct authorstat *auth1 = i1->util;
-	const struct authorstat *auth2 = i2->util;
-
-	return auth2->total - auth1->total;
-}
-
-/* Walk the commit DAG and collect number of commits per author per
- * timeperiod into a nested string_list collection.
- */
-static struct string_list collect_stats(const struct cgit_period *period)
-{
-	struct string_list authors;
-	struct rev_info rev;
-	struct commit *commit;
-	const char *argv[] = {NULL, ctx.qry.head, NULL, NULL, NULL, NULL};
-	int argc = 3;
-	time_t now;
-	long i;
-	struct tm *tm;
-	char tmp[11];
-
-	time(&now);
-	tm = gmtime(&now);
-	period->trunc(tm);
-	for (i = 1; i < period->count; i++)
-		period->dec(tm);
-	strftime(tmp, sizeof(tmp), "%Y-%m-%d", tm);
-	argv[2] = xstrdup(fmt("--since=%s", tmp));
-	if (ctx.qry.path) {
-		argv[3] = "--";
-		argv[4] = ctx.qry.path;
-		argc += 2;
-	}
-	init_revisions(&rev, NULL);
-	rev.abbrev = DEFAULT_ABBREV;
-	rev.commit_format = CMIT_FMT_DEFAULT;
-	rev.max_parents = 1;
-	rev.verbose_header = 1;
-	rev.show_root_diff = 0;
-	setup_revisions(argc, argv, &rev, NULL);
-	prepare_revision_walk(&rev);
-	memset(&authors, 0, sizeof(authors));
-	while ((commit = get_revision(&rev)) != NULL) {
-		add_commit(&authors, commit, period);
-		free_commit_buffer(the_repository->parsed_objects, commit);
-		free_commit_list(commit->parents);
-		commit->parents = NULL;
-	}
-	return authors;
-}
-
-static void print_combined_authorrow(struct string_list *authors, int from,
-				     int to, const char *name,
-				     const char *leftclass,
-				     const char *centerclass,
-				     const char *rightclass,
-				     const struct cgit_period *period)
-{
-	struct string_list_item *author;
-	struct authorstat *authorstat;
-	struct string_list *items;
-	struct string_list_item *date;
-	time_t now;
-	long i, j, total, subtotal;
-	struct tm *tm;
-	char *tmp;
-
-	time(&now);
-	tm = gmtime(&now);
-	period->trunc(tm);
-	for (i = 1; i < period->count; i++)
-		period->dec(tm);
-
-	total = 0;
-	htmlf("<tr><td class='%s'>%s</td>", leftclass,
-		fmt(name, to - from + 1));
-	for (j = 0; j < period->count; j++) {
-		tmp = period->pretty(tm);
-		period->inc(tm);
-		subtotal = 0;
-		for (i = from; i <= to; i++) {
-			author = &authors->items[i];
-			authorstat = author->util;
-			items = &authorstat->list;
-			date = string_list_lookup(items, tmp);
-			if (date)
-				subtotal += (uintptr_t)date->util;
-		}
-		htmlf("<td class='%s'>%ld</td>", centerclass, subtotal);
-		total += subtotal;
-	}
-	htmlf("<td class='%s'>%ld</td></tr>", rightclass, total);
-}
-
-static void print_authors(struct string_list *authors, int top,
-			  const struct cgit_period *period)
-{
-	struct string_list_item *author;
-	struct authorstat *authorstat;
-	struct string_list *items;
-	struct string_list_item *date;
-	time_t now;
-	long i, j, total;
-	struct tm *tm;
-	char *tmp;
-
-	time(&now);
-	tm = gmtime(&now);
-	period->trunc(tm);
-	for (i = 1; i < period->count; i++)
-		period->dec(tm);
-
-	html("<table class='stats'><tr><th>Author</th>");
-	for (j = 0; j < period->count; j++) {
-		tmp = period->pretty(tm);
-		htmlf("<th>%s</th>", tmp);
-		period->inc(tm);
-	}
-	html("<th>Total</th></tr>\n");
-
-	if (top <= 0 || top > authors->nr)
-		top = authors->nr;
-
-	for (i = 0; i < top; i++) {
-		author = &authors->items[i];
-		html("<tr><td class='left'>");
-		html_txt(author->string);
-		html("</td>");
-		authorstat = author->util;
-		items = &authorstat->list;
-		total = 0;
-		for (j = 0; j < period->count; j++)
-			period->dec(tm);
-		for (j = 0; j < period->count; j++) {
-			tmp = period->pretty(tm);
-			period->inc(tm);
-			date = string_list_lookup(items, tmp);
-			if (!date)
-				html("<td>0</td>");
-			else {
-				htmlf("<td>%lu</td>", (uintptr_t)date->util);
-				total += (uintptr_t)date->util;
-			}
-		}
-		htmlf("<td class='sum'>%ld</td></tr>", total);
-	}
-
-	if (top < authors->nr)
-		print_combined_authorrow(authors, top, authors->nr - 1,
-			"Others (%ld)", "left", "", "sum", period);
-
-	print_combined_authorrow(authors, 0, authors->nr - 1, "Total",
-		"total", "sum", "sum", period);
-	html("</table>");
-}
-
-/* Create a sorted string_list with one entry per author. The util-field
- * for each author is another string_list which is used to calculate the
- * number of commits per time-interval.
- */
-void cgit_show_stats(void)
-{
-	struct string_list authors;
-	const struct cgit_period *period;
-	int top, i;
-	const char *code = "w";
-
-	if (ctx.qry.period)
-		code = ctx.qry.period;
-
-	i = cgit_find_stats_period(code, &period);
-	if (!i) {
-		cgit_print_error_page(404, "Not found",
-			"Unknown statistics type: %c", code[0]);
-		return;
-	}
-	if (i > ctx.repo->max_stats) {
-		cgit_print_error_page(400, "Bad request",
-			"Statistics type disabled: %s", period->name);
-		return;
-	}
-	authors = collect_stats(period);
-	qsort(authors.items, authors.nr, sizeof(struct string_list_item),
-		cmp_total_commits);
-
-	top = ctx.qry.ofs;
-	if (!top)
-		top = 10;
-
-	cgit_print_layout_start();
-	html("<div class='cgit-panel'>");
-	html("<b>stat options</b>");
-	html("<form method='get'>");
-	cgit_add_hidden_formfields(1, 0, "stats");
-	html("<table><tr><td colspan='2'/></tr>");
-	if (ctx.repo->max_stats > 1) {
-		html("<tr><td class='label'>Period:</td>");
-		html("<td class='ctrl'><select name='period' onchange='this.form.submit();'>");
-		for (i = 0; i < ctx.repo->max_stats; i++)
-			html_option(fmt("%c", periods[i].code),
-				    periods[i].name, fmt("%c", period->code));
-		html("</select></td></tr>");
-	}
-	html("<tr><td class='label'>Authors:</td>");
-	html("<td class='ctrl'><select name='ofs' onchange='this.form.submit();'>");
-	html_intoption(10, "10", top);
-	html_intoption(25, "25", top);
-	html_intoption(50, "50", top);
-	html_intoption(100, "100", top);
-	html_intoption(-1, "all", top);
-	html("</select></td></tr>");
-	html("<tr><td/><td class='ctrl'>");
-	html("<noscript><input type='submit' value='Reload'/></noscript>");
-	html("</td></tr></table>");
-	html("</form>");
-	html("</div>");
-	htmlf("<h2>Commits per author per %s", period->name);
-	if (ctx.qry.path) {
-		html(" (path '");
-		html_txt(ctx.qry.path);
-		html("')");
-	}
-	html("</h2>");
-	print_authors(&authors, top, period);
-	cgit_print_layout_end();
-}
-
diff --git a/ui-stats.h b/ui-stats.h
deleted file mode 100644
index 0e61b03da3..0000000000
--- a/ui-stats.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#ifndef UI_STATS_H
-#define UI_STATS_H
-
-#include "cgit.h"
-
-struct cgit_period {
-	const char code;
-	const char *name;
-	int max_periods;
-	int count;
-
-	/* Convert a tm value to the first day in the period */
-	void (*trunc)(struct tm *tm);
-
-	/* Update tm value to start of next/previous period */
-	void (*dec)(struct tm *tm);
-	void (*inc)(struct tm *tm);
-
-	/* Pretty-print a tm value */
-	char *(*pretty)(struct tm *tm);
-};
-
-extern int cgit_find_stats_period(const char *expr, const struct cgit_period **period);
-extern const char *cgit_find_stats_periodname(int idx);
-
-extern void cgit_show_stats(void);
-
-#endif /* UI_STATS_H */
diff --git a/ui-summary.c b/ui-summary.c
deleted file mode 100644
index 947812a814..0000000000
--- a/ui-summary.c
+++ /dev/null
@@ -1,148 +0,0 @@
-/* ui-summary.c: functions for generating repo summary page
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-summary.h"
-#include "html.h"
-#include "ui-blob.h"
-#include "ui-log.h"
-#include "ui-plain.h"
-#include "ui-refs.h"
-#include "ui-shared.h"
-
-static int urls;
-
-static void print_url(const char *url)
-{
-	int columns = 3;
-
-	if (ctx.repo->enable_log_filecount)
-		columns++;
-	if (ctx.repo->enable_log_linecount)
-		columns++;
-
-	if (urls++ == 0) {
-		htmlf("<tr class='nohover'><td colspan='%d'>&nbsp;</td></tr>", columns);
-		htmlf("<tr class='nohover'><th class='left' colspan='%d'>Clone</th></tr>\n", columns);
-	}
-
-	htmlf("<tr><td colspan='%d'><a rel='vcs-git' href='", columns);
-	html_url_path(url);
-	html("' title='");
-	html_attr(ctx.repo->name);
-	html(" Git repository'>");
-	html_txt(url);
-	html("</a></td></tr>\n");
-}
-
-void cgit_print_summary(void)
-{
-	int columns = 3;
-
-	if (ctx.repo->enable_log_filecount)
-		columns++;
-	if (ctx.repo->enable_log_linecount)
-		columns++;
-
-	cgit_print_layout_start();
-	html("<table summary='repository info' class='list nowrap'>");
-	cgit_print_branches(ctx.cfg.summary_branches);
-	htmlf("<tr class='nohover'><td colspan='%d'>&nbsp;</td></tr>", columns);
-	cgit_print_tags(ctx.cfg.summary_tags);
-	if (ctx.cfg.summary_log > 0) {
-		htmlf("<tr class='nohover'><td colspan='%d'>&nbsp;</td></tr>", columns);
-		cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL,
-			       NULL, NULL, 0, 0, 0);
-	}
-	urls = 0;
-	cgit_add_clone_urls(print_url);
-	html("</table>");
-	cgit_print_layout_end();
-}
-
-/* The caller must free the return value. */
-static char* append_readme_path(const char *filename, const char *ref, const char *path)
-{
-	char *file, *base_dir, *full_path, *resolved_base = NULL, *resolved_full = NULL;
-	/* If a subpath is specified for the about page, make it relative
-	 * to the directory containing the configured readme. */
-
-	file = xstrdup(filename);
-	base_dir = dirname(file);
-	if (!strcmp(base_dir, ".") || !strcmp(base_dir, "..")) {
-		if (!ref) {
-			free(file);
-			return NULL;
-		}
-		full_path = xstrdup(path);
-	} else
-		full_path = fmtalloc("%s/%s", base_dir, path);
-
-	if (!ref) {
-		resolved_base = realpath(base_dir, NULL);
-		resolved_full = realpath(full_path, NULL);
-		if (!resolved_base || !resolved_full || !starts_with(resolved_full, resolved_base)) {
-			free(full_path);
-			full_path = NULL;
-		}
-	}
-
-	free(file);
-	free(resolved_base);
-	free(resolved_full);
-
-	return full_path;
-}
-
-void cgit_print_repo_readme(const char *path)
-{
-	char *filename, *ref, *mimetype;
-	int free_filename = 0;
-
-	mimetype = get_mimetype_for_filename(path);
-	if (mimetype && (!strncmp(mimetype, "image/", 6) || !strncmp(mimetype, "video/", 6))) {
-		ctx.page.mimetype = mimetype;
-		ctx.page.charset = NULL;
-		cgit_print_plain();
-		free(mimetype);
-		return;
-	}
-	free(mimetype);
-
-	cgit_print_layout_start();
-	if (ctx.repo->readme.nr == 0)
-		goto done;
-
-	filename = ctx.repo->readme.items[0].string;
-	ref = ctx.repo->readme.items[0].util;
-
-	if (path) {
-		free_filename = 1;
-		filename = append_readme_path(filename, ref, path);
-		if (!filename)
-			goto done;
-	}
-
-	/* Print the calculated readme, either from the git repo or from the
-	 * filesystem, while applying the about-filter.
-	 */
-	html("<div id='summary'>");
-	cgit_open_filter(ctx.repo->about_filter, filename);
-	if (ref)
-		cgit_print_file(filename, ref, 1);
-	else
-		html_include(filename);
-	cgit_close_filter(ctx.repo->about_filter);
-
-	html("</div>");
-	if (free_filename)
-		free(filename);
-
-done:
-	cgit_print_layout_end();
-}
diff --git a/ui-summary.h b/ui-summary.h
deleted file mode 100644
index cba696af53..0000000000
--- a/ui-summary.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#ifndef UI_SUMMARY_H
-#define UI_SUMMARY_H
-
-extern void cgit_print_summary(void);
-extern void cgit_print_repo_readme(const char *path);
-
-#endif /* UI_SUMMARY_H */
diff --git a/ui-tag.c b/ui-tag.c
deleted file mode 100644
index 846d5b141f..0000000000
--- a/ui-tag.c
+++ /dev/null
@@ -1,120 +0,0 @@
-/* ui-tag.c: display a tag
- *
- * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-tag.h"
-#include "html.h"
-#include "ui-shared.h"
-
-static void print_tag_content(char *buf)
-{
-	char *p;
-
-	if (!buf)
-		return;
-
-	html("<div class='commit-subject'>");
-	p = strchr(buf, '\n');
-	if (p)
-		*p = '\0';
-	html_txt(buf);
-	html("</div>");
-	if (p) {
-		html("<div class='commit-msg'>");
-		html_txt(++p);
-		html("</div>");
-	}
-}
-
-static void print_download_links(char *revname)
-{
-	html("<tr><th>download</th><td class='sha1'>");
-	cgit_print_snapshot_links(ctx.repo, revname, "<br/>");
-	html("</td></tr>");
-}
-
-void cgit_print_tag(char *revname)
-{
-	struct strbuf fullref = STRBUF_INIT;
-	struct object_id oid;
-	struct object *obj;
-
-	if (!revname)
-		revname = ctx.qry.head;
-
-	strbuf_addf(&fullref, "refs/tags/%s", revname);
-	if (get_oid(fullref.buf, &oid)) {
-		cgit_print_error_page(404, "Not found",
-			"Bad tag reference: %s", revname);
-		goto cleanup;
-	}
-	obj = parse_object(the_repository, &oid);
-	if (!obj) {
-		cgit_print_error_page(500, "Internal server error",
-			"Bad object id: %s", oid_to_hex(&oid));
-		goto cleanup;
-	}
-	if (obj->type == OBJ_TAG) {
-		struct tag *tag;
-		struct taginfo *info;
-
-		tag = lookup_tag(the_repository, &oid);
-		if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) {
-			cgit_print_error_page(500, "Internal server error",
-				"Bad tag object: %s", revname);
-			goto cleanup;
-		}
-		cgit_print_layout_start();
-		html("<table class='commit-info'>\n");
-		html("<tr><td>tag name</td><td>");
-		html_txt(revname);
-		htmlf(" (%s)</td></tr>\n", oid_to_hex(&oid));
-		if (info->tagger_date > 0) {
-			html("<tr><td>tag date</td><td>");
-			html_txt(show_date(info->tagger_date, info->tagger_tz,
-						cgit_date_mode(DATE_ISO8601)));
-			html("</td></tr>\n");
-		}
-		if (info->tagger) {
-			html("<tr><td>tagged by</td><td>");
-			cgit_open_filter(ctx.repo->email_filter, info->tagger_email, "tag");
-			html_txt(info->tagger);
-			if (info->tagger_email && !ctx.cfg.noplainemail) {
-				html(" ");
-				html_txt(info->tagger_email);
-			}
-			cgit_close_filter(ctx.repo->email_filter);
-			html("</td></tr>\n");
-		}
-		html("<tr><td>tagged object</td><td class='sha1'>");
-		cgit_object_link(tag->tagged);
-		html("</td></tr>\n");
-		if (ctx.repo->snapshots)
-			print_download_links(revname);
-		html("</table>\n");
-		print_tag_content(info->msg);
-		cgit_print_layout_end();
-		cgit_free_taginfo(info);
-	} else {
-		cgit_print_layout_start();
-		html("<table class='commit-info'>\n");
-		html("<tr><td>tag name</td><td>");
-		html_txt(revname);
-		html("</td></tr>\n");
-		html("<tr><td>tagged object</td><td class='sha1'>");
-		cgit_object_link(obj);
-		html("</td></tr>\n");
-		if (ctx.repo->snapshots)
-			print_download_links(revname);
-		html("</table>\n");
-		cgit_print_layout_end();
-	}
-
-cleanup:
-	strbuf_release(&fullref);
-}
diff --git a/ui-tag.h b/ui-tag.h
deleted file mode 100644
index d295cdcdbd..0000000000
--- a/ui-tag.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef UI_TAG_H
-#define UI_TAG_H
-
-extern void cgit_print_tag(char *revname);
-
-#endif /* UI_TAG_H */
diff --git a/ui-tree.c b/ui-tree.c
deleted file mode 100644
index 84eb17d647..0000000000
--- a/ui-tree.c
+++ /dev/null
@@ -1,388 +0,0 @@
-/* ui-tree.c: functions for tree output
- *
- * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- *   (see COPYING for full license text)
- */
-
-#include "cgit.h"
-#include "ui-tree.h"
-#include "html.h"
-#include "ui-shared.h"
-
-struct walk_tree_context {
-	char *curr_rev;
-	char *match_path;
-	int state;
-};
-
-static void print_text_buffer(const char *name, char *buf, unsigned long size)
-{
-	unsigned long lineno, idx;
-	const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n";
-
-	html("<table summary='blob content' class='blob'>\n");
-
-	if (ctx.cfg.enable_tree_linenumbers) {
-		html("<tr><td class='linenumbers'><pre>");
-		idx = 0;
-		lineno = 0;
-
-		if (size) {
-			htmlf(numberfmt, ++lineno);
-			while (idx < size - 1) { // skip absolute last newline
-				if (buf[idx] == '\n')
-					htmlf(numberfmt, ++lineno);
-				idx++;
-			}
-		}
-		html("</pre></td>\n");
-	}
-	else {
-		html("<tr>\n");
-	}
-
-	if (ctx.repo->source_filter) {
-		char *filter_arg = xstrdup(name);
-		html("<td class='lines'><pre><code>");
-		cgit_open_filter(ctx.repo->source_filter, filter_arg);
-		html_raw(buf, size);
-		cgit_close_filter(ctx.repo->source_filter);
-		free(filter_arg);
-		html("</code></pre></td></tr></table>\n");
-		return;
-	}
-
-	html("<td class='lines'><pre><code>");
-	html_txt(buf);
-	html("</code></pre></td></tr></table>\n");
-}
-
-#define ROWLEN 32
-
-static void print_binary_buffer(char *buf, unsigned long size)
-{
-	unsigned long ofs, idx;
-	static char ascii[ROWLEN + 1];
-
-	html("<table summary='blob content' class='bin-blob'>\n");
-	html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
-	for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
-		htmlf("<tr><td class='right'>%04lx</td><td class='hex'>", ofs);
-		for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
-			htmlf("%*s%02x",
-			      idx == 16 ? 4 : 1, "",
-			      buf[idx] & 0xff);
-		html(" </td><td class='hex'>");
-		for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
-			ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
-		ascii[idx] = '\0';
-		html_txt(ascii);
-		html("</td></tr>\n");
-	}
-	html("</table>\n");
-}
-
-static void print_object(const struct object_id *oid, const char *path, const char *basename, const char *rev)
-{
-	enum object_type type;
-	char *buf;
-	unsigned long size;
-
-	type = oid_object_info(the_repository, oid, &size);
-	if (type == OBJ_BAD) {
-		cgit_print_error_page(404, "Not found",
-			"Bad object name: %s", oid_to_hex(oid));
-		return;
-	}
-
-	buf = read_object_file(oid, &type, &size);
-	if (!buf) {
-		cgit_print_error_page(500, "Internal server error",
-			"Error reading object %s", oid_to_hex(oid));
-		return;
-	}
-
-	cgit_set_title_from_path(path);
-
-	cgit_print_layout_start();
-	htmlf("blob: %s (", oid_to_hex(oid));
-	cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
-		        rev, path);
-	if (ctx.repo->enable_blame) {
-		html(") (");
-		cgit_blame_link("blame", NULL, NULL, ctx.qry.head,
-			        rev, path);
-	}
-	html(")\n");
-
-	if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
-		htmlf("<div class='error'>blob size (%ldKB) exceeds display size limit (%dKB).</div>",
-				size / 1024, ctx.cfg.max_blob_size);
-		return;
-	}
-
-	if (buffer_is_binary(buf, size))
-		print_binary_buffer(buf, size);
-	else
-		print_text_buffer(basename, buf, size);
-
-	free(buf);
-}
-
-struct single_tree_ctx {
-	struct strbuf *path;
-	struct object_id oid;
-	char *name;
-	size_t count;
-};
-
-static int single_tree_cb(const struct object_id *oid, struct strbuf *base,
-			  const char *pathname, unsigned mode, int stage,
-			  void *cbdata)
-{
-	struct single_tree_ctx *ctx = cbdata;
-
-	if (++ctx->count > 1)
-		return -1;
-
-	if (!S_ISDIR(mode)) {
-		ctx->count = 2;
-		return -1;
-	}
-
-	ctx->name = xstrdup(pathname);
-	oidcpy(&ctx->oid, oid);
-	strbuf_addf(ctx->path, "/%s", pathname);
-	return 0;
-}
-
-static void write_tree_link(const struct object_id *oid, char *name,
-			    char *rev, struct strbuf *fullpath)
-{
-	size_t initial_length = fullpath->len;
-	struct tree *tree;
-	struct single_tree_ctx tree_ctx = {
-		.path = fullpath,
-		.count = 1,
-	};
-	struct pathspec paths = {
-		.nr = 0
-	};
-
-	oidcpy(&tree_ctx.oid, oid);
-
-	while (tree_ctx.count == 1) {
-		cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, rev,
-			       fullpath->buf);
-
-		tree = lookup_tree(the_repository, &tree_ctx.oid);
-		if (!tree)
-			return;
-
-		free(tree_ctx.name);
-		tree_ctx.name = NULL;
-		tree_ctx.count = 0;
-
-		read_tree_recursive(the_repository, tree, "", 0, 1,
-			&paths, single_tree_cb, &tree_ctx);
-
-		if (tree_ctx.count != 1)
-			break;
-
-		html(" / ");
-		name = tree_ctx.name;
-	}
-
-	strbuf_setlen(fullpath, initial_length);
-}
-
-static int ls_item(const struct object_id *oid, struct strbuf *base,
-		const char *pathname, unsigned mode, int stage, void *cbdata)
-{
-	struct walk_tree_context *walk_tree_ctx = cbdata;
-	char *name;
-	struct strbuf fullpath = STRBUF_INIT;
-	struct strbuf class = STRBUF_INIT;
-	enum object_type type;
-	unsigned long size = 0;
-
-	name = xstrdup(pathname);
-	strbuf_addf(&fullpath, "%s%s%s", ctx.qry.path ? ctx.qry.path : "",
-		    ctx.qry.path ? "/" : "", name);
-
-	if (!S_ISGITLINK(mode)) {
-		type = oid_object_info(the_repository, oid, &size);
-		if (type == OBJ_BAD) {
-			htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
-			      name,
-			      oid_to_hex(oid));
-			free(name);
-			return 0;
-		}
-	}
-
-	html("<tr><td class='ls-mode'>");
-	cgit_print_filemode(mode);
-	html("</td><td>");
-	if (S_ISGITLINK(mode)) {
-		cgit_submodule_link("ls-mod", fullpath.buf, oid_to_hex(oid));
-	} else if (S_ISDIR(mode)) {
-		write_tree_link(oid, name, walk_tree_ctx->curr_rev,
-				&fullpath);
-	} else {
-		char *ext = strrchr(name, '.');
-		strbuf_addstr(&class, "ls-blob");
-		if (ext)
-			strbuf_addf(&class, " %s", ext + 1);
-		cgit_tree_link(name, NULL, class.buf, ctx.qry.head,
-			       walk_tree_ctx->curr_rev, fullpath.buf);
-	}
-	htmlf("</td><td class='ls-size'>%li</td>", size);
-
-	html("<td>");
-	cgit_log_link("log", NULL, "button", ctx.qry.head,
-		      walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL,
-		      ctx.qry.showmsg, 0);
-	if (ctx.repo->max_stats)
-		cgit_stats_link("stats", NULL, "button", ctx.qry.head,
-				fullpath.buf);
-	if (!S_ISGITLINK(mode))
-		cgit_plain_link("plain", NULL, "button", ctx.qry.head,
-				walk_tree_ctx->curr_rev, fullpath.buf);
-	if (!S_ISDIR(mode) && ctx.repo->enable_blame)
-		cgit_blame_link("blame", NULL, "button", ctx.qry.head,
-				walk_tree_ctx->curr_rev, fullpath.buf);
-	html("</td></tr>\n");
-	free(name);
-	strbuf_release(&fullpath);
-	strbuf_release(&class);
-	return 0;
-}
-
-static void ls_head(void)
-{
-	cgit_print_layout_start();
-	html("<table summary='tree listing' class='list'>\n");
-	html("<tr class='nohover'>");
-	html("<th class='left'>Mode</th>");
-	html("<th class='left'>Name</th>");
-	html("<th class='right'>Size</th>");
-	html("<th/>");
-	html("</tr>\n");
-}
-
-static void ls_tail(void)
-{
-	html("</table>\n");
-	cgit_print_layout_end();
-}
-
-static void ls_tree(const struct object_id *oid, const char *path, struct walk_tree_context *walk_tree_ctx)
-{
-	struct tree *tree;
-	struct pathspec paths = {
-		.nr = 0
-	};
-
-	tree = parse_tree_indirect(oid);
-	if (!tree) {
-		cgit_print_error_page(404, "Not found",
-			"Not a tree object: %s", oid_to_hex(oid));
-		return;
-	}
-
-	ls_head();
-	read_tree_recursive(the_repository, tree, "", 0, 1,
-		&paths, ls_item, walk_tree_ctx);
-	ls_tail();
-}
-
-
-static int walk_tree(const struct object_id *oid, struct strbuf *base,
-		const char *pathname, unsigned mode, int stage, void *cbdata)
-{
-	struct walk_tree_context *walk_tree_ctx = cbdata;
-
-	if (walk_tree_ctx->state == 0) {
-		struct strbuf buffer = STRBUF_INIT;
-
-		strbuf_addbuf(&buffer, base);
-		strbuf_addstr(&buffer, pathname);
-		if (strcmp(walk_tree_ctx->match_path, buffer.buf))
-			return READ_TREE_RECURSIVE;
-
-		if (S_ISDIR(mode)) {
-			walk_tree_ctx->state = 1;
-			cgit_set_title_from_path(buffer.buf);
-			strbuf_release(&buffer);
-			ls_head();
-			return READ_TREE_RECURSIVE;
-		} else {
-			walk_tree_ctx->state = 2;
-			print_object(oid, buffer.buf, pathname, walk_tree_ctx->curr_rev);
-			strbuf_release(&buffer);
-			return 0;
-		}
-	}
-	ls_item(oid, base, pathname, mode, stage, walk_tree_ctx);
-	return 0;
-}
-
-/*
- * Show a tree or a blob
- *   rev:  the commit pointing at the root tree object
- *   path: path to tree or blob
- */
-void cgit_print_tree(const char *rev, char *path)
-{
-	struct object_id oid;
-	struct commit *commit;
-	struct pathspec_item path_items = {
-		.match = path,
-		.len = path ? strlen(path) : 0
-	};
-	struct pathspec paths = {
-		.nr = path ? 1 : 0,
-		.items = &path_items
-	};
-	struct walk_tree_context walk_tree_ctx = {
-		.match_path = path,
-		.state = 0
-	};
-
-	if (!rev)
-		rev = ctx.qry.head;
-
-	if (get_oid(rev, &oid)) {
-		cgit_print_error_page(404, "Not found",
-			"Invalid revision name: %s", rev);
-		return;
-	}
-	commit = lookup_commit_reference(the_repository, &oid);
-	if (!commit || parse_commit(commit)) {
-		cgit_print_error_page(404, "Not found",
-			"Invalid commit reference: %s", rev);
-		return;
-	}
-
-	walk_tree_ctx.curr_rev = xstrdup(rev);
-
-	if (path == NULL) {
-		ls_tree(&commit->maybe_tree->object.oid, NULL, &walk_tree_ctx);
-		goto cleanup;
-	}
-
-	read_tree_recursive(the_repository, commit->maybe_tree, "", 0, 0,
-		&paths, walk_tree, &walk_tree_ctx);
-	if (walk_tree_ctx.state == 1)
-		ls_tail();
-	else if (walk_tree_ctx.state == 2)
-		cgit_print_layout_end();
-	else
-		cgit_print_error_page(404, "Not found", "Path not found");
-
-cleanup:
-	free(walk_tree_ctx.curr_rev);
-}
diff --git a/ui-tree.h b/ui-tree.h
deleted file mode 100644
index bbd34e3566..0000000000
--- a/ui-tree.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef UI_TREE_H
-#define UI_TREE_H
-
-extern void cgit_print_tree(const char *rev, char *path);
-
-#endif /* UI_TREE_H */
diff --git a/xinitrc b/xinitrc
new file mode 100644
index 0000000000..591e419914
--- /dev/null
+++ b/xinitrc
@@ -0,0 +1,20 @@
+# Disable access control for the current user.
+xhost +SI:localuser:$USER
+
+# Make Java applications aware this is a non-reparenting window manager.
+export _JAVA_AWT_WM_NONREPARENTING=1
+
+# Set default cursor.
+xsetroot -cursor_name left_ptr
+
+# Set keyboard repeat rate.
+xset r rate 200 60
+
+# Uncomment the following block to use the exwm-xim module.
+#export XMODIFIERS=@im=exwm-xim
+#export GTK_IM_MODULE=xim
+#export QT_IM_MODULE=xim
+#export CLUTTER_IM_MODULE=xim
+
+# Finally start Emacs
+exec emacs