diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml
deleted file mode 100644
index c905969..0000000
--- a/.forgejo/workflows/build.yml
+++ /dev/null
@@ -1,69 +0,0 @@
-# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
-name: Build/Publish XBPS
-
-on:
-  push:
-    paths-ignore:
-      - ".forgejo**"
-      - ".renovaterc.json"
-      - "build.sh"
-
-jobs:
-  build-and-publish:
-    runs-on: docker
-    container: git.snaile.de/snailed/xbps-builder:2024.0502.1733@sha256:0ca263f8a97fbd29f88349e731153f60569d19ddd7320f2c7c80047a35260c9b
-    env:
-      LICENSE: "GPL-2.0"
-      SHORT_DESCRIPTION: "Customized dmenu"
-      DEPENDENCIES: "pango>=1.44,glibc>=2.32,libX11>=1.2,libXft>=2.3.8,libXinerama>=1.0.3,fontconfig>=2.6.0"
-      MAINTAINER: "Luca Bilke <luca@bil.ke>"
-      NAME: "dmenu-custom"
-      PROVIDES: "dmenu-5.2_1"
-      REPLACES: "dmenu>=0"
-      ARCH: "x86_64"
-    steps:
-      - name: Checkout
-        uses: https://code.forgejo.org/actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4
-        with:
-          submodules: true
-
-      - name: Generate tag and package name
-        run: |
-          set -xeu
-          MAJOR=$(date +%Y)
-          MINOR=$(date +%m%d)
-          PATCH=$(date +%H%M)
-          echo "PACKAGE_NAME=${{ env.NAME }}-${MAJOR}.${MINOR}.${PATCH}_1" >> $GITHUB_ENV
-
-      - name: Build
-        run: |
-          set -xeu
-          make clean install DESTDIR="${GITHUB_WORKSPACE}/pkg" PREFIX="/usr"
-
-      - name: Create package
-        run: |
-          set -xeu
-          export XBPS_TARGET_ARCH=${{ env.ARCH }}
-          mkdir /target
-          cd /target || exit 1
-          xbps-create -A ${{ env.ARCH }} \
-            -H "${{ github.server_url }}/${{ github.repository }}" \
-            -l "${{ env.LICENSE }}" \
-            -n "${{ env.PACKAGE_NAME }}" \
-            -m "${{ env.MAINTAINER }}" \
-            -s "${{ env.SHORT_DESCRIPTION }}" \
-            -D "${{ env.DEPENDENCIES }}" \
-            -R "${{ env.REPLACES }}" \
-            -P "${{ env.PROVIDES }}" \
-            "${GITHUB_WORKSPACE}/pkg"
-
-      - name: Push Package
-        run: |
-          set -xeu
-          curl -so "/target/${{ env.ARCH }}-repodata" "https://xbps.snaile.de/${{ env.ARCH }}-repodata"
-          xbps-rindex --add "/target/${{ env.PACKAGE_NAME }}.${{ env.ARCH }}.xbps"
-          echo '${{ secrets.XBPS_SIGNING_KEY }}' >/tmp/privkey.pem
-          XBPS_PASSPHRASE=${{ secrets.XBPS_SIGNING_PASSPHRASE }} xbps-rindex --privkey /tmp/privkey.pem --sign-pkg --signedby "${{ env.MAINTAINER }}" "/target/${{ env.PACKAGE_NAME }}.${{ env.ARCH }}.xbps"
-          rm /tmp/privkey.pem
-          ls -lAH /target
-          find /target -type f -exec sh -c 'curl -X PUT --digest -u "${{ vars.XBPS_WEBDAV_USER }}:${{ secrets.XBPS_WEBDAV_KEY }}" -T "${1}" "https://xbps.snaile.de/$(basename $1)"' find-shell {} \;
diff --git a/.forgejo/workflows/void-packages.yml b/.forgejo/workflows/void-packages.yml
new file mode 100644
index 0000000..44e3f3c
--- /dev/null
+++ b/.forgejo/workflows/void-packages.yml
@@ -0,0 +1,36 @@
+# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
+name: Update void-packages template
+
+on:
+  push:
+    tags: ["*"]
+
+jobs:
+  update-template:
+    name: Update xbps-src template
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+          token: ${{ secrets.FORGEJO_PUSH_TOKEN }}
+          repository: "snailed/void-packages"
+          sparse-checkout: "srcpkgs/dmenu-custom/"
+          ref: "master"
+
+      - name: Modify template
+        id: checksum
+        run: |
+          sed -i '/version=/ c version=${{ github.ref_name }}' srcpkgs/dmenu-custom/template
+
+          wget https://git.snaile.de/snailed/dmenu-custom/archive/${{ github.ref_name }}.tar.gz
+          sha256sum="$(sha256sum ${{ github.ref_name }}.tar.gz | cut -d ' ' -f 1)"
+          sed -i "/checksum=/ c checksum=${sha256sum}" srcpkgs/dmenu-custom/template
+
+      - name: Commit
+        uses: https://github.com/stefanzweifel/git-auto-commit-action@3ea6ae190baf489ba007f7c92608f33ce20ef04a # v4.16.0
+        with:
+          commit_user_name: "Johnny5"
+          commit_user_email: "bot@snaile.de"
+          commit_author: "Johnny5 <bot@snaile.de>"
+          commit_message: "dmenu-custom: update to ${{ github.ref_name }}."
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 8f5c2fc..0000000
--- a/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-dmenu-final
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 31ac874..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,6 +0,0 @@
-[submodule "flexipatch-finalizer"]
-	path = flexipatch-finalizer
-	url = https://github.com/bakkeby/flexipatch-finalizer
-[submodule "dmenu-flexipatch"]
-	path = dmenu-flexipatch
-	url = https://github.com/bakkeby/dmenu-flexipatch
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2a64b28
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+MIT/X Consortium License
+
+© 2006-2019 Anselm R Garbe <anselm@garbe.ca>
+© 2006-2008 Sander van Dijk <a.h.vandijk@gmail.com>
+© 2006-2007 Michał Janeczek <janeczek@gmail.com>
+© 2007 Kris Maglione <jg@suckless.org>
+© 2009 Gottox <gottox@s01.de>
+© 2009 Markus Schnalke <meillo@marmaro.de>
+© 2009 Evan Gates <evan.gates@gmail.com>
+© 2010-2012 Connor Lane Smith <cls@lubutu.com>
+© 2014-2022 Hiltjo Posthuma <hiltjo@codemadness.org>
+© 2015-2019 Quentin Rameau <quinq@fifth.space>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
index 2eadf5e..458c524 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,5 @@
 # dmenu - dynamic menu
 # See LICENSE file for copyright and license details.
-.PHONY: all clean dist install uninstall
 
 include config.mk
 
@@ -15,31 +14,25 @@ all: dmenu stest
 config.h:
 	cp config.def.h $@
 
-config.mk:
-	cp config/config.mk config.mk
-
-${SRC}: buildroot
-
 $(OBJ): arg.h config.h config.mk drw.h
 
-stest: stest.o
-	$(CC) -o $@ stest.o $(LDFLAGS)
-
 dmenu: dmenu.o drw.o util.o
 	$(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS)
 
-clean:
-	find . -maxdepth 1 -type f  | grep -Pv "^\./\.|Makefile$$" | xargs -r rm
-	rm -r tmp patch 2>/dev/null || true
-	git -C dmenu-flexipatch reset --hard HEAD
-	git -C dmenu-flexipatch clean -ffdx
+stest: stest.o
+	$(CC) -o $@ stest.o $(LDFLAGS)
 
-buildroot:
-	cp config/patches.h dmenu-flexipatch/patches.h
-	flexipatch-finalizer/flexipatch-finalizer.sh -r -d dmenu-flexipatch -o tmp
-	rm -r tmp/Makefile tmp/config.mk patch 2>/dev/null || true
-	mv tmp/* ./
-	rmdir tmp
+clean:
+	rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz
+
+dist: clean
+	mkdir -p dmenu-$(VERSION)
+	cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\
+		drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\
+		dmenu-$(VERSION)
+	tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION)
+	gzip dmenu-$(VERSION).tar
+	rm -rf dmenu-$(VERSION)
 
 install: all
 	mkdir -p $(DESTDIR)$(PREFIX)/bin
@@ -62,3 +55,4 @@ uninstall:
 		$(DESTDIR)$(MANPREFIX)/man1/dmenu.1\
 		$(DESTDIR)$(MANPREFIX)/man1/stest.1
 
+.PHONY: all clean dist install uninstall
diff --git a/README b/README
new file mode 100644
index 0000000..a8fcdfe
--- /dev/null
+++ b/README
@@ -0,0 +1,24 @@
+dmenu - dynamic menu
+====================
+dmenu is an efficient dynamic menu for X.
+
+
+Requirements
+------------
+In order to build dmenu you need the Xlib header files.
+
+
+Installation
+------------
+Edit config.mk to match your local setup (dmenu is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install dmenu
+(if necessary as root):
+
+    make clean install
+
+
+Running dmenu
+-------------
+See the man page for details.
diff --git a/arg.h b/arg.h
new file mode 100644
index 0000000..e94e02b
--- /dev/null
+++ b/arg.h
@@ -0,0 +1,49 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN	for (argv0 = *argv, argv++, argc--;\
+					argv[0] && argv[0][0] == '-'\
+					&& argv[0][1];\
+					argc--, argv++) {\
+				char argc_;\
+				char **argv_;\
+				int brk_;\
+				if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+					argv++;\
+					argc--;\
+					break;\
+				}\
+				for (brk_ = 0, argv[0]++, argv_ = argv;\
+						argv[0][0] && !brk_;\
+						argv[0]++) {\
+					if (argv_ != argv)\
+						break;\
+					argc_ = argv[0][0];\
+					switch (argc_)
+
+#define ARGEND			}\
+			}
+
+#define ARGC()		argc_
+
+#define EARGF(x)	((argv[0][1] == '\0' && argv[1] == NULL)?\
+				((x), abort(), (char *)0) :\
+				(brk_ = 1, (argv[0][1] != '\0')?\
+					(&argv[0][1]) :\
+					(argc--, argv++, argv[0])))
+
+#define ARGF()		((argv[0][1] == '\0' && argv[1] == NULL)?\
+				(char *)0 :\
+				(brk_ = 1, (argv[0][1] != '\0')?\
+					(&argv[0][1]) :\
+					(argc--, argv++, argv[0])))
+
+#endif
diff --git a/config.def.h b/config.def.h
new file mode 100644
index 0000000..777e155
--- /dev/null
+++ b/config.def.h
@@ -0,0 +1,26 @@
+/* See LICENSE file for copyright and license details. */
+/* Default settings; can be overriden by command line. */
+
+static int topbar = 1;                      /* -b  option; if 0, dmenu appears at bottom */
+static int fuzzy = 1;                       /* -F  option; if 0, dmenu doesn't use fuzzy matching */
+/* -fn option overrides fonts[0]; default X11 font or font set */
+static char *font = "monospace 10";
+static const char *prompt      = NULL;      /* -p  option; prompt to the left of input field */
+
+static
+const
+char *colors[][2] = {
+	/*               fg         bg       */
+	[SchemeNorm] = { "#bbbbbb", "#222222" },
+	[SchemeSel]  = { "#eeeeee", "#005577" },
+	[SchemeOut]  = { "#000000", "#00ffff" },
+};
+/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
+static unsigned int lines      = 0;
+
+/*
+ * Characters not considered part of a word while deleting words
+ * for example: " /?\"&[]"
+ */
+static const char worddelimiters[] = " ";
+
diff --git a/config/config.h b/config.h
similarity index 96%
rename from config/config.h
rename to config.h
index 16c3f55..8500e23 100644
--- a/config/config.h
+++ b/config.h
@@ -4,7 +4,7 @@
 static int topbar = 1; /* -b  option; if 0, dmenu appears at bottom */
 static int fuzzy = 1;  /* -F  option; if 0, dmenu doesn't use fuzzy matching */
 /* -fn option overrides fonts[0]; default X11 font or font set */
-static char font[] = "FiraCode Nerd Font 12";
+static char *font = "FiraCode Nerd Font 12";
 static const char *prompt = NULL; /* -p  option; prompt to the left of input field */
 
 #define BLACK "#15161E"
diff --git a/config/config.mk b/config.mk
similarity index 98%
rename from config/config.mk
rename to config.mk
index 8aa090d..049c358 100644
--- a/config/config.mk
+++ b/config.mk
@@ -23,7 +23,7 @@ FREETYPEINC = /usr/include/freetype2
 #EXTRAFLAGS=-D_GNU_SOURCE
 
 # Uncomment this for the alpha patch / ALPHA_PATCH
-XRENDER = -lXrender
+# XRENDER = -lXrender
 
 # Uncomment for the pango patch / PANGO_PATCH
 PANGOINC = `pkg-config --cflags xft pango pangoxft`
diff --git a/config/patches.h b/config/patches.h
deleted file mode 100644
index dc0f874..0000000
--- a/config/patches.h
+++ /dev/null
@@ -1,355 +0,0 @@
-/* Patches */
-
-/* The alpha patch adds transparency for the dmenu window.
- * You need to uncomment the corresponding line in config.mk to use the -lXrender library
- * when including this patch.
- * https://github.com/bakkeby/patches/blob/master/dmenu/dmenu-alpha-5.0_20210725_523aa08.diff
- */
-#define ALPHA_PATCH 0
-
-/* This adds padding for dmenu in similar fashion to the similarly named patch for dwm. The idea
- * is to have dmenu appear on top of the bar when using said patch in dwm.
- * https://github.com/bakkeby/patches/wiki/barpadding
- */
-#define BARPADDING_PATCH 0
-
-/* This patch adds a border around the dmenu window. It is intended to be used with the center
- * or xyw patches, to make the menu stand out from similarly coloured windows.
- * http://tools.suckless.org/dmenu/patches/border/
- */
-#define BORDER_PATCH 0
-
-/* By default the caret in dmenu has a width of 2 pixels. This patch makes that configurable
- * as well as overridable via a command line option.
- * https://github.com/DarkSamus669/dmenu-patches/blob/main/dmenu-caretwidth-5.2.diff
- */
-#define CARET_WIDTH_PATCH 0
-
-/* This patch makes dmenu case-insensitive by default, replacing the
- * case-insensitive -i option with a case sensitive -s option.
- * http://tools.suckless.org/dmenu/patches/case-insensitive/
- */
-#define CASEINSENSITIVE_PATCH 0
-
-/* This patch centers dmenu in the middle of the screen.
- * https://tools.suckless.org/dmenu/patches/center/
- */
-#define CENTER_PATCH 0
-
-/* Minor patch to enable the use of Ctrl+v (XA_PRIMARY) and Ctrl+Shift+v (CLIPBOARD) to paste.
- * By default dmenu only supports Ctrl+y and Ctrl+Shift+y to paste.
- */
-#define CTRL_V_TO_PASTE_PATCH 1
-
-/* This patch adds a flag (-dy) which makes dmenu run the command given to it whenever input
- * is changed with the current input as the last argument and update the option list according
- * to the output of that command.
- * https://tools.suckless.org/dmenu/patches/dynamicoptions/
- */
-#define DYNAMIC_OPTIONS_PATCH 0
-
-/* This patch will allow for emojis on the left side with a colored background when selected.
- * To test this try running:
- *    $ echo -e ":b here\n:p there\n:r and here" | ./dmenu -p "Search..." -W 400 -l 20 -i -h -1
- * NB: the original patch came embedded with the the xyw patch, the morecolors patch and the
- * line height patch and as such is intended to be combined with these.
- * https://tools.suckless.org/dmenu/patches/emoji-highlight/
- */
-#define EMOJI_HIGHLIGHT_PATCH 0
-
-/* This patch make it so that fuzzy matches gets highlighted and is therefore meant
- * to be used together with the fuzzymatch patch.
- * https://tools.suckless.org/dmenu/patches/fuzzyhighlight/
- */
-#define FUZZYHIGHLIGHT_PATCH 0
-
-/* This patch adds support for fuzzy-matching to dmenu, allowing users to type non-consecutive
- * portions of the string to be matched.
- * https://tools.suckless.org/dmenu/patches/fuzzymatch/
- */
-#define FUZZYMATCH_PATCH 1
-
-/* Adds fzf-like functionality for dmenu.
- * Refer to https://github.com/DAFF0D11/dafmenu/ for documentation and example use cases.
- * https://github.com/DAFF0D11/dafmenu/blob/master/patches/dmenu-fzfexpect-5.1.diff
- */
-#define FZFEXPECT_PATCH 0
-
-/* Allows dmenu's entries to be rendered in a grid by adding a new -g flag to specify
- * the number of grid columns. The -g and -l options can be used together to create a
- * G columns * L lines grid.
- * https://tools.suckless.org/dmenu/patches/grid/
- */
-#define GRID_PATCH 0
-
-/* This patch adds the ability to move left and right through a grid.
- * This patch depends on the grid patch.
- * https://tools.suckless.org/dmenu/patches/gridnav/
- */
-#define GRIDNAV_PATCH 0
-
-/* This patch highlights the individual characters of matched text for each dmenu list entry.
- * The fuzzy highlight patch takes precedence over this patch.
- * https://tools.suckless.org/dmenu/patches/highlight/
- */
-#define HIGHLIGHT_PATCH 0
-
-/* This will automatically sort the search result so that high priority items are shown first.
- * https://tools.suckless.org/dmenu/patches/highpriority/
- */
-#define HIGHPRIORITY_PATCH 0
-
-/* This patch causes dmenu to print out the current text each time a key is pressed.
- * https://tools.suckless.org/dmenu/patches/incremental/
- */
-#define INCREMENTAL_PATCH 0
-
-/* This patch adds an option to provide preselected text.
- * https://tools.suckless.org/dmenu/patches/initialtext/
- */
-#define INITIALTEXT_PATCH 0
-
-/* This patch adds a flag which will cause dmenu to select an item immediately if there
- * is only one matching option left.
- * https://tools.suckless.org/dmenu/patches/instant/
- */
-#define INSTANT_PATCH 0
-
-/* This patch adds a '-h' option which sets the minimum height of a dmenu line. This helps
- * integrate dmenu with other UI elements that require a particular vertical size.
- * http://tools.suckless.org/dmenu/patches/line-height/
- */
-#define LINE_HEIGHT_PATCH 0
-
-/* This patch adds a -wm flag which sets override_redirect to false; thus letting your window
- * manager manage the dmenu window.
- *
- * This may be helpful in contexts where you don't want to exclusively bind dmenu or want to
- * treat dmenu more as a "window" rather than as an overlay.
- * https://tools.suckless.org/dmenu/patches/managed/
- */
-#define MANAGED_PATCH 0
-
-/* This patch adds an additional color scheme for highlighting entries adjacent to the current
- * selection.
- * https://tools.suckless.org/dmenu/patches/morecolor/
- */
-#define MORECOLOR_PATCH 0
-
-/* This patch adds basic mouse support for dmenu.
- * https://tools.suckless.org/dmenu/patches/mouse-support/
- */
-#define MOUSE_SUPPORT_PATCH 0
-
-/* Without this patch when you press Ctrl+Enter dmenu just outputs current item and it is not
- * possible to undo that.
- * With this patch dmenu will output all selected items only on exit. It is also possible to
- * deselect any selected item.
- * Also refer to the dmenu_run replacement on the below URL that supports multiple selections.
- *
- * This patch is not compatible with, and takes precedence over, the json, printinputtext,
- * pipeout and non-blocking stdin patches.
- *
- * https://tools.suckless.org/dmenu/patches/multi-selection/
- */
-#define MULTI_SELECTION_PATCH 0
-
-/* This patch provides dmenu the ability for history navigation similar to that of bash.
- *
- * If you take this patch then it is recommended that you also uncomment the line in the
- * dmenu_run script which replaces the exec command.
- *
- * https://tools.suckless.org/dmenu/patches/navhistory/
- */
-#define NAVHISTORY_PATCH 0
-
-/* This patch adds back in the workaround for a BadLength error in the Xft library when color
- * glyphs are used. This is for systems that do not have an updated version of the Xft library
- * (or generally prefer monochrome fonts).
- */
-#define NO_COLOR_EMOJI_PATCH 0
-
-/* Adds the -S option to disable sorting menu items after matching. Useful, for example, when menu
- * items are sorted by their frequency of use (using an external cache) and the most frequently
- * selected items should always appear first regardless of how they were exact, prefix, or
- * substring matches.
- * https://tools.suckless.org/dmenu/patches/no-sort/
- */
-#define NO_SORT_PATCH 0
-
-/* This is a patch to have dmenu read stdin in a non blocking way, making it wait for input both
- * from stdin and from X. This means that you can continue feeding dmenu while you type.
- * This patch is meant to be used along with the incremental patch, so that you can use stdout
- * to feed stdin.
- *
- * This patch is not compatible with the json and multi-selection patches, both of which takes
- * precedence over this patch.
- *
- * https://tools.suckless.org/dmenu/patches/non_blocking_stdin/
- */
-#define NON_BLOCKING_STDIN_PATCH 0
-
-/* Adds text which displays the number of matched and total items in the top right corner of dmenu.
- * https://tools.suckless.org/dmenu/patches/numbers/
- */
-#define NUMBERS_PATCH 0
-
-/* This patch adds simple markup for dmenu using pango markup.
- * This depends on the pango library v1.44 or greater.
- * You need to uncomment the corresponding lines in config.mk to use the pango libraries
- * when including this patch.
- *
- * Note that the pango patch is incompatible with the scroll patch and will result in
- * compilation errors if both are enabled.
- *
- * Note that the pango patch does not protect against the BadLength error from Xft
- * when color glyphs are used, which means that dmenu will crash if color emoji is used.
- *
- * If you need color emoji then you may want to install this patched library from the AUR:
- * https://aur.archlinux.org/packages/libxft-bgra/
- *
- * A long term fix for the libXft library is pending approval of this pull request:
- * https://gitlab.freedesktop.org/xorg/lib/libxft/-/merge_requests/1
- *
- * Also see:
- * https://developer.gnome.org/pygtk/stable/pango-markup-language.html
- * https://github.com/StillANixRookie/dmenu-pango
- */
-#define PANGO_PATCH 1
-
-/* With this patch dmenu will not directly display the keyboard input, but instead replace
- * it with dots. All data from stdin will be ignored.
- * https://tools.suckless.org/dmenu/patches/password/
- */
-#define PASSWORD_PATCH 1
-
-/* This patch allows the selected text to be piped back out with dmenu. This can be useful if you
- * want to display the output of a command on the screen.
- * Only text starting with the character '#' is piped out by default.
- *
- * This patch is not compatible with the json and multi-select patches, both of which takes
- * precedence over this one.
- *
- * https://tools.suckless.org/dmenu/patches/pipeout/
- */
-#define PIPEOUT_PATCH 0
-
-/* Lifted from the listfullwidth patch this simple change just avoids colors for the prompt (with
- * the -p option or in config.h) by making it use the same style as the rest of the input field.
- * The rest of the listfullwidth patch is covered by the vertfull patch.
- * https://tools.suckless.org/dmenu/patches/listfullwidth/
- */
-#define PLAIN_PROMPT_PATCH 0
-
-/* This patch changes the behaviour of matched items and the Tab key to allow tab completion.
- * https://tools.suckless.org/dmenu/patches/prefix-completion/
- */
-#define PREFIXCOMPLETION_PATCH 0
-
-/* This patch adds an option -ps to specify an item by providing the index that should be
- * pre-selected.
- * https://tools.suckless.org/dmenu/patches/preselect/
- */
-#define PRESELECT_PATCH 0
-
-/* This patch allows dmenu to print out the 0-based index of matched text instead of the matched
- * text itself. This can be useful in cases where you would like to select entries from one array
- * of text but index into another, or when you are selecting from an ordered list of non-unique
- * items.
- * https://tools.suckless.org/dmenu/patches/printindex/
- */
-#define PRINTINDEX_PATCH 0
-
-/* This patch adds a flag (-t) which makes Return key to ignore selection and print the input
- * text to stdout. The flag basically swaps the functions of Return and Shift+Return hotkeys.
- *
- * This patch is not compatible with the multi-select and json patches, both of which takes
- * precedence over this one.
- *
- * https://tools.suckless.org/dmenu/patches/printinputtext/
- */
-#define PRINTINPUTTEXT_PATCH 0
-
-/* This patch adds a new flag to dmenu with which text input will be rejected if it would
- * result in no matching item.
- * https://tools.suckless.org/dmenu/patches/reject-no-match/
- */
-#define REJECTNOMATCH_PATCH 0
-
-/* The input width used to be relative to the input options prior to commit e1e1de7:
- * https://git.suckless.org/dmenu/commit/e1e1de7b3b8399cba90ddca9613f837b2dbef7b9.html
- *
- * This had a performance hit when using large data sets and was removed in favour of having the
- * input width take up 1/3rd of the available space.
- *
- * This option adds that feature back in with some performance optimisations at the cost of
- * accuracy and correctness.
- */
-#define RELATIVE_INPUT_WIDTH_PATCH 0
-
-/* This patch adds a '-1' option which disables Shift-Return and Ctrl-Return.
- * This guarantees that dmenu will only output one item, and that item was read from stdin.
- * The original patch used '-r'. This was changed to '-1' to avoid conflict with the incremental
- * patch.
- * https://tools.suckless.org/dmenu/patches/restrict-return/
- */
-#define RESTRICT_RETURN_PATCH 0
-
-/* This patch adds support for text scrolling and no longer appends '...' for long input as
- * it can handle long text.
- * https://tools.suckless.org/dmenu/patches/scroll/
- */
-#define SCROLL_PATCH 0
-
-/* This patch adds -d and -D flags which separates the input into two halves; one half to be
- * displayed in dmenu and the other to be printed to stdout. This patch takes precedence over
- * the TSV patch.
- * https://tools.suckless.org/dmenu/patches/separator/
- */
-#define SEPARATOR_PATCH 0
-
-/* This patch allows the symbols, which are printed in dmenu to indicate that either the input
- * is too long or there are too many options to be shown in dmenu in one line, to be defined.
- * https://tools.suckless.org/dmenu/patches/symbols/
- */
-#define SYMBOLS_PATCH 0
-
-/* With this patch dmenu will split input lines at first tab character and only display first
- * part, but it will perform matching on and output full lines as usual.
- *
- * This can be useful if you want to separate data and representation, for example, a music
- * player wrapper can display only a track title to user, but still supply full filename to
- * the underlying script.
- * https://tools.suckless.org/dmenu/patches/tsv/
- */
-#define TSV_PATCH 1
-
-/* This patch prevents dmenu from indenting items at the same level as the prompt length.
- * https://tools.suckless.org/dmenu/patches/vertfull/
- */
-#define VERTFULL_PATCH 0
-
-/* Adds extended window manager hints such as _NET_WM_WINDOW_TYPE and _NET_WM_WINDOW_TYPE_DOCK.
- * https://github.com/Baitinq/dmenu/blob/master/patches/dmenu-wm_type.diff
- */
-#define WMTYPE_PATCH 0
-
-/* This patch adds the ability to configure dmenu via Xresources. At startup, dmenu will read and
- * apply the resources named below:
- *    dmenu.font          : font or font set
- *    dmenu.background    : normal background color
- *    dmenu.foreground    : normal foreground color
- *    dmenu.selbackground : selected background color
- *    dmenu.selforeground : selected foreground color
- *
- * See patch/xresources.c for more color settings.
- *
- * https://tools.suckless.org/dmenu/patches/xresources/
- */
-#define XRESOURCES_PATCH 0
-
-/* This patch adds options for specifying dmenu window position and width.
- * The center patch takes precedence over the XYW patch if enabled.
- * https://tools.suckless.org/dmenu/patches/xyw/
- */
-#define XYW_PATCH 0
diff --git a/dmenu-flexipatch b/dmenu-flexipatch
deleted file mode 160000
index 0d6d5ac..0000000
--- a/dmenu-flexipatch
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 0d6d5ac5cc81016b0cd3588b94aa76623dec84de
diff --git a/dmenu.1 b/dmenu.1
new file mode 100644
index 0000000..323f93c
--- /dev/null
+++ b/dmenu.1
@@ -0,0 +1,194 @@
+.TH DMENU 1 dmenu\-VERSION
+.SH NAME
+dmenu \- dynamic menu
+.SH SYNOPSIS
+.B dmenu
+.RB [ \-bfiv ]
+.RB [ \-l
+.IR lines ]
+.RB [ \-m
+.IR monitor ]
+.RB [ \-p
+.IR prompt ]
+.RB [ \-fn
+.IR font ]
+.RB [ \-nb
+.IR color ]
+.RB [ \-nf
+.IR color ]
+.RB [ \-sb
+.IR color ]
+.RB [ \-sf
+.IR color ]
+.RB [ \-w
+.IR windowid ]
+.P
+.BR dmenu_run " ..."
+.SH DESCRIPTION
+.B dmenu
+is a dynamic menu for X, which reads a list of newline\-separated items from
+stdin.  When the user selects an item and presses Return, their choice is printed
+to stdout and dmenu terminates.  Entering text will narrow the items to those
+matching the tokens in the input.
+.P
+.B dmenu_run
+is a script used by
+.IR dwm (1)
+which lists programs in the user's $PATH and runs the result in their $SHELL.
+.SH OPTIONS
+.TP
+.B \-b
+dmenu appears at the bottom of the screen.
+.TP
+.B \-f
+dmenu grabs the keyboard before reading stdin if not reading from a tty. This
+is faster, but will lock up X until stdin reaches end\-of\-file.
+.TP
+.B \-i
+dmenu matches menu items case insensitively.
+.TP
+.BI \-l " lines"
+dmenu lists items vertically, with the given number of lines.
+.TP
+.BI \-m " monitor"
+dmenu is displayed on the monitor number supplied. Monitor numbers are starting
+from 0.
+.TP
+.BI \-p " prompt"
+defines the prompt to be displayed to the left of the input field.
+.TP
+.BI \-fn " font"
+defines the font or font set used.
+.TP
+.BI \-nb " color"
+defines the normal background color.
+.IR #RGB ,
+.IR #RRGGBB ,
+and X color names are supported.
+.TP
+.BI \-nf " color"
+defines the normal foreground color.
+.TP
+.BI \-sb " color"
+defines the selected background color.
+.TP
+.BI \-sf " color"
+defines the selected foreground color.
+.TP
+.B \-v
+prints version information to stdout, then exits.
+.TP
+.BI \-w " windowid"
+embed into windowid.
+.SH USAGE
+dmenu is completely controlled by the keyboard.  Items are selected using the
+arrow keys, page up, page down, home, and end.
+.TP
+.B Tab
+Copy the selected item to the input field.
+.TP
+.B Return
+Confirm selection.  Prints the selected item to stdout and exits, returning
+success.
+.TP
+.B Ctrl-Return
+Confirm selection.  Prints the selected item to stdout and continues.
+.TP
+.B Shift\-Return
+Confirm input.  Prints the input text to stdout and exits, returning success.
+.TP
+.B Escape
+Exit without selecting an item, returning failure.
+.TP
+.B Ctrl-Left
+Move cursor to the start of the current word
+.TP
+.B Ctrl-Right
+Move cursor to the end of the current word
+.TP
+.B C\-a
+Home
+.TP
+.B C\-b
+Left
+.TP
+.B C\-c
+Escape
+.TP
+.B C\-d
+Delete
+.TP
+.B C\-e
+End
+.TP
+.B C\-f
+Right
+.TP
+.B C\-g
+Escape
+.TP
+.B C\-h
+Backspace
+.TP
+.B C\-i
+Tab
+.TP
+.B C\-j
+Return
+.TP
+.B C\-J
+Shift-Return
+.TP
+.B C\-k
+Delete line right
+.TP
+.B C\-m
+Return
+.TP
+.B C\-M
+Shift-Return
+.TP
+.B C\-n
+Down
+.TP
+.B C\-p
+Up
+.TP
+.B C\-u
+Delete line left
+.TP
+.B C\-w
+Delete word left
+.TP
+.B C\-y
+Paste from primary X selection
+.TP
+.B C\-Y
+Paste from X clipboard
+.TP
+.B M\-b
+Move cursor to the start of the current word
+.TP
+.B M\-f
+Move cursor to the end of the current word
+.TP
+.B M\-g
+Home
+.TP
+.B M\-G
+End
+.TP
+.B M\-h
+Up
+.TP
+.B M\-j
+Page down
+.TP
+.B M\-k
+Page up
+.TP
+.B M\-l
+Down
+.SH SEE ALSO
+.IR dwm (1),
+.IR stest (1)
diff --git a/dmenu.c b/dmenu.c
new file mode 100644
index 0000000..6f97e68
--- /dev/null
+++ b/dmenu.c
@@ -0,0 +1,915 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#ifdef XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+#include <X11/Xft/Xft.h>
+
+/* Patch incompatibility overrides */
+
+#include "drw.h"
+#include "util.h"
+
+/* macros */
+#define INTERSECT(x,y,w,h,r)  (MAX(0, MIN((x)+(w),(r).x_org+(r).width)  - MAX((x),(r).x_org)) \
+                             * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
+#define TEXTW(X)              (drw_font_getwidth(drw, (X), False) + lrpad)
+#define TEXTWM(X)             (drw_font_getwidth(drw, (X), True) + lrpad)
+
+/* enums */
+enum {
+	SchemeNorm,
+	SchemeSel,
+	SchemeOut,
+	SchemeLast,
+}; /* color schemes */
+
+struct item {
+	char *text;
+	char *stext;
+	struct item *left, *right;
+	int out;
+	double distance;
+};
+
+static char text[BUFSIZ] = "";
+static char *embed;
+static int bh, mw, mh;
+static int inputw = 0, promptw;
+static int passwd = 0;
+static int lrpad; /* sum of left and right padding */
+static size_t cursor;
+static struct item *items = NULL;
+static struct item *matches, *matchend;
+static struct item *prev, *curr, *next, *sel;
+static int mon = -1, screen;
+
+static Atom clip, utf8;
+static Display *dpy;
+static Window root, parentwin, win;
+static XIC xic;
+
+static Drw *drw;
+static Clr *scheme[SchemeLast];
+
+#include "patch/include.h"
+
+#include "config.h"
+
+static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+static char *(*fstrstr)(const char *, const char *) = strstr;
+
+static unsigned int
+textw_clamp(const char *str, unsigned int n)
+{
+	unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
+	return MIN(w, n);
+}
+
+static void appenditem(struct item *item, struct item **list, struct item **last);
+static void calcoffsets(void);
+static void cleanup(void);
+static char * cistrstr(const char *s, const char *sub);
+static int drawitem(struct item *item, int x, int y, int w);
+static void drawmenu(void);
+static void grabfocus(void);
+static void grabkeyboard(void);
+static void match(void);
+static void insert(const char *str, ssize_t n);
+static size_t nextrune(int inc);
+static void movewordedge(int dir);
+static void keypress(XKeyEvent *ev);
+static void paste(void);
+static void readstdin(void);
+static void run(void);
+static void setup(void);
+static void usage(void);
+
+#include "patch/include.c"
+
+static void
+appenditem(struct item *item, struct item **list, struct item **last)
+{
+	if (*last)
+		(*last)->right = item;
+	else
+		*list = item;
+
+	item->left = *last;
+	item->right = NULL;
+	*last = item;
+}
+
+static void
+calcoffsets(void)
+{
+	int i, n, rpad = 0;
+
+	if (lines > 0) {
+		n = lines * bh;
+	} else {
+		n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">") + rpad);
+	}
+	/* calculate which items will begin the next page and previous page */
+	for (i = 0, next = curr; next; next = next->right)
+		if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n)
+			break;
+	for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
+		if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n)
+			break;
+}
+
+static void
+cleanup(void)
+{
+	size_t i;
+
+	XUngrabKey(dpy, AnyKey, AnyModifier, root);
+	for (i = 0; i < SchemeLast; i++)
+		free(scheme[i]);
+	for (i = 0; items && items[i].text; ++i)
+		free(items[i].text);
+	free(items);
+	drw_free(drw);
+	XSync(dpy, False);
+	XCloseDisplay(dpy);
+}
+
+static char *
+cistrstr(const char *s, const char *sub)
+{
+	size_t len;
+
+	for (len = strlen(sub); *s; s++)
+		if (!strncasecmp(s, sub, len))
+			return (char *)s;
+	return NULL;
+}
+
+static int
+drawitem(struct item *item, int x, int y, int w)
+{
+	int r;
+	char *text = item->stext;
+
+	if (item == sel)
+		drw_setscheme(drw, scheme[SchemeSel]);
+	else if (item->out)
+		drw_setscheme(drw, scheme[SchemeOut]);
+	else
+		drw_setscheme(drw, scheme[SchemeNorm]);
+
+	r = drw_text(drw
+		, x
+		, y
+		, w
+		, bh
+		, lrpad / 2
+		, text
+		, 0
+		, True
+		);
+	return r;
+}
+
+static void
+drawmenu(void)
+{
+	unsigned int curpos;
+	struct item *item;
+	int x = 0, y = 0, w, rpad = 0, itw = 0, stw = 0;
+	char *censort;
+
+	drw_setscheme(drw, scheme[SchemeNorm]);
+	drw_rect(drw, 0, 0, mw, mh, 1, 1);
+
+	if (prompt && *prompt) {
+		drw_setscheme(drw, scheme[SchemeSel]);
+		x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0
+			, True
+		);
+	}
+	/* draw input field */
+	w = (lines > 0 || !matches) ? mw - x : inputw;
+
+	drw_setscheme(drw, scheme[SchemeNorm]);
+	if (passwd) {
+		censort = ecalloc(1, sizeof(text));
+		memset(censort, '.', strlen(text));
+		drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0
+			, False
+		);
+		drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0
+			, False
+		);
+		free(censort);
+	} else {
+		drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0
+			, False
+		);
+	}
+
+	curpos = TEXTW(text) - TEXTW(&text[cursor]);
+	if ((curpos += lrpad / 2 - 1) < w) {
+		drw_setscheme(drw, scheme[SchemeNorm]);
+		drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
+	}
+
+	if (lines > 0) {
+		/* draw vertical list */
+		for (item = curr; item != next; item = item->right)
+			drawitem(item, x, y += bh, mw - x);
+	} else if (matches) {
+		/* draw horizontal list */
+		x += inputw;
+		w = TEXTW("<");
+		if (curr->left) {
+			drw_setscheme(drw, scheme[SchemeNorm]);
+			drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0
+				, True
+			);
+		}
+		x += w;
+		for (item = curr; item != next; item = item->right) {
+			stw = TEXTW(">");
+			itw = textw_clamp(item->stext, mw - x - stw - rpad);
+			x = drawitem(item, x, 0, itw);
+		}
+		if (next) {
+			w = TEXTW(">");
+			drw_setscheme(drw, scheme[SchemeNorm]);
+			drw_text(drw, mw - w - rpad, 0, w, bh, lrpad / 2
+				, ">"
+				, 0
+				, True
+			);
+		}
+	}
+	drw_map(drw, win, 0, 0, mw, mh);
+}
+
+static void
+grabfocus(void)
+{
+	struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000  };
+	Window focuswin;
+	int i, revertwin;
+
+	for (i = 0; i < 100; ++i) {
+		XGetInputFocus(dpy, &focuswin, &revertwin);
+		if (focuswin == win)
+			return;
+		XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
+		nanosleep(&ts, NULL);
+	}
+	die("cannot grab focus");
+}
+
+static void
+grabkeyboard(void)
+{
+	struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000  };
+	int i;
+
+	if (embed)
+		return;
+	/* try to grab keyboard, we may have to wait for another process to ungrab */
+	for (i = 0; i < 1000; i++) {
+		if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync,
+		                  GrabModeAsync, CurrentTime) == GrabSuccess)
+			return;
+		nanosleep(&ts, NULL);
+	}
+	die("cannot grab keyboard");
+}
+
+static void
+match(void)
+{
+
+	if (fuzzy) {
+		fuzzymatch();
+		return;
+	}
+	static char **tokv = NULL;
+	static int tokn = 0;
+
+	char buf[sizeof text], *s;
+	int i, tokc = 0;
+	size_t len, textsize;
+	struct item *item, *lprefix, *lsubstr, *prefixend, *substrend;
+
+	strcpy(buf, text);
+	/* separate input text into tokens to be matched individually */
+	for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " "))
+		if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
+			die("cannot realloc %zu bytes:", tokn * sizeof *tokv);
+	len = tokc ? strlen(tokv[0]) : 0;
+
+	matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
+	textsize = strlen(text) + 1;
+	for (item = items; item && item->text; item++)
+	{
+		for (i = 0; i < tokc; i++)
+			if (!fstrstr(item->text, tokv[i]))
+				break;
+		if (i != tokc) /* not all tokens match */
+			continue;
+		/* exact matches go first, then prefixes, then substrings */
+		if (!tokc || !fstrncmp(text, item->text, textsize))
+			appenditem(item, &matches, &matchend);
+		else if (!fstrncmp(tokv[0], item->text, len))
+			appenditem(item, &lprefix, &prefixend);
+		else
+			appenditem(item, &lsubstr, &substrend);
+	}
+	if (lprefix) {
+		if (matches) {
+			matchend->right = lprefix;
+			lprefix->left = matchend;
+		} else
+			matches = lprefix;
+		matchend = prefixend;
+	}
+	if (lsubstr)
+	{
+		if (matches) {
+			matchend->right = lsubstr;
+			lsubstr->left = matchend;
+		} else
+			matches = lsubstr;
+		matchend = substrend;
+	}
+	curr = sel = matches;
+
+	calcoffsets();
+}
+
+static void
+insert(const char *str, ssize_t n)
+{
+	if (strlen(text) + n > sizeof text - 1)
+		return;
+
+	/* move existing text out of the way, insert new text, and update cursor */
+	memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0));
+	if (n > 0)
+		memcpy(&text[cursor], str, n);
+	cursor += n;
+	match();
+
+}
+
+static size_t
+nextrune(int inc)
+{
+	ssize_t n;
+
+	/* return location of next utf8 rune in the given direction (+1 or -1) */
+	for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc)
+		;
+	return n;
+}
+
+static void
+movewordedge(int dir)
+{
+	if (dir < 0) { /* move cursor to the start of the word*/
+		while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
+			cursor = nextrune(-1);
+		while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
+			cursor = nextrune(-1);
+	} else { /* move cursor to the end of the word */
+		while (text[cursor] && strchr(worddelimiters, text[cursor]))
+			cursor = nextrune(+1);
+		while (text[cursor] && !strchr(worddelimiters, text[cursor]))
+			cursor = nextrune(+1);
+	}
+}
+
+static void
+keypress(XKeyEvent *ev)
+{
+	char buf[64];
+	int len;
+	KeySym ksym = NoSymbol;
+	Status status;
+
+	len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
+	switch (status) {
+	default: /* XLookupNone, XBufferOverflow */
+		return;
+	case XLookupChars: /* composed string from input method */
+		goto insert;
+	case XLookupKeySym:
+	case XLookupBoth: /* a KeySym and a string are returned: use keysym */
+		break;
+	}
+
+	if (ev->state & ControlMask) {
+		switch(ksym) {
+		case XK_a: ksym = XK_Home;      break;
+		case XK_b: ksym = XK_Left;      break;
+		case XK_c: ksym = XK_Escape;    break;
+		case XK_d: ksym = XK_Delete;    break;
+		case XK_e: ksym = XK_End;       break;
+		case XK_f: ksym = XK_Right;     break;
+		case XK_g: ksym = XK_Escape;    break;
+		case XK_h: ksym = XK_BackSpace; break;
+		case XK_i: ksym = XK_Tab;       break;
+		case XK_j: /* fallthrough */
+		case XK_J: /* fallthrough */
+		case XK_m: /* fallthrough */
+		case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break;
+		case XK_n: ksym = XK_Down;      break;
+		case XK_p: ksym = XK_Up;        break;
+
+		case XK_k: /* delete right */
+			text[cursor] = '\0';
+			match();
+			break;
+		case XK_u: /* delete left */
+			insert(NULL, 0 - cursor);
+			break;
+		case XK_w: /* delete word */
+			while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
+				insert(NULL, nextrune(-1) - cursor);
+			while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
+				insert(NULL, nextrune(-1) - cursor);
+			break;
+		case XK_v:
+		case XK_V:
+			XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
+			                  utf8, utf8, win, CurrentTime);
+			return;
+		case XK_y: /* paste selection */
+		case XK_Y:
+			XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
+			                  utf8, utf8, win, CurrentTime);
+			return;
+		case XK_Left:
+		case XK_KP_Left:
+			movewordedge(-1);
+			goto draw;
+		case XK_Right:
+		case XK_KP_Right:
+			movewordedge(+1);
+			goto draw;
+		case XK_Return:
+		case XK_KP_Enter:
+			break;
+		case XK_bracketleft:
+			cleanup();
+			exit(1);
+		default:
+			return;
+		}
+	} else if (ev->state & Mod1Mask) {
+		switch(ksym) {
+		case XK_b:
+			movewordedge(-1);
+			goto draw;
+		case XK_f:
+			movewordedge(+1);
+			goto draw;
+		case XK_g: ksym = XK_Home;  break;
+		case XK_G: ksym = XK_End;   break;
+		case XK_h: ksym = XK_Up;    break;
+		case XK_j: ksym = XK_Next;  break;
+		case XK_k: ksym = XK_Prior; break;
+		case XK_l: ksym = XK_Down;  break;
+		default:
+			return;
+		}
+	}
+
+	switch(ksym) {
+	default:
+insert:
+		if (!iscntrl((unsigned char)*buf))
+			insert(buf, len);
+		break;
+	case XK_Delete:
+	case XK_KP_Delete:
+		if (text[cursor] == '\0')
+			return;
+		cursor = nextrune(+1);
+		/* fallthrough */
+	case XK_BackSpace:
+		if (cursor == 0)
+			return;
+		insert(NULL, nextrune(-1) - cursor);
+		break;
+	case XK_End:
+	case XK_KP_End:
+		if (text[cursor] != '\0') {
+			cursor = strlen(text);
+			break;
+		}
+		if (next) {
+			/* jump to end of list and position items in reverse */
+			curr = matchend;
+			calcoffsets();
+			curr = prev;
+			calcoffsets();
+			while (next && (curr = curr->right))
+				calcoffsets();
+		}
+		sel = matchend;
+		break;
+	case XK_Escape:
+		cleanup();
+		exit(1);
+	case XK_Home:
+	case XK_KP_Home:
+		if (sel == matches) {
+			cursor = 0;
+			break;
+		}
+		sel = curr = matches;
+		calcoffsets();
+		break;
+	case XK_Left:
+	case XK_KP_Left:
+		if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
+			cursor = nextrune(-1);
+			break;
+		}
+		if (lines > 0)
+			return;
+		/* fallthrough */
+	case XK_Up:
+	case XK_KP_Up:
+		if (sel && sel->left && (sel = sel->left)->right == curr) {
+			curr = prev;
+			calcoffsets();
+		}
+		break;
+	case XK_Next:
+	case XK_KP_Next:
+		if (!next)
+			return;
+		sel = curr = next;
+		calcoffsets();
+		break;
+	case XK_Prior:
+	case XK_KP_Prior:
+		if (!prev)
+			return;
+		sel = curr = prev;
+		calcoffsets();
+		break;
+	case XK_Return:
+	case XK_KP_Enter:
+		puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
+		if (!(ev->state & ControlMask)) {
+			cleanup();
+			exit(0);
+		}
+		if (sel)
+			sel->out = 1;
+		break;
+	case XK_Right:
+	case XK_KP_Right:
+		if (text[cursor] != '\0') {
+			cursor = nextrune(+1);
+			break;
+		}
+		if (lines > 0)
+			return;
+		/* fallthrough */
+	case XK_Down:
+	case XK_KP_Down:
+		if (sel && sel->right && (sel = sel->right) == next) {
+			curr = next;
+			calcoffsets();
+		}
+		break;
+	case XK_Tab:
+		if (!sel)
+			return;
+		cursor = strnlen(sel->text, sizeof text - 1);
+		memcpy(text, sel->text, cursor);
+		text[cursor] = '\0';
+		match();
+		break;
+	}
+
+draw:
+	drawmenu();
+}
+
+static void
+paste(void)
+{
+	char *p, *q;
+	int di;
+	unsigned long dl;
+	Atom da;
+
+	/* we have been given the current selection, now insert it into input */
+	if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False,
+	                   utf8, &da, &di, &dl, &dl, (unsigned char **)&p)
+	    == Success && p) {
+		insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p));
+		XFree(p);
+	}
+	drawmenu();
+}
+
+static void
+readstdin(void)
+{
+	char *line = NULL;
+	char *buf, *p;
+
+	size_t i, linesiz, itemsiz = 0;
+	ssize_t len;
+
+	if (passwd) {
+		inputw = lines = 0;
+		return;
+	}
+
+	/* read each line from stdin and add it to the item list */
+	for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) {
+		if (i + 1 >= itemsiz) {
+			itemsiz += 256;
+			if (!(items = realloc(items, itemsiz * sizeof(*items))))
+				die("cannot realloc %zu bytes:", itemsiz * sizeof(*items));
+		}
+		if (line[len - 1] == '\n')
+			line[len - 1] = '\0';
+
+		if (!(items[i].text = strdup(line)))
+			die("strdup:");
+		if (!(buf = strdup(line)))
+			die("cannot strdup %u bytes:", strlen(line) + 1);
+		if ((p = strchr(buf, '\t')))
+			*p = '\0';
+		items[i].stext = buf;
+		items[i].out = 0;
+
+	}
+	free(line);
+	if (items)
+		items[i].text = NULL;
+	lines = MIN(lines, i);
+}
+
+static void
+run(void)
+{
+	XEvent ev;
+
+	while (!XNextEvent(dpy, &ev)) {
+		if (XFilterEvent(&ev, win))
+			continue;
+		switch(ev.type) {
+		case DestroyNotify:
+			if (ev.xdestroywindow.window != win)
+				break;
+			cleanup();
+			exit(1);
+		case Expose:
+			if (ev.xexpose.count == 0)
+				drw_map(drw, win, 0, 0, mw, mh);
+			break;
+		case FocusIn:
+			/* regrab focus from parent window */
+			if (ev.xfocus.window != win)
+				grabfocus();
+			break;
+		case KeyPress:
+			keypress(&ev.xkey);
+			break;
+		case SelectionNotify:
+			if (ev.xselection.property == utf8)
+				paste();
+			break;
+		case VisibilityNotify:
+			if (ev.xvisibility.state != VisibilityUnobscured)
+				XRaiseWindow(dpy, win);
+			break;
+		}
+	}
+}
+
+static void
+setup(void)
+{
+	int x, y, i, j;
+	unsigned int du;
+	XSetWindowAttributes swa;
+	XIM xim;
+	Window w, dw, *dws;
+	XWindowAttributes wa;
+	XClassHint ch = {"dmenu", "dmenu"};
+#ifdef XINERAMA
+	XineramaScreenInfo *info;
+	Window pw;
+	int a, di, n, area = 0;
+#endif
+	/* init appearance */
+	for (j = 0; j < SchemeLast; j++)
+		scheme[j] = drw_scm_create(drw, colors[j], 2);
+
+	clip = XInternAtom(dpy, "CLIPBOARD",   False);
+	utf8 = XInternAtom(dpy, "UTF8_STRING", False);
+
+	/* calculate menu geometry */
+	bh = drw->font->h + 2;
+	lines = MAX(lines, 0);
+	mh = (lines + 1) * bh;
+#ifdef XINERAMA
+	i = 0;
+	if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
+		XGetInputFocus(dpy, &w, &di);
+		if (mon >= 0 && mon < n)
+			i = mon;
+		else if (w != root && w != PointerRoot && w != None) {
+			/* find top-level window containing current input focus */
+			do {
+				if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws)
+					XFree(dws);
+			} while (w != root && w != pw);
+			/* find xinerama screen with which the window intersects most */
+			if (XGetWindowAttributes(dpy, pw, &wa))
+				for (j = 0; j < n; j++)
+					if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) {
+						area = a;
+						i = j;
+					}
+		}
+		/* no focused window is on screen, so use pointer location instead */
+		if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du))
+			for (i = 0; i < n; i++)
+				if (INTERSECT(x, y, 1, 1, info[i]) != 0)
+					break;
+
+		x = info[i].x_org;
+		y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
+		mw = info[i].width;
+		XFree(info);
+	} else
+#endif
+	{
+		if (!XGetWindowAttributes(dpy, parentwin, &wa))
+			die("could not get embedding window attributes: 0x%lx",
+			    parentwin);
+		x = 0;
+		y = topbar ? 0 : wa.height - mh;
+		mw = wa.width;
+	}
+	promptw = (prompt && *prompt) ? TEXTWM(prompt) - lrpad / 4 : 0;
+	inputw = mw / 3; /* input width: ~33.33% of monitor width */
+	match();
+
+	/* create menu window */
+	swa.override_redirect = True;
+	swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
+	swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask
+	;
+	win = XCreateWindow(
+		dpy, root,
+		x, y, mw, mh, 0,
+		CopyFromParent, CopyFromParent, CopyFromParent,
+		CWOverrideRedirect | CWBackPixel | CWEventMask, &swa
+	);
+	XSetClassHint(dpy, win, &ch);
+
+	/* input methods */
+	if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
+		die("XOpenIM failed: could not open input device");
+
+	xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
+	                XNClientWindow, win, XNFocusWindow, win, NULL);
+
+	XMapRaised(dpy, win);
+	if (embed) {
+		XReparentWindow(dpy, win, parentwin, x, y);
+		XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask);
+		if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) {
+			for (i = 0; i < du && dws[i] != win; ++i)
+				XSelectInput(dpy, dws[i], FocusChangeMask);
+			XFree(dws);
+		}
+		grabfocus();
+	}
+	drw_resize(drw, mw, mh);
+	drawmenu();
+}
+
+static void
+usage(void)
+{
+	die("usage: dmenu [-bv"
+		"f"
+		"i"
+		"F"
+		"P"
+		"] "
+		"[-l lines] [-p prompt] [-fn font] [-m monitor]"
+		"\n             [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"
+		"\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+	XWindowAttributes wa;
+	int i;
+	int fast = 0;
+
+	for (i = 1; i < argc; i++) {
+		if (argv[i][0] == '\0')
+			continue;
+
+		/* these options take no arguments */
+		if (!strcmp(argv[i], "-v")) {      /* prints version information */
+			puts("dmenu-"VERSION);
+			exit(0);
+		} else if (!strcmp(argv[i], "-b")) { /* appears at the bottom of the screen */
+			topbar = 0;
+		} else if (!strcmp(argv[i], "-f")) { /* grabs keyboard before reading stdin */
+			fast = 1;
+		} else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
+			fstrncmp = strncasecmp;
+			fstrstr = cistrstr;
+		} else if (!strcmp(argv[i], "-F")) { /* disable/enable fuzzy matching, depends on default */
+			fuzzy = !fuzzy;
+		} else if (!strcmp(argv[i], "-P")) { /* is the input a password */
+			passwd = 1;
+		} else if (i + 1 == argc)
+			usage();
+		/* these options take one argument */
+		else if (!strcmp(argv[i], "-l"))   /* number of lines in vertical list */
+			lines = atoi(argv[++i]);
+		else if (!strcmp(argv[i], "-m"))
+			mon = atoi(argv[++i]);
+		else if (!strcmp(argv[i], "-p"))   /* adds prompt to left of input field */
+			prompt = argv[++i];
+		else if (!strcmp(argv[i], "-fn"))  /* font or font set */
+			font = argv[++i];
+		else if (!strcmp(argv[i], "-nb"))  /* normal background color */
+			colors[SchemeNorm][ColBg] = argv[++i];
+		else if (!strcmp(argv[i], "-nf"))  /* normal foreground color */
+			colors[SchemeNorm][ColFg] = argv[++i];
+		else if (!strcmp(argv[i], "-sb"))  /* selected background color */
+			colors[SchemeSel][ColBg] = argv[++i];
+		else if (!strcmp(argv[i], "-sf"))  /* selected foreground color */
+			colors[SchemeSel][ColFg] = argv[++i];
+		else if (!strcmp(argv[i], "-w"))   /* embedding window id */
+			embed = argv[++i];
+		else {
+			usage();
+		}
+	}
+
+	if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
+		fputs("warning: no locale support\n", stderr);
+	if (!(dpy = XOpenDisplay(NULL)))
+		die("cannot open display");
+	screen = DefaultScreen(dpy);
+	root = RootWindow(dpy, screen);
+	if (!embed || !(parentwin = strtol(embed, NULL, 0)))
+		parentwin = root;
+	if (!XGetWindowAttributes(dpy, parentwin, &wa))
+		die("could not get embedding window attributes: 0x%lx",
+		    parentwin);
+
+	drw = drw_create(dpy, screen, root, wa.width, wa.height);
+
+	if (!drw_font_create(drw, font))
+		die("no fonts could be loaded.");
+
+	lrpad = drw->font->h;
+
+#ifdef __OpenBSD__
+	if (pledge("stdio rpath", NULL) == -1)
+		die("pledge");
+#endif
+
+	if (fast && !isatty(0)) {
+		grabkeyboard();
+		readstdin();
+	} else {
+		readstdin();
+		grabkeyboard();
+	}
+	setup();
+	run();
+
+	return 1; /* unreachable */
+}
diff --git a/dmenu_path b/dmenu_path
new file mode 100755
index 0000000..3a7cda7
--- /dev/null
+++ b/dmenu_path
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}"
+cache="$cachedir/dmenu_run"
+
+[ ! -e "$cachedir" ] && mkdir -p "$cachedir"
+
+IFS=:
+if stest -dqr -n "$cache" $PATH; then
+	stest -flx $PATH | sort -u | tee "$cache"
+else
+	cat "$cache"
+fi
diff --git a/dmenu_run b/dmenu_run
new file mode 100755
index 0000000..e6d1355
--- /dev/null
+++ b/dmenu_run
@@ -0,0 +1,6 @@
+#!/bin/sh
+export _JAVA_AWT_WM_NONREPARENTING=1
+dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &
+
+# Uncomment for the NAVHISTORY patch (and remove the exec above)
+#dmenu_path | dmenu -H "${XDG_CACHE_HOME:-$HOME/.cache/}/dmenu_run.hist" "$@" | ${SHELL:-"/bin/sh"} &
\ No newline at end of file
diff --git a/drw.c b/drw.c
new file mode 100644
index 0000000..cef24db
--- /dev/null
+++ b/drw.c
@@ -0,0 +1,297 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xlib.h>
+#include <X11/Xft/Xft.h>
+
+#include "drw.h"
+#include "util.h"
+
+Drw *
+drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
+{
+	Drw *drw = ecalloc(1, sizeof(Drw));
+
+	drw->dpy = dpy;
+	drw->screen = screen;
+	drw->root = root;
+	drw->w = w;
+	drw->h = h;
+	drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
+	drw->gc = XCreateGC(dpy, root, 0, NULL);
+	XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
+
+	return drw;
+}
+
+void
+drw_resize(Drw *drw, unsigned int w, unsigned int h)
+{
+	if (!drw)
+		return;
+
+	drw->w = w;
+	drw->h = h;
+	if (drw->drawable)
+		XFreePixmap(drw->dpy, drw->drawable);
+	drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen));
+}
+
+void
+drw_free(Drw *drw)
+{
+	XFreePixmap(drw->dpy, drw->drawable);
+	XFreeGC(drw->dpy, drw->gc);
+	drw_font_free(drw->font);
+	free(drw);
+}
+
+/* This function is an implementation detail. Library users should use
+ * drw_font_create instead.
+ */
+static Fnt *
+xfont_create(Drw *drw, const char *fontname)
+{
+	Fnt *font;
+	PangoFontMap *fontmap;
+	PangoContext *context;
+	PangoFontDescription *desc;
+	PangoFontMetrics *metrics;
+
+	if (!fontname) {
+		die("no font specified.");
+	}
+
+	font = ecalloc(1, sizeof(Fnt));
+	font->dpy = drw->dpy;
+
+	fontmap = pango_xft_get_font_map(drw->dpy, drw->screen);
+	context = pango_font_map_create_context(fontmap);
+	desc = pango_font_description_from_string(fontname);
+	font->layout = pango_layout_new(context);
+	pango_layout_set_font_description(font->layout, desc);
+
+	metrics = pango_context_get_metrics(context, desc, pango_language_from_string ("en-us"));
+	font->h = pango_font_metrics_get_height(metrics) / PANGO_SCALE;
+
+	pango_font_metrics_unref(metrics);
+	g_object_unref(context);
+
+	return font;
+}
+
+static void
+xfont_free(Fnt *font)
+{
+	if (!font)
+		return;
+	if (font->layout)
+		g_object_unref(font->layout);
+	free(font);
+}
+
+Fnt*
+drw_font_create(Drw* drw, const char *font)
+{
+	Fnt *fnt = NULL;
+
+	if (!drw || !font)
+		return NULL;
+
+	fnt = xfont_create(drw, font);
+
+	return (drw->font = fnt);
+}
+
+void
+drw_font_free(Fnt *font)
+{
+	if (font)
+		xfont_free(font);
+}
+
+void
+drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
+{
+	if (!drw || !dest || !clrname)
+		return;
+
+	if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
+	                       DefaultColormap(drw->dpy, drw->screen),
+	                       clrname, dest))
+		die("error, cannot allocate color '%s'", clrname);
+}
+
+/* Wrapper to create color schemes. The caller has to call free(3) on the
+ * returned color scheme when done using it. */
+Clr *
+drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
+{
+	size_t i;
+	Clr *ret;
+
+	/* need at least two colors for a scheme */
+	if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
+		return NULL;
+
+	for (i = 0; i < clrcount; i++)
+		drw_clr_create(drw, &ret[i], clrnames[i]);
+	return ret;
+}
+
+void
+drw_setscheme(Drw *drw, Clr *scm)
+{
+	if (drw)
+		drw->scheme = scm;
+}
+
+void
+drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
+{
+	if (!drw || !drw->scheme)
+		return;
+	XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
+	if (filled)
+		XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+	else
+		XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
+}
+
+int
+drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert, Bool markup)
+{
+	char buf[1024];
+	int i, ty, th;
+	unsigned int ew, eh;
+	XftDraw *d = NULL;
+	size_t len;
+	int render = x || y || w || h;
+
+	if (!drw || (render && !drw->scheme) || !text || !drw->font)
+		return 0;
+
+	if (!render) {
+		w = invert ? invert : ~invert;
+	} else {
+		XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
+		XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+		d = XftDrawCreate(drw->dpy, drw->drawable,
+		                  DefaultVisual(drw->dpy, drw->screen),
+		                  DefaultColormap(drw->dpy, drw->screen));
+		x += lpad;
+		w -= lpad;
+	}
+
+	len = strlen(text);
+
+	if (len) {
+		drw_font_getexts(drw->font, text, len, &ew, &eh, markup);
+		th = eh;
+		/* shorten text if necessary */
+		for (len = MIN(len, sizeof(buf) - 1); len && ew > w; len--) {
+			drw_font_getexts(drw->font, text, len, &ew, &eh, markup);
+			if (eh > th)
+				th = eh;
+		}
+
+		if (len) {
+			memcpy(buf, text, len);
+			buf[len] = '\0';
+			if (len < strlen(text))
+				for (i = len; i && i > len - 3; buf[--i] = '.')
+					; /* NOP */
+
+			if (render) {
+				ty = y + (h - th) / 2;
+				if (markup)
+					pango_layout_set_markup(drw->font->layout, buf, len);
+				else
+					pango_layout_set_text(drw->font->layout, buf, len);
+				pango_xft_render_layout(d, &drw->scheme[invert ? ColBg : ColFg],
+					drw->font->layout, x * PANGO_SCALE, ty * PANGO_SCALE);
+				if (markup) /* clear markup attributes */
+					pango_layout_set_attributes(drw->font->layout, NULL);
+			}
+			x += ew;
+			w -= ew;
+		}
+	}
+
+	if (d)
+		XftDrawDestroy(d);
+
+	return x + (render ? w : 0);
+}
+
+void
+drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
+{
+	if (!drw)
+		return;
+
+	XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
+	XSync(drw->dpy, False);
+}
+
+unsigned int
+drw_font_getwidth(Drw *drw, const char *text, Bool markup)
+{
+	if (!drw || !drw->font || !text)
+		return 0;
+	return drw_text(drw, 0, 0, 0, 0, 0, text, 0, markup);
+}
+
+unsigned int
+drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n)
+{
+	unsigned int tmp = 0;
+	if (drw && drw->font && text && n)
+		tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n, True);
+	return MIN(n, tmp);
+}
+
+void
+drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h, Bool markup)
+{
+	if (!font || !text)
+		return;
+
+	PangoRectangle r;
+	if (markup)
+		pango_layout_set_markup(font->layout, text, len);
+	else
+		pango_layout_set_text(font->layout, text, len);
+	pango_layout_get_extents(font->layout, 0, &r);
+	if (markup) /* clear markup attributes */
+		pango_layout_set_attributes(font->layout, NULL);
+	if (w)
+		*w = r.width / PANGO_SCALE;
+	if (h)
+		*h = r.height / PANGO_SCALE;
+}
+
+Cur *
+drw_cur_create(Drw *drw, int shape)
+{
+	Cur *cur;
+
+	if (!drw || !(cur = ecalloc(1, sizeof(Cur))))
+		return NULL;
+
+	cur->cursor = XCreateFontCursor(drw->dpy, shape);
+
+	return cur;
+}
+
+void
+drw_cur_free(Drw *drw, Cur *cursor)
+{
+	if (!cursor)
+		return;
+
+	XFreeCursor(drw->dpy, cursor->cursor);
+	free(cursor);
+}
+
diff --git a/drw.h b/drw.h
new file mode 100644
index 0000000..e619d0c
--- /dev/null
+++ b/drw.h
@@ -0,0 +1,59 @@
+/* See LICENSE file for copyright and license details. */
+
+#include <pango/pango.h>
+#include <pango/pangoxft.h>
+
+typedef struct {
+	Cursor cursor;
+} Cur;
+
+typedef struct Fnt {
+	Display *dpy;
+	unsigned int h;
+	PangoLayout *layout;
+} Fnt;
+
+enum { ColFg, ColBg }; /* Clr scheme index */
+typedef XftColor Clr;
+
+typedef struct {
+	unsigned int w, h;
+	Display *dpy;
+	int screen;
+	Window root;
+	Drawable drawable;
+	GC gc;
+	Clr *scheme;
+	Fnt *font;
+} Drw;
+
+/* Drawable abstraction */
+Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h);
+void drw_resize(Drw *drw, unsigned int w, unsigned int h);
+void drw_free(Drw *drw);
+
+/* Fnt abstraction */
+Fnt *drw_font_create(Drw* drw, const char *font);
+void drw_font_free(Fnt* set);
+unsigned int drw_font_getwidth(Drw *drw, const char *text, Bool markup);
+unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n);
+void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h, Bool markup);
+
+/* Colorscheme abstraction */
+void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
+Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
+
+/* Cursor abstraction */
+Cur *drw_cur_create(Drw *drw, int shape);
+void drw_cur_free(Drw *drw, Cur *cursor);
+
+/* Drawing context manipulation */
+void drw_setscheme(Drw *drw, Clr *scm);
+
+/* Drawing functions */
+void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
+int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert, Bool markup);
+
+/* Map functions */
+void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
+
diff --git a/flexipatch-finalizer b/flexipatch-finalizer
deleted file mode 160000
index 37e8cb6..0000000
--- a/flexipatch-finalizer
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 37e8cb6145f189b805aa58c7d560b1adf51377a7
diff --git a/patch/fuzzymatch.c b/patch/fuzzymatch.c
new file mode 100644
index 0000000..2ae8654
--- /dev/null
+++ b/patch/fuzzymatch.c
@@ -0,0 +1,83 @@
+#include <math.h>
+
+int
+compare_distance(const void *a, const void *b)
+{
+	struct item *da = *(struct item **) a;
+	struct item *db = *(struct item **) b;
+
+	if (!db)
+		return 1;
+	if (!da)
+		return -1;
+
+	return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1;
+}
+
+void
+fuzzymatch(void)
+{
+	/* bang - we have so much memory */
+	struct item *it;
+	struct item **fuzzymatches = NULL;
+	char c;
+	int number_of_matches = 0, i, pidx, sidx, eidx;
+	int text_len = strlen(text), itext_len;
+	matches = matchend = NULL;
+
+	/* walk through all items */
+	for (it = items; it && it->text; it++) {
+		if (text_len) {
+			itext_len = strlen(it->text);
+			pidx = 0; /* pointer */
+			sidx = eidx = -1; /* start of match, end of match */
+			/* walk through item text */
+			for (i = 0; i < itext_len && (c = it->text[i]); i++) {
+				/* fuzzy match pattern */
+				if (!fstrncmp(&text[pidx], &c, 1)) {
+					if (sidx == -1)
+						sidx = i;
+					pidx++;
+					if (pidx == text_len) {
+						eidx = i;
+						break;
+					}
+				}
+			}
+			/* build list of matches */
+			if (eidx != -1) {
+				/* compute distance */
+				/* add penalty if match starts late (log(sidx+2))
+				 * add penalty for long a match without many matching characters */
+				it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len);
+				/* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */
+				appenditem(it, &matches, &matchend);
+				number_of_matches++;
+			}
+		} else {
+			appenditem(it, &matches, &matchend);
+		}
+	}
+
+	if (number_of_matches) {
+		/* initialize array with matches */
+		if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*))))
+			die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*));
+		for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) {
+			fuzzymatches[i] = it;
+		}
+
+		/* sort matches according to distance */
+		qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance);
+		/* rebuild list of matches */
+		matches = matchend = NULL;
+		for (i = 0, it = fuzzymatches[i];  i < number_of_matches && it && \
+				it->text; i++, it = fuzzymatches[i]) {
+			appenditem(it, &matches, &matchend);
+		}
+		free(fuzzymatches);
+	}
+	curr = sel = matches;
+
+	calcoffsets();
+}
diff --git a/patch/include.c b/patch/include.c
new file mode 100644
index 0000000..0fda441
--- /dev/null
+++ b/patch/include.c
@@ -0,0 +1 @@
+#include "fuzzymatch.c"
diff --git a/patch/include.h b/patch/include.h
new file mode 100644
index 0000000..c3cb75b
--- /dev/null
+++ b/patch/include.h
@@ -0,0 +1,18 @@
+#if DYNAMIC_OPTIONS_PATCH
+#include "dynamicoptions.h"
+#endif
+#if FZFEXPECT_PATCH
+#include "fzfexpect.h"
+#endif
+#if MULTI_SELECTION_PATCH
+#include "multiselect.h"
+#endif
+#if HIGHPRIORITY_PATCH
+#include "highpriority.h"
+#endif
+#if NON_BLOCKING_STDIN_PATCH
+#include "nonblockingstdin.h"
+#endif
+#if NUMBERS_PATCH
+#include "numbers.h"
+#endif
diff --git a/stest.1 b/stest.1
new file mode 100644
index 0000000..2667d8a
--- /dev/null
+++ b/stest.1
@@ -0,0 +1,90 @@
+.TH STEST 1 dmenu\-VERSION
+.SH NAME
+stest \- filter a list of files by properties
+.SH SYNOPSIS
+.B stest
+.RB [ -abcdefghlpqrsuwx ]
+.RB [ -n
+.IR file ]
+.RB [ -o
+.IR file ]
+.RI [ file ...]
+.SH DESCRIPTION
+.B stest
+takes a list of files and filters by the files' properties, analogous to
+.IR test (1).
+Files which pass all tests are printed to stdout. If no files are given, stest
+reads files from stdin.
+.SH OPTIONS
+.TP
+.B \-a
+Test hidden files.
+.TP
+.B \-b
+Test that files are block specials.
+.TP
+.B \-c
+Test that files are character specials.
+.TP
+.B \-d
+Test that files are directories.
+.TP
+.B \-e
+Test that files exist.
+.TP
+.B \-f
+Test that files are regular files.
+.TP
+.B \-g
+Test that files have their set-group-ID flag set.
+.TP
+.B \-h
+Test that files are symbolic links.
+.TP
+.B \-l
+Test the contents of a directory given as an argument.
+.TP
+.BI \-n " file"
+Test that files are newer than
+.IR file .
+.TP
+.BI \-o " file"
+Test that files are older than
+.IR file .
+.TP
+.B \-p
+Test that files are named pipes.
+.TP
+.B \-q
+No files are printed, only the exit status is returned.
+.TP
+.B \-r
+Test that files are readable.
+.TP
+.B \-s
+Test that files are not empty.
+.TP
+.B \-u
+Test that files have their set-user-ID flag set.
+.TP
+.B \-v
+Invert the sense of tests, only failing files pass.
+.TP
+.B \-w
+Test that files are writable.
+.TP
+.B \-x
+Test that files are executable.
+.SH EXIT STATUS
+.TP
+.B 0
+At least one file passed all tests.
+.TP
+.B 1
+No files passed all tests.
+.TP
+.B 2
+An error occurred.
+.SH SEE ALSO
+.IR dmenu (1),
+.IR test (1)
diff --git a/stest.c b/stest.c
new file mode 100644
index 0000000..e27d3a5
--- /dev/null
+++ b/stest.c
@@ -0,0 +1,109 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "arg.h"
+char *argv0;
+
+#define FLAG(x)  (flag[(x)-'a'])
+
+static void test(const char *, const char *);
+static void usage(void);
+
+static int match = 0;
+static int flag[26];
+static struct stat old, new;
+
+static void
+test(const char *path, const char *name)
+{
+	struct stat st, ln;
+
+	if ((!stat(path, &st) && (FLAG('a') || name[0] != '.')        /* hidden files      */
+	&& (!FLAG('b') || S_ISBLK(st.st_mode))                        /* block special     */
+	&& (!FLAG('c') || S_ISCHR(st.st_mode))                        /* character special */
+	&& (!FLAG('d') || S_ISDIR(st.st_mode))                        /* directory         */
+	&& (!FLAG('e') || access(path, F_OK) == 0)                    /* exists            */
+	&& (!FLAG('f') || S_ISREG(st.st_mode))                        /* regular file      */
+	&& (!FLAG('g') || st.st_mode & S_ISGID)                       /* set-group-id flag */
+	&& (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link     */
+	&& (!FLAG('n') || st.st_mtime > new.st_mtime)                 /* newer than file   */
+	&& (!FLAG('o') || st.st_mtime < old.st_mtime)                 /* older than file   */
+	&& (!FLAG('p') || S_ISFIFO(st.st_mode))                       /* named pipe        */
+	&& (!FLAG('r') || access(path, R_OK) == 0)                    /* readable          */
+	&& (!FLAG('s') || st.st_size > 0)                             /* not empty         */
+	&& (!FLAG('u') || st.st_mode & S_ISUID)                       /* set-user-id flag  */
+	&& (!FLAG('w') || access(path, W_OK) == 0)                    /* writable          */
+	&& (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) {   /* executable        */
+		if (FLAG('q'))
+			exit(0);
+		match = 1;
+		puts(name);
+	}
+}
+
+static void
+usage(void)
+{
+	fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] "
+	        "[-n file] [-o file] [file...]\n", argv0);
+	exit(2); /* like test(1) return > 1 on error */
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct dirent *d;
+	char path[PATH_MAX], *line = NULL, *file;
+	size_t linesiz = 0;
+	ssize_t n;
+	DIR *dir;
+	int r;
+
+	ARGBEGIN {
+	case 'n': /* newer than file */
+	case 'o': /* older than file */
+		file = EARGF(usage());
+		if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old))))
+			perror(file);
+		break;
+	default:
+		/* miscellaneous operators */
+		if (strchr("abcdefghlpqrsuvwx", ARGC()))
+			FLAG(ARGC()) = 1;
+		else
+			usage(); /* unknown flag */
+	} ARGEND;
+
+	if (!argc) {
+		/* read list from stdin */
+		while ((n = getline(&line, &linesiz, stdin)) > 0) {
+			if (line[n - 1] == '\n')
+				line[n - 1] = '\0';
+			test(line, line);
+		}
+		free(line);
+	} else {
+		for (; argc; argc--, argv++) {
+			if (FLAG('l') && (dir = opendir(*argv))) {
+				/* test directory contents */
+				while ((d = readdir(dir))) {
+					r = snprintf(path, sizeof path, "%s/%s",
+					             *argv, d->d_name);
+					if (r >= 0 && (size_t)r < sizeof path)
+						test(path, d->d_name);
+				}
+				closedir(dir);
+			} else {
+				test(*argv, *argv);
+			}
+		}
+	}
+	return match ? 0 : 1;
+}
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..96b82c9
--- /dev/null
+++ b/util.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+void
+die(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+
+	if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+		fputc(' ', stderr);
+		perror(NULL);
+	} else {
+		fputc('\n', stderr);
+	}
+
+	exit(1);
+}
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+	void *p;
+
+	if (!(p = calloc(nmemb, size)))
+		die("calloc:");
+	return p;
+}
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..2922d77
--- /dev/null
+++ b/util.h
@@ -0,0 +1,13 @@
+/* See LICENSE file for copyright and license details. */
+
+#ifndef MAX
+#define MAX(A, B)               ((A) > (B) ? (A) : (B))
+#endif
+#ifndef MIN
+#define MIN(A, B)               ((A) < (B) ? (A) : (B))
+#endif
+#define BETWEEN(X, A, B)        ((A) <= (X) && (X) <= (B))
+#define LENGTH(X)               (sizeof (X) / sizeof (X)[0])
+
+void die(const char *fmt, ...);
+void *ecalloc(size_t nmemb, size_t size);