dmenu

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 097f38a4bd85760e5cf322eb973d2f671673c289
parent ae389797788cc66d3b93c92fe8639d68d47b8b39
Author: Kris Yotam <krisyotam@protonmail.com>
Date:   Fri, 13 Feb 2026 14:53:48 -0600

Migrate to dmenu-flexipatch

Enables preprocessor-based patch management with the following patches:
- alpha: Transparency support
- border: Window border
- caseinsensitive: Case-insensitive matching by default
- center: Centered dmenu window
- ctrl_v_to_paste: Ctrl+V paste support
- fuzzymatch: Fuzzy matching
- highlight: Highlight matched text
- line_height: Configurable line height
- mouse_support: Mouse interaction
- numbers: Show item count
- password: Password input mode
- rejectnomatch: Reject non-matching input
- xresources: Runtime color configuration

Custom settings:
- Font: JetBrainsMono Nerd Font:size=14
- Colors: #2a4055 fg, #9CC2EC bg, #DEC292 selection
- Center enabled by default
- Border width: 2px

Diffstat:
A.gitignore | 4++++
MMakefile | 5++++-
MREADME.md | 138++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mconfig.def.h | 174++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mconfig.h | 174++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mconfig.mk | 20+++++++++++++++++---
Mdmenu.1 | 13++++++++++---
Mdmenu.c | 1836+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mdmenu_run | 5+++++
Mdrw.c | 311+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mdrw.h | 51++++++++++++++++++++++++++++++++++++++++++++++++++-
Apatch/center.c | 8++++++++
Apatch/dynamicoptions.c | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatch/dynamicoptions.h | 2++
Apatch/fuzzymatch.c | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatch/fzfexpect.c | 33+++++++++++++++++++++++++++++++++
Apatch/fzfexpect.h | 1+
Apatch/highlight.c | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatch/highpriority.c | 35+++++++++++++++++++++++++++++++++++
Apatch/highpriority.h | 1+
Apatch/include.c | 42++++++++++++++++++++++++++++++++++++++++++
Apatch/include.h | 30++++++++++++++++++++++++++++++
Apatch/inputmethod.c | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatch/inputmethod.h | 11+++++++++++
Apatch/mousesupport.c | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatch/mousesupport.h | 5+++++
Apatch/multiselect.c | 41+++++++++++++++++++++++++++++++++++++++++
Apatch/multiselect.h | 2++
Apatch/navhistory.c | 234+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatch/navhistory.h | 9+++++++++
Apatch/nonblockingstdin.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatch/nonblockingstdin.h | 1+
Apatch/numbers.c | 16++++++++++++++++
Apatch/numbers.h | 4++++
Apatch/scroll.c | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatch/scroll.h | 3+++
Apatch/vi_mode.c | 188+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatch/vi_mode.h | 6++++++
Apatch/xresources.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatches.def.h | 405+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apatches.h | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutil.c | 13++++++-------
Mutil.h | 4++++
43 files changed, 4905 insertions(+), 207 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +*.o +dmenu +stest +config.h diff --git a/Makefile b/Makefile @@ -14,7 +14,10 @@ all: dmenu stest config.h: cp config.def.h $@ -$(OBJ): arg.h config.h config.mk drw.h +patches.h: + cp patches.def.h $@ + +$(OBJ): arg.h config.h config.mk drw.h patches.h dmenu: dmenu.o drw.o util.o $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS) diff --git a/README.md b/README.md @@ -1,44 +1,53 @@ -# Kris's build of dmenu +# Kris's build of dmenu (flexipatch) My build of [dmenu](https://tools.suckless.org/dmenu/), the dynamic menu for X. -Based on [BreadOnPenguins' dmenu](https://github.com/BreadOnPenguins/dmenu) with center and xresources patches. +This build is based on [dmenu-flexipatch](https://github.com/bakkeby/dmenu-flexipatch), which uses preprocessor directives to manage patches. This allows easy toggling of features without manual patching. --- -## Features +## How Patches Work (Preprocessor Directives) + +Unlike traditional dmenu patching, this build uses C preprocessor directives to include/exclude patches at compile time. To enable or disable a patch, edit `patches.h` and set the value to `1` (enabled) or `0` (disabled): + +```c +#define CENTER_PATCH 1 /* enabled */ +#define VI_MODE_PATCH 0 /* disabled */ +``` -- **Center patch**: dmenu appears centered on screen (`-c` flag) -- **Vertical listing**: Show options in a vertical list (`-l` flag) -- **Xresources**: Read colors from `~/.Xresources` (pywal compatible) -- **Alpha**: Transparency support -- **Emoji**: Can view color characters like emoji -- **Password mode**: `-P` flag hides user input -- **Reject non-matching**: `-r` flag rejects input that doesn't match an option -- **Mouse clickable**: dmenu options are mouse clickable +Then rebuild: +```bash +sudo make clean install +``` + +All patch options are documented in `patches.h`. --- -## Patches +## Features -| Patch | Description | -|-------|-------------| -| [center](https://tools.suckless.org/dmenu/patches/center/) | Center dmenu on screen | -| [xresources](https://tools.suckless.org/dmenu/patches/xresources/) | Read colors and settings from ~/.Xresources | -| [alpha](https://tools.suckless.org/dmenu/patches/alpha/) | Transparency support | -| [password](https://tools.suckless.org/dmenu/patches/password/) | Hide input for password entry | -| mouse support | Click to select options | +| Feature | Description | +|---------|-------------| +| **Centered** | dmenu appears centered on screen (enabled by default) | +| **Fuzzy matching** | Type non-consecutive portions to match | +| **Highlight** | Matched characters are highlighted | +| **Mouse support** | Click to select options | +| **Password mode** | `-P` flag hides input with dots | +| **Transparency** | Alpha support with compositor | +| **Xresources** | Read colors from `~/.Xresources` (pywal compatible) | +| **Numbers** | Shows match count in top right | +| **Border** | Visible border when centered | --- ## Usage ```bash -# Run dmenu centered -dmenu_run -c +# Run dmenu (centered by default) +dmenu_run -# Vertical list centered -echo -e "option1\noption2\noption3" | dmenu -c -l 3 +# Vertical list +echo -e "option1\noption2\noption3" | dmenu -l 5 # Password mode echo -e "option1\noption2" | dmenu -P @@ -47,33 +56,104 @@ echo -e "option1\noption2" | dmenu -P echo -e "yes\nno" | dmenu -r # With prompt -dmenu -c -l 5 -p "Select:" < options.txt +dmenu -l 5 -p "Select:" < options.txt + +# Case-sensitive (default is insensitive) +dmenu -s ``` --- +## Enabled Patches + +Configured in `patches.h`: + +| Patch | Description | +|-------|-------------| +| alpha | Transparency support | +| border | Visible border around dmenu | +| caseinsensitive | Case-insensitive by default | +| center | Centered on screen | +| ctrl_v_to_paste | Ctrl+V paste support | +| fuzzymatch | Fuzzy matching | +| highlight | Highlight matched characters | +| line_height | Configurable line height | +| mouse_support | Click to select | +| numbers | Show match count | +| password | Hide input with -P | +| rejectnomatch | Reject non-matching input | +| xresources | Read from Xresources | + +--- + ## Installation ```bash git clone https://github.com/krisyotam/dmenu cd dmenu -sudo make install +sudo make clean install ``` +> Requires a compositor (`xcompmgr`, `picom`, etc.) for transparency. + --- -## Credits +## Configuration + +### Enabling/Disabling Patches -- [suckless.org](https://tools.suckless.org/dmenu/) - original dmenu -- [BreadOnPenguins](https://github.com/BreadOnPenguins/dmenu) - base build with center + xresources patches +Edit `patches.h` to toggle patches: + +```c +#define FUZZYMATCH_PATCH 1 /* Enable fuzzy matching */ +#define VI_MODE_PATCH 0 /* Disable vi mode */ +``` + +### Customizing Settings + +Edit `config.def.h` for: +- Font settings +- Colors +- Default behavior (centered, etc.) +- Border width + +After editing, rebuild: +```bash +rm config.h && sudo make clean install +``` + +### Xresources + +This build reads settings from `~/.Xresources`. Example: + +``` +dmenu.font: JetBrainsMono Nerd Font:size=14 +dmenu.background: #9CC2EC +dmenu.foreground: #2a4055 +dmenu.selbackground: #DEC292 +dmenu.selforeground: #2a4055 +``` + +Apply with: +```bash +xrdb merge ~/.Xresources +``` --- -## Other Suckless Repos +## My Other Suckless Repos - [dwm](https://github.com/krisyotam/dwm) - dynamic window manager - [st](https://github.com/krisyotam/st) - simple terminal - [dwmblocks](https://github.com/krisyotam/dwmblocks) - modular status bar +- [surf](https://github.com/krisyotam/surf) - simple web browser + +--- + +## Credits + +- Based on [dmenu-flexipatch](https://github.com/bakkeby/dmenu-flexipatch) by bakkeby +- [suckless.org](https://tools.suckless.org/dmenu/) for the original dmenu --- diff --git a/config.def.h b/config.def.h @@ -1,26 +1,182 @@ /* 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 centered = 1; /* -c option; centers dmenu on screen */ -static int min_width = 500; /* minimum width when centered */ -static const float menu_height_ratio = 4.0f; /* This is the ratio used in the original calculation */ +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +#if ALPHA_PATCH +static int opacity = 1; /* -o option; if 0, then alpha is disabled */ +#endif // ALPHA_PATCH +#if CARET_WIDTH_PATCH +static int caret_width = 2; /* -cw option; set default caret width */ +#endif // CARET_WIDTH_PATCH +#if FUZZYMATCH_PATCH +static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */ +#endif // FUZZYMATCH_PATCH +#if INCREMENTAL_PATCH +static int incremental = 0; /* -r option; if 1, outputs text each time a key is pressed */ +#endif // INCREMENTAL_PATCH +#if INSTANT_PATCH +static int instant = 0; /* -n option; if 1, selects matching item without the need to press enter */ +#endif // INSTANT_PATCH +#if CENTER_PATCH +static int center = 1; /* -c option; if 0, dmenu won't be centered on the screen */ +static int min_width = 500; /* minimum width when centered */ +#endif // CENTER_PATCH +#if BARPADDING_PATCH +static const int vertpad = 10; /* vertical padding of bar */ +static const int sidepad = 10; /* horizontal padding of bar */ +#endif // BARPADDING_PATCH +#if QUIET_PATCH +static int quiet = 0; /* -q option; if 1, dmenu will not show any items if the search string is empty */ +#endif // QUIET_PATCH +#if RESTRICT_RETURN_PATCH +static int restrict_return = 0; /* -1 option; if 1, disables shift-return and ctrl-return */ +#endif // RESTRICT_RETURN_PATCH /* -fn option overrides fonts[0]; default X11 font or font set */ -static const char *fonts[] = { +#if PANGO_PATCH +static char *font = "monospace 10"; +#else +#if XRESOURCES_PATCH +static char *fonts[] = +#else +static const char *fonts[] = +#endif // XRESOURCES_PATCH +{ "JetBrainsMono Nerd Font:size=14" }; +#endif // PANGO_PATCH +#if MANAGED_PATCH +static char *prompt = NULL; /* -p option; prompt to the left of input field */ +#else static const char *prompt = NULL; /* -p option; prompt to the left of input field */ -static const char *colors[SchemeLast][2] = { - /* fg bg */ +#endif // MANAGED_PATCH +#if DYNAMIC_OPTIONS_PATCH +static const char *dynamic = NULL; /* -dy option; dynamic command to run on input change */ +#endif // DYNAMIC_OPTIONS_PATCH +#if SYMBOLS_PATCH +static const char *symbol_1 = "<"; +static const char *symbol_2 = ">"; +#endif // SYMBOLS_PATCH + +#if ALPHA_PATCH +static const unsigned int baralpha = 0xd0; +static const unsigned int borderalpha = OPAQUE; +static const unsigned int alphas[][3] = { + /* fg bg border */ + [SchemeNorm] = { OPAQUE, baralpha, borderalpha }, + [SchemeSel] = { OPAQUE, baralpha, borderalpha }, + #if BORDER_PATCH + [SchemeBorder] = { OPAQUE, OPAQUE, OPAQUE }, + #endif // BORDER_PATCH + #if MORECOLOR_PATCH + [SchemeMid] = { OPAQUE, baralpha, borderalpha }, + #endif // MORECOLOR_PATCH + #if HIGHLIGHT_PATCH + [SchemeSelHighlight] = { OPAQUE, baralpha, borderalpha }, + [SchemeNormHighlight] = { OPAQUE, baralpha, borderalpha }, + #endif // HIGHLIGHT_PATCH + #if HIGHPRIORITY_PATCH + [SchemeHp] = { OPAQUE, baralpha, borderalpha }, + #endif // HIGHPRIORITY_PATCH + #if EMOJI_HIGHLIGHT_PATCH + [SchemeHover] = { OPAQUE, baralpha, borderalpha }, + [SchemeGreen] = { OPAQUE, baralpha, borderalpha }, + [SchemeRed] = { OPAQUE, baralpha, borderalpha }, + [SchemeYellow] = { OPAQUE, baralpha, borderalpha }, + [SchemeBlue] = { OPAQUE, baralpha, borderalpha }, + [SchemePurple] = { OPAQUE, baralpha, borderalpha }, + #endif // EMOJI_HIGHLIGHT_PATCH + #if VI_MODE_PATCH + [SchemeCursor] = { OPAQUE, baralpha, borderalpha }, + #endif // VI_MODE_PATCH + #if CARET_SCHEME_PATCH + [SchemeCaret] = { OPAQUE, baralpha, borderalpha }, + #endif // CARET_SCHEME_PATCH +}; +#endif // ALPHA_PATCH + +static +#if !XRESOURCES_PATCH +const +#endif // XRESOURCES_PATCH +char *colors[][2] = { + /* fg bg */ [SchemeNorm] = { "#2a4055", "#9CC2EC" }, - [SchemeSel] = { "#2a4055", "#DEC292" }, - [SchemeOut] = { "#2a4055", "#90B0D2" }, + [SchemeSel] = { "#2a4055", "#DEC292" }, + [SchemeOut] = { "#2a4055", "#90B0D2" }, + #if BORDER_PATCH + [SchemeBorder] = { "#2a4055", "#DEC292" }, + #endif // BORDER_PATCH + #if MORECOLOR_PATCH + [SchemeMid] = { "#2a4055", "#B0C0D0" }, + #endif // MORECOLOR_PATCH + #if HIGHLIGHT_PATCH + [SchemeSelHighlight] = { "#1a3045", "#DEC292" }, + [SchemeNormHighlight] = { "#1a3045", "#9CC2EC" }, + #endif // HIGHLIGHT_PATCH + #if HIGHPRIORITY_PATCH + [SchemeHp] = { "#bbbbbb", "#333333" }, + #endif // HIGHPRIORITY_PATCH + #if EMOJI_HIGHLIGHT_PATCH + [SchemeHover] = { "#ffffff", "#353D4B" }, + [SchemeGreen] = { "#ffffff", "#52E067" }, + [SchemeRed] = { "#ffffff", "#e05252" }, + [SchemeYellow] = { "#ffffff", "#e0c452" }, + [SchemeBlue] = { "#ffffff", "#5280e0" }, + [SchemePurple] = { "#ffffff", "#9952e0" }, + #endif // EMOJI_HIGHLIGHT_PATCH + #if VI_MODE_PATCH + [SchemeCursor] = { "#222222", "#bbbbbb" }, + #endif // VI_MODE_PATCH + #if CARET_SCHEME_PATCH + [SchemeCaret] = { "#eeeeee", "#222222" }, + #endif // CARET_SCHEME_PATCH }; /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ static unsigned int lines = 0; +#if GRID_PATCH +/* -g option; if nonzero, dmenu uses a grid comprised of columns and lines */ +static unsigned int columns = 0; +#endif // GRID_PATCH +#if LINE_HEIGHT_PATCH +static unsigned int lineheight = 0; /* -h option; minimum height of a menu line */ +static unsigned int min_lineheight = 8; +#endif // LINE_HEIGHT_PATCH +#if NAVHISTORY_PATCH +static unsigned int maxhist = 15; +static int histnodup = 1; /* if 0, record repeated histories */ +#endif // NAVHISTORY_PATCH /* * Characters not considered part of a word while deleting words * for example: " /?\"&[]" */ +#if PIPEOUT_PATCH +static const char startpipe[] = "#"; +#endif // PIPEOUT_PATCH static const char worddelimiters[] = " "; + +#if VI_MODE_PATCH +/* + * -vi option; if nonzero, vi mode is always enabled and can be + * accessed with the global_esc keysym + mod mask + */ +static unsigned int vi_mode = 1; +static unsigned int start_mode = 0; /* mode to use when -vi is passed. 0 = insert mode, 1 = normal mode */ +static Key global_esc = { XK_n, Mod1Mask }; /* escape key when vi mode is not enabled explicitly */ +static Key quit_keys[] = { + /* keysym modifier */ + { XK_q, 0 } +}; +#endif // VI_MODE_PATCH + +#if BORDER_PATCH +/* Size of the window border */ +static unsigned int border_width = 2; +#endif // BORDER_PATCH + +#if PREFIXCOMPLETION_PATCH +/* + * Use prefix matching by default; can be inverted with the -x flag. + */ +static int use_prefix = 1; +#endif // PREFIXCOMPLETION_PATCH diff --git a/config.h b/config.h @@ -1,26 +1,182 @@ /* 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 centered = 1; /* -c option; centers dmenu on screen */ -static int min_width = 500; /* minimum width when centered */ -static const float menu_height_ratio = 4.0f; /* This is the ratio used in the original calculation */ +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +#if ALPHA_PATCH +static int opacity = 1; /* -o option; if 0, then alpha is disabled */ +#endif // ALPHA_PATCH +#if CARET_WIDTH_PATCH +static int caret_width = 2; /* -cw option; set default caret width */ +#endif // CARET_WIDTH_PATCH +#if FUZZYMATCH_PATCH +static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */ +#endif // FUZZYMATCH_PATCH +#if INCREMENTAL_PATCH +static int incremental = 0; /* -r option; if 1, outputs text each time a key is pressed */ +#endif // INCREMENTAL_PATCH +#if INSTANT_PATCH +static int instant = 0; /* -n option; if 1, selects matching item without the need to press enter */ +#endif // INSTANT_PATCH +#if CENTER_PATCH +static int center = 1; /* -c option; if 0, dmenu won't be centered on the screen */ +static int min_width = 500; /* minimum width when centered */ +#endif // CENTER_PATCH +#if BARPADDING_PATCH +static const int vertpad = 10; /* vertical padding of bar */ +static const int sidepad = 10; /* horizontal padding of bar */ +#endif // BARPADDING_PATCH +#if QUIET_PATCH +static int quiet = 0; /* -q option; if 1, dmenu will not show any items if the search string is empty */ +#endif // QUIET_PATCH +#if RESTRICT_RETURN_PATCH +static int restrict_return = 0; /* -1 option; if 1, disables shift-return and ctrl-return */ +#endif // RESTRICT_RETURN_PATCH /* -fn option overrides fonts[0]; default X11 font or font set */ -static const char *fonts[] = { +#if PANGO_PATCH +static char *font = "monospace 10"; +#else +#if XRESOURCES_PATCH +static char *fonts[] = +#else +static const char *fonts[] = +#endif // XRESOURCES_PATCH +{ "JetBrainsMono Nerd Font:size=14" }; +#endif // PANGO_PATCH +#if MANAGED_PATCH +static char *prompt = NULL; /* -p option; prompt to the left of input field */ +#else static const char *prompt = NULL; /* -p option; prompt to the left of input field */ -static const char *colors[SchemeLast][2] = { - /* fg bg */ +#endif // MANAGED_PATCH +#if DYNAMIC_OPTIONS_PATCH +static const char *dynamic = NULL; /* -dy option; dynamic command to run on input change */ +#endif // DYNAMIC_OPTIONS_PATCH +#if SYMBOLS_PATCH +static const char *symbol_1 = "<"; +static const char *symbol_2 = ">"; +#endif // SYMBOLS_PATCH + +#if ALPHA_PATCH +static const unsigned int baralpha = 0xd0; +static const unsigned int borderalpha = OPAQUE; +static const unsigned int alphas[][3] = { + /* fg bg border */ + [SchemeNorm] = { OPAQUE, baralpha, borderalpha }, + [SchemeSel] = { OPAQUE, baralpha, borderalpha }, + #if BORDER_PATCH + [SchemeBorder] = { OPAQUE, OPAQUE, OPAQUE }, + #endif // BORDER_PATCH + #if MORECOLOR_PATCH + [SchemeMid] = { OPAQUE, baralpha, borderalpha }, + #endif // MORECOLOR_PATCH + #if HIGHLIGHT_PATCH + [SchemeSelHighlight] = { OPAQUE, baralpha, borderalpha }, + [SchemeNormHighlight] = { OPAQUE, baralpha, borderalpha }, + #endif // HIGHLIGHT_PATCH + #if HIGHPRIORITY_PATCH + [SchemeHp] = { OPAQUE, baralpha, borderalpha }, + #endif // HIGHPRIORITY_PATCH + #if EMOJI_HIGHLIGHT_PATCH + [SchemeHover] = { OPAQUE, baralpha, borderalpha }, + [SchemeGreen] = { OPAQUE, baralpha, borderalpha }, + [SchemeRed] = { OPAQUE, baralpha, borderalpha }, + [SchemeYellow] = { OPAQUE, baralpha, borderalpha }, + [SchemeBlue] = { OPAQUE, baralpha, borderalpha }, + [SchemePurple] = { OPAQUE, baralpha, borderalpha }, + #endif // EMOJI_HIGHLIGHT_PATCH + #if VI_MODE_PATCH + [SchemeCursor] = { OPAQUE, baralpha, borderalpha }, + #endif // VI_MODE_PATCH + #if CARET_SCHEME_PATCH + [SchemeCaret] = { OPAQUE, baralpha, borderalpha }, + #endif // CARET_SCHEME_PATCH +}; +#endif // ALPHA_PATCH + +static +#if !XRESOURCES_PATCH +const +#endif // XRESOURCES_PATCH +char *colors[][2] = { + /* fg bg */ [SchemeNorm] = { "#2a4055", "#9CC2EC" }, - [SchemeSel] = { "#2a4055", "#DEC292" }, - [SchemeOut] = { "#2a4055", "#90B0D2" }, + [SchemeSel] = { "#2a4055", "#DEC292" }, + [SchemeOut] = { "#2a4055", "#90B0D2" }, + #if BORDER_PATCH + [SchemeBorder] = { "#2a4055", "#DEC292" }, + #endif // BORDER_PATCH + #if MORECOLOR_PATCH + [SchemeMid] = { "#2a4055", "#B0C0D0" }, + #endif // MORECOLOR_PATCH + #if HIGHLIGHT_PATCH + [SchemeSelHighlight] = { "#1a3045", "#DEC292" }, + [SchemeNormHighlight] = { "#1a3045", "#9CC2EC" }, + #endif // HIGHLIGHT_PATCH + #if HIGHPRIORITY_PATCH + [SchemeHp] = { "#bbbbbb", "#333333" }, + #endif // HIGHPRIORITY_PATCH + #if EMOJI_HIGHLIGHT_PATCH + [SchemeHover] = { "#ffffff", "#353D4B" }, + [SchemeGreen] = { "#ffffff", "#52E067" }, + [SchemeRed] = { "#ffffff", "#e05252" }, + [SchemeYellow] = { "#ffffff", "#e0c452" }, + [SchemeBlue] = { "#ffffff", "#5280e0" }, + [SchemePurple] = { "#ffffff", "#9952e0" }, + #endif // EMOJI_HIGHLIGHT_PATCH + #if VI_MODE_PATCH + [SchemeCursor] = { "#222222", "#bbbbbb" }, + #endif // VI_MODE_PATCH + #if CARET_SCHEME_PATCH + [SchemeCaret] = { "#eeeeee", "#222222" }, + #endif // CARET_SCHEME_PATCH }; /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ static unsigned int lines = 0; +#if GRID_PATCH +/* -g option; if nonzero, dmenu uses a grid comprised of columns and lines */ +static unsigned int columns = 0; +#endif // GRID_PATCH +#if LINE_HEIGHT_PATCH +static unsigned int lineheight = 0; /* -h option; minimum height of a menu line */ +static unsigned int min_lineheight = 8; +#endif // LINE_HEIGHT_PATCH +#if NAVHISTORY_PATCH +static unsigned int maxhist = 15; +static int histnodup = 1; /* if 0, record repeated histories */ +#endif // NAVHISTORY_PATCH /* * Characters not considered part of a word while deleting words * for example: " /?\"&[]" */ +#if PIPEOUT_PATCH +static const char startpipe[] = "#"; +#endif // PIPEOUT_PATCH static const char worddelimiters[] = " "; + +#if VI_MODE_PATCH +/* + * -vi option; if nonzero, vi mode is always enabled and can be + * accessed with the global_esc keysym + mod mask + */ +static unsigned int vi_mode = 1; +static unsigned int start_mode = 0; /* mode to use when -vi is passed. 0 = insert mode, 1 = normal mode */ +static Key global_esc = { XK_n, Mod1Mask }; /* escape key when vi mode is not enabled explicitly */ +static Key quit_keys[] = { + /* keysym modifier */ + { XK_q, 0 } +}; +#endif // VI_MODE_PATCH + +#if BORDER_PATCH +/* Size of the window border */ +static unsigned int border_width = 2; +#endif // BORDER_PATCH + +#if PREFIXCOMPLETION_PATCH +/* + * Use prefix matching by default; can be inverted with the -x flag. + */ +static int use_prefix = 1; +#endif // PREFIXCOMPLETION_PATCH diff --git a/config.mk b/config.mk @@ -19,12 +19,26 @@ FREETYPEINC = /usr/include/freetype2 #FREETYPEINC = $(X11INC)/freetype2 #MANPREFIX = ${PREFIX}/man +# Uncomment on RHEL for strcasecmp +#EXTRAFLAGS=-D_GNU_SOURCE + +# Alpha patch / ALPHA_PATCH +XRENDER = -lXrender + +# Uncomment for the bidi patch / BIDI_PATCH +#BIDILIBS = `pkg-config --libs fribidi` +#BIDIINC = `pkg-config --cflags fribidi` + +# Uncomment for the pango patch / PANGO_PATCH +#PANGOINC = `pkg-config --cflags xft pango pangoxft` +#PANGOLIB = `pkg-config --libs xft pango pangoxft` + # includes and libs -INCS = -I$(X11INC) -I$(FREETYPEINC) -LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) +INCS = -I$(X11INC) -I$(FREETYPEINC) $(PANGOINC) $(BIDIINC) +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm $(XRENDER) $(PANGOLIB) $(BIDILIBS) # flags -CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) $(EXTRAFLAGS) CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) LDFLAGS = $(LIBS) diff --git a/dmenu.1 b/dmenu.1 @@ -20,6 +20,10 @@ dmenu \- dynamic menu .IR color ] .RB [ \-sf .IR color ] +.RB [ \-ob +.IR color ] +.RB [ \-of +.IR color ] .RB [ \-w .IR windowid ] .P @@ -40,9 +44,6 @@ which lists programs in the user's $PATH and runs the result in their $SHELL. .B \-b dmenu appears at the bottom of the screen. .TP -.B \-c -dmenu appears centered on 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. @@ -78,6 +79,12 @@ defines the selected background color. .BI \-sf " color" defines the selected foreground color. .TP +.BI \-ob " color" +defines the outline background color (for multiple selection). +.TP +.BI \-of " color" +defines the outline foreground color (for multiple selection). +.TP .B \-v prints version information to stdout, then exits. .TP diff --git a/dmenu.c b/dmenu.c @@ -15,54 +15,175 @@ #include <X11/extensions/Xinerama.h> #endif #include <X11/Xft/Xft.h> -#include <X11/Xresource.h> + +#include "patches.h" +/* Patch incompatibility overrides */ +#if MULTI_SELECTION_PATCH +#undef NON_BLOCKING_STDIN_PATCH +#undef PIPEOUT_PATCH +#undef PRINTINPUTTEXT_PATCH +#endif // MULTI_SELECTION_PATCH #include "drw.h" #include "util.h" +#if GRIDNAV_PATCH +#include <stdbool.h> +#endif // GRIDNAV_PATCH /* 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))) +#if PANGO_PATCH +#define TEXTW(X) (drw_font_getwidth(drw, (X), False) + lrpad) +#define TEXTWM(X) (drw_font_getwidth(drw, (X), True) + lrpad) +#else #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) +#endif // PANGO_PATCH +#if ALPHA_PATCH +#define OPAQUE 0xffU +#define OPACITY "_NET_WM_WINDOW_OPACITY" +#endif // ALPHA_PATCH /* enums */ -enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ +enum { + SchemeNorm, + SchemeSel, + SchemeOut, + #if BORDER_PATCH + SchemeBorder, + #endif // BORDER_PATCH + #if MORECOLOR_PATCH + SchemeMid, + #endif // MORECOLOR_PATCH + #if HIGHLIGHT_PATCH + SchemeNormHighlight, + SchemeSelHighlight, + #endif // HIGHLIGHT_PATCH + #if HIGHPRIORITY_PATCH + SchemeHp, + #endif // HIGHPRIORITY_PATCH + #if EMOJI_HIGHLIGHT_PATCH + SchemeHover, + SchemeGreen, + SchemeYellow, + SchemeBlue, + SchemePurple, + SchemeRed, + #endif // EMOJI_HIGHLIGHT_PATCH + #if VI_MODE_PATCH + SchemeCursor, + #endif // VI_MODE_PATCH + #if CARET_SCHEME_PATCH + SchemeCaret, + #endif // CARET_SCHEME_PATCH + SchemeLast, +}; /* color schemes */ struct item { char *text; - unsigned int width; + #if SEPARATOR_PATCH + char *text_output; + #elif TSV_PATCH + char *stext; + #endif // SEPARATOR_PATCH | TSV_PATCH struct item *left, *right; + #if NON_BLOCKING_STDIN_PATCH + struct item *next; + #endif // NON_BLOCKING_STDIN_PATCH + #if MULTI_SELECTION_PATCH + int id; /* for multiselect */ + #else int out; + #endif // MULTI_SELECTION_PATCH + #if HIGHPRIORITY_PATCH + int hp; + #endif // HIGHPRIORITY_PATCH + #if FUZZYMATCH_PATCH + double distance; + #endif // FUZZYMATCH_PATCH + #if PRINTINDEX_PATCH + int index; + #endif // PRINTINDEX_PATCH }; static char text[BUFSIZ] = ""; +#if PIPEOUT_PATCH +static char pipeout[8] = " | dmenu"; +#endif // PIPEOUT_PATCH static char *embed; +#if SEPARATOR_PATCH +static char separator; +static char * (*sepchr)(const char *, int); +static int separator_reverse; +#endif // SEPARATOR_PATCH static int bh, mw, mh; +#if XYW_PATCH +static int dmx = 0, dmy = 0; /* put dmenu at these x and y offsets */ +static unsigned int dmw = 0; /* make dmenu this wide */ +#endif // XYW_PATCH static int inputw = 0, promptw; +#if PASSWORD_PATCH +static int passwd = 0; +#endif // PASSWORD_PATCH static int lrpad; /* sum of left and right padding */ +#if BARPADDING_PATCH +static int vp; /* vertical padding for bar */ +static int sp; /* side padding for bar */ +#endif // BARPADDING_PATCH +#if REJECTNOMATCH_PATCH +static int reject_no_match = 0; +#endif // REJECTNOMATCH_PATCH 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; +#if PRINTINDEX_PATCH +static int print_index = 0; +#endif // PRINTINDEX_PATCH +#if MANAGED_PATCH +static int managed = 0; +#endif // MANAGED_PATCH +#if MULTI_SELECTION_PATCH +static int *selid = NULL; +static unsigned int selidsize = 0; +#endif // MULTI_SELECTION_PATCH +#if NO_SORT_PATCH +static unsigned int sortmatches = 1; +#endif // NO_SORT_PATCH +#if PRINTINPUTTEXT_PATCH +static int use_text_input = 0; +#endif // PRINTINPUTTEXT_PATCH +#if PRESELECT_PATCH +static unsigned int preselected = 0; +#endif // PRESELECT_PATCH +#if EMOJI_HIGHLIGHT_PATCH +static int commented = 0; +static int animated = 0; +#endif // EMOJI_HIGHLIGHT_PATCH static Atom clip, utf8; +#if WMTYPE_PATCH +static Atom type, dock; +#endif // WMTYPE_PATCH static Display *dpy; static Window root, parentwin, win; static XIC xic; +#if ALPHA_PATCH +static int useargb = 0; +static Visual *visual; +static int depth; +static Colormap cmap; +#endif // ALPHA_PATCH + static Drw *drw; static Clr *scheme[SchemeLast]; -/* Temporary arrays to allow overriding xresources values */ -static char *colortemp[4]; -static char *tempfonts; +#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) { @@ -70,6 +191,41 @@ textw_clamp(const char *str, unsigned int n) 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 printitem(struct item *item); +static void printtext(char *text); +static void printcurrent(unsigned int state); +#if ALPHA_PATCH +static void xinitvisual(void); +#endif // ALPHA_PATCH +static void readstdin(void); +static void run(void); +static void setup(void); +static void usage(void); + +#if CASEINSENSITIVE_PATCH +static int (*fstrncmp)(const char *, const char *, size_t) = strncasecmp; +static char *(*fstrstr)(const char *, const char *) = cistrstr; +#else +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; +#endif // CASEINSENSITIVE_PATCH + +#include "patch/include.c" + static void appenditem(struct item *item, struct item **list, struct item **last) { @@ -86,12 +242,27 @@ appenditem(struct item *item, struct item **list, struct item **last) static void calcoffsets(void) { - int i, n; + int i, n, rpad = 0; - if (lines > 0) + if (lines > 0) { + #if GRID_PATCH + if (columns) + n = lines * columns * bh; + else + n = lines * bh; + #else n = lines * bh; - else - n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + #endif // GRID_PATCH + } else { + #if NUMBERS_PATCH + rpad = TEXTW(numbers); + #endif // NUMBERS_PATCH + #if SYMBOLS_PATCH + n = mw - (promptw + inputw + TEXTW(symbol_1) + TEXTW(symbol_2) + rpad); + #else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">") + rpad); + #endif // SYMBOLS_PATCH + } /* 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) @@ -101,109 +272,536 @@ calcoffsets(void) break; } -static int -max_textw(void) -{ - int len = 0; - for (struct item *item = items; item && item->text; item++) - len = MAX(item->width, len); - return len; -} - static void cleanup(void) { size_t i; XUngrabKeyboard(dpy, CurrentTime); + #if INPUTMETHOD_PATCH + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + #endif // INPUTMETHOD_PATCH for (i = 0; i < SchemeLast; i++) drw_scm_free(drw, scheme[i], 2); - for (i = 0; items && items[i].text; ++i) + + #if NAVHISTORY_PATCH + savehistory(); + cleanhistory(); + restorebackupitems(); + #endif // NAVHISTORY_PATCH + + for (i = 0; items && items[i].text; ++i) { + #if SEPARATOR_PATCH + free(separator_reverse ? items[i].text_output : items[i].text); + #else free(items[i].text); + #endif // SEPARATOR_PATCH + } free(items); + + #if HIGHPRIORITY_PATCH + for (i = 0; i < hplength; ++i) + free(hpitems[i]); + free(hpitems); + #endif // HIGHPRIORITY_PATCH drw_free(drw); XSync(dpy, False); XCloseDisplay(dpy); + #if MULTI_SELECTION_PATCH + free(selid); + #endif // MULTI_SELECTION_PATCH } static char * -cistrstr(const char *h, const char *n) +cistrstr(const char *s, const char *sub) { - size_t i; - - if (!n[0]) - return (char *)h; + size_t len; - for (; *h; ++h) { - for (i = 0; n[i] && tolower((unsigned char)n[i]) == - tolower((unsigned char)h[i]); ++i) - ; - if (n[i] == '\0') - return (char *)h; - } + 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; + #if TSV_PATCH && !SEPARATOR_PATCH + char *text = item->stext; + #else + char *text = item->text; + #endif // TSV_PATCH + + #if EMOJI_HIGHLIGHT_PATCH + int iscomment = 0; + if (text[0] == '>') { + if (text[1] == '>') { + iscomment = 3; + switch (text[2]) { + case 'r': + drw_setscheme(drw, scheme[SchemeRed]); + break; + case 'g': + drw_setscheme(drw, scheme[SchemeGreen]); + break; + case 'y': + drw_setscheme(drw, scheme[SchemeYellow]); + break; + case 'b': + drw_setscheme(drw, scheme[SchemeBlue]); + break; + case 'p': + drw_setscheme(drw, scheme[SchemePurple]); + break; + #if HIGHLIGHT_PATCH + case 'h': + drw_setscheme(drw, scheme[SchemeNormHighlight]); + break; + #endif // HIGHLIGHT_PATCH + case 's': + drw_setscheme(drw, scheme[SchemeSel]); + break; + default: + iscomment = 1; + drw_setscheme(drw, scheme[SchemeNorm]); + break; + } + } else { + drw_setscheme(drw, scheme[SchemeNorm]); + iscomment = 1; + } + } else if (text[0] == ':') { + iscomment = 2; + if (item == sel) { + switch (text[1]) { + case 'r': + drw_setscheme(drw, scheme[SchemeRed]); + break; + case 'g': + drw_setscheme(drw, scheme[SchemeGreen]); + break; + case 'y': + drw_setscheme(drw, scheme[SchemeYellow]); + break; + case 'b': + drw_setscheme(drw, scheme[SchemeBlue]); + break; + case 'p': + drw_setscheme(drw, scheme[SchemePurple]); + break; + #if HIGHLIGHT_PATCH + case 'h': + drw_setscheme(drw, scheme[SchemeNormHighlight]); + break; + #endif // HIGHLIGHT_PATCH + case 's': + drw_setscheme(drw, scheme[SchemeSel]); + break; + default: + drw_setscheme(drw, scheme[SchemeSel]); + iscomment = 0; + break; + } + } else { + drw_setscheme(drw, scheme[SchemeNorm]); + } + } + #endif // EMOJI_HIGHLIGHT_PATCH + + #if EMOJI_HIGHLIGHT_PATCH + int temppadding = 0; + if (iscomment == 2) { + if (text[2] == ' ') { + #if PANGO_PATCH + temppadding = drw->font->h * 3; + #else + temppadding = drw->fonts->h * 3; + #endif // PANGO_PATCH + animated = 1; + char dest[1000]; + strcpy(dest, text); + dest[6] = '\0'; + drw_text(drw, x, y + , temppadding + #if LINE_HEIGHT_PATCH + , MAX(lineheight, bh) + #else + , bh + #endif // LINE_HEIGHT_PATCH + , temppadding / 2.6 + , dest + 3 + , 0 + #if PANGO_PATCH + , True + #endif // PANGO_PATCH + ); + iscomment = 6; + drw_setscheme(drw, sel == item ? scheme[SchemeHover] : scheme[SchemeNorm]); + } + } + + char *output; + if (commented) { + static char onestr[2]; + onestr[0] = text[0]; + onestr[1] = '\0'; + output = onestr; + } else { + output = text; + } + #endif // EMOJI_HIGHLIGHT_PATCH + if (item == sel) drw_setscheme(drw, scheme[SchemeSel]); + #if HIGHPRIORITY_PATCH + else if (item->hp) + drw_setscheme(drw, scheme[SchemeHp]); + #endif // HIGHPRIORITY_PATCH + #if MORECOLOR_PATCH + else if (item->left == sel || item->right == sel) + drw_setscheme(drw, scheme[SchemeMid]); + #endif // MORECOLOR_PATCH + #if MULTI_SELECTION_PATCH + else if (issel(item->id)) + #else else if (item->out) + #endif // MULTI_SELECTION_PATCH drw_setscheme(drw, scheme[SchemeOut]); else drw_setscheme(drw, scheme[SchemeNorm]); - return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); + r = drw_text(drw + #if EMOJI_HIGHLIGHT_PATCH + , x + ((iscomment == 6) ? temppadding : 0) + #else + , x + #endif // EMOJI_HIGHLIGHT_PATCH + , y + , w + , bh + #if EMOJI_HIGHLIGHT_PATCH + , commented ? (bh - TEXTW(output) - lrpad) / 2 : lrpad / 2 + #else + , lrpad / 2 + #endif // EMOJI_HIGHLIGHT_PATCH + #if EMOJI_HIGHLIGHT_PATCH + , output + iscomment + #else + , text + #endif // EMOJI_HIGHLIGHT_PATCH + , 0 + #if PANGO_PATCH + , True + #endif // PANGO_PATCH + ); + #if HIGHLIGHT_PATCH + #if EMOJI_HIGHLIGHT_PATCH + drawhighlights(item, output + iscomment, x + ((iscomment == 6) ? temppadding : 0), y, w); + #else + drawhighlights(item, x, y, w); + #endif // EMOJI_HIGHLIGHT_PATCH + #endif // HIGHLIGHT_PATCH + return r; } static void drawmenu(void) { + #if SCROLL_PATCH + static int curpos, oldcurlen; + int curlen, rcurlen; + #else unsigned int curpos; + #endif // SCROLL_PATCH struct item *item; - int x = 0, y = 0, w; + int x = 0, y = 0, w, rpad = 0, itw = 0, stw = 0; + #if LINE_HEIGHT_PATCH && PANGO_PATCH + int fh = drw->font->h; + #elif LINE_HEIGHT_PATCH + int fh = drw->fonts->h; + #endif // LINE_HEIGHT_PATCH + #if PASSWORD_PATCH + char *censort; + #endif // PASSWORD_PATCH drw_setscheme(drw, scheme[SchemeNorm]); drw_rect(drw, 0, 0, mw, mh, 1, 1); if (prompt && *prompt) { + #if !PLAIN_PROMPT_PATCH drw_setscheme(drw, scheme[SchemeSel]); - x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + #endif // PLAIN_PROMPT_PATCH + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0 + #if PANGO_PATCH + , True + #endif // PANGO_PATCH + ); } /* draw input field */ w = (lines > 0 || !matches) ? mw - x : inputw; + + #if SCROLL_PATCH + w -= lrpad / 2; + x += lrpad / 2; + rcurlen = TEXTW(text + cursor) - lrpad; + curlen = TEXTW(text) - lrpad - rcurlen; + curpos += curlen - oldcurlen; + curpos = MIN(w, MAX(0, curpos)); + curpos = MAX(curpos, w - rcurlen); + curpos = MIN(curpos, curlen); + oldcurlen = curlen; + drw_setscheme(drw, scheme[SchemeNorm]); - drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); + #if PASSWORD_PATCH + if (passwd) { + censort = ecalloc(1, sizeof(text)); + memset(censort, '.', strlen(text)); + drw_text_align(drw, x, 0, curpos, bh, censort, cursor, AlignR); + drw_text_align(drw, x + curpos, 0, w - curpos, bh, censort + cursor, strlen(censort) - cursor, AlignL); + free(censort); + } else { + drw_text_align(drw, x, 0, curpos, bh, text, cursor, AlignR); + drw_text_align(drw, x + curpos, 0, w - curpos, bh, text + cursor, strlen(text) - cursor, AlignL); + } + #else + drw_text_align(drw, x, 0, curpos, bh, text, cursor, AlignR); + drw_text_align(drw, x + curpos, 0, w - curpos, bh, text + cursor, strlen(text) - cursor, AlignL); + #endif // PASSWORD_PATCH + + #if VI_MODE_PATCH + if (using_vi_mode && text[0] != '\0') { + drw_setscheme(drw, scheme[SchemeCursor]); + char vi_char[] = {text[cursor], '\0'}; + drw_text(drw, x + curpos, 0, TEXTW(vi_char) - lrpad, bh, 0, vi_char, 0 + #if PANGO_PATCH + , False + #endif // PANGO_PATCH + ); + } else if (using_vi_mode) { + drw_rect(drw, x + curpos, 2, lrpad / 2, bh - 4, 1, 0); + } else + #endif // VI_MODE_PATCH + #if LINE_HEIGHT_PATCH + drw_rect(drw, x + curpos - 1, 2 + (bh-fh)/2, 2, fh - 4, 1, 0); + #else + drw_rect(drw, x + curpos - 1, 2, 2, bh - 4, 1, 0); + #endif // LINE_HEIGHT_PATCH + #else // !SCROLL_PATCH + drw_setscheme(drw, scheme[SchemeNorm]); + #if PASSWORD_PATCH + if (passwd) { + censort = ecalloc(1, sizeof(text)); + memset(censort, '.', strlen(text)); + drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0 + #if PANGO_PATCH + , False + #endif // PANGO_PATCH + ); + drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0 + #if PANGO_PATCH + , False + #endif // PANGO_PATCH + ); + free(censort); + } else { + drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0 + #if PANGO_PATCH + , False + #endif // PANGO_PATCH + ); + } + #else + drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0 + #if PANGO_PATCH + , False + #endif // PANGO_PATCH + ); + #endif // PASSWORD_PATCH curpos = TEXTW(text) - TEXTW(&text[cursor]); - if ((curpos += lrpad / 2 - 1) < w) { + curpos += lrpad / 2 - 1; + + #if VI_MODE_PATCH + if (using_vi_mode && text[0] != '\0') { + drw_setscheme(drw, scheme[SchemeCursor]); + char vi_char[] = {text[cursor], '\0'}; + drw_text(drw, x + curpos, 0, TEXTW(vi_char) - lrpad, bh, 0, vi_char, 0 + #if PANGO_PATCH + , False + #endif // PANGO_PATCH + ); + } else if (using_vi_mode) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2, lrpad / 2, bh - 4, 1, 0); + } else + #endif // VI_MODE_PATCH + if (curpos < w) { + #if CARET_SCHEME_PATCH + drw_setscheme(drw, scheme[SchemeCaret]); + #else drw_setscheme(drw, scheme[SchemeNorm]); + #endif // CARET_SCHEME_PATCH + #if CARET_WIDTH_PATCH && LINE_HEIGHT_PATCH + drw_rect(drw, x + curpos, 2 + (bh-fh)/2, caret_width, fh - 4, 1, 0); + #elif CARET_WIDTH_PATCH + drw_rect(drw, x + curpos, 2, caret_width, bh - 4, 1, 0); + #elif LINE_HEIGHT_PATCH + drw_rect(drw, x + curpos, 2 + (bh-fh)/2, 2, fh - 4, 1, 0); + #else drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); + #endif // LINE_HEIGHT_PATCH + } + #endif // SCROLL_PATCH + + #if NUMBERS_PATCH + recalculatenumbers(); + rpad = TEXTW(numbers); + #if BARPADDING_PATCH + rpad += 2 * sp; + #endif // BARPADDING_PATCH + #if BORDER_PATCH + rpad += border_width; + #endif // BORDER_PATCH + #endif // NUMBERS_PATCH + + #if QUIET_PATCH + if (quiet && strlen(text) == 0) { + #if DYNAMIC_HEIGHT_PATCH + if (lines > 0) + XResizeWindow(dpy, win, mw, bh); + #endif // DYNAMIC_HEIGHT_PATCH + goto skip_item_listing; } + #endif // QUIET_PATCH if (lines > 0) { + #if DYNAMIC_HEIGHT_PATCH || GRID_PATCH + int i = 0; + #endif // DYNAMIC_HEIGHT_PATCH | GRID_PATCH + + #if GRID_PATCH + /* draw grid */ + for (item = curr; item != next; item = item->right, i++) { + if (columns) { + #if VERTFULL_PATCH + drawitem( + item, + 0 + ((i / lines) * (mw / columns)), + y + (((i % lines) + 1) * bh), + mw / columns + ); + #else + drawitem( + item, + x + ((i / lines) * ((mw - x) / columns)), + y + (((i % lines) + 1) * bh), + (mw - x) / columns + ); + #endif // VERTFULL_PATCH + } else { + #if VERTFULL_PATCH + drawitem(item, 0, y += bh, mw); + #else + drawitem(item, x, y += bh, mw - x); + #endif // VERTFULL_PATCH + } + } + #if DYNAMIC_HEIGHT_PATCH + if (columns) { + XResizeWindow(dpy, win, mw, (MIN(i, lines) + 1) * bh); + } else { + XResizeWindow(dpy, win, mw, (i + 1) * bh); + } + #endif // DYNAMIC_HEIGHT_PATCH + + #else /* draw vertical list */ - for (item = curr; item != next; item = item->right) + for (item = curr; item != next; item = item->right) { + #if DYNAMIC_HEIGHT_PATCH + i++; + #endif // DYNAMIC_HEIGHT_PATCH + #if VERTFULL_PATCH + drawitem(item, 0, y += bh, mw); + #else drawitem(item, x, y += bh, mw - x); + #endif // VERTFULL_PATCH + } + #if DYNAMIC_HEIGHT_PATCH + XResizeWindow(dpy, win, mw, (i + 1) * bh); + #endif // DYNAMIC_HEIGHT_PATCH + #endif // GRID_PATCH } else if (matches) { /* draw horizontal list */ x += inputw; + #if SYMBOLS_PATCH + w = TEXTW(symbol_1); + #else w = TEXTW("<"); + #endif // SYMBOLS_PATCH if (curr->left) { drw_setscheme(drw, scheme[SchemeNorm]); - drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); + #if SYMBOLS_PATCH + drw_text(drw, x, 0, w, bh, lrpad / 2, symbol_1, 0 + #else + drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0 + #endif // SYMBOLS_PATCH + #if PANGO_PATCH + , True + #endif // PANGO_PATCH + ); } x += w; - for (item = curr; item != next; item = item->right) - x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"))); + for (item = curr; item != next; item = item->right) { + #if SYMBOLS_PATCH + stw = TEXTW(symbol_2); + #else + stw = TEXTW(">"); + #endif // SYMBOLS_PATCH + #if TSV_PATCH && !SEPARATOR_PATCH + itw = textw_clamp(item->stext, mw - x - stw - rpad); + #else + itw = textw_clamp(item->text, mw - x - stw - rpad); + #endif // TSV_PATCH + x = drawitem(item, x, 0, itw); + } if (next) { + #if SYMBOLS_PATCH + w = TEXTW(symbol_2); + #else w = TEXTW(">"); + #endif // SYMBOLS_PATCH drw_setscheme(drw, scheme[SchemeNorm]); - drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); + drw_text(drw, mw - w - rpad, 0, w, bh, lrpad / 2 + #if SYMBOLS_PATCH + , symbol_2 + #else + , ">" + #endif // SYMBOLS_PATCH + , 0 + #if PANGO_PATCH + , True + #endif // PANGO_PATCH + ); } } + + #if QUIET_PATCH +skip_item_listing: + #endif // QUIET_PATCH + + #if NUMBERS_PATCH + drw_setscheme(drw, scheme[SchemeNorm]); + #if PANGO_PATCH + drw_text(drw, mw - rpad, 0, TEXTW(numbers), bh, lrpad / 2, numbers, 0, False); + #else + drw_text(drw, mw - rpad, 0, TEXTW(numbers), bh, lrpad / 2, numbers, 0); + #endif // PANGO_PATCH + #endif // NUMBERS_PATCH drw_map(drw, win, 0, 0, mw, mh); + #if NON_BLOCKING_STDIN_PATCH + XFlush(dpy); + #endif // NON_BLOCKING_STDIN_PATCH } static void @@ -217,7 +815,9 @@ grabfocus(void) XGetInputFocus(dpy, &focuswin, &revertwin); if (focuswin == win) return; + #if !MANAGED_PATCH XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + #endif // MANAGED_PATCH nanosleep(&ts, NULL); } die("cannot grab focus"); @@ -229,13 +829,23 @@ grabkeyboard(void) struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; int i; + #if MANAGED_PATCH + if (embed || managed) + #else if (embed) + #endif // MANAGED_PATCH 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) + GrabModeAsync, CurrentTime) == GrabSuccess) { + #if MOUSE_SUPPORT_PATCH + /* one off attempt at grabbing the mouse pointer to avoid interactions + * with other windows while dmenu is active */ + XGrabPointer(dpy, DefaultRootWindow(dpy), True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + #endif // MOUSE_SUPPORT_PATCH return; + } nanosleep(&ts, NULL); } die("cannot grab keyboard"); @@ -244,6 +854,17 @@ grabkeyboard(void) static void match(void) { + #if DYNAMIC_OPTIONS_PATCH + if (dynamic && *dynamic) + refreshoptions(); + #endif // DYNAMIC_OPTIONS_PATCH + + #if FUZZYMATCH_PATCH + if (fuzzy) { + fuzzymatch(); + return; + } + #endif static char **tokv = NULL; static int tokn = 0; @@ -251,6 +872,12 @@ match(void) int i, tokc = 0; size_t len, textsize; struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; + #if HIGHPRIORITY_PATCH + struct item *lhpprefix, *hpprefixend; + #endif // HIGHPRIORITY_PATCH + #if NON_BLOCKING_STDIN_PATCH + int preserve = 0; + #endif // NON_BLOCKING_STDIN_PATCH strcpy(buf, text); /* separate input text into tokens to be matched individually */ @@ -259,22 +886,78 @@ match(void) die("cannot realloc %zu bytes:", tokn * sizeof *tokv); len = tokc ? strlen(tokv[0]) : 0; + #if PREFIXCOMPLETION_PATCH + if (use_prefix) { + matches = lprefix = matchend = prefixend = NULL; + textsize = strlen(text); + } else { + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + textsize = strlen(text) + 1; + } + #else matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; textsize = strlen(text) + 1; - for (item = items; item && item->text; item++) { + #endif // PREFIXCOMPLETION_PATCH + #if HIGHPRIORITY_PATCH + lhpprefix = hpprefixend = NULL; + #endif // HIGHPRIORITY_PATCH + #if NON_BLOCKING_STDIN_PATCH && DYNAMIC_OPTIONS_PATCH + for (item = items; item && (!(dynamic && *dynamic) || item->text); item = (dynamic && *dynamic) ? item + 1 : item->next) + #elif NON_BLOCKING_STDIN_PATCH + for (item = items; item; item = item->next) + #else + for (item = items; item && item->text; item++) + #endif + { for (i = 0; i < tokc; i++) if (!fstrstr(item->text, tokv[i])) break; + #if DYNAMIC_OPTIONS_PATCH + if (i != tokc && !(dynamic && *dynamic)) /* not all tokens match */ + continue; + #else if (i != tokc) /* not all tokens match */ continue; + #endif // DYNAMIC_OPTIONS_PATCH + #if HIGHPRIORITY_PATCH + /* exact matches go first, then prefixes with high priority, then prefixes, then substrings */ + #else /* exact matches go first, then prefixes, then substrings */ + #endif // HIGHPRIORITY_PATCH + #if NO_SORT_PATCH + if (!sortmatches) + appenditem(item, &matches, &matchend); + else + #endif // NO_SORT_PATCH if (!tokc || !fstrncmp(text, item->text, textsize)) appenditem(item, &matches, &matchend); + #if HIGHPRIORITY_PATCH + else if (item->hp && !fstrncmp(tokv[0], item->text, len)) + appenditem(item, &lhpprefix, &hpprefixend); + #endif // HIGHPRIORITY_PATCH else if (!fstrncmp(tokv[0], item->text, len)) appenditem(item, &lprefix, &prefixend); + #if PREFIXCOMPLETION_PATCH + else if (!use_prefix) + #else else + #endif // PREFIXCOMPLETION_PATCH appenditem(item, &lsubstr, &substrend); + #if NON_BLOCKING_STDIN_PATCH + if (sel == item) + preserve = 1; + #endif // NON_BLOCKING_STDIN_PATCH + } + #if HIGHPRIORITY_PATCH + if (lhpprefix) { + if (matches) { + matchend->right = lhpprefix; + lhpprefix->left = matchend; + } else + matches = lhpprefix; + matchend = hpprefixend; } + #endif // HIGHPRIORITY_PATCH if (lprefix) { if (matches) { matchend->right = lprefix; @@ -283,7 +966,12 @@ match(void) matches = lprefix; matchend = prefixend; } - if (lsubstr) { + #if PREFIXCOMPLETION_PATCH + if (!use_prefix && lsubstr) + #else + if (lsubstr) + #endif // PREFIXCOMPLETION_PATCH + { if (matches) { matchend->right = lsubstr; lsubstr->left = matchend; @@ -291,7 +979,19 @@ match(void) matches = lsubstr; matchend = substrend; } + #if NON_BLOCKING_STDIN_PATCH + if (!preserve) + #endif // NON_BLOCKING_STDIN_PATCH curr = sel = matches; + + #if INSTANT_PATCH + if (instant && matches && matches==matchend && !lsubstr) { + printitem(matches); + cleanup(); + exit(0); + } + #endif // INSTANT_PATCH + calcoffsets(); } @@ -300,12 +1000,30 @@ insert(const char *str, ssize_t n) { if (strlen(text) + n > sizeof text - 1) return; + + #if REJECTNOMATCH_PATCH + static char last[BUFSIZ] = ""; + if (reject_no_match) { + /* store last text value in case we need to revert it */ + memcpy(last, text, BUFSIZ); + } + #endif // REJECTNOMATCH_PATCH + /* 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(); + + #if REJECTNOMATCH_PATCH + if (!matches && reject_no_match) { + /* revert to last text value if theres no match */ + memcpy(text, last, BUFSIZ); + cursor -= n; + match(); + } + #endif // REJECTNOMATCH_PATCH } static size_t @@ -340,8 +1058,16 @@ keypress(XKeyEvent *ev) { char buf[64]; int len; + #if PREFIXCOMPLETION_PATCH + struct item * item; + #endif // PREFIXCOMPLETION_PATCH KeySym ksym = NoSymbol; Status status; + #if GRID_PATCH && GRIDNAV_PATCH + int i; + struct item *tmpsel; + bool offscreen = false; + #endif // GRIDNAV_PATCH len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); switch (status) { @@ -354,8 +1080,50 @@ keypress(XKeyEvent *ev) break; } + #if VI_MODE_PATCH + if (using_vi_mode) { + vi_keypress(ksym, ev); + return; + } + + if (vi_mode && + (ksym == global_esc.ksym && + (ev->state & global_esc.state) == global_esc.state)) { + using_vi_mode = 1; + if (cursor) + cursor = nextrune(-1); + goto draw; + } + #endif // VI_MODE_PATCH + if (ev->state & ControlMask) { switch(ksym) { + #if FZFEXPECT_PATCH + case XK_a: expect("ctrl-a", ev); ksym = XK_Home; break; + case XK_b: expect("ctrl-b", ev); ksym = XK_Left; break; + case XK_c: expect("ctrl-c", ev); ksym = XK_Escape; break; + case XK_d: expect("ctrl-d", ev); ksym = XK_Delete; break; + case XK_e: expect("ctrl-e", ev); ksym = XK_End; break; + case XK_f: expect("ctrl-f", ev); ksym = XK_Right; break; + case XK_g: expect("ctrl-g", ev); ksym = XK_Escape; break; + case XK_h: expect("ctrl-h", ev); ksym = XK_BackSpace; break; + case XK_i: expect("ctrl-i", ev); ksym = XK_Tab; break; + case XK_j: expect("ctrl-j", ev); ksym = XK_Down; break; + case XK_J:/* fallthrough */ + case XK_l: expect("ctrl-l", ev); break; + case XK_m: expect("ctrl-m", ev); /* fallthrough */ + case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; + case XK_n: expect("ctrl-n", ev); ksym = XK_Down; break; + case XK_p: expect("ctrl-p", ev); ksym = XK_Up; break; + case XK_o: expect("ctrl-o", ev); break; + case XK_q: expect("ctrl-q", ev); break; + #if !NAVHISTORY_PATCH + case XK_r: expect("ctrl-r", ev); break; + #endif // NAVHISTORY_PATCH + case XK_s: expect("ctrl-s", ev); break; + case XK_t: expect("ctrl-t", ev); break; + case XK_k: expect("ctrl-k", ev); ksym = XK_Up; break; + #else case XK_a: ksym = XK_Home; break; case XK_b: ksym = XK_Left; break; case XK_c: ksym = XK_Escape; break; @@ -376,20 +1144,53 @@ keypress(XKeyEvent *ev) text[cursor] = '\0'; match(); break; + #endif // FZFEXPECT_PATCH + #if FZFEXPECT_PATCH + case XK_u: expect("ctrl-u", ev); /* delete left */ + #else case XK_u: /* delete left */ + #endif // FZFEXPECT_PATCH insert(NULL, 0 - cursor); break; + #if FZFEXPECT_PATCH + case XK_w: expect("ctrl-w", ev); /* delete word */ + #else case XK_w: /* delete word */ + #endif // FZFEXPECT_PATCH 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; + #if FZFEXPECT_PATCH || CTRL_V_TO_PASTE_PATCH + case XK_v: + #if FZFEXPECT_PATCH + expect("ctrl-v", ev); + #endif // FZFEXPECT_PATCH + case XK_V: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + #endif // FZFEXPECT_PATCH | CTRL_V_TO_PASTE_PATCH + #if NAVHISTORY_PATCH + case XK_r: + togglehistoryitems(); + match(); + goto draw; + #endif // NAVHISTORY_PATCH + #if FZFEXPECT_PATCH + case XK_y: expect("ctrl-y", ev); /* paste selection */ + #else case XK_y: /* paste selection */ + #endif // FZFEXPECT_PATCH case XK_Y: XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, utf8, utf8, win, CurrentTime); return; + #if FZFEXPECT_PATCH + case XK_x: expect("ctrl-x", ev); break; + case XK_z: expect("ctrl-z", ev); break; + #endif // FZFEXPECT_PATCH case XK_Left: case XK_KP_Left: movewordedge(-1); @@ -400,6 +1201,13 @@ keypress(XKeyEvent *ev) goto draw; case XK_Return: case XK_KP_Enter: + #if RESTRICT_RETURN_PATCH + if (restrict_return) + break; + #endif // RESTRICT_RETURN_PATCH + #if MULTI_SELECTION_PATCH + selsel(); + #endif // MULTI_SELECTION_PATCH break; case XK_bracketleft: cleanup(); @@ -421,6 +1229,16 @@ keypress(XKeyEvent *ev) case XK_j: ksym = XK_Next; break; case XK_k: ksym = XK_Prior; break; case XK_l: ksym = XK_Down; break; + #if NAVHISTORY_PATCH + case XK_p: + navhistory(-1); + buf[0]=0; + break; + case XK_n: + navhistory(1); + buf[0]=0; + break; + #endif // NAVHISTORY_PATCH default: return; } @@ -474,6 +1292,26 @@ insert: break; case XK_Left: case XK_KP_Left: + #if GRID_PATCH && GRIDNAV_PATCH + if (columns > 1) { + if (!sel) + return; + tmpsel = sel; + for (i = 0; i < lines; i++) { + if (!tmpsel->left || tmpsel->left->right != tmpsel) + return; + if (tmpsel == curr) + offscreen = true; + tmpsel = tmpsel->left; + } + sel = tmpsel; + if (offscreen) { + curr = prev; + calcoffsets(); + } + break; + } + #endif // GRIDNAV_PATCH if (cursor > 0 && (!sel || !sel->left || lines > 0)) { cursor = nextrune(-1); break; @@ -504,16 +1342,47 @@ insert: break; case XK_Return: case XK_KP_Enter: - puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + #if RESTRICT_RETURN_PATCH + if (restrict_return && (!sel || ev->state & (ShiftMask | ControlMask))) + break; + #endif // RESTRICT_RETURN_PATCH + #if !MULTI_SELECTION_PATCH + printcurrent(ev->state); + #endif // MULTI_SELECTION_PATCH if (!(ev->state & ControlMask)) { + #if MULTI_SELECTION_PATCH + printselected(ev->state); + #endif // MULTI_SELECTION_PATCH cleanup(); exit(0); } + #if !MULTI_SELECTION_PATCH if (sel) sel->out = 1; + #endif // MULTI_SELECTION_PATCH break; case XK_Right: case XK_KP_Right: + #if GRID_PATCH && GRIDNAV_PATCH + if (columns > 1) { + if (!sel) + return; + tmpsel = sel; + for (i = 0; i < lines; i++) { + if (!tmpsel->right || tmpsel->right->left != tmpsel) + return; + tmpsel = tmpsel->right; + if (tmpsel == next) + offscreen = true; + } + sel = tmpsel; + if (offscreen) { + curr = next; + calcoffsets(); + } + break; + } + #endif // GRIDNAV_PATCH if (text[cursor] != '\0') { cursor = nextrune(+1); break; @@ -529,16 +1398,48 @@ insert: } break; case XK_Tab: + #if PREFIXCOMPLETION_PATCH + if (!matches) + break; /* cannot complete no matches */ + #if FUZZYMATCH_PATCH + /* only do tab completion if all matches start with prefix */ + for (item = matches; item && item->text; item = item->right) + if (item->text[0] != text[0]) + goto draw; + #endif // FUZZYMATCH_PATCH + strncpy(text, matches->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + len = cursor = strlen(text); /* length of longest common prefix */ + for (item = matches; item && item->text; item = item->right) { + cursor = 0; + while (cursor < len && text[cursor] == item->text[cursor]) + cursor++; + len = cursor; + } + memset(text + len, '\0', strlen(text) - len); + #else if (!sel) return; cursor = strnlen(sel->text, sizeof text - 1); memcpy(text, sel->text, cursor); text[cursor] = '\0'; match(); + #endif // PREFIXCOMPLETION_PATCH break; } draw: + #if INCREMENTAL_PATCH + if (incremental) { + puts(text); + fflush(stdout); + } + #endif // INCREMENTAL_PATCH + #if VI_MODE_PATCH + if (using_vi_mode && text[cursor] == '\0') + --cursor; + #endif // VI_MODE_PATCH + drawmenu(); } @@ -561,12 +1462,133 @@ paste(void) } static void +printitem(struct item *item) +{ + if (!item) + return; + + #if NAVHISTORY_PATCH + addhistoryitem(item); + #endif // NAVHISTORY_PATCH + + #if PIPEOUT_PATCH + if (item->text[0] == startpipe[0]) { + strncpy(item->text + strlen(item->text),pipeout,8); + puts(item->text+1); + return; + } + #endif // PIPEOUT_PATCH + + #if PRINTINDEX_PATCH + if (print_index) { + printf("%d\n", item->index); + return; + } + #endif // PRINTINDEX_PATCH + + #if SEPARATOR_PATCH + puts(item->text_output); + #else + puts(item->text); + #endif // SEPARATOR_PATCH +} + +static void +printtext(char *text) +{ + if (!text || !strlen(text)) + return; + + #if NAVHISTORY_PATCH + addhistory(text); + #endif // NAVHISTORY_PATCH + + #if PIPEOUT_PATCH + if (text[0] == startpipe[0]) { + strncpy(text + strlen(text),pipeout,8); + puts(text+1); + return; + } + #endif // PIPEOUT_PATCH + + puts(text); +} + +static void +printcurrent(unsigned int state) +{ + #if PRINTINPUTTEXT_PATCH + if (sel && (use_text_input == !!(state & ShiftMask))) + #else + if (sel && !(state & ShiftMask)) + #endif // PRINTINPUTTEXT_PATCH + { + printitem(sel); + } else { + printtext(text); + } +} + +#if ALPHA_PATCH +static void +xinitvisual(void) +{ + XVisualInfo *infos; + XRenderPictFormat *fmt; + int nitems; + int i; + + XVisualInfo tpl = { + .screen = screen, + .depth = 32, + .class = TrueColor + }; + long masks = VisualScreenMask | VisualDepthMask | VisualClassMask; + + infos = XGetVisualInfo(dpy, masks, &tpl, &nitems); + visual = NULL; + for(i = 0; i < nitems; i ++) { + fmt = XRenderFindVisualFormat(dpy, infos[i].visual); + if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) { + visual = infos[i].visual; + depth = infos[i].depth; + cmap = XCreateColormap(dpy, root, visual, AllocNone); + useargb = 1; + break; + } + } + + XFree(infos); + + if (!visual || !opacity) { + visual = DefaultVisual(dpy, screen); + depth = DefaultDepth(dpy, screen); + cmap = DefaultColormap(dpy, screen); + } +} +#endif // ALPHA_PATCH + +#if !NON_BLOCKING_STDIN_PATCH +static void readstdin(void) { char *line = NULL; - size_t i, itemsiz = 0, linesiz = 0; + #if SEPARATOR_PATCH + char *p; + #elif TSV_PATCH + char *buf, *p; + #endif // SEPARATOR_PATCH | TSV_PATCH + + size_t i, linesiz, itemsiz = 0; ssize_t len; + #if PASSWORD_PATCH + if (passwd) { + inputw = lines = 0; + return; + } + #endif // PASSWORD_PATCH + /* 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) { @@ -576,27 +1598,94 @@ readstdin(void) } if (line[len - 1] == '\n') line[len - 1] = '\0'; + if (!(items[i].text = strdup(line))) die("strdup:"); - items[i].width = TEXTW(line); - + #if SEPARATOR_PATCH + if (separator && (p = sepchr(items[i].text, separator)) != NULL) { + *p = '\0'; + items[i].text_output = ++p; + } else { + items[i].text_output = items[i].text; + } + if (separator_reverse) { + p = items[i].text; + items[i].text = items[i].text_output; + items[i].text_output = p; + } + #elif TSV_PATCH + if (!(buf = strdup(line))) + die("cannot strdup %u bytes:", strlen(line) + 1); + if ((p = strchr(buf, '\t'))) + *p = '\0'; + items[i].stext = buf; + #endif // SEPARATOR_PATCH | TSV_PATCH + #if MULTI_SELECTION_PATCH + items[i].id = i; /* for multiselect */ + #if PRINTINDEX_PATCH + items[i].index = i; + #endif // PRINTINDEX_PATCH + #elif PRINTINDEX_PATCH + items[i].index = i; + #else items[i].out = 0; + #endif // MULTI_SELECTION_PATCH | PRINTINDEX_PATCH + + #if HIGHPRIORITY_PATCH + items[i].hp = arrayhas(hpitems, hplength, items[i].text); + #endif // HIGHPRIORITY_PATCH } free(line); if (items) items[i].text = NULL; lines = MIN(lines, i); } +#endif // NON_BLOCKING_STDIN_PATCH static void +#if NON_BLOCKING_STDIN_PATCH +readevent(void) +#else run(void) +#endif // NON_BLOCKING_STDIN_PATCH { XEvent ev; - + #if PRESELECT_PATCH + int i; + #endif // PRESELECT_PATCH while (!XNextEvent(dpy, &ev)) { + #if PRESELECT_PATCH + if (preselected) { + for (i = 0; i < preselected; i++) { + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + } + drawmenu(); + preselected = 0; + } + #endif // PRESELECT_PATCH + #if INPUTMETHOD_PATCH + if (XFilterEvent(&ev, None)) + continue; + if (composing) + continue; + #else if (XFilterEvent(&ev, win)) continue; + #endif // INPUTMETHOD_PATCH switch(ev.type) { + #if MOUSE_SUPPORT_PATCH + case ButtonPress: + buttonpress(&ev); + break; + #if MOTION_SUPPORT_PATCH + case MotionNotify: + motionevent(&ev.xbutton); + break; + #endif // MOTION_SUPPORT_PATCH + #endif // MOUSE_SUPPORT_PATCH case DestroyNotify: if (ev.xdestroywindow.window != win) break; @@ -631,6 +1720,11 @@ setup(void) { int x, y, i, j; unsigned int du; + #if RELATIVE_INPUT_WIDTH_PATCH + unsigned int tmp, minstrlen = 0, curstrlen = 0; + int numwidthchecks = 100; + struct item *item; + #endif // RELATIVE_INPUT_WIDTH_PATCH XSetWindowAttributes swa; XIM xim; Window w, dw, *dws; @@ -642,22 +1736,45 @@ setup(void) int a, di, n, area = 0; #endif /* init appearance */ - for (j = 0; j < SchemeLast; j++) { + #if XRESOURCES_PATCH + for (j = 0; j < SchemeLast; j++) + #if ALPHA_PATCH + scheme[j] = drw_scm_create(drw, (const char**)colors[j], alphas[j], 2); + #else scheme[j] = drw_scm_create(drw, (const char**)colors[j], 2); - } - for (j = 0; j < SchemeOut; ++j) { - for (i = 0; i < 2; ++i) - free(colors[j][i]); - } + #endif // ALPHA_PATCH + #else + for (j = 0; j < SchemeLast; j++) + #if ALPHA_PATCH + scheme[j] = drw_scm_create(drw, colors[j], alphas[j], 2); + #else + scheme[j] = drw_scm_create(drw, colors[j], 2); + #endif // ALPHA_PATCH + #endif // XRESOURCES_PATCH clip = XInternAtom(dpy, "CLIPBOARD", False); utf8 = XInternAtom(dpy, "UTF8_STRING", False); + #if WMTYPE_PATCH + type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + dock = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False); + #endif // WMTYPE_PATCH /* calculate menu geometry */ + #if PANGO_PATCH + bh = drw->font->h + 2; + #else bh = drw->fonts->h + 2; + #endif // PANGO_PATCH + #if LINE_HEIGHT_PATCH + bh = MAX(bh,lineheight); /* make a menu line AT LEAST 'lineheight' tall */ + #endif // LINE_HEIGHT_PATCH lines = MAX(lines, 0); mh = (lines + 1) * bh; + #if CENTER_PATCH && PANGO_PATCH + promptw = (prompt && *prompt) ? TEXTWM(prompt) - lrpad / 4 : 0; + #elif CENTER_PATCH promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + #endif // CENTER_PATCH #ifdef XINERAMA i = 0; if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { @@ -684,16 +1801,35 @@ setup(void) if (INTERSECT(x, y, 1, 1, info[i]) != 0) break; - if (centered) { + #if CENTER_PATCH + if (center) { + #if XYW_PATCH + mw = (dmw>0 ? dmw : MIN(MAX(max_textw() + promptw, min_width), info[i].width)); + #else mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width); + #endif // XYW_PATCH x = info[i].x_org + ((info[i].width - mw) / 2); - y = info[i].y_org + ((info[i].height - mh) / menu_height_ratio); + y = info[i].y_org + ((info[i].height - mh) / 2); } else { + #if XYW_PATCH + x = info[i].x_org + dmx; + y = info[i].y_org + (topbar ? dmy : info[i].height - mh - dmy); + mw = (dmw>0 ? dmw : info[i].width); + #else x = info[i].x_org; y = info[i].y_org + (topbar ? 0 : info[i].height - mh); mw = info[i].width; + #endif // XYW_PATCH } - + #elif XYW_PATCH + x = info[i].x_org + dmx; + y = info[i].y_org + (topbar ? dmy : info[i].height - mh - dmy); + mw = (dmw>0 ? dmw : info[i].width); + #else + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; + #endif // CENTER_PATCH / XYW_PATCH XFree(info); } else #endif @@ -701,36 +1837,131 @@ setup(void) if (!XGetWindowAttributes(dpy, parentwin, &wa)) die("could not get embedding window attributes: 0x%lx", parentwin); - - if (centered) { + #if CENTER_PATCH + if (center) { + #if XYW_PATCH + mw = (dmw>0 ? dmw : MIN(MAX(max_textw() + promptw, min_width), wa.width)); + #else mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); + #endif // XYW_PATCH x = (wa.width - mw) / 2; y = (wa.height - mh) / 2; } else { + #if XYW_PATCH + x = dmx; + y = topbar ? dmy : wa.height - mh - dmy; + mw = (dmw>0 ? dmw : wa.width); + #else x = 0; y = topbar ? 0 : wa.height - mh; mw = wa.width; + #endif // XYW_PATCH } + #elif XYW_PATCH + x = dmx; + y = topbar ? dmy : wa.height - mh - dmy; + mw = (dmw>0 ? dmw : wa.width); + #else + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + #endif // CENTER_PATCH / XYW_PATCH } + #if !CENTER_PATCH + #if PANGO_PATCH + promptw = (prompt && *prompt) ? TEXTWM(prompt) - lrpad / 4 : 0; + #else promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; - inputw = mw / 3; /* input width: ~33% of monitor width */ + #endif // PANGO_PATCH + #endif // CENTER_PATCH + #if RELATIVE_INPUT_WIDTH_PATCH + for (item = items; !lines && item && item->text; ++item) { + curstrlen = strlen(item->text); + if (numwidthchecks || minstrlen < curstrlen) { + numwidthchecks = MAX(numwidthchecks - 1, 0); + minstrlen = MAX(minstrlen, curstrlen); + if ((tmp = textw_clamp(item->text, mw/3)) > inputw) { + inputw = tmp; + if (tmp == mw/3) + break; + } + } + } + #else + inputw = mw / 3; /* input width: ~33.33% of monitor width */ + #endif // RELATIVE_INPUT_WIDTH_PATCH match(); /* create menu window */ + #if MANAGED_PATCH + swa.override_redirect = managed ? False : True; + #else swa.override_redirect = True; + #endif // MANAGED_PATCH + #if ALPHA_PATCH + swa.background_pixel = 0; + swa.colormap = cmap; + #else 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); + #endif // ALPHA_PATCH + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask + #if MOUSE_SUPPORT_PATCH + | ButtonPressMask + #if MOTION_SUPPORT_PATCH + | PointerMotionMask + #endif // MOTION_SUPPORT_PATCH + #endif // MOUSE_SUPPORT_PATCH + ; + win = XCreateWindow( + dpy, root, + #if BARPADDING_PATCH && BORDER_PATCH + x + sp, y + vp - (topbar ? 0 : border_width * 2), mw - 2 * sp - border_width * 2, mh, border_width, + #elif BARPADDING_PATCH + x + sp, y + vp, mw - 2 * sp, mh, 0, + #elif BORDER_PATCH + x, y - (topbar ? 0 : border_width * 2), mw - border_width * 2, mh, border_width, + #else + x, y, mw, mh, 0, + #endif // BORDER_PATCH | BARPADDING_PATCH + #if ALPHA_PATCH + depth, InputOutput, visual, + CWOverrideRedirect|CWBackPixel|CWBorderPixel|CWColormap|CWEventMask, &swa + #else + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa + #endif // ALPHA_PATCH + ); + #if BORDER_PATCH + if (border_width) + XSetWindowBorder(dpy, win, scheme[SchemeBorder][ColBg].pixel); + #endif // BORDER_PATCH XSetClassHint(dpy, win, &ch); + #if WMTYPE_PATCH + XChangeProperty(dpy, win, type, XA_ATOM, 32, PropModeReplace, + (unsigned char *) &dock, 1); + #endif // WMTYPE_PATCH /* input methods */ if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) die("XOpenIM failed: could not open input device"); + #if INPUTMETHOD_PATCH + init_input_method(xim); + #else xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, win, XNFocusWindow, win, NULL); + #endif // INPUTMETHOD_PATCH + + #if MANAGED_PATCH + if (managed) { + XTextProperty prop; + char *windowtitle = prompt != NULL ? prompt : "dmenu"; + Xutf8TextListToTextProperty(dpy, &windowtitle, 1, XUTF8StringStyle, &prop); + XSetWMName(dpy, win, &prop); + XSetTextProperty(dpy, win, &prop, XInternAtom(dpy, "_NET_WM_NAME", False)); + XFree(prop.value); + } + #endif // MANAGED_PATCH XMapRaised(dpy, win); if (embed) { @@ -741,8 +1972,13 @@ setup(void) XSelectInput(dpy, dws[i], FocusChangeMask); XFree(dws); } + #if !INPUTMETHOD_PATCH grabfocus(); + #endif // INPUTMETHOD_PATCH } + #if INPUTMETHOD_PATCH + grabfocus(); + #endif // INPUTMETHOD_PATCH drw_resize(drw, mw, mh); drawmenu(); } @@ -750,91 +1986,391 @@ setup(void) static void usage(void) { - die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" - " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); -} - -void -readxresources(void) { - XrmInitialize(); - - char* xrm; - if ((xrm = XResourceManagerString(drw->dpy))) { - char *type; - XrmDatabase xdb = XrmGetStringDatabase(xrm); - XrmValue xval; - - if (XrmGetResource(xdb, "dmenu.font", "*", &type, &xval)) - fonts[0] = strdup(xval.addr); - else - fonts[0] = strdup(fonts[0]); - if (XrmGetResource(xdb, "dmenu.background", "*", &type, &xval)) - colors[SchemeNorm][ColBg] = strdup(xval.addr); - else - colors[SchemeNorm][ColBg] = strdup(colors[SchemeNorm][ColBg]); - if (XrmGetResource(xdb, "dmenu.foreground", "*", &type, &xval)) - colors[SchemeNorm][ColFg] = strdup(xval.addr); - else - colors[SchemeNorm][ColFg] = strdup(colors[SchemeNorm][ColFg]); - if (XrmGetResource(xdb, "dmenu.selbackground", "*", &type, &xval)) - colors[SchemeSel][ColBg] = strdup(xval.addr); - else - colors[SchemeSel][ColBg] = strdup(colors[SchemeSel][ColBg]); - if (XrmGetResource(xdb, "dmenu.selforeground", "*", &type, &xval)) - colors[SchemeSel][ColFg] = strdup(xval.addr); - else - colors[SchemeSel][ColFg] = strdup(colors[SchemeSel][ColFg]); - - XrmDestroyDatabase(xdb); - } + die("usage: dmenu [-bv" + #if CENTER_PATCH + "c" + #endif + #if !NON_BLOCKING_STDIN_PATCH + "f" + #endif // NON_BLOCKING_STDIN_PATCH + #if QUIET_PATCH + "q" + #endif // QUIET_PATCH + #if INCREMENTAL_PATCH + "r" + #endif // INCREMENTAL_PATCH + #if CASEINSENSITIVE_PATCH + "s" + #else + "i" + #endif // CASEINSENSITIVE_PATCH + #if INSTANT_PATCH + "n" + #endif // INSTANT_PATCH + #if PRINTINPUTTEXT_PATCH + "t" + #endif // PRINTINPUTTEXT_PATCH + #if PREFIXCOMPLETION_PATCH + "x" + #endif // PREFIXCOMPLETION_PATCH + #if FUZZYMATCH_PATCH + "F" + #endif // FUZZYMATCH_PATCH + #if PASSWORD_PATCH + "P" + #endif // PASSWORD_PATCH + #if NO_SORT_PATCH + "S" + #endif // NO_SORT_PATCH + #if REJECTNOMATCH_PATCH + "R" // (changed from r to R due to conflict with INCREMENTAL_PATCH) + #endif // REJECTNOMATCH_PATCH + #if RESTRICT_RETURN_PATCH + "1" + #endif // RESTRICT_RETURN_PATCH + "] " + #if CARET_WIDTH_PATCH + "[-cw caret_width] " + #endif // CARET_WIDTH_PATCH + #if VI_MODE_PATCH + "[-vi] " + #endif // VI_MODE_PATCH + #if MANAGED_PATCH + "[-wm] " + #endif // MANAGED_PATCH + #if GRID_PATCH + "[-g columns] " + #endif // GRID_PATCH + "[-l lines] [-p prompt] [-fn font] [-m monitor]" + "\n [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]" + #if DYNAMIC_OPTIONS_PATCH || FZFEXPECT_PATCH || ALPHA_PATCH || BORDER_PATCH || HIGHPRIORITY_PATCH + "\n " + #endif + #if DYNAMIC_OPTIONS_PATCH + " [-dy command]" + #endif // DYNAMIC_OPTIONS_PATCH + #if FZFEXPECT_PATCH + " [-ex expectkey]" + #endif // FZFEXPECT_PATCH + #if ALPHA_PATCH + " [-o opacity]" + #endif // ALPHA_PATCH + #if BORDER_PATCH + " [-bw width]" + #endif // BORDER_PATCH + #if HIGHPRIORITY_PATCH + " [-hb color] [-hf color] [-hp items]" + #endif // HIGHPRIORITY_PATCH + #if INITIALTEXT_PATCH || LINE_HEIGHT_PATCH || PRESELECT_PATCH || NAVHISTORY_PATCH || XYW_PATCH + "\n " + #endif + #if INITIALTEXT_PATCH + " [-it text]" + #endif // INITIALTEXT_PATCH + #if LINE_HEIGHT_PATCH + " [-h height]" + #endif // LINE_HEIGHT_PATCH + #if PRESELECT_PATCH + " [-ps index]" + #endif // PRESELECT_PATCH + #if NAVHISTORY_PATCH + " [-H histfile]" + #endif // NAVHISTORY_PATCH + #if XYW_PATCH + " [-X xoffset] [-Y yoffset] [-W width]" // (arguments made upper case due to conflicts) + #endif // XYW_PATCH + #if HIGHLIGHT_PATCH + "\n [-nhb color] [-nhf color] [-shb color] [-shf color]" // highlight colors + #endif // HIGHLIGHT_PATCH + #if SEPARATOR_PATCH + "\n [-d separator] [-D separator]" + #endif // SEPARATOR_PATCH + "\n"); } int main(int argc, char *argv[]) { XWindowAttributes wa; - int i, fast = 0; + int i; + #if !NON_BLOCKING_STDIN_PATCH + int fast = 0; + #endif // NON_BLOCKING_STDIN_PATCH + + #if XRESOURCES_PATCH + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + #if INPUTMETHOD_PATCH + if (!XSetLocaleModifiers("")) + fputs("warning: could not set locale modifiers", stderr); + #endif // INPUTMETHOD_PATCH + if (!(dpy = XOpenDisplay(NULL))) + die("cannot open display"); + + /* These need to be checked before we init the visuals and read X resources. */ + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-v")) { /* prints version information */ + puts("dmenu-"VERSION); + exit(0); + } else if (!strcmp(argv[i], "-w")) { + argv[i][0] = '\0'; + embed = strdup(argv[++i]); + #if ALPHA_PATCH + } else if (!strcmp(argv[i], "-o")) { /* opacity, pass -o 0 to disable alpha */ + opacity = atoi(argv[++i]); + #endif // ALPHA_PATCH + } else { + continue; + } + argv[i][0] = '\0'; // mark as used + } + + 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); + + #if ALPHA_PATCH + xinitvisual(); + drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap); + #else + drw = drw_create(dpy, screen, root, wa.width, wa.height); + #endif // ALPHA_PATCH + readxresources(); + #endif // XRESOURCES_PATCH + + for (i = 1; i < argc; i++) { + if (argv[i][0] == '\0') + continue; - for (i = 1; i < argc; i++) /* 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 */ + } 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 */ + #if CENTER_PATCH + } else if (!strcmp(argv[i], "-c")) { /* toggles centering of dmenu window on screen */ + center = !center; + #endif // CENTER_PATCH + #if !NON_BLOCKING_STDIN_PATCH + } else if (!strcmp(argv[i], "-f")) { /* grabs keyboard before reading stdin */ fast = 1; - else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */ - centered = 1; - else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + #endif // NON_BLOCKING_STDIN_PATCH + #if INCREMENTAL_PATCH + } else if (!strcmp(argv[i], "-r")) { /* incremental */ + incremental = !incremental; + #endif // INCREMENTAL_PATCH + #if QUIET_PATCH + } else if (!strcmp(argv[i], "-q")) { /* quiet, don't list items if search is empty */ + quiet = !quiet; + #endif // QUIET_PATCH + #if CASEINSENSITIVE_PATCH + } else if (!strcmp(argv[i], "-s")) { /* case-sensitive item matching */ + fstrncmp = strncmp; + fstrstr = strstr; + #else + } else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ fstrncmp = strncasecmp; fstrstr = cistrstr; + #endif // CASEINSENSITIVE_PATCH + #if VI_MODE_PATCH + } else if (!strcmp(argv[i], "-vi")) { + if (i + 1 < argc) { + if (!strcmp(argv[i+1], "0")) { + start_mode = 0; + i++; + } else if (!strcmp(argv[i+1], "1")) { + start_mode = 1; + i++; + } + } + vi_mode = 1; + using_vi_mode = start_mode; + global_esc.ksym = XK_Escape; + global_esc.state = 0; + #endif // VI_MODE_PATCH + #if MANAGED_PATCH + } else if (!strcmp(argv[i], "-wm")) { /* display as managed wm window */ + managed = 1; + #endif // MANAGED_PATCH + #if INSTANT_PATCH + } else if (!strcmp(argv[i], "-n")) { /* instant select only match */ + instant = !instant; + #endif // INSTANT_PATCH + #if PRINTINPUTTEXT_PATCH + } else if (!strcmp(argv[i], "-t")) { /* favors text input over selection */ + use_text_input = 1; + #endif // PRINTINPUTTEXT_PATCH + #if PREFIXCOMPLETION_PATCH + } else if (!strcmp(argv[i], "-x")) { /* invert use_prefix */ + use_prefix = !use_prefix; + #endif // PREFIXCOMPLETION_PATCH + #if FUZZYMATCH_PATCH + } else if (!strcmp(argv[i], "-F")) { /* disable/enable fuzzy matching, depends on default */ + fuzzy = !fuzzy; + #endif // FUZZYMATCH_PATCH + #if PASSWORD_PATCH + } else if (!strcmp(argv[i], "-P")) { /* is the input a password */ + passwd = 1; + #endif // PASSWORD_PATCH + #if FZFEXPECT_PATCH + } else if (!strcmp(argv[i], "-ex")) { /* expect key */ + expected = argv[++i]; + #endif // FZFEXPECT_PATCH + #if REJECTNOMATCH_PATCH + } else if (!strcmp(argv[i], "-R")) { /* reject input which results in no match */ + reject_no_match = 1; + #endif // REJECTNOMATCH_PATCH + #if NO_SORT_PATCH + } else if (!strcmp(argv[i], "-S")) { /* do not sort matches */ + sortmatches = 0; + #endif // NO_SORT_PATCH + #if PRINTINDEX_PATCH + } else if (!strcmp(argv[i], "-ix")) { /* adds ability to return index in list */ + print_index = 1; + #endif // PRINTINDEX_PATCH + #if RESTRICT_RETURN_PATCH + } else if (!strcmp(argv[i], "-1")) { + restrict_return = 1; + #endif // RESTRICT_RETURN_PATCH } else if (i + 1 == argc) usage(); /* these options take one argument */ + #if NAVHISTORY_PATCH + else if (!strcmp(argv[i], "-H")) + histfile = argv[++i]; + #endif // NAVHISTORY_PATCH + #if GRID_PATCH + else if (!strcmp(argv[i], "-g")) { /* number of columns in grid */ + columns = atoi(argv[++i]); + if (columns && lines == 0) + lines = 1; + } + #endif // GRID_PATCH else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ lines = atoi(argv[++i]); + #if XYW_PATCH + else if (!strcmp(argv[i], "-X")) /* window x offset */ + dmx = atoi(argv[++i]); + else if (!strcmp(argv[i], "-Y")) /* window y offset (from bottom up if -b) */ + dmy = atoi(argv[++i]); + else if (!strcmp(argv[i], "-W")) /* make dmenu this wide */ + dmw = atoi(argv[++i]); + #endif // XYW_PATCH else if (!strcmp(argv[i], "-m")) mon = atoi(argv[++i]); + #if ALPHA_PATCH && !XRESOURCES_PATCH + else if (!strcmp(argv[i], "-o")) /* opacity, pass -o 0 to disable alpha */ + opacity = atoi(argv[++i]); + #endif // ALPHA_PATCH 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 */ - tempfonts = argv[++i]; + #if PANGO_PATCH + font = argv[++i]; + #else + fonts[0] = argv[++i]; + #endif // PANGO_PATCH + #if LINE_HEIGHT_PATCH + else if(!strcmp(argv[i], "-h")) { /* minimum height of one menu line */ + lineheight = atoi(argv[++i]); + lineheight = MAX(lineheight, min_lineheight); /* reasonable default in case of value too small/negative */ + } + #endif // LINE_HEIGHT_PATCH else if (!strcmp(argv[i], "-nb")) /* normal background color */ - colortemp[0] = argv[++i]; + colors[SchemeNorm][ColBg] = argv[++i]; else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ - colortemp[1] = argv[++i]; + colors[SchemeNorm][ColFg] = argv[++i]; else if (!strcmp(argv[i], "-sb")) /* selected background color */ - colortemp[2] = argv[++i]; + colors[SchemeSel][ColBg] = argv[++i]; else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ - colortemp[3] = argv[++i]; + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-ob")) /* outline background color */ + colors[SchemeOut][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-of")) /* outline foreground color */ + colors[SchemeOut][ColFg] = argv[++i]; + #if HIGHPRIORITY_PATCH + else if (!strcmp(argv[i], "-hb")) /* high priority background color */ + colors[SchemeHp][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-hf")) /* low priority background color */ + colors[SchemeHp][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-hp")) + hpitems = tokenize(argv[++i], ",", &hplength); + #endif // HIGHPRIORITY_PATCH + #if HIGHLIGHT_PATCH + else if (!strcmp(argv[i], "-nhb")) /* normal hi background color */ + colors[SchemeNormHighlight][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nhf")) /* normal hi foreground color */ + colors[SchemeNormHighlight][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-shb")) /* selected hi background color */ + colors[SchemeSelHighlight][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-shf")) /* selected hi foreground color */ + colors[SchemeSelHighlight][ColFg] = argv[++i]; + #endif // HIGHLIGHT_PATCH + #if CARET_WIDTH_PATCH + else if (!strcmp(argv[i], "-cw")) /* sets caret witdth */ + caret_width = atoi(argv[++i]); + #endif // CARET_WIDTH_PATCH + #if !XRESOURCES_PATCH else if (!strcmp(argv[i], "-w")) /* embedding window id */ embed = argv[++i]; - else + #endif // XRESOURCES_PATCH + #if SEPARATOR_PATCH + else if (!strcmp(argv[i], "-d")) /* field separator */ + { + sepchr = strchr; + separator = argv[++i][0]; + separator_reverse = argv[i][1] == '|'; + } + else if (!strcmp(argv[i], "-D")) /* greedy field separator */ + { + sepchr = strrchr; + separator = argv[++i][0]; + separator_reverse = argv[i][1] == '|'; + } + #endif // SEPARATOR_PATCH + #if PRESELECT_PATCH + else if (!strcmp(argv[i], "-ps")) /* preselected item */ + preselected = atoi(argv[++i]); + #endif // PRESELECT_PATCH + #if DYNAMIC_OPTIONS_PATCH + else if (!strcmp(argv[i], "-dy")) /* dynamic command to run */ + dynamic = argv[++i]; + #endif // DYNAMIC_OPTIONS_PATCH + #if BORDER_PATCH + else if (!strcmp(argv[i], "-bw")) /* border width around dmenu */ + border_width = atoi(argv[++i]); + #endif // BORDER_PATCH + #if INITIALTEXT_PATCH + else if (!strcmp(argv[i], "-it")) { /* adds initial text */ + const char * text = argv[++i]; + insert(text, strlen(text)); + } + #endif // INITIALTEXT_PATCH + else { usage(); + } + } + #if XRESOURCES_PATCH + #if PANGO_PATCH + if (!drw_font_create(drw, font)) + die("no fonts could be loaded."); + #else + if (!drw_fontset_create(drw, (const char**)fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + #endif // PANGO_PATCH + #else // !XRESOURCES_PATCH if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fputs("warning: no locale support\n", stderr); + #if INPUTMETHOD_PATCH + if (!XSetLocaleModifiers("")) + fputs("warning: could not set locale modifiers", stderr); + #endif // INPUTMETHOD_PATCH if (!(dpy = XOpenDisplay(NULL))) die("cannot open display"); screen = DefaultScreen(dpy); @@ -844,38 +2380,72 @@ main(int argc, char *argv[]) if (!XGetWindowAttributes(dpy, parentwin, &wa)) die("could not get embedding window attributes: 0x%lx", parentwin); + + #if ALPHA_PATCH + xinitvisual(); + drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap); + #else drw = drw_create(dpy, screen, root, wa.width, wa.height); - readxresources(); - /* Now we check whether to override xresources with commandline parameters */ - if ( tempfonts ) - fonts[0] = strdup(tempfonts); - if ( colortemp[0]) - colors[SchemeNorm][ColBg] = strdup(colortemp[0]); - if ( colortemp[1]) - colors[SchemeNorm][ColFg] = strdup(colortemp[1]); - if ( colortemp[2]) - colors[SchemeSel][ColBg] = strdup(colortemp[2]); - if ( colortemp[3]) - colors[SchemeSel][ColFg] = strdup(colortemp[3]); + #endif // ALPHA_PATCH - if (!drw_fontset_create(drw, (const char**)fonts, LENGTH(fonts))) + #if PANGO_PATCH + if (!drw_font_create(drw, font)) + die("no fonts could be loaded."); + #else + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) die("no fonts could be loaded."); + #endif // PANGO_PATCH + #endif // XRESOURCES_PATCH - free(fonts[0]); + #if PANGO_PATCH + lrpad = drw->font->h; + #else lrpad = drw->fonts->h; + #endif // PANGO_PATCH + + #if BARPADDING_PATCH + sp = sidepad; + vp = (topbar ? vertpad : - vertpad); + #endif // BARPADDING_PATCH + + #if LINE_HEIGHT_PATCH + if (lineheight == -1) + #if PANGO_PATCH + lineheight = drw->font->h * 2.5; + #else + lineheight = drw->fonts->h * 2.5; + #endif // PANGO_PATCH + #endif // LINE_HEIGHT_PATCH #ifdef __OpenBSD__ if (pledge("stdio rpath", NULL) == -1) die("pledge"); #endif + #if NAVHISTORY_PATCH + loadhistory(); + #endif // NAVHISTORY_PATCH + #if NON_BLOCKING_STDIN_PATCH + grabkeyboard(); + #else if (fast && !isatty(0)) { grabkeyboard(); + #if DYNAMIC_OPTIONS_PATCH + if (!(dynamic && *dynamic)) + readstdin(); + #else readstdin(); + #endif // DYNAMIC_OPTIONS_PATCH } else { + #if DYNAMIC_OPTIONS_PATCH + if (!(dynamic && *dynamic)) + readstdin(); + #else readstdin(); + #endif // DYNAMIC_OPTIONS_PATCH grabkeyboard(); } + #endif // NON_BLOCKING_STDIN_PATCH setup(); run(); diff --git a/dmenu_run b/dmenu_run @@ -1,2 +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 @@ -5,9 +5,15 @@ #include <X11/Xlib.h> #include <X11/Xft/Xft.h> +#include "patches.h" #include "drw.h" #include "util.h" +#if BIDI_PATCH && !PANGO_PATCH +#include <fribidi.h> +#endif //BIDI_PATCH + +#if !PANGO_PATCH || HIGHLIGHT_PATCH #define UTF_INVALID 0xFFFD static int @@ -46,8 +52,42 @@ utf8decode(const char *s_in, long *u, int *err) return len; } +#if HIGHLIGHT_PATCH +int +utf8len(const char *c) +{ + long utf8codepoint = 0; + int utf8err = 0; + return utf8decode(c, &utf8codepoint, &utf8err); +} +#endif // HIGHLIGHT_PATCH +#endif // PANGO_PATCH + +#if BIDI_PATCH && !PANGO_PATCH +static char fribidi_text[BUFSIZ] = ""; + +static void +apply_fribidi(const char *str) +{ + FriBidiStrIndex len = strlen(str); + FriBidiChar logical[BUFSIZ]; + FriBidiChar visual[BUFSIZ]; + FriBidiParType base = FRIBIDI_PAR_ON; + FriBidiCharSet charset; + + charset = fribidi_parse_charset("UTF-8"); + len = fribidi_charset_to_unicode(charset, str, len, logical); + fribidi_log2vis(logical, len, &base, visual, NULL, NULL, NULL); + fribidi_unicode_to_charset(charset, visual, len, fribidi_text); +} +#endif //BIDI_PATCH + Drw * +#if ALPHA_PATCH +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap) +#else drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +#endif // ALPHA_PATCH { Drw *drw = ecalloc(1, sizeof(Drw)); @@ -56,8 +96,16 @@ drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h drw->root = root; drw->w = w; drw->h = h; + #if ALPHA_PATCH + drw->visual = visual; + drw->depth = depth; + drw->cmap = cmap; + drw->drawable = XCreatePixmap(dpy, root, w, h, depth); + drw->gc = XCreateGC(dpy, drw->drawable, 0, NULL); + #else drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); drw->gc = XCreateGC(dpy, root, 0, NULL); + #endif // ALPHA_PATCH XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); return drw; @@ -73,7 +121,11 @@ drw_resize(Drw *drw, unsigned int w, unsigned int h) drw->h = h; if (drw->drawable) XFreePixmap(drw->dpy, drw->drawable); + #if ALPHA_PATCH + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, drw->depth); + #else drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); + #endif // ALPHA_PATCH } void @@ -81,10 +133,49 @@ drw_free(Drw *drw) { XFreePixmap(drw->dpy, drw->drawable); XFreeGC(drw->dpy, drw->gc); + #if PANGO_PATCH + drw_font_free(drw->font); + #else drw_fontset_free(drw->fonts); + #endif // PANGO_PATCH free(drw); } +#if PANGO_PATCH +/* 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; +} +#else /* This function is an implementation detail. Library users should use * drw_fontset_create instead. */ @@ -119,6 +210,21 @@ xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) die("no font specified."); } + #if NO_COLOR_EMOJI_PATCH + /* Do not allow using color fonts. This is a workaround for a BadLength + * error from Xft with color glyphs. Modelled on the Xterm workaround. See + * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + * https://lists.suckless.org/dev/1701/30932.html + * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 + * and lots more all over the internet. + */ + FcBool iscol; + if (FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { + XftFontClose(drw->dpy, xfont); + return NULL; + } + #endif // NO_COLOR_EMOJI_PATCH + font = ecalloc(1, sizeof(Fnt)); font->xfont = xfont; font->pattern = pattern; @@ -127,18 +233,38 @@ xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) return font; } +#endif // PANGO_PATCH static void xfont_free(Fnt *font) { if (!font) return; + #if PANGO_PATCH + if (font->layout) + g_object_unref(font->layout); + #else if (font->pattern) FcPatternDestroy(font->pattern); XftFontClose(font->dpy, font->xfont); + #endif // PANGO_PATCH free(font); } +#if PANGO_PATCH +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); +} +#else Fnt* drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) { @@ -156,7 +282,16 @@ drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) } return (drw->fonts = ret); } +#endif // PANGO_PATCH +#if PANGO_PATCH +void +drw_font_free(Fnt *font) +{ + if (font) + xfont_free(font); +} +#else void drw_fontset_free(Fnt *font) { @@ -165,22 +300,39 @@ drw_fontset_free(Fnt *font) xfont_free(font); } } +#endif // PANGO_PATCH void +#if ALPHA_PATCH +drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha) +#else drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +#endif // ALPHA_PATCH { if (!drw || !dest || !clrname) return; + #if ALPHA_PATCH + if (!XftColorAllocName(drw->dpy, drw->visual, drw->cmap, + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); + + dest->pixel = (dest->pixel & 0x00ffffffU) | (alpha << 24); + #else if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), DefaultColormap(drw->dpy, drw->screen), clrname, dest)) die("error, cannot allocate color '%s'", clrname); + #endif // ALPHA_PATCH } /* Create color schemes. */ Clr * +#if ALPHA_PATCH +drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount) +#else drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +#endif // ALPHA_PATCH { size_t i; Clr *ret; @@ -190,7 +342,11 @@ drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) return NULL; for (i = 0; i < clrcount; i++) + #if ALPHA_PATCH + drw_clr_create(drw, &ret[i], clrnames[i], alphas[i]); + #else drw_clr_create(drw, &ret[i], clrnames[i]); + #endif // ALPHA_PATCH return ret; } @@ -218,12 +374,14 @@ drw_scm_free(Drw *drw, Clr *scm, size_t clrcount) free(scm); } +#if !PANGO_PATCH void drw_setfontset(Drw *drw, Fnt *set) { if (drw) drw->fonts = set; } +#endif // PANGO_PATCH void drw_setscheme(Drw *drw, Clr *scm) @@ -244,6 +402,77 @@ drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); } +#if PANGO_PATCH +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); + #if ALPHA_PATCH + d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap); + #else + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + #endif // ALPHA_PATCH + 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); +} +#else int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) { @@ -262,6 +491,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp /* keep track of a couple codepoints for which we have no match. */ static unsigned int nomatches[128], ellipsis_width, invalid_width; static const char invalid[] = "�"; + const char *ellipsis = "..."; if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) return 0; @@ -271,22 +501,30 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp } else { XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); - if (w < lpad) - return x + w; + #if ALPHA_PATCH + d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap); + #else d = XftDrawCreate(drw->dpy, drw->drawable, DefaultVisual(drw->dpy, drw->screen), DefaultColormap(drw->dpy, drw->screen)); + #endif // ALPHA_PATCH x += lpad; w -= lpad; } - usedfont = drw->fonts; if (!ellipsis_width && render) - ellipsis_width = drw_fontset_getwidth(drw, "..."); + ellipsis_width = drw_fontset_getwidth(drw, ellipsis); if (!invalid_width && render) invalid_width = drw_fontset_getwidth(drw, invalid); + + #if BIDI_PATCH + apply_fribidi(text); + text = fribidi_text; + #endif // BIDI_PATCH + + usedfont = drw->fonts; while (1) { - ew = ellipsis_len = utf8err = utf8charlen = utf8strlen = 0; + ew = ellipsis_len = utf8err = utf8strlen = 0; utf8str = text; nextfont = NULL; while (*text) { @@ -312,12 +550,12 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp else utf8strlen = ellipsis_len; } else if (curfont == usedfont) { - text += utf8charlen; + text += utf8charlen; utf8strlen += utf8err ? 0 : utf8charlen; ew += utf8err ? 0 : tmpw; - } else { - nextfont = curfont; - } + } else { + nextfont = curfont; + } break; } } @@ -333,7 +571,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); - } + } x += ew; w -= ew; } @@ -343,8 +581,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp x += invalid_width; w -= invalid_width; } - if (render && overflow) - drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); + if (render && overflow && ellipsis_w) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, ellipsis, invert); if (!*text || overflow) { break; @@ -376,6 +614,9 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp fcpattern = FcPatternDuplicate(drw->fonts->pattern); FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + #if NO_COLOR_EMOJI_PATCH + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + #endif // NO_COLOR_EMOJI_PATCH FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); FcDefaultSubstitute(fcpattern); @@ -404,6 +645,7 @@ no_match: return x + (render ? w : 0); } +#endif // PANGO_PATCH void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) @@ -415,6 +657,24 @@ drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) XSync(drw->dpy, False); } +#if PANGO_PATCH +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); +} +#else unsigned int drw_fontset_getwidth(Drw *drw, const char *text) { @@ -431,7 +691,29 @@ drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); return MIN(n, tmp); } +#endif // PANGO_PATCH +#if PANGO_PATCH +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; +} +#else void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) { @@ -446,6 +728,7 @@ drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, if (h) *h = font->h; } +#endif // PANGO_PATCH Cur * drw_cur_create(Drw *drw, int shape) @@ -469,3 +752,7 @@ drw_cur_free(Drw *drw, Cur *cursor) XFreeCursor(drw->dpy, cursor->cursor); free(cursor); } + +#if SCROLL_PATCH +#include "patch/scroll.c" +#endif diff --git a/drw.h b/drw.h @@ -1,5 +1,10 @@ /* See LICENSE file for copyright and license details. */ +#if PANGO_PATCH +#include <pango/pango.h> +#include <pango/pangoxft.h> +#endif // PANGO_PATCH + typedef struct { Cursor cursor; } Cur; @@ -7,9 +12,13 @@ typedef struct { typedef struct Fnt { Display *dpy; unsigned int h; + #if PANGO_PATCH + PangoLayout *layout; + #else XftFont *xfont; FcPattern *pattern; struct Fnt *next; + #endif // PANGO_PATCH } Fnt; enum { ColFg, ColBg }; /* Clr scheme index */ @@ -20,28 +29,58 @@ typedef struct { Display *dpy; int screen; Window root; + #if ALPHA_PATCH + Visual *visual; + unsigned int depth; + Colormap cmap; + #endif // ALPHA_PATCH Drawable drawable; GC gc; Clr *scheme; + #if PANGO_PATCH + Fnt *font; + #else Fnt *fonts; + #endif // PANGO_PATCH } Drw; /* Drawable abstraction */ +#if ALPHA_PATCH +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap); +#else Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +#endif // ALPHA_PATCH void drw_resize(Drw *drw, unsigned int w, unsigned int h); void drw_free(Drw *drw); /* Fnt abstraction */ +#if PANGO_PATCH +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); +#else Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); void drw_fontset_free(Fnt* set); unsigned int drw_fontset_getwidth(Drw *drw, const char *text); 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); +#endif // PANGO_PATCH + +#if HIGHLIGHT_PATCH +int utf8len(const char *c); +#endif // HIGHLIGHT_PATCH /* Colorscheme abstraction */ +#if ALPHA_PATCH +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount); +#else void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); -void drw_clr_free(Drw *drw, Clr *c); Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); +#endif // ALPHA_PATCH +void drw_clr_free(Drw *drw, Clr *c); void drw_scm_free(Drw *drw, Clr *scm, size_t clrcount); /* Cursor abstraction */ @@ -49,12 +88,22 @@ Cur *drw_cur_create(Drw *drw, int shape); void drw_cur_free(Drw *drw, Cur *cursor); /* Drawing context manipulation */ +#if !PANGO_PATCH void drw_setfontset(Drw *drw, Fnt *set); +#endif // PANGO_PATCH 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); +#if PANGO_PATCH +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); +#else int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); +#endif // PANGO_PATCH /* Map functions */ void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); + +#if SCROLL_PATCH +#include "patch/scroll.h" +#endif diff --git a/patch/center.c b/patch/center.c @@ -0,0 +1,8 @@ +static int +max_textw(void) +{ + int len = 0; + for (struct item *item = items; item && item->text; item++) + len = MAX(TEXTW(item->text), len); + return len; +} diff --git a/patch/dynamicoptions.c b/patch/dynamicoptions.c @@ -0,0 +1,90 @@ +static void +refreshoptions(void) +{ + int dynlen = strlen(dynamic); + char* cmd= malloc(dynlen + strlen(text) + 2); + if (cmd == NULL) + die("malloc:"); + sprintf(cmd, "%s %s", dynamic, text); + FILE *stream = popen(cmd, "r"); + if (!stream) + die("popen(%s):", cmd); + readstream(stream); + int pc = pclose(stream); + if (pc == -1) + die("pclose:"); + free(cmd); + curr = sel = items; +} + +static void +readstream(FILE* stream) +{ + char buf[sizeof text], *p; + size_t i, imax = 0, size = 0; + unsigned int tmpmax = 0; + + /* read each line from stdin and add it to the item list */ + for (i = 0; fgets(buf, sizeof buf, stream); i++) { + if (i + 1 >= size / sizeof *items) + if (!(items = realloc(items, (size += BUFSIZ)))) + die("cannot realloc %u bytes:", size); + if ((p = strchr(buf, '\n'))) + *p = '\0'; + if (!(items[i].text = strdup(buf))) + die("cannot strdup %u bytes:", strlen(buf) + 1); + #if SEPARATOR_PATCH + if (separator && (p = sepchr(items[i].text, separator)) != NULL) { + *p = '\0'; + items[i].text_output = ++p; + } else { + items[i].text_output = items[i].text; + } + if (separator_reverse) { + p = items[i].text; + items[i].text = items[i].text_output; + items[i].text_output = p; + } + #elif TSV_PATCH + if ((p = strchr(buf, '\t'))) + *p = '\0'; + if (!(items[i].stext = strdup(buf))) + die("cannot strdup %u bytes:", strlen(buf) + 1); + #endif // TSV_PATCH + #if MULTI_SELECTION_PATCH + items[i].id = i; + #else + items[i].out = 0; + #endif // MULTI_SELECTION_PATCH + #if HIGHPRIORITY_PATCH + items[i].hp = arrayhas(hpitems, hplength, items[i].text); + #endif // HIGHPRIORITY_PATCH + #if PANGO_PATCH + drw_font_getexts(drw->font, buf, strlen(buf), &tmpmax, NULL, True); + #else + drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); + #endif // PANGO_PATCH + if (tmpmax > inputw) { + inputw = tmpmax; + imax = i; + } + } + + /* If the command did not give any output at all, then do not clear the existing items */ + if (!i) + return; + + if (items) + items[i].text = NULL; + #if PANGO_PATCH + inputw = items ? TEXTWM(items[imax].text) : 0; + #else + inputw = items ? TEXTW(items[imax].text) : 0; + #endif // PANGO_PATCH + if (!dynamic || !*dynamic) + lines = MIN(lines, i); + else { + text[0] = '\0'; + cursor = 0; + } +} diff --git a/patch/dynamicoptions.h b/patch/dynamicoptions.h @@ -0,0 +1,2 @@ +static void refreshoptions(void); +static void readstream(FILE* stream); diff --git a/patch/fuzzymatch.c b/patch/fuzzymatch.c @@ -0,0 +1,115 @@ +#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; + #if HIGHPRIORITY_PATCH + struct item *lhpprefix, *hpprefixend; + lhpprefix = hpprefixend = NULL; + #endif // HIGHPRIORITY_PATCH + 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; + } + + #if NO_SORT_PATCH + if (sortmatches) + #endif // NO_SORT_PATCH + /* 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]) { + #if HIGHPRIORITY_PATCH + #if NO_SORT_PATCH + if (sortmatches && it->hp) + #else + if (it->hp) + #endif // NO_SORT_PATCH + appenditem(it, &lhpprefix, &hpprefixend); + else + appenditem(it, &matches, &matchend); + #else + appenditem(it, &matches, &matchend); + #endif // HIGHPRIORITY_PATCH + } + free(fuzzymatches); + } + #if HIGHPRIORITY_PATCH + if (lhpprefix) { + hpprefixend->right = matches; + matches = lhpprefix; + } + #endif // HIGHPRIORITY_PATCH + curr = sel = matches; + + #if INSTANT_PATCH + if (instant && matches && matches==matchend) { + printitem(matches); + cleanup(); + exit(0); + } + #endif // INSTANT_PATCH + + calcoffsets(); +} diff --git a/patch/fzfexpect.c b/patch/fzfexpect.c @@ -0,0 +1,33 @@ +static char *expected; +#if MULTI_SELECTION_PATCH +void +expect(char *expect, XKeyEvent *ev) +{ + if (sel && expected && strstr(expected, expect)) { + if (expected && sel && !(ev->state & ShiftMask)) + puts(expect); + printselected(ev->state); + cleanup(); + exit(1); + } else if (!sel && expected && strstr(expected, expect)) { + puts(expect); + cleanup(); + exit(1); + } +} +#else +void +expect(char *expect, XKeyEvent *ignored) +{ + if (sel && expected && strstr(expected, expect)) { + puts(expect); + printitem(sel); + cleanup(); + exit(1); + } else if (!sel && expected && strstr(expected, expect)){ + puts(expect); + cleanup(); + exit(1); + } +} +#endif // MULTI_SELECTION_PATCH diff --git a/patch/fzfexpect.h b/patch/fzfexpect.h @@ -0,0 +1 @@ +static void expect(char *expect, XKeyEvent *ev); diff --git a/patch/highlight.c b/patch/highlight.c @@ -0,0 +1,107 @@ +static void +#if EMOJI_HIGHLIGHT_PATCH +drawhighlights(struct item *item, char *output, int x, int y, int maxw) +#else +drawhighlights(struct item *item, int x, int y, int maxw) +#endif // EMOJI_HIGHLIGHT_PATCH +{ + char restorechar, tokens[sizeof text], *highlight, *token; + int indent, highlightlen; + + #if FUZZYMATCH_PATCH + int i; + #endif // FUZZYMATCH_PATCH + + #if EMOJI_HIGHLIGHT_PATCH + char *itemtext = output; + #elif TSV_PATCH && !SEPARATOR_PATCH + char *itemtext = item->stext; + #else + char *itemtext = item->text; + #endif // EMOJI_HIGHLIGHT_PATCH | TSV_PATCH + + if (!(strlen(itemtext) && strlen(text))) + return; + + /* Do not highlight items scheduled for output */ + #if MULTI_SELECTION_PATCH + if (issel(item->id)) + return; + #else + if (item->out) + return; + #endif // MULTI_SELECTION_PATCH + + drw_setscheme(drw, scheme[item == sel ? SchemeSelHighlight : SchemeNormHighlight]); + + #if FUZZYMATCH_PATCH + if (fuzzy) { + for (i = 0, highlight = itemtext; *highlight && text[i];) { + highlightlen = utf8len(highlight); + #if FUZZYMATCH_PATCH + if (!fstrncmp(&(*highlight), &text[i], highlightlen)) + #else + if (*highlight == text[i]) + #endif // FUZZYMATCH_PATCH + { + /* get indentation */ + restorechar = *highlight; + *highlight = '\0'; + indent = TEXTW(itemtext) - lrpad; + *highlight = restorechar; + + /* highlight character */ + restorechar = highlight[highlightlen]; + highlight[highlightlen] = '\0'; + drw_text( + drw, + x + indent + (lrpad / 2), + y, + MIN(maxw - indent - lrpad, TEXTW(highlight) - lrpad), + bh, 0, highlight, 0 + #if PANGO_PATCH + , True + #endif // PANGO_PATCH + ); + highlight[highlightlen] = restorechar; + i += highlightlen; + } + highlight++; + } + return; + } + #endif // FUZZYMATCH_PATCH + + strcpy(tokens, text); + for (token = strtok(tokens, " "); token; token = strtok(NULL, " ")) { + highlight = fstrstr(itemtext, token); + while (highlight) { + // Move item str end, calc width for highlight indent, & restore + highlightlen = highlight - itemtext; + restorechar = *highlight; + itemtext[highlightlen] = '\0'; + indent = TEXTW(itemtext); + itemtext[highlightlen] = restorechar; + + // Move highlight str end, draw highlight, & restore + restorechar = highlight[strlen(token)]; + highlight[strlen(token)] = '\0'; + if (indent - (lrpad / 2) - 1 < maxw) + drw_text( + drw, + x + indent - (lrpad / 2) - 1, + y, + MIN(maxw - indent, TEXTW(highlight) - lrpad), + bh, 0, highlight, 0 + #if PANGO_PATCH + , True + #endif // PANGO_PATCH + ); + highlight[strlen(token)] = restorechar; + + if (strlen(highlight) - strlen(token) < strlen(token)) + break; + highlight = fstrstr(highlight + strlen(token), token); + } + } +} diff --git a/patch/highpriority.c b/patch/highpriority.c @@ -0,0 +1,35 @@ +static char **hpitems = NULL; +static int hplength = 0; + +static char ** +tokenize(char *source, const char *delim, int *llen) +{ + int listlength = 0, list_size = 0; + char **list = NULL, *token; + + token = strtok(source, delim); + while (token) { + if (listlength + 1 >= list_size) { + if (!(list = realloc(list, (list_size += 8) * sizeof(*list)))) + die("Unable to realloc %zu bytes\n", list_size * sizeof(*list)); + } + if (!(list[listlength] = strdup(token))) + die("Unable to strdup %zu bytes\n", strlen(token) + 1); + token = strtok(NULL, delim); + listlength++; + } + + *llen = listlength; + return list; +} + +static int +arrayhas(char **list, int length, char *item) { + for (int i = 0; i < length; i++) { + int len1 = strlen(list[i]); + int len2 = strlen(item); + if (fstrncmp(list[i], item, len1 > len2 ? len2 : len1) == 0) + return 1; + } + return 0; +} diff --git a/patch/highpriority.h b/patch/highpriority.h @@ -0,0 +1 @@ +static int arrayhas(char **list, int length, char *item); diff --git a/patch/include.c b/patch/include.c @@ -0,0 +1,42 @@ +#if CENTER_PATCH +#include "center.c" +#endif +#if HIGHLIGHT_PATCH +#include "highlight.c" +#endif +#if FUZZYMATCH_PATCH +#include "fuzzymatch.c" +#endif +#if FZFEXPECT_PATCH +#include "fzfexpect.c" +#endif +#if HIGHPRIORITY_PATCH +#include "highpriority.c" +#endif +#if INPUTMETHOD_PATCH +#include "inputmethod.c" +#endif +#if DYNAMIC_OPTIONS_PATCH +#include "dynamicoptions.c" +#endif +#if MULTI_SELECTION_PATCH +#include "multiselect.c" +#endif +#if MOUSE_SUPPORT_PATCH +#include "mousesupport.c" +#endif +#if NAVHISTORY_PATCH +#include "navhistory.c" +#endif +#if VI_MODE_PATCH +#include "vi_mode.c" +#endif +#if NON_BLOCKING_STDIN_PATCH +#include "nonblockingstdin.c" +#endif +#if NUMBERS_PATCH +#include "numbers.c" +#endif +#if XRESOURCES_PATCH +#include "xresources.c" +#endif diff --git a/patch/include.h b/patch/include.h @@ -0,0 +1,30 @@ +#if DYNAMIC_OPTIONS_PATCH +#include "dynamicoptions.h" +#endif +#if FZFEXPECT_PATCH +#include "fzfexpect.h" +#endif +#if INPUTMETHOD_PATCH +#include "inputmethod.h" +#endif +#if MOUSE_SUPPORT_PATCH +#include "mousesupport.h" +#endif +#if MULTI_SELECTION_PATCH +#include "multiselect.h" +#endif +#if NAVHISTORY_PATCH +#include "navhistory.h" +#endif +#if VI_MODE_PATCH +#include "vi_mode.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/patch/inputmethod.c b/patch/inputmethod.c @@ -0,0 +1,196 @@ +static size_t nextrunetext(const char *text, size_t position, int inc) +{ + ssize_t n; + + /* return location of next utf8 rune in the given direction (+1 or -1) */ + for (n = position + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) + ; + return n; +} + +/* return bytes from beginning of text to nth utf8 rune to the right */ +static size_t runebytes(const char *text, size_t n) +{ + size_t ret; + + ret = 0; + while (n-- > 0) + ret += nextrunetext(text + ret, 0, 1); + return ret; +} + +/* return number of characters from beginning of text to nth byte to the right + */ +static size_t runechars(const char *text, size_t n) +{ + size_t ret, i; + + ret = i = 0; + while (i < n) { + i += nextrunetext(text + i, 0, 1); + ret++; + } + return ret; +} + +/* move caret on pre-edit text */ +static void preeditcaret(XIC xic, XPointer clientdata, XPointer calldata) +{ + XIMPreeditCaretCallbackStruct *pcaret; + + (void)xic; + pcaret = (XIMPreeditCaretCallbackStruct *)calldata; + if (!pcaret) + return; + switch (pcaret->direction) { + case XIMForwardChar: + cursor = nextrune(+1); + break; + case XIMBackwardChar: + cursor = nextrune(-1); + break; + case XIMForwardWord: + movewordedge(+1); + break; + case XIMBackwardWord: + movewordedge(-1); + break; + case XIMLineStart: + cursor = 0; + break; + case XIMLineEnd: + if (preview[cursor] != '\0') + cursor = strlen(preview); + break; + case XIMAbsolutePosition: + cursor = runebytes(text, pcaret->position); + break; + case XIMDontChange: + /* do nothing */ + break; + case XIMCaretUp: + case XIMCaretDown: + case XIMNextLine: + case XIMPreviousLine: + /* not implemented */ + break; + } + pcaret->position = runechars(preview, cursor); +} + +/* start input method pre-editing */ +static int preeditstart(XIC xic, XPointer clientdata, XPointer calldata) +{ + (void)xic; + (void)calldata; + (void)clientdata; + composing = 1; + printf("PREEDIT\n"); + return BUFSIZ; +} + +/* end input method pre-editing */ +static void preeditdone(XIC xic, XPointer clientdata, XPointer calldata) +{ + (void)xic; + (void)clientdata; + (void)calldata; + printf("DONE\n"); + + composing = 0; +} + +/* draw input method pre-edit text */ +static void preeditdraw(XIC xic, XPointer clientdata, XPointer calldata) +{ + XIMPreeditDrawCallbackStruct *pdraw; + size_t beg, dellen, inslen, endlen; + + printf("DRAW\n"); + + (void)xic; + pdraw = (XIMPreeditDrawCallbackStruct *)calldata; + if (!pdraw) + return; + + /* we do not support wide characters */ + if (pdraw->text && pdraw->text->encoding_is_wchar == True) { + fputs("warning: wchar is not supportecd; use utf8", stderr); + return; + } + + beg = runebytes(text, pdraw->chg_first); + dellen = runebytes(preview + beg, pdraw->chg_length); + inslen = pdraw->text ? runebytes(pdraw->text->string.multi_byte, pdraw->text->length) : 0; + endlen = 0; + if (beg + dellen < strlen(preview)) + endlen = strlen(preview + beg + dellen); + + /* we cannot change text past the end of our pre-edit string */ + + if (beg + dellen >= BUFSIZ || beg + inslen >= BUFSIZ) + return; + + /* get space for text to be copied, and copy it */ + memmove(preview + beg + inslen, preview + beg + dellen, endlen + 1); + if (pdraw->text && pdraw->text->length) + memcpy(preview + beg, pdraw->text->string.multi_byte, inslen); + (preview + beg + inslen + endlen)[0] = '\0'; + + /* get caret position */ + cursor = runebytes(text, pdraw->caret); +} + +static void init_input_method(XIM xim) +{ + XVaNestedList preedit = NULL; + XICCallback start, done, draw, caret; + XIMStyle preeditstyle; + XIMStyle statusstyle; + XIMStyles *imstyles; + int i; + + /* get styles supported by input method */ + if (XGetIMValues(xim, XNQueryInputStyle, &imstyles, NULL) != NULL) + fputs("XGetIMValues: could not obtain input method values", stderr); + + /* check whether input method support on-the-spot pre-editing */ + preeditstyle = XIMPreeditNothing; + statusstyle = XIMStatusNothing; + for (i = 0; i < imstyles->count_styles; i++) { + if (imstyles->supported_styles[i] & XIMPreeditCallbacks) { + preeditstyle = XIMPreeditCallbacks; + break; + } + } + + /* create callbacks for the input context */ + start.client_data = NULL; + done.client_data = NULL; + draw.client_data = (XPointer)text; + caret.client_data = (XPointer)text; + start.callback = (XICProc)preeditstart; + done.callback = (XICProc)preeditdone; + draw.callback = (XICProc)preeditdraw; + caret.callback = (XICProc)preeditcaret; + + /* create list of values for input context */ + preedit = XVaCreateNestedList(0, XNPreeditStartCallback, &start, XNPreeditDoneCallback, + &done, XNPreeditDrawCallback, &draw, XNPreeditCaretCallback, + &caret, NULL); + if (preedit == NULL) + fputs("XVaCreateNestedList: could not create nested list", stderr); + + xic = XCreateIC(xim, XNInputStyle, preeditstyle | statusstyle, XNPreeditAttributes, preedit, + XNClientWindow, win, XNFocusWindow, win, NULL); + XFree(preedit); + + long eventmask; + /* get events the input method is interested in */ + if (XGetICValues(xic, XNFilterEvents, &eventmask, NULL)) + fputs("XGetICValues: could not obtain input context values", stderr); + + XSelectInput(dpy, win, + ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask | + PointerMotionMask | eventmask); +} diff --git a/patch/inputmethod.h b/patch/inputmethod.h @@ -0,0 +1,11 @@ +static int composing; +static char preview[512] = ""; + +static size_t nextrunetext(const char *text, size_t position, int inc); +static size_t runebytes(const char *text, size_t n); +static size_t runechars(const char *text, size_t n); +static void preeditcaret(XIC xic, XPointer clientdata, XPointer calldata); +static int preeditstart(XIC xic, XPointer clientdata, XPointer calldata); +static void preeditdone(XIC xic, XPointer clientdata, XPointer calldata); +static void preeditdraw(XIC xic, XPointer clientdata, XPointer calldata); +static void init_input_method(XIM xim); diff --git a/patch/mousesupport.c b/patch/mousesupport.c @@ -0,0 +1,249 @@ +void +buttonpress(XEvent *e) +{ + struct item *item; + XButtonPressedEvent *ev = &e->xbutton; + int x = 0, y = 0, h = bh, w; + #if GRID_PATCH + int i, cols; + #endif // GRID_PATCH + + if (ev->window != win) { + /* automatically close dmenu if the user clicks outside of dmenu, but + * ignore the scroll wheel and buttons above that */ + if (ev->button <= Button3) { + exit(1); + } + return; + } + + /* right-click: exit */ + if (ev->button == Button3) + exit(1); + + if (prompt && *prompt) + x += promptw; + + /* input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + + /* left-click on input: clear input, + * NOTE: if there is no left-arrow the space for < is reserved so + * add that to the input width */ + #if SYMBOLS_PATCH + if (ev->button == Button1 && + ((lines <= 0 && ev->x >= 0 && ev->x <= x + w + + ((!prev || !curr->left) ? TEXTW(symbol_1) : 0)) || + (lines > 0 && ev->y >= y && ev->y <= y + h))) { + insert(NULL, -cursor); + drawmenu(); + return; + } + #else + if (ev->button == Button1 && + ((lines <= 0 && ev->x >= 0 && ev->x <= x + w + + ((!prev || !curr->left) ? TEXTW("<") : 0)) || + (lines > 0 && ev->y >= y && ev->y <= y + h))) { + insert(NULL, -cursor); + drawmenu(); + return; + } + #endif // SYMBOLS_PATCH + /* middle-mouse click: paste selection */ + if (ev->button == Button2) { + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + drawmenu(); + return; + } + /* scroll up */ + if (ev->button == Button4 && prev) { + sel = curr = prev; + calcoffsets(); + drawmenu(); + return; + } + /* scroll down */ + if (ev->button == Button5 && next) { + sel = curr = next; + calcoffsets(); + drawmenu(); + return; + } + if (ev->button != Button1) + return; + if (ev->state & ShiftMask) + return; + if (lines > 0) { + #if GRID_PATCH + cols = columns ? columns : 1; + for (i = 0, item = curr; item != next; item = item->right, i++) { + if ( + (ev->y >= y + ((i % lines) + 1) * bh) && // line y start + (ev->y <= y + ((i % lines) + 2) * bh) && // line y end + (ev->x >= x + ((i / lines) * (w / cols))) && // column x start + (ev->x <= x + ((i / lines + 1) * (w / cols))) // column x end + ) { + clickitem(item, ev); + return; + } + } + #else + /* vertical list: (ctrl)left-click on item */ + for (item = curr; item != next; item = item->right) { + y += h; + if (ev->y >= y && ev->y <= (y + h)) { + clickitem(item, ev); + return; + } + } + #endif // GRID_PATCH + } else if (matches) { + /* left-click on left arrow */ + x += inputw; + #if SYMBOLS_PATCH + w = TEXTW(symbol_1); + #else + w = TEXTW("<"); + #endif // SYMBOLS_PATCH + if (prev && curr->left) { + if (ev->x >= x && ev->x <= x + w) { + sel = curr = prev; + calcoffsets(); + drawmenu(); + return; + } + } + /* horizontal list: (ctrl)left-click on item */ + for (item = curr; item != next; item = item->right) { + x += w; + #if SYMBOLS_PATCH + w = MIN(TEXTW(item->text), mw - x - TEXTW(symbol_2)); + #else + w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); + #endif // SYMBOLS_PATCH + if (ev->x >= x && ev->x <= x + w) { + clickitem(item, ev); + return; + } + } + /* left-click on right arrow */ + #if SYMBOLS_PATCH + w = TEXTW(symbol_2); + #else + w = TEXTW(">"); + #endif // SYMBOLS_PATCH + x = mw - w; + if (next && ev->x >= x && ev->x <= x + w) { + sel = curr = next; + calcoffsets(); + drawmenu(); + return; + } + } +} + +static void +clickitem(struct item *item, XButtonEvent *ev) +{ + #if RESTRICT_RETURN_PATCH + if (restrict_return && (ev->state & (ShiftMask | ControlMask))) + return; + #endif // RESTRICT_RETURN_PATCH + + #if !MULTI_SELECTION_PATCH + printitem(item); + #endif // MULTI_SELECTION_PATCH + + sel = item; + if (!(ev->state & ControlMask)) { + #if MULTI_SELECTION_PATCH + printselected(ev->state); + #endif // MULTI_SELECTION_PATCH + cleanup(); + exit(0); + } + + #if MULTI_SELECTION_PATCH + selsel(); + #else + sel->out = 1; + #endif // MULTI_SELECTION_PATCH + drawmenu(); +} + +#if MOTION_SUPPORT_PATCH +void +motionevent(XButtonEvent *ev) +{ + struct item *item; + int x = 0, y = 0, w; + #if GRID_PATCH + int i, cols; + #endif // GRID_PATCH + + if (ev->window != win || matches == 0) + return; + + if (prompt && *prompt) + x += promptw; + + if (lines > 0) { + /* input field */ + w = mw - x; + #if GRID_PATCH + cols = columns ? columns : 1; + /* grid view or vertical list */ + for (i = 0, item = curr; item != next; item = item->right, i++) { + if ( + (ev->y >= y + ((i % lines) + 1) * bh) && // line y start + (ev->y <= y + ((i % lines) + 2) * bh) && // line y end + (ev->x >= x + ((i / lines) * (w / cols))) && // column x start + (ev->x <= x + ((i / lines + 1) * (w / cols))) // column x end + ) { + sel = item; + calcoffsets(); + drawmenu(); + break; + } + } + #else + /* vertical list */ + w = mw - x; + for (item = curr; item != next; item = item->right) { + y += bh; + if (ev->y >= y && ev->y <= (y + bh)) { + sel = item; + calcoffsets(); + drawmenu(); + break; + } + } + #endif // GRID_PATCH + return; + } + + /* left-click on left arrow */ + x += inputw; + #if SYMBOLS_PATCH + w = TEXTW(symbol_1); + #else + w = TEXTW("<"); + #endif // SYMBOLS_PATCH + /* horizontal list */ + for (item = curr; item != next; item = item->right) { + x += w; + #if SYMBOLS_PATCH + w = MIN(TEXTW(item->text), mw - x - TEXTW(symbol_2)); + #else + w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); + #endif // SYMBOLS_PATCH + if (ev->x >= x && ev->x <= x + w) { + sel = item; + calcoffsets(); + drawmenu(); + break; + } + } +} +#endif diff --git a/patch/mousesupport.h b/patch/mousesupport.h @@ -0,0 +1,5 @@ +static void buttonpress(XEvent *e); +static void clickitem(struct item *item, XButtonEvent *ev); +#if MOTION_SUPPORT_PATCH +static void motionevent(XButtonEvent *ev); +#endif // MOTION_SUPPORT_PATCH diff --git a/patch/multiselect.c b/patch/multiselect.c @@ -0,0 +1,41 @@ +static int +issel(size_t id) +{ + for (int i = 0;i < selidsize;i++) + if (selid[i] == id) + return 1; + return 0; +} + +static void +printselected(unsigned int state) +{ + for (int i = 0; i < selidsize; i++) { + if (selid[i] != -1 && (!sel || sel->id != selid[i])) { + printitem(&items[selid[i]]); + } + } + + printcurrent(state); +} + +static void +selsel(void) +{ + if (!sel) + return; + if (issel(sel->id)) { + for (int i = 0; i < selidsize; i++) + if (selid[i] == sel->id) + selid[i] = -1; + } else { + for (int i = 0; i < selidsize; i++) + if (selid[i] == -1) { + selid[i] = sel->id; + return; + } + selidsize++; + selid = realloc(selid, (selidsize + 1) * sizeof(int)); + selid[selidsize - 1] = sel->id; + } +} diff --git a/patch/multiselect.h b/patch/multiselect.h @@ -0,0 +1,2 @@ +static int issel(size_t id); +static void printselected(unsigned int state); diff --git a/patch/navhistory.c b/patch/navhistory.c @@ -0,0 +1,234 @@ +static char *histfile; +static char **history; +static size_t histsz, histpos; +static size_t cap = 0; +static struct item *backup_items = NULL; + +void +cleanhistory(void) +{ + int i; + + for (i = 0; i < histsz; i++) { + free(history[i]); + } + free(history); +} + +void +loadhistory(void) +{ + FILE *fp = NULL; + size_t llen; + char *line; + + if (!histfile) { + return; + } + + fp = fopen(histfile, "r"); + if (!fp) { + return; + } + + for (;;) { + line = NULL; + llen = 0; + if (-1 == getline(&line, &llen, fp)) { + if (ferror(fp)) { + die("failed to read history"); + } + free(line); + break; + } + + addhistory(line); + free(line); + } + histpos = histsz; + + if (fclose(fp)) { + die("failed to close file %s", histfile); + } +} + +void +navhistory(int dir) +{ + static char def[BUFSIZ]; + char *p = NULL; + size_t len = 0; + + if (!history || histpos + 1 == 0) + return; + + if (histsz == histpos) { + strncpy(def, text, sizeof(def)); + } + + switch(dir) { + case 1: + if (histpos < histsz - 1) { + p = history[++histpos]; + } else if (histpos == histsz - 1) { + p = def; + histpos++; + } + break; + case -1: + if (histpos > 0) { + p = history[--histpos]; + } + break; + } + if (p == NULL) { + return; + } + + len = MIN(strlen(p), BUFSIZ - 1); + strncpy(text, p, len); + text[len] = '\0'; + cursor = len; + match(); +} + +void +addhistory(char *input) +{ + unsigned int i; + + if (!histfile || + 0 == maxhist || + 0 == strlen(input)) { + return; + } + + strtok(input, "\n"); + + if (histnodup) { + for (i = 0; i < histsz; i++) { + if (!strcmp(input, history[i])) { + return; + } + } + } + + if (cap == histsz) { + reallochistory(); + } + + history[histsz] = strdup(input); + histsz++; +} + +void +addhistoryitem(struct item *item) +{ + #if SEPARATOR_PATCH + if (separator && item->text_output && item->text != item->text_output) { + int histlen = strlen(item->text) + strlen(item->text_output) + 2; + char *histitem = ecalloc(histlen + 1, sizeof(char *)); + snprintf(histitem, histlen, "%s%c%s", item->text, separator, item->text_output); + addhistory(histitem); + free(histitem); + return; + } + #endif // SEPARATOR_PATCH + + addhistory(item->text); +} + +void +reallochistory(void) +{ + size_t oldcap = cap; + cap += 64; + char **newhistory = realloc(history, cap * sizeof *history); + if (!newhistory) { + die("failed to realloc memory"); + } + + history = newhistory; + memset(history + oldcap, 0, (cap - oldcap) * sizeof *history); +} + +void +togglehistoryitems(void) +{ + int i; + #if SEPARATOR_PATCH || TSV_PATCH + char *p; + #endif // SEPARATOR_PATCH + + if (!histfile) + return; + + if (backup_items) { + restorebackupitems(); + return; + } + + backup_items = items; + items = calloc(histsz + 1, sizeof(struct item)); + if (!items) { + die("cannot allocate memory"); + } + + for (i = 0; i < histsz; i++) { + items[i].text = strdup(history[i]); + #if SEPARATOR_PATCH + if (separator && (p = sepchr(items[i].text, separator)) != NULL) { + *p = '\0'; + items[i].text_output = ++p; + } else { + items[i].text_output = items[i].text; + } + #elif TSV_PATCH + items[i].stext = strdup(items[i].text); + if ((p = strchr(items[i].stext, '\t'))) + *p = '\0'; + #endif // SEPARATOR_PATCH + } +} + +void +restorebackupitems(void) +{ + size_t i; + + if (!backup_items) + return; + + for (i = 0; items && items[i].text; ++i) { + free(items[i].text); + } + free(items); + + items = backup_items; + backup_items = NULL; +} + +void +savehistory(void) +{ + unsigned int i; + FILE *fp; + + if (!histfile || 0 == maxhist) + return; + + fp = fopen(histfile, "w"); + if (!fp) { + die("failed to open %s", histfile); + } + + for (i = histsz < maxhist ? 0 : histsz - maxhist; i < histsz; i++) { + if (0 >= fprintf(fp, "%s\n", history[i])) { + die("failed to write to %s", histfile); + } + } + + if (fclose(fp)) { + die("failed to close file %s", histfile); + } +} diff --git a/patch/navhistory.h b/patch/navhistory.h @@ -0,0 +1,9 @@ +static void addhistory(char *input); +static void addhistoryitem(struct item *item); +static void cleanhistory(void); +static void loadhistory(void); +static void navhistory(int dir); +static void reallochistory(void); +static void restorebackupitems(void); +static void savehistory(void); +static void togglehistoryitems(void); diff --git a/patch/nonblockingstdin.c b/patch/nonblockingstdin.c @@ -0,0 +1,68 @@ +#include <fcntl.h> +#include <unistd.h> +#include <sys/select.h> + +static void +readstdin(void) +{ + static size_t max = 0; + static struct item **end = &items; + + char buf[sizeof text], *p, *maxstr; + struct item *item; + + #if PASSWORD_PATCH + if (passwd) { + inputw = lines = 0; + return; + } + #endif // PASSWORD_PATCH + + /* read each line from stdin and add it to the item list */ + while (fgets(buf, sizeof buf, stdin)) { + if (!(item = malloc(sizeof *item))) + die("cannot malloc %u bytes:", sizeof *item); + if ((p = strchr(buf, '\n'))) + *p = '\0'; + if (!(item->text = strdup(buf))) + die("cannot strdup %u bytes:", strlen(buf)+1); + if (strlen(item->text) > max) { + max = strlen(maxstr = item->text); + #if PANGO_PATCH + inputw = maxstr ? TEXTWM(maxstr) : 0; + #else + inputw = maxstr ? TEXTW(maxstr) : 0; + #endif // PANGO_PATCH + } + *end = item; + end = &item->next; + item->next = NULL; + item->out = 0; + } + match(); + drawmenu(); +} + +static void +run(void) +{ + fd_set fds; + int flags, xfd = XConnectionNumber(dpy); + + if ((flags = fcntl(0, F_GETFL)) == -1) + die("cannot get stdin control flags:"); + if (fcntl(0, F_SETFL, flags | O_NONBLOCK) == -1) + die("cannot set stdin control flags:"); + for (;;) { + FD_ZERO(&fds); + FD_SET(xfd, &fds); + if (!feof(stdin)) + FD_SET(0, &fds); + if (select(xfd + 1, &fds, NULL, NULL, NULL) == -1) + die("cannot multiplex input:"); + if (FD_ISSET(xfd, &fds)) + readevent(); + if (FD_ISSET(0, &fds)) + readstdin(); + } +} diff --git a/patch/nonblockingstdin.h b/patch/nonblockingstdin.h @@ -0,0 +1 @@ +static void readevent(); diff --git a/patch/numbers.c b/patch/numbers.c @@ -0,0 +1,16 @@ +static char numbers[NUMBERSBUFSIZE] = ""; + +static void +recalculatenumbers(void) +{ + unsigned int numer = 0, denom = 0; + struct item *item; + if (matchend) { + numer++; + for (item = matchend; item && item->left; item = item->left) + numer++; + } + for (item = items; item && item->text; item++) + denom++; + snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); +} diff --git a/patch/numbers.h b/patch/numbers.h @@ -0,0 +1,4 @@ +#define NUMBERSMAXDIGITS 100 +#define NUMBERSBUFSIZE (NUMBERSMAXDIGITS * 2) + 1 + +static void recalculatenumbers(void); diff --git a/patch/scroll.c b/patch/scroll.c @@ -0,0 +1,178 @@ +int +utf8nextchar(const char *str, int len, int i, int inc) +{ + int n; + + for (n = i + inc; n + inc >= 0 && n + inc <= len + && (str[n] & 0xc0) == 0x80; n += inc) + ; + return n; +} + +int +drw_text_align(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int textlen, int align) +{ + int ty; + unsigned int ew; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + size_t len; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0; + int utf8err = 0; + int i, n; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts || textlen <= 0 + || (align != AlignL && align != AlignR)) + return 0; + + if (!render) { + w = ~w; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + #if ALPHA_PATCH + d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap); + #else + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + #endif // ALPHA_PATCH + } + + #if BIDI_PATCH + apply_fribidi(text); + text = fribidi_text; + #endif // BIDI_PATCH + + usedfont = drw->fonts; + i = align == AlignL ? 0 : textlen; + x = align == AlignL ? x : x + w; + while (1) { + utf8strlen = 0; + nextfont = NULL; + /* if (align == AlignL) */ + utf8str = text + i; + + while ((align == AlignL && i < textlen) || (align == AlignR && i > 0)) { + if (align == AlignL) { + utf8charlen = utf8decode(text + i, &utf8codepoint, &utf8err); + if (!utf8charlen) { + textlen = i; + break; + } + } else { + n = utf8nextchar(text, textlen, i, -1); + utf8charlen = utf8decode(text + n, &utf8codepoint, &utf8err); + if (!utf8charlen) { + textlen -= i; + text += i; + i = 0; + break; + } + } + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + if (curfont == usedfont) { + utf8strlen += utf8charlen; + i += align == AlignL ? utf8charlen : -utf8charlen; + } else { + nextfont = curfont; + } + break; + } + } + + if (!charexists || nextfont) + break; + else + charexists = 0; + } + + if (align == AlignR) + utf8str = text + i; + + if (utf8strlen) { + drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); + /* shorten text if necessary */ + if (align == AlignL) { + for (len = utf8strlen; len && ew > w; ) { + len = utf8nextchar(utf8str, len, len, -1); + drw_font_getexts(usedfont, utf8str, len, &ew, NULL); + } + } else { + for (len = utf8strlen; len && ew > w; ) { + n = utf8nextchar(utf8str, len, 0, +1); + utf8str += n; + len -= n; + drw_font_getexts(usedfont, utf8str, len, &ew, NULL); + } + } + + if (len) { + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[ColFg], + usedfont->xfont, align == AlignL ? x : x - ew, ty, (XftChar8 *)utf8str, len); + } + x += align == AlignL ? ew : -ew; + w -= ew; + } + if (len < utf8strlen) + break; + } + + if ((align == AlignR && i <= 0) || (align == AlignL && i >= textlen)) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x; +} diff --git a/patch/scroll.h b/patch/scroll.h @@ -0,0 +1,3 @@ +enum { AlignL, AlignR }; + +int drw_text_align(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int textlen, int align); diff --git a/patch/vi_mode.c b/patch/vi_mode.c @@ -0,0 +1,188 @@ +static unsigned int using_vi_mode = 0; + +void +vi_keypress(KeySym ksym, const XKeyEvent *ev) +{ + static const size_t quit_len = LENGTH(quit_keys); + if (ev->state & ControlMask) { + switch(ksym) { + /* movement */ + case XK_d: /* fallthrough */ + if (next) { + sel = curr = next; + calcoffsets(); + goto draw; + } else + ksym = XK_G; + break; + case XK_u: + if (prev) { + sel = curr = prev; + calcoffsets(); + goto draw; + } else + ksym = XK_g; + break; + case XK_p: /* fallthrough */ + case XK_P: break; + case XK_c: + cleanup(); + exit(1); + case XK_Return: /* fallthrough */ + case XK_KP_Enter: break; + default: return; + } + } + + switch(ksym) { + /* movement */ + case XK_0: + cursor = 0; + break; + case XK_dollar: + if (text[cursor + 1] != '\0') { + cursor = strlen(text) - 1; + break; + } + break; + case XK_b: + movewordedge(-1); + break; + case XK_e: + cursor = nextrune(+1); + movewordedge(+1); + if (text[cursor] == '\0') + --cursor; + else + cursor = nextrune(-1); + break; + case XK_g: + if (sel == matches) { + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_G: + 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_h: + if (cursor) + cursor = nextrune(-1); + break; + case XK_j: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_k: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } + break; + case XK_l: + if (text[cursor] != '\0' && text[cursor + 1] != '\0') + cursor = nextrune(+1); + else if (text[cursor] == '\0' && cursor) + --cursor; + break; + case XK_w: + movewordedge(+1); + if (text[cursor] != '\0' && text[cursor + 1] != '\0') + cursor = nextrune(+1); + else if (cursor) + --cursor; + break; + /* insertion */ + case XK_a: + cursor = nextrune(+1); + /* fallthrough */ + case XK_i: + using_vi_mode = 0; + break; + case XK_A: + if (text[cursor] != '\0') + cursor = strlen(text); + using_vi_mode = 0; + break; + case XK_I: + cursor = using_vi_mode = 0; + break; + case XK_p: + if (text[cursor] != '\0') + cursor = nextrune(+1); + XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_P: + XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + /* deletion */ + case XK_D: + text[cursor] = '\0'; + if (cursor) + cursor = nextrune(-1); + match(); + break; + case XK_x: + cursor = nextrune(+1); + insert(NULL, nextrune(-1) - cursor); + if (text[cursor] == '\0' && text[0] != '\0') + --cursor; + match(); + break; + /* misc. */ + case XK_Return: + case XK_KP_Enter: + #if RESTRICT_RETURN_PATCH + if (restrict_return && (!sel || ev->state & (ShiftMask | ControlMask))) + break; + #endif // RESTRICT_RETURN_PATCH + #if !MULTI_SELECTION_PATCH + printcurrent(ev->state); + #endif // MULTI_SELECTION_PATCH + if (!(ev->state & ControlMask)) { + #if MULTI_SELECTION_PATCH + printselected(ev->state); + #endif // MULTI_SELECTION_PATCH + cleanup(); + exit(0); + } + #if !MULTI_SELECTION_PATCH + if (sel) + sel->out = 1; + #endif // MULTI_SELECTION_PATCH + break; + break; + case XK_Tab: + if (!sel) + return; + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + cursor = strlen(text) - 1; + match(); + break; + default: + for (size_t i = 0; i < quit_len; ++i) + if (quit_keys[i].ksym == ksym && + (quit_keys[i].state & ev->state) == quit_keys[i].state) { + cleanup(); + exit(1); + } + } + +draw: + drawmenu(); +} diff --git a/patch/vi_mode.h b/patch/vi_mode.h @@ -0,0 +1,6 @@ +typedef struct { + KeySym ksym; + unsigned int state; +} Key; + +static void vi_keypress(KeySym ksym, const XKeyEvent *ev); diff --git a/patch/xresources.c b/patch/xresources.c @@ -0,0 +1,102 @@ +#include <X11/Xresource.h> + +void +readxresources(void) +{ + XrmInitialize(); + + char* xrm; + if ((xrm = XResourceManagerString(drw->dpy))) { + char *type; + XrmDatabase xdb = XrmGetStringDatabase(xrm); + XrmValue xval; + + if (XrmGetResource(xdb, "dmenu.font", "*", &type, &xval)) + #if PANGO_PATCH + strcpy(font, xval.addr); + #else + fonts[0] = strdup(xval.addr); + #endif // PANGO_PATCH + #if !PANGO_PATCH + else + fonts[0] = strdup(fonts[0]); + #endif // PANGO_PATCH + if (XrmGetResource(xdb, "dmenu.background", "*", &type, &xval)) + colors[SchemeNorm][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.foreground", "*", &type, &xval)) + colors[SchemeNorm][ColFg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.selbackground", "*", &type, &xval)) + colors[SchemeSel][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.selforeground", "*", &type, &xval)) + colors[SchemeSel][ColFg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.outbackground", "*", &type, &xval)) + colors[SchemeOut][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.outforeground", "*", &type, &xval)) + colors[SchemeOut][ColFg] = strdup(xval.addr); + #if MORECOLOR_PATCH + if (XrmGetResource(xdb, "dmenu.midbackground", "*", &type, &xval)) + colors[SchemeMid][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.midforeground", "*", &type, &xval)) + colors[SchemeMid][ColFg] = strdup(xval.addr); + #endif // MORECOLOR_PATCH + #if BORDER_PATCH + if (XrmGetResource(xdb, "dmenu.bordercolor", "*", &type, &xval)) + colors[SchemeBorder][ColBg] = strdup(xval.addr); + #endif // BORDER_PATCH + #if HIGHLIGHT_PATCH + if (XrmGetResource(xdb, "dmenu.selhlbackground", "*", &type, &xval)) + colors[SchemeSelHighlight][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.selhlforeground", "*", &type, &xval)) + colors[SchemeSelHighlight][ColFg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.hlbackground", "*", &type, &xval)) + colors[SchemeNormHighlight][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.hlforeground", "*", &type, &xval)) + colors[SchemeNormHighlight][ColFg] = strdup(xval.addr); + #endif // HIGHLIGHT_PATCH + #if HIGHPRIORITY_PATCH + if (XrmGetResource(xdb, "dmenu.hpbackground", "*", &type, &xval)) + colors[SchemeHp][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.hpforeground", "*", &type, &xval)) + colors[SchemeHp][ColFg] = strdup(xval.addr); + #endif // HIGHPRIORITY_PATCH + #if EMOJI_HIGHLIGHT_PATCH + if (XrmGetResource(xdb, "dmenu.hoverbackground", "*", &type, &xval)) + colors[SchemeHover][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.hoverforeground", "*", &type, &xval)) + colors[SchemeHover][ColFg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.greenbackground", "*", &type, &xval)) + colors[SchemeGreen][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.greenforeground", "*", &type, &xval)) + colors[SchemeGreen][ColFg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.yellowbackground", "*", &type, &xval)) + colors[SchemeYellow][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.yellowforeground", "*", &type, &xval)) + colors[SchemeYellow][ColFg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.bluebackground", "*", &type, &xval)) + colors[SchemeBlue][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.blueforeground", "*", &type, &xval)) + colors[SchemeBlue][ColFg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.purplebackground", "*", &type, &xval)) + colors[SchemePurple][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.purpleforeground", "*", &type, &xval)) + colors[SchemePurple][ColFg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.redbackground", "*", &type, &xval)) + colors[SchemeRed][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.redforeground", "*", &type, &xval)) + colors[SchemeRed][ColFg] = strdup(xval.addr); + #endif // EMOJI_HIGHLIGHT_PATCH + #if VI_MODE_PATCH + if (XrmGetResource(xdb, "dmenu.cursorbackground", "*", &type, &xval)) + colors[SchemeCursor][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.cursorforeground", "*", &type, &xval)) + colors[SchemeCursor][ColFg] = strdup(xval.addr); + #endif // VI_MODE_PATCH + #if CARET_SCHEME_PATCH + if (XrmGetResource(xdb, "dmenu.caretbackground", "*", &type, &xval)) + colors[SchemeCaret][ColBg] = strdup(xval.addr); + if (XrmGetResource(xdb, "dmenu.caretforeground", "*", &type, &xval)) + colors[SchemeCaret][ColFg] = strdup(xval.addr); + #endif // CARET_SCHEME_PATCH + XrmDestroyDatabase(xdb); + } +} diff --git a/patches.def.h b/patches.def.h @@ -0,0 +1,405 @@ +/* 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 proper support for Right-To-Left (RTL) languages, such as Hebrew, + * Arabic, and Farsi. + * + * Texts combining both RTL and LTR languages are displayed correctly. This is + * achieved using the GNU FriBiDi library, which is an additional dependency for + * this patch. + * + * You need to uncomment the corresponding line in config.mk to use the fribidi library. + * https://tools.suckless.org/dmenu/patches/bidi/ + */ +#define BIDI_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 + +/* The caret scheme patch, a.k.a. colored caret patch, adds the option to change the color + * of the caret via the SchemeCaret color scheme. + * + * https://tools.suckless.org/dmenu/patches/colored-caret/ + */ +#define CARET_SCHEME_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 0 + +/* This patch dyamically changes the size of the dmenu window based on how many items are + * drawn in a vertical view. For this to work set a higher maximum of lines, e.g. -l 500. + * https://gist.github.com/mircodz/1d9b88db958089bb08adbf45eb53b66f + */ +#define DYNAMIC_HEIGHT_PATCH 0 + +/* 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 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 0 + +/* 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. + * If combined with the fuzzymatch patch then fuzzy highlight will be used for highlighting + * depending on whether fuzzy matching is enabled. + * + * Known issue: highlighting does not work properly when pango markup is used + * + * https://tools.suckless.org/dmenu/patches/highlight/ + * https://tools.suckless.org/dmenu/patches/fuzzyhighlight/ + */ +#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 + +/* Adds support for input methods (fctix, ibus, etc.) allowing the user to change the + * keyboard layout while dmenu is open. + * https://github.com/bakkeby/dmenu-flexipatch/pull/22 + */ +#define INPUTMETHOD_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 + +/* Expands the above to support mouse hovering. + * https://tools.suckless.org/dmenu/patches/mouse-support/ + */ +#define MOTION_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. + * + * Known issue: not compatible with the scroll patch + * + * Also see: + * https://developer.gnome.org/pygtk/stable/pango-markup-language.html + * https://github.com/StillANixRookie/dmenu-pango + */ +#define PANGO_PATCH 0 + +/* 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 0 + +/* 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 flag (-q) which makes dmenu not show any items if the search string is + * empty. + * https://github.com/baskerville/dmenu_qxyw/blob/master/dmenu_qxyw-hg.diff + */ +#define QUIET_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. + * + * Known issue: not compatible with the pango patch + * + * 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 0 + +/* 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 + +/* This patch adds basic vi mode capabilities to dmenu. + * - movements inside typed text with [h|l|w|b|e|0|$] + * - movements through list with [j|k|g|G|C-d|C-u] + * - standard insertions with [a|A|i|I] + * - paste after|before cursor with [p|P], use ctrl to use clipboard + * - delete from cursor to eol with D + * - delete the character under cursor with x + * - Enter and Tab will work like normal + * + * https://tools.suckless.org/dmenu/patches/vi-mode/ + */ +#define VI_MODE_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/patches.h b/patches.h @@ -0,0 +1,182 @@ +/* + * This file contains patch control flags. + * + * Kris Yotam's dmenu build - patches enabled: + * - alpha (transparency) + * - border (visible border when centered) + * - caseinsensitive (case-insensitive by default) + * - center (centered on screen) + * - ctrl_v_to_paste (Ctrl+V paste support) + * - fuzzymatch (fuzzy matching) + * - highlight (highlight matched chars) + * - line_height (configurable line height) + * - mouse_support (click to select) + * - numbers (show match count) + * - password (hide input with -P) + * - rejectnomatch (reject non-matching input) + * - xresources (pywal compatible) + */ + +/* Patches */ + +/* The alpha patch adds transparency for the dmenu window. */ +#define ALPHA_PATCH 1 + +/* Adds padding for dmenu similar to dwm barpadding. */ +#define BARPADDING_PATCH 0 + +/* Right-To-Left language support (requires fribidi). */ +#define BIDI_PATCH 0 + +/* Adds a border around the dmenu window. */ +#define BORDER_PATCH 1 + +/* Colored caret option via SchemeCaret. */ +#define CARET_SCHEME_PATCH 0 + +/* Configurable caret width. */ +#define CARET_WIDTH_PATCH 0 + +/* Case-insensitive by default. */ +#define CASEINSENSITIVE_PATCH 1 + +/* Centers dmenu in the middle of the screen. */ +#define CENTER_PATCH 1 + +/* Ctrl+v to paste support. */ +#define CTRL_V_TO_PASTE_PATCH 1 + +/* Dynamically changes dmenu window size based on items. */ +#define DYNAMIC_HEIGHT_PATCH 0 + +/* Dynamic options based on input. */ +#define DYNAMIC_OPTIONS_PATCH 0 + +/* Emoji highlight with colored background. */ +#define EMOJI_HIGHLIGHT_PATCH 0 + +/* Fuzzy matching support. */ +#define FUZZYMATCH_PATCH 1 + +/* Fzf-like functionality. */ +#define FZFEXPECT_PATCH 0 + +/* Grid layout for entries. */ +#define GRID_PATCH 0 + +/* Grid navigation with arrows. */ +#define GRIDNAV_PATCH 0 + +/* Highlight matched characters. */ +#define HIGHLIGHT_PATCH 1 + +/* Sort results by priority. */ +#define HIGHPRIORITY_PATCH 0 + +/* Print current text on each keypress. */ +#define INCREMENTAL_PATCH 0 + +/* Preselected text option. */ +#define INITIALTEXT_PATCH 0 + +/* Input method support (fcitx, ibus). */ +#define INPUTMETHOD_PATCH 0 + +/* Auto-select if only one match. */ +#define INSTANT_PATCH 0 + +/* Minimum line height option. */ +#define LINE_HEIGHT_PATCH 1 + +/* Let window manager manage dmenu window. */ +#define MANAGED_PATCH 0 + +/* Additional color scheme for adjacent items. */ +#define MORECOLOR_PATCH 0 + +/* Mouse support for clicking items. */ +#define MOUSE_SUPPORT_PATCH 1 + +/* Mouse hover support. */ +#define MOTION_SUPPORT_PATCH 0 + +/* Multi-selection with Ctrl+Enter. */ +#define MULTI_SELECTION_PATCH 0 + +/* Bash-like history navigation. */ +#define NAVHISTORY_PATCH 0 + +/* Workaround for BadLength with color glyphs. */ +#define NO_COLOR_EMOJI_PATCH 0 + +/* Disable sorting after matching. */ +#define NO_SORT_PATCH 0 + +/* Non-blocking stdin reading. */ +#define NON_BLOCKING_STDIN_PATCH 0 + +/* Show matched/total count. */ +#define NUMBERS_PATCH 1 + +/* Pango markup support. */ +#define PANGO_PATCH 0 + +/* Password mode - hide input with dots. */ +#define PASSWORD_PATCH 1 + +/* Pipe selected text back out. */ +#define PIPEOUT_PATCH 0 + +/* Plain prompt without colors. */ +#define PLAIN_PROMPT_PATCH 0 + +/* Tab completion support. */ +#define PREFIXCOMPLETION_PATCH 0 + +/* Preselect item by index. */ +#define PRESELECT_PATCH 0 + +/* Print index instead of text. */ +#define PRINTINDEX_PATCH 0 + +/* Return prints input text instead of selection. */ +#define PRINTINPUTTEXT_PATCH 0 + +/* Don't show items if search is empty. */ +#define QUIET_PATCH 0 + +/* Reject input if no match. */ +#define REJECTNOMATCH_PATCH 1 + +/* Input width relative to options. */ +#define RELATIVE_INPUT_WIDTH_PATCH 0 + +/* Disable Shift/Ctrl+Return. */ +#define RESTRICT_RETURN_PATCH 0 + +/* Text scrolling for long input. */ +#define SCROLL_PATCH 0 + +/* Separator for display vs output. */ +#define SEPARATOR_PATCH 0 + +/* Custom symbols for overflow. */ +#define SYMBOLS_PATCH 0 + +/* TSV input support. */ +#define TSV_PATCH 0 + +/* Vertical full width items. */ +#define VERTFULL_PATCH 0 + +/* Vi mode keybindings. */ +#define VI_MODE_PATCH 0 + +/* Extended window manager hints. */ +#define WMTYPE_PATCH 0 + +/* Xresources support for colors/settings. */ +#define XRESOURCES_PATCH 1 + +/* Position and width options. */ +#define XYW_PATCH 0 diff --git a/util.c b/util.c @@ -1,5 +1,4 @@ /* See LICENSE file for copyright and license details. */ -#include <errno.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -11,17 +10,17 @@ void die(const char *fmt, ...) { va_list ap; - int saved_errno; - - saved_errno = errno; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); - if (fmt[0] && fmt[strlen(fmt)-1] == ':') - fprintf(stderr, " %s", strerror(saved_errno)); - fputc('\n', stderr); + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } exit(1); } diff --git a/util.h b/util.h @@ -1,7 +1,11 @@ /* 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])