diff --git a/.gitignore b/.gitignore index 890d107..70d974e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,12 +10,11 @@ dmenu/dmenu dmenu/stest slstatus/slstatus slock/slock -clipmenu/src/clipmenu -clipmenu/src/clipmenud -clipmenu/src/clipdel -clipmenu/src/clipdelmenu -clipmenu/src/clipctl -clipmenu/src/clipserve +clipmenu/clipmenu +clipmenu/clipmenud +clipmenu/clipdel +clipmenu/clipctl +clipmenu/clipserve # Restos de parches *.orig diff --git a/README.md b/README.md deleted file mode 100644 index 1d0b4d8..0000000 --- a/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# suckless - -Mi configuración de dwm, st, dmenu, slstatus, slock, scroll y clipmenu. -Tema Everforest y fuente Fira Code en todos. - -Cada programa se importó con `git subtree --squash`, así que es un único repo -pero se puede seguir actualizando desde el proyecto original. - -## Compilar e instalar - - cd dwm && make && sudo make install - -(Igual para `st`, `dmenu`, `slstatus`, `slock`, `scroll` y `clipmenu`.) - -## Actualizar desde el proyecto original - - git subtree pull --prefix=dwm https://git.suckless.org/dwm master --squash - git subtree pull --prefix=clipmenu https://github.com/cdown/clipmenu develop --squash - -Los demás funcionan igual que dwm, cambiando el nombre en `--prefix` y en la URL. - -## Atajos de dwm - -`Mod` es `Super` (en el dwm original es `Alt`). Los atajos de ratón también usan -`Super`. Solo aparecen los atajos que cambian respecto al original o que son -nuevos; el resto están igual. Los atajos de st, scroll, dmenu y slock no se han -tocado. - -### Cambiados - -| Acción | Original | Ahora | -|---|---|---| -| Abrir terminal (`st`) | Mod+Shift+Return | Mod+Return | -| zoom (mover la ventana al área principal) | Mod+Return | Mod+Shift+Return | -| Cerrar ventana | Mod+Shift+c | Mod+q | -| Salir de dwm | Mod+Shift+q | Mod+Shift+m | - -### Nuevos - -| Atajo | Acción | -|---|---| -| Mod+v | clipmenu (misma paleta y fuente que dmenu) | -| Mod+Shift+l | Bloquear la pantalla con `slock` | -| Mod+`-` / Mod+`+` | Reducir / aumentar los gaps | -| Mod+Shift+`+` | Gaps a 0 | -| Tecla de subir / bajar volumen | Volumen ±5% con `wpctl` (máximo 100%) | -| Tecla de silenciar | Silenciar o activar el audio | -| Tecla de silenciar micro o Mod+ñ | Silenciar o activar el micrófono | -| Impr Pant | Captura de pantalla completa con `maim` | -| Shift+Impr Pant | Captura de una región | - -Las capturas se guardan en `~/Imágenes/Capturas`, se copian al portapapeles y -muestran una notificación. - -## Notas - -- La configuración personal está en `config.h`. `config.def.h` es la versión - original más los parches. Si un parche cambia `config.def.h`, hay que pasar - el cambio a mano a `config.h`: `make` solo lo copia si `config.h` no existe. -- Los parches aplicados están en `/patches/`. - - dwm: fullgaps - - st: ligatures, alpha y anysize (en ese orden; anysize necesita dos - arreglos a mano, ver el historial de git) diff --git a/clipmenu/.clang-format b/clipmenu/.clang-format deleted file mode 100644 index 076d317..0000000 --- a/clipmenu/.clang-format +++ /dev/null @@ -1,11 +0,0 @@ -BinPackArguments: true -BreakStringLiterals: false -ColumnLimit: 80 -IndentCaseLabels: true -IndentPPDirectives: BeforeHash -IndentWidth: 4 -InsertBraces: true -PenaltyBreakComment: 0 -SortIncludes: true -SpaceBeforeParens: ControlStatementsExceptForEachMacros -UseTab: Never diff --git a/clipmenu/.github/workflows/ci.yml b/clipmenu/.github/workflows/ci.yml deleted file mode 100644 index b412fc6..0000000 --- a/clipmenu/.github/workflows/ci.yml +++ /dev/null @@ -1,28 +0,0 @@ -jobs: - build_and_test: - name: CI - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - - run: sudo apt-get update - - - uses: awalsh128/cache-apt-pkgs-action@v1 - with: - packages: gcc-10 clang clang-format clang-tidy cppcheck xvfb xsel libxfixes-dev libx11-dev - version: 1.0 - - - run: gcc --version - - - run: make clean analyse - - run: make tests - - run: make integration_tests - env: - NO_PID_NAMESPACE: 1 - -on: - push: - pull_request: - workflow_dispatch: diff --git a/clipmenu/.gitignore b/clipmenu/.gitignore deleted file mode 100644 index 3668d31..0000000 --- a/clipmenu/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -src/clipmenud -src/clipctl -src/clipdel -src/clipdelmenu -src/clipmenu -src/clipserve -tests/test -tests/test_store -*.o diff --git a/clipmenu/LICENSE b/clipmenu/LICENSE deleted file mode 100644 index 9019292..0000000 --- a/clipmenu/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License - -Copyright (c) 2014-present Christopher Down - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/clipmenu/Makefile b/clipmenu/Makefile deleted file mode 100644 index 934c0d1..0000000 --- a/clipmenu/Makefile +++ /dev/null @@ -1,135 +0,0 @@ -CFLAGS := -std=gnu11 -O2 -Wall -Wextra -Wshadow -Wpointer-arith \ - -Wcast-align -Wmissing-prototypes -Wstrict-overflow -Wformat=2 \ - -Wwrite-strings -Warray-bounds -Wstrict-prototypes \ - -Wno-maybe-uninitialized \ - -Werror $(CFLAGS) -CPPFLAGS += -I/usr/X11R6/include -L/usr/X11R6/lib -LDLIBS += -lX11 -lXfixes -PREFIX ?= /usr/local -bindir := $(PREFIX)/bin -datarootdir := $(PREFIX)/share -mandir := $(datarootdir)/man -systemd_user_dir = $(DESTDIR)$(PREFIX)/lib/systemd/user -debug_cflags := -D_FORTIFY_SOURCE=2 -fsanitize=leak -fsanitize=address \ - -fsanitize=undefined -Og -ggdb -fno-omit-frame-pointer \ - -fstack-protector-strong -c_files := $(wildcard src/*.c) -h_files := $(wildcard src/*.h) -libs := $(filter $(c_files:.c=.o), $(h_files:.h=.o)) - -man1_files = clipctl.1 clipdel.1 clipdelmenu.1 clipmenu.1 clipmenud.1 clipserve.1 -man5_files = clipmenu.conf.5 - -bins := clipctl clipmenud clipdel clipdelmenu clipserve clipmenu - -all: $(addprefix src/,$(bins)) - -src/%: src/%.c $(libs) - $(CC) $(CFLAGS) $(CPPFLAGS) $^ $(LDFLAGS) $(LDLIBS) -o $@ - -src/%.o: src/%.c src/%.h - $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ - -debug: all -debug: CFLAGS+=$(debug_cflags) - -install: all - @for f in $(man1_files); do \ - install -Dp -m 644 man/$$f $(DESTDIR)$(mandir)/man1/$$f; \ - done - @for f in $(man5_files); do \ - install -Dp -m 644 man/$$f $(DESTDIR)$(mandir)/man5/$$f; \ - done - - mkdir -p $(DESTDIR)$(bindir)/ - install -pt $(DESTDIR)$(bindir)/ $(addprefix src/,$(bins)) - mkdir -p $(systemd_user_dir) - sed 's|@bindir@|$(bindir)|g' init/clipmenud.service.in > $(systemd_user_dir)/clipmenud.service - -uninstall: - rm -f $(addprefix $(DESTDIR)$(PREFIX)/bin/,$(bins)) - rm -f "$(DESTDIR)${PREFIX}/lib/systemd/user/clipmenud.service" - rm -f $(addprefix $(DESTDIR)$(mandir)/man1/,$(man1_files)) - rm -f $(addprefix $(DESTDIR)$(mandir)/man5/,$(man5_files)) - -clean: - rm -f src/*.o src/*~ $(addprefix src/,$(bins)) tests/test_store - -clang_supports_unsafe_buffer_usage := $(shell clang -x c -c /dev/null -o /dev/null -Werror -Wunsafe-buffer-usage > /dev/null 2>&1; echo $$?) -ifeq ($(clang_supports_unsafe_buffer_usage),0) - extra_clang_flags := -Wno-unsafe-buffer-usage -Wno-missing-include-dirs \ - -Wno-unknown-warning-option \ - -Wno-unused-command-line-argument \ - -Wno-error -else - extra_clang_flags := -Wno-missing-include-dirs \ - -Wno-unknown-warning-option \ - -Wno-unused-command-line-argument \ - -Wno-error -endif - -c_analyse_targets := $(c_files:%=%-analyse) -h_analyse_targets := $(h_files:%=%-analyse) - -analyse: CFLAGS+=$(debug_cflags) -analyse: cppcheck $(c_analyse_targets) $(h_analyse_targets) - -$(c_analyse_targets): %-analyse: % - # -W options here are not clang compatible, so out of generic CFLAGS - gcc $< -o /dev/null -c \ - -std=gnu99 -Ofast -fwhole-program -Wall -Wextra \ - -Wlogical-op -Wduplicated-cond \ - -fanalyzer $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(LDLIBS) - clang $< -o /dev/null -c -std=gnu99 -Ofast \ - $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(LDLIBS) \ - $(extra_clang_flags) - $(MAKE) $*-shared-analyse - -$(h_analyse_targets): %-analyse: - $(MAKE) $*-shared-analyse - -%-shared-analyse: % - # clang-analyzer-unix.Malloc does not understand _drop_() - clang-tidy $< --quiet -checks=-clang-analyzer-unix.Malloc -- -std=gnu99 - clang-format --dry-run --Werror $< - -# --suppress=missingIncludeSystem: -# -# Without this there's a bunch of noise from cppcheck from the system headers -# themselves. -# -# --suppress=unusedFunction: -# -# cppcheck does not understand _drop_ and marks those as unused. This is -# already well checked by Clang/GCC, just leave it to them. -# -# --suppress=unmatchedSuppression: -# -# We run both locally and on CI, so there may be some suppressions that -# depending on version do not match in one version but do on another. -# -# --suppress=unusedStructMember -# -# Structs are used across translation units and cppcheck gets this wrong. -cppcheck: $(c_files) $(h_files) - cppcheck $(c_files) $(h_files) --std=c99 --quiet --inline-suppr --force \ - --enable=all \ - --suppress=missingIncludeSystem \ - --suppress=unusedFunction \ - --suppress=unmatchedSuppression \ - --suppress=checkersReport \ - --suppress=unusedStructMember \ - --check-level=exhaustive \ - --max-ctu-depth=10 --error-exitcode=1 - -tests: tests/test_store - tests/test_store - -integration_tests: - tests/x_integration_tests - -tests/test_store: tests/test_store.c src/store.o src/util.o - $(CC) $(CFLAGS) $(CPPFLAGS) -I./src -o $@ $^ $(LDLIBS) - -.PHONY: all debug install uninstall clean analyse tests integration_tests \ - cppcheck diff --git a/clipmenu/README.md b/clipmenu/README.md deleted file mode 100644 index cf29fac..0000000 --- a/clipmenu/README.md +++ /dev/null @@ -1,97 +0,0 @@ -clipmenu is a simple clipboard manager using [dmenu][], [rofi][] or similar. - -# Demo - -![Demo](https://cloud.githubusercontent.com/assets/660663/24079784/6f76da94-0c88-11e7-8251-40b1f02ebf3c.gif) - -# Usage - -## clipmenud - -Start `clipmenud`, then run `clipmenu` to select something to put on the -clipboard. For systemd users, a user service called `clipmenud` is packaged as -part of the project. - -If you start X via `startx`/`xinit` and use the systemd user service, make sure -`$DISPLAY` is set so clipmenud knows which X server to use. Most distributions -include scripts in `/etc/X11/xinit/xinitrc.d/` for this, so if they are present -you can usually just source them in your `~/.xinitrc`. Alternatively, before -launching the clipmenud service, run: - - systemctl --user import-environment DISPLAY - -## clipmenu - -You may wish to bind a shortcut in your window manager to launch `clipmenu`. - -All args passed to clipmenu are transparently dispatched to dmenu. That is, if -you usually call dmenu with args to set colours and other properties, you can -invoke clipmenu in exactly the same way to get the same effect, like so: - - clipmenu -i -fn Terminus:size=8 -nb '#002b36' -nf '#839496' -sb '#073642' -sf '#93a1a1' - -By default, clipmenu also adds dmenu-compatible defaults such as `-p` and `-l`. -If that gets in the way for a custom launcher, set `launcher_pass_dmenu_args` to -0 in `clipmenu.conf`. - -For a full list of configuration options (and their environment variable -equivalents), please see `man clipmenu.conf`. - -There is also `clipdel` to delete clips, `clipdelmenu` for interactive deletion, -and `clipctl` to enable or disable clipboard monitoring. - -# Features - -The behaviour of `clipmenud` can be customised through a config file. As some -examples of things you can change: - -* Customising the maximum number of clips stored (default 1000) -* Disabling clip collection temporarily with `clipctl disable`, reenabling with - `clipctl enable` -* Deduplicating repeated clips so only the newest remains -* Not storing clipboard changes from certain applications, like password - managers -* Taking direct ownership of the clipboard -* ...and much more. - -See `man clipmenu.conf` to view all possible configuration variables and what -they do. - -# Supported launchers - -Any dmenu-compliant application will work, but here are `CM_LAUNCHER` -configurations that are known to work: - -- `dmenu` (the default) -- `fzf` -- `rofi` - -# Installation - -Several distributions, including Arch and Nix, provide clipmenu as an official -package called `clipmenu`. - -## Manual installation - -If your distribution doesn't provide a package, you can manually install using -`make install` (or better yet, create a package for your distribution!). - -# How does it work? - -## clipmenud - -1. clipmenud passively monitors X11 clipboard selections (PRIMARY, CLIPBOARD, - and SECONDARY) for changes using XFixes (no polling). -2. If `clipmenud` detects changes to the clipboard contents, it writes them out - to storage and indexes using a hash as the filename. - -## clipmenu - -1. `clipmenu` reads the index to find all available clips. -2. `dmenu` (or another configured launcher) is executed to allow the user to - select a clip. -3. After selection, the clip is put onto the PRIMARY and CLIPBOARD X - selections. - -[dmenu]: http://tools.suckless.org/dmenu/ -[rofi]: https://github.com/DaveDavenport/Rofi diff --git a/clipmenu/init/clipmenud.service.in b/clipmenu/init/clipmenud.service.in deleted file mode 100644 index f68267b..0000000 --- a/clipmenu/init/clipmenud.service.in +++ /dev/null @@ -1,21 +0,0 @@ -[Unit] -Description=Clipmenu daemon - -[Service] -ExecStart=@bindir@/clipmenud -Restart=always -RestartSec=500ms - -MemoryDenyWriteExecute=yes -NoNewPrivileges=yes -ProtectControlGroups=yes -ProtectKernelTunables=yes -RestrictAddressFamilies= -RestrictRealtime=yes - -# We don't need to do any clean up, so if something hangs (borked X server, -# etc), it's going to stay that way. Just forcefully kill and get it over with. -TimeoutStopSec=2 - -[Install] -WantedBy=default.target diff --git a/clipmenu/man/clipctl.1 b/clipmenu/man/clipctl.1 deleted file mode 100644 index 1b95e05..0000000 --- a/clipmenu/man/clipctl.1 +++ /dev/null @@ -1,56 +0,0 @@ -.TH CLIPCTL 1 -.SH NAME -clipctl \- control the clipmenud daemon -.SH SYNOPSIS -.B clipctl -ACTION -.SH DESCRIPTION -.B clipctl -communicates with the clipmenud daemon to control clipboard collection. It -sends a signal to clipmenud so that clipboard monitoring is enabled, disabled, -or toggled, or it simply displays the current status. -.SH OPTIONS -.TP -.B enable -Instruct clipmenud to enable clipboard collection. -.TP -.B disable -Instruct clipmenud to disable clipboard collection. -.TP -.B toggle -Invert the current state of clipboard collection. -.TP -.B status -Print the current state of clipboard collection. -.TP -.B version -Print the major clipmenu version. -.TP -.B cache-dir -Print the cache directory used by clipmenud. - -.TP -clipctl also accepts one dash option: - -.B \-h, \--help -Display the help message (invokes the manual page). -.SH CONFIGURATION -See -.BR clipmenu.conf (5). -.SH DEPENDENCIES -clipctl requires a running instance of the -.BR clipmenud -daemon. -.SH SEE ALSO -.BR clipdel (1), -.BR clipmenu (1), -.BR clipmenud (1), -.BR clipmenu.conf (5) -.SH AUTHOR -Chris Down -.MT chris@chrisdown.name -.ME -.SH REPORTING BUGS -Please send bug reports to -.UR https://github.com/cdown/clipmenu/issues -.UE . diff --git a/clipmenu/man/clipdel.1 b/clipmenu/man/clipdel.1 deleted file mode 100644 index 1d93acd..0000000 --- a/clipmenu/man/clipdel.1 +++ /dev/null @@ -1,53 +0,0 @@ -.TH CLIPDEL 1 -.SH NAME -clipdel \- delete entries from the clip store -.SH SYNOPSIS -.B clipdel -[OPTION...] PATTERN -.SH DESCRIPTION -.B clipdel -removes clipboard entries from the clip store managed by clipmenu. By default, -it performs a dry-run and prints any matching entries. With the -d flag, -matching entries are permanently removed. -.SH OPTIONS -.TP -.B \-d -Real deletion mode. Matching clipboard entries will be removed. -.TP -.B \-F -Perform a literal (fixed-string) match instead of interpreting the pattern as a regular expression. -.TP -.B \-N -Interpret PATTERN as the amount of entries to delete starting from the oldest. -E.g -.I "\-N 1" -deletes the oldest entry. -.TP -.B \-n -Same as -.B \-N -but starting from newest. -.TP -.B \-v -Invert the matching condition; entries that do not match the given pattern are selected for deletion. -.TP -.B \-h, \--help -Display the help message (invokes the manual page). -.SH CONFIGURATION -See -.BR clipmenu.conf (5). -.SH DEPENDENCIES -clipdel requires access to the clip store directory as defined in the configuration. -.SH SEE ALSO -.BR clipctl (1), -.BR clipmenu (1), -.BR clipmenud (1), -.BR clipmenu.conf (5) -.SH AUTHOR -Chris Down -.MT chris@chrisdown.name -.ME -.SH REPORTING BUGS -Please send bug reports to -.UR https://github.com/cdown/clipmenu/issues -.UE . diff --git a/clipmenu/man/clipdelmenu.1 b/clipmenu/man/clipdelmenu.1 deleted file mode 100644 index 72f76ee..0000000 --- a/clipmenu/man/clipdelmenu.1 +++ /dev/null @@ -1,43 +0,0 @@ -.TH CLIPDELMENU 1 -.SH NAME -clipdelmenu \- interactive launcher for deleting clipboard entries -.SH SYNOPSIS -.B clipdelmenu -[OPTION...] -.SH DESCRIPTION -.B clipdelmenu -provides an interactive interface for browsing and deleting clipboard entries -stored by clipmenud. It launches a menu using a configured launcher -(e.g., dmenu, rofi, or another custom command) and displays a numbered list of -stored clips. Once a selection is made, the chosen clip(s) are permanently -deleted from the clip store. Multiple clips can be selected at once using the -launcher's multi-select feature (e.g., Ctrl-Enter in dmenu). -.SH OPTIONS -.TP -.B \-h, \--help -Display the help message (invokes the manual page). - -Other than that, command-line arguments are passed directly to the underlying -launcher. -.SH CONFIGURATION -See -.BR clipmenu.conf (5). -.SH DEPENDENCIES -clipdelmenu requires a launcher capable of a dmenu-like interface (e.g., rofi or a -custom command). Population of the clipstore requires -.BR clipmenud -to be running. -.SH SEE ALSO -.BR clipctl (1), -.BR clipdel (1), -.BR clipmenu (1), -.BR clipmenud (1), -.BR clipmenu.conf (5) -.SH AUTHOR -Chris Down -.MT chris@chrisdown.name -.ME -.SH REPORTING BUGS -Please send bug reports to -.UR https://github.com/cdown/clipmenu/issues -.UE . diff --git a/clipmenu/man/clipmenu.1 b/clipmenu/man/clipmenu.1 deleted file mode 100644 index 00cfd00..0000000 --- a/clipmenu/man/clipmenu.1 +++ /dev/null @@ -1,42 +0,0 @@ -.TH CLIPMENU 1 -.SH NAME -clipmenu \- interactive launcher for clipboard entry selection -.SH SYNOPSIS -.B clipmenu -[OPTION...] -.SH DESCRIPTION -.B clipmenu -provides an interactive interface for browsing and selecting clipboard entries -stored by clipmenud. It launches a menu using a configured launcher -(e.g., dmenu, rofi, or another custom command) and displays a numbered list of -stored clips. Once a selection is made, clipmenu calls -.BR clipserve -to set the chosen clip as the current X11 selections (PRIMARY and CLIPBOARD). -.SH OPTIONS -.TP -.B \-h, \--help -Display the help message (invokes the manual page). - -Other than that, command-line arguments are passed directly to the underlying -launcher. -.SH CONFIGURATION -See -.BR clipmenu.conf (5). -.SH DEPENDENCIES -clipmenu requires a launcher capable of a dmenu-like interface (e.g., rofi or a -custom command). Population of the clipstore requires -.BR clipmenud -to be running. -.SH SEE ALSO -.BR clipctl (1), -.BR clipdel (1), -.BR clipmenud (1), -.BR clipmenu.conf (5) -.SH AUTHOR -Chris Down -.MT chris@chrisdown.name -.ME -.SH REPORTING BUGS -Please send bug reports to -.UR https://github.com/cdown/clipmenu/issues -.UE . diff --git a/clipmenu/man/clipmenu.conf.5 b/clipmenu/man/clipmenu.conf.5 deleted file mode 100644 index 2c93bed..0000000 --- a/clipmenu/man/clipmenu.conf.5 +++ /dev/null @@ -1,124 +0,0 @@ -.TH CLIPMENU.CONF 5 -.SH NAME -clipmenu.conf \- configuration file for clipmenu applications -.SH DESCRIPTION -clipmenu.conf defines the runtime settings for clipmenu applications (including -clipmenu itself, clipmenud, clipctl, clipdel, clipdelmenu, and clipserve). -.SH FORMAT -Each non-comment line in clipmenu.conf consists of a key followed by one or -more whitespace-separated values. Blank lines and lines starting with '#' are -ignored. -.PP -For example: -.EX -max_clips 1000 -selections clipboard primary -.EE -.SH CONFIGURATION OPTIONS -.TP -.B max_clips -Specifies the maximum number of clipboard entries to retain in the clip store. -Default: 1000. -.TP -.B max_clips_batch -Provides a buffer above max_clips; when the number of entries exceeds -(max_clips + max_clips_batch), the clip store is trimmed back to max_clips -entries. Default: 100. -.TP -.B oneshot -If set to 1, clipmenud processes clipboard selections only once before exiting. -Default: 0. -.TP -.B deduplicate -If set to 1, clipmenud keeps only the newest copy of duplicate clips instead of -storing every duplicate entry. Default: 0. -.TP -.B own_clipboard -Determines whether clipmenud should claim ownership of the X11 clipboard. Works -together with own_selections. Default: 0. -.TP -.B own_selections -Specifies which X11 selections (e.g., "clipboard" or "primary") clipmenud -should actively own when -.B own_clipboard -is enabled. When a clip from one of these selections is stored, clipmenud uses -clipserve to own those selections. This does not affect which selections are -monitored; use -.B selections -for that. Default: "clipboard". -If this is set to an empty value, it is treated as if -.B own_clipboard -was disabled. -.TP -.B selections -Lists the X11 selections to monitor for changes. Valid values include -"clipboard", "primary", and "secondary". Default: "clipboard primary". -.TP -.B ignore_window -Defines a regular expression matching window titles to exclude from clipboard -monitoring. Unset by default. -.TP -.B launcher -Specifies the launcher command to use with clipmenu. Alternative choices -include rofi's dmenu mode or a custom command. Default: "dmenu". -.TP -.B launcher_pass_dmenu_args -When enabled, clipmenu and clipdelmenu add dmenu-compatible defaults (such as --p and -l) before any user-provided arguments. Default: 1. -.TP -.B touch_on_select -When an entry is selected via -.BR clipmenu (1) -it will also be moved up to the newest slot. -Default: 0. -.TP -.B partial_merge_secs -Specifies the time window in seconds during which consecutive clipboard updates -where the previous entry was a substring of the new one are merged together -instead of being stored separately. This is useful for applications that -incrementally update the clipboard (e.g. progressive text selection in -browsers). Set to 0 to disable partial merging entirely. Default: 2. -.TP -.B cm_dir -Overrides the default base directory for clipmenu runtime data. The clip store -is created at a subdirectory named clipmenu.. inside this -directory. By default, it uses XDG_RUNTIME_DIR, TMPDIR, or /tmp if neither are -set. The path must be absolute. A leading ~ expands to $HOME. -.SH FILE LOCATION -Typically, clipmenu.conf is located at -.BR ~/.config/clipmenu/clipmenu.conf -. -The file location can be overridden with $CM_CONFIG, which must be an absolute -path. A leading ~ expands to $HOME. If $CM_CONFIG is unset, $XDG_CONFIG_HOME is -used (falling back to ~/.config). -.SH ENVIRONMENT -.TP -.B CM_CONFIG -Absolute path (or a path beginning with ~) to the configuration file. This -takes precedence over $XDG_CONFIG_HOME. -.TP -.B XDG_CONFIG_HOME -Base directory for configuration files. If unset, ~/.config is used. -.TP -.B CM_DEBUG -If set to 1, enable debug logging. -.TP -.B Config environment variables -CM_MAX_CLIPS, CM_MAX_CLIPS_BATCH, CM_ONESHOT, CM_DEDUPLICATE, CM_OWN_CLIPBOARD, -CM_SELECTIONS, CM_OWN_SELECTIONS, CM_IGNORE_WINDOW, CM_LAUNCHER, -CM_LAUNCHER_PASS_DMENU_ARGS, and CM_DIR are environment variable equivalents for -the configuration keys of the same name. These override values from the config -file and are provided for compatibility with versions prior to 7. -.SH SEE ALSO -.BR clipctl (1), -.BR clipdel (1), -.BR clipmenu (1), -.BR clipmenud (1), -.SH AUTHOR -Chris Down -.MT chris@chrisdown.name -.ME -.SH REPORTING BUGS -Please send bug reports to -.UR https://github.com/cdown/clipmenu/issues -.UE . diff --git a/clipmenu/man/clipmenud.1 b/clipmenu/man/clipmenud.1 deleted file mode 100644 index 65f367a..0000000 --- a/clipmenu/man/clipmenud.1 +++ /dev/null @@ -1,32 +0,0 @@ -.TH CLIPMENUD 1 -.SH NAME -clipmenud \- daemon for clipboard monitoring and storage -.SH DESCRIPTION -.B clipmenud -runs in the background, monitoring X11 clipboard selections (including PRIMARY, -CLIPBOARD, and SECONDARY). It stores new clipboard entries into a persistent -clip store. clipmenud responds to signals sent by -.BR clipctl -to enable or disable clipboard collection. -.SH OPTIONS -.TP -.B \-h, \--help -Display the help message (invokes the manual page). -.SH CONFIGURATION -See -.BR clipmenu.conf (5). -.SH DEPENDENCIES -clipmenud requires an X11 environment with the XFixes extension and access to the clip store directory as defined in the configuration. -.SH SEE ALSO -.BR clipctl (1), -.BR clipdel (1), -.BR clipmenu (1), -.BR clipmenu.conf (5) -.SH AUTHOR -Chris Down -.MT chris@chrisdown.name -.ME -.SH REPORTING BUGS -Please send bug reports to -.UR https://github.com/cdown/clipmenu/issues -.UE . diff --git a/clipmenu/man/clipserve.1 b/clipmenu/man/clipserve.1 deleted file mode 100644 index 72f6d9b..0000000 --- a/clipmenu/man/clipserve.1 +++ /dev/null @@ -1,44 +0,0 @@ -.TH CLIPSERVE 1 -.SH NAME -clipserve \- serve a selected clipboard entry to X11 selections -.SH SYNOPSIS -.B clipserve -[\-s selection]... -.SH DESCRIPTION -.B clipserve -serves the clipboard content identified by a hash from the clip store on the -configured X11 selections. By default, it serves both PRIMARY and CLIPBOARD. - -This program is not usually invoked directly, but is instead called from inside -other clipmenu applications. -.SH OPTIONS -.TP -.B \-h, \--help -Display the help message (invokes the manual page). -.TP -.B \-s, \--selection -Specify a selection to serve. Valid values are "primary", "clipboard", and -"secondary". This option may be repeated. If not provided, PRIMARY and -CLIPBOARD are used. -.TP -clipserve requires a hexadecimal hash of the clipboard entry to be served. -.SH CONFIGURATION -See -.BR clipmenu.conf (5). -.SH DEPENDENCIES -clipserve needs a valid clip store and an operational X11 environment to -function correctly. -.SH SEE ALSO -.BR clipctl (1), -.BR clipdel (1), -.BR clipmenu (1), -.BR clipmenud (1), -.BR clipmenu.conf (5) -.SH AUTHOR -Chris Down -.MT chris@chrisdown.name -.ME -.SH REPORTING BUGS -Please send bug reports to -.UR https://github.com/cdown/clipmenu/issues -.UE . diff --git a/clipmenu/src/clipctl.c b/clipmenu/src/clipctl.c deleted file mode 100644 index 7f5c961..0000000 --- a/clipmenu/src/clipctl.c +++ /dev/null @@ -1,152 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "util.h" - -/** - * Check if the clipmenud service is enabled. - */ -static bool is_enabled(struct config *cfg) { - _drop_(fclose) FILE *file = fopen(get_enabled_path(cfg), "r"); - die_on(!file, "Failed to open enabled file: %s\n", strerror(errno)); - return fgetc(file) == '1'; -} - -/** - * Retrieve the process ID of the clipmenud daemon. - * - * -EEXIST is returned if multiple instances are detected. -ENOENT is returned - * if no instances are found. - */ -static pid_t get_clipmenud_pid(struct config *cfg) { - int fd = open(get_session_lock_path(cfg), O_RDWR | O_CLOEXEC); - if (fd < 0) { - return -ENOENT; - } - - if (flock(fd, LOCK_EX | LOCK_NB) == 0) { - close(fd); - return -ENOENT; - } - bool would_block; -#if EWOULDBLOCK == EAGAIN - would_block = errno == EWOULDBLOCK; -#else - would_block = errno == EWOULDBLOCK || errno == EAGAIN; -#endif - if (!would_block) { - close(fd); - return -ENOENT; - } - - char buf[32]; - ssize_t read_sz = read(fd, buf, sizeof(buf) - 1); - close(fd); - if (read_sz <= 0) { - return -ENOENT; - } - buf[read_sz] = '\0'; - char *newline = strchr(buf, '\n'); - if (newline) { - *newline = '\0'; - } - - uint64_t pid; - if (str_to_uint64(buf, &pid) < 0) { - return -ENOENT; - } - - if (kill((pid_t)pid, 0) < 0 && errno == ESRCH) { - return -ENOENT; - } - - return (pid_t)pid; -} - -/** - * Determine if clipmenud should be enabled based on the given mode string and - * clipmenu state. - */ -static bool _nonnull_ should_enable(struct config *cfg, const char *mode_str) { - if (streq(mode_str, "enable")) { - return true; - } else if (streq(mode_str, "disable")) { - return false; - } else if (streq(mode_str, "toggle")) { - return !is_enabled(cfg); - } - - die("Unknown command: %s\n", mode_str); -} - -int main(int argc, char *argv[]) { - _drop_(config_free) struct config cfg = setup("clipctl"); - exec_man_on_help(argc, argv); - die_on(argc != 2, - "Usage: clipctl \n"); - - const char *cmd = argv[1]; - - if (streq(cmd, "cache-dir")) { - printf("%s\n", get_cache_dir(&cfg)); - return 0; - } - if (streq(cmd, "version")) { - printf("%d\n", CLIPMENU_VERSION); - return 0; - } - - pid_t pid = get_clipmenud_pid(&cfg); - die_on(pid == -ENOENT, "clipmenud is not running\n"); - expect(pid > 0); - - if (streq(cmd, "status")) { - printf("%s\n", is_enabled(&cfg) ? "enabled" : "disabled"); - return 0; - } - - bool want_enable = should_enable(&cfg, cmd); - - expect(kill(pid, want_enable ? SIGUSR2 : SIGUSR1) == 0); - dbg("Sent signal to pid %d\n", pid); - - unsigned int delay_ms = 1; - unsigned int total_wait_ms = 0; - const unsigned int max_wait_ms = 1000; - - while (total_wait_ms < max_wait_ms) { - if (is_enabled(&cfg) == want_enable) { - return 0; - } - - struct timespec req = {.tv_sec = delay_ms / 1000, - .tv_nsec = (delay_ms % 1000) * 1000000L}; - struct timespec rem; - - while (nanosleep(&req, &rem) != 0) { - expect(errno == EINTR); - req = rem; - } - - total_wait_ms += delay_ms; - delay_ms *= 2; - } - - if (is_enabled(&cfg) == want_enable) { - return 0; - } - - die("Failed to %s clipmenud within %u ms\n", - want_enable ? "enable" : "disable", max_wait_ms); -} diff --git a/clipmenu/src/clipdel.c b/clipmenu/src/clipdel.c deleted file mode 100644 index 30fd0b7..0000000 --- a/clipmenu/src/clipdel.c +++ /dev/null @@ -1,162 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "store.h" -#include "util.h" - -/** - * The deletion mode for clipdel operations. - */ -enum delete_mode { - DELETE_DRY_RUN, - DELETE_REAL, -}; - -/** - * The match type for clipdel operations. - */ -enum match_type { - MATCH_REGEX, - MATCH_LITERAL, - MATCH_COUNT_NEWEST, - MATCH_COUNT_OLDEST, -}; - -/** - * Holds the application state for a clipdel operation in preparation for - * passing it as private data to the cs_remove callback. - */ -struct clipdel_state { - enum delete_mode mode; - enum match_type type; - bool invert_match; - union { - regex_t rgx; - const char *needle; - struct { - uint64_t current; - uint64_t num_delete; - } count; - }; -}; - -/** - * Callback for cs_remove. In order for the delete to actually happen, we must - * be running DELETE_REAL. - */ -static enum cs_remove_action _nonnull_ remove_if_match(uint64_t hash _unused_, - const char *line, - void *private) { - struct clipdel_state *state = private; - int ret; - bool matches; - switch (state->type) { - case MATCH_LITERAL: - matches = strstr(line, state->needle) != NULL; - break; - case MATCH_REGEX: - ret = regexec(&state->rgx, line, 0, NULL, 0); - expect(ret == 0 || ret == REG_NOMATCH); - matches = ret == 0; - break; - case MATCH_COUNT_NEWEST: - case MATCH_COUNT_OLDEST: - if (state->count.current < state->count.num_delete) { - matches = true; - ++state->count.current; - } else { - matches = false; - } - break; - default: - die("unreachable\n"); - } - - bool wants_del = state->invert_match ? !matches : matches; - if (wants_del) { - puts(line); - } - - return state->mode == DELETE_REAL && wants_del ? CS_ACTION_REMOVE - : CS_ACTION_KEEP; -} - -int main(int argc, char *argv[]) { - const char usage[] = "Usage: clipdel [-d] [-F] [-N] [-n] [-v] pattern"; - - _drop_(config_free) struct config cfg = setup("clipdel"); - - struct clipdel_state state = {0}; - - int opt; - while ((opt = getopt(argc, argv, "dFNnvh")) != -1) { - switch (opt) { - case 'd': - state.mode = DELETE_REAL; - break; - case 'F': - state.type = MATCH_LITERAL; - break; - case 'N': - state.type = MATCH_COUNT_OLDEST; - break; - case 'n': - state.type = MATCH_COUNT_NEWEST; - break; - case 'v': - state.invert_match = true; - break; - case 'h': - exec_man(); - break; - default: - die("%s\n", usage); - } - } - - die_on(optind >= argc, "%s\n", usage); - - _drop_(close) int content_dir_fd = open(get_cache_dir(&cfg), O_RDONLY); - _drop_(close) int snip_fd = - open(get_line_cache_path(&cfg), O_RDWR | O_CREAT, 0600); - expect(content_dir_fd >= 0 && snip_fd >= 0); - - _drop_(cs_destroy) struct clip_store cs; - expect(cs_init(&cs, snip_fd, content_dir_fd) == 0); - - enum cs_iter_direction direction = CS_ITER_OLDEST_FIRST; - switch (state.type) { - case MATCH_REGEX: - die_on(regcomp(&state.rgx, argv[optind], REG_EXTENDED | REG_NOSUB), - "Could not compile regex\n"); - break; - case MATCH_LITERAL: - state.needle = argv[optind]; - break; - case MATCH_COUNT_NEWEST: - case MATCH_COUNT_OLDEST: - die_on(str_to_uint64(argv[optind], &state.count.num_delete) < 0, - "Bad argument, expected integer: %s\n", argv[optind]); - state.count.current = 0; - if (state.type == MATCH_COUNT_NEWEST) { - direction = CS_ITER_NEWEST_FIRST; - } - break; - default: - die("unreachable\n"); - } - - expect(cs_remove(&cs, direction, remove_if_match, &state) == 0); - - if (state.type == MATCH_REGEX) { - regfree(&state.rgx); - } - - return 0; -} diff --git a/clipmenu/src/clipdelmenu.c b/clipmenu/src/clipdelmenu.c deleted file mode 100644 index 255c7bf..0000000 --- a/clipmenu/src/clipdelmenu.c +++ /dev/null @@ -1,45 +0,0 @@ -#include -#include -#include - -#include "config.h" -#include "menu_util.h" -#include "store.h" -#include "util.h" - -struct delete_state { - uint64_t hash_to_delete; -}; - -// cppcheck-suppress-begin constParameterCallback -static enum cs_remove_action _nonnull_ -remove_if_hash_match(uint64_t hash, const char *line _unused_, void *private) { - const struct delete_state *state = private; - return hash == state->hash_to_delete ? CS_ACTION_REMOVE : CS_ACTION_KEEP; -} -// cppcheck-suppress-end constParameterCallback - -static int _nonnull_ clipdelmenu_action(struct config *cfg, uint64_t hash) { - _drop_(close) int content_dir_fd = open(get_cache_dir(cfg), O_RDONLY); - _drop_(close) int snip_fd = - open(get_line_cache_path(cfg), O_RDWR | O_CREAT, 0600); - expect(content_dir_fd >= 0 && snip_fd >= 0); - - _drop_(cs_destroy) struct clip_store cs; - expect(cs_init(&cs, snip_fd, content_dir_fd) == 0); - - struct delete_state del_state = {.hash_to_delete = hash}; - expect(cs_remove(&cs, CS_ITER_NEWEST_FIRST, remove_if_hash_match, - &del_state) == 0); - - return 0; -} - -int main(int argc, char *argv[]) { - menu_set_user_args(argc, argv); - - _drop_(config_free) struct config cfg = setup("clipdelmenu"); - exec_man_on_help(argc, argv); - - return menu_prompt_and_act(&cfg, "clipdelmenu", clipdelmenu_action); -} diff --git a/clipmenu/src/clipmenu.c b/clipmenu/src/clipmenu.c deleted file mode 100644 index a7e1982..0000000 --- a/clipmenu/src/clipmenu.c +++ /dev/null @@ -1,42 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "menu_util.h" -#include "store.h" -#include "util.h" -#include "x.h" - -static int _nonnull_ clipmenu_action(struct config *cfg, uint64_t hash) { - if (cfg->touch_on_select) { - _drop_(close) int content_dir_fd = open(get_cache_dir(cfg), O_RDONLY); - _drop_(close) int snip_fd = - open(get_line_cache_path(cfg), O_RDWR | O_CREAT, 0600); - expect(content_dir_fd >= 0 && snip_fd >= 0); - - _drop_(cs_destroy) struct clip_store cs; - expect(cs_init(&cs, snip_fd, content_dir_fd) == 0); - int ret = cs_make_newest(&cs, hash); - if (ret == -ENOENT) { - fprintf(stderr, "Selected clip no longer exists\n"); - return EXIT_FAILURE; - } - expect(ret == 0); - } - - run_clipserve(hash, NULL); - return 0; -} - -int main(int argc, char *argv[]) { - menu_set_user_args(argc, argv); - - _drop_(config_free) struct config cfg = setup("clipmenu"); - exec_man_on_help(argc, argv); - - return menu_prompt_and_act(&cfg, "clipmenu", clipmenu_action); -} diff --git a/clipmenu/src/clipmenud.c b/clipmenu/src/clipmenud.c deleted file mode 100644 index 446a2d7..0000000 --- a/clipmenu/src/clipmenud.c +++ /dev/null @@ -1,765 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "store.h" -#include "util.h" -#include "x.h" - -static Display *dpy; -static struct clip_store cs; -static struct config cfg; -static Window win; - -static int enabled = 1; -static int sig_pipe[2]; - -static Atom timestamp_atom; -static Atom incr_atom; -static struct incr_transfer *it_list; - -static struct cm_selections sels[CM_SEL_MAX]; - -static Time last_disable_time = 0; -static Time last_enable_time = 0; - -static bool has_owned_selections(void) { - for (size_t i = 0; i < CM_SEL_MAX; i++) { - if (cfg.owned_selections[i].active) { - return true; - } - } - return false; -} - -enum clip_text_source { - CLIP_TEXT_SOURCE_X, - CLIP_TEXT_SOURCE_MALLOC, - CLIP_TEXT_SOURCE_INVALID -}; - -struct clip_text { - char *data; - enum clip_text_source source; -}; - -static struct clip_text last_text[CM_SEL_MAX] = { - {NULL, CLIP_TEXT_SOURCE_MALLOC}, - {NULL, CLIP_TEXT_SOURCE_MALLOC}, - {NULL, CLIP_TEXT_SOURCE_MALLOC}, -}; -static struct timespec last_text_time[CM_SEL_MAX]; - -static void free_clip_text(struct clip_text *ct) { - expect(ct->source != CLIP_TEXT_SOURCE_INVALID); - - if (ct->data) { - if (ct->source == CLIP_TEXT_SOURCE_X) { - XFree(ct->data); - } else { - free(ct->data); - } - ct->data = NULL; - } - - ct->source = CLIP_TEXT_SOURCE_INVALID; -} - -static struct timespec get_monotonic_time(void) { - struct timespec ts; - expect(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); - return ts; -} - -static bool within_partial_merge_window(struct timespec current_time, - struct timespec last_time) { - time_t elapsed_secs = current_time.tv_sec - last_time.tv_sec; - - if (elapsed_secs < cfg.partial_merge_secs) { - return true; - } - if (elapsed_secs > cfg.partial_merge_secs) { - return false; - } - return current_time.tv_nsec <= last_time.tv_nsec; -} - -// cppcheck-suppress [constParameterCallback,unmatchedSuppression] -static Bool is_timestamp_event(Display *display _unused_, XEvent *event, - XPointer arg) { - const void *argp = arg; - return event->type == PropertyNotify && - event->xproperty.atom == *(const Atom *)argp; -} - -/** - * Get the current X server time by triggering a PropertyNotify. - */ -static Time get_current_server_time(void) { - XEvent ev; - XChangeProperty(dpy, win, timestamp_atom, XA_INTEGER, 32, PropModeReplace, - NULL, 0); - XIfEvent(dpy, &ev, is_timestamp_event, (XPointer)×tamp_atom); - return ev.xproperty.time; -} - -/** - * Check if a text s1 is a possible partial of s2. - * - * Chromium and some other badly behaved applications spam PRIMARY during - * selection, so if you're selecting the text "abc", you get three clips: "a", - * "ab", and "abc" (or "c", "bc", "abc" if selecting right to left). Attempt to - * detect these. It's possible we were not fast enough to get all of them, so - * unfortunately we can't check for strlen(s1)+1 either. It's also possible the - * user first expands, and then retracts the selection, so we need to handle - * that too. - */ -static bool is_possible_partial(const char *s1, const char *s2) { - size_t len1 = strlen(s1), len2 = strlen(s2); - - // Is one a prefix of the other? - if (strncmp(s1, s2, len1 < len2 ? len1 : len2) == 0) { - return true; - } - - // Is one a suffix of the other? - if (len1 < len2) { - return strcmp(s1, s2 + len2 - len1) == 0; - } else { - return strcmp(s2, s1 + len1 - len2) == 0; - } -} - -/** - * Retrieve the converted text put into our clip atom. In order for this to - * happen a conversion must have been performed in an earlier iteration with - * XConvertSelection. - */ -static struct clip_text get_clipboard_text(Atom clip_atom) { - struct clip_text ct = {NULL, CLIP_TEXT_SOURCE_X}; - unsigned char *cur_text; - Atom actual_type; - int actual_format; - unsigned long nitems, bytes_after; - - int res = XGetWindowProperty(dpy, win, clip_atom, 0L, (~0L), False, - AnyPropertyType, &actual_type, &actual_format, - &nitems, &bytes_after, &cur_text); - if (res != Success) { - return ct; - } - - if (actual_type == incr_atom) { - dbg("Unexpected INCR transfer detected\n"); - XFree(cur_text); - return ct; - } - - ct.data = (char *)cur_text; - - return ct; -} - -/** - * Return true if the given string contains any non-whitespace characters. - */ -static bool is_salient_text(const char *str) { - if (!str) { - return false; - } - - for (; *str; str++) { - if (!isspace((unsigned char)*str)) { - return true; - } - } - return false; -} - -/** - * Write the current enabled status to a designated status file. - */ -static void write_status(void) { - _drop_(close) int fd = - open(get_enabled_path(&cfg), O_WRONLY | O_CREAT, 0600); - die_on(fd < 0, "Failed to update status: %s\n", strerror(errno)); - dprintf(fd, "%d", (int)enabled); -} - -/** - * Return true if the given window title matches the title of the clipserve - * window. - */ -static bool is_clipserve(const char *win_title) { - return win_title && streq(win_title, "clipserve"); -} - -/** - * Determine if a window with the given title should be ignored based on user - * configuration. - */ -static bool is_ignored_window(char *win_title) { - if (!win_title || !cfg.ignore_window.set) { - return 0; - } - int ret = regexec(&cfg.ignore_window.rgx, win_title, 0, NULL, 0); - expect(ret == 0 || ret == REG_NOMATCH); - return !ret; -} - -/** - * Disable or enable clip collection based on received signals. - */ -static void handle_signal_event(int signo) { - dbg("Got signal %d\n", signo); - switch (signo) { - case SIGUSR1: - // If we're already disabled, we need to keep the original - // timestamp to properly filter all events that were queued during - // any part of the disabled period. - if (enabled) { - last_disable_time = get_current_server_time(); - } - enabled = 0; - dbg("Clipboard collection disabled by signal at time %lu\n", - (unsigned long)last_disable_time); - break; - case SIGUSR2: - // If we're already enabled, we need to keep the original timestamp - // so we don't mistakenly filter out valid messages. - if (!enabled) { - last_enable_time = get_current_server_time(); - } - enabled = 1; - dbg("Clipboard collection enabled by signal at time %lu\n", - (unsigned long)last_enable_time); - break; - } - write_status(); -} - -static void handle_signal_pipe(void) { - unsigned char sigs[32]; - ssize_t read_sz; - while ((read_sz = read(sig_pipe[0], sigs, sizeof(sigs))) > 0) { - for (ssize_t i = 0; i < read_sz; i++) { - handle_signal_event((int)sigs[i]); - } - } - if (read_sz < 0) { - expect(errno == EAGAIN || errno == EINTR); - } -} - -static void signal_handler(int signo) { - int saved_errno = errno; - unsigned char sig = (unsigned char)signo; - ssize_t written = write(sig_pipe[1], &sig, sizeof(sig)); - (void)written; - errno = saved_errno; -} - -static void set_fd_flags(int fd) { - int flags = fcntl(fd, F_GETFL); - expect(flags >= 0); - expect(fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0); - - int fd_flags = fcntl(fd, F_GETFD); - expect(fd_flags >= 0); - expect(fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) == 0); -} - -/** - * Something changed about the watched selection, consider converting it to our - * desired property type. - */ -static void handle_xfixes_selection_notify(XFixesSelectionNotifyEvent *se) { - if (last_disable_time > 0 && se->timestamp >= last_disable_time && - se->timestamp < last_enable_time) { - dbg("Ignoring selection event from disabled period (event time: %lu, disabled: %lu, enabled: %lu)\n", - (unsigned long)se->timestamp, (unsigned long)last_disable_time, - (unsigned long)last_enable_time); - return; - } - - enum selection_type sel = - selection_atom_to_selection_type(se->selection, sels); - if (sel == CM_SEL_INVALID) { - dbg("Received XFixesSelectionNotify for unknown sel\n"); - return; - } - - _drop_(XFree) char *win_title = get_window_title(dpy, se->owner); - if (is_clipserve(win_title) || is_ignored_window(win_title)) { - dbg("Ignoring clip from window titled '%s'\n", win_title); - return; - } - - dbg("Notified about selection update. Selection: %s, Owner: '%s' (0x%lx)\n", - cfg.selections[sel].name, strnull(win_title), (unsigned long)se->owner); - - struct incr_transfer *it = it_list; - while (it) { - struct incr_transfer *next = it->next; - if (it->property == sels[sel].storage) { - it_dbg(it, "Cleaning up stale INCR transfer\n"); - free(it->data); - it_remove(&it_list, it); - free(it); - } - it = next; - } - - XConvertSelection(dpy, se->selection, - XInternAtom(dpy, "UTF8_STRING", False), sels[sel].storage, - win, CurrentTime); - - return; -} - -/** - * Something changed about the watched selection, but we don't explicitly - * listen for SelectionNotify, so in reality this only happens in response to - * an explicit request to tell us that there is no owner. In that case, return - * -ENOENT. - */ -static int handle_selection_notify(const XSelectionEvent *se) { - if (se->property == None) { - enum selection_type sel = - selection_atom_to_selection_type(se->selection, sels); - if (sel == CM_SEL_INVALID) { - dbg("Received no owner notification for unknown sel\n"); - return 0; - } - dbg("X reports that %s has no current owner\n", - cfg.selections[sel].name); - return -ENOENT; - } - return 0; -} - -/** - * Trims the clip store if the number of clips exceeds the configured batch - * size. - */ -static void maybe_trim(void) { - size_t cur_clips; - expect(cs_len(&cs, &cur_clips) == 0); - if (cur_clips > (size_t)cfg.max_clips + (size_t)cfg.max_clips_batch) { - expect(cs_trim(&cs, CS_ITER_NEWEST_FIRST, (size_t)cfg.max_clips) == 0); - } -} - -/** - * Store the clipboard text for a selection. If the text is a possible partial - * of the last clip on the same selection and it was received shortly - * afterwards, replace instead of adding. - */ -static uint64_t store_clip(enum selection_type sel, struct clip_text *ct) { - dbg("Clipboard text is considered salient, storing\n"); - struct timespec current_time = get_monotonic_time(); - uint64_t hash; - - if (cfg.partial_merge_secs > 0 && last_text[sel].data && - within_partial_merge_window(current_time, last_text_time[sel]) && - is_possible_partial(last_text[sel].data, ct->data)) { - dbg("Possible partial of last clip on %s, replacing\n", - cfg.selections[sel].name); - expect(cs_replace(&cs, CS_ITER_NEWEST_FIRST, 0, ct->data, &hash) == 0); - } else { - expect(cs_add(&cs, ct->data, &hash, - cfg.deduplicate ? CS_DUPE_KEEP_LAST : CS_DUPE_KEEP_ALL) == - 0); - } - - free_clip_text(&last_text[sel]); - last_text[sel] = *ct; - last_text_time[sel] = current_time; - - // The caller no longer owns this data. - ct->data = NULL; - ct->source = CLIP_TEXT_SOURCE_INVALID; - - return hash; -} - -/** - * Process the final data collected during an INCR transfer. - */ -static void incr_receive_finish(struct incr_transfer *it) { - enum selection_type sel = - storage_atom_to_selection_type(it->property, sels); - if (sel == CM_SEL_INVALID) { - it_dbg(it, "Received INCR finish for unknown sel\n"); - return; - } - - it_dbg(it, "Finished (bytes buffered: %zu)\n", it->data_size); - char *text = malloc(it->data_size + 1); - expect(text); - memcpy(text, it->data, it->data_size); - text[it->data_size] = '\0'; - - struct clip_text ct = {text, CLIP_TEXT_SOURCE_MALLOC}; - - char line[CS_SNIP_LINE_SIZE]; - first_line(ct.data, line); - it_dbg(it, "First line: %s\n", line); - - if (is_salient_text(ct.data)) { - uint64_t hash = store_clip(sel, &ct); - maybe_trim(); - if (cfg.own_clipboard && has_owned_selections()) { - run_clipserve(hash, cfg.owned_selections); - } - } else { - it_dbg(it, "Clipboard text is whitespace only, ignoring\n"); - free_clip_text(&ct); - } - - free(it->data); - it_remove(&it_list, it); - free(it); -} - -#define INCR_DATA_START_BYTES 1024 * 1024 - -/** - * Acknowledge and start an INCR transfer. - */ -static void incr_receive_start(const XPropertyEvent *pe) { - struct incr_transfer *it = malloc(sizeof(struct incr_transfer)); - expect(it); - *it = (struct incr_transfer){ - .property = pe->atom, - .requestor = pe->window, - .data_size = 0, - .data_capacity = INCR_DATA_START_BYTES, - .data = malloc(INCR_DATA_START_BYTES), - }; - expect(it->data); - - it_dbg(it, "Starting transfer\n"); - it_add(&it_list, it); - - // Signal readiness for chunks - XDeleteProperty(dpy, win, pe->atom); -} - -/** - * Continue receiving data during an INCR transfer. - */ -static void incr_receive_data(const XPropertyEvent *pe, - struct incr_transfer *it) { - if (pe->state != PropertyNewValue) { - return; - } - - it_dbg(it, "Receiving chunk (bytes buffered: %zu)\n", it->data_size); - - _drop_(XFree) unsigned char *chunk = NULL; - Atom actual_type; - int actual_format; - unsigned long nitems, bytes_after; - XGetWindowProperty(dpy, win, pe->atom, 0, LONG_MAX, False, AnyPropertyType, - &actual_type, &actual_format, &nitems, &bytes_after, - &chunk); - - size_t chunk_size = nitems * (actual_format / 8); - - if (chunk_size == 0) { - it_dbg(it, "Transfer complete\n"); - incr_receive_finish(it); - return; - } - - if (it->data_size + chunk_size > it->data_capacity) { - it->data_capacity = (it->data_size + chunk_size) * 2; - it->data = realloc(it->data, it->data_capacity); - expect(it->data); - it_dbg(it, "Expanded data buffer to %zu bytes\n", it->data_capacity); - } - - memcpy(it->data + it->data_size, chunk, chunk_size); - it->data_size += chunk_size; - - // Signal readiness for next chunk - XDeleteProperty(dpy, win, pe->atom); -} - -/** - * Something changed in our clip storage atoms. Work out whether we want to - * store the new content as a clipboard entry. - */ -static int handle_property_notify(const XPropertyEvent *pe) { - if (pe->state != PropertyNewValue && pe->state != PropertyDelete) { - return -EINVAL; - } - - enum selection_type sel = storage_atom_to_selection_type(pe->atom, sels); - if (sel == CM_SEL_INVALID) { - dbg("Received PropertyNotify for unknown sel\n"); - return -EINVAL; - } - - // Check if this property corresponds to an INCR transfer in progress - struct incr_transfer *it = it_list; - while (it) { - if (it->property == pe->atom && it->requestor == pe->window) { - break; - } - it = it->next; - } - - if (it) { - incr_receive_data(pe, it); - return 0; - } - - // Not an INCR transfer in progress. Check if this is an INCR transfer - // starting - Atom actual_type; - int actual_format; - unsigned long nitems, bytes_after; - _drop_(XFree) unsigned char *prop = NULL; - - XGetWindowProperty(dpy, win, pe->atom, 0, 0, False, AnyPropertyType, - &actual_type, &actual_format, &nitems, &bytes_after, - &prop); - - if (actual_type == incr_atom) { - incr_receive_start(pe); - } else { - dbg("Received non-INCR PropertyNotify\n"); - - // store_clip will take care of freeing this later when it's gone from - // last_text. - struct clip_text ct = get_clipboard_text(pe->atom); - if (!ct.data) { - dbg("Failed to get clipboard text\n"); - return -EINVAL; - } - char line[CS_SNIP_LINE_SIZE]; - first_line(ct.data, line); - dbg("First line: %s\n", line); - - if (is_salient_text(ct.data)) { - uint64_t hash = store_clip(sel, &ct); - maybe_trim(); - /* We only own CLIPBOARD because otherwise the behaviour is wonky: - * - * 1. When you select in a browser and press ^V, it repastes what - * you have selected instead of the previous content - * 2. urxvt and some other terminal emulators will unhilight on - * PRIMARY ownership being taken away from them - */ - if (cfg.own_clipboard && has_owned_selections()) { - run_clipserve(hash, cfg.owned_selections); - } - } else { - dbg("Clipboard text is whitespace only, ignoring\n"); - free_clip_text(&ct); - } - } - - return 0; -} - -/** - * Process X11 events, returning when we have either processed one clip, or - * have received an indication that the selection is not owned. - * - * The usual sequence is: - * - * 1. Get an XFixesSelectionNotify that we have a new selection. - * 2. Call XConvertSelection() on it to get a string in our prop. - * 3. Wait for a PropertyNotify that says that's ready. - * 4. When it's ready, store it, and return from the function. - * - * Another possible outcome, especially when trying to get the initial state at - * startup, is that we get a SelectionNotify even with owner == None, which - * means the selection is unowned. At that point we also return, since it's - * clear that an explicit request has been nacked. - */ -static int handle_x11_event(int evt_base) { - while (XPending(dpy)) { - XEvent evt; - XNextEvent(dpy, &evt); - - if (!enabled) { - dbg("Got X event, but ignoring as collection is disabled\n"); - continue; - } - - int ret; - if (evt.type == evt_base + XFixesSelectionNotify) { - handle_xfixes_selection_notify((XFixesSelectionNotifyEvent *)&evt); - } else if (evt.type == PropertyNotify) { - ret = handle_property_notify((XPropertyEvent *)&evt); - if (ret == 0) { - return ret; - } - } else if (evt.type == SelectionNotify) { - ret = handle_selection_notify((XSelectionEvent *)&evt); - if (ret < 0) { - return ret; - } - } - } - - return -EINPROGRESS; -} - -/** - * Continuously wait for and process X11 or signal events until we fully - * process success or failure for a clip. - * - * The usual sequence is: - * - * 1. Get an XFixesSelectionNotify that we have a new selection. - * 2. Call XConvertSelection() on it to get a string in our prop. - * 3. Wait for a PropertyNotify that says that's ready. - * 4. When it's ready, store it, and return from the function. - * - * Another possible outcome, especially when trying to get the initial state at - * startup, is that we get a SelectionNotify even with owner == None, which - * means the selection is unowned. At that point we also return, since it's - * clear that an explicit request has been nacked. - */ -static int get_one_clip(int evt_base) { - while (1) { - // It's possible that we have more X events to process, but because of - // the way the protocol works, we won't get told about them until we - // next get an event if we wait for select(). Check for them first. - if (XPending(dpy)) { - return handle_x11_event(evt_base); - } - - fd_set fds; - int x_fd = ConnectionNumber(dpy); - - FD_ZERO(&fds); - FD_SET(sig_pipe[0], &fds); - FD_SET(x_fd, &fds); - - int max_fd = sig_pipe[0] > x_fd ? sig_pipe[0] : x_fd; - int ret = select(max_fd + 1, &fds, NULL, NULL, NULL); - if (ret < 0) { - if (errno == EINTR) { - return 0; - } - expect(ret > 0); - } - - if (FD_ISSET(sig_pipe[0], &fds)) { - handle_signal_pipe(); - } - - if (FD_ISSET(x_fd, &fds)) { - return handle_x11_event(evt_base); - } - } -} - -static int setup_watches(int evt_base) { - XSelectInput(dpy, win, PropertyChangeMask); - - for (size_t i = 0; i < CM_SEL_MAX; i++) { - struct selection sel = cfg.selections[i]; - if (!sel.active) { - continue; - } - Atom sel_atom = sels[i].selection; - XFixesSelectSelectionInput(dpy, win, sel_atom, - XFixesSetSelectionOwnerNotifyMask); - dbg("Getting initial value for selection %s\n", sel.name); - XConvertSelection(dpy, sel_atom, XInternAtom(dpy, "UTF8_STRING", False), - sels[i].storage, win, CurrentTime); - get_one_clip(evt_base); - } - - return 0; -} - -static int _noreturn_ run(int evt_base) { - while (1) { - get_one_clip(evt_base); - } -} - -#ifndef UNIT_TEST -int main(int argc, char *argv[]) { - int evt_base; - - cfg = setup("clipmenud"); - exec_man_on_help(argc, argv); - die_on(argc != 1, "clipmenud doesn't accept any arguments\n"); - - _drop_(close) int session_fd = - open(get_session_lock_path(&cfg), O_RDWR | O_CREAT | O_CLOEXEC, 0600); - die_on(session_fd < 0, "Failed to open session file: %s\n", - strerror(errno)); - die_on(flock(session_fd, LOCK_EX | LOCK_NB) < 0, - "Failed to lock session file -- is another clipmenud running?\n"); - - expect(ftruncate(session_fd, 0) == 0); - expect(lseek(session_fd, 0, SEEK_SET) == 0); - dprintf(session_fd, "%ld\n", (long)getpid()); - - write_status(); - - _drop_(close) int content_dir_fd = open(get_cache_dir(&cfg), O_RDONLY); - _drop_(close) int snip_fd = - open(get_line_cache_path(&cfg), O_RDWR | O_CREAT, 0600); - expect(content_dir_fd >= 0 && snip_fd >= 0); - - expect(cs_init(&cs, snip_fd, content_dir_fd) == 0); - - die_on(!(dpy = XOpenDisplay(NULL)), "Cannot open display\n"); - win = DefaultRootWindow(dpy); - setup_selections(dpy, sels); - - incr_atom = XInternAtom(dpy, "INCR", False); - timestamp_atom = XInternAtom(dpy, "CLIPMENUD_TIMESTAMP", False); - - expect(pipe(sig_pipe) == 0); - set_fd_flags(sig_pipe[0]); - set_fd_flags(sig_pipe[1]); - - struct sigaction sa = {0}; - sa.sa_handler = signal_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - expect(sigaction(SIGUSR1, &sa, NULL) == 0); - expect(sigaction(SIGUSR2, &sa, NULL) == 0); - expect(signal(SIGCHLD, SIG_IGN) != SIG_ERR); - - int unused; - die_on(!XFixesQueryExtension(dpy, &evt_base, &unused), "XFixes missing\n"); - - setup_watches(evt_base); - - if (!cfg.oneshot) { - run(evt_base); - } - - expect(cs_destroy(&cs) == 0); - config_free(&cfg); - XCloseDisplay(dpy); - return 0; -} -#endif diff --git a/clipmenu/src/clipserve.c b/clipmenu/src/clipserve.c deleted file mode 100644 index 9d490cd..0000000 --- a/clipmenu/src/clipserve.c +++ /dev/null @@ -1,286 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "store.h" -#include "util.h" -#include "x.h" - -static struct incr_transfer *it_list = NULL; -static Display *dpy; -static Atom incr_atom; - -static size_t incr_threshold; -static size_t chunk_size; - -static enum selection_type selection_name_to_type(const char *name) { - if (streq(name, "clipboard")) { - return CM_SEL_CLIPBOARD; - } - if (streq(name, "primary")) { - return CM_SEL_PRIMARY; - } - if (streq(name, "secondary")) { - return CM_SEL_SECONDARY; - } - return CM_SEL_INVALID; -} - -/** - * Start an INCR transfer. - */ -static void incr_send_start(XSelectionRequestEvent *req, - struct cs_content *content) { - long incr_size = content->size; - XChangeProperty(dpy, req->requestor, req->property, incr_atom, 32, - PropModeReplace, (unsigned char *)&incr_size, 1); - - struct incr_transfer *it = malloc(sizeof(struct incr_transfer)); - expect(it); - *it = (struct incr_transfer){ - .requestor = req->requestor, - .property = req->property, - .target = req->target, - .format = 8, - .data = (char *)content->data, - .data_size = content->size, - .offset = 0, - }; - - it_dbg(it, "Starting transfer\n"); - it_add(&it_list, it); - - // Listen for PropertyNotify events on the requestor window - XSelectInput(dpy, it->requestor, PropertyChangeMask); -} - -/** - * Finish an INCR transfer. - */ -static void incr_send_finish(struct incr_transfer *it) { - XChangeProperty(dpy, it->requestor, it->property, it->target, it->format, - PropModeReplace, NULL, 0); - it_dbg(it, "Transfer complete\n"); - it_remove(&it_list, it); - free(it); -} - -/** - * Continue sending data during an INCR transfer. - */ -static void incr_send_chunk(const XPropertyEvent *pe) { - if (pe->state != PropertyDelete) { - return; - } - - struct incr_transfer *it = it_list; - while (it) { - if (it->requestor == pe->window && it->property == pe->atom) { - size_t remaining = it->data_size - it->offset; - size_t this_chunk_size = - (remaining > chunk_size) ? chunk_size : remaining; - - it_dbg(it, - "Sending chunk (bytes sent: %zu, bytes remaining: %zu)\n", - it->offset, remaining); - - if (this_chunk_size > 0) { - XChangeProperty(dpy, it->requestor, it->property, it->target, - it->format, PropModeReplace, - (unsigned char *)(it->data + it->offset), - this_chunk_size); - it->offset += this_chunk_size; - } else { - incr_send_finish(it); - } - break; - } - it = it->next; - } -} - -/** - * Serve clipboard content for all X11 selection requests until all selections - * have been claimed by another application. - */ -static void _nonnull_ serve_clipboard(uint64_t hash, struct cs_content *content, - const bool selection_active[CM_SEL_MAX]) { - bool running = true; - XEvent evt; - Atom targets, utf8_string; - Atom selection_atoms[CM_SEL_MAX]; - Window win; - int remaining_selections; - size_t selection_atoms_len = 0; - - dpy = XOpenDisplay(NULL); - expect(dpy); - - // Threshold and payload size are intentionally different: the server's - // max request size is a reasonable INCR cutoff, but too aggressive as an - // outbound chunk size for some requestors. - incr_threshold = get_incr_threshold(dpy); - chunk_size = get_chunk_size(dpy); - - win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 1, 1, 0, 0, 0); - XStoreName(dpy, win, "clipserve"); - targets = XInternAtom(dpy, "TARGETS", False); - utf8_string = XInternAtom(dpy, "UTF8_STRING", False); - incr_atom = XInternAtom(dpy, "INCR", False); - - if (selection_active[CM_SEL_PRIMARY]) { - selection_atoms[selection_atoms_len++] = XA_PRIMARY; - } - if (selection_active[CM_SEL_CLIPBOARD]) { - selection_atoms[selection_atoms_len++] = - XInternAtom(dpy, "CLIPBOARD", False); - } - if (selection_active[CM_SEL_SECONDARY]) { - selection_atoms[selection_atoms_len++] = XA_SECONDARY; - } - - die_on(selection_atoms_len == 0, "No selections configured\n"); - - for (size_t i = 0; i < selection_atoms_len; i++) { - bool success = false; - for (int attempts = 0; attempts < 5; attempts++) { - XSetSelectionOwner(dpy, selection_atoms[i], win, CurrentTime); - - // According to ICCCM 2.1, a client acquiring a selection should - // confirm success by verifying with GetSelectionOwner. - if (XGetSelectionOwner(dpy, selection_atoms[i]) == win) { - success = true; - break; - } - } - if (!success) { - die("Failed to set selection for %s\n", - XGetAtomName(dpy, selection_atoms[i])); - } - } - remaining_selections = (int)selection_atoms_len; - - while (running) { - XNextEvent(dpy, &evt); - switch (evt.type) { - case SelectionRequest: { - XSelectionRequestEvent *req = &evt.xselectionrequest; - XSelectionEvent sev = {.type = SelectionNotify, - .display = req->display, - .requestor = req->requestor, - .selection = req->selection, - .time = req->time, - .target = req->target, - .property = req->property}; - - // cppcheck-suppress constVariablePointer - // _drop_(XFree) passes this to XFree, so keep it non-const. - _drop_(XFree) char *window_title = - get_window_title(dpy, req->requestor); - dbg("Servicing request to window '%s' (0x%lX) for clip " PRI_HASH - "\n", - strnull(window_title), (unsigned long)req->requestor, hash); - - if (req->target == targets) { - Atom available_targets[] = {utf8_string, XA_STRING}; - XChangeProperty(dpy, req->requestor, req->property, XA_ATOM, - 32, PropModeReplace, - (unsigned char *)&available_targets, - arrlen(available_targets)); - } else if (req->target == utf8_string || - req->target == XA_STRING) { - if (content->size < (off_t)incr_threshold) { - // Data size is small enough, send directly - XChangeProperty(dpy, req->requestor, req->property, - req->target, 8, PropModeReplace, - (unsigned char *)content->data, - (int)content->size); - } else { - // Initiate INCR transfer - incr_send_start(req, content); - } - } else { - sev.property = None; - } - - XSendEvent(dpy, req->requestor, False, 0, (XEvent *)&sev); - break; - } - case SelectionClear: { - if (--remaining_selections == 0) { - dbg("Finished serving clip " PRI_HASH "\n", hash); - running = false; - } else { - dbg("%d selections remaining to serve for clip " PRI_HASH - "\n", - remaining_selections, hash); - } - break; - } - case PropertyNotify: { - incr_send_chunk(&evt.xproperty); - break; - } - } - } - - XCloseDisplay(dpy); -} - -int main(int argc, char *argv[]) { - _drop_(config_free) struct config cfg = setup("clipserve"); - exec_man_on_help(argc, argv); - - bool selection_active[CM_SEL_MAX] = {0}; - bool selection_set = false; - int argi = 1; - for (; argi < argc; argi++) { - if (streq(argv[argi], "--")) { - argi++; - break; - } - if (streq(argv[argi], "-s") || streq(argv[argi], "--selection")) { - die_on(argi + 1 >= argc, - "Usage: clipserve [-s selection]... \n"); - enum selection_type sel = selection_name_to_type(argv[++argi]); - die_on(sel == CM_SEL_INVALID, "Unknown selection: %s\n", - argv[argi]); - selection_active[sel] = true; - selection_set = true; - continue; - } - break; - } - - die_on(argi != argc - 1, "Usage: clipserve [-s selection]... \n"); - - if (!selection_set) { - selection_active[CM_SEL_PRIMARY] = true; - selection_active[CM_SEL_CLIPBOARD] = true; - } - - uint64_t hash; - expect(str_to_hex64(argv[argi], &hash) == 0); - - _drop_(close) int content_dir_fd = open(get_cache_dir(&cfg), O_RDONLY); - _drop_(close) int snip_fd = - open(get_line_cache_path(&cfg), O_RDWR | O_CREAT, 0600); - expect(content_dir_fd >= 0 && snip_fd >= 0); - - _drop_(cs_destroy) struct clip_store cs; - expect(cs_init(&cs, snip_fd, content_dir_fd) == 0); - - _drop_(cs_content_unmap) struct cs_content content; - die_on(cs_content_get(&cs, hash, &content) < 0, - "Hash " PRI_HASH " inaccessible\n", hash); - - serve_clipboard(hash, &content, selection_active); - - return 0; -} diff --git a/clipmenu/src/config.c b/clipmenu/src/config.c deleted file mode 100644 index 10ba62c..0000000 --- a/clipmenu/src/config.c +++ /dev/null @@ -1,500 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "x.h" - -/** - * Determines the runtime directory for storing application data. This is _not_ - * the clip store, but the place to create the directory that will become it - * (unless CM_DIR is set, in which case we use that directly). - */ -static const char *get_runtime_directory(void) { - static const char *runtime_dir = NULL; - const char *env_vars[] = {"XDG_RUNTIME_DIR", "TMPDIR"}; - - if (runtime_dir) { - return runtime_dir; - } - - for (size_t i = 0; i < arrlen(env_vars); i++) { - const char *dir = getenv(env_vars[i]); - if (dir) { - return (runtime_dir = dir); - } - } - - return (runtime_dir = "/tmp"); -} - -/** - * Constructs the path to the clip store directory for clipmenu, creating the - * directory if it does not exist. - */ -char *get_cache_dir(struct config *cfg) { - expect(cfg->ready); - static char cache_dir[PATH_MAX]; - // In case config changed, do the write anyway - snprintf_safe(cache_dir, PATH_MAX, "%s/clipmenu.%d.%ld", cfg->runtime_dir, - CLIPMENU_VERSION, (long)getuid()); - die_on(mkdir(cache_dir, S_IRWXU) != 0 && errno != EEXIST, - "Failed to create directory: %s\n", cache_dir); - return cache_dir; -} - -static bool is_absolute_path(const char *path) { - return path && path[0] == '/'; -} - -static int resolve_path(const char *input, void *output) { - char **out_path = output; - if (!input) { - return -EINVAL; - } - - if (input[0] == '~') { - if (input[1] != '\0' && input[1] != '/') { - return -EINVAL; - } - const char *home = getenv("HOME"); - if (!home) { - return -EINVAL; - } - size_t len = strlen(home) + strlen(input); - char *expanded = malloc(len); - expect(expanded); - snprintf_safe(expanded, len, "%s%s", home, input + 1); - *out_path = expanded; - } else { - char *dup = strdup(input); - expect(dup); - *out_path = dup; - } - - if (!is_absolute_path(*out_path)) { - free(*out_path); - *out_path = NULL; - return -EINVAL; - } - - return 0; -} - -/** - * This whole section consists of conversion functions to go from a string in - * the config file to the type we expect for `struct Config`. - */ - -static int convert_bool(const char *str, void *output) { - const char *const truthy[] = {"1", "y", "yes", "true", "on"}; - const char *const falsy[] = {"0", "n", "no", "false", "off"}; - - for (size_t i = 0; i < arrlen(truthy); i++) { - if (strceq(str, truthy[i])) { - *(bool *)output = true; - return 0; - } - } - - for (size_t i = 0; i < arrlen(falsy); i++) { - if (strceq(str, falsy[i])) { - *(bool *)output = false; - return 0; - } - } - - return -EINVAL; -} - -static int convert_positive_int(const char *str, void *output) { - char *end; - long val = strtol(str, &end, 10); - if (*end != '\0' || end == str || val < 0 || val > INT_MAX) { - return -EINVAL; - } - *(int *)output = (int)val; - return 0; -} - -static int convert_ignore_window(const char *str, void *output) { - struct ignore_window *iw = output; - iw->set = (bool)str; - if (!iw->set) { - return 0; - } - if (regcomp(&iw->rgx, str, REG_EXTENDED | REG_NOSUB)) { - return -EINVAL; - } - return 0; -} - -static int convert_cm_dir(const char *str, void *output) { - if (!str) { - str = get_runtime_directory(); - } - return resolve_path(str, output); -} - -static int _nonnull_ convert_launcher(const char *str, void *output) { - struct launcher *lnch = output; - - lnch->custom = strdup(str); - expect(lnch->custom); - - if (streq(str, "rofi")) { - lnch->ltype = LAUNCHER_ROFI; - } else { - lnch->ltype = LAUNCHER_CUSTOM; - } - - return 0; -} - -#define DEFAULT_SELECTION_STATE(name) \ - (struct selection) { name, 0, NULL } - -static char *skip_whitespace(char *str) { - while (*str != '\0' && isspace((unsigned char)*str)) { - str++; - } - - return str; -} - -static int convert_selections(const char *str, void *output) { - struct selection *sels = malloc(3 * sizeof(struct selection)); - expect(sels); - sels[CM_SEL_CLIPBOARD] = DEFAULT_SELECTION_STATE("clipboard"); - sels[CM_SEL_PRIMARY] = DEFAULT_SELECTION_STATE("primary"); - sels[CM_SEL_SECONDARY] = DEFAULT_SELECTION_STATE("secondary"); - - _drop_(free) char *inner_str = strdup(str); - expect(inner_str); - char *next = skip_whitespace(inner_str); - while (*next != '\0') { - const char *token = next; - while (*next != '\0' && !isspace((unsigned char)*next)) { - next++; - } - if (*next != '\0') { - *next++ = '\0'; - next = skip_whitespace(next); - } - - bool found = false; - for (size_t i = 0; i < CM_SEL_MAX; i++) { - if (streq(token, sels[i].name)) { - sels[i].active = true; - found = true; - break; - } - } - if (!found) { - return -EINVAL; - } - } - - *(struct selection **)output = sels; - - return 0; -} - -/** - * Constructs the path to the clipmenu configuration file. The user can - * manually specify a path with $CM_CONFIG, otherwise, it's inferred based on - * $XDG_CONFIG_HOME or ~/.config. It's typically - * ~/.config/clipmenu/clipmenu.conf. - */ - -static int get_config_file(char *config_path, size_t config_path_len) { - const char *cm_config = getenv("CM_CONFIG"); - const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); - const char *home = getenv("HOME"); - _drop_(free) char *resolved = NULL; - - if (cm_config) { - int ret = resolve_path(cm_config, &resolved); - if (ret != 0) { - fprintf(stderr, - "Invalid config path from $CM_CONFIG: expected absolute " - "path or leading ~\n"); - return ret; - } - snprintf_safe(config_path, config_path_len, "%s", resolved); - } else if (xdg_config_home) { - int ret = resolve_path(xdg_config_home, &resolved); - if (ret != 0) { - fprintf(stderr, - "Invalid config path from $XDG_CONFIG_HOME: expected " - "absolute path or leading ~\n"); - return ret; - } - snprintf_safe(config_path, config_path_len, "%s/clipmenu/clipmenu.conf", - resolved); - } else { - die_on(!home, - "None of $CM_CONFIG, $XDG_CONFIG_HOME, or $HOME is set\n"); - int ret = resolve_path(home, &resolved); - if (ret != 0) { - fprintf(stderr, - "Invalid config path from $HOME: expected absolute path or " - "leading ~\n"); - return ret; - } - snprintf_safe(config_path, config_path_len, - "%s/.config/clipmenu/clipmenu.conf", resolved); - } - - return 0; -} - -static int config_parse_env_vars(struct config_entry entries[], - size_t entries_len) { - for (size_t i = 0; i < entries_len; ++i) { - const char *env_var = entries[i].env_var; - if (!env_var) { - continue; - } - const char *env_value = getenv(env_var); - if (env_value && entries[i].convert(env_value, entries[i].value) != 0) { - fprintf(stderr, "Error parsing environment variable for $%s\n", - env_var); - return -EINVAL; - } else if (env_value) { - dbg("Config entry %s is set to %s by $%s\n", entries[i].config_key, - env_value, env_var); - entries[i].is_set = true; - } - } - - return 0; -} - -static int config_parse_file(FILE *file, struct config_entry entries[], - size_t entries_len) { - if (!file) { - return 0; - } - - char line[256]; - while (fgets(line, sizeof(line), file)) { - char *key = skip_whitespace(line); - if (*key == '\0' || *key == '#') { - continue; - } - - char *value = key; - while (*value != '\0' && !isspace((unsigned char)*value)) { - value++; - } - if (*value == '\0') { - continue; - } - *value++ = '\0'; - value = skip_whitespace(value); - - char *newline = strpbrk(value, "\r\n"); - if (newline) { - *newline = '\0'; - } - - for (size_t i = 0; i < entries_len; ++i) { - if (!entries[i].is_set && streq(entries[i].config_key, key)) { - if (entries[i].convert(value, entries[i].value) != 0) { - fprintf(stderr, "Error parsing config file for %s\n", - entries[i].config_key); - return -EINVAL; - } - dbg("Config entry %s is set to %s by config file\n", - entries[i].config_key, value); - entries[i].is_set = true; - break; - } - } - } - - return 0; -} - -static int config_apply_default_values(struct config_entry entries[], - size_t entries_len) { - for (size_t i = 0; i < entries_len; ++i) { - if (!entries[i].is_set) { - if (entries[i].convert(entries[i].default_value, - entries[i].value) != 0) { - fprintf(stderr, "Error setting default value for %s\n", - entries[i].config_key); - return -EINVAL; - } - dbg("Config entry %s is set to %s by fallback\n", - entries[i].config_key, entries[i].default_value); - } - } - return 0; -} - -/** - * Parse the clipmenu configuration file and environment variables to set up - * the application configuration. - * - * Prior to version 7, clipmenu and friends could only be configured via - * environment variables, so these are supported for backwards compatibility. - * In general, it's more straightforward to use the config file nowadays. - * - * This is generally not expected to be called by applications -- call - * config_setup() instead, which provides the right file for you. - */ -static int config_setup_internal(FILE *file, struct config *cfg) { - struct config_entry entries[] = { - {"max_clips", "CM_MAX_CLIPS", &cfg->max_clips, convert_positive_int, - "1000", 0}, - {"max_clips_batch", "CM_MAX_CLIPS_BATCH", &cfg->max_clips_batch, - convert_positive_int, "100", 0}, - {"oneshot", "CM_ONESHOT", &cfg->oneshot, convert_positive_int, "0", 0}, - {"deduplicate", "CM_DEDUPLICATE", &cfg->deduplicate, convert_bool, "0", - 0}, - {"own_clipboard", "CM_OWN_CLIPBOARD", &cfg->own_clipboard, convert_bool, - "0", 0}, - {"selections", "CM_SELECTIONS", &cfg->selections, convert_selections, - "clipboard primary", 0}, - {"own_selections", "CM_OWN_SELECTIONS", &cfg->owned_selections, - convert_selections, "clipboard", 0}, - {"ignore_window", "CM_IGNORE_WINDOW", &cfg->ignore_window, - convert_ignore_window, NULL, 0}, - {"launcher", "CM_LAUNCHER", &cfg->launcher, convert_launcher, "dmenu", - 0}, - {"launcher_pass_dmenu_args", "CM_LAUNCHER_PASS_DMENU_ARGS", - &cfg->launcher_pass_dmenu_args, convert_bool, "1", 0}, - {"touch_on_select", NULL, &cfg->touch_on_select, convert_bool, "0", 0}, - {"partial_merge_secs", NULL, &cfg->partial_merge_secs, - convert_positive_int, "2", 0}, - {"cm_dir", "CM_DIR", &cfg->runtime_dir, convert_cm_dir, NULL, 0}}; - - size_t entries_len = arrlen(entries); - - int ret = config_parse_env_vars(entries, entries_len); - if (ret < 0) { - return ret; - } - ret = config_parse_file(file, entries, entries_len); - if (ret < 0) { - return ret; - } - ret = config_apply_default_values(entries, entries_len); - if (ret < 0) { - return ret; - } - - cfg->ready = true; - - return 0; -} - -/** - * Frees dynamically allocated memory within the config structure. - */ -void config_free(struct config *cfg) { - free(cfg->runtime_dir); - free(cfg->launcher.custom); - free(cfg->selections); - free(cfg->owned_selections); - if (cfg->ignore_window.set) { - regfree(&cfg->ignore_window.rgx); - } -} - -/** - * Initialise the clipmenu configuration by loading settings from environment - * variables and the configuration file. - */ -static void config_setup(struct config *cfg) { - char config_path[PATH_MAX]; - die_on(get_config_file(config_path, sizeof(config_path)) != 0, - "Invalid config\n"); - _drop_(fclose) FILE *file = fopen(config_path, "r"); - expect(file || errno == ENOENT); - die_on(config_setup_internal(file, cfg) != 0, "Invalid config\n"); -} - -static char stdout_buf[512]; -const char *prog_name = "broken"; - -/** - * Performs initial setup for clipmenu applications, including setting the - * program name (used for dbg()), making sure stdout is line buffered, getting - * the config, and setting up X11 error handling. - */ -struct config setup(const char *inner_prog_name) { - struct config cfg; - prog_name = inner_prog_name; - expect(setvbuf(stdout, stdout_buf, _IOLBF, sizeof(stdout_buf)) == 0); - config_setup(&cfg); - XSetErrorHandler(xerror_handler); - return cfg; -} - -void setup_selections(Display *dpy, struct cm_selections *sels) { - sels[CM_SEL_CLIPBOARD].selection = XInternAtom(dpy, "CLIPBOARD", False); - sels[CM_SEL_CLIPBOARD].storage = - XInternAtom(dpy, "CLIPMENUD_CUR_CLIPBOARD", False); - sels[CM_SEL_PRIMARY].selection = XA_PRIMARY; - sels[CM_SEL_PRIMARY].storage = - XInternAtom(dpy, "CLIPMENUD_CUR_PRIMARY", False); - sels[CM_SEL_SECONDARY].selection = XA_SECONDARY; - sels[CM_SEL_SECONDARY].storage = - XInternAtom(dpy, "CLIPMENUD_CUR_SECONDARY", False); -} - -/** - * Maps an Atom to a selection_type based on the selection atoms in the - * provided cm_selections struct. Returns CM_SEL_INVALID on error if the - * selection type is not found. - */ -enum selection_type -selection_atom_to_selection_type(Atom atom, const struct cm_selections *sels) { - for (size_t i = 0; i < CM_SEL_MAX; ++i) { - if (sels[i].selection == atom) { - return i; - } - } - return CM_SEL_INVALID; -} - -/** - * Maps an Atom to a selection_type based on the storage atoms in the provided - * cm_selections struct. Returns CM_SEL_INVALID on error if the storage type is - * not found. - */ -enum selection_type -storage_atom_to_selection_type(Atom atom, const struct cm_selections *sels) { - for (size_t i = 0; i < CM_SEL_MAX; ++i) { - if (sels[i].storage == atom) { - return i; - } - } - return CM_SEL_INVALID; -} - -void exec_man(void) { - execlp("man", "man", prog_name, NULL); - die("Failed to exec man: %s\n", strerror(errno)); -} - -// cppcheck doesn't know this comes from an outside type -// cppcheck-suppress [constParameter,unmatchedSuppression] -void exec_man_on_help(int argc, char *argv[]) { - for (int i = 1; i < argc; i++) { - if (streq(argv[i], "--")) { - break; - } - if (streq(argv[i], "-h") || streq(argv[i], "--help")) { - exec_man(); - } - } -} diff --git a/clipmenu/src/config.h b/clipmenu/src/config.h deleted file mode 100644 index 949ebd8..0000000 --- a/clipmenu/src/config.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef CM_CONFIG_H -#define CM_CONFIG_H - -#include -#include -#include -#include -#include - -#include "util.h" - -#define CLIPMENU_VERSION 7 - -struct selection { - const char *name; - bool active; - Atom *atom; -}; -enum selection_type { - CM_SEL_CLIPBOARD, - CM_SEL_PRIMARY, - CM_SEL_SECONDARY, - CM_SEL_MAX, - CM_SEL_INVALID -}; -struct cm_selections { - Atom selection; - Atom storage; -}; -struct ignore_window { - bool set; - regex_t rgx; -}; -enum launcher_known { - LAUNCHER_ROFI, - LAUNCHER_CUSTOM, -}; -struct launcher { - enum launcher_known ltype; - char *custom; -}; -struct config { - bool ready; - bool debug; - char *runtime_dir; - int max_clips; - int max_clips_batch; - int oneshot; - bool deduplicate; - bool own_clipboard; - struct selection *owned_selections; - struct selection *selections; - struct ignore_window ignore_window; - struct launcher launcher; - bool launcher_pass_dmenu_args; - bool touch_on_select; - int partial_merge_secs; -}; -typedef int (*conversion_func_t)(const char *, void *); -struct config_entry { - const char *config_key; - const char *env_var; - void *value; - conversion_func_t convert; - const char *default_value; - bool is_set; -}; - -char *get_cache_dir(struct config *cfg); - -void exec_man(void); -void exec_man_on_help(int argc, char *argv[]); - -/** - * Define a function that generates and caches a path within the application's - * cache directory. - * - * For example, DEFINE_GET_PATH_FUNCTION(foo) defines a function `get_foo_path` - * that returns the path to "foo" within the cache directory. - */ -#define DEFINE_GET_PATH_FUNCTION(name) \ - static inline char *get_##name##_path(struct config *cfg) { \ - static char path[PATH_MAX]; \ - /* Just in case the config changed, do the write anyway */ \ - snprintf_safe(path, PATH_MAX, "%s/" #name, get_cache_dir(cfg)); \ - return path; \ - } - -DEFINE_GET_PATH_FUNCTION(line_cache) -DEFINE_GET_PATH_FUNCTION(enabled) -DEFINE_GET_PATH_FUNCTION(session_lock) - -extern const char *prog_name; -struct config _nonnull_ setup(const char *inner_prog_name); -void _nonnull_ setup_selections(Display *dpy, struct cm_selections *sels); -enum selection_type _nonnull_ -selection_atom_to_selection_type(Atom atom, const struct cm_selections *sels); -enum selection_type _nonnull_ -storage_atom_to_selection_type(Atom atom, const struct cm_selections *sels); - -void config_free(struct config *cfg); -DEFINE_DROP_FUNC_PTR(struct config, config_free) - -#endif diff --git a/clipmenu/src/menu_util.c b/clipmenu/src/menu_util.c deleted file mode 100644 index 3358c43..0000000 --- a/clipmenu/src/menu_util.c +++ /dev/null @@ -1,302 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "menu_util.h" -#include "store.h" -#include "util.h" - -#define MAX_ARGS 32 -/* "[N] " prefix + snip line + " (N lines)\n" suffix + NUL */ -#define LAUNCHER_LINE_MAX \ - (1 + UINT64_MAX_STRLEN + 2 + (CS_SNIP_LINE_SIZE - 1) + 2 + \ - UINT64_MAX_STRLEN + 7 + 1 + 1) - -static int dmenu_user_argc; -static char **dmenu_user_argv; - -/** - * Calculate the base 10 padding length for a number. - */ -static int get_padding_length(size_t num) { - int digits = 1; - while (num /= 10) { - digits++; - } - return digits; -} - -/** - * Execute the launcher. Called after fork() is already done in the new child. - */ -static void _noreturn_ _nonnull_ exec_launcher(struct config *cfg, - const char *prompt, - int *input_pipe, - int *output_pipe) { - dup2(input_pipe[0], STDIN_FILENO); - close(input_pipe[1]); - close(output_pipe[0]); - dup2(output_pipe[1], STDOUT_FILENO); - - const char *dmenu_args[] = {"-p", prompt, "-l", "20"}; - const char **cmd = malloc(MAX_ARGS * sizeof(char *)); - - size_t d_i = 0; - - switch (cfg->launcher.ltype) { - case LAUNCHER_ROFI: - cmd[d_i++] = "rofi"; - cmd[d_i++] = "--"; - cmd[d_i++] = "-dmenu"; - break; - case LAUNCHER_CUSTOM: - cmd[d_i++] = cfg->launcher.custom; - break; - default: - die("Unreachable\n"); - } - - if (cfg->launcher_pass_dmenu_args) { - expect(d_i + arrlen(dmenu_args) < MAX_ARGS); - for (size_t i = 0; i < arrlen(dmenu_args); i++) { - cmd[d_i++] = dmenu_args[i]; - } - } - - for (int i = 1; i < dmenu_user_argc && d_i < MAX_ARGS - 1; i++, d_i++) { - cmd[d_i] = dmenu_user_argv[i]; - } - - cmd[d_i] = NULL; - execvp(cmd[0], (char *const *)cmd); // SUS says cmd unchanged - die("Failed to exec %s: %s\n", cmd[0], strerror(errno)); -} - -static int dprintf_ellipsise_long_snip_line(int fd, const char *line) { - size_t line_len = strlen(line); - if (line_len == CS_SNIP_LINE_SIZE - 1) { - return dprintf(fd, "%.*s...", (int)(CS_SNIP_LINE_SIZE - 4), line); - } else { - return dprintf(fd, "%s", line); - } -} - -static int launcher_write_all(int fd, const char *buf, size_t count) { - while (count > 0) { - ssize_t written = write(fd, buf, count); - if (written < 0) { - if (errno == EINTR) { - continue; - } - return negative_errno(); - } - buf += written; - count -= (size_t)written; - } - return 0; -} - -static int wait_for_pid(pid_t pid, int *status) { - while (waitpid(pid, status, 0) < 0) { - if (errno == EINTR) { - continue; - } - return negative_errno(); - } - return 0; -} - -static int parse_sel_idx(const char *line, size_t cur_clips, size_t *out_idx) { - if (line[0] != '[') { - return -1; - } - const char *start = line + 1; - const char *end = strchr(start, ']'); - if (!end) { - return -1; - } - char idx_str[UINT64_MAX_STRLEN + 1]; - size_t len = (size_t)(end - start); - if (len >= sizeof(idx_str)) { - return -1; - } - memcpy(idx_str, start, len); - idx_str[len] = '\0'; - uint64_t sel_idx; - if (str_to_uint64(idx_str, &sel_idx) < 0 || sel_idx == 0 || - sel_idx > cur_clips) { - return -1; - } - *out_idx = (size_t)(sel_idx - 1); - return 0; -} - -/** - * Writes the available clips to the launcher and reads back the user's - * selection(s), calling action for each selected clip. - */ -static int _nonnull_ interact_with_dmenu(struct config *cfg, int *input_pipe, - int *output_pipe, pid_t launcher_pid, - clip_action_fn action) { - close(input_pipe[0]); - close(output_pipe[1]); - - _drop_(close) int content_dir_fd = open(get_cache_dir(cfg), O_RDONLY); - _drop_(close) int snip_fd = - open(get_line_cache_path(cfg), O_RDWR | O_CREAT, 0600); - expect(content_dir_fd >= 0 && snip_fd >= 0); - - _drop_(cs_destroy) struct clip_store cs; - expect(cs_init(&cs, snip_fd, content_dir_fd) == 0); - - struct ref_guard guard = cs_ref(&cs); - size_t cur_clips; - expect(cs_len(&cs, &cur_clips) == 0); - _drop_(free) uint64_t *idx_to_hash = malloc(cur_clips * sizeof(uint64_t)); - expect(idx_to_hash); - int pad = get_padding_length(cur_clips); - size_t clip_idx = cur_clips; - bool launcher_closed_stdin = false; - int forced_ret = 0; - - struct sigaction ignore_sa = {0}; - ignore_sa.sa_handler = SIG_IGN; - sigemptyset(&ignore_sa.sa_mask); - struct sigaction old_sa; - expect(sigaction(SIGPIPE, &ignore_sa, &old_sa) == 0); - - struct cs_snip *snip = NULL; - while (cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip)) { - size_t idx = --clip_idx; - idx_to_hash[idx] = snip->hash; - - if (launcher_closed_stdin) { - continue; - } - - char prefix[64]; - size_t prefix_len = - snprintf_safe(prefix, sizeof(prefix), "[%*zu] ", pad, idx + 1); - int write_ret = launcher_write_all(input_pipe[1], prefix, prefix_len); - if (write_ret == -EPIPE) { - launcher_closed_stdin = true; - continue; - } - if (write_ret < 0) { - forced_ret = EXIT_FAILURE; - break; - } - - write_ret = dprintf_ellipsise_long_snip_line(input_pipe[1], snip->line); - if (write_ret < 0 && errno == EPIPE) { - launcher_closed_stdin = true; - continue; - } - if (write_ret < 0) { - forced_ret = EXIT_FAILURE; - break; - } - - if (snip->nr_lines > 1) { - char lines_suffix[64]; - size_t lines_suffix_len = - snprintf_safe(lines_suffix, sizeof(lines_suffix), - " (%zu lines)", snip->nr_lines); - write_ret = launcher_write_all(input_pipe[1], lines_suffix, - lines_suffix_len); - if (write_ret == -EPIPE) { - launcher_closed_stdin = true; - continue; - } - if (write_ret < 0) { - forced_ret = EXIT_FAILURE; - break; - } - } - - write_ret = launcher_write_all(input_pipe[1], "\n", 1); - if (write_ret == -EPIPE) { - launcher_closed_stdin = true; - continue; - } - if (write_ret < 0) { - forced_ret = EXIT_FAILURE; - break; - } - } - - // We've written everything and have our own map, no need to hold any more - cs_unref(guard.cs); - - close(input_pipe[1]); - expect(sigaction(SIGPIPE, &old_sa, NULL) == 0); - - _drop_(fclose) FILE *output = fdopen(output_pipe[0], "r"); - expect(output != NULL); - char line[LAUNCHER_LINE_MAX]; - while (!forced_ret && fgets(line, sizeof(line), output) != NULL) { - size_t len = strlen(line); - if (len > 0 && line[len - 1] == '\n') { - line[len - 1] = '\0'; - len--; - } - if (len == 0) { - continue; - } - size_t idx; - if (parse_sel_idx(line, cur_clips, &idx) == 0) { - int ret = action(cfg, idx_to_hash[idx]); - if (ret != 0) { - forced_ret = ret; - } - } else { - forced_ret = EXIT_FAILURE; - } - } - - int dmenu_status; - int wait_ret = wait_for_pid(launcher_pid, &dmenu_status); - - if (forced_ret || wait_ret < 0 || !WIFEXITED(dmenu_status)) { - return EXIT_FAILURE; - } - - return WEXITSTATUS(dmenu_status); -} - -/** - * Prompts the user to select clip(s) via their configured launcher, and - * executes the provided action on each selected clip. - */ -int _nonnull_ menu_prompt_and_act(struct config *cfg, const char *prompt, - clip_action_fn action) { - int input_pipe[2], output_pipe[2]; - expect(pipe(input_pipe) == 0 && pipe(output_pipe) == 0); - - pid_t pid = fork(); - expect(pid >= 0); - - if (pid == 0) { - exec_launcher(cfg, prompt, input_pipe, output_pipe); - } - - return interact_with_dmenu(cfg, input_pipe, output_pipe, pid, action); -} - -/** - * Set the user-provided command-line arguments for launcher passthrough. - * - * This is janky and just mimics how clipmenu worked by itself, in the long run - * we should not rely on these. - */ -void menu_set_user_args(int argc, char **argv) { - dmenu_user_argc = argc; - dmenu_user_argv = argv; -} diff --git a/clipmenu/src/menu_util.h b/clipmenu/src/menu_util.h deleted file mode 100644 index 3bb15de..0000000 --- a/clipmenu/src/menu_util.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef CM_MENU_UTIL_H -#define CM_MENU_UTIL_H - -#include - -#include "config.h" - -/** - * Callback function type for handling selected clips. - */ -typedef int (*clip_action_fn)(struct config *cfg, uint64_t hash); - -void menu_set_user_args(int argc, char **argv); - -int _nonnull_ menu_prompt_and_act(struct config *cfg, const char *prompt, - clip_action_fn action); - -#endif diff --git a/clipmenu/src/store.c b/clipmenu/src/store.c deleted file mode 100644 index fd2d80e..0000000 --- a/clipmenu/src/store.c +++ /dev/null @@ -1,847 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "store.h" - -/** - * TERMINOLOGY - * - * - Clip store: A pairing of a file containing snips and a content directory - * - Clip store entry: A single pair of snip and content entry - * - Snip file: A file which maps lines to content entries using hashes - * - Snip: A (hash, line) pair in the clip store, representing a content entry - * - Content entry: The full data stored from the clipboard - * - Content directory: The directory in which the content entries are stored - * - * KEY FUNCTIONS: - * - * - cs_add - add a clip store entry - * - cs_remove - remove a clip store entry by callback - * - cs_trim - trim to the newest/oldest N entries - * - cs_snip_iter - iterate over snip hashes and lines - * - cs_content_get - get the content for a snip hash - * - * CLIP STORE DESIGN - * - * Our primary focus is on achieving high efficiency in appending new snips, - * iterating over the entire list of snips, and replacing the final snip in the - * snip file. For this reason complexity is avoided in processing deletions, - * since deletions are usually rare. This avoids us having to implement (for - * example) tombstones, which would slow things down and complicate iteration. - * - * CONTENT DIRECTORY DESIGN - * - * The content directory is extremely simple: it contains files with the same - * name as the snip hash. This allows quickly going from the one line summary - * in the snip to the full contents. - * - * SYNCHRONISATION - * - * The clip store's size may be increased or decreased by another program using - * the library, so before each operation we must take a lock and check if the - * header was updated. If it was, we update the size of the mmapped area to - * suit. The lock is implemented using flock() on cs->snip_fd, see cs_ref(), - * cs_ref_no_update(), and cs_unref(). - */ - -/** - * Calculate the needed file size in bytes for @nr_snips snips, adding the - * header. - * - * @nr_snips: The number of snips to calculate for - */ -static size_t _must_use_ cs_file_size(size_t nr_snips) { - return (nr_snips + 1) * CS_SNIP_SIZE; -} - -/** - * Validate the consistency of the clip store's header information. - * - * @cs: The clip store to operate on - * @file_size: The current size of the file - */ -static int _must_use_ _nonnull_ cs_header_validate(const struct clip_store *cs, - size_t file_size) { - if (cs->header->nr_snips > cs->header->nr_snips_alloc || - (cs->header->nr_snips_alloc + 1) * CS_SNIP_SIZE != file_size) { - return -EINVAL; - } - return 0; -} - -static int _must_use_ _nonnull_ cs_remap(struct clip_store *cs, size_t old_size, - size_t new_size) { - struct cs_header *new_header = mmap(NULL, new_size, PROT_READ | PROT_WRITE, - MAP_SHARED, cs->snip_fd, 0); - if (new_header == MAP_FAILED) { - return negative_errno(); - } - if (munmap(cs->header, old_size) < 0) { - int ret = negative_errno(); - munmap(new_header, new_size); - return ret; - } - cs->header = new_header; - cs->snips = (struct cs_snip *)(cs->header + 1); - cs->mapped_file_size = new_size; - return 0; -} - -/** - * Decrease the reference count for the clip store lock, unrefing it if - * the refcount reaches zero. - * - * @cs: The clip store to operate on - */ -void cs_unref(struct clip_store *cs) { - expect(cs->refcount > 0); - cs->refcount--; - if (cs->refcount == 0) { - expect(flock(cs->snip_fd, LOCK_UN) == 0); - } -} - -/** - * Increase the reference count for the clip store lock. - * - * @cs: The clip store to operate on - */ -static struct ref_guard _must_use_ _nonnull_ -cs_ref_no_update(struct clip_store *cs) { - struct ref_guard guard = {.status = 0, .unref = cs_unref, .cs = cs}; - if (cs->refcount == 0) { - expect(flock(cs->snip_fd, LOCK_EX) == 0); - } - static_assert(sizeof(cs->refcount) == sizeof(size_t), - "refcount type wrong"); - expect(cs->refcount < SIZE_MAX); - cs->refcount++; - return guard; -} - -/** - * Increase the reference count for the clip store lock, and remap as needed if - * the header values have changed. - * - * Even if the guard status indicates an error, you must still call cs_unref(). - * - * @cs: The clip store to operate on - */ -struct ref_guard cs_ref(struct clip_store *cs) { - struct ref_guard guard = cs_ref_no_update(cs); - - if (cs->refcount > 1) { - // We're an inner reference, so any necessary remapping has already - // been performed. - return guard; - } - - if (cs->local_nr_snips != cs->header->nr_snips || - cs->local_nr_snips_alloc != cs->header->nr_snips_alloc) { - struct stat st; - if (fstat(cs->snip_fd, &st) < 0) { - guard.status = negative_errno(); - return guard; - } - - int ret = cs_header_validate(cs, (size_t)st.st_size); - if (ret < 0) { - guard.status = ret; - return guard; - } - - // If we shrank, no need to remap, since we'll just use the new bounds. - if (cs->local_nr_snips_alloc < cs->header->nr_snips_alloc) { - int remap_ret = cs_remap(cs, cs->mapped_file_size, - cs_file_size(cs->header->nr_snips_alloc)); - if (remap_ret < 0) { - guard.status = remap_ret; - return guard; - } - } - - cs->local_nr_snips = cs->header->nr_snips; - cs->local_nr_snips_alloc = cs->header->nr_snips_alloc; - } - - return guard; -} - -/** - * _drop_() function for when a `ref_guard` structure goes out of scope. - * - * @guard: The guard lock - */ -void drop_cs_unref(struct ref_guard *guard) { - guard->status = -EBADF; - guard->unref(guard->cs); -} - -/** - * Destroy the clip store, releasing all of its resources. - * - * @cs: The clip store to operate on - */ -int cs_destroy(struct clip_store *cs) { - cs->ready = false; - if (munmap(cs->header, cs->mapped_file_size)) { - return negative_errno(); - } - return 0; -} - -/** - * _drop_() function for when a `clip_store` goes out of scope. - * - * @guard: The guard lock - */ -void drop_cs_destroy(struct clip_store *cs) { expect(cs_destroy(cs) == 0); } - -/** - * Initialise a `struct clip_store` with snip_fd open to a file for snip - * storage and content_fd open to a directory for content entry storage. - * - * The snip file is extended and the header snip is written if the file size is - * zero. The file is mapped into memory until cs_destroy() is called. - * - * @cs: The clip store to initialise - * @snip_fd: Open file descriptor for the snip file - * @content_dir_fd: Open file descriptor for the content directory - */ -int cs_init(struct clip_store *cs, int snip_fd, int content_dir_fd) { - cs->ready = false; - cs->snip_fd = snip_fd; - cs->content_dir_fd = content_dir_fd; - cs->refcount = 0; - _drop_(cs_unref) struct ref_guard guard = cs_ref_no_update(cs); - - struct stat st; - if (fstat(snip_fd, &st) < 0) { - return negative_errno(); - } - if (st.st_size % CS_SNIP_SIZE != 0) { - return -EINVAL; - } - - size_t file_size = (size_t)st.st_size; - if (file_size == 0) { - file_size = CS_SNIP_SIZE; - if (ftruncate(snip_fd, (off_t)file_size) < 0) { - return negative_errno(); - } - } - - cs->header = - mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, snip_fd, 0); - if (cs->header == MAP_FAILED) { - return negative_errno(); - } - - int ret = cs_header_validate(cs, file_size); - if (ret < 0) { - munmap(cs->header, file_size); - return ret; - } - - cs->snips = (struct cs_snip *)(cs->header + 1); - cs->local_nr_snips = cs->header->nr_snips; - cs->local_nr_snips_alloc = cs->header->nr_snips_alloc; - cs->mapped_file_size = file_size; - cs->ready = true; - - (void)guard; // Old clang will complain guard is unused, despite cleanup - - return 0; -} - -/** - * Round up a number to the nearest multiple of a specified step. - * - * @n: The number to round up - * @step: The step size to round up to - */ -static size_t round_up(size_t n, size_t step) { - return ((n + step - 1) / step) * step; -} - -/** - * Adjust the size of the mmapped file used by the clip store to the specified - * new size. It first attempts to resize the file using ftruncate(). If this - * operation is successful, it then remaps the memory mapping to reflect the - * new size of the file. - * - * WARNING: cs->snips and cs->header may move after cs_file_resize(), so - * copied pointers from before invocation must not be used after calling this. - * - * @cs: The clip store to operate on - * @new_nr_snips: The new number of snips in the snip file - */ -static int _must_use_ _nonnull_ cs_file_resize(struct clip_store *cs, - size_t new_nr_snips) { - bool grow = new_nr_snips >= cs->header->nr_snips; - - if (grow && new_nr_snips <= cs->header->nr_snips_alloc) { - cs->header->nr_snips = new_nr_snips; - return 0; - } - - /* If this is a shrink, do it exactly: someone may be deleting sensitive - * snips and so it's best to remove them immediately. Otherwise, batch - * allocate. */ - size_t new_nr_snips_alloc = - grow ? round_up(new_nr_snips, CS_SNIP_ALLOC_BATCH) : new_nr_snips; - - size_t new_size = cs_file_size(new_nr_snips_alloc); - if (ftruncate(cs->snip_fd, (off_t)new_size) < 0) { - return negative_errno(); - } - - if (grow) { - size_t old_file_size = cs_file_size(cs->header->nr_snips_alloc); - int ret = cs_remap(cs, cs->mapped_file_size, new_size); - if (ret < 0) { - expect(ftruncate(cs->snip_fd, (off_t)old_file_size) == 0); - return ret; - } - } - - cs->header->nr_snips = cs->local_nr_snips = new_nr_snips; - cs->header->nr_snips_alloc = cs->local_nr_snips_alloc = new_nr_snips_alloc; - - return 0; -} - -/** - * Update a clip store snip to contain the specified hash and line content. - * - * @snip: Pointer to the snip to modify - * @hash: The new hash value for the snip - * @line: The new line content for the snip - * @nr_lines: The number of lines in the line content - */ -static void _nonnull_ cs_snip_update(struct cs_snip *snip, uint64_t hash, - const char *line, uint64_t nr_lines) { - snip->hash = hash; - snip->doomed = false; - snip->nr_lines = nr_lines; - strncpy(snip->line, line, CS_SNIP_LINE_SIZE - 1); - snip->line[CS_SNIP_LINE_SIZE - 1] = '\0'; -} - -/** - * Extracts the first non-empty line from a given text buffer and copies it to - * the output buffer. Returns the total number of lines. A final line with no - * newline is considered a line for accounting purposes. - * - * @text: The input text buffer - * @out: The output buffer. Must be at least CS_SNIP_LINE_SIZE bytes - */ -size_t first_line(const char *text, char *out) { - bool found = false; - size_t nr_lines = 0; - const char *cur = text; - - out[0] = '\0'; - - for (; *cur; cur++) { - nr_lines += (*cur == '\n'); - if (!found && *cur != '\n') { - found = true; - snprintf(out, CS_SNIP_LINE_SIZE, "%.*s", (int)strcspn(cur, "\n"), - cur); - } - } - - return nr_lines + (found && *--cur != '\n'); -} - -/** - * Add a new snip consisting of a hash value and a line of text to the end of - * the clip store. The snip file size is grown as necessary to accommodate the - * new snip. - * - * @cs: The clip store to operate on - * @hash: The hash value of the snip to add - * @line: The line content of the snip to add - * @nr_lines: The number of lines in the line content - */ -static int _must_use_ _nonnull_ cs_snip_add(struct clip_store *cs, - uint64_t hash, const char *line, - uint64_t nr_lines) { - _drop_(cs_unref) struct ref_guard guard = cs_ref(cs); - if (guard.status < 0) { - return guard.status; - } - int ret = cs_file_resize(cs, cs->header->nr_snips + 1); - if (ret < 0) { - return ret; - } - cs_snip_update(cs->snips + cs->header->nr_snips - 1, hash, line, nr_lines); - return 0; -} - -/** - * Add content to the content directory using the hash as the filename. - * - * @cs: The clip store to operate on - * @hash: The hash of the content to add - * @content: The content to add to the file - * @dupe_policy: If set to CS_DUPE_KEEP_LAST, will return with -EEXIST when - * trying to insert duplicate entry. - */ -static int _must_use_ _nonnull_ -cs_content_add(struct clip_store *cs, uint64_t hash, const char *content, - enum cs_dupe_policy dupe_policy) { - bool dupe = false; - size_t content_len = strlen(content); - - char dir_path[CS_HASH_STR_MAX]; - snprintf(dir_path, sizeof(dir_path), PRI_HASH, hash); - - int ret = mkdirat(cs->content_dir_fd, dir_path, 0700); - if (ret < 0) { - if (errno != EEXIST || dupe_policy == CS_DUPE_KEEP_LAST) { - return negative_errno(); - } - dupe = true; - } - - char base_file_path[PATH_MAX]; - snprintf(base_file_path, sizeof(base_file_path), "%s/1", dir_path); - - if (dupe) { - // This clip already exists, just create a link for refcounting - struct stat st; - if (fstatat(cs->content_dir_fd, base_file_path, &st, 0) < 0) { - return negative_errno(); - } - - if ((size_t)st.st_size != content_len) { - // Extremely unlikely with FNV-1a 64-bit outside of artificial - // scenarios, but never hurts to be careful... - return -EFAULT; - } - - size_t link_num = (size_t)st.st_nlink + 1; - char linkpath[PATH_MAX]; - snprintf(linkpath, sizeof(linkpath), "%s/%zu", dir_path, link_num); - if (linkat(cs->content_dir_fd, base_file_path, cs->content_dir_fd, - linkpath, 0) < 0) { - return negative_errno(); - } - - return 0; - } - - // This is a new clip - _drop_(close) int fd = openat(cs->content_dir_fd, base_file_path, - O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd < 0) { - return negative_errno(); - } - - const char *cur = content; - size_t remaining = content_len; - - while (remaining > 0) { - ssize_t written = write(fd, cur, remaining); - if (written < 0) { - return negative_errno(); - } - remaining -= (size_t)written; - cur += written; - } - - return 0; -} - -/** - * Clean up the backing mapped data and file descriptor for a `struct - * cs_content` object. - * - * @content: The content to unmap - */ -static int cs_content_unmap(struct cs_content *content) { - if (content && content->data) { - close(content->fd); - if (munmap(content->data, (size_t)content->size)) { - return negative_errno(); - } - } - return 0; -} - -/** - * _drop_ function for cs_content_unmap() - * - * @content: The content to unmap - */ -void drop_cs_content_unmap(struct cs_content *content) { - int ret = cs_content_unmap(content); - expect(ret == 0); -} - -/** - * Retrieve the content associated with a given hash from the content directory - * and map it into memory. - * - * @cs: The clip store to operate on - * @hash: The hash of the content to retrieve - * @content: A pointer to a `struct cs_content` to populate. The caller must - * call cs_content_unmap() when done to free it - */ -int cs_content_get(struct clip_store *cs, uint64_t hash, - struct cs_content *content) { - memset(content, '\0', sizeof(struct cs_content)); - - char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), PRI_HASH "/1", hash); - - _drop_(close) int fd = openat(cs->content_dir_fd, filename, O_RDONLY); - if (fd < 0) { - return negative_errno(); - } - - struct stat st; - if (fstat(fd, &st) < 0) { - return negative_errno(); - } - - char *data = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (data == MAP_FAILED) { - return negative_errno(); - } - - content->data = data; - content->fd = fd; - content->size = st.st_size; - fd = -1; // Disarm the _drop_, this will be handled by cs_content_unmap() - - return 0; -} - -/** - * Move the entry with the specified hash to the newest slot. - * - * @cs: The clip store to operate on - * @hash: The hash of the entry to move - */ -int cs_make_newest(struct clip_store *cs, uint64_t hash) { - _drop_(cs_unref) struct ref_guard guard = cs_ref(cs); - if (guard.status < 0) { - return guard.status; - } - - for (int i = 0; i < (int)cs->local_nr_snips; ++i) { - if (cs->snips[i].hash == hash) { - struct cs_snip tmp = cs->snips[i]; - memmove(cs->snips + i, cs->snips + i + 1, - (cs->local_nr_snips - (i + 1)) * sizeof(*cs->snips)); - cs->snips[cs->local_nr_snips - 1] = tmp; - return 0; - } - } - return -ENOENT; -} - -static int _must_use_ _nonnull_ cs_content_remove(struct clip_store *cs, - uint64_t hash); - -/** - * Add a new content entry to the clip store and content directory. - * - * @cs: The clip store to operate on - * @content: The content to add - * @out_hash: Output for the generated hash, or NULL - * @dupe_policy: Policy to use for duplicate entries - */ -int cs_add(struct clip_store *cs, const char *content, uint64_t *out_hash, - enum cs_dupe_policy dupe_policy) { - uint64_t hash = fnv1a_64_hash(content); - char line[CS_SNIP_LINE_SIZE]; - size_t nr_lines = first_line(content, line); - - if (out_hash) { - *out_hash = hash; - } - - _drop_(cs_unref) struct ref_guard guard = cs_ref(cs); - if (guard.status < 0) { - return guard.status; - } - - int ret = cs_content_add(cs, hash, content, dupe_policy); - if (ret == -EEXIST && dupe_policy == CS_DUPE_KEEP_LAST) { - return cs_make_newest(cs, hash); - } - if (ret) { - return ret; - } - ret = cs_snip_add(cs, hash, line, nr_lines); - if (ret) { - int rm_ret = cs_content_remove(cs, hash); - if (rm_ret) { - warn("Failed to roll back leaked content for hash " PRI_HASH - " after cs_snip_add failed (%s): %s\n", - hash, strerror(-ret), strerror(-rm_ret)); - return rm_ret; - } - } - return ret; -} - -/** - * Iterate over the snips in the clip store. The function should be initially - * called with *snip set to NULL, which will set *snip to point to the newest - * snip (excluding the header). On subsequent calls, *snip is updated to point - * to the next snip in the snip file. The iteration stops when there are no - * more snips to process, indicated by the function returning false. - * - * @guard: The guard lock - * @snip: Pointer to a pointer to the current snip being iterated over - * @direction: Whether to iterate from the oldest to newest or vice versa - */ -bool cs_snip_iter(struct ref_guard *guard, enum cs_iter_direction direction, - struct cs_snip **snip) { - if (guard->status < 0 || guard->cs->header->nr_snips == 0) { - return false; - } - - struct cs_snip *oldest = guard->cs->snips; - struct cs_snip *newest = oldest + (guard->cs->header->nr_snips - 1); - const struct cs_snip *stop = - direction == CS_ITER_NEWEST_FIRST ? oldest : newest; - - if (!*snip) { - *snip = direction == CS_ITER_NEWEST_FIRST ? newest : oldest; - return true; - } else if (*snip != stop) { - *snip = *snip + (direction == CS_ITER_NEWEST_FIRST ? -1 : 1); - return true; - } - return false; -} - -/** - * Remove content from the content directory using the hash as the filename. - * - * @cs: The clip store to operate on - * @hash: The hash of the content to remove - */ -static int _must_use_ _nonnull_ cs_content_remove(struct clip_store *cs, - uint64_t hash) { - - char hash_dir_name[CS_HASH_STR_MAX]; - snprintf(hash_dir_name, sizeof(hash_dir_name), PRI_HASH, hash); - - _drop_(close) int hash_dir_fd = - openat(cs->content_dir_fd, hash_dir_name, O_RDONLY); - if (hash_dir_fd < 0) { - return negative_errno(); - } - - struct stat st; - if (fstatat(hash_dir_fd, "1", &st, 0) < 0) { - return negative_errno(); - } - - char nlink_path[PATH_MAX]; - snprintf(nlink_path, sizeof(nlink_path), "%u", (unsigned)st.st_nlink); - - if (unlinkat(hash_dir_fd, nlink_path, 0) < 0) { - return negative_errno(); - } - - if (st.st_nlink == 1 && - unlinkat(cs->content_dir_fd, hash_dir_name, AT_REMOVEDIR) < 0) { - return negative_errno(); - } - - return 0; -} - -/** - * Compacts the clip store by removing doomed snips, finalising their removal - * after being marked in cs_remove(). - * - * @guard: The guard lock - */ -static size_t _nonnull_ cs_snip_remove_doomed(struct ref_guard *guard) { - size_t nr_doomed = 0; - struct cs_snip *snip = NULL; - - while (cs_snip_iter(guard, CS_ITER_OLDEST_FIRST, &snip)) { - if (snip->doomed) { - nr_doomed++; - } else if (nr_doomed > 0) { - *(snip - nr_doomed) = *snip; - } - } - - return nr_doomed; -} - -/** - * Iterate over the specified number of snips in the clip store from newest - * to oldest and remove those for which the predicate function returns - * CS_ACTION_REMOVE (see `enum cs_remove_action` above). - * - * @cs: The clip store to operate on - * @should_remove: Function pointer to the predicate used to decide removal - * @private: Pointer to user-defined data passed to the predicate function - * @direction: Whether to iterate from the oldest to newest or vice versa - */ -int cs_remove(struct clip_store *cs, enum cs_iter_direction direction, - enum cs_remove_action (*should_remove)(uint64_t, const char *, - void *), - void *private) { - _drop_(cs_unref) struct ref_guard guard = cs_ref(cs); - if (guard.status < 0) { - return guard.status; - } - - bool found = false; - struct cs_snip *snip = NULL; - - while (cs_snip_iter(&guard, direction, &snip)) { - enum cs_remove_action action = - should_remove(snip->hash, snip->line, private); - - if (action & CS_ACTION_REMOVE) { - found = true; - int ret = cs_content_remove(cs, snip->hash); - if (ret < 0) { - return ret; - } - snip->doomed = true; - } - if (action & CS_ACTION_STOP) { - break; - } - } - - if (!found) { - return 0; - } - - size_t nr_doomed = cs_snip_remove_doomed(&guard); - int ret = cs_file_resize(cs, cs->header->nr_snips - nr_doomed); - if (ret < 0) { - return ret; - } - - return 0; -} - -/** - * Callback function for deciding which snips to remove during trim operation. - * - * @hash: Unused - * @line: Unused - * @private: The count of remaining entries to trim - */ -static enum cs_remove_action _must_use_ _nonnull_ -trim_callback(uint64_t hash, const char *line, void *private) { - (void)hash; - (void)line; - - size_t *count = private; - if (*count == 0) { - return CS_ACTION_REMOVE; - } - (*count)--; - return CS_ACTION_KEEP; -} - -/** - * Trim the clip store to only retain the specified number of snips. - * - * @cs: The clip store to operate on - * @direction: Whether to remove the N newest or N oldest - * @nr_keep: The number of newest snips to retain - */ -int cs_trim(struct clip_store *cs, enum cs_iter_direction direction, - size_t nr_keep) { - if (nr_keep >= cs->header->nr_snips) { - return 0; // No action needed if we're keeping everything or more - } - - int ret = cs_remove(cs, direction, trim_callback, &nr_keep); - if (ret < 0) { - return ret; - } - - return 0; -} - -/** - * Replace the content and snip for an entry in the clip store, identified by - * its age. - * - * @cs: The clip store to operate on - * @age: The age of the snip to replace, with 0 being the newest - * @direction: Whether to iterate from the oldest to newest or vice versa - * @content: The content to replace this entry with - * @out_hash: Output for the generated hash, or NULL - */ -int cs_replace(struct clip_store *cs, enum cs_iter_direction direction, - size_t age, const char *content, uint64_t *out_hash) { - _drop_(cs_unref) struct ref_guard guard = cs_ref(cs); - if (guard.status < 0) { - return guard.status; - } - - if (age >= cs->header->nr_snips) { - return -ERANGE; - } - - size_t idx = direction == CS_ITER_NEWEST_FIRST - ? cs->header->nr_snips - age - 1 - : age; - struct cs_snip *snip = cs->snips + idx; - - char line[CS_SNIP_LINE_SIZE]; - size_t nr_lines = first_line(content, line); - uint64_t old_hash = snip->hash; - uint64_t hash = fnv1a_64_hash(content); - - int ret = cs_content_add(cs, hash, content, CS_DUPE_KEEP_ALL); - if (ret) { - return ret; - } - ret = cs_content_remove(cs, old_hash); - if (ret) { - return ret; - } - cs_snip_update(snip, hash, line, nr_lines); - if (out_hash) { - *out_hash = hash; - } - return 0; -} - -/** - * Get the current number of entries in the clip store. - * - * @cs: The clip store to operate on - * @out_len: Output for the length of the clip store - */ -int cs_len(struct clip_store *cs, size_t *out_len) { - _drop_(cs_unref) struct ref_guard guard = cs_ref(cs); - if (guard.status < 0) { - return guard.status; - } - *out_len = cs->header->nr_snips; - return 0; -} diff --git a/clipmenu/src/store.h b/clipmenu/src/store.h deleted file mode 100644 index 3a0f3bd..0000000 --- a/clipmenu/src/store.h +++ /dev/null @@ -1,192 +0,0 @@ -#ifndef CM_STORE_H -#define CM_STORE_H - -#include -#include -#include - -#include "util.h" - -#define CS_SNIP_SIZE 256 /* The size of each struct cs_snip */ -#define CS_SNIP_ALLOC_BATCH 1024 /* How many snips to allocate when growing */ -#define CS_HASH_STR_MAX 17 /* String length of 64bit hex + \0 */ -#define PRI_HASH "%016" PRIX64 - -/** - * A single snip within the clip store. - * - * @hash: A 64-bit hash value associated with the content entry - * @doomed: Used during cs_remove to batch mark entries for removal - * @nr_lines: The number of lines in the content entry - * @line: A character array containing the first salient line, terminated by a - * null byte - */ -#define CS_SNIP_LINE_SIZE CS_SNIP_SIZE - (sizeof(uint64_t) * 2) - sizeof(bool) -struct _packed_ cs_snip { - uint64_t hash; - bool doomed; - uint64_t nr_lines; - char line[CS_SNIP_LINE_SIZE]; -}; - -/** - * The header of the clip store. Must fit within the footprint of a regular - * `cs_snip`. - * - * @nr_snips: The total number of snips in the clip store, excluding the - * header - * @nr_snips_alloc: The total number of allocated snips in the clip store - * that can be used without _cs_file_resize(), excluding the - * header - * @_unused_padding: Padding to match the size of cs_snip - */ -#define CS_HEADER_PADDING_SIZE CS_SNIP_SIZE - (sizeof(uint64_t) * 2) -struct _packed_ cs_header { - uint64_t nr_snips; - uint64_t nr_snips_alloc; - char _unused_padding[CS_HEADER_PADDING_SIZE]; -}; - -static_assert(sizeof(struct cs_snip) == CS_SNIP_SIZE, "cs_snip wrong size"); -static_assert(sizeof(struct cs_snip) == sizeof(struct cs_header), - "cs_header and cs_snip must be the same size"); - -/** - * The main interface to the clip store for the user. - * - * @snip_fd: The file descriptor for the snip file - * @content_dir_fd: The file descriptor for the content directory - * @header: Pointer to the header in the mmapped file - * @snips: Pointer to the beginning of the clip store snips in the mmapped - * file, directly after the header - * @ready: Indicates if the clip store is ready for operations - * @refcount: The reference count for the fd flock - * @local_nr_snips: Our last known header->nr_snips - * @local_nr_snips_alloc: Our last known header->nr_snips_alloc - * @mapped_file_size: The actual size of our current mmap() in bytes - */ -struct clip_store { - /* FDs */ - int snip_fd; - int content_dir_fd; - - /* Pointers inside mmapped snip file */ - struct cs_header *header; - struct cs_snip *snips; - - /* Synchronisation */ - size_t refcount; - size_t local_nr_snips; - size_t local_nr_snips_alloc; - size_t mapped_file_size; - bool ready; -}; - -/** - * Manages the lifecycle of a single clip store lock reference. - * - * @status: The result of trying update our mappings to new header values. If - * less than 0, the lock should not be used. Set to -EBADF on unref. - * @unref: The function to call to unref. This mostly exists to ensure that - * the ref_guard is used and is not optimised by the compiler. - * @cs: A pointer to the clip store structure on which the lock is held. - * - * Usage of this structure involves creating a ref guard instance at the - * beginning of a scope where a lock is needed. Functions which need a lock - * accept a `struct ref_guard *` as their first argument - */ -struct ref_guard { - int status; - struct clip_store *cs; - void (*unref)(struct clip_store *); -}; - -/** - * The memory-mapped content associated with a hash in the content directory. - * - * @data: A pointer to the memory-mapped data - * @fd: The file descriptor of the opened file from which the content is mapped - * @size: The size of the mapped data - */ -struct cs_content { - char *data; - int fd; - off_t size; -}; - -/** - * The direction in which to iterate over snips in the clip store. - * - * @CS_ITER_NEWEST_FIRST: Iterate over the snips starting from the newest. - * @CS_ITER_OLDEST_FIRST: Iterate over the snips starting from the oldest. - */ -enum cs_iter_direction { CS_ITER_NEWEST_FIRST, CS_ITER_OLDEST_FIRST }; - -/** - * Set the bit at position n. - * - * @n: The position of the bit to set - */ -#define BIT(n) (1UL << (n)) - -/** - * The action to take as returned by `cs_remove`'s `should_remove()` predicate - * function. - * - * @CS_ACTION_REMOVE: Remove this snip from the clip store - * @CS_ACTION_KEEP: Keep this snip in the clip store - * @CS_ACTION_STOP: Stop iteration after processing this snip - * - * If neither of CS_ACTION_REMOVE or CS_ACTION_KEEP are specified, the snip is - * kept. - */ -enum cs_remove_action { - CS_ACTION_REMOVE = BIT(0), - CS_ACTION_KEEP = BIT(1), - CS_ACTION_STOP = BIT(2), -}; - -/** - * What to do when there's a duplicate entry. - * - * @CS_DUPE_KEEP_ALL: Keep all duplicate entries. - * @CS_DUPE_KEEP_LAST: Only keep the newest, do not insert duplicate entries. - */ -enum cs_dupe_policy { - CS_DUPE_KEEP_ALL, - CS_DUPE_KEEP_LAST, -}; - -struct ref_guard _must_use_ _nonnull_ cs_ref(struct clip_store *cs); -void _nonnull_ cs_unref(struct clip_store *cs); -void _nonnull_ drop_cs_unref(struct ref_guard *guard); -int _must_use_ _nonnull_ cs_destroy(struct clip_store *cs); -int _must_use_ _nonnull_ cs_init(struct clip_store *cs, int snip_fd, - int content_dir_fd); -void drop_cs_content_unmap(struct cs_content *content); -void drop_cs_destroy(struct clip_store *cs); -int _must_use_ _nonnull_ cs_content_get(struct clip_store *cs, uint64_t hash, - struct cs_content *content); -int _must_use_ _nonnull_n_(1) - cs_make_newest(struct clip_store *cs, uint64_t hash); -int _must_use_ _nonnull_n_(1) - cs_add(struct clip_store *cs, const char *content, uint64_t *out_hash, - enum cs_dupe_policy dupe_policy); -bool _must_use_ _nonnull_ cs_snip_iter(struct ref_guard *guard, - enum cs_iter_direction direction, - struct cs_snip **snip); -int _must_use_ _nonnull_ cs_remove( - struct clip_store *cs, enum cs_iter_direction direction, - enum cs_remove_action (*should_remove)(uint64_t, const char *, void *), - void *private); -int _must_use_ _nonnull_ cs_trim(struct clip_store *cs, - enum cs_iter_direction direction, - size_t nr_keep); -int _must_use_ _nonnull_n_(1, 4) - cs_replace(struct clip_store *cs, enum cs_iter_direction direction, - size_t age, const char *content, uint64_t *out_hash); -int _nonnull_ cs_len(struct clip_store *cs, size_t *out_len); - -size_t _nonnull_ first_line(const char *text, char *out); - -#endif diff --git a/clipmenu/src/util.c b/clipmenu/src/util.c deleted file mode 100644 index e9cf994..0000000 --- a/clipmenu/src/util.c +++ /dev/null @@ -1,165 +0,0 @@ -#include -#include -#include -#include - -#include "config.h" -#include "store.h" -#include "util.h" - -/** - * Write data to a file descriptor, ensuring all bytes are written. - */ -void write_safe(int fd, const char *buf, size_t count) { - while (count > 0) { - ssize_t chunk_size = write(fd, buf, count); - expect(chunk_size >= 0); - buf += chunk_size; - expect(chunk_size <= (ssize_t)count); - count -= (size_t)chunk_size; - } -} - -/** - * Read data from a file descriptor into a buffer safely, ensuring correct - * handling of partial reads. - */ -size_t read_safe(int fd, char *buf, size_t count) { - size_t count_start = count; - while (count > 0) { - ssize_t chunk_size = read(fd, buf, count); - expect(chunk_size >= 0); - if (chunk_size == 0) { // EOF - break; - } - buf += chunk_size; - count -= (size_t)chunk_size; - } - expect(count_start >= count); - return count_start - count; -} - -/** - * Performs safe, bounded string formatting into a buffer. On error or - * truncation, expect() aborts. - */ -size_t snprintf_safe(char *buf, size_t len, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - int needed = vsnprintf(buf, len, fmt, args); - va_end(args); - expect(needed >= 0 && (size_t)needed < len); - return (size_t)needed; -} - -/** - * Runs clipserve to handle selection requests for a hash in the clip store. - */ -#define CLIPSERVE_MAX_ARGS (CM_SEL_MAX * 2 + 3) - -void run_clipserve(uint64_t hash, const struct selection *selections) { - char hash_str[CS_HASH_STR_MAX]; - snprintf(hash_str, sizeof(hash_str), PRI_HASH, hash); - - const char *cmd[CLIPSERVE_MAX_ARGS]; - size_t cmd_idx = 0; - cmd[cmd_idx++] = "clipserve"; - if (selections) { - for (size_t i = 0; i < CM_SEL_MAX; i++) { - if (!selections[i].active) { - continue; - } - expect(cmd_idx + 2 < CLIPSERVE_MAX_ARGS); - cmd[cmd_idx++] = "--selection"; - cmd[cmd_idx++] = selections[i].name; - } - } - cmd[cmd_idx++] = hash_str; - cmd[cmd_idx++] = NULL; - pid_t pid = fork(); - expect(pid >= 0); - - if (pid > 0) { - return; - } - - execvp(cmd[0], (char *const *)cmd); - die("Failed to exec %s: %s\n", cmd[0], strerror(errno)); -} - -/** - * Convert a positive errno value to a negative error code, ensuring a - * non-zero value is returned. - * - * This is needed because clang-tidy and gcc may complain when doing plain - * "return -errno" because the compiler does not know that errno cannot be 0 - * (and thus that later checks with func() == 0 cannot pass in error - * situations). - */ -int negative_errno(void) { return errno > 0 ? -errno : -EINVAL; } - -/** - * Convert a string to an unsigned 64-bit integer in given base, validating the - * format and range of the input. - */ -static int str_to_uint64_base(const char *input, uint64_t *output, int base) { - char *endptr; - errno = 0; - - uint64_t val = strtoull(input, &endptr, base); - if (errno > 0) { - return negative_errno(); - } - if (!endptr || endptr == input || *endptr != 0) { - return -EINVAL; - } - if (val != 0 && input[0] == '-') { - return -ERANGE; - } - - *output = val; - return 0; -} - -/** - * Convert a string to an unsigned 64-bit integer, validating the format and - * range of the input. - */ -int str_to_uint64(const char *input, uint64_t *output) { - return str_to_uint64_base(input, output, 10); -} - -/** - * Convert a hex string to an unsigned 64-bit integer, validating the format - * and range of the input. - */ -int str_to_hex64(const char *input, uint64_t *output) { - return str_to_uint64_base(input, output, 16); -} - -/** - * Check whether debug mode is enabled and cache the result. Used for dbg(). - */ -bool debug_mode_enabled(void) { - static int debug_enabled = -1; - if (debug_enabled == -1) { - const char *dbg_env = getenv("CM_DEBUG"); - debug_enabled = dbg_env && streq(dbg_env, "1"); - } - return debug_enabled; -} - -/** - * Compute a 64-bit FNV-1a hash for a NULL-terminated string. - */ -uint64_t fnv1a_64_hash(const char *buf) { - const uint64_t fnv_offset_basis = 0xcbf29ce484222325ULL; - const uint64_t fnv_prime = 0x100000001b3ULL; - uint64_t hash = fnv_offset_basis; - const uint8_t *src = (const uint8_t *)buf; - while (*src) { - hash ^= *src++; - hash *= fnv_prime; - } - return hash; -} diff --git a/clipmenu/src/util.h b/clipmenu/src/util.h deleted file mode 100644 index 3aa1592..0000000 --- a/clipmenu/src/util.h +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef CM_UTIL_H -#define CM_UTIL_H - -#include -#include -#include -#include -#include -#include - -struct selection; - -#define _drop_(x) __attribute__((__cleanup__(drop_##x))) -#define _must_use_ __attribute__((warn_unused_result)) -#define _nonnull_ __attribute__((nonnull)) -#define _nonnull_n_(...) __attribute__((nonnull(__VA_ARGS__))) -#define _noreturn_ __attribute__((noreturn)) -#define _packed_ __attribute__((packed)) -#define _printf_(a, b) __attribute__((__format__(printf, a, b))) -#define _unused_ __attribute__((__unused__)) -#define likely(x) __builtin_expect(!!(x), 1) -#define unlikely(x) __builtin_expect(!!(x), 0) -#ifndef __cplusplus - #define static_assert _Static_assert -#endif - -#define UINT64_MAX_STRLEN 20 // (1 << 64) - 1 is 20 digits wide - -#define streq(a, b) (strcmp((a), (b)) == 0) -#define strceq(a, b) (strcasecmp((a), (b)) == 0) -#define strnull(s) (s) ? (s) : "[null]" - -#define arrlen(x) \ - (__builtin_choose_expr( \ - !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ - sizeof(x) / sizeof((x)[0]), (void)0 /* decayed, compile error */)) - -#define _msg(level, fmt, ...) fprintf(stderr, level ": " fmt, ##__VA_ARGS__) -#define warn(fmt, ...) _msg("WARN", fmt, ##__VA_ARGS__) -#define _die(dump, fmt, ...) \ - do { \ - _msg("FATAL", fmt, ##__VA_ARGS__); \ - if (dump) { \ - abort(); \ - } \ - exit(1); \ - } while (0) -#define die(fmt, ...) _die(0, fmt, ##__VA_ARGS__) -#define die_on(cond, fmt, ...) \ - do { \ - if (unlikely(cond)) { \ - die(fmt, ##__VA_ARGS__); \ - } \ - } while (0) -#define expect(x) \ - do { \ - if (!likely(x)) { \ - _die(1, "!(%s) at %s:%s:%d\n", #x, __FILE__, __func__, __LINE__); \ - } \ - } while (0) - -#define dbg(fmt, ...) \ - do { \ - if (debug_mode_enabled()) { \ - fprintf(stderr, "%s:%ld:%s:%s:%d: " fmt, prog_name, \ - (long)getpid(), __FILE__, __func__, __LINE__, \ - ##__VA_ARGS__); \ - } \ - } while (0) - -void _nonnull_ write_safe(int fd, const char *buf, size_t count); -size_t _nonnull_ read_safe(int fd, char *buf, size_t count); -size_t _printf_(3, 4) - snprintf_safe(char *buf, size_t len, const char *fmt, ...); - -void run_clipserve(uint64_t hash, const struct selection *selections); -uint64_t _nonnull_ fnv1a_64_hash(const char *buf); - -/** - * __attribute__((cleanup)) functions - */ -#define DEFINE_DROP_FUNC_PTR(type, func) \ - static inline void drop_##func(type *p) { func(p); } -#define DEFINE_DROP_FUNC(type, func) \ - static inline void drop_##func(type *p) { \ - if (*p) \ - func(*p); \ - } -#define DEFINE_DROP_FUNC_VOID(func) \ - static inline void drop_##func(void *p) { \ - void **pp = p; \ - if (*pp) \ - func(*pp); \ - } - -static inline void drop_close(int *fd) { - if (*fd >= 0) { - close(*fd); - } -} - -DEFINE_DROP_FUNC_VOID(free) -DEFINE_DROP_FUNC(FILE *, fclose) -DEFINE_DROP_FUNC(DIR *, closedir) - -int _must_use_ negative_errno(void); -int _nonnull_ str_to_uint64(const char *input, uint64_t *output); -int _nonnull_ str_to_hex64(const char *input, uint64_t *output); -bool debug_mode_enabled(void); - -#endif diff --git a/clipmenu/src/x.c b/clipmenu/src/x.c deleted file mode 100644 index 18809f3..0000000 --- a/clipmenu/src/x.c +++ /dev/null @@ -1,121 +0,0 @@ -#include -#include -#include - -#include "x.h" - -/** - * Fetch the title of the window with the specified window ID. - */ -char *get_window_title(Display *dpy, Window owner) { - Atom props[] = {XInternAtom(dpy, "_NET_WM_NAME", False), XA_WM_NAME}; - Atom utf8_string = XInternAtom(dpy, "UTF8_STRING", False); - Atom actual_type; - int format; - unsigned long nr_items, bytes_after; - unsigned char *prop = NULL; - - for (size_t i = 0; i < arrlen(props); i++) { - if (XGetWindowProperty(dpy, owner, props[i], 0, (~0L), False, - (props[i] == XA_WM_NAME) ? AnyPropertyType - : utf8_string, - &actual_type, &format, &nr_items, &bytes_after, - &prop) == Success && - prop) { - return (char *)prop; - } - } - return NULL; -} - -/** - * Certain X11 operations may fail in expected ways. For example, when - * attempting to interact with a window that has been closed. This handler - * avoids the application terminating in such cases. - * - * The cppcheck suppression is for a false positive: this is a callback and - * cannot be changed. - */ -// cppcheck-suppress [constParameterPointer,unmatchedSuppression] -int xerror_handler(Display *dpy _unused_, XErrorEvent *ee) { - if (ee->error_code == BadWindow || - (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) || - (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) || - (ee->request_code == X_PolyFillRectangle && - ee->error_code == BadDrawable) || - (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) || - (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) || - (ee->request_code == X_GrabButton && ee->error_code == BadAccess) || - (ee->request_code == X_GrabKey && ee->error_code == BadAccess) || - (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) { - return 0; - } - die("X error with request code=%d, error code=%d\n", ee->request_code, - ee->error_code); -} - -#define FALLBACK_CHUNK_BYTES 4 * 1024 - -/** - * Return a quarter of the maximum request size in bytes. - * - * XMaxRequestSize and XExtendedMaxRequestSize return units of 4-byte words, so - * the word count already equals 1/4 of the maximum request size in bytes. - */ -size_t get_incr_threshold(Display *dpy) { - size_t max_request_words = XExtendedMaxRequestSize(dpy); - if (max_request_words == 0) { - max_request_words = XMaxRequestSize(dpy); - } - return max_request_words ? max_request_words : FALLBACK_CHUNK_BYTES; -} - -/** - * Calculate an appropriate INCR payload size. - * - * The request size word count is a sensible threshold for deciding when to use - * INCR at all, but using that same value as the payload of each - * XChangeProperty request is too aggressive in practice. - * - * This is an interoperability constraint, not a protocol-limit one, because: - * - * - XMaxRequestSize/XExtendedMaxRequestSize only tell us what request size the - * X server will accept. - * - INCR has no chunk-size negotiation, so if a requestor dislikes large - * payloads it can simply stop deleting the property, leaving the sender - * waiting forever. - * - * Other implementations do not agree on a single rule here either: xclip uses - * a heuristic, some clipboard managers use a fixed cap, and Emacs couples - * large chunks with a timeout. - */ -size_t get_chunk_size(Display *dpy) { - size_t incr_threshold = get_incr_threshold(dpy); - return incr_threshold / 4 ? incr_threshold / 4 : FALLBACK_CHUNK_BYTES; -} - -/** - * Add a new INCR transfer to the active list. - */ -void it_add(struct incr_transfer **it_list, struct incr_transfer *it) { - if (*it_list) { - (*it_list)->prev = it; - } - it->next = *it_list; - it->prev = NULL; - *it_list = it; -} -/** - * Remove an INCR transfer from the active list. - */ -void it_remove(struct incr_transfer **it_list, struct incr_transfer *it) { - if (it->prev) { - it->prev->next = it->next; - } - if (it->next) { - it->next->prev = it->prev; - } - if (*it_list == it) { - *it_list = it->next; - } -} diff --git a/clipmenu/src/x.h b/clipmenu/src/x.h deleted file mode 100644 index a56f6d1..0000000 --- a/clipmenu/src/x.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef CM_X_H -#define CM_X_H - -#include - -#include "util.h" - -DEFINE_DROP_FUNC_VOID(XFree) - -size_t _nonnull_ get_incr_threshold(Display *dpy); -size_t _nonnull_ get_chunk_size(Display *dpy); -char _nonnull_ *get_window_title(Display *dpy, Window owner); -int xerror_handler(Display *dpy _unused_, XErrorEvent *ee); - -struct incr_transfer { - struct incr_transfer *next; - struct incr_transfer *prev; - Window requestor; - Atom property; - Atom target; - int format; - char *data; - size_t data_size; - size_t data_capacity; - size_t offset; -}; - -#define it_dbg(it, fmt, ...) \ - dbg("[incr 0x%lx] " fmt, (unsigned long)(it)->requestor, ##__VA_ARGS__) -void _nonnull_ it_add(struct incr_transfer **it_list, struct incr_transfer *it); -void _nonnull_ it_remove(struct incr_transfer **it_list, - struct incr_transfer *it); - -#endif diff --git a/clipmenu/tests/test_store.c b/clipmenu/tests/test_store.c deleted file mode 100644 index a1d598e..0000000 --- a/clipmenu/tests/test_store.c +++ /dev/null @@ -1,848 +0,0 @@ -#undef NDEBUG - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../src/store.h" -#include "../src/util.h" - -#define COL_NORMAL "\x1B[0m" -#define COL_GREEN "\x1B[32m" -#define COL_RED "\x1B[31m" - -#define t_assert(test) \ - do { \ - if (!(test)) { \ - printf(" %s[FAIL:%d] %s%s\n", COL_RED, __LINE__, #test, \ - COL_NORMAL); \ - return false; \ - } \ - printf(" %s[PASS:%d] %s%s\n", COL_GREEN, __LINE__, #test, \ - COL_NORMAL); \ - } while (0) - -#define t_run(test) \ - do { \ - printf("%s:\n", #test); \ - bool ret = test(); \ - printf("\n"); \ - if (!ret) { \ - return ret; \ - } \ - } while (0) - -#define TEST_SNIP_FILE "/clip_store_snip_test" -#define TEST_CONTENT_DIR "/dev/shm/clip_store_content_dir_test" - -static int create_test_snip_fd(void) { - shm_unlink(TEST_SNIP_FILE); - int snip_fd = shm_open(TEST_SNIP_FILE, O_RDWR | O_CREAT | O_EXCL, 0600); - assert(snip_fd >= 0); - return snip_fd; -} - -static void drop_remove_test_snip_fd(int *snip_fd) { - close(*snip_fd); - int ret = shm_unlink(TEST_SNIP_FILE); - assert(ret == 0); -} - -static void _drop_remove_test_content_dir_fd(int *dir_fd_ptr) { - int dir_fd = *dir_fd_ptr; - DIR *dir = fdopendir(dir_fd); - if (dir == NULL) { - close(dir_fd); - return; - } - - struct dirent *entry; - while ((entry = readdir(dir)) != NULL) { - if (streq(entry->d_name, ".") || streq(entry->d_name, "..")) { - continue; - } - - if (entry->d_type == DT_REG) { - int ret = unlinkat(dir_fd, entry->d_name, 0); - assert(ret == 0); - } else if (entry->d_type == DT_DIR) { - int subdir_fd = - openat(dir_fd, entry->d_name, O_RDONLY | O_DIRECTORY); - assert(subdir_fd >= 0); - _drop_remove_test_content_dir_fd(&subdir_fd); - int ret = unlinkat(dir_fd, entry->d_name, AT_REMOVEDIR); - assert(ret == 0); - } - } - - closedir(dir); -} - -static void drop_remove_test_content_dir_fd(int *dir_fd_ptr) { - _drop_remove_test_content_dir_fd(dir_fd_ptr); - int ret = rmdir(TEST_CONTENT_DIR); - assert(ret == 0); -} - -static void remove_test_content_dir(const char *path) { - int dir_fd = open(path, O_RDONLY); - if (dir_fd < 0) { - assert(errno == ENOENT); - return; - } - drop_remove_test_content_dir_fd(&dir_fd); -} - -static int create_test_content_dir_fd(void) { - remove_test_content_dir(TEST_CONTENT_DIR); - int ret = mkdir(TEST_CONTENT_DIR, 0700); - assert(ret == 0); - int dir_fd = open(TEST_CONTENT_DIR, O_RDONLY); - assert(dir_fd >= 0); - return dir_fd; -} - -static int count_test_snip_maps(void) { -#ifdef __linux__ - _drop_(fclose) FILE *maps = fopen("/proc/self/maps", "r"); - assert(maps != NULL); - - char line[4096]; - int count = 0; - while (fgets(line, sizeof(line), maps) != NULL) { - if (strstr(line, TEST_SNIP_FILE) != NULL) { - count++; - } - } - - return count; -#else - return -1; -#endif -} - -/* Test callables */ -static enum cs_remove_action remove_if_five(uint64_t hash, const char *line, - void *private) { - (void)hash; - size_t *count = private; - enum cs_remove_action ret = 0; - - if (*count == 0) { - ret |= CS_ACTION_STOP; - } - (*count)--; - - if (line[0] == '5') { - ret |= CS_ACTION_REMOVE; - } - - return ret; -} - -static struct clip_store setup_test(void) { - struct clip_store cs; - int snip_fd = create_test_snip_fd(); - int content_dir_fd = create_test_content_dir_fd(); - int ret = cs_init(&cs, snip_fd, content_dir_fd); - assert(ret == 0); - return cs; -} - -static void drop_teardown_test(struct clip_store *cs) { - int snip_fd = cs->snip_fd; - int content_dir_fd = cs->content_dir_fd; - int ret = cs_destroy(cs); - assert(ret == 0); - close(snip_fd); - shm_unlink(TEST_SNIP_FILE); - close(content_dir_fd); - remove_test_content_dir(TEST_CONTENT_DIR); -} - -static void add_ten_snips(struct clip_store *cs) { - for (char i = 0; i < 10; i++) { - char num[8]; - snprintf(num, sizeof(num), "%d", i); - int ret = cs_add(cs, num, NULL, CS_DUPE_KEEP_ALL); - assert(ret == 0); - } -} - -/* Tests */ - -static bool test__cs_init(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - t_assert(cs.snip_fd >= 0); - t_assert(cs.content_dir_fd >= 0); - - /* Check header fields were set up and are correct */ - struct stat st; - t_assert(fstat(cs.snip_fd, &st) == 0); - t_assert(st.st_size == CS_SNIP_SIZE); - t_assert(cs.header->nr_snips_alloc == 0); - - return true; -} - -static bool test__cs_init__bad_size(void) { - _drop_(remove_test_snip_fd) int snip_fd = create_test_snip_fd(); - _drop_(remove_test_content_dir_fd) int content_dir_fd = - create_test_content_dir_fd(); - t_assert(ftruncate(snip_fd, CS_SNIP_SIZE - 1) == 0); - struct clip_store cs; - t_assert(cs_init(&cs, snip_fd, content_dir_fd) == -EINVAL); - - return true; -} - -static bool test__cs_init__bad_size_aligned(void) { - _drop_(remove_test_snip_fd) int snip_fd = create_test_snip_fd(); - _drop_(remove_test_content_dir_fd) int content_dir_fd = - create_test_content_dir_fd(); - t_assert(ftruncate(snip_fd, CS_SNIP_SIZE * 2) == 0); - struct clip_store cs; - t_assert(cs_init(&cs, snip_fd, content_dir_fd) == -EINVAL); - - return true; -} - -static bool test__cs_add(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - for (char i = 0; i < 10; i++) { - char num[8]; - snprintf(num, sizeof(num), "%d", i); - - uint64_t hash; - int ret = cs_add(&cs, num, &hash, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - - _drop_(cs_content_unmap) struct cs_content content; - ret = cs_content_get(&cs, hash, &content); - t_assert(ret == 0); - t_assert(strncmp(content.data, num, content.size) == 0); - } - - t_assert(cs.header->nr_snips == 10); - - return true; -} - -static bool test__cs_content_get__keeps_fd_open(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - uint64_t hash; - int ret = cs_add(&cs, "fd lifecycle", &hash, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - - struct cs_content content; - ret = cs_content_get(&cs, hash, &content); - t_assert(ret == 0); - t_assert(fcntl(content.fd, F_GETFD) == 0); - - int unrelated_fd = open("/dev/null", O_RDONLY); - t_assert(unrelated_fd >= 0); - - drop_cs_content_unmap(&content); - - t_assert(fcntl(unrelated_fd, F_GETFD) == 0); - close(unrelated_fd); - - return true; -} - -static bool test__cs_snip_iter(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - _drop_(cs_unref) struct ref_guard guard = cs_ref(&cs); - - add_ten_snips(&cs); - - struct cs_snip *snip = NULL; - int last_num = 9; - while (cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip)) { - _drop_(cs_content_unmap) struct cs_content content; - int ret = cs_content_get(&cs, snip->hash, &content); - t_assert(ret == 0); - - /* add_ten_snips adds from 0-9, we should get the last one first */ - int num = content.data[0] - '0'; - t_assert(num == last_num--); - } - - snip = NULL; - last_num = 0; - while (cs_snip_iter(&guard, CS_ITER_OLDEST_FIRST, &snip)) { - _drop_(cs_content_unmap) struct cs_content content; - int ret = cs_content_get(&cs, snip->hash, &content); - t_assert(ret == 0); - - int num = content.data[0] - '0'; - t_assert(num == last_num++); - } - - return true; -} - -static bool test__cs_remove(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - _drop_(cs_unref) struct ref_guard guard = cs_ref(&cs); - - add_ten_snips(&cs); - - size_t nr_iter = 1; - t_assert(cs_remove(&cs, CS_ITER_NEWEST_FIRST, remove_if_five, &nr_iter) == - 0); - t_assert(cs.header->nr_snips == 10); - nr_iter = SIZE_MAX; - t_assert(cs_remove(&cs, CS_ITER_NEWEST_FIRST, remove_if_five, &nr_iter) == - 0); - t_assert(cs.header->nr_snips == 9); - - return true; -} - -static bool test__cs_trim(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - _drop_(cs_unref) struct ref_guard guard = cs_ref(&cs); - - add_ten_snips(&cs); - - struct cs_snip *snip = NULL; - while (cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip)) - ; - uint64_t oldest_hash = snip->hash; - - t_assert(cs_trim(&cs, CS_ITER_NEWEST_FIRST, 3) == 0); - t_assert(cs.header->nr_snips == 3); - - /* Check we kept the most recently added ones */ - int last_num = 9; - snip = NULL; - while (cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip)) { - _drop_(cs_content_unmap) struct cs_content content; - int ret = cs_content_get(&cs, snip->hash, &content); - t_assert(ret == 0); - - int num = content.data[0] - '0'; - t_assert(num == last_num--); - } - - /* Check the oldest snip hash is gone */ - _drop_(cs_content_unmap) struct cs_content content; - t_assert(cs_content_get(&cs, oldest_hash, &content) == -ENOENT); - - return true; -} - -static bool test__cs_replace(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - _drop_(cs_unref) struct ref_guard guard = cs_ref(&cs); - - add_ten_snips(&cs); - - const char *new = "new"; - - int ret = cs_replace(&cs, CS_ITER_NEWEST_FIRST, 1, new, NULL); - t_assert(ret == 0); - - struct cs_snip *snip = NULL; - bool i_ret = cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip); - t_assert(i_ret == true); - t_assert(streq(snip->line, "9")); - i_ret = cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip); - t_assert(i_ret == true); - t_assert(streq(snip->line, new)); - - _drop_(cs_content_unmap) struct cs_content first_content; - ret = cs_content_get(&cs, snip->hash, &first_content); - t_assert(ret == 0); - t_assert(strncmp(first_content.data, new, first_content.size) == 0); - - /* No other clips should be affected */ - int last_num = 7; - while (cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip)) { - _drop_(cs_content_unmap) struct cs_content content; - ret = cs_content_get(&cs, snip->hash, &content); - t_assert(ret == 0); - - int num = content.data[0] - '0'; - t_assert(num == last_num--); - } - - return true; -} - -static bool test__reuse_cs(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - add_ten_snips(&cs); - t_assert(cs_destroy(&cs) == 0); - - t_assert(cs_init(&cs, cs.snip_fd, cs.content_dir_fd) == 0); - t_assert(cs.header->nr_snips == 10); - - return true; -} - -static bool test__cs_add__exceeds_snip_line_size(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - /* Construct a string that exceeds CS_SNIP_LINE_SIZE */ - char long_content[CS_SNIP_LINE_SIZE + 100]; - memset(long_content, 'A', sizeof(long_content)); - long_content[sizeof(long_content) - 1] = '\0'; - - int ret = cs_add(&cs, long_content, NULL, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - - struct cs_snip *snip = NULL; - _drop_(cs_unref) struct ref_guard guard = cs_ref(&cs); - bool found = cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip); - t_assert(found); - t_assert(strlen(snip->line) == CS_SNIP_LINE_SIZE - 1); - - return true; -} - -static bool test__cs_trim__when_empty(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - int ret = cs_trim(&cs, CS_ITER_NEWEST_FIRST, 0); - t_assert(ret == 0); - t_assert(cs.header->nr_snips == 0); - - return true; -} - -static bool test__cs_remove___empty(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - /* Attempt to remove any entry, if present */ - size_t dummy = 0; - int ret = cs_remove(&cs, CS_ITER_NEWEST_FIRST, remove_if_five, &dummy); - t_assert(ret == 0); - t_assert(cs.header->nr_snips == 0); - - return true; -} - -static bool test__cs_add__around_alloc_batch_threshold(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - for (size_t i = 0; i < CS_SNIP_ALLOC_BATCH - 1; i++) { - int ret = cs_add(&cs, "test content", NULL, CS_DUPE_KEEP_ALL); - assert(ret == 0); - } - t_assert(cs.header->nr_snips == CS_SNIP_ALLOC_BATCH - 1); - - /* Add one more entry to exceed the batch threshold */ - t_assert(cs_add(&cs, "test content", NULL, CS_DUPE_KEEP_ALL) == 0); - t_assert(cs.header->nr_snips == CS_SNIP_ALLOC_BATCH); - t_assert(cs.header->nr_snips_alloc >= CS_SNIP_ALLOC_BATCH); - - return true; -} - -static bool test__cs_trim__no_remove_when_still_referenced(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - uint64_t hash; - for (size_t i = 0; i < 2; i++) { - int ret = cs_add(&cs, "test content", &hash, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - } - - _drop_(cs_content_unmap) struct cs_content content; - - t_assert(cs.header->nr_snips == 2); - t_assert(cs_content_get(&cs, hash, &content) == 0); - - t_assert(cs_trim(&cs, CS_ITER_NEWEST_FIRST, 1) == 0); - t_assert(cs.header->nr_snips == 1); - t_assert(cs_content_get(&cs, hash, &content) == 0); - - t_assert(cs_trim(&cs, CS_ITER_NEWEST_FIRST, 0) == 0); - t_assert(cs.header->nr_snips == 0); - t_assert(cs_content_get(&cs, hash, &content) == -ENOENT); - - return true; -} - -static bool test__cs_replace__out_of_bounds(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - add_ten_snips(&cs); - - int ret = cs_replace(&cs, CS_ITER_NEWEST_FIRST, 10, "test content", NULL); - t_assert(ret == -ERANGE); - - return true; -} - -static bool test__cs_replace__add_fail_keeps_old_entry(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - const char *old_content = "OLD_CONTENT"; - uint64_t old_hash; - int ret = cs_add(&cs, old_content, &old_hash, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - - const char *new_content = "new"; - uint64_t new_hash = fnv1a_64_hash(new_content); - - char hash_dir[CS_HASH_STR_MAX]; - snprintf(hash_dir, sizeof(hash_dir), PRI_HASH, new_hash); - ret = mkdirat(cs.content_dir_fd, hash_dir, 0700); - t_assert(ret == 0); - - char fake_path[CS_HASH_STR_MAX + 3]; - snprintf(fake_path, sizeof(fake_path), "%s/1", hash_dir); - _drop_(close) int fake_fd = openat(cs.content_dir_fd, fake_path, - O_WRONLY | O_CREAT | O_TRUNC, 0600); - t_assert(fake_fd >= 0); - t_assert(write(fake_fd, "X", 1) == 1); - - ret = cs_replace(&cs, CS_ITER_NEWEST_FIRST, 0, new_content, NULL); - t_assert(ret == -EFAULT); - - _drop_(cs_content_unmap) struct cs_content old_after; - ret = cs_content_get(&cs, old_hash, &old_after); - t_assert(ret == 0); - t_assert((size_t)old_after.size == strlen(old_content)); - t_assert(strncmp(old_after.data, old_content, (size_t)old_after.size) == 0); - - _drop_(cs_unref) struct ref_guard guard = cs_ref(&cs); - struct cs_snip *snip = NULL; - t_assert(cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip)); - t_assert(snip->hash == old_hash); - t_assert(streq(snip->line, old_content)); - - return true; -} - -static bool test__cs_snip__correct_nr_lines(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - add_ten_snips(&cs); - - /* No need to do exhaustive ones, they're done in test__first_line_* */ - uint64_t hash; - struct cs_snip *snip = NULL; - int ret = - cs_replace(&cs, CS_ITER_NEWEST_FIRST, 0, "one\ntwo\nthree", &hash); - - t_assert(ret == 0); - _drop_(cs_unref) struct ref_guard guard = cs_ref(&cs); - t_assert(cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip)); - t_assert(snip->hash == hash); - t_assert(snip->nr_lines == 3); - - return true; -} - -static bool test__first_line__empty(void) { - char line[CS_SNIP_LINE_SIZE]; - size_t num_lines = first_line("", line); - printf("%s\n", line); - t_assert(streq(line, "")); - t_assert(num_lines == 0); - num_lines = first_line("\n", line); - t_assert(streq(line, "")); - t_assert(num_lines == 1); - - num_lines = first_line("\n\n\n", line); - t_assert(streq(line, "")); - t_assert(num_lines == 3); - return true; -} - -static bool test__first_line__only_one(void) { - char line[CS_SNIP_LINE_SIZE]; - size_t num_lines = first_line("Foo bar\n", line); - t_assert(streq(line, "Foo bar")); - t_assert(num_lines == 1); - return true; -} - -static bool test__first_line__multiple(void) { - char line[CS_SNIP_LINE_SIZE]; - size_t num_lines = first_line("Foo bar\nbaz\nqux\n", line); - t_assert(streq(line, "Foo bar")); - t_assert(num_lines == 3); - return true; -} - -static bool test__first_line__no_final_newline(void) { - /* If the last line didn't end with a newline, still count it */ - char line[CS_SNIP_LINE_SIZE]; - size_t num_lines = first_line("Foo bar", line); - t_assert(streq(line, "Foo bar")); - t_assert(num_lines == 1); - num_lines = first_line("Foo bar\nbaz", line); - t_assert(streq(line, "Foo bar")); - t_assert(num_lines == 2); - return true; -} - -static bool test__first_line__ignore_blank_lines(void) { - char line[CS_SNIP_LINE_SIZE]; - size_t num_lines = first_line("\n\n\nFoo bar\n\n\n", line); - t_assert(streq(line, "Foo bar")); - t_assert(num_lines == 6); - return true; -} - -static bool test__first_line__unicode(void) { - char line[CS_SNIP_LINE_SIZE]; - size_t num_lines = first_line("道", line); - t_assert(streq(line, "道")); - t_assert(num_lines == 1); - num_lines = first_line("道\n", line); - t_assert(streq(line, "道")); - t_assert(num_lines == 1); - num_lines = first_line("道\n非", line); - t_assert(streq(line, "道")); - t_assert(num_lines == 2); - return true; -} - -static bool test__synchronisation(void) { - _drop_(remove_test_snip_fd) int snip_fd1 = create_test_snip_fd(); - _drop_(remove_test_content_dir_fd) int content_dir_fd1 = - create_test_content_dir_fd(); - _drop_(close) int snip_fd2 = dup(snip_fd1); - _drop_(close) int content_dir_fd2 = dup(content_dir_fd1); - - assert(snip_fd2 >= 0 && content_dir_fd2 >= 0); - - struct clip_store cs1, cs2; - int ret = cs_init(&cs1, snip_fd1, content_dir_fd1); - t_assert(ret == 0); - ret = cs_init(&cs2, snip_fd2, content_dir_fd2); - t_assert(ret == 0); - - uint64_t hash; - ret = cs_add(&cs1, "test content", &hash, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - - bool found = false; - struct cs_snip *snip = NULL; - _drop_(cs_unref) struct ref_guard guard_cs2 = cs_ref(&cs2); - while (cs_snip_iter(&guard_cs2, CS_ITER_NEWEST_FIRST, &snip)) { - if (snip->hash == hash) { - found = true; - break; - } - } - t_assert(found); - - ret = cs_trim(&cs2, CS_ITER_NEWEST_FIRST, 0); - t_assert(ret == 0); - - found = false; - snip = NULL; - _drop_(cs_unref) struct ref_guard guard_cs1 = cs_ref(&cs1); - while (cs_snip_iter(&guard_cs1, CS_ITER_NEWEST_FIRST, &snip)) { - found = true; - } - t_assert(!found); - - t_assert(cs_destroy(&cs1) == 0); - t_assert(cs_destroy(&cs2) == 0); - - return true; -} - -static bool test__cs_trim__regrow_does_not_leak_mapping(void) { - struct clip_store cs = setup_test(); - - bool added = true; - for (int i = 0; i < 1500; i++) { - char buf[32]; - snprintf(buf, sizeof(buf), "clip-%d", i); - if (cs_add(&cs, buf, NULL, CS_DUPE_KEEP_ALL) != 0) { - added = false; - break; - } - } - t_assert(added); - - int maps_after_growth = count_test_snip_maps(); - if (maps_after_growth < 0) { - drop_teardown_test(&cs); - return true; - } - t_assert(maps_after_growth == 1); - - t_assert(cs_trim(&cs, CS_ITER_NEWEST_FIRST, 0) == 0); - t_assert(count_test_snip_maps() == 1); - - t_assert(cs_add(&cs, "regrow", NULL, CS_DUPE_KEEP_ALL) == 0); - t_assert(count_test_snip_maps() == 1); - - int snip_fd = cs.snip_fd; - int content_dir_fd = cs.content_dir_fd; - t_assert(cs_destroy(&cs) == 0); - t_assert(count_test_snip_maps() == 0); - - close(snip_fd); - shm_unlink(TEST_SNIP_FILE); - close(content_dir_fd); - remove_test_content_dir(TEST_CONTENT_DIR); - - return true; -} - -static bool test__cs_add__dupe_keep_all(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - uint64_t hash1, hash2; - int ret = cs_add(&cs, "duplicate", &hash1, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - ret = cs_add(&cs, "duplicate", &hash2, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - t_assert(hash1 == hash2); - t_assert(cs.header->nr_snips == 2); - - _drop_(cs_unref) struct ref_guard guard = cs_ref(&cs); - struct cs_snip *snip = NULL; - bool iter_ret = cs_snip_iter(&guard, CS_ITER_OLDEST_FIRST, &snip); - t_assert(iter_ret == true); - t_assert(snip->hash == hash1); - iter_ret = cs_snip_iter(&guard, CS_ITER_OLDEST_FIRST, &snip); - t_assert(iter_ret == true); - t_assert(snip->hash == hash2); - - return true; -} - -static bool test__cs_add__dupe_keep_last(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - uint64_t hash1, hash2, hash3; - int ret = cs_add(&cs, "duplicate", &hash1, CS_DUPE_KEEP_LAST); - t_assert(ret == 0); - t_assert(cs.header->nr_snips == 1); - ret = cs_add(&cs, "duplicate", &hash2, CS_DUPE_KEEP_LAST); - t_assert(ret == 0); - t_assert(cs.header->nr_snips == 1); - ret = cs_add(&cs, "duplicate", &hash3, CS_DUPE_KEEP_LAST); - t_assert(ret == 0); - t_assert(cs.header->nr_snips == 1); - t_assert(hash1 == hash2); - t_assert(hash1 == hash3); - - return true; -} - -/* After adding a duplicate entry, ensure the duplicate is moved to the newest - * slot while other entries remain in order. */ -static bool test__cs_add__dupe_keep_last_with_multiple_entries(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - uint64_t hash_a, hash_dup; - int ret = cs_add(&cs, "A", &hash_a, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - ret = cs_add(&cs, "duplicate", &hash_dup, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - ret = cs_add(&cs, "B", NULL, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - t_assert(cs.header->nr_snips == 3); - /* Now add a duplicate entry with KEEP_LAST which should move the duplicate - * to the newest slot */ - ret = cs_add(&cs, "duplicate", NULL, CS_DUPE_KEEP_LAST); - t_assert(ret == 0); - t_assert(cs.header->nr_snips == 3); - _drop_(cs_unref) struct ref_guard guard = cs_ref(&cs); - struct cs_snip *snip = NULL; - bool iter_ret = cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip); - t_assert(iter_ret == true); - t_assert(snip->hash == hash_dup); - - return true; -} - -static bool check_order(struct clip_store *cs, uint64_t *order, size_t len) { - _drop_(cs_unref) struct ref_guard guard = cs_ref(cs); - struct cs_snip *snip = NULL; - for (size_t i = 0; i < len; ++i) { - bool iter_ret = cs_snip_iter(&guard, CS_ITER_NEWEST_FIRST, &snip); - t_assert(iter_ret == true); - t_assert(snip->hash == order[i]); - } - return 0; -} - -/* After calling cs_make_newest make sure the entry is at the newest slot while - * other entries remain in order. */ -static bool test__cs_make_newest(void) { - _drop_(teardown_test) struct clip_store cs = setup_test(); - - uint64_t hash_a, hash_b, hash_c; - int ret = cs_add(&cs, "A", &hash_a, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - ret = cs_add(&cs, "B", &hash_b, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - ret = cs_add(&cs, "C", &hash_c, CS_DUPE_KEEP_ALL); - t_assert(ret == 0); - t_assert(cs.header->nr_snips == 3); - - uint64_t order_before[3] = {hash_c, hash_b, hash_a}; - ret = check_order(&cs, order_before, 3); - t_assert(ret == 0); - /* Now the order should change to ["A", "C", "B"] */ - ret = cs_make_newest(&cs, hash_a); - t_assert(ret == 0); - uint64_t order_after[3] = {hash_a, hash_c, hash_b}; - ret = check_order(&cs, order_after, 3); - t_assert(ret == 0); - - return true; -} - -int main(void) { - t_run(test__cs_init); - t_run(test__cs_init__bad_size); - t_run(test__cs_init__bad_size_aligned); - t_run(test__cs_add); - t_run(test__cs_content_get__keeps_fd_open); - t_run(test__cs_snip_iter); - t_run(test__cs_remove); - t_run(test__cs_trim); - t_run(test__cs_replace); - t_run(test__reuse_cs); - t_run(test__cs_add__exceeds_snip_line_size); - t_run(test__cs_trim__when_empty); - t_run(test__cs_remove___empty); - t_run(test__cs_add__around_alloc_batch_threshold); - t_run(test__cs_replace__out_of_bounds); - t_run(test__cs_replace__add_fail_keeps_old_entry); - t_run(test__synchronisation); - t_run(test__cs_trim__regrow_does_not_leak_mapping); - t_run(test__cs_trim__no_remove_when_still_referenced); - t_run(test__cs_snip__correct_nr_lines); - t_run(test__first_line__empty); - t_run(test__first_line__only_one); - t_run(test__first_line__multiple); - t_run(test__first_line__no_final_newline); - t_run(test__first_line__ignore_blank_lines); - t_run(test__first_line__unicode); - t_run(test__cs_add__dupe_keep_all); - t_run(test__cs_add__dupe_keep_last); - t_run(test__cs_add__dupe_keep_last_with_multiple_entries); - t_run(test__cs_make_newest); - - return 0; -} diff --git a/clipmenu/tests/x_integration_tests b/clipmenu/tests/x_integration_tests deleted file mode 100755 index f24de8b..0000000 --- a/clipmenu/tests/x_integration_tests +++ /dev/null @@ -1,350 +0,0 @@ -#!/usr/bin/env bash - -set -exE -o errtrace - -dump_cache() { - printf '\nTest failure: contents of cache:\n\n' >&2 - cat "$l_out" >&2 -} - -expect_fail() { - set +e - "$@" - local status=$? - set -e - (( status != 0 )) -} - -if ! (( NO_PID_NAMESPACE )) && (( EUID )); then - export _UNSHARED=1 - exec unshare --user --map-root-user --pid --mount --fork --mount-proc "$0" "$@" -fi - -if (( _UNSHARED )); then - # Get our own tmp - mount -t tmpfs unshared_tmp /tmp -fi - -cd "${0%/*}"/.. -make clean debug -cd src - -export PATH=$PWD:$PATH - -expect_fail env CM_CONFIG=relative CM_DIR="$(mktemp -d)" clipmenu -expect_fail env CM_DIR=relative CM_CONFIG="$(mktemp)" clipmenu - -export CM_CONFIG=$(mktemp) -export CM_DIR=$(mktemp -d) -export CM_DEBUG=1 -export ASAN_OPTIONS='halt_on_error=1:abort_on_error=1' -export UBSAN_OPTIONS='halt_on_error=1:abort_on_error=1' -uid=$(id -u) -[[ "$(clipctl version)" == 7 ]] -[[ "$(clipctl cache-dir)" == "$CM_DIR/clipmenu.7.$uid" ]] - -launcher=$(mktemp) -l_out=$(mktemp) - -trap dump_cache ERR - -cat > "$launcher" << EOF -#!/bin/bash - -cat > "$l_out" - -if [[ \$SELECT ]]; then - printf '%s\\n' "\$SELECT" -fi -EOF -chmod a+x "$launcher" -export CM_LAUNCHER="$launcher" - -tilde_home=$(mktemp -d) -tilde_dir="$tilde_home/clipmenu_tilde" -mkdir -p "$tilde_dir" -HOME="$tilde_home" CM_DIR="~/clipmenu_tilde" CM_CONFIG="$(mktemp)" \ - clipmenu || true -[[ -d "$tilde_dir/clipmenu.7.$uid" ]] -rm -rf "$tilde_home" - -tilde_conf_home=$(mktemp -d) -printf 'max_clips nope\n' > "$tilde_conf_home/bad.conf" -err=$(HOME="$tilde_conf_home" CM_CONFIG="~/bad.conf" CM_DIR="$(mktemp -d)" \ - clipmenu 2>&1 || true) -[[ "$err" == *"for max_clips"* ]] -rm -rf "$tilde_conf_home" - -kill_background_jobs() { - local -a bg - readarray -t bg < <(jobs -p) - (( ${#bg[@]} )) && kill -- "${bg[@]}" 2>/dev/null -} - -primary() { - printf '%s' "${1?}" | xsel -p -} - -trap 'kill_background_jobs' EXIT - -settle() { - sleep 0.2 -} - -long_settle() { - sleep 2 -} - -check_nr_clips() { - if [[ $SELECT ]]; then - clipmenu - else - # On no selection, we exit 1. Avoid masking real problems by only doing - # it when no selection is specified. - clipmenu || true - fi - (( $(wc -l < "$l_out") == "${1?}" )) -} - -wait_nr_clips() { - local want=${1?} - local deadline=$((SECONDS + 5)) - - while (( SECONDS < deadline )); do - if check_nr_clips "$want"; then - return 0 - fi - sleep 0.1 - done - - check_nr_clips "$want" -} - -if ! (( USE_CURRENT_DISPLAY )); then - export DISPLAY=:1911 - Xvfb "$DISPLAY" & - sleep 2 -fi - -# Clear selections -xsel -bc -xsel -pc -xsel -sc - -clipmenud & -clipmenud_pid=$! -settle - -# Should be empty -check_nr_clips 0 - -primary foo -settle - -check_nr_clips 1 - -# Put the same one repeatedly, should ignore it -primary foo -primary foo -primary foo -settle - -settle -check_nr_clips 1 -[[ "$(< "$l_out")" == "[1] foo" ]] - -# Put possible partials, should update it -primary fooa -primary fooab -primary fooabc - -# Should have replaced the old one -settle -check_nr_clips 1 -[[ "$(< "$l_out")" == "[1] fooabc" ]] - -primary fooab -primary fooa -primary foo - -# Should have replaced the old one -settle -check_nr_clips 1 -[[ "$(< "$l_out")" == "[1] foo" ]] - -# Put some more content on the clipboard for testing -primary bar -primary baz -settle - -check_nr_clips 3 - -# Nothing gets deleted, but we recognise the right clips -[[ $(clipdel a) == $'bar\nbaz' ]] -check_nr_clips 3 - -# Likewise but inversion -[[ $(clipdel -v a) == foo ]] -check_nr_clips 3 - -# Real delete with inversion -[[ $(clipdel -dv a) == foo ]] -check_nr_clips 2 - -# Test literal match -primary '*foo' -settle -check_nr_clips 3 - -[[ $(clipdel -dF '*') == '*foo' ]] -check_nr_clips 2 - -# Test -n -primary latest -settle -check_nr_clips 3 - -# Should delete the newest entry -[[ $(clipdel -dn 1) == 'latest' ]] -check_nr_clips 2 - -# Test -N -primary wibble -primary wobble -settle -check_nr_clips 4 - -# Should delete everything other than the oldest 2 entries -[[ $(clipdel -dvN 2) == $'wibble\nwobble' ]] -check_nr_clips 2 - -# Test clipdelmenu -primary test1 -primary test2 -primary test3 -settle -check_nr_clips 5 - -# Should delete the selected entry -SELECT='[4] test2' clipdelmenu -settle -check_nr_clips 4 - -# Verify test2 is gone and test1, test3 remain -clipmenu || true -[[ "$(< "$l_out")" == *"test1"* ]] -[[ "$(< "$l_out")" == *"test3"* ]] -[[ "$(< "$l_out")" != *"test2"* ]] - -# Delete the others -SELECT='[3] test1' clipdelmenu -SELECT='[3] test3' clipdelmenu - -# Test multi-select deletion -primary multi1 -primary multi2 -settle -check_nr_clips 4 - -SELECT=$'[3] multi1\n[4] multi2' clipdelmenu -check_nr_clips 2 - -# Check selecting starts serving -xsel -pc - -SELECT='[1] bar' clipmenu -settle -[[ "$(xsel -po)" == bar ]] - -SELECT='[2] baz' clipmenu -settle -[[ "$(xsel -po)" == baz ]] - -# Disable populates no new clips -clipctl disable -[[ "$(clipctl status)" == disabled ]] -primary wibble -primary wobble -settle -check_nr_clips 2 - -# Enable enables again -clipctl enable -[[ "$(clipctl status)" == enabled ]] -primary wibble -primary wobble -settle -check_nr_clips 4 - -# Toggle works both ways -clipctl toggle -[[ "$(clipctl status)" == disabled ]] -clipctl toggle -[[ "$(clipctl status)" == enabled ]] - -# Test INCR support -set +x -printf '%.0sa' {1..9999999} | xsel -p -set -x -long_settle # This is a big one, give it some time -len_before=$(xsel -po | wc -c) -check_nr_clips 5 -SELECT='[5] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...' \ - clipmenu -long_settle -len_after=$(xsel -po | wc -c) - -# Make sure we got the whole thing -(( len_before == len_after )) - -# Issue #241. Put a large clipboard selection via INCR, then put something else -# small immediately after. -xsel -bc -settle -check_nr_clips 5 - -# Large selection (INCR) -printf '%.0sa' {1..4001} | xsel -b -wait_nr_clips 6 - -# After large selection is stored, put something small that's a possible # -# partial (non-INCR) -printf a | xsel -b -settle - -check_nr_clips 6 - -# own_selections: default does not own PRIMARY -kill "$clipmenud_pid" -wait "$clipmenud_pid" || true -pkill -x clipserve 2>/dev/null || true -CM_OWN_CLIPBOARD=1 CM_OWN_SELECTIONS=clipboard clipmenud & -clipmenud_pid=$! -settle -xsel -bc -xsel -pc -printf 'primary_stale' | xsel -p -printf 'clip_default' | xsel -b -settle -[[ "$(xsel -bo)" == clip_default ]] -primary_out=$(xsel -po 2>/dev/null || true) -[[ "$primary_out" != clip_default ]] - -# own_selections: owns all listed selections -kill "$clipmenud_pid" -wait "$clipmenud_pid" || true -pkill -x clipserve 2>/dev/null || true -CM_OWN_CLIPBOARD=1 CM_OWN_SELECTIONS='clipboard primary' clipmenud & -clipmenud_pid=$! -settle -xsel -bc -xsel -pc -printf 'primary_stale' | xsel -p -printf 'own_both' | xsel -b -settle -[[ "$(xsel -bo)" == own_both ]] -[[ "$(xsel -po)" == own_both ]] - -if (( _UNSHARED )); then - umount -l /tmp -fi diff --git a/dwm/config.h b/dwm/config.h index dc970d6..c035071 100644 --- a/dwm/config.h +++ b/dwm/config.h @@ -65,7 +65,7 @@ static const Layout layouts[] = { static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_bg, "-nf", col_fg, "-sb", col_accent, "-sf", col_accent_fg, NULL }; static const char *termcmd[] = { "st", NULL }; -static const char *clipcmd[] = { "clipmenu", "-fn", dmenufont, "-nb", col_bg, "-nf", col_fg, "-sb", col_accent, "-sf", col_accent_fg, NULL }; +static const char *clipcmd[] = { "clipmenu", NULL }; static const char *lockcmd[] = { "slock", NULL }; /* Audio: tras cada cambio se avisa a slstatus (SIGUSR1) para que refresque la barra al momento */ diff --git a/slock/LICENSE b/slock/LICENSE deleted file mode 100644 index 8b44e16..0000000 --- a/slock/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -MIT/X Consortium License - -© 2016-2026 Hiltjo Posthuma -© 2015-2016 Markus Teich -© 2014 Dimitris Papastamos -© 2006-2014 Anselm R Garbe -© 2014-2016 Laslo Hunhold - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/slock/Makefile b/slock/Makefile deleted file mode 100644 index c41e57e..0000000 --- a/slock/Makefile +++ /dev/null @@ -1,46 +0,0 @@ -# slock - simple screen locker -# See LICENSE file for copyright and license details. - -include config.mk - -SRC = slock.c ${COMPATSRC} -OBJ = ${SRC:.c=.o} - -all: slock - -.c.o: - ${CC} -c ${CFLAGS} $< - -${OBJ}: config.h config.mk util.h - -config.h: - cp config.def.h $@ - -slock: ${OBJ} - ${CC} -o $@ ${OBJ} ${LDFLAGS} - -clean: - rm -f slock ${OBJ} slock-${VERSION}.tar.gz - -dist: clean - mkdir -p slock-${VERSION} - cp -R LICENSE Makefile README slock.1 config.mk \ - ${SRC} config.def.h util.h slock-${VERSION} - tar -cf slock-${VERSION}.tar slock-${VERSION} - gzip slock-${VERSION}.tar - rm -rf slock-${VERSION} - -install: all - mkdir -p ${DESTDIR}${PREFIX}/bin - cp -f slock ${DESTDIR}${PREFIX}/bin - chmod 755 ${DESTDIR}${PREFIX}/bin/slock - chmod u+s ${DESTDIR}${PREFIX}/bin/slock - mkdir -p ${DESTDIR}${MANPREFIX}/man1 - sed "s/VERSION/${VERSION}/g" ${DESTDIR}${MANPREFIX}/man1/slock.1 - chmod 644 ${DESTDIR}${MANPREFIX}/man1/slock.1 - -uninstall: - rm -f ${DESTDIR}${PREFIX}/bin/slock - rm -f ${DESTDIR}${MANPREFIX}/man1/slock.1 - -.PHONY: all clean dist install uninstall diff --git a/slock/README b/slock/README deleted file mode 100644 index dcacd01..0000000 --- a/slock/README +++ /dev/null @@ -1,24 +0,0 @@ -slock - simple screen locker -============================ -simple screen locker utility for X. - - -Requirements ------------- -In order to build slock you need the Xlib header files. - - -Installation ------------- -Edit config.mk to match your local setup (slock is installed into -the /usr/local namespace by default). - -Afterwards enter the following command to build and install slock -(if necessary as root): - - make clean install - - -Running slock -------------- -Simply invoke the 'slock' command. To get out of it, enter your password. diff --git a/slock/config.def.h b/slock/config.def.h deleted file mode 100644 index 9855e21..0000000 --- a/slock/config.def.h +++ /dev/null @@ -1,12 +0,0 @@ -/* user and group to drop privileges to */ -static const char *user = "nobody"; -static const char *group = "nogroup"; - -static const char *colorname[NUMCOLS] = { - [INIT] = "black", /* after initialization */ - [INPUT] = "#005577", /* during input */ - [FAILED] = "#CC3333", /* wrong password */ -}; - -/* treat a cleared input like a wrong password (color) */ -static const int failonclear = 1; diff --git a/slock/config.h b/slock/config.h deleted file mode 100644 index fd5a3b0..0000000 --- a/slock/config.h +++ /dev/null @@ -1,12 +0,0 @@ -/* user and group to drop privileges to */ -static const char *user = "nobody"; -static const char *group = "nogroup"; - -static const char *colorname[NUMCOLS] = { - [INIT] = "#2d353b", /* after initialization (fondo Everforest) */ - [INPUT] = "#a7c080", /* during input (verde acento) */ - [FAILED] = "#e67e80", /* wrong password (rojo Everforest) */ -}; - -/* treat a cleared input like a wrong password (color) */ -static const int failonclear = 1; diff --git a/slock/config.mk b/slock/config.mk deleted file mode 100644 index 9fe5bff..0000000 --- a/slock/config.mk +++ /dev/null @@ -1,29 +0,0 @@ -# slock version -VERSION = 1.7 - -# Customize below to fit your system - -# paths -PREFIX = /usr/local -MANPREFIX = ${PREFIX}/share/man - -X11INC = /usr/X11R6/include -X11LIB = /usr/X11R6/lib - -# includes and libs -INCS = -I. -I/usr/include -I${X11INC} -LIBS = -L/usr/lib -lc -lcrypt -L${X11LIB} -lX11 -lXext -lXrandr - -# flags -CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE -DHAVE_SHADOW_H -CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} -LDFLAGS = -s ${LIBS} -COMPATSRC = explicit_bzero.c - -# On OpenBSD and Darwin remove -lcrypt from LIBS -#LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lXext -lXrandr -# On *BSD remove -DHAVE_SHADOW_H from CPPFLAGS -# On NetBSD add -D_NETBSD_SOURCE to CPPFLAGS -#CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE -D_NETBSD_SOURCE -# On OpenBSD set COMPATSRC to empty -#COMPATSRC = diff --git a/slock/explicit_bzero.c b/slock/explicit_bzero.c deleted file mode 100644 index 3e33ca8..0000000 --- a/slock/explicit_bzero.c +++ /dev/null @@ -1,19 +0,0 @@ -/* $OpenBSD: explicit_bzero.c,v 1.3 2014/06/21 02:34:26 matthew Exp $ */ -/* - * Public domain. - * Written by Matthew Dempsky. - */ - -#include - -__attribute__((weak)) void -__explicit_bzero_hook(void *buf, size_t len) -{ -} - -void -explicit_bzero(void *buf, size_t len) -{ - memset(buf, 0, len); - __explicit_bzero_hook(buf, len); -} diff --git a/slock/slock.1 b/slock/slock.1 deleted file mode 100644 index e485992..0000000 --- a/slock/slock.1 +++ /dev/null @@ -1,36 +0,0 @@ -.Dd September 5, 2026 -.Dt SLOCK 1 -.Os -.Sh NAME -.Nm slock -.Nd simple X screen locker -.Sh SYNOPSIS -.Nm -.Op Fl v -.Sh DESCRIPTION -.Nm -is a simple X screen locker. -The options are as follows: -.Bl -tag -width Ds -.It Fl v -Print version information to stdout and exit. -.El -.Sh EXIT STATUS -.Ex -std -.Sh SECURITY CONSIDERATIONS -To make sure a locked screen can not be bypassed by switching VTs -or killing the X server with Ctrl+Alt+Backspace, it is recommended -to disable both in -.Xr xorg.conf 5 -for maximum security: -.Bd -literal -Section "ServerFlags" - Option "DontVTSwitch" "True" - Option "DontZap" "True" -EndSection -.Ed -.Sh CUSTOMIZATION -.Nm -can be customized by creating a custom config.h from config.def.h and -(re)compiling the source code. -This keeps it fast, secure and simple. diff --git a/slock/slock.c b/slock/slock.c deleted file mode 100644 index 9c587e1..0000000 --- a/slock/slock.c +++ /dev/null @@ -1,382 +0,0 @@ -/* See LICENSE file for license details. */ -#define _XOPEN_SOURCE 500 -#if HAVE_SHADOW_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "util.h" - -char *argv0; - -enum { - INIT, - INPUT, - FAILED, - NUMCOLS -}; - -struct lock { - int screen; - Window root, win; - Pixmap pmap; - unsigned long colors[NUMCOLS]; -}; - -struct xrandr { - int active; - int evbase; - int errbase; -}; - -#include "config.h" - -static void -die(const char *errstr, ...) -{ - va_list ap; - - va_start(ap, errstr); - vfprintf(stderr, errstr, ap); - va_end(ap); - exit(1); -} - -#ifdef __linux__ -#include -#include - -static void -dontkillme(void) -{ - FILE *f; - const char oomfile[] = "/proc/self/oom_score_adj"; - - if (!(f = fopen(oomfile, "w"))) { - if (errno == ENOENT) - return; - die("slock: fopen %s: %s\n", oomfile, strerror(errno)); - } - fprintf(f, "%d", OOM_SCORE_ADJ_MIN); - if (fclose(f)) { - if (errno == EACCES) - die("slock: unable to disable OOM killer. " - "Make sure to suid or sgid slock.\n"); - else - die("slock: fclose %s: %s\n", oomfile, strerror(errno)); - } -} -#endif - -static const char * -gethash(void) -{ - const char *hash; - struct passwd *pw; - - /* Check if the current user has a password entry */ - errno = 0; - if (!(pw = getpwuid(getuid()))) { - if (errno) - die("slock: getpwuid: %s\n", strerror(errno)); - else - die("slock: cannot retrieve password entry\n"); - } - hash = pw->pw_passwd; - -#if HAVE_SHADOW_H - if (!strcmp(hash, "x")) { - struct spwd *sp; - if (!(sp = getspnam(pw->pw_name))) - die("slock: getspnam: cannot retrieve shadow entry. " - "Make sure to suid or sgid slock.\n"); - hash = sp->sp_pwdp; - } -#else - if (!strcmp(hash, "*")) { -#ifdef __OpenBSD__ - if (!(pw = getpwuid_shadow(getuid()))) - die("slock: getpwnam_shadow: cannot retrieve shadow entry. " - "Make sure to suid or sgid slock.\n"); - hash = pw->pw_passwd; -#else - die("slock: getpwuid: cannot retrieve shadow entry. " - "Make sure to suid or sgid slock.\n"); -#endif /* __OpenBSD__ */ - } -#endif /* HAVE_SHADOW_H */ - - return hash; -} - -static void -readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens, - const char *hash) -{ - XRRScreenChangeNotifyEvent *rre; - char buf[32], passwd[256], *inputhash; - int num, screen, running, failure, oldc; - unsigned int len, color; - KeySym ksym; - XEvent ev; - - len = 0; - running = 1; - failure = 0; - oldc = INIT; - - while (running && !XNextEvent(dpy, &ev)) { - if (ev.type == KeyPress) { - explicit_bzero(&buf, sizeof(buf)); - num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0); - if (IsKeypadKey(ksym)) { - if (ksym == XK_KP_Enter) - ksym = XK_Return; - else if (ksym >= XK_KP_0 && ksym <= XK_KP_9) - ksym = (ksym - XK_KP_0) + XK_0; - } - if (IsFunctionKey(ksym) || - IsKeypadKey(ksym) || - IsMiscFunctionKey(ksym) || - IsPFKey(ksym) || - IsPrivateKeypadKey(ksym)) - continue; - switch (ksym) { - case XK_Return: - passwd[len] = '\0'; - errno = 0; - if (!(inputhash = crypt(passwd, hash))) - fprintf(stderr, "slock: crypt: %s\n", strerror(errno)); - else - running = !!strcmp(inputhash, hash); - if (running) { - XBell(dpy, 100); - failure = 1; - } - explicit_bzero(&passwd, sizeof(passwd)); - len = 0; - break; - case XK_Escape: - explicit_bzero(&passwd, sizeof(passwd)); - len = 0; - break; - case XK_BackSpace: - if (len) - passwd[--len] = '\0'; - break; - default: - if (num && !iscntrl((unsigned char)buf[0]) && - (len + num < sizeof(passwd))) { - memcpy(passwd + len, buf, num); - len += num; - } else if (buf[0] == '\025') { /* ctrl-u clears input */ - explicit_bzero(&passwd, sizeof(passwd)); - len = 0; - } - break; - } - color = len ? INPUT : ((failure || failonclear) ? FAILED : INIT); - if (running && oldc != color) { - for (screen = 0; screen < nscreens; screen++) { - XSetWindowBackground(dpy, - locks[screen]->win, - locks[screen]->colors[color]); - XClearWindow(dpy, locks[screen]->win); - } - oldc = color; - } - } else if (rr->active && ev.type == rr->evbase + RRScreenChangeNotify) { - rre = (XRRScreenChangeNotifyEvent*)&ev; - for (screen = 0; screen < nscreens; screen++) { - if (locks[screen]->win == rre->window) { - if (rre->rotation == RR_Rotate_90 || - rre->rotation == RR_Rotate_270) - XResizeWindow(dpy, locks[screen]->win, - rre->height, rre->width); - else - XResizeWindow(dpy, locks[screen]->win, - rre->width, rre->height); - XClearWindow(dpy, locks[screen]->win); - break; - } - } - } else { - for (screen = 0; screen < nscreens; screen++) - XRaiseWindow(dpy, locks[screen]->win); - } - } -} - -static struct lock * -lockscreen(Display *dpy, struct xrandr *rr, int screen) -{ - char curs[] = {0, 0, 0, 0, 0, 0, 0, 0}; - int i, ptgrab, kbgrab; - struct lock *lock; - XColor color, dummy; - XSetWindowAttributes wa; - Cursor invisible; - - if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(struct lock)))) - return NULL; - - lock->screen = screen; - lock->root = RootWindow(dpy, lock->screen); - - for (i = 0; i < NUMCOLS; i++) { - XAllocNamedColor(dpy, DefaultColormap(dpy, lock->screen), - colorname[i], &color, &dummy); - lock->colors[i] = color.pixel; - } - - /* init */ - wa.override_redirect = 1; - wa.background_pixel = lock->colors[INIT]; - lock->win = XCreateWindow(dpy, lock->root, 0, 0, - DisplayWidth(dpy, lock->screen), - DisplayHeight(dpy, lock->screen), - 0, DefaultDepth(dpy, lock->screen), - CopyFromParent, - DefaultVisual(dpy, lock->screen), - CWOverrideRedirect | CWBackPixel, &wa); - lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8); - invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap, - &color, &color, 0, 0); - XDefineCursor(dpy, lock->win, invisible); - - /* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */ - for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) { - if (ptgrab != GrabSuccess) { - ptgrab = XGrabPointer(dpy, lock->root, False, - ButtonPressMask | ButtonReleaseMask | - PointerMotionMask, GrabModeAsync, - GrabModeAsync, None, invisible, CurrentTime); - } - if (kbgrab != GrabSuccess) { - kbgrab = XGrabKeyboard(dpy, lock->root, True, - GrabModeAsync, GrabModeAsync, CurrentTime); - } - - /* input is grabbed: we can lock the screen */ - if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) { - XMapRaised(dpy, lock->win); - if (rr->active) - XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask); - - XSelectInput(dpy, lock->root, SubstructureNotifyMask); - return lock; - } - - /* retry on AlreadyGrabbed but fail on other errors */ - if ((ptgrab != AlreadyGrabbed && ptgrab != GrabSuccess) || - (kbgrab != AlreadyGrabbed && kbgrab != GrabSuccess)) - break; - - usleep(100000); - } - - /* we couldn't grab all input: fail out */ - if (ptgrab != GrabSuccess) - fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n", - screen); - if (kbgrab != GrabSuccess) - fprintf(stderr, "slock: unable to grab keyboard for screen %d\n", - screen); - return NULL; -} - -static void -usage(void) -{ - die("usage: slock [-v]\n"); -} - -int -main(int argc, char **argv) { - struct xrandr rr; - struct lock **locks; - struct passwd *pwd; - struct group *grp; - uid_t duid; - gid_t dgid; - const char *hash; - Display *dpy; - int s, nlocks, nscreens; - - if (argc > 1 && !strcmp(argv[1], "-v")) { - puts("slock-"VERSION); - return 0; - } else if (argc > 1) { - usage(); - } - - /* validate drop-user and -group */ - errno = 0; - if (!(pwd = getpwnam(user))) - die("slock: getpwnam %s: %s\n", user, - errno ? strerror(errno) : "user entry not found"); - duid = pwd->pw_uid; - errno = 0; - if (!(grp = getgrnam(group))) - die("slock: getgrnam %s: %s\n", group, - errno ? strerror(errno) : "group entry not found"); - dgid = grp->gr_gid; - -#ifdef __linux__ - dontkillme(); -#endif - - hash = gethash(); - errno = 0; - if (!crypt("", hash)) - die("slock: crypt: %s\n", strerror(errno)); - - if (!(dpy = XOpenDisplay(NULL))) - die("slock: cannot open display\n"); - - /* drop privileges */ - if (setgroups(0, NULL) < 0) - die("slock: setgroups: %s\n", strerror(errno)); - if (setgid(dgid) < 0) - die("slock: setgid: %s\n", strerror(errno)); - if (setuid(duid) < 0) - die("slock: setuid: %s\n", strerror(errno)); - - /* check for Xrandr support */ - rr.active = XRRQueryExtension(dpy, &rr.evbase, &rr.errbase); - - /* get number of screens in display "dpy" and blank them */ - nscreens = ScreenCount(dpy); - if (!(locks = calloc(nscreens, sizeof(struct lock *)))) - die("slock: out of memory\n"); - for (nlocks = 0, s = 0; s < nscreens; s++) { - if ((locks[s] = lockscreen(dpy, &rr, s)) != NULL) - nlocks++; - else - break; - } - XSync(dpy, 0); - - /* did we manage to lock everything? */ - if (nlocks != nscreens) - return 1; - - /* everything is now blank. Wait for the correct password */ - readpw(dpy, &rr, locks, nscreens, hash); - - return 0; -} diff --git a/slock/util.h b/slock/util.h deleted file mode 100644 index 6f748b8..0000000 --- a/slock/util.h +++ /dev/null @@ -1,2 +0,0 @@ -#undef explicit_bzero -void explicit_bzero(void *, size_t); diff --git a/st/Makefile b/st/Makefile index dfcea0f..15db421 100644 --- a/st/Makefile +++ b/st/Makefile @@ -4,7 +4,7 @@ include config.mk -SRC = st.c x.c hb.c +SRC = st.c x.c OBJ = $(SRC:.c=.o) all: st @@ -16,8 +16,7 @@ config.h: $(CC) $(STCFLAGS) -c $< st.o: config.h st.h win.h -x.o: arg.h config.h st.h win.h hb.h -hb.o: st.h +x.o: arg.h config.h st.h win.h $(OBJ): config.h config.mk diff --git a/st/config.def.h b/st/config.def.h index 019a4e1..2cd740a 100644 --- a/st/config.def.h +++ b/st/config.def.h @@ -93,9 +93,6 @@ char *termname = "st-256color"; */ unsigned int tabspaces = 8; -/* bg opacity */ -float alpha = 0.8; - /* Terminal colors (16 first used in escape sequence) */ static const char *colorname[] = { /* 8 normal colors */ diff --git a/st/config.h b/st/config.h index 2e27b4d..261b129 100644 --- a/st/config.h +++ b/st/config.h @@ -83,49 +83,45 @@ char *termname = "st-256color"; * the st.info and appropriately install the st.info in the environment where * you use this st version. * - * it#$tabspaces, + * it#$tabspaces, * * Secondly make sure your kernel is not expanding tabs. When running `stty * -a` »tab0« should appear. You can tell the terminal to not expand tabs by - * running following command: + * running following command: * - * stty tabs + * stty tabs */ unsigned int tabspaces = 8; -/* bg opacity */ -float alpha = 0.9; - /* Terminal colors (16 first used in escape sequence) */ static const char *colorname[] = { - /* Paleta Everforest (dark medium) */ - /* 8 normal colors */ - "#343f44", /* 0: black */ - "#e67e80", /* 1: red */ - "#a7c080", /* 2: green */ - "#dbbc7f", /* 3: yellow */ - "#7fbbb3", /* 4: blue */ - "#d699b6", /* 5: magenta */ - "#83c092", /* 6: cyan */ - "#d3c6aa", /* 7: white */ + /* 8 normal colors */ + "#343f44", /* 0: black */ + "#e67e80", /* 1: red */ + "#a7c080", /* 2: green */ + "#dbbc7f", /* 3: yellow */ + "#7fbbb3", /* 4: blue */ + "#d699b6", /* 5: magenta */ + "#83c092", /* 6: cyan */ + "#d3c6aa", /* 7: white */ - /* 8 bright colors (un 18% más claros; el negro brillante es el gris de comentarios) */ - "#859289", /* 8: bright black */ - "#ea9597", /* 9: bright red */ - "#b7cb97", /* 10: bright green */ - "#e1c896", /* 11: bright yellow */ - "#96c7c1", /* 12: bright blue */ - "#ddabc3", /* 13: bright magenta */ - "#99cba6", /* 14: bright cyan */ - "#dbd0b9", /* 15: bright white */ + /* 8 bright colors */ + "#475258", /* 8: bright black */ + "#e67e80", /* 9: bright red */ + "#a7c080", /* 10: bright green */ + "#dbbc7f", /* 11: bright yellow */ + "#7fbbb3", /* 12: bright blue */ + "#d699b6", /* 13: bright magenta */ + "#83c092", /* 14: bright cyan */ + "#d3c6aa", /* 15: bright white */ - [255] = 0, + [255] = 0, - /* more colors can be added after 255 to use with DefaultXX */ - "#a7c080", /* 256: default cursor colour (verde acento) */ - "#3d484d", /* 257: default reverse cursor colour */ - "#d3c6aa", /* 258: default foreground colour */ - "#2d353b", /* 259: default background colour */ + /* more colors can be added after 255 to use with DefaultXX */ + "#a7c080", /* 256: default cursor colour (Everforest Green Accent) */ + "#3d484d", /* 257: default reverse cursor colour */ + "#d3c6aa", /* 258: default foreground colour (Everforest FG) */ + "#2d353b", /* 259: default background colour (Everforest Dark Medium) */ }; @@ -179,12 +175,12 @@ static uint forcemousemod = ShiftMask; * Beware that overloading Button1 will disable the selection. */ static MouseShortcut mshortcuts[] = { - /* mask button function argument release */ - { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, - { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, - { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, - { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, - { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, }; /* Internal keyboard shortcuts. */ @@ -192,19 +188,19 @@ static MouseShortcut mshortcuts[] = { #define TERMMOD (ControlMask|ShiftMask) static Shortcut shortcuts[] = { - /* mask keysym function argument */ - { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, - { ControlMask, XK_Print, toggleprinter, {.i = 0} }, - { ShiftMask, XK_Print, printscreen, {.i = 0} }, - { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, - { TERMMOD, XK_Prior, zoom, {.f = +1} }, - { TERMMOD, XK_Next, zoom, {.f = -1} }, - { TERMMOD, XK_Home, zoomreset, {.f = 0} }, - { TERMMOD, XK_C, clipcopy, {.i = 0} }, - { TERMMOD, XK_V, clippaste, {.i = 0} }, - { TERMMOD, XK_Y, selpaste, {.i = 0} }, - { ShiftMask, XK_Insert, selpaste, {.i = 0} }, - { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, }; /* @@ -216,7 +212,7 @@ static Shortcut shortcuts[] = { * appkey value: * * 0: no value * * > 0: keypad application mode enabled - * * = 2: term.numlock = 1 + * * = 2: term.numlock = 1 * * < 0: keypad application mode disabled * appcursor value: * * 0: no value @@ -245,216 +241,216 @@ static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; * world. Please decide about changes wisely. */ static Key key[] = { - /* keysym mask string appkey appcursor */ - { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, - { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, - { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, - { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, - { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, - { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, - { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, - { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, - { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, - { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, - { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, - { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, - { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, - { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, - { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, - { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, - { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, - { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, - { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, - { XK_KP_End, ControlMask, "\033[J", -1, 0}, - { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, - { XK_KP_End, ShiftMask, "\033[K", -1, 0}, - { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, - { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, - { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, - { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, - { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, - { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, - { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, - { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, - { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, - { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, - { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, - { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, - { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, - { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, - { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, - { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, - { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, - { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, - { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, - { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, - { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, - { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, - { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, - { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, - { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, - { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, - { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, - { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, - { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, - { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, - { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, - { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, - { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, - { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, - { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, - { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, - { XK_Up, ControlMask, "\033[1;5A", 0, 0}, - { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, - { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, - { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, - { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, - { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, - { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, - { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, - { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, - { XK_Down, ControlMask, "\033[1;5B", 0, 0}, - { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, - { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, - { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, - { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, - { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, - { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, - { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, - { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, - { XK_Left, ControlMask, "\033[1;5D", 0, 0}, - { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, - { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, - { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, - { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, - { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, - { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, - { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, - { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, - { XK_Right, ControlMask, "\033[1;5C", 0, 0}, - { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, - { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, - { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, - { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, - { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, - { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, - { XK_Return, Mod1Mask, "\033\r", 0, 0}, - { XK_Return, XK_ANY_MOD, "\r", 0, 0}, - { XK_Insert, ShiftMask, "\033[4l", -1, 0}, - { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, - { XK_Insert, ControlMask, "\033[L", -1, 0}, - { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, - { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, - { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, - { XK_Delete, ControlMask, "\033[M", -1, 0}, - { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, - { XK_Delete, ShiftMask, "\033[2K", -1, 0}, - { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, - { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, - { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, - { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, - { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, - { XK_Home, ShiftMask, "\033[2J", 0, -1}, - { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, - { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, - { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, - { XK_End, ControlMask, "\033[J", -1, 0}, - { XK_End, ControlMask, "\033[1;5F", +1, 0}, - { XK_End, ShiftMask, "\033[K", -1, 0}, - { XK_End, ShiftMask, "\033[1;2F", +1, 0}, - { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, - { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, - { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, - { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, - { XK_Next, ControlMask, "\033[6;5~", 0, 0}, - { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, - { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, - { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, - { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, - { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, - { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, - { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, - { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, - { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, - { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, - { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, - { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, - { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, - { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, - { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, - { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, - { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, - { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, - { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, - { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, - { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, - { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, - { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, - { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, - { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, - { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, - { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, - { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, - { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, - { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, - { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, - { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, - { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, - { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, - { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, - { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, - { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, - { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, - { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, - { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, - { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, - { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, - { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, - { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, - { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, - { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, - { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, - { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, - { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, - { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, - { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, - { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, - { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, - { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, - { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, - { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, - { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, - { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, - { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, - { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, - { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, - { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, - { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, - { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, - { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, - { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, - { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, - { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, - { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, - { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, - { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, - { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, - { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, - { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, - { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, - { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, - { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, - { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, - { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, - { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, - { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, - { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, - { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, - { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, - { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, - { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, - { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, - { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, }; /* @@ -465,7 +461,7 @@ static Key key[] = { * If no match is found, regular selection is used. */ static uint selmasks[] = { - [SEL_RECTANGULAR] = Mod1Mask, + [SEL_RECTANGULAR] = Mod1Mask, }; /* @@ -473,6 +469,6 @@ static uint selmasks[] = { * of single wide characters. */ static char ascii_printable[] = - " !\"#$%&'()*+,-./0123456789:;<=>?" - "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" - "`abcdefghijklmnopqrstuvwxyz{|}~"; + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/st/config.mk b/st/config.mk index ec69d1a..2fc854e 100644 --- a/st/config.mk +++ b/st/config.mk @@ -15,12 +15,10 @@ PKG_CONFIG = pkg-config # includes and libs INCS = -I$(X11INC) \ `$(PKG_CONFIG) --cflags fontconfig` \ - `$(PKG_CONFIG) --cflags freetype2` \ - `$(PKG_CONFIG) --cflags harfbuzz` + `$(PKG_CONFIG) --cflags freetype2` LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ `$(PKG_CONFIG) --libs fontconfig` \ - `$(PKG_CONFIG) --libs freetype2` \ - `$(PKG_CONFIG) --libs harfbuzz` + `$(PKG_CONFIG) --libs freetype2` # flags STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 @@ -31,8 +29,7 @@ STLDFLAGS = $(LIBS) $(LDFLAGS) #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ # `$(PKG_CONFIG) --libs fontconfig` \ -# `$(PKG_CONFIG) --libs freetype2` \ -# `$(PKG_CONFIG) --libs harfbuzz` +# `$(PKG_CONFIG) --libs freetype2` #MANPREFIX = ${PREFIX}/man # compiler and linker diff --git a/st/hb.c b/st/hb.c deleted file mode 100644 index 99412c8..0000000 --- a/st/hb.c +++ /dev/null @@ -1,125 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "st.h" -#include "hb.h" - -#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END } -#define BUFFER_STEP 256 - -hb_font_t *hbfindfont(XftFont *match); - -typedef struct { - XftFont *match; - hb_font_t *font; -} HbFontMatch; - -typedef struct { - size_t capacity; - HbFontMatch *fonts; -} HbFontCache; - -static HbFontCache hbfontcache = { 0, NULL }; - -typedef struct { - size_t capacity; - Rune *runes; -} RuneBuffer; - -static RuneBuffer hbrunebuffer = { 0, NULL }; - -/* - * Poplulate the array with a list of font features, wrapped in FEATURE macro, - * e. g. - * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') - */ -hb_feature_t features[] = { }; - -void -hbunloadfonts() -{ - for (int i = 0; i < hbfontcache.capacity; i++) { - hb_font_destroy(hbfontcache.fonts[i].font); - XftUnlockFace(hbfontcache.fonts[i].match); - } - - if (hbfontcache.fonts != NULL) { - free(hbfontcache.fonts); - hbfontcache.fonts = NULL; - } - hbfontcache.capacity = 0; -} - -hb_font_t * -hbfindfont(XftFont *match) -{ - for (int i = 0; i < hbfontcache.capacity; i++) { - if (hbfontcache.fonts[i].match == match) - return hbfontcache.fonts[i].font; - } - - /* Font not found in cache, caching it now. */ - hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1)); - FT_Face face = XftLockFace(match); - hb_font_t *font = hb_ft_font_create(face, NULL); - if (font == NULL) - die("Failed to load Harfbuzz font."); - - hbfontcache.fonts[hbfontcache.capacity].match = match; - hbfontcache.fonts[hbfontcache.capacity].font = font; - hbfontcache.capacity += 1; - - return font; -} - -void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { - ushort mode = USHRT_MAX; - unsigned int glyph_count; - int rune_idx, glyph_idx, end = start + length; - - hb_font_t *font = hbfindfont(xfont); - if (font == NULL) - return; - - hb_buffer_t *buffer = hb_buffer_create(); - hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); - hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); - - /* Resize the buffer if required length is larger. */ - if (hbrunebuffer.capacity < length) { - hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP; - hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune)); - } - - /* Fill buffer with codepoints. */ - for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) { - hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u; - mode = glyphs[glyph_idx].mode; - if (mode & ATTR_WDUMMY) - hbrunebuffer.runes[rune_idx] = 0x0020; - } - hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length); - - /* Shape the segment. */ - hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); - - /* Get new glyph info. */ - hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); - hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); - - /* Fill the output. */ - data->buffer = buffer; - data->glyphs = info; - data->positions = pos; - data->count = glyph_count; -} - -void hbcleanup(HbTransformData *data) { - hb_buffer_destroy(data->buffer); - memset(data, 0, sizeof(HbTransformData)); -} diff --git a/st/hb.h b/st/hb.h deleted file mode 100644 index 3b0ef44..0000000 --- a/st/hb.h +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include -#include - -typedef struct { - hb_buffer_t *buffer; - hb_glyph_info_t *glyphs; - hb_glyph_position_t *positions; - unsigned int count; -} HbTransformData; - -void hbunloadfonts(); -void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); -void hbcleanup(HbTransformData *); diff --git a/st/patches/st-alpha-20240814-a0274bc.diff b/st/patches/st-alpha-20240814-a0274bc.diff deleted file mode 100644 index 6913d19..0000000 --- a/st/patches/st-alpha-20240814-a0274bc.diff +++ /dev/null @@ -1,129 +0,0 @@ -diff --git a/config.def.h b/config.def.h -index 2cd740a..019a4e1 100644 ---- a/config.def.h -+++ b/config.def.h -@@ -93,6 +93,9 @@ char *termname = "st-256color"; - */ - unsigned int tabspaces = 8; - -+/* bg opacity */ -+float alpha = 0.8; -+ - /* Terminal colors (16 first used in escape sequence) */ - static const char *colorname[] = { - /* 8 normal colors */ -diff --git a/x.c b/x.c -index d73152b..f32fd6c 100644 ---- a/x.c -+++ b/x.c -@@ -105,6 +105,7 @@ typedef struct { - XSetWindowAttributes attrs; - int scr; - int isfixed; /* is fixed geometry? */ -+ int depth; /* bit depth */ - int l, t; /* left and top offset */ - int gm; /* geometry mask */ - } XWindow; -@@ -752,7 +753,7 @@ xresize(int col, int row) - - XFreePixmap(xw.dpy, xw.buf); - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, -- DefaultDepth(xw.dpy, xw.scr)); -+ xw.depth); - XftDrawChange(xw.draw, xw.buf); - xclear(0, 0, win.w, win.h); - -@@ -812,6 +813,10 @@ xloadcols(void) - else - die("could not allocate color %d\n", i); - } -+ -+ dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); -+ dc.col[defaultbg].pixel &= 0x00FFFFFF; -+ dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; - loaded = 1; - } - -@@ -842,6 +847,12 @@ xsetcolorname(int x, const char *name) - XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); - dc.col[x] = ncolor; - -+ if (x == defaultbg) { -+ dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); -+ dc.col[defaultbg].pixel &= 0x00FFFFFF; -+ dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; -+ } -+ - return 0; - } - -@@ -1134,11 +1145,25 @@ xinit(int cols, int rows) - Window parent, root; - pid_t thispid = getpid(); - XColor xmousefg, xmousebg; -+ XWindowAttributes attr; -+ XVisualInfo vis; - - if (!(xw.dpy = XOpenDisplay(NULL))) - die("can't open display\n"); - xw.scr = XDefaultScreen(xw.dpy); -- xw.vis = XDefaultVisual(xw.dpy, xw.scr); -+ -+ root = XRootWindow(xw.dpy, xw.scr); -+ if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) -+ parent = root; -+ -+ if (XMatchVisualInfo(xw.dpy, xw.scr, 32, TrueColor, &vis) != 0) { -+ xw.vis = vis.visual; -+ xw.depth = vis.depth; -+ } else { -+ XGetWindowAttributes(xw.dpy, parent, &attr); -+ xw.vis = attr.visual; -+ xw.depth = attr.depth; -+ } - - /* font */ - if (!FcInit()) -@@ -1148,7 +1173,7 @@ xinit(int cols, int rows) - xloadfonts(usedfont, 0); - - /* colors */ -- xw.cmap = XDefaultColormap(xw.dpy, xw.scr); -+ xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); - xloadcols(); - - /* adjust fixed window geometry */ -@@ -1168,11 +1193,8 @@ xinit(int cols, int rows) - | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; - xw.attrs.colormap = xw.cmap; - -- root = XRootWindow(xw.dpy, xw.scr); -- if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) -- parent = root; -- xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, -- win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, -+ xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, -+ win.w, win.h, 0, xw.depth, InputOutput, - xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity - | CWEventMask | CWColormap, &xw.attrs); - if (parent != root) -@@ -1183,7 +1205,7 @@ xinit(int cols, int rows) - dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, - &gcvalues); - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, -- DefaultDepth(xw.dpy, xw.scr)); -+ xw.depth); - XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); - XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); - -@@ -2047,6 +2069,10 @@ main(int argc, char *argv[]) - case 'a': - allowaltscreen = 0; - break; -+ case 'A': -+ alpha = strtof(EARGF(usage()), NULL); -+ LIMIT(alpha, 0.0, 1.0); -+ break; - case 'c': - opt_class = EARGF(usage()); - break; diff --git a/st/patches/st-anysize-20220718-baa9357.diff b/st/patches/st-anysize-20220718-baa9357.diff deleted file mode 100644 index 675ffdf..0000000 --- a/st/patches/st-anysize-20220718-baa9357.diff +++ /dev/null @@ -1,164 +0,0 @@ -From 8dcdc4b21a73268e167d98aa30f24315c7f3b7ff Mon Sep 17 00:00:00 2001 -From: Bakkeby -Date: Mon, 18 Jul 2022 16:52:03 +0200 -Subject: [PATCH] Adding anysize patch - ---- - x.c | 56 ++++++++++++++++++++++++++++++-------------------------- - 1 file changed, 30 insertions(+), 26 deletions(-) - -diff --git a/x.c b/x.c -index 2a3bd38..f534347 100644 ---- a/x.c -+++ b/x.c -@@ -81,6 +81,7 @@ typedef XftGlyphFontSpec GlyphFontSpec; - typedef struct { - int tw, th; /* tty width and height */ - int w, h; /* window width and height */ -+ int hborderpx, vborderpx; - int ch; /* char height */ - int cw; /* char width */ - int mode; /* window state/mode flags */ -@@ -331,7 +332,7 @@ ttysend(const Arg *arg) - int - evcol(XEvent *e) - { -- int x = e->xbutton.x - borderpx; -+ int x = e->xbutton.x - win.hborderpx; - LIMIT(x, 0, win.tw - 1); - return x / win.cw; - } -@@ -339,7 +340,7 @@ evcol(XEvent *e) - int - evrow(XEvent *e) - { -- int y = e->xbutton.y - borderpx; -+ int y = e->xbutton.y - win.vborderpx; - LIMIT(y, 0, win.th - 1); - return y / win.ch; - } -@@ -739,6 +740,9 @@ cresize(int width, int height) - col = MAX(1, col); - row = MAX(1, row); - -+ win.hborderpx = (win.w - col * win.cw) / 2; -+ win.vborderpx = (win.h - row * win.ch) / 2; -+ - tresize(col, row); - xresize(col, row); - ttyresize(win.tw, win.th); -@@ -869,8 +873,8 @@ xhints(void) - sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; - sizeh->height = win.h; - sizeh->width = win.w; -- sizeh->height_inc = win.ch; -- sizeh->width_inc = win.cw; -+ sizeh->height_inc = 1; -+ sizeh->width_inc = 1; - sizeh->base_height = 2 * borderpx; - sizeh->base_width = 2 * borderpx; - sizeh->min_height = win.ch + 2 * borderpx; -@@ -1152,8 +1156,8 @@ xinit(int cols, int rows) - xloadcols(); - - /* adjust fixed window geometry */ -- win.w = 2 * borderpx + cols * win.cw; -- win.h = 2 * borderpx + rows * win.ch; -+ win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw; -+ win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch; - if (xw.gm & XNegative) - xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; - if (xw.gm & YNegative) -@@ -1242,7 +1246,7 @@ xinit(int cols, int rows) - int - xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) - { -- float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; -+ float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp; - ushort mode, prevmode = USHRT_MAX; - Font *font = &dc.font; - int frcflags = FRC_NORMAL; -@@ -1375,7 +1379,7 @@ void - xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) - { - int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); -- int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, -+ int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, - width = charlen * win.cw; - Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; - XRenderColor colfg, colbg; -@@ -1465,17 +1469,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i - - /* Intelligent cleaning up of the borders. */ - if (x == 0) { -- xclear(0, (y == 0)? 0 : winy, borderpx, -+ xclear(0, (y == 0)? 0 : winy, win.hborderpx, - winy + win.ch + -- ((winy + win.ch >= borderpx + win.th)? win.h : 0)); -+ ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0)); - } -- if (winx + width >= borderpx + win.tw) { -+ if (winx + width >= win.hborderpx + win.tw) { - xclear(winx + width, (y == 0)? 0 : winy, win.w, -- ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); -+ ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch))); - } - if (y == 0) -- xclear(winx, 0, winx + width, borderpx); -- if (winy + win.ch >= borderpx + win.th) -+ xclear(winx, 0, winx + width, win.vborderpx); -+ if (winy + win.ch >= win.vborderpx + win.th) - xclear(winx, winy + win.ch, winx + width, win.h); - - /* Clean up the region we want to draw to. */ -@@ -1569,35 +1573,35 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) - case 3: /* Blinking Underline */ - case 4: /* Steady Underline */ - XftDrawRect(xw.draw, &drawcol, -- borderpx + cx * win.cw, -- borderpx + (cy + 1) * win.ch - \ -+ win.hborderpx + cx * win.cw, -+ win.vborderpx + (cy + 1) * win.ch - \ - cursorthickness, - win.cw, cursorthickness); - break; - case 5: /* Blinking bar */ - case 6: /* Steady bar */ - XftDrawRect(xw.draw, &drawcol, -- borderpx + cx * win.cw, -- borderpx + cy * win.ch, -+ win.hborderpx + cx * win.cw, -+ win.vborderpx + cy * win.ch, - cursorthickness, win.ch); - break; - } - } else { - XftDrawRect(xw.draw, &drawcol, -- borderpx + cx * win.cw, -- borderpx + cy * win.ch, -+ win.hborderpx + cx * win.cw, -+ win.vborderpx + cy * win.ch, - win.cw - 1, 1); - XftDrawRect(xw.draw, &drawcol, -- borderpx + cx * win.cw, -- borderpx + cy * win.ch, -+ win.hborderpx + cx * win.cw, -+ win.vborderpx + cy * win.ch, - 1, win.ch - 1); - XftDrawRect(xw.draw, &drawcol, -- borderpx + (cx + 1) * win.cw - 1, -- borderpx + cy * win.ch, -+ win.hborderpx + (cx + 1) * win.cw - 1, -+ win.vborderpx + cy * win.ch, - 1, win.ch - 1); - XftDrawRect(xw.draw, &drawcol, -- borderpx + cx * win.cw, -- borderpx + (cy + 1) * win.ch - 1, -+ win.hborderpx + cx * win.cw, -+ win.vborderpx + (cy + 1) * win.ch - 1, - win.cw, 1); - } - } --- -2.37.1 - diff --git a/st/patches/st-ligatures-20251007-0.9.3.diff b/st/patches/st-ligatures-20251007-0.9.3.diff deleted file mode 100644 index a5942b5..0000000 --- a/st/patches/st-ligatures-20251007-0.9.3.diff +++ /dev/null @@ -1,619 +0,0 @@ -diff --git a/Makefile b/Makefile -index 15db421..dfcea0f 100644 ---- a/Makefile -+++ b/Makefile -@@ -4,7 +4,7 @@ - - include config.mk - --SRC = st.c x.c -+SRC = st.c x.c hb.c - OBJ = $(SRC:.c=.o) - - all: st -@@ -16,7 +16,8 @@ config.h: - $(CC) $(STCFLAGS) -c $< - - st.o: config.h st.h win.h --x.o: arg.h config.h st.h win.h -+x.o: arg.h config.h st.h win.h hb.h -+hb.o: st.h - - $(OBJ): config.h config.mk - -diff --git a/config.mk b/config.mk -index 2fc854e..ec69d1a 100644 ---- a/config.mk -+++ b/config.mk -@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config - # includes and libs - INCS = -I$(X11INC) \ - `$(PKG_CONFIG) --cflags fontconfig` \ -- `$(PKG_CONFIG) --cflags freetype2` -+ `$(PKG_CONFIG) --cflags freetype2` \ -+ `$(PKG_CONFIG) --cflags harfbuzz` - LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ - `$(PKG_CONFIG) --libs fontconfig` \ -- `$(PKG_CONFIG) --libs freetype2` -+ `$(PKG_CONFIG) --libs freetype2` \ -+ `$(PKG_CONFIG) --libs harfbuzz` - - # flags - STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -@@ -29,7 +31,8 @@ STLDFLAGS = $(LIBS) $(LDFLAGS) - #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE - #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ - # `$(PKG_CONFIG) --libs fontconfig` \ --# `$(PKG_CONFIG) --libs freetype2` -+# `$(PKG_CONFIG) --libs freetype2` \ -+# `$(PKG_CONFIG) --libs harfbuzz` - #MANPREFIX = ${PREFIX}/man - - # compiler and linker -diff --git a/hb.c b/hb.c -new file mode 100644 -index 0000000..99412c8 ---- /dev/null -+++ b/hb.c -@@ -0,0 +1,125 @@ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "st.h" -+#include "hb.h" -+ -+#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END } -+#define BUFFER_STEP 256 -+ -+hb_font_t *hbfindfont(XftFont *match); -+ -+typedef struct { -+ XftFont *match; -+ hb_font_t *font; -+} HbFontMatch; -+ -+typedef struct { -+ size_t capacity; -+ HbFontMatch *fonts; -+} HbFontCache; -+ -+static HbFontCache hbfontcache = { 0, NULL }; -+ -+typedef struct { -+ size_t capacity; -+ Rune *runes; -+} RuneBuffer; -+ -+static RuneBuffer hbrunebuffer = { 0, NULL }; -+ -+/* -+ * Poplulate the array with a list of font features, wrapped in FEATURE macro, -+ * e. g. -+ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') -+ */ -+hb_feature_t features[] = { }; -+ -+void -+hbunloadfonts() -+{ -+ for (int i = 0; i < hbfontcache.capacity; i++) { -+ hb_font_destroy(hbfontcache.fonts[i].font); -+ XftUnlockFace(hbfontcache.fonts[i].match); -+ } -+ -+ if (hbfontcache.fonts != NULL) { -+ free(hbfontcache.fonts); -+ hbfontcache.fonts = NULL; -+ } -+ hbfontcache.capacity = 0; -+} -+ -+hb_font_t * -+hbfindfont(XftFont *match) -+{ -+ for (int i = 0; i < hbfontcache.capacity; i++) { -+ if (hbfontcache.fonts[i].match == match) -+ return hbfontcache.fonts[i].font; -+ } -+ -+ /* Font not found in cache, caching it now. */ -+ hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1)); -+ FT_Face face = XftLockFace(match); -+ hb_font_t *font = hb_ft_font_create(face, NULL); -+ if (font == NULL) -+ die("Failed to load Harfbuzz font."); -+ -+ hbfontcache.fonts[hbfontcache.capacity].match = match; -+ hbfontcache.fonts[hbfontcache.capacity].font = font; -+ hbfontcache.capacity += 1; -+ -+ return font; -+} -+ -+void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { -+ ushort mode = USHRT_MAX; -+ unsigned int glyph_count; -+ int rune_idx, glyph_idx, end = start + length; -+ -+ hb_font_t *font = hbfindfont(xfont); -+ if (font == NULL) -+ return; -+ -+ hb_buffer_t *buffer = hb_buffer_create(); -+ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); -+ hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); -+ -+ /* Resize the buffer if required length is larger. */ -+ if (hbrunebuffer.capacity < length) { -+ hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP; -+ hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune)); -+ } -+ -+ /* Fill buffer with codepoints. */ -+ for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) { -+ hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u; -+ mode = glyphs[glyph_idx].mode; -+ if (mode & ATTR_WDUMMY) -+ hbrunebuffer.runes[rune_idx] = 0x0020; -+ } -+ hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length); -+ -+ /* Shape the segment. */ -+ hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); -+ -+ /* Get new glyph info. */ -+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); -+ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); -+ -+ /* Fill the output. */ -+ data->buffer = buffer; -+ data->glyphs = info; -+ data->positions = pos; -+ data->count = glyph_count; -+} -+ -+void hbcleanup(HbTransformData *data) { -+ hb_buffer_destroy(data->buffer); -+ memset(data, 0, sizeof(HbTransformData)); -+} -diff --git a/hb.h b/hb.h -new file mode 100644 -index 0000000..3b0ef44 ---- /dev/null -+++ b/hb.h -@@ -0,0 +1,14 @@ -+#include -+#include -+#include -+ -+typedef struct { -+ hb_buffer_t *buffer; -+ hb_glyph_info_t *glyphs; -+ hb_glyph_position_t *positions; -+ unsigned int count; -+} HbTransformData; -+ -+void hbunloadfonts(); -+void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); -+void hbcleanup(HbTransformData *); -diff --git a/st.c b/st.c -index 8e57991..9641c85 100644 ---- a/st.c -+++ b/st.c -@@ -2685,7 +2685,8 @@ draw(void) - - drawregion(0, 0, term.col, term.row); - xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], -- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); -+ term.ocx, term.ocy, term.line[term.ocy][term.ocx], -+ term.line[term.ocy], term.col); - term.ocx = cx; - term.ocy = term.c.y; - xfinishdraw(); -diff --git a/st.h b/st.h -index fd3b0d8..142fdfe 100644 ---- a/st.h -+++ b/st.h -@@ -11,7 +11,8 @@ - #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) - #define DEFAULT(a, b) (a) = (a) ? (a) : (b) - #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) --#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ -+#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ -+ (a).fg != (b).fg || \ - (a).bg != (b).bg) - #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ - (t1.tv_nsec-t2.tv_nsec)/1E6) -diff --git a/win.h b/win.h -index 6de960d..94679e4 100644 ---- a/win.h -+++ b/win.h -@@ -25,7 +25,7 @@ enum win_mode { - - void xbell(void); - void xclipcopy(void); --void xdrawcursor(int, int, Glyph, int, int, Glyph); -+void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); - void xdrawline(Line, int, int, int); - void xfinishdraw(void); - void xloadcols(void); -diff --git a/x.c b/x.c -index d73152b..4d05b9f 100644 ---- a/x.c -+++ b/x.c -@@ -19,6 +19,7 @@ char *argv0; - #include "arg.h" - #include "st.h" - #include "win.h" -+#include "hb.h" - - /* types used in config.h */ - typedef struct { -@@ -141,8 +142,9 @@ typedef struct { - } DC; - - static inline ushort sixd_to_16bit(int); -+static void xresetfontsettings(ushort mode, Font **font, int *frcflags); - static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); --static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); -+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); - static void xdrawglyph(Glyph, int, int); - static void xclear(int, int, int, int); - static int xgeommasktogravity(int); -@@ -757,7 +759,7 @@ xresize(int col, int row) - xclear(0, 0, win.w, win.h); - - /* resize to new width */ -- xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); -+ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); - } - - ushort -@@ -1062,6 +1064,9 @@ xunloadfont(Font *f) - void - xunloadfonts(void) - { -+ /* Clear Harfbuzz font cache. */ -+ hbunloadfonts(); -+ - /* Free the loaded fonts in the font cache. */ - while (frclen > 0) - XftFontClose(xw.dpy, frc[--frclen].font); -@@ -1188,7 +1193,7 @@ xinit(int cols, int rows) - XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); - - /* font spec buffer */ -- xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); -+ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); - - /* Xft rendering context */ - xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); -@@ -1242,142 +1247,153 @@ xinit(int cols, int rows) - xsel.xtarget = XA_STRING; - } - -+void -+xresetfontsettings(ushort mode, Font **font, int *frcflags) -+{ -+ *font = &dc.font; -+ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { -+ *font = &dc.ibfont; -+ *frcflags = FRC_ITALICBOLD; -+ } else if (mode & ATTR_ITALIC) { -+ *font = &dc.ifont; -+ *frcflags = FRC_ITALIC; -+ } else if (mode & ATTR_BOLD) { -+ *font = &dc.bfont; -+ *frcflags = FRC_BOLD; -+ } -+} -+ - int - xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) - { - float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; -- ushort mode, prevmode = USHRT_MAX; -+ ushort mode = glyphs[0].mode & ~ATTR_WRAP; - Font *font = &dc.font; - int frcflags = FRC_NORMAL; -- float runewidth = win.cw; -+ float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f); - Rune rune; - FT_UInt glyphidx; - FcResult fcres; - FcPattern *fcpattern, *fontpattern; - FcFontSet *fcsets[] = { NULL }; - FcCharSet *fccharset; -- int i, f, numspecs = 0; -+ int f, code_idx, numspecs = 0; -+ float cluster_xp = xp, cluster_yp = yp; -+ HbTransformData shaped = { 0 }; - -- for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { -- /* Fetch rune and mode for current glyph. */ -- rune = glyphs[i].u; -- mode = glyphs[i].mode; -+ /* Initial values. */ -+ xresetfontsettings(mode, &font, &frcflags); - -- /* Skip dummy wide-character spacing. */ -- if (mode == ATTR_WDUMMY) -+ /* Shape the segment. */ -+ hbtransform(&shaped, font->match, glyphs, 0, len); -+ xp = winx; yp = winy + font->ascent; -+ cluster_xp = xp; cluster_yp = yp; -+ -+ for (code_idx = 0; code_idx < shaped.count; code_idx++) { -+ int idx = shaped.glyphs[code_idx].cluster; -+ -+ if (glyphs[idx].mode & ATTR_WDUMMY) - continue; - -- /* Determine font for glyph if different from previous glyph. */ -- if (prevmode != mode) { -- prevmode = mode; -- font = &dc.font; -- frcflags = FRC_NORMAL; -- runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); -- if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { -- font = &dc.ibfont; -- frcflags = FRC_ITALICBOLD; -- } else if (mode & ATTR_ITALIC) { -- font = &dc.ifont; -- frcflags = FRC_ITALIC; -- } else if (mode & ATTR_BOLD) { -- font = &dc.bfont; -- frcflags = FRC_BOLD; -- } -- yp = winy + font->ascent; -+ /* Advance the drawing cursor if we've moved to a new cluster */ -+ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) { -+ xp += runewidth * (idx - shaped.glyphs[code_idx - 1].cluster); -+ cluster_xp = xp; -+ cluster_yp = yp; - } - -- /* Lookup character index with default font. */ -- glyphidx = XftCharIndex(xw.dpy, font->match, rune); -- if (glyphidx) { -+ if (shaped.glyphs[code_idx].codepoint != 0) { -+ /* If symbol is found, put it into the specs. */ - specs[numspecs].font = font->match; -- specs[numspecs].glyph = glyphidx; -- specs[numspecs].x = (short)xp; -- specs[numspecs].y = (short)yp; -- xp += runewidth; -+ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; -+ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.); -+ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.); -+ cluster_xp += shaped.positions[code_idx].x_advance / 64.; -+ cluster_yp += shaped.positions[code_idx].y_advance / 64.; - numspecs++; -- continue; -- } -- -- /* Fallback on font cache, search the font cache for match. */ -- for (f = 0; f < frclen; f++) { -- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); -- /* Everything correct. */ -- if (glyphidx && frc[f].flags == frcflags) -- break; -- /* We got a default font for a not found glyph. */ -- if (!glyphidx && frc[f].flags == frcflags -- && frc[f].unicodep == rune) { -- break; -+ } else { -+ /* If it's not found, try to fetch it through the font cache. */ -+ rune = glyphs[idx].u; -+ for (f = 0; f < frclen; f++) { -+ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); -+ /* Everything correct. */ -+ if (glyphidx && frc[f].flags == frcflags) -+ break; -+ /* We got a default font for a not found glyph. */ -+ if (!glyphidx && frc[f].flags == frcflags -+ && frc[f].unicodep == rune) { -+ break; -+ } - } -- } -- -- /* Nothing was found. Use fontconfig to find matching font. */ -- if (f >= frclen) { -- if (!font->set) -- font->set = FcFontSort(0, font->pattern, -- 1, 0, &fcres); -- fcsets[0] = font->set; - -- /* -- * Nothing was found in the cache. Now use -- * some dozen of Fontconfig calls to get the -- * font for one single character. -- * -- * Xft and fontconfig are design failures. -- */ -- fcpattern = FcPatternDuplicate(font->pattern); -- fccharset = FcCharSetCreate(); -- -- FcCharSetAddChar(fccharset, rune); -- FcPatternAddCharSet(fcpattern, FC_CHARSET, -- fccharset); -- FcPatternAddBool(fcpattern, FC_SCALABLE, 1); -- -- FcConfigSubstitute(0, fcpattern, -- FcMatchPattern); -- FcDefaultSubstitute(fcpattern); -- -- fontpattern = FcFontSetMatch(0, fcsets, 1, -- fcpattern, &fcres); -- -- /* Allocate memory for the new cache entry. */ -- if (frclen >= frccap) { -- frccap += 16; -- frc = xrealloc(frc, frccap * sizeof(Fontcache)); -+ /* Nothing was found. Use fontconfig to find matching font. */ -+ if (f >= frclen) { -+ if (!font->set) -+ font->set = FcFontSort(0, font->pattern, -+ 1, 0, &fcres); -+ fcsets[0] = font->set; -+ -+ /* -+ * Nothing was found in the cache. Now use -+ * some dozen of Fontconfig calls to get the -+ * font for one single character. -+ * -+ * Xft and fontconfig are design failures. -+ */ -+ fcpattern = FcPatternDuplicate(font->pattern); -+ fccharset = FcCharSetCreate(); -+ -+ FcCharSetAddChar(fccharset, rune); -+ FcPatternAddCharSet(fcpattern, FC_CHARSET, -+ fccharset); -+ FcPatternAddBool(fcpattern, FC_SCALABLE, 1); -+ -+ FcConfigSubstitute(0, fcpattern, -+ FcMatchPattern); -+ FcDefaultSubstitute(fcpattern); -+ -+ fontpattern = FcFontSetMatch(0, fcsets, 1, -+ fcpattern, &fcres); -+ -+ /* Allocate memory for the new cache entry. */ -+ if (frclen >= frccap) { -+ frccap += 16; -+ frc = xrealloc(frc, frccap * sizeof(Fontcache)); -+ } -+ -+ frc[frclen].font = XftFontOpenPattern(xw.dpy, -+ fontpattern); -+ if (!frc[frclen].font) -+ die("XftFontOpenPattern failed seeking fallback font: %s\n", -+ strerror(errno)); -+ frc[frclen].flags = frcflags; -+ frc[frclen].unicodep = rune; -+ -+ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); -+ -+ f = frclen; -+ frclen++; -+ -+ FcPatternDestroy(fcpattern); -+ FcCharSetDestroy(fccharset); - } - -- frc[frclen].font = XftFontOpenPattern(xw.dpy, -- fontpattern); -- if (!frc[frclen].font) -- die("XftFontOpenPattern failed seeking fallback font: %s\n", -- strerror(errno)); -- frc[frclen].flags = frcflags; -- frc[frclen].unicodep = rune; -- -- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); -- -- f = frclen; -- frclen++; -- -- FcPatternDestroy(fcpattern); -- FcCharSetDestroy(fccharset); -+ specs[numspecs].font = frc[f].font; -+ specs[numspecs].glyph = glyphidx; -+ specs[numspecs].x = (short)xp; -+ specs[numspecs].y = (short)yp; -+ numspecs++; - } -- -- specs[numspecs].font = frc[f].font; -- specs[numspecs].glyph = glyphidx; -- specs[numspecs].x = (short)xp; -- specs[numspecs].y = (short)yp; -- xp += runewidth; -- numspecs++; - } - -+ /* Cleanup and get ready for next segment. */ -+ hbcleanup(&shaped); - return numspecs; - } - - void --xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) -+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) - { -- int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); - int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, - width = charlen * win.cw; - Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; -@@ -1513,21 +1529,24 @@ void - xdrawglyph(Glyph g, int x, int y) - { - int numspecs; -- XftGlyphFontSpec spec; -+ XftGlyphFontSpec *specs = xw.specbuf; - -- numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); -- xdrawglyphfontspecs(&spec, g, numspecs, x, y); -+ numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); -+ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); - } - - void --xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) -+xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) - { - Color drawcol; - - /* remove the old cursor */ - if (selected(ox, oy)) - og.mode ^= ATTR_REVERSE; -- xdrawglyph(og, ox, oy); -+ -+ /* Redraw the line where cursor was previously. -+ * It will restore the ligatures broken by the cursor. */ -+ xdrawline(line, 0, oy, len); - - if (IS_SET(MODE_HIDE)) - return; -@@ -1661,18 +1680,16 @@ xdrawline(Line line, int x1, int y1, int x2) - Glyph base, new; - XftGlyphFontSpec *specs = xw.specbuf; - -- numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); - i = ox = 0; -- for (x = x1; x < x2 && i < numspecs; x++) { -+ for (x = x1; x < x2; x++) { - new = line[x]; - if (new.mode == ATTR_WDUMMY) - continue; - if (selected(x, y1)) - new.mode ^= ATTR_REVERSE; -- if (i > 0 && ATTRCMP(base, new)) { -- xdrawglyphfontspecs(specs, base, i, ox, y1); -- specs += i; -- numspecs -= i; -+ if ((i > 0) && ATTRCMP(base, new)) { -+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1); -+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox); - i = 0; - } - if (i == 0) { -@@ -1681,8 +1698,10 @@ xdrawline(Line line, int x1, int y1, int x2) - } - i++; - } -- if (i > 0) -- xdrawglyphfontspecs(specs, base, i, ox, y1); -+ if (i > 0) { -+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); -+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); -+ } - } - - void diff --git a/st/st.c b/st/st.c index e16541a..6f40e35 100644 --- a/st/st.c +++ b/st/st.c @@ -2689,8 +2689,7 @@ draw(void) drawregion(0, 0, term.col, term.row); xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], - term.ocx, term.ocy, term.line[term.ocy][term.ocx], - term.line[term.ocy], term.col); + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); term.ocx = cx; term.ocy = term.c.y; xfinishdraw(); diff --git a/st/st.h b/st/st.h index 142fdfe..fd3b0d8 100644 --- a/st/st.h +++ b/st/st.h @@ -11,8 +11,7 @@ #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) #define DEFAULT(a, b) (a) = (a) ? (a) : (b) #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) -#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ - (a).fg != (b).fg || \ +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ (a).bg != (b).bg) #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ (t1.tv_nsec-t2.tv_nsec)/1E6) diff --git a/st/win.h b/st/win.h index 94679e4..6de960d 100644 --- a/st/win.h +++ b/st/win.h @@ -25,7 +25,7 @@ enum win_mode { void xbell(void); void xclipcopy(void); -void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); +void xdrawcursor(int, int, Glyph, int, int, Glyph); void xdrawline(Line, int, int, int); void xfinishdraw(void); void xloadcols(void); diff --git a/st/x.c b/st/x.c index 9292287..d73152b 100644 --- a/st/x.c +++ b/st/x.c @@ -19,7 +19,6 @@ char *argv0; #include "arg.h" #include "st.h" #include "win.h" -#include "hb.h" /* types used in config.h */ typedef struct { @@ -82,7 +81,6 @@ typedef XftGlyphFontSpec GlyphFontSpec; typedef struct { int tw, th; /* tty width and height */ int w, h; /* window width and height */ - int hborderpx, vborderpx; int ch; /* char height */ int cw; /* char width */ int mode; /* window state/mode flags */ @@ -107,7 +105,6 @@ typedef struct { XSetWindowAttributes attrs; int scr; int isfixed; /* is fixed geometry? */ - int depth; /* bit depth */ int l, t; /* left and top offset */ int gm; /* geometry mask */ } XWindow; @@ -144,9 +141,8 @@ typedef struct { } DC; static inline ushort sixd_to_16bit(int); -static void xresetfontsettings(ushort mode, Font **font, int *frcflags); static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); -static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); static void xdrawglyph(Glyph, int, int); static void xclear(int, int, int, int); static int xgeommasktogravity(int); @@ -335,7 +331,7 @@ ttysend(const Arg *arg) int evcol(XEvent *e) { - int x = e->xbutton.x - win.hborderpx; + int x = e->xbutton.x - borderpx; LIMIT(x, 0, win.tw - 1); return x / win.cw; } @@ -343,7 +339,7 @@ evcol(XEvent *e) int evrow(XEvent *e) { - int y = e->xbutton.y - win.vborderpx; + int y = e->xbutton.y - borderpx; LIMIT(y, 0, win.th - 1); return y / win.ch; } @@ -743,9 +739,6 @@ cresize(int width, int height) col = MAX(1, col); row = MAX(1, row); - win.hborderpx = (win.w - col * win.cw) / 2; - win.vborderpx = (win.h - row * win.ch) / 2; - tresize(col, row); xresize(col, row); ttyresize(win.tw, win.th); @@ -759,12 +752,12 @@ xresize(int col, int row) XFreePixmap(xw.dpy, xw.buf); xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - xw.depth); + DefaultDepth(xw.dpy, xw.scr)); XftDrawChange(xw.draw, xw.buf); xclear(0, 0, win.w, win.h); /* resize to new width */ - xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); } ushort @@ -819,10 +812,6 @@ xloadcols(void) else die("could not allocate color %d\n", i); } - - dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); - dc.col[defaultbg].pixel &= 0x00FFFFFF; - dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; loaded = 1; } @@ -853,12 +842,6 @@ xsetcolorname(int x, const char *name) XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); dc.col[x] = ncolor; - if (x == defaultbg) { - dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); - dc.col[defaultbg].pixel &= 0x00FFFFFF; - dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; - } - return 0; } @@ -886,8 +869,8 @@ xhints(void) sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; sizeh->height = win.h; sizeh->width = win.w; - sizeh->height_inc = 1; - sizeh->width_inc = 1; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; sizeh->base_height = 2 * borderpx; sizeh->base_width = 2 * borderpx; sizeh->min_height = win.ch + 2 * borderpx; @@ -1079,9 +1062,6 @@ xunloadfont(Font *f) void xunloadfonts(void) { - /* Clear Harfbuzz font cache. */ - hbunloadfonts(); - /* Free the loaded fonts in the font cache. */ while (frclen > 0) XftFontClose(xw.dpy, frc[--frclen].font); @@ -1154,25 +1134,11 @@ xinit(int cols, int rows) Window parent, root; pid_t thispid = getpid(); XColor xmousefg, xmousebg; - XWindowAttributes attr; - XVisualInfo vis; if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display\n"); xw.scr = XDefaultScreen(xw.dpy); - - root = XRootWindow(xw.dpy, xw.scr); - if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) - parent = root; - - if (XMatchVisualInfo(xw.dpy, xw.scr, 32, TrueColor, &vis) != 0) { - xw.vis = vis.visual; - xw.depth = vis.depth; - } else { - XGetWindowAttributes(xw.dpy, parent, &attr); - xw.vis = attr.visual; - xw.depth = attr.depth; - } + xw.vis = XDefaultVisual(xw.dpy, xw.scr); /* font */ if (!FcInit()) @@ -1182,12 +1148,12 @@ xinit(int cols, int rows) xloadfonts(usedfont, 0); /* colors */ - xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); xloadcols(); /* adjust fixed window geometry */ - win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw; - win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch; + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; if (xw.gm & XNegative) xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; if (xw.gm & YNegative) @@ -1202,8 +1168,11 @@ xinit(int cols, int rows) | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; xw.attrs.colormap = xw.cmap; - xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, - win.w, win.h, 0, xw.depth, InputOutput, + root = XRootWindow(xw.dpy, xw.scr); + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = root; + xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask | CWColormap, &xw.attrs); if (parent != root) @@ -1214,12 +1183,12 @@ xinit(int cols, int rows) dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues); xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - xw.depth); + DefaultDepth(xw.dpy, xw.scr)); XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); /* font spec buffer */ - xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); /* Xft rendering context */ xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); @@ -1273,154 +1242,143 @@ xinit(int cols, int rows) xsel.xtarget = XA_STRING; } -void -xresetfontsettings(ushort mode, Font **font, int *frcflags) -{ - *font = &dc.font; - if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { - *font = &dc.ibfont; - *frcflags = FRC_ITALICBOLD; - } else if (mode & ATTR_ITALIC) { - *font = &dc.ifont; - *frcflags = FRC_ITALIC; - } else if (mode & ATTR_BOLD) { - *font = &dc.bfont; - *frcflags = FRC_BOLD; - } -} - int xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) { - float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp; - ushort mode = glyphs[0].mode & ~ATTR_WRAP; + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; Font *font = &dc.font; int frcflags = FRC_NORMAL; - float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f); + float runewidth = win.cw; Rune rune; FT_UInt glyphidx; FcResult fcres; FcPattern *fcpattern, *fontpattern; FcFontSet *fcsets[] = { NULL }; FcCharSet *fccharset; - int f, code_idx, numspecs = 0; - float cluster_xp = xp, cluster_yp = yp; - HbTransformData shaped = { 0 }; + int i, f, numspecs = 0; - /* Initial values. */ - xresetfontsettings(mode, &font, &frcflags); + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; - /* Shape the segment. */ - hbtransform(&shaped, font->match, glyphs, 0, len); - xp = winx; yp = winy + font->ascent; - cluster_xp = xp; cluster_yp = yp; - - for (code_idx = 0; code_idx < shaped.count; code_idx++) { - int idx = shaped.glyphs[code_idx].cluster; - - if (glyphs[idx].mode & ATTR_WDUMMY) + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) continue; - /* Advance the drawing cursor if we've moved to a new cluster */ - if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) { - xp += runewidth * (idx - shaped.glyphs[code_idx - 1].cluster); - cluster_xp = xp; - cluster_yp = yp; + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; } - if (shaped.glyphs[code_idx].codepoint != 0) { - /* If symbol is found, put it into the specs. */ + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { specs[numspecs].font = font->match; - specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; - specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.); - specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.); - cluster_xp += shaped.positions[code_idx].x_advance / 64.; - cluster_yp += shaped.positions[code_idx].y_advance / 64.; - numspecs++; - } else { - /* If it's not found, try to fetch it through the font cache. */ - rune = glyphs[idx].u; - for (f = 0; f < frclen; f++) { - glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); - /* Everything correct. */ - if (glyphidx && frc[f].flags == frcflags) - break; - /* We got a default font for a not found glyph. */ - if (!glyphidx && frc[f].flags == frcflags - && frc[f].unicodep == rune) { - break; - } - } - - /* Nothing was found. Use fontconfig to find matching font. */ - if (f >= frclen) { - if (!font->set) - font->set = FcFontSort(0, font->pattern, - 1, 0, &fcres); - fcsets[0] = font->set; - - /* - * Nothing was found in the cache. Now use - * some dozen of Fontconfig calls to get the - * font for one single character. - * - * Xft and fontconfig are design failures. - */ - fcpattern = FcPatternDuplicate(font->pattern); - fccharset = FcCharSetCreate(); - - FcCharSetAddChar(fccharset, rune); - FcPatternAddCharSet(fcpattern, FC_CHARSET, - fccharset); - FcPatternAddBool(fcpattern, FC_SCALABLE, 1); - - FcConfigSubstitute(0, fcpattern, - FcMatchPattern); - FcDefaultSubstitute(fcpattern); - - fontpattern = FcFontSetMatch(0, fcsets, 1, - fcpattern, &fcres); - - /* Allocate memory for the new cache entry. */ - if (frclen >= frccap) { - frccap += 16; - frc = xrealloc(frc, frccap * sizeof(Fontcache)); - } - - frc[frclen].font = XftFontOpenPattern(xw.dpy, - fontpattern); - if (!frc[frclen].font) - die("XftFontOpenPattern failed seeking fallback font: %s\n", - strerror(errno)); - frc[frclen].flags = frcflags; - frc[frclen].unicodep = rune; - - glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); - - f = frclen; - frclen++; - - FcPatternDestroy(fcpattern); - FcCharSetDestroy(fccharset); - } - - specs[numspecs].font = frc[f].font; specs[numspecs].glyph = glyphidx; specs[numspecs].x = (short)xp; specs[numspecs].y = (short)yp; + xp += runewidth; numspecs++; + continue; } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; } - /* Cleanup and get ready for next segment. */ - hbcleanup(&shaped); return numspecs; } void -xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) { - int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, width = charlen * win.cw; Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; XRenderColor colfg, colbg; @@ -1510,17 +1468,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i /* Intelligent cleaning up of the borders. */ if (x == 0) { - xclear(0, (y == 0)? 0 : winy, win.hborderpx, + xclear(0, (y == 0)? 0 : winy, borderpx, winy + win.ch + - ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0)); + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); } - if (winx + width >= win.hborderpx + win.tw) { + if (winx + width >= borderpx + win.tw) { xclear(winx + width, (y == 0)? 0 : winy, win.w, - ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch))); + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); } if (y == 0) - xclear(winx, 0, winx + width, win.vborderpx); - if (winy + win.ch >= win.vborderpx + win.th) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) xclear(winx, winy + win.ch, winx + width, win.h); /* Clean up the region we want to draw to. */ @@ -1555,24 +1513,21 @@ void xdrawglyph(Glyph g, int x, int y) { int numspecs; - XftGlyphFontSpec *specs = xw.specbuf; + XftGlyphFontSpec spec; - numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); - xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); } void -xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) { Color drawcol; /* remove the old cursor */ if (selected(ox, oy)) og.mode ^= ATTR_REVERSE; - - /* Redraw the line where cursor was previously. - * It will restore the ligatures broken by the cursor. */ - xdrawline(line, 0, oy, len); + xdrawglyph(og, ox, oy); if (IS_SET(MODE_HIDE)) return; @@ -1617,35 +1572,35 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int le case 3: /* Blinking Underline */ case 4: /* Steady Underline */ XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + (cy + 1) * win.ch - \ + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ cursorthickness, win.cw, cursorthickness); break; case 5: /* Blinking bar */ case 6: /* Steady bar */ XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + cy * win.ch, + borderpx + cx * win.cw, + borderpx + cy * win.ch, cursorthickness, win.ch); break; } } else { XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + cy * win.ch, + borderpx + cx * win.cw, + borderpx + cy * win.ch, win.cw - 1, 1); XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + cy * win.ch, + borderpx + cx * win.cw, + borderpx + cy * win.ch, 1, win.ch - 1); XftDrawRect(xw.draw, &drawcol, - win.hborderpx + (cx + 1) * win.cw - 1, - win.vborderpx + cy * win.ch, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, 1, win.ch - 1); XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + (cy + 1) * win.ch - 1, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, win.cw, 1); } } @@ -1706,16 +1661,18 @@ xdrawline(Line line, int x1, int y1, int x2) Glyph base, new; XftGlyphFontSpec *specs = xw.specbuf; + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); i = ox = 0; - for (x = x1; x < x2; x++) { + for (x = x1; x < x2 && i < numspecs; x++) { new = line[x]; if (new.mode == ATTR_WDUMMY) continue; if (selected(x, y1)) new.mode ^= ATTR_REVERSE; - if ((i > 0) && ATTRCMP(base, new)) { - numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1); - xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox); + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; i = 0; } if (i == 0) { @@ -1724,10 +1681,8 @@ xdrawline(Line line, int x1, int y1, int x2) } i++; } - if (i > 0) { - numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); - xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); - } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); } void @@ -1746,8 +1701,8 @@ xximspot(int x, int y) if (xw.ime.xic == NULL) return; - xw.ime.spot.x = win.hborderpx + x * win.cw; - xw.ime.spot.y = win.vborderpx + (y + 1) * win.ch; + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); } @@ -2092,10 +2047,6 @@ main(int argc, char *argv[]) case 'a': allowaltscreen = 0; break; - case 'A': - alpha = strtof(EARGF(usage()), NULL); - LIMIT(alpha, 0.0, 1.0); - break; case 'c': opt_class = EARGF(usage()); break;