diff --git a/MANUAL.md b/MANUAL.md index 07b9ad9..7fa5a71 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -27,9 +27,9 @@ This manual covers flashing, operating, and customizing **Bananarchy Linux** (`v - **Cross-compiled Xlibre (`X11Libre/xserver` 25.2.0)**: compiled specifically for ARMv7 Cortex-A7 (`-O2 -march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard -fno-plt -fomit-frame-pointer`) with all 3D/GLX/Glamor/VGA/DGA/XVideo bloat stripped out, rootless `elogind` device management, and `modesetting` KMS + `ShadowFB` (`drmModeDirtyFB` partial rectangle SPI updates) with automatic software cursor fallback on `/dev/dri/card0` (`panel-mipi-dbi`). - **Non-Nerd `Fira Code` with OpenType ligatures**: `Fira Code` (`font-fira-code`) is configured as the primary system and terminal font across Fontconfig, Xft, `st`, `dmenu`, `dwm`, `conky`, and `dunst`, with `DejaVu SansM Nerd Font` used strictly as a fallback for Private Use Area icon glyphs. - **Patched `sxmo-st` (`st`) with HarfBuzz ligatures & XIM**: patched with `libharfbuzz` (`hb.c`/`hb.h`) shaping (`liga`, `calt`, `clig`, `dlig`) so multi-character programming operators (`->`, `=>`, `!=`, `<=`, `>=`, `===`, `::`, `//`, `/*`) render as continuous ligatures at `pixelsize=18`, alongside full `XIM` (`fcitx5-t9`) input support. - - **Patched `sxmo-dmenu` (`dmenu`) (`< 15 ms` startup)**: full-screen `240x296` 10-line vertical menu (positioned flush at `Y = 24..320` right below the 24px `dwm` bar, with a `26px` search bar and ten `27px` item rows) with a `#383838` top search bar, `#005577` prompt badge, `#1c1c1c` item list, `XIM` (`XSetLocaleModifiers("")`, `XSetICFocus`, `Xutf8LookupString`) for live `fcitx5-t9` search filtering and `#` mode switching, C-level focus preservation (`prevfocuswin`), and direct Nokia keypad bindings. + - **Patched `sxmo-dmenu` plugin (`dmenu.so` / `dmenu` / `xdmenu`) (`< 2 ms` popup/close)**: preloaded resident DWM plugin rendering a full-screen `240x296` 10-line vertical menu (positioned flush at `Y = 24..320` right below the 24px `dwm` bar, with a `26px` search bar and ten `27px` item rows) with a `#383838` top search bar, `#005577` prompt badge, `#1c1c1c` item list, `XIM` (`XSetLocaleModifiers("")`, `XSetICFocus`, `Xutf8LookupString`) for live `fcitx5-t9` search filtering and `#` mode switching, C-level focus preservation (`prevfocuswin`), built-in `< 0.5 ms` in-place Sxmo menu transitions (`Sys`, `Apps`, `Scripts`, `Modem`, `Config`, `Power`, `St`, `Nano`), and a `/tmp/dwm_plugin_dmenu.sock` IPC server for all `/usr/bin/dmenu` and `sxmo_dmenu.sh` calls. - **Patched `sxmo-dwm` (`dwm`) (`< 2 ms` display wake/unlock & native plugin system)**: configured for `240x320` (`bh = 24`, `#323232` top bar, `#005577` active tag/border, vertical `bstack` layout default, overflow-protected Pango status bar rendering keeping the right-aligned clock always visible, and a transparent-backgrounded bottom desktop softkey bar displaying `"Menu"` and `"Names"` over the wallpaper). Directly polls `/dev/input/by-path/platform-gpio-keys-event` (`SW_LID`) and intercepts locked-state wake keys (`XF86PowerOff`, `XF86PickupPhone`, `XF86KbdLcdMenu1`, `XF86KbdLcdMenu2`) in C to toggle `/sys/class/backlight/backlight/brightness` and `/sys/class/leds/white:kbd_backlight/brightness` in `< 2 ms` before running Sxmo state hooks asynchronously. - - **Preloaded DWM plugins (`src/dwm-plugins/` & `src/xdrawer/`, `< 10 ms` FIFO wakeup)**: `dwm` dynamically loads `.so` plugins from `/usr/lib/dwm/plugins/` and `~/.local/lib/dwm/plugins/` (`xdrawer.so`, `dialer.so`, `calllog.so`, `contacts.so`) and preloads them as resident background daemons (`dwm-plugin --preload`) at startup. Each daemon keeps its X11 window, offscreen `Pixmap`, Xft/Pango layouts (`15px` `Fira Code, DejaVuSansM Nerd Font`), and `stat()`-cached Sxmo TSV state initialized in memory while sleeping at 0% CPU on `/tmp/dwm_plugin_.fifo`. + - **Preloaded DWM plugins (`src/sxmo-dmenu/`, `src/dwm-plugins/` & `src/xdrawer/`, `< 2 ms` FIFO/socket wakeup)**: `dwm` dynamically loads `.so` plugins from `/usr/lib/dwm/plugins/` and `~/.local/lib/dwm/plugins/` (`dmenu.so`, `xdrawer.so`, `dialer.so`, `calllog.so`, `contacts.so`, `sms.so`, `files.so`, `camera.so`) and preloads them as resident background daemons (`dwm-plugin --preload`) at startup. Each daemon keeps its X11 window, offscreen `Pixmap`, Xft/Pango layouts, and `stat()`-cached Sxmo TSV state initialized in memory while sleeping at 0% CPU on `/tmp/dwm_plugin_.fifo`. 2. **Multi-mode `fcitx5-t9` keypad engine (`src/fcitx5-t9/`)**: - Supports 6 input modes cycled with `#` (`XF86NumericPound`): `abc`, `Abc`, `ABC`, `Sym`, `Ctl`, and `123`, updating the live top-bar status indicator (`[abc]`, `[Abc]`, `[ABC]`, `[Sym]`, `[Ctl]`, `[123]`) in real time. - Locks the candidate popup bar to the bottom of the screen (`X = 1, Y = 290, W = 238, H = 30`) with fixed-width 28-character monospace formatting (`Fira Code 9.5`), so neither the popup window nor individual letters ever jump when cycling through candidates. diff --git a/README.md b/README.md index 52807b8..4eb07a4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ Bananarchy targets an ultra-lightweight **Sxmo X11 (`sxmo-de-dwm`)** environment - **Cross-compiled Xlibre (`X11Libre/xserver` 25.2.0) display server**: `modesetting` KMS + `ShadowFB` Damage partial-rectangle SPI transfers, software cursor fallback, and rootless `elogind` integration. - **Patched `sxmo-dwm` with native C/C++ plugin architecture**: `240x320` vertical `bstack` layout, 24px Pango status bar with an overflow-protected right-aligned clock, transparent-backgrounded `"Menu"` and `"Names"` desktop softkey labels, direct C `SW_LID` slider and locked-key wake handling in `< 2 ms`, and a dynamic `.so` plugin API (`dwm_plugin.h`) with preloaded resident daemons (`--preload`) and non-blocking FIFO IPC (`< 10 ms` wakeup latency). -- **Preloaded `240x320` keypad-driven DWM plugins (`xdrawer`, `dialer`, `calllog`, `contacts`, `sms`, `files`, `camera`)**: +- **Preloaded `240x320` keypad-driven DWM plugins (`dmenu`, `xdrawer`, `dialer`, `calllog`, `contacts`, `sms`, `files`, `camera`)**: + - **`DMenu` (`dmenu.so` / `dmenu` / `xdmenu`)**: zero-lag (`< 2 ms` popup/close) resident `240x320` full-screen 10-line vertical menu (`240x296` flush below the 24px top bar) with a `#383838` search bar, `#005577` prompt badge, `XIM` `fcitx5-t9` text input, in-place `< 0.5 ms` native Sxmo submenu transitions (`Sys`, `Apps`, `Scripts`, `Modem`, `Config`, `Power`, `St`, `Nano`), and a `/tmp/dwm_plugin_dmenu.sock` client interface serving all `/usr/bin/dmenu` and `sxmo_dmenu.sh` pipes instantaneously. - **`XDrawer` (`xdrawer.so` / `xdrawer`)**: global phone controls overlay invoked from anywhere by pressing **both softkeys simultaneously**, providing instant sliders and toggles for speaker volume, earpiece volume, flashlight, sound mute, microphone mute, screen brightness, and keypad backlight. - **`Dialer` (`dialer.so` / `xdialer`)**: opens automatically from the idle desktop when pressing any **`0`–`9`, `*`, or `#` key** (inserting that character immediately), with live T9/number contact filtering, hold-`0` for `+`, and single-press **Call** dialing. - **`Call Log` (`calllog.so` / `xcalllog`)**: opens automatically from the idle desktop when pressing the **Call (green)** button, with `All` / `Missed` / `In` / `Out` tabs, contact resolution, call durations, and instant redial. @@ -18,7 +19,6 @@ Bananarchy targets an ultra-lightweight **Sxmo X11 (`sxmo-de-dwm`)** environment - **`SMS Manager` (`sms.so` / `xsms`)**: conversation thread manager, bubble message history reader, and multi-tap keypad SMS composer/draft editor integrated with `$SXMO_LOGDIR//sms.txt`, `draft.txt`, `modemlog.tsv`, and `sxmo_modemsendsms.sh`. - **`File Manager` (`files.so` / `xfiles`)**: graphical directory browser with live T9/number filtering, dotfiles toggle (`*`), sort mode cycle (`#`), file operations (new folder/file, rename, copy, cut/move, paste, delete, properties, edit in `nano`, open terminal here), built-in text viewer, and built-in `libjpeg`/`libpng` image previewer. - **`Camera` (`camera.so` / `xcamera`)**: live V4L2 `/dev/video0` (`800x600` `RGB24` -> `240x180` 4:3) viewfinder with auto-flash (`*`/`1`), continuous torch (`0`/`2`), digital zoom (`1x`/`2x`/`4x` via `Up`/`Down`), 3-second self-timer (`#`/`3`), direct `libjpeg` photo capture to `~/Pictures/photo-YYYYMMDD-HHMMSS.jpg`, and built-in photo gallery. -- **Patched `sxmo-dmenu`**: `240x320` full-screen 10-line vertical menu (`240x296` flush below the 24px top bar) with a `#383838` search bar, `#005577` prompt badge, and `XIM` `fcitx5-t9` text input in `< 15 ms`. - **Patched `sxmo-st` terminal (`pixelsize=18`)**: **HarfBuzz (`libharfbuzz`) OpenType ligature shaping** (`liga`, `calt`, `clig`, `dlig`) and `XIM` support. - **Typography**: non-Nerd **`Fira Code` (`font-fira-code`) with ligatures** as the primary system and terminal font (with `DejaVu SansM Nerd Font` used strictly as an icon fallback). - **Custom 6-mode `fcitx5-t9` input engine**: fixed bottom candidate bar (`X=1, Y=290, W=238, H=30`) with a constant 28-character monospace preedit layout supporting lowercase, one-shot Shift, Caps Lock, all 32 ASCII punctuation symbols, terminal control codes (`^A`-`^Z`, `Esc`, `Tab`, `F1`-`F12`), and direct numeric entry with live top-bar mode indication (`[abc]`, `[Abc]`, `[ABC]`, `[Sym]`, `[Ctl]`, `[123]`). diff --git a/overlay/rootfs/usr/bin/dmenu b/overlay/rootfs/usr/bin/dmenu deleted file mode 100755 index dbc9eb8..0000000 Binary files a/overlay/rootfs/usr/bin/dmenu and /dev/null differ diff --git a/overlay/rootfs/usr/bin/dmenu b/overlay/rootfs/usr/bin/dmenu new file mode 120000 index 0000000..e492465 --- /dev/null +++ b/overlay/rootfs/usr/bin/dmenu @@ -0,0 +1 @@ +dwm-plugin \ No newline at end of file diff --git a/overlay/rootfs/usr/bin/dwm b/overlay/rootfs/usr/bin/dwm index dc13feb..b7b41f6 100755 Binary files a/overlay/rootfs/usr/bin/dwm and b/overlay/rootfs/usr/bin/dwm differ diff --git a/overlay/rootfs/usr/bin/dwm-plugin b/overlay/rootfs/usr/bin/dwm-plugin index a315043..f7ce7fb 100755 Binary files a/overlay/rootfs/usr/bin/dwm-plugin and b/overlay/rootfs/usr/bin/dwm-plugin differ diff --git a/overlay/rootfs/usr/bin/sxmo_appmenu.sh b/overlay/rootfs/usr/bin/sxmo_appmenu.sh index 3948fa6..5b4edcc 100755 --- a/overlay/rootfs/usr/bin/sxmo_appmenu.sh +++ b/overlay/rootfs/usr/bin/sxmo_appmenu.sh @@ -2,6 +2,16 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright 2022 Sxmo Contributors +if [ "${1:-}" = "--fallback" ]; then + shift +elif [ -p /tmp/dwm_plugin_dmenu.fifo ]; then + case "${1:-}" in + ""|sys|applications|scripts|modem|config|power|sthotkeys) + exec dmenu "${1:-ctx}" + ;; + esac +fi + # include common definitions # shellcheck source=configs/default_hooks/sxmo_hook_icons.sh . sxmo_hook_icons.sh diff --git a/overlay/rootfs/usr/bin/sxmo_dmenu.sh b/overlay/rootfs/usr/bin/sxmo_dmenu.sh index cd6062e..939c84d 100755 --- a/overlay/rootfs/usr/bin/sxmo_dmenu.sh +++ b/overlay/rootfs/usr/bin/sxmo_dmenu.sh @@ -2,8 +2,6 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright 2022 Sxmo Contributors -. sxmo_common.sh - unalias dmenu 2>/dev/null || true case "$1" in @@ -12,10 +10,11 @@ case "$1" in shift ;; "isopen") - exec pidof -s dmenu >/dev/null + [ -e /tmp/dwm_plugin_dmenu.active ] + exit $? ;; "close") - pkill dmenu 2>/dev/null || true + dmenu close 2>/dev/null || true exit 0 ;; esac diff --git a/overlay/rootfs/usr/bin/xdmenu b/overlay/rootfs/usr/bin/xdmenu new file mode 120000 index 0000000..e492465 --- /dev/null +++ b/overlay/rootfs/usr/bin/xdmenu @@ -0,0 +1 @@ +dwm-plugin \ No newline at end of file diff --git a/overlay/rootfs/usr/lib/dwm/plugins/calllog.so b/overlay/rootfs/usr/lib/dwm/plugins/calllog.so index 898be8b..295e9a5 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/calllog.so and b/overlay/rootfs/usr/lib/dwm/plugins/calllog.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/camera.so b/overlay/rootfs/usr/lib/dwm/plugins/camera.so index b9776cf..ebd5a78 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/camera.so and b/overlay/rootfs/usr/lib/dwm/plugins/camera.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/contacts.so b/overlay/rootfs/usr/lib/dwm/plugins/contacts.so index 9bb360d..9665b26 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/contacts.so and b/overlay/rootfs/usr/lib/dwm/plugins/contacts.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/dialer.so b/overlay/rootfs/usr/lib/dwm/plugins/dialer.so index 33d921f..6470b9e 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/dialer.so and b/overlay/rootfs/usr/lib/dwm/plugins/dialer.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so b/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so new file mode 100755 index 0000000..c56caf1 Binary files /dev/null and b/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/files.so b/overlay/rootfs/usr/lib/dwm/plugins/files.so index 28d8412..44c3dcc 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/files.so and b/overlay/rootfs/usr/lib/dwm/plugins/files.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/sms.so b/overlay/rootfs/usr/lib/dwm/plugins/sms.so index 0b1bf27..912b42a 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/sms.so and b/overlay/rootfs/usr/lib/dwm/plugins/sms.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so b/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so index a81842c..534fe99 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so and b/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so differ diff --git a/prebuilt/sxmo-x11/usr/bin/dmenu b/prebuilt/sxmo-x11/usr/bin/dmenu deleted file mode 100755 index dbc9eb8..0000000 Binary files a/prebuilt/sxmo-x11/usr/bin/dmenu and /dev/null differ diff --git a/prebuilt/sxmo-x11/usr/bin/dmenu b/prebuilt/sxmo-x11/usr/bin/dmenu new file mode 120000 index 0000000..e492465 --- /dev/null +++ b/prebuilt/sxmo-x11/usr/bin/dmenu @@ -0,0 +1 @@ +dwm-plugin \ No newline at end of file diff --git a/prebuilt/sxmo-x11/usr/bin/dwm b/prebuilt/sxmo-x11/usr/bin/dwm index dc13feb..b7b41f6 100755 Binary files a/prebuilt/sxmo-x11/usr/bin/dwm and b/prebuilt/sxmo-x11/usr/bin/dwm differ diff --git a/prebuilt/sxmo-x11/usr/bin/dwm-plugin b/prebuilt/sxmo-x11/usr/bin/dwm-plugin index a315043..f7ce7fb 100755 Binary files a/prebuilt/sxmo-x11/usr/bin/dwm-plugin and b/prebuilt/sxmo-x11/usr/bin/dwm-plugin differ diff --git a/prebuilt/sxmo-x11/usr/bin/xdmenu b/prebuilt/sxmo-x11/usr/bin/xdmenu new file mode 120000 index 0000000..e492465 --- /dev/null +++ b/prebuilt/sxmo-x11/usr/bin/xdmenu @@ -0,0 +1 @@ +dwm-plugin \ No newline at end of file diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so index 898be8b..295e9a5 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so index b9776cf..ebd5a78 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so index 9bb360d..9665b26 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so index 33d921f..6470b9e 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so new file mode 100755 index 0000000..c56caf1 Binary files /dev/null and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so index 28d8412..44c3dcc 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so index 0b1bf27..912b42a 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so index a81842c..534fe99 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so differ diff --git a/scripts/build-sxmo-x11.sh b/scripts/build-sxmo-x11.sh index 42c6349..cec3fca 100755 --- a/scripts/build-sxmo-x11.sh +++ b/scripts/build-sxmo-x11.sh @@ -77,18 +77,19 @@ EOF st.c x.c hb.c $ST_LIBS -o /bananarchy/prebuilt/sxmo-x11/usr/bin/st $STRIP --strip-unneeded /bananarchy/prebuilt/sxmo-x11/usr/bin/st - echo "[4/5] Compiling patched sxmo-dmenu (with XIM + 240x320 vertical layout + Fira Code)..." + echo "[4/6] Compiling patched sxmo-dmenu plugin (dmenu.so with XIM + 240x320 vertical layout + Fira Code)..." + mkdir -p /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins cd /bananarchy/src/sxmo-dmenu cp -f config.def.h config.h DMENU_INCS="$(pkgconf --cflags x11 xinerama xft fontconfig freetype2)" DMENU_LIBS="$(pkgconf --libs x11 xinerama xft fontconfig freetype2)" - $CC $OPT_CFLAGS -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"5.0.14-bananarchy\" -DXINERAMA $DMENU_INCS \ - dmenu.c drw.c util.c $DMENU_LIBS -o /bananarchy/prebuilt/sxmo-x11/usr/bin/dmenu + $CC $OPT_CFLAGS -fPIC -shared -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"5.0.14-bananarchy\" -DXINERAMA $DMENU_INCS \ + dmenu.c drw.c util.c $DMENU_LIBS -o /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so $CC $OPT_CFLAGS -D_DEFAULT_SOURCE -DVERSION=\"5.0.14-bananarchy\" \ stest.c -o /bananarchy/prebuilt/sxmo-x11/usr/bin/stest cp -f dmenu_path dmenu_run /bananarchy/prebuilt/sxmo-x11/usr/bin/ chmod 755 /bananarchy/prebuilt/sxmo-x11/usr/bin/dmenu_path /bananarchy/prebuilt/sxmo-x11/usr/bin/dmenu_run - $STRIP --strip-unneeded /bananarchy/prebuilt/sxmo-x11/usr/bin/dmenu /bananarchy/prebuilt/sxmo-x11/usr/bin/stest + $STRIP --strip-unneeded /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so /bananarchy/prebuilt/sxmo-x11/usr/bin/stest echo "[5/6] Compiling patched sxmo-dwm (240x320 + Fira Code + Pango statusbar + Plugin API)..." cd /bananarchy/src/sxmo-dwm @@ -147,6 +148,8 @@ EOF -o /bananarchy/prebuilt/sxmo-x11/usr/bin/dwm-plugin $STRIP --strip-unneeded /bananarchy/prebuilt/sxmo-x11/usr/bin/dwm-plugin + ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/dmenu + ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xdmenu ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xdrawer ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/XDrawer ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xdialer diff --git a/src/dwm-plugins/dwm_plugin_runner.c b/src/dwm-plugins/dwm_plugin_runner.c index a055ff9..2e00058 100644 --- a/src/dwm-plugins/dwm_plugin_runner.c +++ b/src/dwm-plugins/dwm_plugin_runner.c @@ -1,15 +1,131 @@ #include #include +#include #include #include +#include #include #include #include #include +#include +#include #include #include "../sxmo-dwm/dwm_plugin.h" +static int write_all(int fd, const void *buf, size_t len) { + const char *p = (const char *)buf; + while (len > 0) { + ssize_t n = write(fd, p, len); + if (n < 0) { + if (errno == EINTR) + continue; + return -1; + } + p += n; + len -= (size_t)n; + } + return 0; +} + +static int is_dmenu_builtin_arg(const char *arg) { + if (!arg || !arg[0]) + return 0; + return strcmp(arg, "sys") == 0 || + strcmp(arg, "ctx") == 0 || + strcmp(arg, "applications") == 0 || + strcmp(arg, "scripts") == 0 || + strcmp(arg, "modem") == 0 || + strcmp(arg, "config") == 0 || + strcmp(arg, "power") == 0 || + strcmp(arg, "st") == 0 || + strcmp(arg, "nano") == 0 || + strcmp(arg, "sthotkeys") == 0; +} + +static int try_dmenu_socket_client(int sub_argc, char **sub_argv, int *exit_code_out) { + FILE *pf = fopen("/tmp/dwm_plugin_dmenu.pid", "r"); + if (!pf) + return 0; + int pid = 0; + int ok = (fscanf(pf, "%d", &pid) == 1 && pid > 1 && kill((pid_t)pid, 0) == 0); + fclose(pf); + if (!ok) + return 0; + + int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sock < 0) + return 0; + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, "/tmp/dwm_plugin_dmenu.sock", sizeof(addr.sun_path) - 1); + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + close(sock); + return 0; + } + + uint32_t c_argc = (uint32_t)sub_argc; + if (write_all(sock, &c_argc, sizeof(c_argc)) != 0) { + close(sock); + return 0; + } + for (int i = 0; i < sub_argc; i++) { + const char *s = sub_argv[i] ? sub_argv[i] : ""; + uint32_t slen = (uint32_t)strlen(s); + if (write_all(sock, &slen, sizeof(slen)) != 0 || + (slen > 0 && write_all(sock, s, slen) != 0)) { + close(sock); + return 0; + } + } + + char buf[4096]; + for (;;) { + ssize_t n = read(STDIN_FILENO, buf, sizeof(buf)); + if (n < 0) { + if (errno == EINTR) + continue; + break; + } + if (n == 0) + break; + if (write_all(sock, buf, (size_t)n) != 0) { + close(sock); + return 0; + } + } + + shutdown(sock, SHUT_WR); + + uint8_t status_byte = 1; + ssize_t first = read(sock, &status_byte, 1); + if (first <= 0) { + close(sock); + *exit_code_out = 1; + return 1; + } + + for (;;) { + ssize_t n = read(sock, buf, sizeof(buf)); + if (n < 0) { + if (errno == EINTR) + continue; + break; + } + if (n == 0) + break; + (void)write_all(STDOUT_FILENO, buf, (size_t)n); + } + + close(sock); + *exit_code_out = (int)status_byte; + return 1; +} + static int try_send_plugin_fifo(const char *plugin_name, int sub_argc, char **sub_argv) { if (sub_argc >= 2 && sub_argv[1] && strcmp(sub_argv[1], "--preload") == 0) { return 0; @@ -38,6 +154,9 @@ static int try_send_plugin_fifo(const char *plugin_name, int sub_argc, char **su int len = 0; if (strcmp(plugin_name, "xdrawer") == 0) { len = snprintf(cmd, sizeof(cmd), "TOGGLE\n"); + } else if (strcmp(plugin_name, "dmenu") == 0 && sub_argc >= 2 && sub_argv[1] && + strcmp(sub_argv[1], "close") == 0) { + len = snprintf(cmd, sizeof(cmd), "CLOSE\n"); } else if (sub_argc >= 2 && sub_argv[1] && strcmp(sub_argv[1], "newcontact") == 0) { const char *num = (sub_argc >= 3 && sub_argv[2]) ? sub_argv[2] : ""; len = snprintf(cmd, sizeof(cmd), "OPEN new:%s\n", num); @@ -69,6 +188,9 @@ static const char *const kPluginDirs[] = { static const char *resolve_plugin_name_from_argv0(const char *argv0) { const char *base = strrchr(argv0, '/'); base = base ? (base + 1) : argv0; + if (strcasecmp(base, "dmenu") == 0 || strcasecmp(base, "xdmenu") == 0) { + return "dmenu"; + } if (strcasecmp(base, "xdrawer") == 0) { return "xdrawer"; } @@ -126,6 +248,7 @@ static int list_plugins(void) { } int main(int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); const char *plugin_name = resolve_plugin_name_from_argv0(argv[0]); int sub_argc = argc; char **sub_argv = argv; @@ -143,7 +266,34 @@ int main(int argc, char **argv) { sub_argv = argv + 1; } - if (try_send_plugin_fifo(plugin_name, sub_argc, sub_argv)) { + if (strcmp(plugin_name, "dmenu") == 0) { + if (sub_argc >= 2 && sub_argv[1]) { + if (strcmp(sub_argv[1], "-v") == 0) { + puts("dmenu-5.0.14-bananarchy"); + return 0; + } + if (strcmp(sub_argv[1], "isopen") == 0) { + return access("/tmp/dwm_plugin_dmenu.active", F_OK) == 0 ? 0 : 1; + } + if (strcmp(sub_argv[1], "close") == 0) { + try_send_plugin_fifo("dmenu", sub_argc, sub_argv); + unlink("/tmp/dwm_plugin_dmenu.active"); + return 0; + } + if (is_dmenu_builtin_arg(sub_argv[1])) { + if (try_send_plugin_fifo("dmenu", sub_argc, sub_argv)) { + return 0; + } + } + } + if (!(sub_argc >= 2 && sub_argv[1] && + (strcmp(sub_argv[1], "--preload") == 0 || is_dmenu_builtin_arg(sub_argv[1])))) { + int exit_code = 1; + if (try_dmenu_socket_client(sub_argc, sub_argv, &exit_code)) { + return exit_code; + } + } + } else if (try_send_plugin_fifo(plugin_name, sub_argc, sub_argv)) { return 0; } diff --git a/src/dwm-plugins/phone_data.h b/src/dwm-plugins/phone_data.h index d7a0f20..90346d0 100644 --- a/src/dwm-plugins/phone_data.h +++ b/src/dwm-plugins/phone_data.h @@ -971,6 +971,9 @@ inline void closeOtherPlugins(const char* selfPluginName) { sendPluginCommand(p.name, "CLOSE"); } } + if (::access("/tmp/dwm_plugin_dmenu.active", F_OK) == 0) { + sendPluginCommand("dmenu", "CLOSE"); + } } inline std::string getModemLogDirPath() { diff --git a/src/sxmo-dmenu/dmenu.c b/src/sxmo-dmenu/dmenu.c index 15097bd..551cba4 100644 --- a/src/sxmo-dmenu/dmenu.c +++ b/src/sxmo-dmenu/dmenu.c @@ -4,12 +4,23 @@ #define _EVDEVK(_v) (0x10081000 + (_v)) #endif #include +#include +#include #include #include +#include +#include +#include #include #include #include #include +#include +#include +#include +#include +#include +#include #include #include @@ -24,8 +35,8 @@ #include "drw.h" #include "util.h" +#include "../sxmo-dwm/dwm_plugin.h" -/* macros */ #define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) #define LENGTH(X) (sizeof X / sizeof X[0]) @@ -33,39 +44,67 @@ #define NUMBERSMAXDIGITS 100 #define NUMBERSBUFSIZE (NUMBERSMAXDIGITS * 2) + 1 -/* enums */ -enum { SchemeNorm, SchemeSel, SchemeOut, SchemeNormHighlight, SchemeSelHighlight, SchemeLast }; /* color schemes */ +#define DMENU_PID_PATH "/tmp/dwm_plugin_dmenu.pid" +#define DMENU_LOCK_PATH "/tmp/dwm_plugin_dmenu.lock" +#define DMENU_FIFO_PATH "/tmp/dwm_plugin_dmenu.fifo" +#define DMENU_SOCK_PATH "/tmp/dwm_plugin_dmenu.sock" +#define DMENU_ACTIVE_PATH "/tmp/dwm_plugin_dmenu.active" + +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeNormHighlight, SchemeSelHighlight, SchemeLast }; +enum { MODE_NONE = 0, MODE_CLIENT = 1, MODE_BUILTIN = 2 }; struct item { - char *text; - struct item *left, *right; - int out; + char *text; + char *action; + int loop; + struct item *left, *right; + int out; }; static char numbers[NUMBERSBUFSIZE] = ""; static char text[BUFSIZ] = ""; static char *embed; static int bh, item_bh, mw, mh; +static int win_x = 0, win_y = 24, win_w = 0, win_h = 0; +static int default_lines = 10; +static int avail_h_cached = 296; static int inputw = 0, promptw; -static int lrpad; /* sum of left and right padding */ +static int lrpad; static size_t cursor; static struct item *items = NULL; +static size_t items_count = 0; +static size_t items_cap = 0; static struct item *matches, *matchend; static struct item *prev, *curr, *next, *sel, *nav; static int mon = -1, screen; static int use_text_input = 0; -static int initialindex; +static int initialindex = 0; static int managed = 0; +static int curpos = 0, oldcurlen = 0; + +static int daemon_mode = 0; +static int menu_active = 0; +static int menu_mode = MODE_NONE; +static int active_client_fd = -1; +static char current_builtin_menu[64] = ""; +static char *session_prompt = NULL; + +static int soft_pending = 0; +static long long soft_deadline = 0; +static long long start_ignore = 0; +static int initial_released = 1; +static int init_soft_mask = 0; +static long long init_soft_ms = 0; static Atom clip, utf8; static Display *dpy; static Window root, parentwin, win; +static Window prevfocuswin = None; static XIC xic; static Drw *drw; static Clr *scheme[SchemeLast]; -/* Empty arrays to be filled from command line, Xresources, and defaults, in decreasing order of precedence */ static char *colors[SchemeLast][2]; static const char *fonts[3] = { NULL, NULL, NULL }; @@ -74,1374 +113,2749 @@ static const char *fonts[3] = { NULL, NULL, NULL }; static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; static char *(*fstrstr)(const char *, const char *) = strstr; +/* Sxmo Nerd Font Icons */ +static const char *ICON_ARD = "\xe2\x86\x93"; +static const char *ICON_ARU = "\xe2\x86\x91"; +static const char *ICON_BEL = "\xef\x83\xb3"; +static const char *ICON_BTH = "\xef\x8a\x93"; +static const char *ICON_CAM = "\xef\x80\xb0"; +static const char *ICON_CFG = "\xee\x98\x95"; +static const char *ICON_CLK = "\xf3\xb0\xa5\x94"; +static const char *ICON_CLS = "\xf3\xb0\x85\x96"; +static const char *ICON_DIR = "\xef\x81\xbc"; +static const char *ICON_FLL = "\xf3\xb0\x89\x84"; +static const char *ICON_GRD = "\xf3\xb0\x80\xbb"; +static const char *ICON_INF = "\xef\x84\xa9"; +static const char *ICON_LCK = "\xef\x80\xa3"; +static const char *ICON_MNU = "\xef\x83\x89"; +static const char *ICON_MSG = "\xf3\xb0\x8d\xa6"; +static const char *ICON_MUS = "\xf3\xb0\x8e\x88"; +static const char *ICON_NET = "\xf3\xb0\x80\x82"; +static const char *ICON_OUT = "\xf3\xb0\x8d\x83"; +static const char *ICON_PHL = "\xf3\xb0\x8f\xb9"; +static const char *ICON_PHN = "\xf3\xb0\x8f\xb2"; +static const char *ICON_PLK = "\xf3\xb0\x8f\xb8"; +static const char *ICON_PWR = "\xe2\x8f\xbb"; +static const char *ICON_RLD = "\xef\x80\xa1"; +static const char *ICON_TOF = "\xef\x88\x84"; +static const char *ICON_TON = "\xef\x88\x85"; +static const char *ICON_TRM = "\xee\x9e\xa2"; +static const char *ICON_USR = "\xf3\xb0\x80\x84"; +static const char *ICON_WIN = "\xef\x8b\x90"; +static const char *ICON_ZZZ = "\xef\x86\x86"; +static const char *ICON_SAV = "\xf3\xb0\x86\x93"; +static const char *ICON_PST = "\xef\x83\xaa"; +static const char *ICON_ITM = "\xef\x83\x9a"; +static const char *ICON_CPY = "\xef\x83\x85"; +static const char *ICON_ZMI = "\xf3\xb0\x9b\xad"; +static const char *ICON_ZMO = "\xf3\xb0\x9b\xac"; +static const char *ICON_A2X = "\xe2\x87\x84"; +static const char *ICON_KBD = "\xef\x84\x9c"; + +static void drawmenu(void); +static void show_menu(void); +static void hide_menu(void); +static void open_builtin_menu(const char *menu_name, int keep_idx); + +static char * +cistrstr(const char *s, const char *sub) +{ + size_t len; + + for (len = strlen(sub); *s; s++) + if (!strncasecmp(s, sub, len)) + return (char *)s; + return NULL; +} + static void appenditem(struct item *item, struct item **list, struct item **last) { - if (*last) - (*last)->right = item; - else - *list = item; + if (*last) + (*last)->right = item; + else + *list = item; - item->left = *last; - item->right = NULL; - *last = item; + item->left = *last; + item->right = NULL; + *last = item; +} + +static void +free_items(void) +{ + if (items) { + for (size_t i = 0; i < items_count; i++) { + free(items[i].text); + free(items[i].action); + } + free(items); + } + items = NULL; + items_count = 0; + items_cap = 0; + matches = matchend = prev = curr = next = sel = NULL; +} + +static void +add_menu_item(const char *label, const char *action, int loop) +{ + if (items_count + 2 > items_cap) { + size_t new_cap = items_cap ? items_cap * 2 : 64; + struct item *nitems = realloc(items, new_cap * sizeof(*items)); + if (!nitems) + return; + items = nitems; + items_cap = new_cap; + } + items[items_count].text = strdup(label ? label : ""); + items[items_count].action = action ? strdup(action) : NULL; + items[items_count].loop = loop; + items[items_count].out = 0; + items[items_count].left = NULL; + items[items_count].right = NULL; + items_count++; + items[items_count].text = NULL; + items[items_count].action = NULL; + items[items_count].loop = 0; + items[items_count].out = 0; + items[items_count].left = NULL; + items[items_count].right = NULL; +} + +static void +reset_session_defaults(int case_insensitive, const char *new_prompt) +{ + free_items(); + topbar = 1; + centered = 0; + lines = default_lines; + use_text_input = 0; + initialindex = 0; + fstrncmp = case_insensitive ? strncasecmp : strncmp; + fstrstr = case_insensitive ? cistrstr : strstr; + free(session_prompt); + session_prompt = new_prompt ? strdup(new_prompt) : NULL; + prompt = session_prompt; + text[0] = '\0'; + cursor = 0; + curpos = 0; + oldcurlen = 0; + inputw = 0; } static void calcoffsets(void) { - int i, n; + int i, n; - if (lines > 0) - n = lines * bh; - else - n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); - /* 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 : MIN(TEXTW(next->text), n)) > n) - break; - for (i = 0, prev = curr; prev && prev->left; prev = prev->left) - if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) - break; + if (lines > 0) + n = lines * bh; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + for (i = 0, next = curr; next; next = next->right) + if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) + break; } -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; -} - -static Window prevfocuswin = None; - static int ignore_xerror(Display *d, XErrorEvent *ee) { - (void)d; - (void)ee; - return 0; + (void)d; + (void)ee; + return 0; +} + +static void +mark_menu_active(int active) +{ + if (active) { + int fd = open(DMENU_ACTIVE_PATH, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (fd >= 0) { + (void)write(fd, "1\n", 2); + close(fd); + } + } else { + unlink(DMENU_ACTIVE_PATH); + } } static void cleanup(void) { - size_t i; + size_t i; - if (xic) { - XUnsetICFocus(xic); - XDestroyIC(xic); - xic = NULL; - } - XUngrabKey(dpy, AnyKey, AnyModifier, root); - XSetErrorHandler(ignore_xerror); - if (prevfocuswin && prevfocuswin != win && prevfocuswin != PointerRoot) - XSetInputFocus(dpy, prevfocuswin, RevertToParent, CurrentTime); - for (i = 0; i < SchemeLast; i++) - free(scheme[i]); - drw_free(drw); - XSync(dpy, False); - XCloseDisplay(dpy); -} - -static char * -cistrstr(const char *s, const char *sub) -{ - size_t len; - - for (len = strlen(sub); *s; s++) - if (!strncasecmp(s, sub, len)) - return (char *)s; - return NULL; + mark_menu_active(0); + if (xic) { + XUnsetICFocus(xic); + XDestroyIC(xic); + xic = NULL; + } + XUngrabKey(dpy, AnyKey, AnyModifier, root); + XUngrabKeyboard(dpy, CurrentTime); + XSetErrorHandler(ignore_xerror); + if (prevfocuswin && prevfocuswin != win && prevfocuswin != PointerRoot) + XSetInputFocus(dpy, prevfocuswin, RevertToParent, CurrentTime); + free_items(); + free(session_prompt); + session_prompt = NULL; + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); } static void drawhighlights(struct item *item, int x, int y, int maxw) { - char restorechar, tokens[sizeof text], *highlight, *token; - int indentx, highlightlen; + char restorechar, tokens[sizeof text], *highlight, *token; + int indentx, highlightlen; - drw_setscheme(drw, scheme[item == sel ? SchemeSelHighlight : SchemeNormHighlight]); - strcpy(tokens, text); - for (token = strtok(tokens, " "); token; token = strtok(NULL, " ")) { - highlight = fstrstr(item->text, token); - while (highlight) { - // Move item str end, calc width for highlight indent, & restore - highlightlen = highlight - item->text; - restorechar = *highlight; - item->text[highlightlen] = '\0'; - indentx = TEXTW(item->text); - item->text[highlightlen] = restorechar; + drw_setscheme(drw, scheme[item == sel ? SchemeSelHighlight : SchemeNormHighlight]); + strcpy(tokens, text); + for (token = strtok(tokens, " "); token; token = strtok(NULL, " ")) { + highlight = fstrstr(item->text, token); + while (highlight) { + highlightlen = highlight - item->text; + restorechar = *highlight; + item->text[highlightlen] = '\0'; + indentx = TEXTW(item->text); + item->text[highlightlen] = restorechar; - // Move highlight str end, draw highlight, & restore - restorechar = highlight[strlen(token)]; - highlight[strlen(token)] = '\0'; - drw_text( - drw, - x + indentx - (lrpad / 2) - 1, - y, - MIN(maxw - indentx, TEXTW(highlight) - lrpad), - (lines > 0 ? item_bh : bh), 0, highlight, 0 - ); - highlight[strlen(token)] = restorechar; + restorechar = highlight[strlen(token)]; + highlight[strlen(token)] = '\0'; + drw_text( + drw, + x + indentx - (lrpad / 2) - 1, + y, + MIN(maxw - indentx, TEXTW(highlight) - lrpad), + (lines > 0 ? item_bh : bh), 0, highlight, 0 + ); + highlight[strlen(token)] = restorechar; - if (strlen(highlight) - strlen(token) < strlen(token)) break; - highlight = fstrstr(highlight + strlen(token), token); - } - } + if (strlen(highlight) - strlen(token) < strlen(token)) + break; + highlight = fstrstr(highlight + strlen(token), token); + } + } } static int drawitem(struct item *item, int x, int y, int w) { - if (item == sel) - drw_setscheme(drw, scheme[SchemeSel]); - else if (item->out) - drw_setscheme(drw, scheme[SchemeOut]); - else - drw_setscheme(drw, scheme[SchemeNorm]); + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->out) + drw_setscheme(drw, scheme[SchemeOut]); + else + drw_setscheme(drw, scheme[SchemeNorm]); - int r = drw_text(drw, x, y, w, (lines > 0 ? item_bh : bh), lrpad / 2, item->text, 0); - drawhighlights(item, x, y, w); - return r; + int r = drw_text(drw, x, y, w, (lines > 0 ? item_bh : bh), lrpad / 2, item->text, 0); + drawhighlights(item, x, y, w); + return r; } static void -recalculatenumbers() +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++; - if (numer == denom) - snprintf(numbers, NUMBERSBUFSIZE, "%d", numer); - else - snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); + 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++; + if (numer == denom) + snprintf(numbers, NUMBERSBUFSIZE, "%d", numer); + else + snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); } static void drawmenu(void) { - static int curpos, oldcurlen; - struct item *item; - int x = 0, y = 0, fh = drw->fonts->h, w; - int curlen, rcurlen; + struct item *item; + int x = 0, y = 0, fh = drw->fonts->h, w; + int curlen, rcurlen; - drw_setscheme(drw, scheme[SchemeNorm]); - drw_rect(drw, 0, 0, mw, mh, 1, 1); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); - /* draw top search bar background in SchemeOut (#383838) */ - drw_setscheme(drw, scheme[SchemeOut]); - drw_rect(drw, 0, 0, mw, bh, 1, 1); + drw_setscheme(drw, scheme[SchemeOut]); + drw_rect(drw, 0, 0, mw, bh, 1, 1); - if (prompt && *prompt) { - if (colorprompt) - drw_setscheme(drw, scheme[SchemeSel]); - x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); - } + if (prompt && *prompt) { + if (colorprompt) + drw_setscheme(drw, scheme[SchemeSel]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + } - recalculatenumbers(); + recalculatenumbers(); - /* draw input field */ - w = (lines > 0 || !matches) ? mw - x : inputw; - w -= lrpad / 2; - x += lrpad / 2; + w = (lines > 0 || !matches) ? mw - x : inputw; + w -= lrpad / 2; + x += lrpad / 2; - rcurlen = drw_fontset_getwidth(drw, text + cursor); - curlen = drw_fontset_getwidth(drw, text) - rcurlen; - curpos += curlen - oldcurlen; - curpos = MIN(w - TEXTW(numbers), MAX(0, curpos)); - curpos = MAX(curpos, w - rcurlen - TEXTW(numbers)); - curpos = MIN(curpos, curlen); - oldcurlen = curlen; + rcurlen = drw_fontset_getwidth(drw, text + cursor); + curlen = drw_fontset_getwidth(drw, text) - rcurlen; + curpos += curlen - oldcurlen; + curpos = MIN(w - TEXTW(numbers), MAX(0, curpos)); + curpos = MAX(curpos, w - rcurlen - TEXTW(numbers)); + curpos = MIN(curpos, curlen); + oldcurlen = curlen; - drw_setscheme(drw, scheme[SchemeOut]); - drw_text_align(drw, x, 0, curpos, bh, text, cursor, AlignR); - drw_text_align(drw, x + curpos, 0, w - curpos - TEXTW(numbers), bh, text + cursor, strlen(text) - cursor, AlignL); - drw_rect(drw, x + curpos - 1, 2 + (bh - fh) / 2, 2, fh - 4, 1, 0); + drw_setscheme(drw, scheme[SchemeOut]); + drw_text_align(drw, x, 0, curpos, bh, text, cursor, AlignR); + drw_text_align(drw, x + curpos, 0, w - curpos - TEXTW(numbers), bh, text + cursor, strlen(text) - cursor, AlignL); + drw_rect(drw, x + curpos - 1, 2 + (bh - fh) / 2, 2, fh - 4, 1, 0); - if (lines > 0) { - /* draw vertical list */ - x -= lrpad / 2; - y = bh; - for (item = curr; item != next; item = item->right) { - drawitem(item, 0, y, mw); - y += item_bh; - } - } else if (matches) { - /* draw horizontal list */ - x += inputw; - w = TEXTW("<"); - if (curr->left) { - drw_setscheme(drw, scheme[SchemeNorm]); - drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); - } - x += w; - for (item = curr; item != next; item = item->right) - x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">") - TEXTW(numbers))); - if (next) { - w = TEXTW(">"); - drw_setscheme(drw, scheme[SchemeNorm]); - drw_text(drw, mw - w - TEXTW(numbers), 0, w, bh, lrpad / 2, ">", 0); - } - } - drw_setscheme(drw, scheme[SchemeOut]); - drw_text(drw, mw - TEXTW(numbers), 0, TEXTW(numbers), bh, lrpad / 2, numbers, 0); - drw_map(drw, win, 0, 0, mw, mh); + if (lines > 0) { + x -= lrpad / 2; + y = bh; + for (item = curr; item != next; item = item->right) { + drawitem(item, 0, y, mw); + y += item_bh; + } + } else if (matches) { + x += inputw; + w = TEXTW("<"); + if (curr->left) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); + } + x += w; + for (item = curr; item != next; item = item->right) + x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">") - TEXTW(numbers))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - w - TEXTW(numbers), 0, w, bh, lrpad / 2, ">", 0); + } + } + drw_setscheme(drw, scheme[SchemeOut]); + drw_text(drw, mw - TEXTW(numbers), 0, TEXTW(numbers), bh, lrpad / 2, numbers, 0); + drw_map(drw, win, 0, 0, mw, mh); } static void grabfocus(void) { - struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; - Window focuswin; - int i, revertwin; + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; + Window focuswin; + int i, revertwin; - for (i = 0; i < 100; ++i) { - XGetInputFocus(dpy, &focuswin, &revertwin); - if (focuswin == win) - return; - if (!prevfocuswin && focuswin != None && focuswin != PointerRoot) - prevfocuswin = focuswin; - XSetInputFocus(dpy, win, RevertToParent, CurrentTime); - XGetInputFocus(dpy, &focuswin, &revertwin); - if (focuswin == win) - return; - nanosleep(&ts, NULL); - } - die("cannot grab focus"); + for (i = 0; i < 100; ++i) { + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) + return; + if (!prevfocuswin && focuswin != None && focuswin != PointerRoot && focuswin != win) + prevfocuswin = focuswin; + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) + return; + nanosleep(&ts, NULL); + } } static void grabkeyboard(void) { - struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; - int i; + struct timespec ts = { .tv_sec = 0, .tv_nsec = 2000000 }; + Window grabwin; + int i; - if (embed || managed) - return; - /* try to grab keyboard, we may have to wait for another process to ungrab */ - for (i = 0; i < 1000; i++) { - if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, - GrabModeAsync, CurrentTime) == GrabSuccess) - return; - nanosleep(&ts, NULL); - } - die("cannot grab keyboard"); + if (embed || managed) + return; + grabwin = daemon_mode ? win : DefaultRootWindow(dpy); + for (i = 0; i < 25; i++) { + if (XGrabKeyboard(dpy, grabwin, True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) + return; + nanosleep(&ts, NULL); + } } static void match(void) { - static char **tokv = NULL; - static int tokn = 0; + static char **tokv = NULL; + static int tokn = 0; - char buf[sizeof text], *s; - int i, tokc = 0; - size_t len, textsize; - struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; + char buf[sizeof text], *s; + int i, tokc = 0; + size_t len, textsize; + struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; - strcpy(buf, text); - /* separate input text into tokens to be matched individually */ - for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) - if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) - die("cannot realloc %u bytes:", tokn * sizeof *tokv); - len = tokc ? strlen(tokv[0]) : 0; + strcpy(buf, text); + for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) + if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) + die("cannot realloc %u bytes:", tokn * sizeof *tokv); + len = tokc ? strlen(tokv[0]) : 0; - matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; - textsize = strlen(text) + 1; - for (item = items; item && item->text; item++) { - for (i = 0; i < tokc; i++) - if (!fstrstr(item->text, tokv[i])) - break; - if (i != tokc) /* not all tokens match */ - continue; - /* exact matches go first, then prefixes, then substrings */ - if (!tokc || !fstrncmp(text, item->text, textsize)) - appenditem(item, &matches, &matchend); - else if (!fstrncmp(tokv[0], item->text, len)) - appenditem(item, &lprefix, &prefixend); - else - appenditem(item, &lsubstr, &substrend); - } - if (lprefix) { - if (matches) { - matchend->right = lprefix; - lprefix->left = matchend; - } else - matches = lprefix; - matchend = prefixend; - } - if (lsubstr) { - if (matches) { - matchend->right = lsubstr; - lsubstr->left = matchend; - } else - matches = lsubstr; - matchend = substrend; - } - curr = sel = matches; - calcoffsets(); + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + textsize = strlen(text) + 1; + for (item = items; item && item->text; item++) { + for (i = 0; i < tokc; i++) + if (!fstrstr(item->text, tokv[i])) + break; + if (i != tokc) + continue; + if (!tokc || !fstrncmp(text, item->text, textsize)) + appenditem(item, &matches, &matchend); + else if (!fstrncmp(tokv[0], item->text, len)) + appenditem(item, &lprefix, &prefixend); + else + appenditem(item, &lsubstr, &substrend); + } + if (lprefix) { + if (matches) { + matchend->right = lprefix; + lprefix->left = matchend; + } else + matches = lprefix; + matchend = prefixend; + } + if (lsubstr) { + if (matches) { + matchend->right = lsubstr; + lsubstr->left = matchend; + } else + matches = lsubstr; + matchend = substrend; + } + curr = sel = matches; + calcoffsets(); } static void insert(const char *str, ssize_t n) { - if (strlen(text) + n > sizeof text - 1) - return; - /* move existing text out of the way, insert new text, and update cursor */ - memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); - if (n > 0) - memcpy(&text[cursor], str, n); - cursor += n; - match(); + if (strlen(text) + n > sizeof text - 1) + return; + memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); + if (n > 0) + memcpy(&text[cursor], str, n); + cursor += n; + match(); } static size_t nextrune(int inc) { - ssize_t n; + ssize_t n; - /* return location of next utf8 rune in the given direction (+1 or -1) */ - for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) - ; - return n; + for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) + ; + return n; } static void movewordedge(int dir) { - if (dir < 0) { /* move cursor to the start of the word*/ - while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) - cursor = nextrune(-1); - while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) - cursor = nextrune(-1); - } else { /* move cursor to the end of the word */ - while (text[cursor] && strchr(worddelimiters, text[cursor])) - cursor = nextrune(+1); - while (text[cursor] && !strchr(worddelimiters, text[cursor])) - cursor = nextrune(+1); - } + if (dir < 0) { + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + } else { + while (text[cursor] && strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + while (text[cursor] && !strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + } } -static void -keypress(XKeyEvent *ev) +static int +current_sel_index(void) { - char buf[32]; - int len; - KeySym ksym; - Status status; - - ksym = NoSymbol; - if (xic && ev->keycode == 0) - len = Xutf8LookupString(xic, ev, buf, sizeof buf, &ksym, &status); - else { - len = XLookupString(ev, buf, sizeof buf, &ksym, NULL); - status = (len > 0 && ksym != NoSymbol) ? XLookupBoth : - (len > 0) ? XLookupChars : - (ksym != NoSymbol) ? XLookupKeySym : XLookupNone; - } - switch (status) { - default: /* XLookupNone, XBufferOverflow */ - return; - case XLookupChars: - goto insert; - case XLookupKeySym: - case XLookupBoth: - break; - } - - if (ev->state & ControlMask) { - switch(ksym) { - case XK_a: ksym = XK_Home; break; - case XK_b: ksym = XK_Left; break; - case XK_c: ksym = XK_Escape; break; - case XK_d: ksym = XK_Delete; break; - case XK_e: ksym = XK_End; break; - case XK_f: ksym = XK_Right; break; - case XK_g: ksym = XK_Escape; break; - case XK_h: ksym = XK_BackSpace; break; - case XK_i: ksym = XK_Tab; break; - case XK_j: /* fallthrough */ - case XK_J: /* fallthrough */ - case XK_m: /* fallthrough */ - case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; - case XK_n: ksym = XK_Down; break; - case XK_p: ksym = XK_Up; break; - - case XK_k: /* delete right */ - text[cursor] = '\0'; - match(); - break; - case XK_u: /* delete left */ - insert(NULL, 0 - cursor); - break; - case XK_w: /* delete word */ - while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) - insert(NULL, nextrune(-1) - cursor); - while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) - insert(NULL, nextrune(-1) - cursor); - break; - case XK_y: /* paste selection */ - case XK_Y: - XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, - utf8, utf8, win, CurrentTime); - return; - case XK_Left: - movewordedge(-1); - goto draw; - case XK_Right: - movewordedge(+1); - goto draw; - case XK_Return: - case XK_KP_Enter: - break; - case XK_bracketleft: - cleanup(); - exit(1); - default: - return; - } - } else if (ev->state & Mod1Mask) { - switch(ksym) { - case XK_b: - movewordedge(-1); - goto draw; - case XK_f: - movewordedge(+1); - goto draw; - case XK_g: ksym = XK_Home; break; - case XK_G: ksym = XK_End; break; - case XK_h: ksym = XK_Up; break; - case XK_j: ksym = XK_Next; break; - case XK_k: ksym = XK_Prior; break; - case XK_l: ksym = XK_Down; break; - default: - return; - } - } - - switch(ksym) { - default: -insert: - if (!iscntrl(*buf)) - insert(buf, len); - break; - case XK_Delete: - if (text[cursor] == '\0') - return; - cursor = nextrune(+1); - /* fallthrough */ - case XK_BackSpace: - if (cursor == 0) - return; - insert(NULL, nextrune(-1) - cursor); - break; - case XK_End: - if (text[cursor] != '\0') { - cursor = strlen(text); - break; - } - if (next) { - /* jump to end of list and position items in reverse */ - curr = matchend; - calcoffsets(); - curr = prev; - calcoffsets(); - while (next && (curr = curr->right)) - calcoffsets(); - } - sel = matchend; - break; - case XK_Escape: - case XF86XK_PowerOff: - cleanup(); - exit(1); - case XF86XK_Back: - case XF86XK_KbdLcdMenu2: - return; - case XK_Home: - if (sel == matches) { - cursor = 0; - break; - } - sel = curr = matches; - calcoffsets(); - break; - case XK_Left: - if (cursor > 0 && (!sel || !sel->left || lines > 0)) { - cursor = nextrune(-1); - break; - } - if (lines > 0) - return; - /* fallthrough */ - case XF86XK_AudioRaiseVolume: - case XK_Up: - if (sel && sel->left) { - sel = sel->left; - if (sel->right == curr) { - curr = prev; - calcoffsets(); - } - } else if (wrap_around) { - // if there is no previous item, jump to the bottom of the list - curr = matchend; - calcoffsets(); - curr = prev; - calcoffsets(); - while (next && (curr = curr->right)) - calcoffsets(); - sel = matchend; - } - break; - case XK_Next: - if (!next) - return; - sel = curr = next; - calcoffsets(); - break; - case XK_Prior: - if (!prev) - return; - sel = curr = prev; - calcoffsets(); - break; - case XK_Menu: - case XF86XK_KbdLcdMenu1: - return; - case XF86XK_Phone: - case XF86XK_PickupPhone: - case XK_Return: - case XK_KP_Enter: - if (use_text_input) - puts((sel && (ev->state & ShiftMask)) ? sel->text : text); - else - puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); - if (!(ev->state & ControlMask)) { - cleanup(); - exit(0); - } - if (sel) - sel->out = 1; - break; - case XK_Right: - if (text[cursor] != '\0') { - cursor = nextrune(+1); - break; - } - if (lines > 0) - return; - /* fallthrough */ - case XF86XK_AudioLowerVolume: - case XK_Down: - if (sel && sel->right) { - sel = sel->right; - if (sel == next) { - curr = next; - } - } else if (wrap_around) { - // if there is no next item, jump to the top of the list - sel = curr = matches; - } - calcoffsets(); - break; - case XK_Tab: - if (!sel) - return; - strncpy(text, sel->text, sizeof text - 1); - text[sizeof text - 1] = '\0'; - cursor = strlen(text); - match(); - break; - } - -draw: - drawmenu(); + int idx = 0; + for (struct item *it = matches; it; it = it->right, idx++) { + if (it == sel) + return idx; + } + return 0; } -static void -motionnotify(XEvent *e) -{ - struct item *item; - XMotionEvent *ev = &e->xmotion; - int y = 0, h = bh; - char *pageup = "Page up", *pagedown = "Page down"; - char *gotop = "Go to top", *gobottom = "Go to bottom"; - char *alreadytop = "Already at top", *alreadybottom = "Already at bottom"; - - if (ev->window != win){ - return; - } - - if (ev->state & ~Button1Mask) - return; - - if (lines > 0) { - /* vertical list: (ctrl)left-click on item */ - y = bh; - for (item = curr; item != next; item = item->right) { - if (ev->y >= y && ev->y < (y + item_bh)) { - sel = item; - drawmenu(); - return; - } - y += item_bh; - } - /* display page up/down or jump top/bottom as appropriate */ - if(ev->y > mh || ev->y < 0){ - if (ev->y >= (mh) && ev->y <= (mh + bh*scrollregion )) { - nav->text = next ? pagedown : alreadybottom; - drw_setscheme(drw, scheme[SchemeSel]); - y=mh-bh; - } else if (ev->y >= (mh + bh*scrollregion ) && ev->y < (mh + bh*scrollregion*2 )) { - nav->text = next ? gobottom : alreadybottom; - drw_setscheme(drw, scheme[SchemeSel]); - y=mh-bh; - } else if (ev->y >= (0 - h*scrollregion) && ev->y <= (0)) { - nav->text = curr != matches ? pageup : alreadytop; - drw_setscheme(drw, scheme[SchemeSel]); - y=0+bh; - } else if (ev->y >= (0 - h*scrollregion*2) && ev->y <= (0 - h*scrollregion)) { - nav->text = curr != matches ? gotop : alreadytop; - drw_setscheme(drw, scheme[SchemeSel]); - y=0+bh; - } else { - drawmenu(); - return; - } - drw_setscheme(drw, scheme[SchemeOut]); - drw_text(drw, 0, y, mw, bh, lrpad / 2, nav->text, 0); - drw_map(drw, win, 0, 0, mw, mh); - XFlush(dpy); - return; - } - } - return; -} - - -static void -buttonpress(XEvent *e) -{ - struct item *item; - XButtonPressedEvent *ev = &e->xbutton; - int x = 0, y = 0, h = bh, w; - - if (ev->window != win) - 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 (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; - } - /* middle-mouse click: paste selection */ - if (ev->button == Button2) { - XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, - utf8, utf8, win, CurrentTime); - drawmenu(); - return; - } - /* jump to top */ - if (ev->y >= (0 - h*scrollregion*2) && ev->y <= (0 - h*scrollregion)) { - sel = curr = matches; - calcoffsets(); - drawmenu(); - return; - } - /* scroll up */ - if ( (ev->button == Button4 && prev) - || (ev->y >= (0 - h*scrollregion) && ev->y <= (0)) - ) { - sel = curr = prev; - calcoffsets(); - drawmenu(); - return; - } - /* scroll down */ - if ( (ev->button == Button5 && next) - || (ev->y >= (mh) && ev->y <= (mh + bh*scrollregion )) - ) { - if(next) - sel = curr = next; - calcoffsets(); - drawmenu(); - return; - } - /* jump to bottom */ - if (ev->y >= (mh + bh*scrollregion ) && ev->y < (mh + bh*scrollregion*2 )) { - 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; - drawmenu(); - return; - } - - if (ev->button != Button1) - return; - if (ev->state & ~ControlMask & ~Button1Mask) - return; - if (lines > 0) { - /* vertical list: (ctrl)left-click on item */ - w = mw - x; - y = bh; - for (item = curr; item != next; item = item->right) { - if (ev->y >= y && ev->y < (y + item_bh)) { - puts(item->text); - if (!(ev->state & ControlMask)) - exit(0); - sel = item; - if (sel) { - sel->out = 1; - drawmenu(); - } - return; - } - y += item_bh; - } - } else if (matches) { - /* left-click on left arrow */ - x += inputw; - w = TEXTW("<"); - 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; - w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); - if (ev->x >= x && ev->x <= x + w) { - puts(item->text); - if (!(ev->state & ControlMask)) - exit(0); - sel = item; - if (sel) { - sel->out = 1; - drawmenu(); - } - return; - } - } - /* left-click on right arrow */ - w = TEXTW(">"); - x = mw - w; - if (next && ev->x >= x && ev->x <= x + w) { - sel = curr = next; - calcoffsets(); - drawmenu(); - return; - } - } -} - -static void -paste(void) -{ - char *p, *q; - int di; - unsigned long dl; - Atom da; - - /* we have been given the current selection, now insert it into input */ - if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, - utf8, &da, &di, &dl, &dl, (unsigned char **)&p) - == Success && p) { - insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); - XFree(p); - } - drawmenu(); -} - -static void -readstdin(void) -{ - char 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, stdin); 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); - items[i].out = 0; - if (lines == 0 && !centered) { - drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); - if (tmpmax > inputw) { - inputw = tmpmax; - imax = i; - } - } - } - if (items) - items[i].text = NULL; - inputw = (items && lines == 0 && !centered) ? TEXTW(items[imax].text) : 0; -} - -#include - static int issoftkey(unsigned int keycode) { - /* X11 keycodes: Power=124, Menu(Left Softkey)=135, Back(Right Softkey)=166, Phone(Call)=177 */ - return keycode == 124 || keycode == 135 || keycode == 166 || keycode == 177; + return keycode == 124 || keycode == 135 || keycode == 166 || keycode == 177; } static long long dmenu_now_ms(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (long long)ts.tv_sec * 1000LL + (long long)(ts.tv_nsec / 1000000L); + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (long long)ts.tv_sec * 1000LL + (long long)(ts.tv_nsec / 1000000L); } static void write_softkey_suppress(long long until_ms) { - char buf[32]; - int fd = open("/tmp/sxmo_softkey_suppress", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); - if (fd >= 0) { - int len = snprintf(buf, sizeof(buf), "%lld\n", until_ms); - (void)write(fd, buf, len); - close(fd); - } + char buf[32]; + int fd = open("/tmp/sxmo_softkey_suppress", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (fd >= 0) { + int len = snprintf(buf, sizeof(buf), "%lld\n", until_ms); + (void)write(fd, buf, len); + close(fd); + } } static int query_softkeys_down(void) { - char km[32]; - int mask = 0; - if (!XQueryKeymap(dpy, km)) - return 0; - if (km[135 / 8] & (1 << (135 % 8))) - mask |= 1; - if (km[166 / 8] & (1 << (166 % 8))) - mask |= 2; - return mask; + char km[32]; + int mask = 0; + if (!XQueryKeymap(dpy, km)) + return 0; + if (km[135 / 8] & (1 << (135 % 8))) + mask |= 1; + if (km[166 / 8] & (1 << (166 % 8))) + mask |= 2; + return mask; +} + +static int +query_trigger_keys_down(void) +{ + char km[32]; + int mask = 0; + if (!XQueryKeymap(dpy, km)) + return 0; + if (km[135 / 8] & (1 << (135 % 8))) + mask |= 1; + if (km[166 / 8] & (1 << (166 % 8))) + mask |= 2; + if (km[36 / 8] & (1 << (36 % 8))) + mask |= 4; + if (km[104 / 8] & (1 << (104 % 8))) + mask |= 8; + return mask; } static void read_softkey_state(int *mask_out, long long *ms_out) { - char buf[64]; - int fd = open("/tmp/sxmo_softkey_state", O_RDONLY | O_CLOEXEC); - *mask_out = 0; - *ms_out = 0; - if (fd >= 0) { - ssize_t n = read(fd, buf, sizeof(buf) - 1); - close(fd); - if (n > 0) { - buf[n] = '\0'; - sscanf(buf, "%d %lld", mask_out, ms_out); - } - } + char buf[64]; + int fd = open("/tmp/sxmo_softkey_state", O_RDONLY | O_CLOEXEC); + *mask_out = 0; + *ms_out = 0; + if (fd >= 0) { + ssize_t n = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (n > 0) { + buf[n] = '\0'; + sscanf(buf, "%d %lld", mask_out, ms_out); + } + } } static void clear_softkey_state(void) { - int fd = open("/tmp/sxmo_softkey_state", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); - if (fd >= 0) { - (void)write(fd, "0 0\n", 4); - close(fd); - } + int fd = open("/tmp/sxmo_softkey_state", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (fd >= 0) { + (void)write(fd, "0 0\n", 4); + close(fd); + } } static void -spawn_xdrawer_and_exit(void) +spawn_detached_sh(const char *cmd) { - clear_softkey_state(); - write_softkey_suppress(dmenu_now_ms() + 500); - cleanup(); - if (fork() == 0) { - int nullfd = open("/dev/null", O_RDWR); - if (nullfd >= 0) { - dup2(nullfd, 0); - dup2(nullfd, 1); - dup2(nullfd, 2); - if (nullfd > 2) - close(nullfd); - } - setsid(); - execlp("xdrawer", "xdrawer", (char *)NULL); - execl("/home/user/.local/bin/xdrawer", "xdrawer", (char *)NULL); - execl("/usr/bin/xdrawer", "xdrawer", (char *)NULL); - _exit(0); - } - exit(1); + if (!cmd || !cmd[0]) + return; + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + int nullfd = open("/dev/null", O_RDWR); + if (nullfd >= 0) { + dup2(nullfd, 0); + dup2(nullfd, 1); + dup2(nullfd, 2); + if (nullfd > 2) + close(nullfd); + } + setsid(); + execl("/bin/sh", "sh", "-c", cmd, (char *)NULL); + _exit(0); + } } static void -run(void) +trigger_plugin_open(const char *plugin_name) { - XEvent ev; - int soft_pending = 0; - long long soft_deadline = 0; - long long start_ignore = dmenu_now_ms() + 40; - int initial_released = (query_softkeys_down() == 0); - int init_soft_mask = 0; - long long init_soft_ms = 0; - int xfd = ConnectionNumber(dpy); - - read_softkey_state(&init_soft_mask, &init_soft_ms); - - for (;;) { - if (!initial_released && query_softkeys_down() == 0) - initial_released = 1; - while (XPending(dpy)) { - if (XNextEvent(dpy, &ev)) - return; - if (!initial_released && query_softkeys_down() == 0) - initial_released = 1; - if (ev.type == KeyPress || ev.type == KeyRelease) { - KeySym ks = XLookupKeysym(&ev.xkey, 0); - int is_left = (ks == XK_Menu || ks == XF86XK_KbdLcdMenu1 || ev.xkey.keycode == 135); - int is_right = (ks == XF86XK_Back || ks == XF86XK_KbdLcdMenu2 || ev.xkey.keycode == 166); - if (is_left || is_right) { - long long now = dmenu_now_ms(); - int which = (is_left ? 1 : 0) | (is_right ? 2 : 0); - if (ev.type == KeyPress && init_soft_mask != 0 && - (init_soft_mask | which) == 3 && - (now - init_soft_ms) <= 320) { - spawn_xdrawer_and_exit(); - } - if (ev.type == KeyRelease) { - initial_released = 1; - continue; - } - if (!initial_released || now < start_ignore) - continue; - soft_pending |= which | query_softkeys_down(); - if (soft_pending == 3) - spawn_xdrawer_and_exit(); - if (soft_deadline == 0) - soft_deadline = now + 30; - continue; - } - if (!issoftkey(ev.xkey.keycode)) { - ev.xkey.window = win; - if (XFilterEvent(&ev, None)) - continue; - } - } else if (XFilterEvent(&ev, None)) { - continue; - } - switch(ev.type) { - case MotionNotify: - motionnotify(&ev); - break; - case ButtonRelease: - buttonpress(&ev); - break; - case DestroyNotify: - if (ev.xdestroywindow.window != win) - break; - cleanup(); - exit(1); - case Expose: - if (ev.xexpose.count == 0) - drw_map(drw, win, 0, 0, mw, mh); - break; - case FocusIn: - /* regrab focus from parent window */ - if (ev.xfocus.window != win) - grabfocus(); - if (xic) - XSetICFocus(xic); - break; - case KeyPress: - keypress(&ev.xkey); - break; - case SelectionNotify: - if (ev.xselection.property == utf8) - paste(); - break; - } - } - - if (soft_pending && soft_deadline > 0) { - long long now = dmenu_now_ms(); - int hw_down = query_softkeys_down(); - soft_pending |= hw_down; - if (soft_pending == 3) - spawn_xdrawer_and_exit(); - long long rem = soft_deadline - now; - if (rem <= 0) { - clear_softkey_state(); - write_softkey_suppress(now + 250); - if (soft_pending == 1) { - puts((sel) ? sel->text : text); - cleanup(); - exit(0); - } else { - cleanup(); - exit(1); - } - } - struct pollfd pfd = { xfd, POLLIN, 0 }; - poll(&pfd, 1, (int)rem); - } else { - struct pollfd pfd = { xfd, POLLIN, 0 }; - poll(&pfd, 1, initial_released ? -1 : 40); - } - } + char fifo_path[128]; + snprintf(fifo_path, sizeof(fifo_path), "/tmp/dwm_plugin_%s.fifo", plugin_name); + int fd = open(fifo_path, O_WRONLY | O_NONBLOCK | O_CLOEXEC); + if (fd >= 0) { + const char *msg = (strcmp(plugin_name, "xdrawer") == 0) ? "TOGGLE\n" : "OPEN\n"; + if (write(fd, msg, strlen(msg)) > 0) { + close(fd); + return; + } + close(fd); + } + char cmd[128]; + snprintf(cmd, sizeof(cmd), "dwm-plugin %s", plugin_name); + spawn_detached_sh(cmd); } static void -setup(void) +send_key_to_window(Window target, KeySym ksym, unsigned int mod_state) { - int x, y, i, j; - unsigned int du; - XSetWindowAttributes swa; - XIM xim; - Window w, dw, *dws; - XWindowAttributes wa; - XClassHint ch = {"dmenu", "dmenu"}; + if (!target || target == None || target == PointerRoot) + return; + KeyCode kc = XKeysymToKeycode(dpy, ksym); + if (!kc) + return; + XKeyEvent kev; + memset(&kev, 0, sizeof(kev)); + kev.type = KeyPress; + kev.display = dpy; + kev.window = target; + kev.root = root; + kev.subwindow = None; + kev.time = CurrentTime; + kev.state = mod_state; + kev.keycode = kc; + kev.same_screen = True; + XSetErrorHandler(ignore_xerror); + XSendEvent(dpy, target, True, KeyPressMask, (XEvent *)&kev); + kev.type = KeyRelease; + XSendEvent(dpy, target, True, KeyReleaseMask, (XEvent *)&kev); + XFlush(dpy); +} + +static void +handle_synth_key_action(const char *spec, Window target) +{ + unsigned int mods = 0; + const char *keyname = spec; + if (strncmp(spec, "ctrl_shift:", 11) == 0) { + mods = ControlMask | ShiftMask; + keyname = spec + 11; + } else if (strncmp(spec, "ctrl:", 5) == 0) { + mods = ControlMask; + keyname = spec + 5; + } else if (strncmp(spec, "none:", 5) == 0) { + mods = 0; + keyname = spec + 5; + } + KeySym ks = XStringToKeysym(keyname); + if (ks != NoSymbol) + send_key_to_window(target, ks, mods); +} + +static int +read_first_line(const char *path, char *buf, size_t bufsz) +{ + int fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return 0; + ssize_t n = read(fd, buf, bufsz - 1); + close(fd); + if (n <= 0) + return 0; + buf[n] = '\0'; + char *nl = strpbrk(buf, "\r\n"); + if (nl) + *nl = '\0'; + return 1; +} + +static int +write_str_file(const char *path, const char *val) +{ + int fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC); + if (fd < 0) + return 0; + ssize_t n = write(fd, val, strlen(val)); + close(fd); + return n > 0; +} + +static void +handle_builtin_toggle(const char *toggle_name) +{ + if (strcmp(toggle_name, "flashlight") == 0) { + char buf[32] = ""; + const char *path = "/sys/class/leds/white:torch/brightness"; + if (access(path, R_OK) != 0) + path = "/sys/class/leds/white:flash/brightness"; + if (read_first_line(path, buf, sizeof(buf))) { + int cur = atoi(buf); + if (!write_str_file(path, cur > 0 ? "0\n" : "255\n")) + spawn_detached_sh("sxmo_flashtoggle.sh"); + } else { + spawn_detached_sh("sxmo_flashtoggle.sh"); + } + } else if (strcmp(toggle_name, "bright_up") == 0 || strcmp(toggle_name, "bright_down") == 0) { + char cur_s[32] = "", max_s[32] = ""; + const char *bpath = "/sys/class/backlight/backlight/brightness"; + const char *mpath = "/sys/class/backlight/backlight/max_brightness"; + if (read_first_line(bpath, cur_s, sizeof(cur_s)) && read_first_line(mpath, max_s, sizeof(max_s))) { + int cur = atoi(cur_s); + int maxv = atoi(max_s); + if (maxv <= 0) + maxv = 255; + int step = MAX(1, maxv / 10); + int nextv = (strcmp(toggle_name, "bright_up") == 0) ? MIN(maxv, cur + step) : MAX(1, cur - step); + char out[32]; + snprintf(out, sizeof(out), "%d\n", nextv); + if (!write_str_file(bpath, out)) + spawn_detached_sh(strcmp(toggle_name, "bright_up") == 0 ? "sxmo_brightness.sh up" : "sxmo_brightness.sh down"); + } else { + spawn_detached_sh(strcmp(toggle_name, "bright_up") == 0 ? "sxmo_brightness.sh up" : "sxmo_brightness.sh down"); + } + } else if (strcmp(toggle_name, "autosuspend") == 0) { + const char *home = getenv("HOME"); + if (!home || !home[0]) + home = "/home/user"; + char path[256]; + snprintf(path, sizeof(path), "%s/.cache/sxmo/sxmo.nosuspend", home); + if (access(path, F_OK) == 0) { + unlink(path); + } else { + char dir[256]; + snprintf(dir, sizeof(dir), "%s/.cache/sxmo", home); + mkdir(dir, 0755); + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (fd >= 0) + close(fd); + } + } else if (strcmp(toggle_name, "autoscreenoff") == 0) { + const char *home = getenv("HOME"); + if (!home || !home[0]) + home = "/home/user"; + char path[256]; + snprintf(path, sizeof(path), "%s/.cache/sxmo/sxmo.noidle", home); + if (access(path, F_OK) == 0) { + unlink(path); + } else { + char dir[256]; + snprintf(dir, sizeof(dir), "%s/.cache/sxmo", home); + mkdir(dir, 0755); + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (fd >= 0) + close(fd); + } + spawn_detached_sh("sxmo_state.sh set unlock"); + } else if (strcmp(toggle_name, "bluetooth") == 0) { + int rc = system("doas sxmo_bluetoothtoggle.sh >/dev/null 2>&1"); + (void)rc; + spawn_detached_sh("sxmo_hook_statusbar.sh bluetooth"); + } +} + +static void +finish_client_session(int exit_code, const char *selected_line) +{ + char outbuf[BUFSIZ + 4]; + size_t outlen = 0; + + outbuf[0] = (char)(exit_code & 0xff); + outlen = 1; + if (exit_code == 0 && selected_line) { + size_t slen = strlen(selected_line); + if (slen > BUFSIZ) + slen = BUFSIZ; + memcpy(outbuf + 1, selected_line, slen); + outbuf[1 + slen] = '\n'; + outlen = 1 + slen + 1; + } + + hide_menu(); + + if (active_client_fd >= 0) { + (void)write(active_client_fd, outbuf, outlen); + close(active_client_fd); + active_client_fd = -1; + } + menu_mode = MODE_NONE; +} + +static void +activate_selection(unsigned int ev_state) +{ + if (!daemon_mode) { + if (use_text_input) + puts((sel && (ev_state & ShiftMask)) ? sel->text : text); + else + puts((sel && !(ev_state & ShiftMask)) ? sel->text : text); + if (!(ev_state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + return; + } + + if (menu_mode == MODE_CLIENT) { + const char *out = use_text_input + ? ((sel && (ev_state & ShiftMask)) ? sel->text : text) + : ((sel && !(ev_state & ShiftMask)) ? sel->text : text); + finish_client_session(0, out ? out : ""); + return; + } + + if (menu_mode == MODE_BUILTIN) { + if (!sel || !sel->action) { + hide_menu(); + menu_mode = MODE_NONE; + return; + } + char act[512]; + snprintf(act, sizeof(act), "%s", sel->action); + int loop = sel->loop; + int cur_idx = current_sel_index(); + Window target_win = prevfocuswin; + + if (strcmp(act, "@close") == 0 || strcmp(act, "quit") == 0) { + hide_menu(); + menu_mode = MODE_NONE; + return; + } + if (strncmp(act, "@menu:", 6) == 0) { + open_builtin_menu(act + 6, 0); + return; + } + if (strncmp(act, "@plugin:", 8) == 0) { + hide_menu(); + menu_mode = MODE_NONE; + trigger_plugin_open(act + 8); + return; + } + if (strncmp(act, "@toggle:", 8) == 0) { + handle_builtin_toggle(act + 8); + char cur_menu[64]; + snprintf(cur_menu, sizeof(cur_menu), "%s", current_builtin_menu); + open_builtin_menu(cur_menu, cur_idx); + return; + } + if (strncmp(act, "@key:", 5) == 0) { + if (loop) { + handle_synth_key_action(act + 5, target_win); + } else { + hide_menu(); + menu_mode = MODE_NONE; + handle_synth_key_action(act + 5, target_win); + } + return; + } + + if (loop) { + spawn_detached_sh(act); + char cur_menu[64]; + snprintf(cur_menu, sizeof(cur_menu), "%s", current_builtin_menu); + open_builtin_menu(cur_menu, cur_idx); + } else { + hide_menu(); + menu_mode = MODE_NONE; + spawn_detached_sh(act); + } + } +} + +static void +cancel_session(void) +{ + if (!daemon_mode) { + cleanup(); + exit(1); + } + if (menu_mode == MODE_CLIENT) { + finish_client_session(1, NULL); + } else { + hide_menu(); + menu_mode = MODE_NONE; + } +} + +static void +spawn_xdrawer_and_close(void) +{ + clear_softkey_state(); + write_softkey_suppress(dmenu_now_ms() + 500); + if (!daemon_mode) { + cleanup(); + trigger_plugin_open("xdrawer"); + exit(1); + } + if (menu_mode == MODE_CLIENT) { + finish_client_session(1, NULL); + } else { + hide_menu(); + menu_mode = MODE_NONE; + } + trigger_plugin_open("xdrawer"); +} + +static void +keypress(XKeyEvent *ev) +{ + char buf[32]; + int len; + KeySym ksym; + Status status; + + ksym = NoSymbol; + if (xic && ev->keycode == 0) + len = Xutf8LookupString(xic, ev, buf, sizeof buf, &ksym, &status); + else { + len = XLookupString(ev, buf, sizeof buf, &ksym, NULL); + status = (len > 0 && ksym != NoSymbol) ? XLookupBoth : + (len > 0) ? XLookupChars : + (ksym != NoSymbol) ? XLookupKeySym : XLookupNone; + } + switch (status) { + default: + return; + case XLookupChars: + goto insert; + case XLookupKeySym: + case XLookupBoth: + break; + } + + if (ev->state & ControlMask) { + switch(ksym) { + case XK_a: ksym = XK_Home; break; + case XK_b: ksym = XK_Left; break; + case XK_c: ksym = XK_Escape; break; + case XK_d: ksym = XK_Delete; break; + case XK_e: ksym = XK_End; break; + case XK_f: ksym = XK_Right; break; + case XK_g: ksym = XK_Escape; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_i: ksym = XK_Tab; break; + case XK_j: + case XK_J: + case XK_m: + case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; + case XK_n: ksym = XK_Down; break; + case XK_p: ksym = XK_Up; break; + + case XK_k: + text[cursor] = '\0'; + match(); + break; + case XK_u: + insert(NULL, 0 - cursor); + break; + case XK_w: + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + break; + case XK_y: + case XK_Y: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_Left: + movewordedge(-1); + goto draw; + case XK_Right: + movewordedge(+1); + goto draw; + case XK_Return: + case XK_KP_Enter: + break; + case XK_bracketleft: + cancel_session(); + return; + default: + return; + } + } else if (ev->state & Mod1Mask) { + switch(ksym) { + case XK_b: + movewordedge(-1); + goto draw; + case XK_f: + movewordedge(+1); + goto draw; + case XK_g: ksym = XK_Home; break; + case XK_G: ksym = XK_End; break; + case XK_h: ksym = XK_Up; break; + case XK_j: ksym = XK_Next; break; + case XK_k: ksym = XK_Prior; break; + case XK_l: ksym = XK_Down; break; + default: + return; + } + } + + switch(ksym) { + default: +insert: + if (!iscntrl((unsigned char)*buf)) + insert(buf, len); + break; + case XK_Delete: + if (text[cursor] == '\0') + return; + cursor = nextrune(+1); + /* fallthrough */ + case XK_BackSpace: + if (cursor == 0) + return; + insert(NULL, nextrune(-1) - cursor); + break; + case XK_End: + if (text[cursor] != '\0') { + cursor = strlen(text); + break; + } + if (next) { + curr = matchend; + calcoffsets(); + curr = prev; + calcoffsets(); + while (next && (curr = curr->right)) + calcoffsets(); + } + sel = matchend; + break; + case XK_Escape: + case XF86XK_PowerOff: + cancel_session(); + return; + case XF86XK_Back: + case XF86XK_KbdLcdMenu2: + return; + case XK_Home: + if (sel == matches) { + cursor = 0; + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_Left: + if (cursor > 0 && (!sel || !sel->left || lines > 0)) { + cursor = nextrune(-1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XF86XK_AudioRaiseVolume: + case XK_Up: + if (sel && sel->left) { + sel = sel->left; + if (sel->right == curr) { + curr = prev; + calcoffsets(); + } + } else if (wrap_around) { + curr = matchend; + calcoffsets(); + curr = prev; + calcoffsets(); + while (next && (curr = curr->right)) + calcoffsets(); + sel = matchend; + } + break; + case XK_Next: + if (!next) + return; + sel = curr = next; + calcoffsets(); + break; + case XK_Prior: + if (!prev) + return; + sel = curr = prev; + calcoffsets(); + break; + case XK_Menu: + case XF86XK_KbdLcdMenu1: + return; + case XF86XK_Phone: + case XF86XK_PickupPhone: + case XK_Return: + case XK_KP_Enter: + activate_selection(ev->state); + if (!menu_active) + return; + break; + case XK_Right: + if (text[cursor] != '\0') { + cursor = nextrune(+1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XF86XK_AudioLowerVolume: + case XK_Down: + if (sel && sel->right) { + sel = sel->right; + if (sel == next) { + curr = next; + } + } else if (wrap_around) { + sel = curr = matches; + } + calcoffsets(); + break; + case XK_Tab: + if (!sel) + return; + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + cursor = strlen(text); + match(); + break; + } + +draw: + if (menu_active || !daemon_mode) + drawmenu(); +} + +static void +motionnotify(XEvent *e) +{ + struct item *item; + XMotionEvent *ev = &e->xmotion; + int y = 0, h = bh; + char *pageup = "Page up", *pagedown = "Page down"; + char *gotop = "Go to top", *gobottom = "Go to bottom"; + char *alreadytop = "Already at top", *alreadybottom = "Already at bottom"; + + if (ev->window != win) + return; + if (ev->state & ~Button1Mask) + return; + + if (lines > 0) { + y = bh; + for (item = curr; item != next; item = item->right) { + if (ev->y >= y && ev->y < (y + item_bh)) { + sel = item; + drawmenu(); + return; + } + y += item_bh; + } + if (ev->y > mh || ev->y < 0) { + if (ev->y >= mh && ev->y <= (mh + bh * scrollregion)) { + nav->text = next ? pagedown : alreadybottom; + drw_setscheme(drw, scheme[SchemeSel]); + y = mh - bh; + } else if (ev->y >= (mh + bh * scrollregion) && ev->y < (mh + bh * scrollregion * 2)) { + nav->text = next ? gobottom : alreadybottom; + drw_setscheme(drw, scheme[SchemeSel]); + y = mh - bh; + } else if (ev->y >= (0 - h * scrollregion) && ev->y <= 0) { + nav->text = curr != matches ? pageup : alreadytop; + drw_setscheme(drw, scheme[SchemeSel]); + y = 0 + bh; + } else if (ev->y >= (0 - h * scrollregion * 2) && ev->y <= (0 - h * scrollregion)) { + nav->text = curr != matches ? gotop : alreadytop; + drw_setscheme(drw, scheme[SchemeSel]); + y = 0 + bh; + } else { + drawmenu(); + return; + } + drw_setscheme(drw, scheme[SchemeOut]); + drw_text(drw, 0, y, mw, bh, lrpad / 2, nav->text, 0); + drw_map(drw, win, 0, 0, mw, mh); + XFlush(dpy); + return; + } + } +} + +static void +buttonpress(XEvent *e) +{ + struct item *item; + XButtonPressedEvent *ev = &e->xbutton; + int x = 0, y = 0, h = bh, w; + + if (ev->window != win) + return; + + if (ev->button == Button3) { + cancel_session(); + return; + } + + if (prompt && *prompt) + x += promptw; + + w = (lines > 0 || !matches) ? mw - x : inputw; + + 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; + } + if (ev->button == Button2) { + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + drawmenu(); + return; + } + if (ev->y >= (0 - h * scrollregion * 2) && ev->y <= (0 - h * scrollregion)) { + sel = curr = matches; + calcoffsets(); + drawmenu(); + return; + } + if ((ev->button == Button4 && prev) || (ev->y >= (0 - h * scrollregion) && ev->y <= 0)) { + sel = curr = prev; + calcoffsets(); + drawmenu(); + return; + } + if ((ev->button == Button5 && next) || (ev->y >= mh && ev->y <= (mh + bh * scrollregion))) { + if (next) + sel = curr = next; + calcoffsets(); + drawmenu(); + return; + } + if (ev->y >= (mh + bh * scrollregion) && ev->y < (mh + bh * scrollregion * 2)) { + if (next) { + curr = matchend; + calcoffsets(); + curr = prev; + calcoffsets(); + while (next && (curr = curr->right)) + calcoffsets(); + } + sel = matchend; + drawmenu(); + return; + } + + if (ev->button != Button1) + return; + if (ev->state & ~ControlMask & ~Button1Mask) + return; + if (lines > 0) { + y = bh; + for (item = curr; item != next; item = item->right) { + if (ev->y >= y && ev->y < (y + item_bh)) { + sel = item; + activate_selection(ev->state); + return; + } + y += item_bh; + } + } else if (matches) { + x += inputw; + w = TEXTW("<"); + if (prev && curr->left) { + if (ev->x >= x && ev->x <= x + w) { + sel = curr = prev; + calcoffsets(); + drawmenu(); + return; + } + } + for (item = curr; item != next; item = item->right) { + x += w; + w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); + if (ev->x >= x && ev->x <= x + w) { + sel = item; + activate_selection(ev->state); + return; + } + } + w = TEXTW(">"); + x = mw - w; + if (next && ev->x >= x && ev->x <= x + w) { + sel = curr = next; + calcoffsets(); + drawmenu(); + return; + } + } +} + +static void +paste(void) +{ + char *p, *q; + int di; + unsigned long dl; + Atom da; + + if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, + utf8, &da, &di, &dl, &dl, (unsigned char **)&p) + == Success && p) { + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); + } + drawmenu(); +} + +static void +readstdin(void) +{ + char buf[sizeof text], *p; + size_t i, imax = 0; + unsigned int tmpmax = 0; + + free_items(); + for (i = 0; fgets(buf, sizeof buf, stdin); i++) { + if ((p = strchr(buf, '\n'))) + *p = '\0'; + add_menu_item(buf, NULL, 0); + if (lines == 0 && !centered) { + drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); + if (tmpmax > (unsigned int)inputw) { + inputw = tmpmax; + imax = i; + } + } + } + inputw = (items && items_count > 0 && lines == 0 && !centered) ? TEXTW(items[imax].text) : 0; +} + +/* Built-in Sxmo Menu Builders */ +static int +bt_is_unblocked_fast(void) +{ + DIR *d = opendir("/sys/class/rfkill"); + if (!d) + return 0; + struct dirent *ent; + int unblocked = 0; + while ((ent = readdir(d)) != NULL) { + if (strncmp(ent->d_name, "rfkill", 6) != 0) + continue; + char path[256], rtype[32] = "", soft[16] = "0", hard[16] = "0"; + snprintf(path, sizeof(path), "/sys/class/rfkill/%s/type", ent->d_name); + if (!read_first_line(path, rtype, sizeof(rtype)) || strcmp(rtype, "bluetooth") != 0) + continue; + snprintf(path, sizeof(path), "/sys/class/rfkill/%s/soft", ent->d_name); + read_first_line(path, soft, sizeof(soft)); + snprintf(path, sizeof(path), "/sys/class/rfkill/%s/hard", ent->d_name); + read_first_line(path, hard, sizeof(hard)); + if (strcmp(soft, "0") == 0 && strcmp(hard, "0") == 0) + unblocked = 1; + break; + } + closedir(d); + return unblocked; +} + +static int +count_notifications(void) +{ + const char *ndir = getenv("SXMO_NOTIFDIR"); + char default_dir[256]; + if (!ndir || !ndir[0]) { + const char *home = getenv("HOME"); + if (!home || !home[0]) + home = "/home/user"; + snprintf(default_dir, sizeof(default_dir), "%s/.local/share/sxmo/notifications", home); + ndir = default_dir; + } + DIR *d = opendir(ndir); + if (!d) + return 0; + int n = 0; + struct dirent *ent; + while ((ent = readdir(d)) != NULL) { + if (ent->d_name[0] == '.') + continue; + char fpath[512]; + snprintf(fpath, sizeof(fpath), "%s/%s", ndir, ent->d_name); + struct stat st; + if (stat(fpath, &st) == 0 && S_ISREG(st.st_mode)) + n++; + } + closedir(d); + return n; +} + +static int +has_tracked_calls(void) +{ + const char *rdir = getenv("XDG_RUNTIME_DIR"); + char cdir[256]; + snprintf(cdir, sizeof(cdir), "%s/sxmo_calls", (rdir && rdir[0]) ? rdir : "/run/user/10000"); + DIR *d = opendir(cdir); + if (!d) + return 0; + struct dirent *ent; + int found = 0; + while ((ent = readdir(d)) != NULL) { + if (ent->d_name[0] != '.') { + found = 1; + break; + } + } + closedir(d); + return found; +} + +static void +trim_inplace(char *s) +{ + if (!s) + return; + char *p = s; + while (*p && isspace((unsigned char)*p)) + p++; + if (p != s) + memmove(s, p, strlen(p) + 1); + size_t len = strlen(s); + while (len > 0 && isspace((unsigned char)s[len - 1])) + s[--len] = '\0'; +} + +static void +add_parsed_choice_line(const char *line) +{ + if (!line || !line[0]) + return; + char buf[1024]; + snprintf(buf, sizeof(buf), "%s", line); + char *c1 = strchr(buf, '^'); + if (!c1) + return; + *c1 = '\0'; + char *c2 = strchr(c1 + 1, '^'); + if (!c2) + return; + *c2 = '\0'; + char *label = buf; + char *loop_str = c1 + 1; + char *cmd = c2 + 1; + trim_inplace(label); + trim_inplace(loop_str); + trim_inplace(cmd); + if (!label[0] || !cmd[0]) + return; + int loop = (strchr(loop_str, '1') != NULL) ? 1 : 0; + if (strncmp(cmd, "sxmo_appmenu.sh ", 16) == 0) { + const char *sub = cmd + 16; + while (*sub && isspace((unsigned char)*sub)) + sub++; + char act[128]; + snprintf(act, sizeof(act), "@menu:%s", sub); + add_menu_item(label, act, 1); + } else if (strcmp(cmd, "quit") == 0) { + add_menu_item(label, "@close", 0); + } else { + add_menu_item(label, cmd, loop); + } +} + +static void +append_active_call_entries(void) +{ + if (!has_tracked_calls()) + return; + FILE *fp = popen("sxmo_modemcall.sh list_active_calls 2>/dev/null", "r"); + if (!fp) + return; + char line[512]; + int shown_incall = 0; + while (fgets(line, sizeof(line), fp)) { + char *nl = strpbrk(line, "\r\n"); + if (nl) + *nl = '\0'; + if (!line[0]) + continue; + if (strstr(line, "(ringing-in)")) { + char callid[64] = ""; + char *sp = strchr(line, ' '); + if (sp) + *sp = '\0'; + char *slash = strrchr(line, '/'); + snprintf(callid, sizeof(callid), "%s", slash ? (slash + 1) : line); + char label[256], act[256]; + snprintf(label, sizeof(label), "%s Incoming call %s", ICON_PHN, callid); + snprintf(act, sizeof(act), "sxmo_jobs.sh start incall_menu sxmo_modemcall.sh incoming_call_menu %s", callid); + add_menu_item(label, act, 0); + } else if (!shown_incall) { + shown_incall = 1; + char label[128]; + snprintf(label, sizeof(label), "%s Incall Menu", ICON_PHN); + add_menu_item(label, "sxmo_jobs.sh start incall_menu sxmo_modemcall.sh incall_menu", 0); + } + } + pclose(fp); +} + +static void +load_cache_or_hook(const char *cache_name, const char *hook_cmd) +{ + const char *rdir = getenv("XDG_RUNTIME_DIR"); + char path[256]; + snprintf(path, sizeof(path), "%s/%s", (rdir && rdir[0]) ? rdir : "/run/user/10000", cache_name); + FILE *fp = fopen(path, "r"); + if (!fp) { + snprintf(path, sizeof(path), "/tmp/%s", cache_name); + fp = fopen(path, "r"); + } + if (!fp) { + fp = popen(hook_cmd, "r"); + if (!fp) + return; + char line[1024]; + while (fgets(line, sizeof(line), fp)) + add_parsed_choice_line(line); + pclose(fp); + return; + } + char line[1024]; + while (fgets(line, sizeof(line), fp)) + add_parsed_choice_line(line); + fclose(fp); +} + +static void +open_builtin_menu(const char *menu_name, int keep_idx) +{ + char label[256], cmd[512]; + + if (!menu_name || !menu_name[0] || strcmp(menu_name, "ctx") == 0) { + const char *rdir = getenv("XDG_RUNTIME_DIR"); + char fpath[256]; + snprintf(fpath, sizeof(fpath), "%s/sxmo.focusedwin", (rdir && rdir[0]) ? rdir : "/run/user/10000"); + FILE *fp = fopen(fpath, "r"); + char cls[128] = "", ttl[256] = ""; + if (fp) { + if (fgets(cls, sizeof(cls), fp)) + trim_inplace(cls); + if (fgets(ttl, sizeof(ttl), fp)) + trim_inplace(ttl); + fclose(fp); + } + if (!cls[0] || strcmp(cls, "sys") == 0) { + menu_name = "sys"; + } else if (strcmp(cls, "st") == 0 || strncmp(cls, "st-", 3) == 0) { + if (strstr(ttl, "nano")) + menu_name = "nano"; + else + menu_name = "st"; + } else { + hide_menu(); + menu_mode = MODE_NONE; + snprintf(cmd, sizeof(cmd), "sxmo_appmenu.sh --fallback '%s' '%s'", cls, ttl); + spawn_detached_sh(cmd); + return; + } + } + + snprintf(current_builtin_menu, sizeof(current_builtin_menu), "%s", menu_name); + menu_mode = MODE_BUILTIN; + + if (strcmp(menu_name, "sys") == 0) { + reset_session_defaults(1, "Sys"); + append_active_call_entries(); + int nnotifs = count_notifications(); + if (nnotifs > 0) { + snprintf(label, sizeof(label), "%s Notifications (%d)", ICON_BEL, nnotifs); + add_menu_item(label, "sxmo_notifs.sh menu", 0); + } + const char *sh = getenv("SHELL"); + if (!sh || !sh[0]) + sh = "/bin/sh"; + snprintf(label, sizeof(label), "%s Terminal", ICON_TRM); + snprintf(cmd, sizeof(cmd), "st -e %s", sh); + add_menu_item(label, cmd, 0); + + snprintf(label, sizeof(label), "%s Apps", ICON_GRD); + add_menu_item(label, "@menu:applications", 1); + + snprintf(label, sizeof(label), "%s Scripts", ICON_GRD); + add_menu_item(label, "@menu:scripts", 1); + + snprintf(label, sizeof(label), "%s Files", ICON_DIR); + add_menu_item(label, "@plugin:files", 0); + + snprintf(label, sizeof(label), "%s Dialer", ICON_PHN); + add_menu_item(label, "@plugin:dialer", 0); + + snprintf(label, sizeof(label), "%s Call Log", ICON_PHL); + add_menu_item(label, "@plugin:calllog", 0); + + snprintf(label, sizeof(label), "%s Texts", ICON_MSG); + add_menu_item(label, "@plugin:sms", 0); + + snprintf(label, sizeof(label), "%s Contacts", ICON_USR); + add_menu_item(label, "@plugin:contacts", 0); + + if (bt_is_unblocked_fast()) { + snprintf(label, sizeof(label), "%s Bluetooth", ICON_BTH); + add_menu_item(label, "sxmo_bluetoothmenu.sh", 0); + } + + snprintf(label, sizeof(label), "%s Camera", ICON_CAM); + add_menu_item(label, "@plugin:camera", 0); + + char bl_buf[32] = ""; + if (read_first_line("/sys/class/leds/white:torch/brightness", bl_buf, sizeof(bl_buf)) || + read_first_line("/sys/class/leds/white:flash/brightness", bl_buf, sizeof(bl_buf))) { + int bl = atoi(bl_buf); + snprintf(label, sizeof(label), "%s Flashlight %s", ICON_FLL, bl > 0 ? ICON_TON : ICON_TOF); + add_menu_item(label, "@toggle:flashlight", 1); + } + + snprintf(label, sizeof(label), "%s Networks", ICON_NET); + add_menu_item(label, "sxmo_networks.sh", 0); + + snprintf(label, sizeof(label), "%s Audio", ICON_MUS); + add_menu_item(label, "sxmo_audio.sh", 0); + + snprintf(label, sizeof(label), "%s Modem", ICON_PHN); + add_menu_item(label, "@menu:modem", 1); + + snprintf(label, sizeof(label), "%s Windows", ICON_WIN); + add_menu_item(label, "sxmo_wmmenu.sh", 0); + + snprintf(label, sizeof(label), "%s Config", ICON_CFG); + add_menu_item(label, "@menu:config", 1); + + snprintf(label, sizeof(label), "%s Power", ICON_PWR); + add_menu_item(label, "@menu:power", 1); + + snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS); + add_menu_item(label, "@close", 0); + } else if (strcmp(menu_name, "applications") == 0) { + reset_session_defaults(1, "Apps"); + append_active_call_entries(); + load_cache_or_hook("sxmo_apps.cache", "sxmo_hook_apps.sh"); + snprintf(label, sizeof(label), "%s System Menu", ICON_MNU); + add_menu_item(label, "@menu:sys", 1); + snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS); + add_menu_item(label, "@close", 0); + } else if (strcmp(menu_name, "scripts") == 0) { + reset_session_defaults(1, "Scripts"); + append_active_call_entries(); + load_cache_or_hook("sxmo_scripts.cache", "sxmo_hook_scripts.sh"); + snprintf(label, sizeof(label), "%s System Menu", ICON_MNU); + add_menu_item(label, "@menu:sys", 1); + snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS); + add_menu_item(label, "@close", 0); + } else if (strcmp(menu_name, "modem") == 0) { + reset_session_defaults(1, "Modem"); + append_active_call_entries(); + snprintf(label, sizeof(label), "%s Modem PIN", ICON_PLK); + add_menu_item(label, "sxmo_unlocksim.sh", 0); + snprintf(label, sizeof(label), "%s Modem Info", ICON_INF); + add_menu_item(label, "sxmo_modeminfo.sh", 0); + snprintf(label, sizeof(label), "%s Modem Log", ICON_PHL); + add_menu_item(label, "sxmo_modemlog.sh", 0); + snprintf(label, sizeof(label), "%s System Menu", ICON_MNU); + add_menu_item(label, "@menu:sys", 1); + snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS); + add_menu_item(label, "@close", 0); + } else if (strcmp(menu_name, "config") == 0) { + reset_session_defaults(1, "Config"); + append_active_call_entries(); + const char *home = getenv("HOME"); + if (!home || !home[0]) + home = "/home/user"; + char pbuf[256]; + + snprintf(label, sizeof(label), "%s Brightness", ICON_ARU); + add_menu_item(label, "@toggle:bright_up", 1); + snprintf(label, sizeof(label), "%s Brightness", ICON_ARD); + add_menu_item(label, "@toggle:bright_down", 1); + snprintf(label, sizeof(label), "%s Toggle Bar", ICON_CFG); + add_menu_item(label, "sxmo_wm.sh togglebar", 0); + + snprintf(label, sizeof(label), "%s Bluetooth %s", ICON_BTH, bt_is_unblocked_fast() ? ICON_TON : ICON_TOF); + add_menu_item(label, "@toggle:bluetooth", 1); + + snprintf(label, sizeof(label), "%s Change Timezone", ICON_CLK); + add_menu_item(label, "sxmo_timezonechange.sh", 0); + + snprintf(pbuf, sizeof(pbuf), "%s/.cache/sxmo/sxmo.nosuspend", home); + snprintf(label, sizeof(label), "%s Auto-suspend %s", ICON_ZZZ, (access(pbuf, F_OK) == 0) ? ICON_TOF : ICON_TON); + add_menu_item(label, "@toggle:autosuspend", 1); + + snprintf(pbuf, sizeof(pbuf), "%s/.cache/sxmo/sxmo.noidle", home); + snprintf(label, sizeof(label), "%s Auto-screen-off %s", ICON_ZZZ, (access(pbuf, F_OK) == 0) ? ICON_TOF : ICON_TON); + add_menu_item(label, "@toggle:autoscreenoff", 1); + + snprintf(label, sizeof(label), "%s Hooks", ICON_TRM); + add_menu_item(label, "sxmo_hookmenu.sh", 0); + snprintf(label, sizeof(label), "%s Suspend Blockers", ICON_CFG); + add_menu_item(label, "sxmo_terminal.sh sxmo_debug_suspend.sh", 0); + snprintf(label, sizeof(label), "%s Log", ICON_INF); + snprintf(cmd, sizeof(cmd), "sxmo_terminal.sh tail -n 100 -f %s/sxmo.log", home); + add_menu_item(label, cmd, 0); + snprintf(label, sizeof(label), "%s Version", ICON_INF); + add_menu_item(label, "sxmo_terminal.sh sxmo_version.sh --block", 0); + + snprintf(label, sizeof(label), "%s System Menu", ICON_MNU); + add_menu_item(label, "@menu:sys", 1); + snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS); + add_menu_item(label, "@close", 0); + } else if (strcmp(menu_name, "power") == 0) { + reset_session_defaults(1, "Power"); + append_active_call_entries(); + snprintf(label, sizeof(label), "%s Lock", ICON_LCK); + add_menu_item(label, "sxmo_state.sh set lock", 0); + snprintf(label, sizeof(label), "%s Lock (Screen off)", ICON_LCK); + add_menu_item(label, "sxmo_state.sh set screenoff", 0); + snprintf(label, sizeof(label), "%s Logout", ICON_OUT); + add_menu_item(label, "@menu:confirm_logout", 1); + snprintf(label, sizeof(label), "%s Reboot", ICON_RLD); + add_menu_item(label, "@menu:confirm_reboot", 1); + snprintf(label, sizeof(label), "%s Poweroff", ICON_PWR); + add_menu_item(label, "@menu:confirm_poweroff", 1); + snprintf(label, sizeof(label), "%s System Menu", ICON_MNU); + add_menu_item(label, "@menu:sys", 1); + snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS); + add_menu_item(label, "@close", 0); + } else if (strcmp(menu_name, "confirm_logout") == 0) { + reset_session_defaults(1, "Confirm Logout"); + add_menu_item("Yes", "sxmo_power.sh logout", 0); + add_menu_item("No", "@menu:power", 1); + } else if (strcmp(menu_name, "confirm_reboot") == 0) { + reset_session_defaults(1, "Confirm Reboot"); + add_menu_item("Yes", "sxmo_power.sh reboot", 0); + add_menu_item("No", "@menu:power", 1); + } else if (strcmp(menu_name, "confirm_poweroff") == 0) { + reset_session_defaults(1, "Confirm Poweroff"); + add_menu_item("Yes", "sxmo_power.sh poweroff", 0); + add_menu_item("No", "@menu:power", 1); + } else if (strcmp(menu_name, "st") == 0) { + reset_session_defaults(1, "St"); + append_active_call_entries(); + snprintf(label, sizeof(label), "%s New Terminal", ICON_TRM); + add_menu_item(label, "sxmo_terminal.sh", 0); + snprintf(label, sizeof(label), "%s Close Terminal", ICON_CLS); + add_menu_item(label, "sxmo_killwindow.sh", 0); + snprintf(label, sizeof(label), "%s Type complete", ICON_ITM); + add_menu_item(label, "@key:ctrl_shift:u", 0); + snprintf(label, sizeof(label), "%s Toggle Selmode", ICON_ITM); + add_menu_item(label, "@key:ctrl_shift:s", 0); + snprintf(label, sizeof(label), "%s Copy complete", ICON_CPY); + add_menu_item(label, "@key:ctrl_shift:i", 0); + snprintf(label, sizeof(label), "%s Copy selection", ICON_CPY); + add_menu_item(label, "@key:ctrl_shift:c", 0); + snprintf(label, sizeof(label), "%s Paste", ICON_PST); + add_menu_item(label, "@key:ctrl_shift:v", 0); + snprintf(label, sizeof(label), "%s Zoom in", ICON_ZMI); + add_menu_item(label, "@key:ctrl_shift:Prior", 1); + snprintf(label, sizeof(label), "%s Zoom out", ICON_ZMO); + add_menu_item(label, "@key:ctrl_shift:Next", 1); + snprintf(label, sizeof(label), "%s Scroll up", ICON_ARU); + add_menu_item(label, "@key:ctrl_shift:b", 1); + snprintf(label, sizeof(label), "%s Scroll down", ICON_ARD); + add_menu_item(label, "@key:ctrl_shift:f", 1); + snprintf(label, sizeof(label), "%s Invert", ICON_A2X); + add_menu_item(label, "@key:ctrl_shift:x", 1); + snprintf(label, sizeof(label), "%s Hotkeys", ICON_KBD); + add_menu_item(label, "@menu:sthotkeys", 1); + snprintf(label, sizeof(label), "%s System Menu", ICON_MNU); + add_menu_item(label, "@menu:sys", 1); + snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS); + add_menu_item(label, "@close", 0); + } else if (strcmp(menu_name, "nano") == 0) { + reset_session_defaults(1, "Nano"); + append_active_call_entries(); + snprintf(label, sizeof(label), "%s Scroll up", ICON_ARU); + add_menu_item(label, "@key:none:Prior", 1); + snprintf(label, sizeof(label), "%s Scroll down", ICON_ARD); + add_menu_item(label, "@key:none:Next", 1); + snprintf(label, sizeof(label), "%s Save", ICON_SAV); + add_menu_item(label, "@key:ctrl:o", 0); + snprintf(label, sizeof(label), "%s Quit", ICON_CLS); + add_menu_item(label, "@key:ctrl:x", 0); + snprintf(label, sizeof(label), "%s Paste", ICON_PST); + add_menu_item(label, "@key:ctrl:u", 0); + snprintf(label, sizeof(label), "%s Type complete", ICON_ITM); + add_menu_item(label, "@key:ctrl_shift:u", 0); + snprintf(label, sizeof(label), "%s Copy complete", ICON_CPY); + add_menu_item(label, "@key:ctrl_shift:i", 0); + snprintf(label, sizeof(label), "%s Zoom in", ICON_ZMI); + add_menu_item(label, "@key:none:Prior", 1); + snprintf(label, sizeof(label), "%s Zoom out", ICON_ZMO); + add_menu_item(label, "@key:none:Next", 1); + snprintf(label, sizeof(label), "%s Terminal menu", ICON_MNU); + add_menu_item(label, "@menu:st", 1); + snprintf(label, sizeof(label), "%s System Menu", ICON_MNU); + add_menu_item(label, "@menu:sys", 1); + snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS); + add_menu_item(label, "@close", 0); + } else if (strcmp(menu_name, "sthotkeys") == 0) { + reset_session_defaults(1, "St"); + append_active_call_entries(); + add_menu_item("Send Ctrl-C", "@key:ctrl:c", 0); + add_menu_item("Send Ctrl-Z", "@key:ctrl:z", 0); + add_menu_item("Send Ctrl-L", "@key:ctrl:l", 0); + add_menu_item("Send Ctrl-D", "@key:ctrl:d", 0); + add_menu_item("Send Ctrl-A", "@key:ctrl:a", 0); + add_menu_item("Send Ctrl-B", "@key:ctrl:b", 0); + add_menu_item("Send ESC:w", "sxmo_type.sh -s 100 -k Escape -s 150 -M Shift -k semicolon -m Shift -k w -k Return", 0); + add_menu_item("Send ESC:wq", "sxmo_type.sh -s 100 -k Escape -s 150 -M Shift -k semicolon -m Shift -k w -k q -k Return", 0); + add_menu_item("Send ESC:wq!", "sxmo_type.sh -s 100 -k Escape -s 150 -M Shift -k semicolon -m Shift -k q -k exclam -k Return", 0); + snprintf(label, sizeof(label), "%s System Menu", ICON_MNU); + add_menu_item(label, "@menu:sys", 1); + snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS); + add_menu_item(label, "@close", 0); + } else { + hide_menu(); + menu_mode = MODE_NONE; + snprintf(cmd, sizeof(cmd), "sxmo_appmenu.sh --fallback '%s'", menu_name); + spawn_detached_sh(cmd); + return; + } + + initialindex = keep_idx; + show_menu(); +} + +static void +close_other_active_plugins(void) +{ + static const struct { + const char *pid_file; + const char *fifo_path; + } plugins[] = { + { "xdrawer.pid", "/tmp/dwm_plugin_xdrawer.fifo" }, + { "xdialer.pid", "/tmp/dwm_plugin_dialer.fifo" }, + { "xcalllog.pid", "/tmp/dwm_plugin_calllog.fifo" }, + { "xcontacts.pid", "/tmp/dwm_plugin_contacts.fifo" }, + { "xsms.pid", "/tmp/dwm_plugin_sms.fifo" }, + { "xfiles.pid", "/tmp/dwm_plugin_files.fifo" }, + { "xcamera.pid", "/tmp/dwm_plugin_camera.fifo" }, + }; + const char *rdir = getenv("XDG_RUNTIME_DIR"); + const char *base = (rdir && rdir[0]) ? rdir : "/run/user/10000"; + for (size_t i = 0; i < LENGTH(plugins); i++) { + char ppath[256]; + snprintf(ppath, sizeof(ppath), "%s/%s", base, plugins[i].pid_file); + if (access(ppath, F_OK) != 0) + snprintf(ppath, sizeof(ppath), "/tmp/%s", plugins[i].pid_file); + if (access(ppath, F_OK) == 0) { + int fd = open(plugins[i].fifo_path, O_WRONLY | O_NONBLOCK | O_CLOEXEC); + if (fd >= 0) { + (void)write(fd, "CLOSE\n", 6); + close(fd); + } + } + } +} + +static void +show_menu(void) +{ + const int bar_h = 24; + int target_y; + + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + if (lines > 0) { + lines = default_lines; + mh = avail_h_cached; + item_bh = (mh - bh) / (int)lines; + } else { + mh = bh; + item_bh = bh; + } + target_y = topbar ? bar_h : MAX(bar_h, (int)avail_h_cached + bar_h - (int)mh); + if (win_w != mw || win_h != mh || win_y != target_y) { + drw_resize(drw, mw, mh); + XMoveResizeWindow(dpy, win, win_x, target_y, mw, mh); + win_y = target_y; + win_w = mw; + win_h = mh; + } + + inputw = MIN(inputw, mw / 3); + if (xic && use_text_input) { + char *r = Xutf8ResetIC(xic); + if (r) + XFree(r); + } + match(); + + for (int i = 0; i < initialindex; i++) { + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + } + + soft_pending = 0; + soft_deadline = 0; + + if (!menu_active) { + close_other_active_plugins(); + prevfocuswin = None; + Window fw = None; + int rev = 0; + XGetInputFocus(dpy, &fw, &rev); + if (fw != None && fw != PointerRoot && fw != win) + prevfocuswin = fw; + + XEvent ev; + while (XPending(dpy)) + XNextEvent(dpy, &ev); + + read_softkey_state(&init_soft_mask, &init_soft_ms); + start_ignore = dmenu_now_ms() + 180; + initial_released = (query_trigger_keys_down() == 0); + menu_active = 1; + mark_menu_active(1); + drawmenu(); + XMapRaised(dpy, win); + drawmenu(); + grabkeyboard(); + if (!daemon_mode) + grabfocus(); + if (xic && use_text_input) + XSetICFocus(xic); + XFlush(dpy); + } else { + init_soft_mask = 0; + init_soft_ms = 0; + start_ignore = dmenu_now_ms() + 60; + initial_released = (query_trigger_keys_down() == 0); + XRaiseWindow(dpy, win); + drawmenu(); + if (xic && use_text_input) + XSetICFocus(xic); + XFlush(dpy); + } +} + +static void +hide_menu(void) +{ + if (!menu_active) { + free_items(); + return; + } + menu_active = 0; + mark_menu_active(0); + soft_pending = 0; + soft_deadline = 0; + + if (xic && use_text_input) { + char *r = Xutf8ResetIC(xic); + if (r) + XFree(r); + XUnsetICFocus(xic); + } + XUngrabKeyboard(dpy, CurrentTime); + XUnmapWindow(dpy, win); + XSetErrorHandler(ignore_xerror); + if (!daemon_mode && prevfocuswin && prevfocuswin != win && prevfocuswin != PointerRoot) { + XWindowAttributes wa; + if (XGetWindowAttributes(dpy, prevfocuswin, &wa) && wa.map_state == IsViewable) + XSetInputFocus(dpy, prevfocuswin, RevertToParent, CurrentTime); + } + prevfocuswin = None; + XSync(dpy, False); + + XEvent ev; + while (XPending(dpy)) + XNextEvent(dpy, &ev); + + free_items(); +} + +static void +handle_client_socket(int sock_fd) +{ + int cfd = accept(sock_fd, NULL, NULL); + if (cfd < 0) + return; + + size_t cap = 8192; + size_t len = 0; + char *buf = malloc(cap); + if (!buf) { + close(cfd); + return; + } + + for (;;) { + struct pollfd pfd = { cfd, POLLIN, 0 }; + int pr = poll(&pfd, 1, 5000); + if (pr <= 0) + break; + if (len + 4096 > cap) { + size_t ncap = cap * 2; + if (ncap > 1024 * 1024) + break; + char *nbuf = realloc(buf, ncap); + if (!nbuf) + break; + buf = nbuf; + cap = ncap; + } + ssize_t n = read(cfd, buf + len, cap - len - 1); + if (n < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + break; + } + if (n == 0) + break; + len += (size_t)n; + } + buf[len] = '\0'; + + if (len < sizeof(uint32_t)) { + free(buf); + close(cfd); + return; + } + + if (active_client_fd >= 0) { + char cancel_byte = 1; + (void)write(active_client_fd, &cancel_byte, 1); + close(active_client_fd); + active_client_fd = -1; + } + + size_t pos = 0; + uint32_t c_argc = 0; + memcpy(&c_argc, buf + pos, sizeof(uint32_t)); + pos += sizeof(uint32_t); + if (c_argc > 128) + c_argc = 128; + + char *c_argv[129]; + uint32_t actual_argc = 0; + for (uint32_t i = 0; i < c_argc; i++) { + if (pos + sizeof(uint32_t) > len) + break; + uint32_t slen = 0; + memcpy(&slen, buf + pos, sizeof(uint32_t)); + pos += sizeof(uint32_t); + if (pos + slen > len) + break; + c_argv[actual_argc] = malloc(slen + 1); + if (!c_argv[actual_argc]) + break; + memcpy(c_argv[actual_argc], buf + pos, slen); + c_argv[actual_argc][slen] = '\0'; + pos += slen; + actual_argc++; + } + + reset_session_defaults(0, NULL); + + for (uint32_t i = 1; i < actual_argc; i++) { + if (!strcmp(c_argv[i], "-b")) + topbar = 0; + else if (!strcmp(c_argv[i], "-f")) + ; + else if (!strcmp(c_argv[i], "-nc")) + centered = 0; + else if (!strcmp(c_argv[i], "-c")) + centered = 1; + else if (!strcmp(c_argv[i], "-i")) { + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if (!strcmp(c_argv[i], "-t")) + use_text_input = 1; + else if (!strcmp(c_argv[i], "-l") && i + 1 < actual_argc) + lines = atoi(c_argv[++i]); + else if (!strcmp(c_argv[i], "-H") && i + 1 < actual_argc) + ++i; + else if (!strcmp(c_argv[i], "-m") && i + 1 < actual_argc) + ++i; + else if (!strcmp(c_argv[i], "-p") && i + 1 < actual_argc) { + free(session_prompt); + session_prompt = strdup(c_argv[++i]); + prompt = session_prompt; + } else if ((!strcmp(c_argv[i], "-fn") || !strcmp(c_argv[i], "-nb") || + !strcmp(c_argv[i], "-nf") || !strcmp(c_argv[i], "-sb") || + !strcmp(c_argv[i], "-sf") || !strcmp(c_argv[i], "-w")) && + i + 1 < actual_argc) { + ++i; + } else if ((!strcmp(c_argv[i], "--index") || !strcmp(c_argv[i], "-I")) && i + 1 < actual_argc) + initialindex = atoi(c_argv[++i]); + else if (!strcmp(c_argv[i], "-idx") && i + 1 < actual_argc) + initialindex = atoi(c_argv[++i]) - 1; + } + + for (uint32_t i = 0; i < actual_argc; i++) + free(c_argv[i]); + + char *stdin_data = buf + pos; + size_t imax = 0, idx = 0; + unsigned int tmpmax = 0; + char *line = stdin_data; + while (line && *line) { + char *nl = strchr(line, '\n'); + if (nl) + *nl = '\0'; + add_menu_item(line, NULL, 0); + if (lines == 0 && !centered) { + drw_font_getexts(drw->fonts, line, strlen(line), &tmpmax, NULL); + if (tmpmax > (unsigned int)inputw) { + inputw = tmpmax; + imax = idx; + } + } + idx++; + if (!nl) + break; + line = nl + 1; + } + inputw = (items && items_count > 0 && lines == 0 && !centered) ? TEXTW(items[imax].text) : 0; + free(buf); + + active_client_fd = cfd; + menu_mode = MODE_CLIENT; + show_menu(); +} + +static void +handle_fifo_commands(int fifo_fd) +{ + char buf[512]; + ssize_t n = read(fifo_fd, buf, sizeof(buf) - 1); + if (n <= 0) + return; + buf[n] = '\0'; + + char *saveptr = NULL; + for (char *line = strtok_r(buf, "\r\n", &saveptr); line; line = strtok_r(NULL, "\r\n", &saveptr)) { + if (strcmp(line, "CLOSE") == 0) { + if (menu_active) + cancel_session(); + } else if (strcmp(line, "OPEN") == 0) { + if (active_client_fd >= 0) + finish_client_session(1, NULL); + open_builtin_menu("sys", 0); + } else if (strncmp(line, "OPEN ", 5) == 0) { + const char *arg = line + 5; + while (*arg && isspace((unsigned char)*arg)) + arg++; + if (active_client_fd >= 0) + finish_client_session(1, NULL); + open_builtin_menu(arg[0] ? arg : "sys", 0); + } + } +} + +static void +process_x11_events(void) +{ + XEvent ev; + + if (!initial_released && query_trigger_keys_down() == 0) + initial_released = 1; + + while (XPending(dpy)) { + if (XNextEvent(dpy, &ev)) + return; + if (!menu_active && daemon_mode) + continue; + if (!initial_released && query_trigger_keys_down() == 0) + initial_released = 1; + if (ev.type == KeyPress || ev.type == KeyRelease) { + KeySym ks = XLookupKeysym(&ev.xkey, 0); + int is_left = (ks == XK_Menu || ks == XF86XK_KbdLcdMenu1 || ev.xkey.keycode == 135); + int is_right = (ks == XF86XK_Back || ks == XF86XK_KbdLcdMenu2 || ev.xkey.keycode == 166); + int is_enter = (ks == XK_Return || ks == XK_KP_Enter || + ks == XF86XK_Phone || ks == XF86XK_PickupPhone || + ev.xkey.keycode == 36 || ev.xkey.keycode == 104); + if (is_left || is_right) { + long long now = dmenu_now_ms(); + int which = (is_left ? 1 : 0) | (is_right ? 2 : 0); + if (ev.type == KeyPress && init_soft_mask != 0 && + (init_soft_mask | which) == 3 && + (now - init_soft_ms) <= 320) { + spawn_xdrawer_and_close(); + if (!menu_active && daemon_mode) + return; + } + if (ev.type == KeyRelease) { + if (query_trigger_keys_down() == 0) + initial_released = 1; + continue; + } + if (!initial_released || now < start_ignore) + continue; + soft_pending |= which | query_softkeys_down(); + if (soft_pending == 3) { + spawn_xdrawer_and_close(); + if (!menu_active && daemon_mode) + return; + } + if (soft_deadline == 0) + soft_deadline = now + 30; + continue; + } + if (is_enter) { + if (ev.type == KeyRelease) { + if (query_trigger_keys_down() == 0) + initial_released = 1; + continue; + } + if (!initial_released || dmenu_now_ms() < start_ignore) + continue; + } + if (use_text_input && !issoftkey(ev.xkey.keycode)) { + ev.xkey.window = win; + if (XFilterEvent(&ev, None)) + continue; + } + } else if (use_text_input && XFilterEvent(&ev, None)) { + continue; + } + switch (ev.type) { + case MotionNotify: + motionnotify(&ev); + break; + case ButtonRelease: + buttonpress(&ev); + break; + case DestroyNotify: + if (ev.xdestroywindow.window != win) + break; + cleanup(); + exit(1); + case Expose: + if (ev.xexpose.count == 0 && (menu_active || !daemon_mode)) + drw_map(drw, win, 0, 0, mw, mh); + break; + case FocusIn: + if (menu_active || !daemon_mode) { + if (!daemon_mode && ev.xfocus.window != win) + grabfocus(); + if (xic && use_text_input) + XSetICFocus(xic); + } + break; + case KeyPress: + keypress(&ev.xkey); + break; + case SelectionNotify: + if (ev.xselection.property == utf8) + paste(); + break; + } + } +} + +static void +check_softkey_timeout(void) +{ + if (!soft_pending || soft_deadline <= 0) + return; + long long now = dmenu_now_ms(); + int hw_down = query_softkeys_down(); + soft_pending |= hw_down; + if (soft_pending == 3) { + spawn_xdrawer_and_close(); + return; + } + long long rem = soft_deadline - now; + if (rem <= 0) { + int action = soft_pending; + soft_pending = 0; + soft_deadline = 0; + clear_softkey_state(); + write_softkey_suppress(now + 250); + if (action == 1) { + activate_selection(0); + } else { + cancel_session(); + } + } +} + +static void +run_standalone(void) +{ + int xfd = ConnectionNumber(dpy); + soft_pending = 0; + soft_deadline = 0; + start_ignore = dmenu_now_ms() + 40; + initial_released = (query_softkeys_down() == 0); + read_softkey_state(&init_soft_mask, &init_soft_ms); + + for (;;) { + process_x11_events(); + check_softkey_timeout(); + if (soft_pending && soft_deadline > 0) { + long long rem = MAX(1LL, soft_deadline - dmenu_now_ms()); + struct pollfd pfd = { xfd, POLLIN, 0 }; + poll(&pfd, 1, (int)rem); + } else { + struct pollfd pfd = { xfd, POLLIN, 0 }; + poll(&pfd, 1, initial_released ? -1 : 40); + } + } +} + +static void +run_daemon_loop(int fifo_fd, int sock_fd) +{ + int xfd = ConnectionNumber(dpy); + + for (;;) { + process_x11_events(); + if (menu_active) + check_softkey_timeout(); + + int timeout_ms = -1; + if (menu_active) { + if (soft_pending && soft_deadline > 0) + timeout_ms = (int)MAX(1LL, soft_deadline - dmenu_now_ms()); + else if (!initial_released) + timeout_ms = 40; + } + + struct pollfd pfds[3]; + pfds[0].fd = xfd; + pfds[0].events = POLLIN; + pfds[0].revents = 0; + pfds[1].fd = fifo_fd; + pfds[1].events = POLLIN; + pfds[1].revents = 0; + pfds[2].fd = sock_fd; + pfds[2].events = POLLIN; + pfds[2].revents = 0; + + int pr = poll(pfds, 3, timeout_ms); + if (pr < 0) { + if (errno == EINTR) + continue; + break; + } + if (pfds[1].revents & POLLIN) + handle_fifo_commands(fifo_fd); + if (pfds[2].revents & POLLIN) + handle_client_socket(sock_fd); + } +} + +static void +setup(int map_immediately) +{ + int x, y, i, j; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; + XClassHint ch = {"dmenu", "dmenu"}; #ifdef XINERAMA - XineramaScreenInfo *info; - Window pw; - int a, di, n, area = 0; + XineramaScreenInfo *info; + Window pw; + int a, di, n, area = 0; #endif - /* init appearance */ - for (j = 0; j < SchemeLast; j++) { - scheme[j] = drw_scm_create(drw, (const char**)colors[j], 2); - } - for (j = 0; j < SchemeLast; ++j) { - free(colors[j][ColFg]); - free(colors[j][ColBg]); - } + for (j = 0; j < SchemeLast; j++) + scheme[j] = drw_scm_create(drw, (const char**)colors[j], 2); + for (j = 0; j < SchemeLast; ++j) { + free(colors[j][ColFg]); + free(colors[j][ColBg]); + } - clip = XInternAtom(dpy, "CLIPBOARD", False); - utf8 = XInternAtom(dpy, "UTF8_STRING", False); + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); - /* calculate menu geometry */ - const int bar_h = 24; - bh = drw->fonts->h + 2; - bh = MAX(bh, lineheight); - item_bh = bh; - lines = MAX(lines, 0); - mh = (lines + 1) * bh; - promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + const int bar_h = 24; + bh = drw->fonts->h + 2; + bh = MAX(bh, (int)lineheight); + item_bh = bh; + lines = MAX(lines, 0); + mh = (lines + 1) * bh; + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; #ifdef XINERAMA - i = 0; - if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { - int avail_h; - XGetInputFocus(dpy, &w, &di); - if (mon >= 0 && mon < n) - i = mon; - else if (w != root && w != PointerRoot && w != None) { - /* find top-level window containing current input focus */ - do { - if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) - XFree(dws); - } while (w != root && w != pw); - /* find xinerama screen with which the window intersects most */ - if (XGetWindowAttributes(dpy, pw, &wa)) - for (j = 0; j < n; j++) - if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { - area = a; - i = j; - } - } - /* no focused window is on screen, so use pointer location instead */ - if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) - for (i = 0; i < n; i++) - if (INTERSECT(x, y, 1, 1, info[i])) - break; + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { + int avail_h; + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while (w != root && w != pw); + if (XGetWindowAttributes(dpy, pw, &wa)) + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } + } + if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + for (i = 0; i < n; i++) + if (INTERSECT(x, y, 1, 1, info[i])) + break; - avail_h = MAX((int)bh * 2, (int)info[i].height - bar_h); - if (lines > 0) { - lines = MAX(1, (avail_h / (int)bh) - 1); - mh = avail_h; - item_bh = (mh - bh) / (int)lines; - } - x = info[i].x_org; - mw = info[i].width; - y = info[i].y_org + (topbar ? bar_h : MAX(bar_h, (int)info[i].height - (int)mh)); + avail_h = MAX((int)bh * 2, (int)info[i].height - bar_h); + avail_h_cached = avail_h; + default_lines = MAX(1, (avail_h / (int)bh) - 1); + if (daemon_mode || lines > 0) { + lines = default_lines; + mh = avail_h; + item_bh = (mh - bh) / (int)lines; + } + x = info[i].x_org; + mw = info[i].width; + y = info[i].y_org + (topbar ? bar_h : MAX(bar_h, (int)info[i].height - (int)mh)); - XFree(info); - } else + XFree(info); + } else #endif - { - int avail_h; - if (!XGetWindowAttributes(dpy, parentwin, &wa)) - die("could not get embedding window attributes: 0x%lx", - parentwin); + { + int avail_h; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", parentwin); - avail_h = MAX((int)bh * 2, (int)wa.height - bar_h); - if (lines > 0) { - lines = MAX(1, (avail_h / (int)bh) - 1); - mh = avail_h; - item_bh = (mh - bh) / (int)lines; - } - x = 0; - mw = wa.width; - y = topbar ? bar_h : MAX(bar_h, (int)wa.height - (int)mh); - } - inputw = MIN(inputw, mw/3); - match(); + avail_h = MAX((int)bh * 2, (int)wa.height - bar_h); + avail_h_cached = avail_h; + default_lines = MAX(1, (avail_h / (int)bh) - 1); + if (daemon_mode || lines > 0) { + lines = default_lines; + mh = avail_h; + item_bh = (mh - bh) / (int)lines; + } + x = 0; + mw = wa.width; + y = topbar ? bar_h : MAX(bar_h, (int)wa.height - (int)mh); + } + win_x = x; + win_y = y; + win_w = mw; + win_h = mh; + inputw = MIN(inputw, mw / 3); + match(); - /* create menu window */ - swa.override_redirect = managed ? False : True; - swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; - swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask; - win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width, - CopyFromParent, CopyFromParent, CopyFromParent, - CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); - XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); - XSetClassHint(dpy, win, &ch); + swa.override_redirect = managed ? False : True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + XSetClassHint(dpy, win, &ch); - drw_resize(drw, mw, mh); - calcoffsets(); + drw_resize(drw, mw, mh); + calcoffsets(); - for (i = 0; i < initialindex; i++) { - if (sel && sel->right && (sel = sel->right) == next) { - curr = next; - calcoffsets(); - } - } + /* Pre-warm font glyphs into offscreen pixmap */ + if (daemon_mode) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); + drw_text(drw, 0, 0, mw, bh, lrpad / 2, "Sys Apps Terminal", 0); + XSync(dpy, False); + } - XMapRaised(dpy, win); - drawmenu(); - XFlush(dpy); + for (i = 0; i < initialindex; i++) { + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + } - if (managed) { - XTextProperty prop; - char *windowtitle = prompt != NULL ? (char *)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); - } else { - grabkeyboard(); - grabfocus(); - } + XSetLocaleModifiers(""); + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) { + XSetLocaleModifiers("@im=local"); + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) { + XSetLocaleModifiers("@im="); + xim = XOpenIM(dpy, NULL, NULL, NULL); + } + } - /* input methods */ - XSetLocaleModifiers(""); - if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) { - XSetLocaleModifiers("@im=local"); - if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) { - XSetLocaleModifiers("@im="); - xim = XOpenIM(dpy, NULL, NULL, NULL); - } - } + if (xim) { + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + } - if (xim) { - xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, win, XNFocusWindow, win, NULL); - if (xic) - XSetICFocus(xic); - } + if (!(nav = malloc(sizeof *nav))) + die("cannot malloc %u bytes:", sizeof *nav); + nav->out = 1; - if (embed) { - XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); - if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { - for (i = 0; i < du && dws[i] != win; ++i) - XSelectInput(dpy, dws[i], FocusChangeMask); - XFree(dws); - } - grabfocus(); - } - - if (!(nav = malloc(sizeof *nav))) - die("cannot malloc %u bytes:", sizeof *nav); - nav->out = 1; + if (map_immediately) { + mark_menu_active(1); + XMapRaised(dpy, win); + drawmenu(); + XFlush(dpy); + if (managed) { + XTextProperty prop; + char *windowtitle = prompt != NULL ? (char *)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); + } else { + grabkeyboard(); + grabfocus(); + } + if (xic) + XSetICFocus(xic); + } } static void usage(void) { - fputs("usage: dmenu [-bfiv] [-l lines] [-H height] [-p prompt] [-fn font] [-m monitor]\n" - " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); - exit(1); + fputs("usage: dmenu [-bfiv] [-l lines] [-H height] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); + exit(1); } -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]) - fonts[0] = strdup(xval.addr); - - if (XrmGetResource(xdb, "dmenu.background", "*", &type, &xval) && !colors[SchemeNorm][ColBg] ) - colors[SchemeNorm][ColBg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dmenu.foreground", "*", &type, &xval) && !colors[SchemeNorm][ColFg] ) - colors[SchemeNorm][ColFg] = strdup(xval.addr); - - if (XrmGetResource(xdb, "dmenu.selbackground", "*", &type, &xval) && !colors[SchemeSel][ColBg] ) - colors[SchemeSel][ColBg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dmenu.selforeground", "*", &type, &xval) && !colors[SchemeSel][ColFg] ) - colors[SchemeSel][ColFg] = strdup(xval.addr); - - if (XrmGetResource(xdb, "dmenu.selhighlightbackground", "*", &type, &xval) && !colors[SchemeSelHighlight][ColBg] ) - colors[SchemeSelHighlight][ColBg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dmenu.selhighlightforeground", "*", &type, &xval) && !colors[SchemeSelHighlight][ColFg] ) - colors[SchemeSelHighlight][ColFg] = strdup(xval.addr); - - if (XrmGetResource(xdb, "dmenu.highlightbackground", "*", &type, &xval) && !colors[SchemeNormHighlight][ColBg] ) - colors[SchemeNormHighlight][ColBg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dmenu.highlightforeground", "*", &type, &xval) && !colors[SchemeNormHighlight][ColFg] ) - colors[SchemeNormHighlight][ColFg] = strdup(xval.addr); - - if (XrmGetResource(xdb, "dmenu.outbackground", "*", &type, &xval) && !colors[SchemeOut][ColBg] ) - colors[SchemeOut][ColBg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dmenu.outforeground", "*", &type, &xval) && !colors[SchemeOut][ColFg] ) - colors[SchemeOut][ColFg] = strdup(xval.addr); - - XrmDestroyDatabase(xdb); - } +static void +prewarm_sxmo_caches(void) +{ + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + int nullfd = open("/dev/null", O_RDWR); + if (nullfd >= 0) { + dup2(nullfd, 0); + dup2(nullfd, 1); + dup2(nullfd, 2); + if (nullfd > 2) + close(nullfd); + } + setsid(); + execl("/bin/sh", "sh", "-c", + "sxmo_hook_apps.sh >/dev/null 2>&1; sxmo_hook_scripts.sh >/dev/null 2>&1", + (char *)NULL); + _exit(0); + } } +static int +dmenu_run_main(int argc, char *argv[]) +{ + XWindowAttributes wa; + int i; + int preload = 0; + const char *initial_builtin = NULL; + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--preload")) { + preload = 1; + } else if (!strcmp(argv[i], "sys") || !strcmp(argv[i], "ctx") || + !strcmp(argv[i], "applications") || !strcmp(argv[i], "scripts") || + !strcmp(argv[i], "modem") || !strcmp(argv[i], "config") || + !strcmp(argv[i], "power") || !strcmp(argv[i], "st") || + !strcmp(argv[i], "nano") || !strcmp(argv[i], "sthotkeys")) { + preload = 1; + initial_builtin = argv[i]; + } else if (!strcmp(argv[i], "-v")) { + puts("dmenu-"VERSION); + return 0; + } else if (!strcmp(argv[i], "-b")) + topbar = 0; + else if (!strcmp(argv[i], "-f")) + ; + else if (!strcmp(argv[i], "-nc")) + centered = 0; + else if (!strcmp(argv[i], "-c")) + centered = 1; + else if (!strcmp(argv[i], "-i")) { + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if (!strcmp(argv[i], "-t")) + use_text_input = 1; + else if (!strcmp(argv[i], "-wm")) + managed = 1; + else if (i + 1 == argc) + usage(); + else if (!strcmp(argv[i], "-l")) + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-H")) { + lineheight = atoi(argv[++i]); + lineheight = MAX(lineheight, min_lineheight); + } else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) + prompt = argv[++i]; + else if (!strcmp(argv[i], "-fn")) + fonts[0] = strdup(argv[++i]); + else if (!strcmp(argv[i], "-nb")) + colors[SchemeNorm][ColBg] = strdup(argv[++i]); + else if (!strcmp(argv[i], "-nf")) + colors[SchemeNorm][ColFg] = strdup(argv[++i]); + else if (!strcmp(argv[i], "-sb")) + colors[SchemeSel][ColBg] = strdup(argv[++i]); + else if (!strcmp(argv[i], "-sf")) + colors[SchemeSel][ColFg] = strdup(argv[++i]); + else if (!strcmp(argv[i], "-w")) + embed = argv[++i]; + else if (!strcmp(argv[i], "--index") || !strcmp(argv[i], "-I")) + initialindex = atoi(argv[++i]); + else if (!strcmp(argv[i], "-idx")) + initialindex = atoi(argv[++i]) - 1; + else + usage(); + } + + if (getenv("DMENU_WRAP_AROUND")) + wrap_around = 1; + + int lock_fd = -1; + int fifo_fd = -1; + int sock_fd = -1; + + if (preload) { + lock_fd = open(DMENU_LOCK_PATH, O_RDWR | O_CREAT | O_CLOEXEC, 0666); + if (lock_fd < 0 || flock(lock_fd, LOCK_EX | LOCK_NB) != 0) { + if (lock_fd >= 0) + close(lock_fd); + if (initial_builtin) { + for (int wait_i = 0; wait_i < 40; wait_i++) { + int ffd = open(DMENU_FIFO_PATH, O_WRONLY | O_NONBLOCK | O_CLOEXEC); + if (ffd >= 0) { + char msg[128]; + int mlen = snprintf(msg, sizeof(msg), "OPEN %s\n", initial_builtin); + (void)write(ffd, msg, mlen); + close(ffd); + break; + } + usleep(10000); + } + } + return 0; + } + + daemon_mode = 1; + signal(SIGCHLD, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + int pfd = open(DMENU_PID_PATH, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + if (pfd >= 0) { + char pbuf[32]; + int plen = snprintf(pbuf, sizeof(pbuf), "%d\n", (int)getpid()); + (void)write(pfd, pbuf, plen); + close(pfd); + } + + unlink(DMENU_FIFO_PATH); + mkfifo(DMENU_FIFO_PATH, 0666); + chmod(DMENU_FIFO_PATH, 0666); + fifo_fd = open(DMENU_FIFO_PATH, O_RDWR | O_NONBLOCK | O_CLOEXEC); + + unlink(DMENU_SOCK_PATH); + sock_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + if (sock_fd >= 0) { + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, DMENU_SOCK_PATH, sizeof(addr.sun_path) - 1); + if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == 0) { + chmod(DMENU_SOCK_PATH, 0666); + listen(sock_fd, 8); + } + } + } + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + XSetLocaleModifiers(""); + if (!(dpy = XOpenDisplay(NULL))) + die("cannot open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + if (!embed || !(parentwin = strtol(embed, NULL, 0))) + parentwin = root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", parentwin); + drw = drw_create(dpy, screen, root, wa.width, wa.height); + + if (!fonts[0]) { + fonts[0] = defaultfonts[0]; + fonts[1] = defaultfonts[1]; + } else { + fonts[1] = defaultfonts[1]; + } + for (i = 0; i < SchemeLast; ++i) { + if (!colors[i][ColBg]) + colors[i][ColBg] = strdup(defaultcolors[i][ColBg]); + if (!colors[i][ColFg]) + colors[i][ColFg] = strdup(defaultcolors[i][ColFg]); + } + + if (!drw_fontset_create(drw, fonts, 2)) + die("no fonts could be loaded."); + + lrpad = drw->fonts->h / 2; + + if (preload) { + setup(0); + prewarm_sxmo_caches(); + + if (initial_builtin) + open_builtin_menu(initial_builtin, 0); + + run_daemon_loop(fifo_fd, sock_fd); + cleanup(); + return 0; + } + + readstdin(); + setup(1); + run_standalone(); + return 1; +} + +static int +dmenu_plugin_init(const DwmHostApi *host) +{ + if (host && host->spawn_plugin) + host->spawn_plugin("dmenu", "--preload"); + return 0; +} + +static void +dmenu_plugin_cleanup(void) +{ + char buf[64] = ""; + if (read_first_line(DMENU_PID_PATH, buf, sizeof(buf))) { + int pid = atoi(buf); + if (pid > 1) + kill((pid_t)pid, SIGTERM); + } + unlink(DMENU_PID_PATH); + unlink(DMENU_LOCK_PATH); + unlink(DMENU_FIFO_PATH); + unlink(DMENU_SOCK_PATH); + unlink(DMENU_ACTIVE_PATH); +} + +const DwmPlugin dwm_plugin_entry = { + .api_version = DWM_PLUGIN_API_VERSION, + .name = "dmenu", + .description = "Zero-lag Sxmo & DWM menu plugin", + .init = dmenu_plugin_init, + .cleanup = dmenu_plugin_cleanup, + .on_softkey_chord = NULL, + .on_idle_key = NULL, + .on_global_key = NULL, + .run_main = dmenu_run_main, +}; + int main(int argc, char *argv[]) { - XWindowAttributes wa; - int i, fast = 0; - - 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 */ - topbar = 0; - else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ - fast = 1; - else if (!strcmp(argv[i], "-nc")) /* prevents centering dmenu on screen */ - centered = 0; - else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */ - centered = 1; - else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ - fstrncmp = strncasecmp; - fstrstr = cistrstr; - } else if (!strcmp(argv[i], "-t")) /* favors text input over selection */ - use_text_input = 1; - else if (!strcmp(argv[i], "-wm")) { /* display as managed wm window */ - managed = 1; - } else if (i + 1 == argc) - usage(); - /* these options take one argument */ - else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ - lines = atoi(argv[++i]); - else if (!strcmp(argv[i], "-H")) { - lineheight = atoi(argv[++i]); - lineheight = MAX(lineheight, min_lineheight); - } - else if (!strcmp(argv[i], "-m")) - mon = atoi(argv[++i]); - else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ - prompt = argv[++i]; - else if (!strcmp(argv[i], "-fn")) /* font or font set */ - fonts[0] = strdup(argv[++i]); - else if (!strcmp(argv[i], "-nb")) /* normal background color */ - colors[SchemeNorm][ColBg] = strdup(argv[++i]); - else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ - colors[SchemeNorm][ColFg] = strdup(argv[++i]); - else if (!strcmp(argv[i], "-sb")) /* selected background color */ - colors[SchemeSel][ColBg] = strdup(argv[++i]); - else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ - colors[SchemeSel][ColFg] = strdup(argv[++i]); - else if (!strcmp(argv[i], "-w")) /* embedding window id */ - embed = argv[++i]; - else if ((!strcmp(argv[i], "--index")) || (!strcmp(argv[i], "-I"))) /* initial index selection, 0-indexed (bemenu compatible) */ - initialindex = atoi(argv[++i]); - else if (!strcmp(argv[i], "-idx")) /* initial index selection, 1-indexed */ - initialindex = atoi(argv[++i]) - 1; - else - usage(); - - if (getenv("DMENU_WRAP_AROUND")) - wrap_around = 1; - - if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) - fputs("warning: no locale support\n", stderr); - XSetLocaleModifiers(""); - if (!(dpy = XOpenDisplay(NULL))) - die("cannot open display"); - screen = DefaultScreen(dpy); - root = RootWindow(dpy, screen); - if (!embed || !(parentwin = strtol(embed, NULL, 0))) - parentwin = root; - if (!XGetWindowAttributes(dpy, parentwin, &wa)) - die("could not get embedding window attributes: 0x%lx", - parentwin); - drw = drw_create(dpy, screen, root, wa.width, wa.height); - - /* Apply defaults to font and colors*/ - if (!fonts[0]) { - fonts[0] = defaultfonts[0]; - fonts[1] = defaultfonts[1]; - } else { - fonts[1] = defaultfonts[1]; - } - for (i = 0; i < SchemeLast; ++i){ - if ( !colors[i][ColBg] ) - colors[i][ColBg] = strdup(defaultcolors[i][ColBg]); - if ( !colors[i][ColFg] ) - colors[i][ColFg] = strdup(defaultcolors[i][ColFg]); - } - - if (!drw_fontset_create(drw, fonts, 2)) - die("no fonts could be loaded."); - - lrpad = drw->fonts->h / 2; - -#ifdef __OpenBSD__ - if (pledge("stdio rpath", NULL) == -1) - die("pledge"); -#endif - - readstdin(); - setup(); - run(); - - return 1; /* unreachable */ + return dmenu_run_main(argc, argv); } diff --git a/src/sxmo-dwm/dwm.c b/src/sxmo-dwm/dwm.c index c0fe7cf..4141da6 100644 --- a/src/sxmo-dwm/dwm.c +++ b/src/sxmo-dwm/dwm.c @@ -1649,7 +1649,7 @@ host_spawn_plugin(const char *plugin_name, const char *arg) if (arg && strcmp(arg, "--preload") == 0) { if (is_plugin_daemon_alive(plugin_name)) return; - int stagger_ms = 600 + (preload_stagger_idx++ * 220); + int stagger_ms = (strcmp(plugin_name, "dmenu") == 0) ? 150 : (600 + (preload_stagger_idx++ * 220)); if (fork() == 0) { if (dpy) close(ConnectionNumber(dpy)); @@ -1670,6 +1670,8 @@ host_spawn_plugin(const char *plugin_name, const char *arg) int len; if (strcmp(plugin_name, "xdrawer") == 0) len = snprintf(msg, sizeof(msg), "TOGGLE\n"); + else if (strcmp(plugin_name, "dmenu") == 0 && arg && strcmp(arg, "close") == 0) + len = snprintf(msg, sizeof(msg), "CLOSE\n"); else if (arg && arg[0]) len = snprintf(msg, sizeof(msg), "OPEN %s\n", arg); else @@ -1843,6 +1845,8 @@ trigger_softkey_chord(long long now) kill(-last_menu_pgid, SIGKILL); last_menu_pgid = 0; } + if (is_plugin_loaded("dmenu")) + host_spawn_plugin("dmenu", "close"); softkey_pending = 0; softkey_first_ms = 0; softkey_deadline_ms = 0; @@ -1868,6 +1872,10 @@ flush_pending_softkey(void) XUngrabKeyboard(dpy, CurrentTime); XFlush(dpy); if (pending == SOFTKEY_LEFT) { + if (is_plugin_loaded("dmenu")) { + host_spawn_plugin("dmenu", "sys"); + return; + } const Arg syscmd = { .v = (const char*[]){ "sxmo_appmenu.sh", "sys", NULL } }; last_menu_pgid = spawn_pgid(&syscmd); } else if (pending == SOFTKEY_RIGHT) { @@ -1875,6 +1883,10 @@ flush_pending_softkey(void) dispatch_plugin_idle_key(XF86XK_Back, 166, 0)) { return; } + if (is_plugin_loaded("dmenu")) { + host_spawn_plugin("dmenu", "ctx"); + return; + } const Arg ctxcmd = { .v = (const char*[]){ "sxmo_appmenu.sh", NULL } }; last_menu_pgid = spawn_pgid(&ctxcmd); } @@ -1953,9 +1965,13 @@ keypress(XEvent *e) if (!selmon->sel && CLEANMASK(ev->state) == 0) { if (keysym == XK_Return) { - const Arg entercmd = { .v = (const char*[]){ "sxmo_appmenu.sh", NULL } }; XUngrabKeyboard(dpy, CurrentTime); XFlush(dpy); + if (is_plugin_loaded("dmenu")) { + host_spawn_plugin("dmenu", "sys"); + return; + } + const Arg entercmd = { .v = (const char*[]){ "sxmo_appmenu.sh", NULL } }; spawn(&entercmd); return; } else if (keysym == XK_Left) { diff --git a/src/xdrawer/xdrawer.cpp b/src/xdrawer/xdrawer.cpp index d6d33c4..571d01a 100644 --- a/src/xdrawer/xdrawer.cpp +++ b/src/xdrawer/xdrawer.cpp @@ -744,7 +744,13 @@ static int xdrawer_run_main(int argc, char** argv) { writeIntFile(pidPath, static_cast(::getpid())); controller.refreshAll(); render(); - runAsyncCmd("pkill -x dmenu >/dev/null 2>&1"); + if (::access("/tmp/dwm_plugin_dmenu.active", F_OK) == 0) { + int dfd = ::open("/tmp/dwm_plugin_dmenu.fifo", O_WRONLY | O_NONBLOCK | O_CLOEXEC); + if (dfd >= 0) { + (void)::write(dfd, "CLOSE\n", 6); + ::close(dfd); + } + } }; auto hideWindow = [&](bool drainSoftkeys) {