From f87d356ff414947e9dd3b75add5ba93459d3d733 Mon Sep 17 00:00:00 2001 From: Raheman Vaiya Date: Sun, 20 Mar 2022 02:38:07 -0400 Subject: [PATCH] v2.3.0-rc This is a major release which breaks backward compatibility for non-trivial configs. (we are still in beta after all :P). In the absence of too much blowback this will probably become the final v2 design. Much of this harkens back to the v1, with some additional simplifications and enhancements. See DESIGN.md for a more detailed account. --- CHANGELOG.md | 15 + DESIGN.md | 116 ++++ Makefile | 37 +- README.md | 13 +- TODO | 15 +- examples/capslock-esc-basic.conf | 2 +- keyd-application-mapper.1.gz | Bin 0 -> 1489 bytes keyd-application-mapper.md | 90 +++ keyd.1.gz | Bin 6538 -> 4868 bytes keyd.md | 505 +++++++++++++++++ man.md | 569 ------------------- src/client.c | 56 -- src/config.c | 320 ++--------- src/config.h | 20 +- src/descriptor.c | 614 +++++++++++++++----- src/descriptor.h | 64 +-- src/device.c | 258 +++++++++ src/device.h | 52 ++ src/error.c | 40 -- src/error.h | 13 - src/evdev.c | 172 ------ src/ini.c | 23 +- src/ini.h | 5 + src/ipc.c | 134 +++++ src/ipc.h | 30 + src/keyboard.c | 793 ++++++++++++-------------- src/keyboard.h | 50 +- src/keyd.c | 942 ++++++++++--------------------- src/keyd.h | 50 +- src/keys.c | 284 +++++++++- src/keys.h | 290 +--------- src/layer.h | 71 ++- src/server.c | 111 ---- src/server.h | 18 - src/vkbd.h | 5 + src/vkbd/stdout.c | 5 + src/vkbd/uinput.c | 15 +- src/vkbd/usb-gadget.c | 6 + src/vkbd/usb-gadget.h | 6 + t/layer.t | 2 - t/layer4.t | 8 +- t/layout.t | 38 +- t/layout2.t | 18 +- t/oneshot.t | 12 +- t/oneshot10.t | 2 - t/oneshot12.t | 23 + t/oneshot14.t | 15 + t/oneshot2.t | 8 +- t/oneshot3.t | 6 +- t/oneshot4.t | 12 +- t/oneshot5.t | 12 +- t/oneshot6.t | 12 +- t/oneshot9.t | 12 +- t/oneshotn.t | 8 +- t/oneshotn3.t | 12 +- t/overload-timeout.t | 9 - t/overload-timeout2.t | 12 - t/reset.t | 15 - t/run.sh | 38 +- t/runner.py | 6 +- t/swap2.t | 2 + t/swap3.t | 13 +- t/swap4.t | 2 + t/swap5.t | 12 +- t/test.conf | 28 +- t/timeout1.t | 21 + t/toggle2.t | 2 + 67 files changed, 3131 insertions(+), 3038 deletions(-) create mode 100644 DESIGN.md create mode 100644 keyd-application-mapper.1.gz create mode 100644 keyd-application-mapper.md create mode 100644 keyd.md delete mode 100644 man.md delete mode 100644 src/client.c create mode 100644 src/device.c create mode 100644 src/device.h delete mode 100644 src/error.c delete mode 100644 src/error.h delete mode 100644 src/evdev.c create mode 100644 src/ipc.c create mode 100644 src/ipc.h delete mode 100644 src/server.c delete mode 100644 src/server.h create mode 100644 t/oneshot12.t create mode 100644 t/oneshot14.t delete mode 100644 t/overload-timeout.t delete mode 100644 t/overload-timeout2.t delete mode 100644 t/reset.t create mode 100644 t/timeout1.t diff --git a/CHANGELOG.md b/CHANGELOG.md index 6254c327..46f2f7e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# v2.3.0-rc + +This is a **major release** which breaks **backward compatibility** with +non trivial configs. It is best to reread the man page. Minimal +breaking changes are expected moving forward. + + - Introduce composite layers + - Add timeout() + - Simplify layer model + - Layer entries are now affected by active modifiers (current layer modifiers excepted) + - Eliminate layer types + - Eliminate -d + +See [DESIGN.md](DESIGN.md) for a more thorough description of changes. + # v2.2.7-beta - Fix support for symlinked config files (#148) diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 00000000..afe7c27c --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,116 @@ +This document contains a description of major design changes. +It is not intended to be exhaustive. + +# v2.3.0-rc + +This is a major release which breaks backward compatibility for +non-trivial configs. (we are still in beta after all :P). + +In the absence of too much blowback, this will probably become the final +v2 design. + +Much of this harkens back to the v1, with some additional simplifications +and enhancements. + +## Notable changes: + + - Introduced composite layers + + - Replaced three arg overload() with a more flexible timeout() mechanism + + - Eliminated sequences in favour of macros (C-x is now just syntactic + sugar for macro(C-x)). + + - Actions which previously accepted sequences as a second argument + now accept macros of any kind. + + - General stability/speed/memory improvements + + - Made the man page less war and peacey + +## Non backward-compatible changes: + + - Modifiers now apply to all bindings with the exception of modifiers + associated with the layer in which the key is bound. + + Rationale: + + The end result is more intuitive and allows for modifiers to be paired with + layer entries. + + E.G + capslock = layer(nav) + + [nav:C] + + h = left + l = right + + will cause 'control+h' to produce 'left' (rather than 'C-left'), while + 'control+capslock+h' will produce 'C-left', as one might intuit. + + - Abolished layer types. Notably, the concept of a 'layout' no longer exists. + Bindings are drawn from layers on the basis of activation order with [main] + being active by default. + + Rationale: + + This simplifies the lookup logic, elminates the need for dedicated layout + actions, and makes it easier to define common bindings for letter layouts + since the main layer can be used as a fallback during lookup. + + E.G + + [main] + + capslock = layer(capslock) + + [dvorak] + + a = a + s = o + ... + + [capslock] + + 1 = toggle(dvorak) + + - Special characters within macros like ) and \ must be escaped with a backslash. + + - Modifier sequences (e.g 'C-M') are no longer valid layers by default. + + Rationale: + + This was legacy magic from v1 which added a bunch of cruft to the code and + seemed to cause confusion by blurring the boundaries between layers and + modifiers. Similar results can be achieved by explicitly defining an + empty layer with the desired modifier tags: + + I.E + + a = layer(M-C) + b = layer(M) + + becomes + + a = layer(meta-control) + b = layer(meta) + + [meta-control:M-C] + + Note that in the above sample "meta" does not need to be + explicitly defined, since full modifier names are + + - Eliminated -d + + Rationale: + + Modern init systems are quite adept at daemonization, and the end user + can always redirect/fork using a wrapper script if desired. + + - Eliminated reload on SIGUSR1 + + Rationale: + + Startup time has been reduced making the cost of a full + restart negligible (and probably more reliable). diff --git a/Makefile b/Makefile index e762e925..8258d454 100644 --- a/Makefile +++ b/Makefile @@ -2,32 +2,31 @@ DESTDIR= PREFIX=/usr -LOCK_FILE="/var/run/keyd.lock" -SOCKET="/var/run/keyd.socket" -LOG_FILE="/var/log/keyd.log" -CONFIG_DIR="/etc/keyd" +VERSION=2.3.0-rc +COMMIT=$(shell git describe --no-match --always --abbrev=7 --dirty) -VERSION=2.2.7-beta -GIT_HASH=$(shell git describe --no-match --always --abbrev=40 --dirty) - -CFLAGS+=-DVERSION=\"$(VERSION)\" \ - -DGIT_COMMIT_HASH=\"$(GIT_HASH)\" \ - -DCONFIG_DIR=\"$(CONFIG_DIR)\" \ - -DLOG_FILE=\"$(LOG_FILE)\" \ - -DSOCKET=\"$(SOCKET)\" \ - -DLOCK_FILE=\"$(LOCK_FILE)\" \ +CFLAGS+=-DVERSION=\"v$(VERSION)\ \($(COMMIT)\)\" \ -I/usr/local/include \ -L/usr/local/lib -LDFLAGS+=$(shell if [ `uname -s` != Linux ]; then echo -linotify; fi) + +platform+=$(shell uname -s) + +ifeq ($(platform), Linux) + COMPAT_FILES= +else + LDFLAGS+=-linotify + COMPAT_FILES= +endif all: vkbd-uinput vkbd-%: mkdir -p bin - $(CC) $(CFLAGS) -O3 src/*.c src/vkbd/$(@:vkbd-%=%).c -o bin/keyd -lpthread $(LDFLAGS) + $(CC) $(CFLAGS) -O3 $(COMPAT_FILES) src/*.c src/vkbd/$(@:vkbd-%=%).c -o bin/keyd -lpthread $(LDFLAGS) debug: - CFLAGS+="-pedantic -Wall -Wextra -g" $(MAKE) + CFLAGS="-pedantic -Wall -Wextra -g" $(MAKE) man: - pandoc -s -t man man.md | gzip > keyd.1.gz + scdoc < keyd.md | gzip > keyd.1.gz + scdoc < keyd-application-mapper.md | gzip > keyd-application-mapper.1.gz clean: -rm -rf bin install: @@ -55,6 +54,7 @@ install: install -m755 bin/keyd $(DESTDIR)$(PREFIX)/bin install -m755 scripts/keyd-application-mapper $(DESTDIR)$(PREFIX)/bin install -m644 keyd.1.gz $(DESTDIR)$(PREFIX)/share/man/man1 + install -m644 keyd-application-mapper.1.gz $(DESTDIR)$(PREFIX)/share/man/man1 install -m644 man.md CHANGELOG.md README.md $(DESTDIR)$(PREFIX)/share/doc/keyd install -m644 examples/* $(DESTDIR)$(PREFIX)/share/doc/keyd/examples @@ -63,7 +63,8 @@ uninstall: $(DESTDIR)$(PREFIX)/lib/systemd/system/keyd.service\ bin/keyd $(DESTDIR)$(PREFIX)/bin/keyd\ $(DESTDIR)$(PREFIX)/bin/keyd-application-mapper\ - $(DESTDIR)$(PREFIX)/share/man/man1/keyd.1.gz + $(DESTDIR)$(PREFIX)/share/man/man1/keyd.1.gz\ + $(DESTDIR)$(PREFIX)/share/man/man1/keyd-application-mapper.1.gz install-usb-gadget: install install -m644 src/vkbd/usb-gadget.service $(DESTDIR)$(PREFIX)/lib/systemd/system/keyd-usb-gadget.service diff --git a/README.md b/README.md index a3840004..905d9439 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,12 @@ result often being tethered to a specified environment (X11). keyd attempts to solve this problem by providing a flexible system wide daemon which remaps keys using kernel level input primitives (evdev, uinput). -# UPDATE (v2-beta) +# UPDATE (v2.3.0-rc) -master is currently tracking `v2-beta`. Things should be reasonably backwards -compatible but may occasionally break before v2 leaves beta. If you are looking -for something a bit more stable you may be interested the [v1](https://github.com/rvaiya/keyd/tree/v1) branch. +master is currently tracking `v2.3.0-rc`. Things should be reasonably backwards +compatible, but minor changes may be introduced before the final release. If +you are looking for something a bit more stable you may be interested the +[v1](https://github.com/rvaiya/keyd/tree/v1) branch. *NOTE: For those migrating their configs from v1, please see the [changelog](CHANGELOG.md) for a list of changes.* @@ -154,7 +155,7 @@ members, no personal responsibility is taken for them. [main] - leftshift = oneshot(S) + leftshift = oneshot(shift) capslock = overload(symbols, esc) [symbols] @@ -182,7 +183,7 @@ following config: leftalt = oneshot(alt) rightalt = oneshot(altgr) - capslock = overload(C, esc) + capslock = overload(control, esc) insert = S-insert This overloads the capslock key to function as both escape (when tapped) and diff --git a/TODO b/TODO index c1127abf..897cb5a1 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,10 @@ - - Organize tests. - - Improve FAQ. - - Add more examples. - - Multi user support? - - Unicode support? +fix hotswap +cleanup manpage +add [globals] with configurable macro timeouts. +[idea] tmux like layer timeouts +nested timeouts? (perhaps not...) +toggleable composite layers? +improved mouse support (integrate moused?) +organize tests +unicode support +multi-user support (remove keyd group) diff --git a/examples/capslock-esc-basic.conf b/examples/capslock-esc-basic.conf index ce297dcc..939ae9a2 100644 --- a/examples/capslock-esc-basic.conf +++ b/examples/capslock-esc-basic.conf @@ -6,4 +6,4 @@ # # - when 'capslock' is held, and used in combination with another key, it # behaves like a 'ctrl' key modifier (just like xcape) -capslock = overload(C, esc) +capslock = overload(control, esc) diff --git a/keyd-application-mapper.1.gz b/keyd-application-mapper.1.gz new file mode 100644 index 0000000000000000000000000000000000000000..8e179aeabad26f9a347472ebc327a3b6ed677921 GIT binary patch literal 1489 zcmV;?1ups@iwFP!000001C>_qZ`(Ey{apPOw+v{LAeQUw$A=;-kQG^KplgdJZ8pUD zKueTNgc4Pfa^j)b-@bQ8DNfdEa8DBj(>ckhnO7dv#STkTcUB`r4;iqaJ{%V)Fv zFw61wm1{byqb98Pjc%ig#7upHH4q7i=@`dShcj8R3JsCZ$FZ0D@UEG zb(R}Vi*`4!DK7&Zf2PH*`kdw3Qu++5&$7H-Ww|QJ3WQ&EZCa}DU>2^80P-Ts&tKEd zwceBy)pgbs0&UXZLHixrnc@HN`QhQ@`45xB*-n&@3?YFK0GCIJ zVnlj*MGrpT)9+jc?v9~4vh{()U5BNevsxC zI<*S&%YX!1M47rgkk^gckn31g%G!VghQ+9Cgwn3)7kV{`YB?@k%EHN3R1{?>d(c-N@HyWM#kObI}p zD!2f9yt!C;*O88%{8Bq%vOA9>YOvu0Wd8Q-{P@kAb6m|8695owf3+B8G|K6YG9Sj$-eh#MJ!=)8^VJ(VVO78rx}Jk!w&(O{_jl|mys z%>FGILc%R?@&zlw`Rr~rq$xA7LGy|ONX8>n;Q)<1K0gkZeW%L<#D}D~YQAy)`hXYf z?Tz|oIUMsX2{LZ^eD1x88xa@go4`oPvet3R|qn4ZUz!Bq;rHXS5?{v3f9ysjC!PS23TM7RHym-S9o8nRBJ**ySU%2^2p)QLa+QU!D^G=l_ row1MZIbZ?n8b|4fP)djqiRE>InJKxJVj5_>J?8!a3ul^&>k9w?aUbIX literal 0 HcmV?d00001 diff --git a/keyd-application-mapper.md b/keyd-application-mapper.md new file mode 100644 index 00000000..d0fff912 --- /dev/null +++ b/keyd-application-mapper.md @@ -0,0 +1,90 @@ +keyd-application-mapper(1) + +# USAGE + +keyd-application-mapper [-d] + +# OVERVIEW + +A script which reads _~/.config/keyd/app.conf_ and applies the supplied +bindings whenever a window with a matching class comes into focus. + +You can think of each section as a set of application specific masks applied +over the global rules defined in _/etc/keyd/\*.conf_. + +The config file has the following form: + +``` + [] + + + + ... +``` + +Where __ has one of the following forms: + + \[\] # Match by window class + \[|\] # Match by class and title + +and each _<expression>_ is a valid argument to _-e_ (see *Expressions*). + +_<class exp>_ and _<title exp>_ are strings which describe window class and title names +to be matched, and may optionally contain unix style wildcards (\*). For example, +_[gnome|\*find\*]_ will match any window with a class of _gnome_ and a title +containing the substring _find_. + +E.G: + +``` + [kitty] + + alt.] = C-tab + alt.[ = C-S-tab + alt.t = C-S-t + + [st-*] + + alt.1 = macro(Inside space st) + + [chromium] + + control.1 = macro(Inside space chrome!) + alt.] = C-tab + alt.[ = C-S-tab + alt.t = C-t +``` + +Will remap _A-1_ to the the string 'Inside st' when a window with a class +that begins with 'st-' (e.g st-256color) is active. + +Window class and title names can be obtained by inspecting the log output while the +daemon is running (e.g _tail\ -f\ ~/.config/keyd/app.log_). + +At the moment X, Sway and Gnome are supported. + +# INSTALLATION + +Installation is a simple matter of running the command _keyd-application-mapper -d_ +somewhere in your display server initialization logic (e.g _~/.xinitrc_ or +_~/.xsession_). If you are running Gnome, +running _keyd-application-mapper_ for the first time will install an extension +which manages the script lifecycle. + +In order for this to work, keyd must be running and the user must have access +to */var/run/keyd.socket* (i.e be a member of the *keyd* group). + +# A note on security + +Any user which can interact with a program that directly controls input devices +should be assumed to have full access to the system. + +While keyd offers slightly better isolation compared to other remappers by dint +of mediating access through an IPC mechanism (rather than granting users +blanket access to /dev/input/\* and /dev/uinput), it still provides an +opportunity for abuse and should be treated with due deference. + +Specifically, access to */var/run/keyd.socket* should only be granted to +trusted users and the group _keyd_ should be regarded with the same reverence +as _wheel_. + diff --git a/keyd.1.gz b/keyd.1.gz index f4729125b3b27e4323c2a778958a068735677596..a51f7391fd2d2c964511c66257758914576c75d3 100644 GIT binary patch literal 4868 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 literal 6538 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 diff --git a/keyd.md b/keyd.md new file mode 100644 index 00000000..07f508be --- /dev/null +++ b/keyd.md @@ -0,0 +1,505 @@ +keyd(1) + +# NAME + +*keyd* - A key remapping daemon. + +# SYNOPSIS + +*keyd* [options] + +# OPTIONS + +*-m, --monitor* + Start keyd in monitor mode. Useful for discovering keycodes and device ids. + +*-e, --expression <expression> [<expression>...]* + Modify bindings of the currently active keyboard. See _Expressions_ for details. + +*-l, --list-keys* + List valid key names. + +*-v, --version* + Print the current version and exit. + +*-h, --help* + Print help and exit. + +# DESCRIPTION + +keyd is a system wide key remapping daemon which supports features like +layering, oneshot modifiers, and macros. In its most basic form it can be used +to define a custom key layout that persists across display server boundaries +(e.g wayland/X/tty). + +The program runs in the foreground, printing diagnostic information to the +standard output streams, and is intended to be run as a single instance managed +by the init system. + +*NOTE:* + +Because keyd modifies your primary input device, it is possible to render your +machine unusable with a bad config file. If you find yourself in this situation +the panic sequence *<backspace>+<escape>+<enter>* will force keyd to +terminate. + +# CONFIGURATION + +Configuration files are stored in _/etc/keyd/_ and are loaded upon initialization. +Changes to these files can be realized by restarting the daemon, which is usually +accomplished using your service manager. + +E.G + + sudo systemctl restart keyd + + +A valid config file has the extension _.conf_ and *must* begin with an _[ids]_ +section that has one of the following forms: + +``` + [ids] + + <vendor id 1>:<product id 1> + <vendor id 2>:<product id 2> + ... +``` + +or + +``` + [ids] + + * + -<vendor id 1>:<product id 1> + -<vendor id 2>:<product id 2> + ... +``` + +The first form specifies a list of ids to be explicitly matched, while the +second matches any id which has not been explicitly excluded. + +For example: + +``` + [ids] + * + -0123:4567 +``` + + +Will match all devices which *do not* have the id _0123:4567_, while: + +``` + [ids] + 0123:4567 +``` + +will exclusively match any devices which do. + +Each subsequent section of the file corresponds to a _layer_. + +## Layers + +A layer is a collection of _bindings_, each of which specifies the behaviour of +a particular key. Multiple layers may be active at any given time, forming a +stack of occluding keymaps consulted in activation order. The default layer is +called _main_ and is where common bindings should be defined. + +For example, the following config snippet defines a layer called _nav_ +and creates a toggle for it in the _main_ layer: + +``` + [main] + + capslock = layer(nav) + + [nav] + + h = left + k = up + j = down + l = right +``` + +When capslock is held, the _nav_ layer occludes the _main_ layer +causing _hjkl_ to function as the corresponding arrow keys. + +Unlike most other remapping tools, keyd provides first class support for +modifiers. A layer name may optionally end with a ':' followed by a +set of modifiers to emulate in the absence of an explicit mapping. + +These layers play nicely with other modifiers and preserve existing stacking +semantics. + +Formally, each layer heading has the following form: + +``` + "[" <layer name>[:<modifier set>] "]" +``` + +Where _<modifier_set>_ has the form: + + _<modifier1>[-<modifier2>]..._ + +and each modifier is one of: + + *C* - Control++ +*M* - Meta/Super++ +*A* - Alt++ +*S* - Shift++ +*G* - AltGr + +Finally, each layer heading is followed by a set of bindings which take the form: + + <key> = <keycode>|<macro>|<action> + +for a description of <action> and <macro> see _ACTIONS_ and _MACROS_. + + +For example: + +``` + [main] + + capslock = layer(capslock) + + [capslock:C] + + j = down +``` + +will cause _capslock_ to behave as _control_, except in the case of _control+j_, which will +emit _down_. This makes it trivial to define custom modifiers which don't interfere with +one another. + +By default, each key is bound to itself within the main layer. The exception to this +are the modifier keys, which are instead bound to eponymously named layers with the +corresponding modifiers. + +For example, _meta_ is acutally bound to _layer(meta)_, where _meta_ is +internally defined as _meta:M_. + +A consequence of this is that overriding modifier keys is a simple matter of +adding the desired bindings to appropriate pre-defined layer. + +Thus + +``` + [ids] + * + + [control] + j = down +``` + +is a completely valid config, which does what the benighted user might expect. Internally, +the full config actually looks something like this: + +``` + [ids] + * + + [main] + leftcontrol = layer(control) + rightcontrol = layer(control) + + [control:C] + j = down +``` + + + +## Composite Layers + +A special kind of layer called a *composite layer* can be defined by creating a +layer with a name consisting of existing layers delimited by _+_. The resultant +layer will be activated and given precedence when one of its constituents are +activated. + +E.G + +``` +[main] +capslock = layer(capslock) + +[capslock:C] +[capslock+shift] + +h = left +``` + +Will cause the sequence _control+shift+h_ to produce _left_, while preserving +the expected functionality of _capslock_ and _shift_ in isolation. + +*Note:* composite layers *must* always be defined _after_ the layers of which they +are comprised. + +That is: + +``` +[layer1] +[layer2] +[layer1+layer2] +``` + +and not + +``` +[layer1+layer2] +[layer1] +[layer2] +``` + +# MACROS + +Various keyd actions accept macro expressions. + +A valid macro expression has one of the following forms: + + . macro(<exp>) + . [<modifier 1>[-<modifier 2>...]-<key> + +Where _<exp>_ has the form _<token1> [<token2>...]_ and each token is one of: + + - a valid key code. + - a type 2. macro. + - a contiguous group of characters, each of which is a valid key code. + - a group of key codes delimited by + to be depressed as a unit. + - a timeout of the form _<time>ms_ (where _<time>_ < 1024). + +The following are all valid macro expressions: + + - C-a + - macro(C-a) + - macro(control+a) + - A-M-x + - macro(Hello space World) + - macro(h e l l o space w o r ld) (identical to the above) + - macro(C-t 100ms google.com enter) + + +# ACTIONS + +A key may optionally be bound to an _action_ which accepts zero or more +arguments. + +*oneshot(<layer>)* + If tapped, activate the supplied layer for the duration of the next keypress. + If _<layer>_ is a modifier layer then it will cause the key to behave as the + corresponding modifiers while held. + +*layer(<layer>)* + Activates the given layer for the duration of the keypress. + +*toggle(<layer>)* + Permanently toggle the state of the given layer. + +*overload(<layer>, <macro>)* + Activates the given layer while held and executes the provided macro when tapped. + +*timeout(<action 1>, <timeout>, <action 2>)* + If the key is held in isolation for more than _<timeout> ms_, activate the first + action, if the key is held for less than _<timeout> ms_ or another key is struck + before <timeout> ms expires, execute the first action. + + E.G + + timeout(a, 500, layer(control)) + + Will cause the assigned key to behave as _control_ if it is held for more than + 500 ms. + +*swap(<layer>[, <macro>])* + Swap the currently active layer with the supplied one. The supplied layer is + active for the duration of the depression of the current layer's activation + key. A macro may optionally be supplied to be performed before the layer + change. + +``` + [control] + + x = swap(xlayer) + + [xlayer] + + s = C-s + b = S-insert +``` + +*noop* + Do nothing. + +# IPC + +To facilitate extensibility, keyd employs a client-server model accessible +through the use of *-e*. The keymap can thus be conceived of as a +living entity that can be modified at run time. + +In addition to allowing the user to try new bindings on the fly, this +enables the user to fully leverage keyd's expressive power from other programs +without incurring a performance penalty. + +For instance, the user may use this functionality to write a script which +alters the keymap when they switch between different tmux sessions. + +The application remapping tool (*keyd-application-mapper(1)*) which ships with keyd +is a good example of this. It is a small python script which performs event +detection for the various display servers (e.g X/sway/gnome, etc) and feeds the +desired mappings to the core using _-e_. + +## Expressions + +The _-e_ flag accepts one or more _expressions_, each of which must have the following form: + + \[<layer>.\]<key> = <key>|<macro>|<action> + +Where _<layer>_ is the name of an (existing) layer in which the key is to be bound. + +As a special case, an expression may be the string *reset*, in which case the +current keymap will revert to its original state (all dynamically applied +bindings will be dropped). + +Examples: + +``` + # keyd -e '- = C-c' + # keyd -e reset # Reset the state of the keymap so it reflects /etc/keyd/. +``` + +By default expressions apply to the most recently active keyboard. + +# EXAMPLES + +## Example 1 + +Make _esc+q_ toggle the dvorak letter layout. + +``` + [ids] + * + + [main] + esc = layer(esc) + + [dvorak] + + a = a + s = o + ... + + [esc] + + q = toggle(dvorak) +``` + +## Example 2 + +Invert the behaviour of the shift key without breaking modifier behaviour. + +``` + [ids] + * + + [main] + 1 = ! + 2 = @ + 3 = # + 4 = $ + 5 = % + 6 = ^ + 7 = & + 8 = * + 9 = ( + 0 = ) + + [shift] + 0 = 0 + 1 = 1 + 2 = 2 + 3 = 3 + 4 = 4 + 5 = 5 + 6 = 6 + 7 = 7 + 8 = 8 + 9 = 9 +``` + +## Example 3 + +Tapping control once causes it to apply to the next key, tapping it twice +activates it until it is pressed again, and holding it produces expected +behaviour. + +``` + [main] + + control = oneshot(control) + + [control] + + toggle(control) +``` + +## Example 4 + +Meta behaves as normal except when \` is pressed, after which the alt_tab layer +is activated for the duration of the leftmeta keypress. Subsequent actuations +of _will thus produce A-tab instead of M-\\_. + +``` + [meta] + + ` = swap(alt_tab, A-tab) + + [alt_tab:A] + + tab = A-S-tab + ` = A-tab +``` + +## Example 5 + +``` + # Uses the compose key functionality of the display server to generate + # international glyphs. # For this to work 'setxkbmap -option + # compose:menu' must # be run after keyd has started. + + # A list of sequences can be found in /usr/share/X11/locale/en_US.UTF-8/Compose + # on most systems. + + + [main] + + rightalt = layer(dia) + + [dia] + + # Map o to ö + o = macro(compose o ") + + # Map e to € + e = macro(compose c =) +``` + +## Example 6 + +``` + # Tapping both shift keys will activate capslock. + + [shift] + + leftshift = capslock + rightshift = capslock +``` + +# AUTHOR + +Written by Raheman Vaiya (2017-). + +# BUGS + +Please file any bugs or feature requests at the following url: + +<https://github.com/rvaiya/keyd/issues> diff --git a/man.md b/man.md deleted file mode 100644 index cf55e631..00000000 --- a/man.md +++ /dev/null @@ -1,569 +0,0 @@ -% KEYD(1) - -# NAME - -**keyd** - A key remapping daemon. - -# SYNOPSIS - -**keyd** \[options\] - -# OPTIONS - -`-m, --monitor` -: Start keyd in monitor mode. Mainly useful for discovering key codes and debugging. - -`-e, --expression <expression> [<expression>...]` -: Modify bindings of the currently active keyboard. See *Expressions* for details. - -`-l, --list` -: List all internal key names. - -`-d, --daemonize` -: Start keyd as a daemon logging out all output to */var/log/keyd.log*. - -`-v, --version` -: Print the current version and exit. - -`-h, --help` -: Print help and exit. - -# DESCRIPTION - -keyd is a system wide key remapping daemon which supports features like -layering, oneshot modifiers, and macros. In its most basic form it can be used -to define a custom key layout that persists across display server boundaries -(e.g wayland/X/tty). - -The program is intended to be managed by the init system, but is capable of -running as a standalone daemon. The default behaviour is to run in the -foreground and print to stderr, unless **-d** is supplied, in which case in -which case log output will be stored in */var/log/keyd.log*. - -**NOTE** - -Because keyd modifies your primary input device it is possible to render your -machine unusable with a bad config file. If you find yourself in this -situation the sequence *\<backspace\>+\<escape\>+\<enter\>* will force keyd -to terminate. - -# CONFIGURATION - -All configuration files are stored in */etc/keyd/* and are loaded upon -initialization. A reload can be triggered by restarting the daemon or by -sending SIGUSR1 to the process (e.g sudo pkill -usr1 keyd). - -A valid config file has the extension .conf and must begin with an *ids* section that has the following form: - - [ids] - - <id 1> - <id 2> - ... - -Where each \<id\> is one of: - - - A device id of the form <vendor id>:<product id> (obtained with -m). - - The wildcard "*". - -A wildcard indicates that the file should apply to all keyboards -which are not explicitly listed in another configuration file and -may optionally be followed by one or more lines of the form: - - -<vendor id>:<product id> - -representing a device to be excluded from the matching policy. Thus the following -config will match all devices except 0123:4567: - - [ids] - * - -0123:4567 - - -The monitor flag (**-m**) can be used to interactively obtain device ids and key names like so: - - > sudo systemctl stop keyd # Avoid loopback. - > sudo keyd -m - - Magic Keyboard 0ade:0fac capslock down - Magic Keyboard 0ade:0fac capslock up - ... - -Every subsequent section of the file corresponds to a layer and has the form: - - [<name>[:<type>]] - -Where `<type>` is either a valid modifier set (see *MODIFIERS*) or "layout". - -Each line within a layer is a binding of the form: - - <key> = <action>|<keyseq> - -Where `<keyseq>` has the form: `[<modifier1>[-<modifier2>...]-<key>` - -and each modifier is one of: - -\ **C** - Control\ -\ **M** - Meta/Super\ -\ **A** - Alt\ -\ **S** - Shift\ -\ **G** - AltGr - -In addition to key sequences, keyd can remap keys to actions which -conditionally send keystrokes or transform the state of the keymap. - -It is, for instance, possible to map a key to escape when tapped and control -when held by assigning it to `overload(C, esc)`. A complete list of available -actions can be found in *ACTIONS*. - -## Layers - -Each layer is a keymap unto itself and can be transiently activated by a key -mapped to the corresponding *layer()* action. - -For example, the following configuration creates a new layer called 'symbols' which -is activated by holding the capslock key. - - [ids] - * - - [main] - capslock = layer(symbols) - - [symbols] - f = ~ - d = / - ... - -Pressing `capslock+f` thus produces a tilde. - -Key sequences within a layer are fully descriptive and completely self -contained. That is, the sequence 'A-b' corresponds exactly to the combination -`<alt>+<b>`. If any additional modifiers are active they will be deactivated -for the duration of the corresponding key stroke. - -## Layouts - -The *layout* is a special kind of layer from which bindings are drawn if no -other layers are active. By default all keys are mapped to themselves within a -layout. Every config has at least one layout called *main*, but additional -layouts may be defined and subsequently activated using the `layout()` action. - -Layouts also have the additional property of being affected by the active modifier -set. That is, unlike layers, key sequences mapped within them are not -interpreted literally. - -If you wish to use an alternative letter arrangement this is the appropriate -place to define it. - -E.G - - [main] - - rightshift = layout(dvorak) - - [dvorak:layout] - - rightshift = layout(main) - s = o - d = e - ... - -## Modifiers - -Unlike most other remapping tools keyd provides first class support for -modifiers. A valid modifier set may optionally be used as a layer type, -causing the layer to behave as the modifier set in all instances except -where an explicit mapping overrides the default behaviour. - -These layers play nicely with other modifiers and preserve existing stacking -semantics. - -For example: - - [main] - - leftalt = layer(myalt) - rightalt = layer(myalt) - - [myalt:A] - - 1 = C-A-f1 - -Will cause the leftalt key to behave as alt in all instances except when -alt+1 is pressed, in which case the key sequence `C-A-f1` will be emitted. - -By default each modifier key is mapped to an eponymously named modifier layer. - -Thus the above config can be shortened to: - - [alt] - - 1 = C-A-f1 - -since leftalt and rightalt are already assigned to `layer(alt)`. - -Additionally, left hand values which are modifier names are expanded to both -associated keycodes. - -E.G - control = esc - -is the equivalent of - - rightcontrol = esc - leftcontrol = esc - -Finally any set of valid modifiers is also a valid layer. For example, the -layer `M-C` corresponds to a layer which behaves like the modifiers meta and -control, which means the following: - - capslock = layer(M-A) - -will cause capslock to behave as meta and alt when held. - -**NOTE**: While it is technically possible to use individual modifier key codes -like `rightalt` and `rightcontrol` as target sequences, the user is strongly -encouraged to avoid these as they can produce unintuitive results when paired -with their layer counterparts. To this end, it is best to think of modifiers -as just a another kind of layer. - -Thus instead of: - - meta = rightcontrol - -one should do: - - meta = layer(control) - -### Lookup Rules - -In order to achieve this (un)holy union, the following lookup rules are used: - - if len(active_layers) > 0: - if key in most_recent_layer: - action = most_recent_layer[key] - else if has_modifiers(most_recent_layer): - for layer in active_layers: - activate_modifiers(layer) - - action = layout[key] - else: - action = layout[key] - -The upshot of all this is that things should mostly just work™. The -majority of users needn't be explicitly conscious of the lookup rules -unless they are doing something unorthodox (e.g nesting hybrid layers). - -## IPC - -To facilitate extensibility keyd employs a client-server model. The keymap can -thus be conceived of as a 'living entity' that can be modified at run time. - -In addition to allowing the user to try new bindings on the fly, this -enables the user to fully leverage keyd's expressive power from other programs -without incurring a performance penalty. - -For instance, the user may use this functionality to write a script which -alters the keymap when they switch between different tmux sessions. - -The application remapping tool (keyd-application-mapper) which ships with keyd -is a good example of this. It is a small python script which performs -event detection for the various platforms (e.g X/sway/gnome, etc) and feeds the -desired mappings to the core using `-e`. - -### Expressions - -The `-e` flag accepts one or more *expressions*, each of which must have one of the following forms: - - [<layer>.]<key> = <action>|<key sequence> - reset - -Where `<layer>` is the name of an (existing) layer in which the key is to be bound. - -As a special case, an expression may be the string 'reset', in which case the -current keymap will revert to its original state (all dynamically applied -bindings will be dropped). - -Examples: - - $ keyd -e 'rightshift = layout(dvorak)' # Map rightshift to layout(dvorak) in [main]. - $ keyd -e 'dvorak.rightshift = layout(main)' # Map rightshift to layout(main) in [dvorak]. - $ keyd -e 'reset' # Reset the state of the keymap so it reflects /etc/keyd/. - -By default expressions apply to the most recently active keyboard. - -### Application Support - -keyd ships with a python script called **keyd-application-mapper** which -reads a file called *~/.config/keyd/app.conf* and applies the supplied bindings -whenever a window with a matching class comes into focus. - -You can think of each section as a set of application specific masks applied -over the global rules defined in `/etc/keyd/*.conf`. - -The config file has the following form: - - [<filter>] - - <expression 1> - <expression 2...> - -Where `<filter>` has one of the following forms: - - [<class exp>] # Match by window class - [<class exp>|<title exp>] # Match by class and title - -and each `<expression>` is a valid argument to `-e` (see *Expressions*). - -`<class exp>` and `<title exp>` are strings which describe window class and title names -to be matched and may optionally contain unix style wildcards (*). For example, -'`[gnome|*find*]`' will match any window with a class of 'gnome' and a title -which contains 'find'. - -E.G - - [kitty] - - alt.] = C-tab - alt.[ = C-S-tab - alt.t = C-S-t - - [st-*] - - alt.1 = macro(Inside space st) - - [chromium] - - control.1 = macro(Inside space chrome!) - alt.] = C-tab - alt.[ = C-S-tab - alt.t = C-t - -Will remap `A-1` to the the string 'Inside st' when a window with a class -that begins with 'st-' (e.g st-256color) is active. - -Window class and title names can be obtained by inspecting the log output while the -daemon is running (e.g `tail -f ~/.config/keyd/app.log`). - -At the moment X, Sway and Gnome are supported. - -#### Installation - -Installation is a simple matter of running the daemon `keyd-application-mapper -d` -somewhere in your display server initialization logic (e.g ~/.xinitrc or -~/.xsession). The exception to this is if you are running Gnome, in which case -running `keyd-application-mapper` for the first time will install an extension -which manages the script lifecycle. - -In order for this to work, keyd must be running and the user must have access -to */var/run/keyd.socket* (i.e be a member of the *keyd* group). - -### A note on security - -Any user which can interact with a program that directly controls input devices -should be assumed to have full access to the system. - -While keyd offers slightly better isolation compared to other remappers by dint -of mediating access through an IPC mechanism (rather than granting users -blanket access to /dev/input/* and /dev/uinput), it still provides an -opportunity for abuse and should be treated with due deference. - -Specifically, access to */var/run/keyd.socket* should only be granted to -trusted users and the group `keyd` should be regarded with the same reverence -as `wheel`. - -## ACTIONS - -**oneshot(\<layer\>)** - -: If tapped, activate the supplied layer for the duration of the next keypress. -If `<layer>` is a modifier layer then it will cause the key to behave as the -corresponding modifiers while held. - -**layer(\<layer\>)** - -: Activates the given layer while held. - -**toggle(\<layer\>)** - -: Toggles the state of the given layer. Note this is intended for layers and is -distinct from `layout()` which should be used for letter layouts. - -**overload(\<layer\>,\<keyseq\>[,\<timeout\>])** - -: Activates the given layer while held and emits the given key sequence when -tapped. A timeout in milliseconds may optionally be supplied to disambiguate -a tap and a hold. - - If a timeout is present depression of the corresponding key is only interpreted -as a layer activation in the event that it is sustained for more than -\<timeout\> a milliseconds. This is useful if the overloaded key is frequently -used on its own (e.g space) and only occasionally treated as a modifier (the opposite -of the default assumption). - -**layout(\<layer\>)** - -: Sets the current layout to the given layer. You will likely want to ensure -you have a way to switch layouts within the newly activated one. - -**swap(\<layer\>[, \<keyseq\>])** - -: Swap the currently active layer with the supplied one. The supplied layer is -active for the duration of the depression of the current layer's activation -key. A key sequence may optionally be supplied to be performed before the layer -change. - -**macro(\<macro\>)** - -: Perform the key sequence described in `<macro>` - -Where `<macro>` has the form `<token1> [<token2>...]` where each token is one of: - -- a valid key sequence. -- a contiguous group of characters each of which is a valid key sequence. -- a group of key codes delimited by + to be depressed as a unit. -- a timeout of the form `<time>ms` (where `<time>` < 1024). - -Examples: - - # Sends alt+p, waits 100ms (allowing the launcher time to start) and then sends 'chromium' before sending enter. - macro(A-p 100ms chromium enter) - - # Types 'Hello World' - macro(h e l l o space w o r ld) - - # Identical to the above - macro(Hello space World) - - # Produces control + b - macro(control + b) - - # Produces the sequence <control down> <b down> <control up> <b up> - macro(control+b) - - # Produces the sequence <control down> <control up> <b down> <b up> - macro(control b) - -# EXAMPLES - -## Example 1 - -Make `esc+q/w` set the letter layout. - - [ids] - * - - [main] - esc = layer(esc) - - [dvorak:layout] - s = o - d = e - ... - - [esc] - q = layout(main) - w = layout(dvorak) - -## Example 3 - -Invert the behaviour of the shift key without breaking modifier behaviour. - - [ids] - * - - [main] - 1 = ! - 2 = @ - 3 = # - 4 = $ - 5 = % - 6 = ^ - 7 = & - 8 = * - 9 = ( - 0 = ) - - [shift] - 0 = 0 - 1 = 1 - 2 = 2 - 3 = 3 - 4 = 4 - 5 = 5 - 6 = 6 - 7 = 7 - 8 = 8 - 9 = 9 - - -## Example 3 - -Tapping control once causes it to apply to the next key, tapping it twice -activates it until it is pressed again, and holding it produces expected -behaviour. - - [main] - - control = oneshot(control) - - [control] - - toggle(control) - -# Example 4 - -Meta behaves as normal except when \` is pressed, after which the alt_tab layer -is activated for the duration of the leftmeta keypress. Subsequent actuations -of \` will thus produce A-tab instead of M-\`. - - [meta] - - ` = swap(alt_tab, A-tab) - - [alt_tab:A] - - tab = A-S-tab - ` = A-tab - -# Example 5 - - # Uses the compose key functionality of the display server to generate - # international glyphs. # For this to work 'setxkbmap -option - # compose:menu' must # be run after keyd has started. - - # A list of sequences can be found in /usr/share/X11/locale/en_US.UTF-8/Compose - # on most systems. - - - [main] - - rightalt = layer(dia) - - [dia] - - # Map o to ö - o = macro(compose o ") - - # Map e to € - e = macro(compose c =) - -# Example 6 - - # Tapping both shift keys will activate capslock. - - [shift] - - leftshift = capslock - rightshift = capslock - -# AUTHOR - -Written by Raheman Vaiya (2017-). - -# BUGS - -Please file any bugs or feature requests at the following url: - -<https://github.com/rvaiya/keyd/issues> diff --git a/src/client.c b/src/client.c deleted file mode 100644 index 406690ef..00000000 --- a/src/client.c +++ /dev/null @@ -1,56 +0,0 @@ -#include <stdio.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <string.h> -#include <unistd.h> -#include <stdlib.h> -#include "server.h" - -static int create_client_socket() -{ - int sd = socket(AF_UNIX, SOCK_SEQPACKET, 0); - struct sockaddr_un addr = {0}; - - if (sd < 0) { - perror("socket"); - exit(-1); - } - - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, SOCKET, sizeof(addr.sun_path)-1); - - if (connect(sd, (struct sockaddr *) &addr, sizeof addr) < 0) { - perror("bind"); - exit(-1); - } - - return sd; -} - -int client_send_message(uint8_t type, const char *payload) -{ - int con; - int rc; - size_t sz; - char response[MAX_RESPONSE_SIZE]; - - con = create_client_socket(); - - if (write(con, &type, sizeof type) < 0) { - perror("write"); - exit(-1); - } - - if (write(con, payload, strlen(payload)+1) < 0) { - perror("write"); - exit(-1); - } - - sz = read(con, response, sizeof response); - - read(con, &rc, sizeof rc); - - write(1, response, sz); - - return rc; -} diff --git a/src/config.c b/src/config.c index 82e2c94d..b9df5ab6 100644 --- a/src/config.c +++ b/src/config.c @@ -1,25 +1,8 @@ -/* Copyright © 2019 Raheman Vaiya. - * - * 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 (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. +/* + * keyd - A key remapping daemon. * - * 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. + * © 2019 Raheman Vaiya (see also: LICENSE). */ - #include <assert.h> #include <dirent.h> #include <fcntl.h> @@ -34,100 +17,10 @@ #include "ini.h" #include "keys.h" -#include "error.h" #include "descriptor.h" #include "layer.h" #include "config.h" - -static struct config *configs = NULL; - -static int config_add_mapping(struct config *config, const char *layer, const char *str); -static int config_add_layer(struct config *config, const char *str); - -/* - * Parse a value of the form 'key = value'. The value may contain = - * and the key may itself be = as a special case. The returned - * values are pointers within the modified input string. - */ - -static int parse_kvp(char *s, char **key, char **value) -{ - char *last_space = NULL; - char *c = s; - - /* Allow the first character to be = as a special case. */ - if (*c == '=') - c++; - - while (*c) { - switch (*c) { - case '=': - if (last_space) - *last_space = 0; - else - *c = 0; - c++; - - while (*c && *c == ' ') - c++; - - if (!*s) - return -1; - - *key = s; - *value = c; - - return 0; - case ' ': - if (!last_space) - last_space = c; - break; - default: - last_space = NULL; - break; - } - - c++; - } - - return -1; -} - -/* Return up to two keycodes associated with the given name. */ -static int lookup_keycodes(const char *name, uint8_t *code1, uint8_t *code2) -{ - size_t i; - - /* - * If the name is a modifier like 'control' we associate it with both - * corresponding key codes (e.g 'rightcontrol'/'leftcontrol') - */ - for (i = 0; i < sizeof(modifier_table)/sizeof(modifier_table[0]); i++) { - struct modifier_table_ent *m = &modifier_table[i]; - - if (!strcmp(m->name, name)) { - *code1 = m->code1; - *code2 = m->code2; - - return 0; - } - } - - for (i = 0; i < MAX_KEYS; i++) { - const struct keycode_table_ent *ent = &keycode_table[i]; - - if (ent->name && - (!strcmp(ent->name, name) || - (ent->alt_name && !strcmp(ent->alt_name, name)))) { - *code1 = i; - *code2 = 0; - - return 0; - } - } - - return -1; -} +#include "keyd.h" static char *read_file(const char *path) { @@ -151,65 +44,61 @@ static char *read_file(const char *path) } data[sz] = '\0'; + close(fd); return data; } -int config_create_layer(struct config *config, const char *name, uint8_t mods) +int config_add_binding(struct config *config, const char *layer, const char *binding) { - struct layer *layer = &config->layers[config->nr_layers++]; - - layer->mods = mods; - strncpy(layer->name, name, sizeof(layer->name)-1); + char exp[MAX_EXP_LEN]; + snprintf(exp, sizeof exp, "%s.%s", layer, binding); - return config->nr_layers-1; + return layer_table_add_entry(&config->layer_table, exp); } -int config_lookup_layer(struct config *config, const char *name) +/* + * returns: + * 1 if the layer exists + * 0 if the layer was created successfully + * < 0 on error + */ +static int config_add_layer(struct config *config, const char *s) { - size_t i; - - for (i = 0; i < config->nr_layers; i++) { - if (!strcmp(config->layers[i].name, name)) - return i; - } - - return -1; -} - + int ret; -/* Returns the index within the layer table of the newly created layout. */ -static int config_create_layout(struct config *config, const char *name) -{ - size_t code; - struct layer *layout; + char buf[MAX_LAYER_NAME_LEN]; + char *name; - int layout_idx = config_create_layer(config, name, 0); - layout = &config->layers[layout_idx]; - - layout->is_layout = 1; + strcpy(buf, s); + name = strtok(buf, ":"); - for (code = 0; code < MAX_KEYS-1; code++) { - struct descriptor *d = &layout->keymap[code]; + if (name && layer_table_lookup(&config->layer_table, name) != -1) + return 1; - d->op = OP_KEYSEQ; - d->args[0].sequence.code = code; - d->args[0].sequence.mods = 0; + if (config->layer_table.nr >= MAX_LAYERS) { + err("max layers (%d) exceeded", MAX_LAYERS); + return -1; } - config_add_mapping(config, name, "shift = layer(shift)"); - config_add_mapping(config, name, "control = layer(control)"); - config_add_mapping(config, name, "meta = layer(meta)"); - config_add_mapping(config, name, "alt = layer(alt)"); - config_add_mapping(config, name, "altgr = layer(altgr)"); + ret = create_layer(&config->layer_table.layers[config->layer_table.nr], + s, + &config->layer_table); + + if (ret < 0) + return -1; - return layout_idx; + config->layer_table.nr++; + return 0; } static void config_init(struct config *config) { + size_t i; + struct descriptor *km; + bzero(config, sizeof(*config)); - /* Create the default modifier layers. */ + config_add_layer(config, "main"); config_add_layer(config, "control:C"); config_add_layer(config, "meta:M"); @@ -217,121 +106,28 @@ static void config_init(struct config *config) config_add_layer(config, "altgr:G"); config_add_layer(config, "alt:A"); - config->default_layout = config_create_layout(config, "main"); -} - -/* - * Consumes a string of the form name:type and creates a layer - * within the provided config. If the layer already exists - * then its attributes will remain unchanged. - */ -static int config_add_layer(struct config *config, const char *str) -{ - uint8_t mods; - char *name; - char *type; - - static char s[512]; - - strncpy(s, str, sizeof(s)-1); - - name = strtok(s, ":"); - type = strtok(NULL, ":"); - - if (config_lookup_layer(config, name) != -1) { - err("%s already exists, cannot redefine it.", name); - return -1; - } - - /* Create the layer. */ - - if (type) { - if (!strcmp(type, "layout")) - config_create_layout(config, name); - else if (!parse_modset(type, &mods)) - config_create_layer(config, name, mods); - else { - err("WARNING: \"%s\" is not a valid layer type (must be \"layout\" or a valid modifier set).\n", type); - - return -1; - } - } else - config_create_layer(config, name, 0); - - return 0; -} - -int config_execute_expression(struct config *config, const char *exp) -{ - char *d, *layer, *mapping; - size_t len = strlen(exp); - static char s[1024]; - - assert(len < sizeof(s)-1); - memcpy(s, exp, len+1); - - if (len > 1 && s[0] == '[' && s[len-1] == ']') { - s[len-1] = 0; - return config_add_layer(config, s+1); - } - - d = strchr(s, '.'); - if (d) { - *d = 0; - - layer = s; - mapping = d+1; - } else { - layer = "main"; - mapping = s; + km = config->layer_table.layers[0].keymap; + for (i = 0; i < 256; i++) { + km[i].op = OP_KEYCODE; + km[i].args[0].code = i; } + for (i = 0; i < MAX_MOD; i++) { + struct descriptor *ent1 = &km[modifier_table[i].code1]; + struct descriptor *ent2 = &km[modifier_table[i].code2]; - return config_add_mapping(config, layer, mapping); -} - -/* - * Consumes a string of the form `<key> = <descriptor>` and adds the - * corresponding mapping to the layer within the supplied config. - * The layer must exist or else be a valid modifier set. - */ -static int config_add_mapping(struct config *config, const char *layer_name, const char *str) -{ - uint8_t code1, code2; - char *key, *descstr; - int idx; - struct descriptor d; - - static char s[1024]; - - strncpy(s, str, sizeof(s)-1); + int idx = layer_table_lookup(&config->layer_table, modifier_table[i].name); - if (parse_kvp(s, &key, &descstr) < 0) { - err("Invalid key value pair."); - return -1; - } + assert(idx != -1); - if (lookup_keycodes(key, &code1, &code2) < 0) { - err("%s is not a valid key.", key); - return -1; - } + ent1->op = OP_LAYER; + ent1->args[0].idx = idx; - idx = config_lookup_layer(config, layer_name); - if(idx == -1) { - err("%s is not a valid layer", layer_name); - return -1; + ent2->op = OP_LAYER; + ent2->args[0].idx = idx; } - if (parse_descriptor(descstr, &d, config) < 0) - return -1; - - if (code1) - config->layers[idx].keymap[code1] = d; - - if (code2) - config->layers[idx].keymap[code2] = d; - - return 0; + config->layer_table.layers[0].flags = LF_ACTIVE; } static int parse(struct config *config, char *str, const char *path) @@ -353,9 +149,6 @@ static int parse(struct config *config, char *str, const char *path) if (!strcmp(section->name, "ids")) continue; - if (config_lookup_layer(config, section->name) != -1) - continue; - if (config_add_layer(config, section->name) < 0) fprintf(stderr, "ERROR %s:%zd: %s\n", path, section->lnum, errstr); } @@ -374,7 +167,7 @@ static int parse(struct config *config, char *str, const char *path) for (j = 0; j < section->nr_entries;j++) { struct ini_entry *ent = §ion->entries[j]; - if (config_add_mapping(config, name, ent->line) < 0) + if (config_add_binding(config, name, ent->line) < 0) fprintf(stderr, "ERROR %s:%zd: %s\n", path, ent->lnum, errstr); } } @@ -403,7 +196,7 @@ int config_parse(struct config *config, const char *path) static int config_check_match(const char *path, uint16_t vendor, uint16_t product) { char line[32]; - int line_sz = 0; + size_t line_sz = 0; int fd = open(path, O_RDONLY); if (fd < 0) { @@ -452,8 +245,10 @@ static int config_check_match(const char *path, uint16_t vendor, uint16_t produc if (line[0] != '#') { ret = sscanf(id, "%hx:%hx", &v, &p); - if (ret == 2 && v == vendor && p == product) + if (ret == 2 && v == vendor && p == product) { + close(fd); return wildcard ? 0 : 2; + } } } } @@ -470,6 +265,7 @@ static int config_check_match(const char *path, uint16_t vendor, uint16_t produc } end: + close(fd); return wildcard; } diff --git a/src/config.h b/src/config.h index 1610595b..61a69b5a 100644 --- a/src/config.h +++ b/src/config.h @@ -1,29 +1,21 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ #ifndef CONFIG_H #define CONFIG_H #define MAX_DEVICE_IDS 32 #define MAX_CONFIG_NAME 256 -#define MAX_LAYERS 32 -#define MAX_MACROS 32 #include "layer.h" struct config { - struct layer layers[MAX_LAYERS]; - struct macro macros[MAX_MACROS]; - - /* The index of the default layout within the config layer table. */ - int default_layout; - - size_t nr_layers; + struct layer_table layer_table; }; -int config_lookup_layer(struct config *config, const char *name); -int config_create_layer(struct config *config, const char *name, uint8_t mods); - -int config_execute_expression(struct config *config, const char *str); const char *config_find_path(const char *dir, uint16_t vendor, uint16_t product); - int config_parse(struct config *config, const char *path); #endif diff --git a/src/descriptor.c b/src/descriptor.c index c360f819..d1a77618 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -1,25 +1,8 @@ -/* Copyright © 2019 Raheman Vaiya. - * - * 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 (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. +/* + * keyd - A key remapping daemon. * - * 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. + * © 2019 Raheman Vaiya (see also: LICENSE). */ - #include <ctype.h> #include <string.h> #include <assert.h> @@ -27,74 +10,174 @@ #include "descriptor.h" #include "layer.h" +#include "keyd.h" #include "keys.h" #include "error.h" #include "config.h" #define MAX_ARGS 5 +/* + * Parse a value of the form 'key = value'. The value may contain = + * and the key may itself be = as a special case. The returned + * values are pointers within the modified input string. + */ + +static int parse_kvp(char *s, char **key, char **value) +{ + char *last_space = NULL; + char *c = s; + + /* Allow the first character to be = as a special case. */ + if (*c == '=') + c++; + + while (*c) { + switch (*c) { + case '=': + if (last_space) + *last_space = 0; + else + *c = 0; + c++; + + while (*c && *c == ' ') + c++; + + if (!*s) + return -1; + + *key = s; + *value = c; + + return 0; + case ' ': + if (!last_space) + last_space = c; + break; + default: + last_space = NULL; + break; + } + + c++; + } + + return -1; +} + +/* modifies the input string */ static int parse_fn(char *s, char **name, char *args[MAX_ARGS], size_t *nargs) { - size_t len = strlen(s); - size_t n = 0; - char *arg, *c; - - if (len == 0 || s[len-1] != ')') - return -1; + char *c, *arg; - s[len-1] = 0; + c = s; + while (*c && *c != '(') + c++; - if (!(c = strchr(s, '('))) + if (!*c) return -1; - *c = '\0'; *name = s; - s = c + 1; + *c++ = 0; + + while (*c == ' ') + c++; + + *nargs = 0; + arg = c; + while (1) { + int plvl = 0; + + while (*c) { + switch (*c) { + case '\\': + if (*(c+1)) { + c+=2; + continue; + } + break; + case '(': + plvl++; + break; + case ')': + plvl--; + + if (plvl == -1) + goto exit; + break; + case ',': + if (plvl == 0) + goto exit; + break; + } + + c++; + } +exit: + + if (!*c) + return -1; - for (arg = strtok(s, ","); arg; arg = strtok(NULL, ",")) { - while (isspace(arg[0])) - arg++; + assert(*nargs < MAX_ARGS); + args[(*nargs)++] = arg; - if (n >= MAX_ARGS) + if (*c == ')') { + *c = 0; return 0; + } - args[n++] = arg; + *c++ = 0; + while (*c == ' ') + c++; + arg = c; } +} + +static uint8_t parse_code(const char *s) +{ + size_t i; - *nargs = n; + for (i = 0; i < 256; i++) { + const struct keycode_table_ent *ent = &keycode_table[i]; + + if ((ent->name && !strcmp(ent->name, s)) || + (ent->alt_name && !strcmp(ent->alt_name, s))) + return i; + } return 0; } -static int parse_key_sequence(const char *s, struct key_sequence *seq) +static int parse_sequence(const char *s, uint8_t *codep, uint8_t *modsp) { const char *c = s; - size_t code; + size_t i; if (!*s) return -1; - seq->mods = 0; + uint8_t mods = 0; while (c[1] == '-') { switch (*c) { case 'C': - seq->mods |= MOD_CTRL; + mods |= MOD_CTRL; break; case 'M': - seq->mods|= MOD_SUPER; + mods |= MOD_SUPER; break; case 'A': - seq->mods |= MOD_ALT; + mods |= MOD_ALT; break; case 'S': - seq->mods |= MOD_SHIFT; + mods |= MOD_SHIFT; break; case 'G': - seq->mods |= MOD_ALT_GR; + mods |= MOD_ALT_GR; break; default: return -1; @@ -104,21 +187,30 @@ static int parse_key_sequence(const char *s, struct key_sequence *seq) c += 2; } - for (code = 0; code < MAX_KEYS; code++) { - const struct keycode_table_ent *ent = &keycode_table[code]; + for (i = 0; i < 256; i++) { + const struct keycode_table_ent *ent = &keycode_table[i]; if (ent->name) { if (ent->shifted_name && !strcmp(ent->shifted_name, c)) { - seq->mods |= MOD_SHIFT; - seq->code = code; + mods |= MOD_SHIFT; + + if (modsp) + *modsp = mods; + + if (codep) + *codep = i; return 0; } else if (!strcmp(ent->name, c) || (ent->alt_name && !strcmp(ent->alt_name, c))) { - seq->code = code; + if (modsp) + *modsp = mods; + + if (codep) + *codep = i; return 0; } @@ -128,34 +220,30 @@ static int parse_key_sequence(const char *s, struct key_sequence *seq) return -1; } -static int parse_macro_fn(struct macro *macro, char *s) +static int do_parse_macro(struct macro *macro, char *s) { - size_t len = strlen(s); - char *tok; size_t sz = 0; - - if (strstr(s, "macro(") != s || s[len-1] != ')') - return -1; - - s[len-1] = 0; + uint8_t code, mods; macro->sz = 0; - for (tok = strtok(s + 6, " "); tok; tok = strtok(NULL, " ")) { + for (tok = strtok(s, " "); tok; tok = strtok(NULL, " ")) { struct macro_entry ent; - len = strlen(tok); + size_t len = strlen(tok); - if (!parse_key_sequence(tok, &ent.data.sequence)) { + if (!parse_sequence(tok, &code, &mods)) { assert(sz < MAX_MACRO_SIZE); ent.type = MACRO_KEYSEQUENCE; + ent.code = code; + ent.mods = mods; macro->entries[sz++] = ent; } else if (len > 1 && tok[len-2] == 'm' && tok[len-1] == 's') { int len = atoi(tok); ent.type = MACRO_TIMEOUT; - ent.data.timeout = len; + ent.timeout = len; assert(sz < MAX_MACRO_SIZE); macro->entries[sz++] = ent; @@ -167,11 +255,13 @@ static int parse_macro_fn(struct macro *macro, char *s) char *key; for (key = strtok_r(tok, "+", &saveptr); key; key = strtok_r(NULL, "+", &saveptr)) { - if (parse_key_sequence(key, &ent.data.sequence) < 0) { + if (parse_sequence(key, &code, &mods) < 0) { return -1; } ent.type = MACRO_HOLD; + ent.code = code; + ent.mods = mods; assert(sz < MAX_MACRO_SIZE); macro->entries[sz++] = ent; @@ -182,16 +272,28 @@ static int parse_macro_fn(struct macro *macro, char *s) macro->entries[sz++] = ent; } else { for (c = tok; *c; c++) { - char s[2]; - - s[0] = *c; - s[1] = 0; + uint8_t code, mods; + char s[32]; + + switch (*c) { + case '\n': + strcpy(s, "enter"); + break; + case '\t': + strcpy(s, "tab"); + break; + default: + s[0] = *c; + s[1] = 0; + break; + } - if (parse_key_sequence(s, &ent.data.sequence) < 0) { + if (parse_sequence(s, &code, &mods) < 0) return -1; - } ent.type = MACRO_KEYSEQUENCE; + ent.code = code; + ent.mods = mods; assert(sz < MAX_MACRO_SIZE); macro->entries[sz++] = ent; @@ -204,116 +306,368 @@ static int parse_macro_fn(struct macro *macro, char *s) return 0; } -static int config_get_free_macro_idx(struct config *config) +static size_t escape(char *s) +{ + int i = 0; + int n = 0; + + for (i = 0; s[i]; i++) { + if (s[i] == '\\') { + switch (s[i+1]) { + case 'n': + s[n++] = '\n'; + break; + case 't': + s[n++] = '\t'; + break; + case '\\': + s[n++] = '\\'; + break; + case ')': + s[n++] = ')'; + break; + case '(': + s[n++] = '('; + break; + case 0: + s[n] = 0; + return n; + default: + s[n++] = '\\'; + s[n++] = s[i+1]; + break; + } + + i++; + } else { + s[n++] = s[i]; + } + } + + s[n] = 0; + + return n; +} + +static int parse_macro(const char *exp, struct macro *macro) +{ + char s[MAX_MACROEXP_LEN]; + int len = strlen(exp); + + if (len >= MAX_MACROEXP_LEN) { + err("macro exceeds maximum macro length (%d)", MAX_MACROEXP_LEN); + return -1; + } + + strcpy(s, exp); + escape(s); + len = strlen(s); + + if (!parse_sequence(s, NULL, NULL)) { + return do_parse_macro(macro, s); + } else if (strstr(s, "macro(") == s && s[len-1] == ')') { + s[len-1] = 0; + + return do_parse_macro(macro, s+6); + } else + return -1; +} + +/* Return up to two keycodes associated with the given name. */ +static int lookup_keycodes(const char *name, uint8_t *code1, uint8_t *code2) { size_t i; - /* Scan the macro table for a free entry. */ - for (i = 0; i < sizeof(config->macros)/sizeof(config->macros[0]); i++) - if (!config->macros[i].sz) - return i; + /* + * If the name is a modifier like 'control' we associate it with both + * corresponding key codes (e.g 'rightcontrol'/'leftcontrol') + */ + for (i = 0; i < MAX_MOD; i++) { + const struct modifier_table_ent *mod = &modifier_table[i]; + + if (!strcmp(mod->name, name)) { + *code1 = mod->code1; + *code2 = mod->code2; + + return 0; + } + } + + for (i = 0; i < 256; i++) { + const struct keycode_table_ent *ent = &keycode_table[i]; + + if (ent->name && + (!strcmp(ent->name, name) || + (ent->alt_name && !strcmp(ent->alt_name, name)))) { + *code1 = i; + *code2 = 0; + + return 0; + } + } + + return -1; +} + +int layer_table_lookup(const struct layer_table *lt, const char *name) +{ + size_t i; - die("Max macros exceeded\n"); + for (i = 0; i < lt->nr; i++) + if (!strcmp(lt->layers[i].name, name)) + return i; return -1; } /* - * Modifies the input string, consumes a function which which is used for - * resolving layer names as required. + * Consumes a string of the form `[<layer>.]<key> = <descriptor>` and adds the + * mapping to the corresponding layer in the layer_table. */ -int parse_descriptor(char *s, - struct descriptor *desc, - struct config *config) + +int layer_table_add_entry(struct layer_table *lt, const char *exp) +{ + uint8_t code1, code2; + char *keystr, *descstr, *c, *s; + char *layername = "main"; + struct descriptor d; + struct layer *layer; + int idx; + + static char buf[MAX_EXP_LEN]; + + if (strlen(exp) >= MAX_EXP_LEN) { + err("%s exceeds maximum expression length (%d)", exp, MAX_EXP_LEN); + return -1; + } + + strcpy(buf, exp); + s = buf; + + if ((c = strchr(s, '.'))) { + layername = s; + *c = 0; + s = c+1; + } + + if (parse_kvp(s, &keystr, &descstr) < 0) { + err("Invalid key value pair."); + return -1; + } + + idx = layer_table_lookup(lt, layername); + + if (idx == -1) { + err("%s is not a valid layer", layername); + return -1; + } + + layer = <->layers[idx]; + + if (lookup_keycodes(keystr, &code1, &code2) < 0) { + err("%s is not a valid key.", keystr); + return -1; + } + + if (parse_descriptor(descstr, &d, lt) < 0) + return -1; + + if (code1) + layer->keymap[code1] = d; + + if (code2) + layer->keymap[code2] = d; + + return 0; +} + +/* + * Populate the provided layer described by `desc`, which is a string of the + * form "<layer>[:<type>]". The provided layer table is used to look up + * constituent layers in the case of a composite descriptor string + * (e.g "layer1+layer2"). + */ + +int create_layer(struct layer *layer, const char *desc, const struct layer_table *lt) { - struct key_sequence seq; + uint8_t mods; + char *name; + char *modstr; + + static char s[MAX_LAYER_NAME_LEN]; - char *fn; + if (strlen(desc) >= sizeof(s)) { + err("%s exceeds max layer length (%d)", desc, MAX_LAYER_NAME_LEN); + return -1; + } + + strcpy(s, desc); + + name = strtok(s, ":"); + modstr = strtok(NULL, ":"); + + strcpy(layer->name, name); + + if (strchr(name, '+')) { + char *layern; + int n = 0; + int layers[MAX_COMPOSITE_LAYERS]; + + if (modstr) { + err("composite layers cannot have a modifier set."); + return -1; + } + + for (layern = strtok(name, "+"); layern; layern = strtok(NULL, "+")) { + int idx = layer_table_lookup(lt, layern); + if (idx < 0) { + err("%s is not a valid layer", layern); + return -1; + } + + if (n >= MAX_COMPOSITE_LAYERS) { + err("max composite layers (%d) exceeded", MAX_COMPOSITE_LAYERS); + return -1; + } + + layers[n++] = idx; + } + + layer->type = LT_COMPOSITE; + layer->nr_layers = n; + memcpy(layer->layers, layers, sizeof(layer->layers)); + } else if (modstr && !parse_modset(modstr, &mods)) { + layer->type = LT_NORMAL; + layer->mods = mods; + } else { + if (modstr) + fprintf(stderr, "WARNING: \"%s\" is not a valid modifier set, ignoring\n", modstr); + + layer->type = LT_NORMAL; + layer->mods = 0; + } + + + dbg("created [%s] from \"%s\"", layer->name, desc); + return 0; +} + +/* + * Modifies the input string. Layers names within the descriptor + * are resolved using the provided layer table. + */ +int parse_descriptor(char *s, + struct descriptor *d, + struct layer_table *lt) +{ + char *fn = NULL; char *args[MAX_ARGS]; size_t nargs = 0; - int macro_idx = config_get_free_macro_idx(config); + struct macro macro; + uint8_t code; + int idx; - if (!parse_key_sequence(s, &seq)) { - desc->op = OP_KEYSEQ; + if ((code = parse_code(s))) { + d->op = OP_KEYCODE; + d->args[0].code = code; - if (keycode_to_mod(seq.code)) + /* TODO: fixme. */ + if (keycode_to_mod(code)) fprintf(stderr, "WARNING: mapping modifier keycodes directly may produce unintended results, you probably want layer(<modifier name>) instead\n"); + } else if (!strcmp(s, "noop")) { + d->op = OP_UNDEFINED; + } else if (!parse_macro(s, ¯o)) { + if (lt->nr_macros >= MAX_MACROS) { + err("max macros (%d), exceeded", MAX_MACROS); + return -1; + } - desc->args[0].sequence = seq; - } else if (!parse_macro_fn(&config->macros[macro_idx], s)) { - desc->op = OP_MACRO; - desc->args[0].idx = macro_idx; - } else if (!parse_fn(s, &fn, args, &nargs)) { - int layer_idx; + d->op = OP_MACRO; + + lt->macros[lt->nr_macros] = macro; + d->args[0].idx = lt->nr_macros; + lt->nr_macros++; + } else if (!parse_fn(s, &fn, args, &nargs)) { if (!strcmp(fn, "layer")) - desc->op = OP_LAYER; - else if (!strcmp(fn, "reset")) - desc->op = OP_RESET; + d->op = OP_LAYER; else if (!strcmp(fn, "toggle")) - desc->op = OP_TOGGLE; - else if (!strcmp(fn, "layout")) - desc->op = OP_LAYOUT; + d->op = OP_TOGGLE; else if (!strcmp(fn, "oneshot")) - desc->op = OP_ONESHOT; + d->op = OP_ONESHOT; else if (!strcmp(fn, "overload")) - desc->op = OP_OVERLOAD; - else if (!strcmp(fn, "swap")) - desc->op = OP_SWAP; - else { - err("\"%s\" is not a valid action or key sequence.", s); + d->op = OP_OVERLOAD; + else if (!strcmp(fn, "swap")) { + d->op = OP_SWAP; + } else if (!strcmp(fn, "timeout")) { + d->op = OP_TIMEOUT; + } else { + err("\"%s\" is not a valid action or macro.", s); return -1; } - if (desc->op == OP_RESET) - return 0; - if (nargs == 0) { err("%s requires one or more arguments.", fn); return -1; } - layer_idx = config_lookup_layer(config, args[0]); - if (layer_idx == -1) { - uint8_t mods; + if (d->op == OP_TIMEOUT) { + struct timeout *timeout; - /* Autovivify modifier layers. */ - if (!parse_modset(args[0], &mods)) { - layer_idx = config_create_layer(config, args[0], mods); - assert(layer_idx > 0); - } else { - err("\"%s\" is not a valid layer", args[0]); + if (nargs != 3) { + err("timeout requires 3 arguments."); return -1; } + + d->op = OP_TIMEOUT; + d->args[0].idx = lt->nr_timeouts; + + if (lt->nr_timeouts >= MAX_TIMEOUTS) { + err("max timeouts (%d) exceeded", MAX_TIMEOUTS); + return -1; + } + + timeout = <->timeouts[lt->nr_timeouts]; + + if (parse_descriptor(args[0], &timeout->d1, lt) < 0) + return -1; + + if (parse_descriptor(args[2], &timeout->d2, lt) < 0) + return -1; + + timeout->timeout = atoi(args[1]); + + lt->nr_timeouts++; + return 0; } - if (desc->op == OP_LAYOUT && !config->layers[layer_idx].is_layout) { - err("\"%s\" must be a valid layout.", args[0]); + idx = layer_table_lookup(lt, args[0]); + + if (idx == -1) { + err("%s is not a valid layer.", args[0]); return -1; } - desc->args[0].idx = layer_idx; - desc->args[1].sequence = (struct key_sequence){0}; + d->args[0].idx = idx; + d->args[1].idx = -1; if (nargs > 1) { - int ret; - ret = parse_key_sequence(args[1], &seq); - - if (ret < 0) { - err("\"%s\" is not a valid key sequence", args[1]); + if (lt->nr_macros >= MAX_MACROS) { + err("max macros (%d), exceeded", MAX_MACROS); return -1; } - desc->args[1].sequence = seq; - } + if (parse_macro(args[1], <->macros[lt->nr_macros]) < 0) { + err("\"%s\" is not a valid macro", args[1]); + return -1; + } - if (desc->op == OP_OVERLOAD && nargs == 3) { - desc->op = OP_OVERLOAD_TIMEOUT; - desc->args[2].timeout = atoi(args[2]); + d->args[1].idx = lt->nr_macros; + lt->nr_macros++; } } else { - err("\"%s\" is not a valid key sequence or action.", s); + err("invalid key or action"); return -1; } diff --git a/src/descriptor.h b/src/descriptor.h index 0fb3f553..3f319603 100644 --- a/src/descriptor.h +++ b/src/descriptor.h @@ -1,56 +1,34 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ #ifndef DESCRIPTOR_H #define DESCRIPTOR_H #include <stdint.h> #include <stddef.h> -struct config; +struct layer; +struct layer_table; -#define MAX_MACRO_SIZE 128 +#define MAX_LAYERS 32 +#define MAX_EXP_LEN 512 +#define MAX_MACROEXP_LEN 512 +#define MAX_DESCRIPTORS 32 enum op { OP_UNDEFINED, - OP_KEYSEQ, + OP_KEYCODE, OP_ONESHOT, OP_SWAP, OP_LAYER, OP_OVERLOAD, - OP_OVERLOAD_TIMEOUT, OP_TOGGLE, - OP_RESET, - - OP_LAYOUT, OP_MACRO, -}; - -struct key_sequence { - uint8_t mods; - uint8_t code; -}; - -struct macro_entry { - enum { - MACRO_KEYSEQUENCE, - MACRO_HOLD, - MACRO_RELEASE, - MACRO_TIMEOUT - } type; - - union { - struct key_sequence sequence; - uint32_t timeout; - } data; -}; - -/* - * A series of key sequences optionally punctuated by - * timeouts - */ -struct macro { - struct macro_entry entries[MAX_MACRO_SIZE]; - size_t sz; + OP_TIMEOUT }; /* Describes the intended purpose of a key. */ @@ -59,9 +37,8 @@ struct descriptor { enum op op; union { - struct key_sequence sequence; - - uint16_t idx; + uint8_t code; + int16_t idx; uint16_t sz; uint16_t timeout; } args[3]; @@ -69,12 +46,15 @@ struct descriptor { /* * Creates a descriptor from the given string which describes a key action. - * Layer and macro indices are relative to the provided config. Potentially - * modifies the input string in the process. + * Potentially modifying the input string in the process. */ int parse_descriptor(char *s, - struct descriptor *desc, - struct config *config); + struct descriptor *d, + struct layer_table *lt); + +int layer_table_add_entry(struct layer_table *lt, const char *exp); +int layer_table_lookup(const struct layer_table *lt, const char *name); +int create_layer(struct layer *layer, const char *desc, const struct layer_table *lt); #endif diff --git a/src/device.c b/src/device.c new file mode 100644 index 00000000..c8cbee02 --- /dev/null +++ b/src/device.c @@ -0,0 +1,258 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ +#include "device.h" +#include "keys.h" + +#include <stdio.h> +#include <pthread.h> +#include <string.h> +#include <sys/types.h> +#include <dirent.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <linux/input.h> +#include <sys/inotify.h> + +/* + * Abstract away evdev and inotify. + * + * We could make this cleaner by creating a single file descriptor via epoll + * but this would break FreeBSD compatibility without a dedicated kqueue + * implementation. A thread based approach was also considered, + * but inter-thread communication adds too much overhead (~100us). + * + * Overview: + * + * A 'devmon' is a file descriptor which can be created with devmon_create() + * and subsequently monitored for new devices read with devmon_read_device(). + * + * A 'device' always corresponds to a keyboard from which activity can be + * monitored with device->fd and events subsequently read using + * device_read_event(). + * + * If the event returned by device_read_event() is of type DEV_REMOVED then the + * corresponding device should be considered invalid by the caller. + */ + +static int is_keyboard(int fd) +{ + uint32_t keymask; + + if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof keymask), &keymask) < 0) { + perror("ioctl"); + return 0; + } + + /* The first 31 bits correspond to [KEY_ESC-KEY_S] */ + return keymask == 0xFFFFFFFE; +} + +static int device_init(const char *path, struct device *dev) +{ + int fd; + if ((fd = open(path, O_RDONLY | O_NONBLOCK, 0600)) < 0) { + fprintf(stderr, "failed to open %s\n", path); + return -1; + } + + if (is_keyboard(fd)) { + struct input_id info; + + if (ioctl(fd, EVIOCGNAME(sizeof(dev->name)), dev->name) == -1) { + perror("ioctl EVIOCGNAME"); + return -1; + } + + if (ioctl(fd, EVIOCGID, &info) == -1) { + perror("ioctl EVIOCGID"); + return -1; + } + + strncpy(dev->path, path, sizeof(dev->path)-1); + dev->path[sizeof(dev->path)-1] = 0; + + dev->fd = fd; + dev->vendor_id = info.vendor; + dev->product_id = info.product; + + return 0; + } else { + close(fd); + return -1; + } + + return -1; +} + +struct device_worker { + pthread_t tid; + char path[1024]; + struct device dev; +}; + +static void *device_scan_worker(void *arg) +{ + struct device_worker *w = (struct device_worker *)arg; + if (device_init(w->path, &w->dev) < 0) + return NULL; + + return &w->dev; +} + +int device_scan(struct device devices[MAX_DEVICES]) +{ + int i; + struct device_worker workers[MAX_DEVICES]; + struct dirent *ent; + DIR *dh = opendir("/dev/input/"); + int n = 0, ndevs; + + if (!dh) { + perror("opendir /dev/input"); + exit(-1); + } + + while((ent = readdir(dh))) { + if (!strncmp(ent->d_name, "event", 5)) { + assert(n < MAX_DEVICES); + struct device_worker *w = &workers[n++]; + + snprintf(w->path, sizeof(w->path), "/dev/input/%s", ent->d_name); + pthread_create(&w->tid, NULL, device_scan_worker, w); + } + } + + ndevs = 0; + for(i = 0; i < n; i++) { + struct device *d; + pthread_join(workers[i].tid, (void**)&d); + + if (d) + devices[ndevs++] = workers[i].dev; + } + + closedir(dh); + return ndevs; +} + +/* + * NOTE: Only a single devmon fd may exist. Implementing this properly + * would involve bookkeeping state for each fd, but this is + * unnecessary for our use. + */ +int devmon_create() +{ + static int init = 0; + assert(!init); + init = 1; + + int fd = inotify_init1(IN_NONBLOCK); + if (fd < 0) { + perror("inotify"); + exit(-1); + } + + int wd = inotify_add_watch(fd, "/dev/input/", IN_CREATE); + if (wd < 0) { + perror("inotify"); + exit(-1); + } + + return fd; +} + +/* + * A non blocking call which returns any devices available on the provided + * monitor descriptor. The return value should not be freed or modified by the calling + * code. Returns NULL if no devices are available. + */ +struct device *devmon_read_device(int fd) +{ + static struct device ret; + + static char buf[4096]; + static int buf_sz = 0; + static char *ptr = buf; + + while (1) { + char path[1024]; + struct inotify_event *ev; + + if (ptr >= (buf+buf_sz)) { + ptr = buf; + buf_sz = read(fd, buf, sizeof(buf)); + if (buf_sz == -1) { + buf_sz = 0; + return NULL; + } + } + + ev = (struct inotify_event*)ptr; + ptr += sizeof(struct inotify_event) + ev->len; + + snprintf(path, sizeof path, "/dev/input/%s", ev->name); + + if (!device_init(path, &ret)) + return &ret; + } +} + +int device_grab(struct device *dev) +{ + return ioctl(dev->fd, EVIOCGRAB, (void *) 1); +} + +int device_ungrab(struct device *dev) +{ + return ioctl(dev->fd, EVIOCGRAB, (void *) 0); +} + +/* + * Read a device event from the given device or return + * NULL if none are available (may happen in the + * case of a spurious wakeup). + */ +struct device_event *device_read_event(struct device *dev) +{ + struct input_event ev; + static struct device_event devev; + + if (read(dev->fd, &ev, sizeof(ev)) < 0) { + if (errno == EAGAIN) { + return NULL; + } else { + devev.type = DEV_REMOVED; + return &devev; + } + } + + if (ev.type != EV_KEY || ev.value == 2) + return NULL; + + if (ev.code >= 256) { + if (ev.code == BTN_LEFT) + ev.code = KEY_LEFT_MOUSE; + else if (ev.code == BTN_MIDDLE) + ev.code = KEY_RIGHT_MOUSE; + else if (ev.code == BTN_RIGHT) + ev.code = KEY_MIDDLE_MOUSE; + else if (ev.code == BTN_SIDE) + ev.code = KEY_MOUSE_1; + else if (ev.code == BTN_EXTRA) + ev.code = KEY_MOUSE_2; + } + + devev.type = DEV_KEYBOARD; + devev.code = ev.code; + devev.pressed = ev.value; + + return &devev; +} diff --git a/src/device.h b/src/device.h new file mode 100644 index 00000000..596b46c4 --- /dev/null +++ b/src/device.h @@ -0,0 +1,52 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ +#ifndef DEVICE_H +#define DEVICE_H + +#include <stdint.h> + +#define DEV_MOUSE 0 +#define DEV_KEYBOARD 1 +#define DEV_REMOVED 2 + +#define MAX_DEVICES 64 + +struct device { + /* + * A file descriptor that can be used to monitor events subsequently read with + * device_read_event(). + */ + int fd; + + uint16_t product_id; + uint16_t vendor_id; + char name[64]; + char path[256]; + + /* Reserved for the user. */ + void *data; +}; + +struct device_event { + uint8_t type; + + uint8_t code; + uint8_t pressed; + uint8_t x; + uint8_t y; +}; + + +struct device_event *device_read_event(struct device *dev); + +int device_scan(struct device devices[MAX_DEVICES]); +int device_grab(struct device *dev); +int device_ungrab(struct device *dev); + +int devmon_create(); +struct device *devmon_read_device(int fd); + +#endif diff --git a/src/error.c b/src/error.c deleted file mode 100644 index 3680f307..00000000 --- a/src/error.c +++ /dev/null @@ -1,40 +0,0 @@ -/* Copyright © 2019 Raheman Vaiya. - * - * 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 (including the next - * paragraph) 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. - */ - -#include <stdio.h> -#include <unistd.h> -#include <stdarg.h> -#include <stdlib.h> - -char errstr[1024]; - -void _die(char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - - vfprintf(stderr, fmt, args); - va_end(args); - fprintf(stderr, "\n"); - exit(-1); -} - diff --git a/src/error.h b/src/error.h deleted file mode 100644 index 420b2304..00000000 --- a/src/error.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef ERROR_H -#define ERROR_H - -#include <stdio.h> - -extern char errstr[1024]; -void _die(char *fmt, ...); - -#define err(fmt, ...) snprintf(errstr, sizeof(errstr), fmt, ##__VA_ARGS__); -#define die(fmt, ...) _die("%s:%d: "fmt, __FILE__, __LINE__, ## __VA_ARGS__); - - -#endif diff --git a/src/evdev.c b/src/evdev.c deleted file mode 100644 index ae4b2a1b..00000000 --- a/src/evdev.c +++ /dev/null @@ -1,172 +0,0 @@ -#include "keyd.h" -#include <stdio.h> -#include <assert.h> -#include <fcntl.h> -#include <string.h> -#include <stdint.h> -#include <sys/ioctl.h> -#include <pthread.h> -#include <dirent.h> -#include <linux/input-event-codes.h> -#include <linux/input.h> -#include <stdlib.h> -#include <unistd.h> - -int evdev_is_keyboard(const char *devnode) -{ - int fd = open(devnode, O_RDONLY); - if (fd < 0) { - fprintf(stderr, "is_keyboard: Failed to open %s\n", devnode); - return 0; - } - - uint8_t keymask[(KEY_CNT+7)/8]; - - if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof keymask), keymask) < 0) { - perror("ioctl"); - fprintf(stderr, "WARNING: Failed to retrieve key bit for %s\n", devnode); - return 0; - } - - int has_a = keymask[KEY_A/8] & (0x01 << (KEY_A%8)); - int has_d = keymask[KEY_D/8] & (0x01 << (KEY_D%8)); - - close(fd); - - return has_a && has_d; -} - - -struct worker { - pthread_t tid; - char path[1024]; - int result; -} workers[256]; - -static void *is_keyboard_worker(void *worker) -{ - struct worker *w = (struct worker*) worker; - w->result = evdev_is_keyboard(w->path); -} - -/* - * Note: the returned array is owned by the function and - * should not be freed by the caller. Successive invocations - * invalidate devs. - */ - -int evdev_get_keyboard_nodes(char **devs, int *ndevs) -{ - struct dirent *ent; - int nkbds = 0; - int n = 0; - - DIR *dh = opendir("/dev/input/"); - if (!dh) { - perror("opendir /dev/input"); - exit(-1); - } - - n = 0; - - while((ent = readdir(dh))) { - char path[1024]; - if (strstr(ent->d_name, "event") == ent->d_name) { - assert(n < sizeof(workers)/sizeof(workers[0])); - struct worker *w = &workers[n++]; - snprintf(w->path, sizeof(w->path), "/dev/input/%s", ent->d_name); - pthread_create(&w->tid, NULL, is_keyboard_worker, w); - } - } - - closedir(dh); - - *ndevs = 0; - while(n--) { - pthread_join(workers[n].tid, NULL); - - if(workers[n].result) - devs[(*ndevs)++] = workers[n].path; - } -} - -const char *evdev_device_name(const char *devnode) -{ - static char name[256]; - - int fd = open(devnode, O_RDONLY); - if (fd < 0) { - perror("open name"); - return NULL; - } - - if (ioctl(fd, EVIOCGNAME(sizeof(name)), &name) == -1) - return NULL; - - close(fd); - return name; -} - -int evdev_device_id(const char *devnode, uint16_t *vendor, uint16_t *product) -{ - struct input_id info; - - int fd = open(devnode, O_RDONLY); - if (fd < 0) { - perror("open"); - return -1; - } - - if (ioctl(fd, EVIOCGID, &info) == -1) { - perror("ioctl"); - return -1; - } - - close(fd); - - *vendor = info.vendor; - *product = info.product; - - return 0; -} - -int evdev_grab_keyboard(int fd) -{ - size_t i; - struct input_event ev; - uint8_t state[KEY_MAX / 8 + 1]; - - /* - * await neutral key state to ensure any residual - * key up events propagate. - */ - - while (1) { - int n = 0; - memset(state, 0, sizeof(state)); - - if (ioctl(fd, EVIOCGKEY(sizeof state), state) < 0) { - perror("ioctl EVIOCGKEY"); - return -1; - } - - for (i = 0; i < KEY_MAX; i++) { - if ((state[i/8] >> (i % 8)) & 0x1) - n++; - } - - if (n == 0) - break; - } - - if (ioctl(fd, EVIOCGRAB, (void *) 1) < 0) { - perror("EVIOCGRAB"); - return -1; - } - - /* drain any input events before the grab (assumes NONBLOCK is set on the fd) */ - while (read(fd, &ev, sizeof(ev)) > 0) { - } - - return 0; -} diff --git a/src/ini.c b/src/ini.c index f2d7589d..603cd7c0 100644 --- a/src/ini.c +++ b/src/ini.c @@ -1,25 +1,8 @@ -/* Copyright © 2019 Raheman Vaiya. +/* + * keyd - A key remapping daemon. * - * 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 (including the next - * paragraph) 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. + * © 2019 Raheman Vaiya (see also: LICENSE). */ - #include <ctype.h> #include <string.h> #include <assert.h> diff --git a/src/ini.h b/src/ini.h index a2d826d1..a9bc6ba3 100644 --- a/src/ini.h +++ b/src/ini.h @@ -1,3 +1,8 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ #ifndef INI_H #define INI_H diff --git a/src/ipc.c b/src/ipc.c new file mode 100644 index 00000000..5e216e0f --- /dev/null +++ b/src/ipc.c @@ -0,0 +1,134 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ +#include "ipc.h" + +/* Establish a client connection to the given socket path. */ +static int client_connect(const char *path) +{ + int sd = socket(AF_UNIX, SOCK_STREAM, 0); + struct sockaddr_un addr = {0}; + + if (sd < 0) { + perror("socket"); + exit(-1); + } + + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path)-1); + + if (connect(sd, (struct sockaddr *) &addr, sizeof addr) < 0) { + perror("bind"); + exit(-1); + } + + return sd; +} + +/* Create a listening socket on the supplied path. */ +int ipc_create_server(const char *path) +{ + char lockpath[PATH_MAX]; + int sd = socket(AF_UNIX, SOCK_STREAM, 0); + int lfd; + struct sockaddr_un addr = {0}; + + if (sd < 0) { + perror("socket"); + exit(-1); + } + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path)-1); + snprintf(lockpath, sizeof lockpath, "%s.lock", path); + lfd = open(lockpath, O_CREAT | O_RDONLY, 0600); + + if (lfd < 0) { + perror("open"); + exit(-1); + } + + if (flock(lfd, LOCK_EX | LOCK_NB)) + return -1; + + unlink(path); + if (bind(sd, (struct sockaddr *) &addr, sizeof addr) < 0) { + fprintf(stderr, "failed to bind to socket %s\n", path); + exit(-1); + } + + if (listen(sd, 20) < 0) { + perror("listen"); + exit(-1); + } + + chmod(path, 0660); + return sd; +} + +static int readmsg(int sd, char buf[MAX_MESSAGE_SIZE]) +{ + int n = 0; + + while (1) { + int ret = read(sd, buf+n, MAX_MESSAGE_SIZE-n); + if (ret < 0) + return -1; + + n += ret; + if (n > 1 && buf[n-1] == 0 && buf[n-2] == 0) + return n-2; + + assert(n < MAX_MESSAGE_SIZE); + } +} + +/* + * Consume a \x00\x00 terminated input string from the supplied connection and + * delegate processing to the provided callback. The return value of 'handler' + * will ultimately be returned by the corresponding ipc_run() call on the + * client and all output written to 'output_fd' will be printed to the client's + * standard output stream. + */ + +void ipc_server_process_connection(int sd, int (*handler) (int output_fd, const char *input)) +{ + char input[MAX_MESSAGE_SIZE]; + uint8_t ret = 0; + + if (readmsg(sd, input) < 0) { + fprintf(stderr, "ipc: failed to read input\n"); + return; + } + + ret = handler(sd, input); + + write(sd, &ret, 1); + write(sd, "\x00\x00", 2); + close(sd); +} + +int ipc_run(const char *socket, const char *input) +{ + int n; + uint8_t ret; + char buf[MAX_MESSAGE_SIZE]; + + int sd = client_connect(socket); + + if (sd < 0) + return -1; + + write(sd, input, strlen(input)); + write(sd, "\x00\x00", 2); + + n = readmsg(sd, buf); + if (n < 0) + return -1; + + printf("%s", buf); + + ret = buf[n-1]; + return (int)ret; +} diff --git a/src/ipc.h b/src/ipc.h new file mode 100644 index 00000000..1e736575 --- /dev/null +++ b/src/ipc.h @@ -0,0 +1,30 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ +#ifndef IPC_H +#define IPC_H + +#include <stdint.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/file.h> +#include <sys/socket.h> +#include <limits.h> +#include <fcntl.h> +#include <assert.h> +#include <sys/un.h> +#include <unistd.h> +#include <sys/stat.h> + +#define MAX_MESSAGE_SIZE 4096 + + +int ipc_create_server(const char *path); +void ipc_server_process_connection(int sd, int (*handler) (int fd, const char *input)); +int ipc_run(const char *socket, const char *input); + +#endif diff --git a/src/keyboard.c b/src/keyboard.c index 05e8b8e9..f3ab0d5d 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -1,285 +1,110 @@ -/* Copyright © 2019 Raheman Vaiya. - * - * 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 (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. +/* + * keyd - A key remapping daemon. * - * 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. + * © 2019 Raheman Vaiya (see also: LICENSE). */ - #include <stdint.h> #include <unistd.h> #include <stdio.h> -#include <time.h> +#include <string.h> + #include "keyboard.h" #include "keyd.h" +#include "vkbd.h" #include "descriptor.h" #include "layer.h" -#define MACRO_REPEAT_TIMEOUT 400 /* In ms */ -#define MACRO_REPEAT_INTERVAL 20 /* In ms */ +#define MACRO_REPEAT_TIMEOUT 600 /* In ms */ +#define MACRO_REPEAT_INTERVAL 50 /* In ms */ -static long get_time_ms() -{ - struct timespec tv; - clock_gettime(CLOCK_MONOTONIC, &tv); - return (tv.tv_sec*1E3)+tv.tv_nsec/1E6; -} +static struct macro *active_macro = NULL; +static uint8_t active_macro_mods = 0; +static uint8_t oneshot_latch = 0; -static void send_mods(uint8_t mods, int press) +static long get_time() { - size_t i; - - for (i = 0; i < sizeof modifier_table / sizeof(modifier_table[0]); i++) { - uint8_t code = modifier_table[i].code1; - uint8_t mask = modifier_table[i].mask; - - if (mask & mods && keystate[code] != press) - send_key(code, press); - } + /* close enough :/. using a syscall is unnecessary. */ + static long time = 1; + return time++; } -/* Intelligently disarm active mods to avoid spurious key press events. */ -static void disarm_mods(uint8_t mods) +static void kbd_send_key(struct keyboard *kbd, uint8_t code, uint8_t pressed) { - uint8_t dmods = (MOD_ALT|MOD_SUPER) & mods; - /* - * We interpose a control sequence to prevent '<alt down> <alt up>' - * from being interpreted as an alt keypress. - */ - if (dmods) { - if (keystate[KEY_LEFTCTRL]) - send_mods(dmods, 0); - else { - send_key(KEY_LEFTCTRL, 1); - send_mods(dmods, 0); - send_key(KEY_LEFTCTRL, 0); - } - - mods &= ~dmods; - } - - send_mods(mods, 0); -} - - - -static void kbd_lookup_descriptor(struct keyboard *kbd, - uint8_t code, - int pressed, - struct descriptor *descriptor, - int *descriptor_layer) -{ - size_t i; - struct descriptor d; - int dl; - int found = 0; - - /* Check if the key is active */ - for (i = 0; i < kbd->nr_active_keys; i++) { - struct active_key *ak = &kbd->active_keys[i]; + if (pressed) + kbd->last_pressed_output_code = code; - if (ak->code == code) { - dl = ak->dl; - d = ak->d; + kbd->keystate[code] = pressed; - found = 1; - } - } - - if (!found) { - struct layer *layer; - - if (!kbd->nr_active_layers) - dl = kbd->layout; - else - dl = kbd->active_layers[kbd->nr_active_layers-1].layer; - - layer = &kbd->config.layers[dl]; - - d = layer->keymap[code]; - - /* - * If the most recently active layer is a modifier layer - * and the key is undefined, use the layout definition. - * If the key is undefined in a normal layer, treat it - * as undefined. - */ - if (!d.op && layer->mods) { - dl = kbd->layout; - d = kbd->config.layers[dl].keymap[code]; - } - } - - if (pressed) { - struct active_key *ak = &kbd->active_keys[kbd->nr_active_keys++]; - - ak->code = code; - ak->d = d; - ak->dl = dl; - } else { - int n = 0; - - for (i = 0; i < kbd->nr_active_keys; i++) { - if (kbd->active_keys[i].code != code) - kbd->active_keys[n++] = kbd->active_keys[i]; - } - - kbd->nr_active_keys = n; + switch (code) { + case KEY_LEFT_MOUSE: + vkbd_send_button(vkbd, 1, pressed); + break; + case KEY_MIDDLE_MOUSE: + vkbd_send_button(vkbd, 2, pressed); + break; + case KEY_RIGHT_MOUSE: + vkbd_send_button(vkbd, 3, pressed); + break; + default: + vkbd_send_key(vkbd, code, pressed); + break; } - - *descriptor = d; - *descriptor_layer = dl; } -/* Compute the current modifier set based on the activated layers. */ -static uint8_t kbd_compute_mods(struct keyboard *kbd) -{ - size_t i; - uint8_t mods = 0; - - for (i = 0; i < kbd->nr_active_layers; i++) { - struct layer *layer = &kbd->config.layers[kbd->active_layers[i].layer]; - mods |= layer->mods; - } - - return mods; -} +/* + * refcounted to account for overlapping active mods without adding manual + * accounting to the calling code, each send_mods(foo, 1) *must* be accompanied + * by a corresponding send_mods(foo, 0) at some point. Failure to do so is + * considered a bug (i.e treat this like malloc). + */ -/* Returns the active_layer struct associated with the given layer index. */ -static const struct active_layer *kbd_lookup_active_layer(struct keyboard *kbd, int layer) +static void send_mods(struct keyboard *kbd, uint8_t mods, int press) { size_t i; - for (i = 0; i < kbd->nr_active_layers; i++) { - struct active_layer *al = &kbd->active_layers[i]; - - if (al->layer == layer) - return al; - } - - return NULL; -} - -static void kbd_deactivate_layer(struct keyboard *kbd, int layer_idx, int disarm_mods_p) -{ - int i; - int n = kbd->nr_active_layers; - uint8_t active_mods = 0; - struct layer *layer = &kbd->config.layers[layer_idx]; - - dbg("Deactivating layer %s", layer->name); + for (i = 0; i < sizeof modifier_table / sizeof(modifier_table[0]); i++) { + uint8_t code = modifier_table[i].code1; + uint8_t mask = modifier_table[i].mask; - kbd->nr_active_layers = 0; - for (i = 0; i < n; i++) { - struct active_layer *al = &kbd->active_layers[i]; + if (mask & mods) { + kbd->modstate[i] += press ? 1 : -1; - if (al->layer != layer_idx) { - kbd->active_layers[kbd->nr_active_layers++] = *al; - active_mods |= kbd->config.layers[al->layer].mods; + if (kbd->modstate[i] == 0) + kbd_send_key(kbd, code, 0); + else if (kbd->modstate[i] == 1) + kbd_send_key(kbd, code, 1); } } - - if (disarm_mods_p) - disarm_mods(layer->mods & ~active_mods); - else - send_mods(layer->mods & ~active_mods, 0); } -static void kbd_clear_oneshots(struct keyboard *kbd) +/* intelligently disarm active mods to avoid spurious alt/meta keypresses. */ +static void disarm_mods(struct keyboard *kbd, uint8_t mods) { - size_t i, n; - uint8_t cleared_mods = 0; - - n = 0; - for (i = 0; i < kbd->nr_active_layers; i++) { - struct active_layer *al = &kbd->active_layers[i]; - - if (!al->oneshot) { - kbd->active_layers[n++] = *al; - } else { - cleared_mods |= kbd->config.layers[al->layer].mods; - dbg("Clearing oneshot layer %s", kbd->config.layers[al->layer].name); + uint8_t dmods = (MOD_ALT|MOD_SUPER) & mods; + /* + * We interpose a control sequence to prevent '<alt down> <alt up>' + * from being interpreted as an alt keypress. + */ + if (dmods && ((kbd->last_pressed_output_code == KEY_LEFTMETA) || (kbd->last_pressed_output_code == KEY_LEFTALT))) { + if (kbd->keystate[KEY_LEFTCTRL]) + send_mods(kbd, dmods, 0); + else { + kbd_send_key(kbd, KEY_LEFTCTRL, 1); + send_mods(kbd, dmods, 0); + kbd_send_key(kbd, KEY_LEFTCTRL, 0); } - } - - kbd->nr_active_layers = n; - - send_mods(cleared_mods & ~kbd_compute_mods(kbd), 0); -} - -static void kbd_activate_layer(struct keyboard *kbd, int layer_idx, int oneshot) -{ - struct active_layer *al; - struct layer *layer = &kbd->config.layers[layer_idx]; - size_t i; - - dbg("Activating layer %s", kbd->config.layers[layer_idx].name); - for (i = 0; i < kbd->nr_active_layers; i++) { - al = &kbd->active_layers[i]; - if(al->layer == layer_idx) { - al->oneshot = oneshot; - return; - } + mods &= ~dmods; } - al = &kbd->active_layers[kbd->nr_active_layers]; - - al->layer = layer_idx; - al->oneshot = oneshot; - - kbd->nr_active_layers++; - - send_mods(layer->mods, 1); + send_mods(kbd, mods, 0); } -static void kbd_swap_layer(struct keyboard *kbd, - int dl, - int replacment_layer, - struct descriptor new_descriptor) -{ - size_t i; - - /* - * Find the key activating dl and swap out its descriptor with the - * current one to deactivate the replacement layer. - */ - for (i = 0; i < kbd->nr_active_keys; i++) { - struct active_key *ak = &kbd->active_keys[i]; - - if((ak->d.op == OP_LAYER || ak->d.op == OP_OVERLOAD || ak->d.op == OP_SWAP) && ak->d.args[0].idx == dl) { - kbd_deactivate_layer(kbd, dl, 1); - kbd_activate_layer(kbd, replacment_layer, 0); - - ak->d = new_descriptor; - - return; - } - } -} -static void kbd_execute_macro(struct keyboard *kbd, - const struct macro *macro) +static void execute_macro(struct keyboard *kbd, const struct macro *macro) { size_t i; int hold_start = -1; - uint8_t active_mods = kbd_compute_mods(kbd); - - disarm_mods(active_mods); for (i = 0; i < macro->sz; i++) { const struct macro_entry *ent = ¯o->entries[i]; @@ -289,7 +114,7 @@ static void kbd_execute_macro(struct keyboard *kbd, if (hold_start == -1) hold_start = i; - send_key(ent->data.sequence.code, 1); + kbd_send_key(kbd, ent->code, 1); break; case MACRO_RELEASE: @@ -298,283 +123,399 @@ static void kbd_execute_macro(struct keyboard *kbd, for (j = hold_start; j < i; j++) { const struct macro_entry *ent = ¯o->entries[j]; - send_key(ent->data.sequence.code, 0); + kbd_send_key(kbd, ent->code, 0); } } break; case MACRO_KEYSEQUENCE: - send_mods(ent->data.sequence.mods, 1); + if (kbd->keystate[ent->code]) + kbd_send_key(kbd, ent->code, 0); - send_key(ent->data.sequence.code, 1); - send_key(ent->data.sequence.code, 0); + send_mods(kbd, ent->mods, 1); + kbd_send_key(kbd, ent->code, 1); + kbd_send_key(kbd, ent->code, 0); + send_mods(kbd, ent->mods, 0); - send_mods(ent->data.sequence.mods, 0); break; case MACRO_TIMEOUT: - usleep(ent->data.timeout*1E3); + usleep(ent->timeout*1E3); break; } } - - send_mods(active_mods, 1); } int kbd_execute_expression(struct keyboard *kbd, const char *exp) { - return config_execute_expression(&kbd->config, exp); + return layer_table_add_entry(&kbd->layer_table, exp); } void kbd_reset(struct keyboard *kbd) { - /* TODO optimize */ - - kbd->config = kbd->original_config; + memcpy(&kbd->layer_table, + &kbd->config.layer_table, + sizeof(kbd->layer_table)); } -static void kbd_start_sequence(struct keyboard *kbd, const struct key_sequence *sequence, int literally) +static void lookup_descriptor(struct keyboard *kbd, uint8_t code, uint8_t *layer_mods, struct descriptor *d) { - uint8_t active_mods = kbd_compute_mods(kbd); + size_t i; + uint8_t active[MAX_LAYERS]; + struct layer_table *lt = &kbd->layer_table; + d->op = OP_UNDEFINED; + + *layer_mods = 0; + long maxts = 0; + + for (i = 0; i < lt->nr; i++) { + struct layer *layer = <->layers[i]; + + if (layer->flags) { + active[i] = 1; + if (layer->keymap[code].op && layer->activation_time >= maxts) { + maxts = layer->activation_time; + *layer_mods = layer->mods; + *d = layer->keymap[code]; + } + } else { + active[i] = 0; + } + } - if (!sequence->code && !sequence->mods) - return; + /* Scan for any composite matches (which take precedence). */ + for (i = 0; i < lt->nr; i++) { + struct layer *layer = <->layers[i]; - if (literally) - disarm_mods(active_mods); + if (layer->type == LT_COMPOSITE) { + size_t j; + int match = 1; + uint8_t mods = 0; + + for (j = 0; j < layer->nr_layers; j++) { + if (active[layer->layers[j]]) + mods |= lt->layers[layer->layers[j]].mods; + else + match = 0; + } - send_mods(sequence->mods, 1); - send_key(sequence->code, 1); + if (match && layer->keymap[code].op) { + *layer_mods = mods; + *d = layer->keymap[code]; + } + } + } } -static void kbd_end_sequence(struct keyboard *kbd, const struct key_sequence *sequence) +static int cache_set(struct keyboard *kbd, uint8_t code, const struct descriptor *d, uint8_t mods) { - uint8_t active_mods = kbd_compute_mods(kbd); + size_t i; + int slot = -1; + + for (i = 0; i < CACHE_SIZE; i++) + if (kbd->cache[i].code == code) { + slot = i; + break; + } else if (!kbd->cache[i].code) { + slot = i; + } + + if (slot == -1) + return -1; + + if (d == NULL) { + kbd->cache[slot].code = 0; + } else { + kbd->cache[slot].code = code; + kbd->cache[slot].d = *d; + kbd->cache[slot].layermods = mods; + } - send_key(sequence->code, 0); - send_mods(sequence->mods, 0); - send_mods(active_mods, 1); + return 0; } -static void kbd_execute_sequence(struct keyboard *kbd, const struct key_sequence *sequence, int literally) +static int cache_get(struct keyboard *kbd, uint8_t code, struct descriptor *d, uint8_t *mods) { - kbd_start_sequence(kbd, sequence, literally); - kbd_end_sequence(kbd, sequence); + size_t i; + + for (i = 0; i < CACHE_SIZE; i++) + if (kbd->cache[i].code == code) { + if (d) + *d = kbd->cache[i].d; + if (mods) + *mods = kbd->cache[i].layermods; + + return 0; + } + + return -1; } +static void activate_layer(struct keyboard *kbd, struct layer *layer) +{ + layer->flags |= LF_ACTIVE; + send_mods(kbd, layer->mods, 1); + layer->activation_time = get_time(); +} -/* - * Here be tiny dragons. - * - * `code` may be 0 in the event of a timeout. - * - * The return value corresponds to a timeout before which the next invocation - * of kbd_process_key_event must take place. A return value of 0 permits the - * main loop to call at liberty. - */ -long kbd_process_key_event(struct keyboard *kbd, - uint8_t code, - int pressed) +static void deactivate_layer(struct keyboard *kbd, struct layer *layer, int disarm_p) { - int dl; - struct descriptor d; - int timeout = 0; - int disarm = 1; - static struct key_sequence active_sequence = {0}; + layer->flags &= ~LF_ACTIVE; - static int oneshot_latch = 0; - static const struct macro *active_macro = NULL; + if (disarm_p) + disarm_mods(kbd, layer->mods); + else + send_mods(kbd, layer->mods, 0); +} - static struct { - int layer; - struct key_sequence sequence; - int timeout; +static long process_descriptor(struct keyboard *kbd, uint8_t code, struct descriptor *d, int descriptor_layer_mods, int pressed) +{ + int timeout = 0; + uint8_t clear_oneshot = 0; - long start_time; - uint8_t is_active; - } pending_overload = {0}; + struct macro *macros = kbd->layer_table.macros; + struct timeout *timeouts = kbd->layer_table.timeouts; + struct layer *layers = kbd->layer_table.layers; + size_t nr_layers = kbd->layer_table.nr; - if (active_sequence.code) { - kbd_end_sequence(kbd, &active_sequence); - active_sequence.code = 0; - } + switch (d->op) { + struct macro *macro; + struct layer *layer; - if (active_macro) { - if (!code) { - kbd_execute_macro(kbd, active_macro); - return MACRO_REPEAT_INTERVAL; - } else - active_macro = NULL; - } + case OP_MACRO: + macro = ¯os[d->args[0].idx]; - if (pending_overload.is_active) { - if ((get_time_ms() - pending_overload.start_time) >= (long)pending_overload.timeout) { - kbd_activate_layer(kbd, pending_overload.layer, 0); - } else { - kbd_execute_sequence(kbd, &pending_overload.sequence, 1); - } + if (pressed) { + disarm_mods(kbd, descriptor_layer_mods); - pending_overload.is_active = 0; - } + execute_macro(kbd, macro); - if (!code) - return 0; + active_macro = macro; + active_macro_mods = descriptor_layer_mods; - kbd_lookup_descriptor(kbd, code, pressed, &d, &dl); + timeout = MACRO_REPEAT_TIMEOUT; + } - if(!d.op) { - kbd_clear_oneshots(kbd); - return 0; - } + oneshot_latch = 0; + clear_oneshot = 1; + break; + case OP_ONESHOT: + layer = &layers[d->args[0].idx]; - switch (d.op) { - int verbatim; - int is_sequence; - const struct key_sequence *sequence; - int layer; - const struct macro *macro; - - case OP_KEYSEQ: - /* TODO: distinguish between key codes and key sequences at the config level. */ - sequence = &d.args[0].sequence; - is_sequence = sequence->mods || dl != kbd->layout; - - if (is_sequence) { - if (pressed) { - active_sequence = *sequence; - kbd_start_sequence(kbd, sequence, 1); + if (pressed) { + if (layer->flags & LF_ONESHOT_HELD) { + /* Neutralize key up */ + cache_set(kbd, code, NULL, 0); + } else { + if (layer->flags & LF_ONESHOT) { + disarm_mods(kbd, layer->mods); + layer->flags &= ~LF_ONESHOT; + } + + send_mods(kbd, layer->mods, 1); + + oneshot_latch = 1; + layer->flags |= LF_ONESHOT_HELD; + layer->activation_time = get_time(); + } + } else if (oneshot_latch) { + if (layer->flags & LF_ONESHOT) { + /* + * If oneshot is already set for the layer we can't + * rely on the clear logic to mirror our send_mod() + * call. + */ + disarm_mods(kbd, layer->mods); + } else { + layer->flags |= LF_ONESHOT; + layer->flags &= ~LF_ONESHOT_HELD; } } else { - disarm = 0; - send_key(sequence->code, pressed); - } + send_mods(kbd, layer->mods, 0); - break; - case OP_RESET: - if (!pressed) { - send_mods(kbd_compute_mods(kbd), 0); - kbd->nr_active_layers = 0; + layer->flags &= ~LF_ONESHOT_HELD; } break; case OP_TOGGLE: - layer = d.args[0].idx; + layer = &layers[d->args[0].idx]; if (!pressed) { - const struct active_layer *al = kbd_lookup_active_layer(kbd, layer); - if (al) { - if (al->oneshot) /* Allow oneshot layers to toggle themselves. */ - kbd_activate_layer(kbd, layer, 0); - else - kbd_deactivate_layer(kbd, layer, 1); - } else - kbd_activate_layer(kbd, layer, 0); + layer->flags ^= LF_TOGGLE; + + if (layer->flags & LF_TOGGLE) + activate_layer(kbd, layer); + else + deactivate_layer(kbd, layer, 0); } + clear_oneshot = 1; break; - case OP_LAYOUT: - if (!pressed) - kbd->layout = d.args[0].idx; + case OP_KEYCODE: + if (pressed) { + disarm_mods(kbd, descriptor_layer_mods); + kbd_send_key(kbd, d->args[0].code, 1); + } else { + kbd_send_key(kbd, d->args[0].code, 0); + send_mods(kbd, descriptor_layer_mods, 1); + } + oneshot_latch = 0; + clear_oneshot = 1; break; case OP_LAYER: - layer = d.args[0].idx; - - if (pressed) - kbd_activate_layer(kbd, layer, 0); - else { - int disarm = kbd->disarm_flag; - if (kbd->last_pressed_keycode == code) - disarm = 0; + layer = &layers[d->args[0].idx]; - kbd_deactivate_layer(kbd, layer, disarm); + if (pressed) { + activate_layer(kbd, layer); + kbd->last_layer_code = code; + } else { + deactivate_layer(kbd, layer, kbd->last_pressed_keycode != code); } - break; - case OP_ONESHOT: - layer = d.args[0].idx; + case OP_SWAP: + layer = &layers[d->args[0].idx]; + macro = d->args[1].idx == -1 ? NULL : ¯os[d->args[1].idx]; if (pressed) { - oneshot_latch++; - kbd_activate_layer(kbd, layer, 0); - } else if (oneshot_latch) /* No interposed KEYSEQ since key down (other modifiers don't interfere with this). */ - kbd_activate_layer(kbd, layer, 1); - else - kbd_deactivate_layer(kbd, layer, 1); + struct descriptor od; + if (macro) { + disarm_mods(kbd, descriptor_layer_mods); + execute_macro(kbd, macro); + send_mods(kbd, descriptor_layer_mods, 1); + } - break; - case OP_SWAP: - layer = d.args[0].idx; - sequence = &d.args[1].sequence; + if (!cache_get(kbd, kbd->last_layer_code, &od, NULL)) { + struct layer *oldlayer = &layers[od.args[0].idx]; - if (pressed) { - kbd_execute_sequence(kbd, sequence, 1); - kbd_swap_layer(kbd, dl, layer, d); - } else if (kbd->last_pressed_keycode != code) { /* We only reach this from the remapped dl activate key. */ - kbd_deactivate_layer(kbd, layer, 1); - } + cache_set(kbd, kbd->last_layer_code, d, descriptor_layer_mods); + cache_set(kbd, code, NULL, 0); + + activate_layer(kbd, layer); + deactivate_layer(kbd, oldlayer, 1); + } + } else + deactivate_layer(kbd, layer, 1); break; case OP_OVERLOAD: - layer = d.args[0].idx; - sequence = &d.args[1].sequence; + layer = &layers[d->args[0].idx]; + macro = ¯os[d->args[1].idx]; if (pressed) { - kbd_activate_layer(kbd, layer, 0); - } else if (kbd->last_pressed_keycode == code) { - kbd_deactivate_layer(kbd, layer, 1); - - /* TODO: enforce this as a code in the config. */ - kbd_execute_sequence(kbd, sequence, dl != kbd->layout); + activate_layer(kbd, layer); + kbd->last_layer_code = code; } else { - kbd_deactivate_layer(kbd, layer, 1); - } + deactivate_layer(kbd, layer, 1); - break; - case OP_OVERLOAD_TIMEOUT: - layer = d.args[0].idx; - sequence = &d.args[1].sequence; - - if (pressed) { - pending_overload.layer = d.args[0].idx; - pending_overload.sequence = d.args[1].sequence; - pending_overload.timeout = d.args[2].timeout; - pending_overload.start_time = get_time_ms(); + if (kbd->last_pressed_keycode == code) { + disarm_mods(kbd, descriptor_layer_mods); + execute_macro(kbd, macro); + send_mods(kbd, descriptor_layer_mods, 1); - pending_overload.is_active = 1; - } else { - kbd_deactivate_layer(kbd, layer, 0); + oneshot_latch = 0; + clear_oneshot = 1; + } } break; - case OP_MACRO: - macro = &kbd->config.macros[d.args[0].idx]; - + case OP_TIMEOUT: if (pressed) { - active_macro = macro; - kbd_execute_macro(kbd, macro); - timeout = MACRO_REPEAT_TIMEOUT; - } + kbd->pending_timeout.t = timeouts[d->args[0].idx]; + kbd->pending_timeout.code = code; + kbd->pending_timeout.mods = descriptor_layer_mods; + timeout = kbd->pending_timeout.t.timeout; + } break; - default: - printf("Unrecognized op: %d, ignoring...\n", d.op); + case OP_UNDEFINED: + if (pressed) + clear_oneshot = 1; break; } - if (!d.op || - (pressed && - (d.op == OP_KEYSEQ || - d.op == OP_MACRO || - d.op == OP_OVERLOAD))) { + if (clear_oneshot) { + size_t i = 0; + + for (i = 0; i < nr_layers; i++) { + struct layer *layer = &layers[i]; + + if (layer->flags & LF_ONESHOT) { + layer->flags &= ~LF_ONESHOT; + + send_mods(kbd, layer->mods, 0); + } + } + oneshot_latch = 0; - kbd_clear_oneshots(kbd); } - if (pressed) { + if (pressed) kbd->last_pressed_keycode = code; - kbd->disarm_flag = disarm; + + return timeout; +} + + +/* + * Here be tiny dragons. + * + * `code` may be 0 in the event of a timeout. + * + * The return value corresponds to a timeout before which the next invocation + * of kbd_process_key_event must take place. A return value of 0 permits the + * main loop to call at liberty. + */ +long kbd_process_key_event(struct keyboard *kbd, + uint8_t code, + int pressed) +{ + uint8_t descriptor_layer_mods; + struct descriptor d; + + /* timeout */ + if (!code) { + if (active_macro) { + execute_macro(kbd, active_macro); + return MACRO_REPEAT_INTERVAL; + } else if (kbd->pending_timeout.code) { + uint8_t mods = kbd->pending_timeout.mods; + uint8_t code = kbd->pending_timeout.code; + struct descriptor *d = &kbd->pending_timeout.t.d2; + + cache_set(kbd, code, d, mods); + + kbd->pending_timeout.code = 0; + return process_descriptor(kbd, code, d, mods, 1); + } } + if (kbd->pending_timeout.code) { + uint8_t mods = kbd->pending_timeout.mods; + uint8_t code = kbd->pending_timeout.code; + struct descriptor *d = &kbd->pending_timeout.t.d1; - return timeout; + cache_set(kbd, code, d, mods); + process_descriptor(kbd, code, d, mods, 1); + + kbd->pending_timeout.code = 0; + } + + if (active_macro) { + active_macro = NULL; + send_mods(kbd, active_macro_mods, 1); + } + + if (pressed) { + lookup_descriptor(kbd, code, &descriptor_layer_mods, &d); + + if (cache_set(kbd, code, &d, descriptor_layer_mods) < 0) + return 0; + } else { + if (cache_get(kbd, code, &d, &descriptor_layer_mods) < 0) + return 0; + + cache_set(kbd, code, NULL, 0); + } + + return process_descriptor(kbd, code, &d, descriptor_layer_mods, pressed); } diff --git a/src/keyboard.h b/src/keyboard.h index 22304376..20c41302 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -1,50 +1,50 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ #ifndef KEYBOARD_H #define KEYBOARD_H #include "config.h" #include "layer.h" -#define MAX_ACTIVE_KEYS 32 +#define MAX_ACTIVE_KEYS 32 +#define CACHE_SIZE 16 //Effectively nkro -extern struct keyboard *active_keyboard; - -struct active_layer { - int layer; - int oneshot; -}; - -/* Represents a currently depressed key */ -struct active_key { +struct cache_entry { uint8_t code; - struct descriptor d; - int dl; /* The layer from which the descriptor was drawn. */ + uint8_t layermods; }; -/* Active keyboard state. */ struct keyboard { int fd; - char devnode[256]; - uint32_t id; - - struct active_layer active_layers[MAX_LAYERS]; - size_t nr_active_layers; - struct config original_config; struct config config; - int layout; - struct active_key active_keys[MAX_ACTIVE_KEYS]; - size_t nr_active_keys; + struct layer_table layer_table; + /* state*/ + + /* for key up events */ + struct cache_entry cache[CACHE_SIZE]; + + uint8_t last_pressed_output_code; uint8_t last_pressed_keycode; - int disarm_flag; + uint8_t last_layer_code; - struct keyboard *next; + struct { + uint8_t code; + uint8_t mods; + struct timeout t; + } pending_timeout; + + uint8_t keystate[256]; + uint8_t modstate[MAX_MOD]; }; long kbd_process_key_event(struct keyboard *kbd, uint8_t code, int pressed); - void kbd_reset(struct keyboard *kbd); int kbd_execute_expression(struct keyboard *kbd, const char *exp); diff --git a/src/keyd.c b/src/keyd.c index aa314b95..790bd3ba 100644 --- a/src/keyd.c +++ b/src/keyd.c @@ -1,788 +1,468 @@ -/* Copyright © 2019 Raheman Vaiya. +/* + * keyd - A key remapping daemon. * - * 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 (including the next - * paragraph) 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. + * © 2019 Raheman Vaiya (see also: LICENSE). */ - -#ifdef __FreeBSD__ -#include <dev/evdev/input.h> -#include <dev/evdev/uinput.h> -#else -#include <linux/input.h> -#include <linux/uinput.h> -#endif - -#include <stdio.h> -#include <sys/time.h> -#include <signal.h> -#include <sys/types.h> -#include <sys/file.h> -#include <dirent.h> -#include <assert.h> -#include <errno.h> -#include <string.h> -#include <sys/stat.h> -#include <signal.h> -#include <unistd.h> -#include <termios.h> -#include <stdint.h> -#include <stdarg.h> -#include <time.h> -#include <stdlib.h> -#include <fcntl.h> -#include <grp.h> -#include <sys/inotify.h> - -#include "keys.h" -#include "config.h" -#include "keyboard.h" #include "keyd.h" -#include "server.h" -#include "vkbd.h" -#include "error.h" -#define VIRTUAL_KEYBOARD_NAME "keyd virtual keyboard" +/* config variables */ -static struct vkbd *vkbd = NULL; +static const char *virtual_keyboard_name; +static const char *config_dir; +static const char *socket_file; -uint8_t keystate[MAX_KEYS] = { 0 }; -static int sigfds[2]; -struct keyboard *active_keyboard = NULL; +/* local state */ -static struct keyboard *keyboards = NULL; +static struct device devices[MAX_DEVICES]; +static size_t nr_devices = 0; +static struct keyboard *active_kbd = NULL; -static int panic_counter = 0; +/* loop() callback functions */ -int debug = 0; +static void (*device_add_cb) (struct device *dev); +static void (*device_remove_cb) (struct device *dev); -void dbg(const char *fmt, ...) -{ - va_list ap; +/* returns a minimum timeout value */ +static int (*device_event_cb) (struct device *dev, uint8_t code, uint8_t processed); - if (!debug) - return; +/* globals */ - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); - va_end(ap); -} +struct vkbd *vkbd; +char errstr[2048]; +int debug_level; -static void info(char *fmt, ...) +static void daemon_remove_cb(struct device *dev) { - va_list args; - va_start(args, fmt); + struct keyboard *kbd = dev->data; - vfprintf(stderr, fmt, args); - va_end(args); - fprintf(stderr, "\n"); -} - -static void send_repetitions() -{ - size_t i; - - /* Inefficient, but still reasonably fast (<100us) */ - for (i = 0; i < sizeof keystate / sizeof keystate[0]; i++) { - if (keystate[i]) - send_key(i, 2); - } -} - -void reset_vkbd() -{ - size_t code; - for (code = 0; code < MAX_KEYS; code++) { - if (keystate[code]) - send_key(code, 0); - } -} - - -void reset_keyboards() -{ - struct keyboard *kbd; + if (kbd) + free(kbd); - for (kbd = keyboards; kbd; kbd = kbd->next) - kbd_reset(kbd); -} + active_kbd = NULL; -void send_key(int code, int state) -{ - keystate[code] = state; - vkbd_send_key(vkbd, code, state); + printf("device removed: %04x:%04x %s (%s)\n", + dev->vendor_id, + dev->product_id, + dev->name, + dev->path); } -static int manage_keyboard(const char *devnode) +static void daemon_add_cb(struct device *dev) { - int fd; - const char *devname; - const char *config_path; - struct keyboard *kbd; - struct config *config = NULL; - uint16_t vendor_id, product_id; + const char *config_path = config_find_path(config_dir, dev->vendor_id, dev->product_id); - if (!(devname = evdev_device_name(devnode))) { - fprintf(stderr, "WARNING: Failed to obtain device info for %s, skipping..\n", devnode); - return -1; - } + dev->data = NULL; - /* Don't manage keyd's devices. */ - if (!strcmp(devname, VIRTUAL_KEYBOARD_NAME)) - return -1; + printf("device added: %04x:%04x %s (%s)\n", + dev->vendor_id, + dev->product_id, + dev->name, + dev->path); - for (kbd = keyboards; kbd; kbd = kbd->next) { - if (!strcmp(kbd->devnode, devnode)) { - dbg("Already managing %s.", devnode); - return -1; - } - } - - if (evdev_device_id(devnode, &vendor_id, &product_id) < 0) { - fprintf(stderr, "WARNING: Failed to obtain device id for %s (%s)\n", devnode, devname); - return -1; - } - - config_path = config_find_path(CONFIG_DIR, vendor_id, product_id); if (!config_path) { - fprintf(stderr, "No config found for %s (%04x:%04x)\n", devname, vendor_id, product_id); - return -1; + printf("\tignored (no matching config)\n"); + return; } kbd = calloc(1, sizeof(struct keyboard)); if (config_parse(&kbd->config, config_path) < 0) { - fprintf(stderr, "ERROR: failed to parse %s\n", config_path); free(kbd); - return -1; - } - - if ((fd = open(devnode, O_RDONLY | O_NONBLOCK)) < 0) { - fprintf(stderr, "WARNING: Failed to open %s (%s)\n", devnode, devname); - perror("open"); - - free(kbd); - return -1; + printf("\tfailed to parse %s\n", config_path); + return; } - if (evdev_grab_keyboard(fd) < 0) { - info("Failed to grab %04x:%04x, ignoring...\n", vendor_id, product_id); + if (device_grab(dev) < 0) { free(kbd); - - return -1; + printf("\tgrab failed\n"); + return; } - kbd->fd = fd; - - kbd->original_config = kbd->config; - kbd->layout = kbd->config.default_layout; - - strcpy(kbd->devnode, devnode); - - kbd->next = keyboards; - keyboards = kbd; - - active_keyboard = kbd; + printf("\tmatched %s\n", config_path); - info("Managing %s (%04x:%04x) (%s)", devname, vendor_id, product_id, config_path); - return 0; + memcpy(&kbd->layer_table, &kbd->config.layer_table, sizeof(kbd->layer_table)); + dev->data = kbd; } -static void scan_keyboards() +static void panic_check(uint8_t code, uint8_t pressed) { - int i, n; - char *devs[MAX_KEYBOARDS]; - - evdev_get_keyboard_nodes(devs, &n); + static uint8_t enter, backspace, escape; + switch (code) { + case KEY_ENTER: + enter = pressed; + break; + case KEY_BACKSPACE: + backspace = pressed; + break; + case KEY_ESC: + escape = pressed; + break; + } - for (i = 0; i < n; i++) - manage_keyboard(devs[i]); + if (backspace && enter && escape) + exit(-1); } -/* TODO: optimize */ -void reload_config() +static int daemon_event_cb(struct device *dev, uint8_t code, uint8_t pressed) { - struct keyboard *kbd = keyboards; - - while (kbd) { - struct keyboard *tmp = kbd; + struct keyboard *kbd = dev ? dev->data : active_kbd; - ioctl(kbd->fd, EVIOCGRAB, (void *) 0); - close(kbd->fd); - kbd = kbd->next; - free(tmp); - } + if (!kbd) + return 0; - keyboards = NULL; + panic_check(code, pressed); + active_kbd = kbd; - scan_keyboards(); - panic_counter = 0; + return kbd_process_key_event(kbd, code, pressed); } -static int destroy_keyboard(const char *devnode) +static int ipc_cb(int fd, const char *input) { - struct keyboard **ent = &keyboards; + int ret = 0; - while (*ent) { - if (!strcmp((*ent)->devnode, devnode)) { - dbg("Destroying %s", devnode); - struct keyboard *kbd = *ent; - *ent = kbd->next; - - /* Attempt to ungrab the the keyboard (assuming it still exists) */ - ioctl(kbd->fd, EVIOCGRAB, (void *) 1); - - close(kbd->fd); - free(kbd); + if (!active_kbd) + return -1; - return 1; + if (!strcmp(input, "reset")) { + kbd_reset(active_kbd); + } else if (!strcmp(input, "ping")) { + char s[] = "pong\n"; + write(fd, s, sizeof s); + } else { + ret = kbd_execute_expression(active_kbd, input); + if (ret < 0) { + write(fd, "ERROR: ", 7); + write(fd, errstr, strlen(errstr)); + write(fd, "\n\x00", 2); } - - ent = &(*ent)->next; } - return 0; + return ret; } -static void monitor_cleanup() -{ - struct termios tinfo; - tcgetattr(0, &tinfo); - tinfo.c_lflag |= ECHO; - tcsetattr(0, TCSANOW, &tinfo); +static void monitor_remove_cb(struct device *dev) +{ + fprintf(stderr, "device removed: %04x:%04x (%s)\n", + dev->vendor_id, + dev->product_id, + dev->name); } -static void panic_check(uint8_t code, int state) +static void monitor_add_cb(struct device *dev) { - switch (code) { - case KEY_BACKSPACE: - case KEY_ENTER: - case KEY_ESC: - if (state == 1) - panic_counter++; - else if (state == 0) - panic_counter--; - - break; - } + fprintf(stderr, "device added: %04x:%04x (%s)\n", + dev->vendor_id, + dev->product_id, + dev->name); - if (panic_counter == 3) - die("Termination key sequence triggered (backspace+escape+enter), terminating."); } - -static void evdev_monitor_loop(int *fds, int sz) +static int monitor_event_cb(struct device *dev, uint8_t code, uint8_t pressed) { - struct input_event ev; - fd_set fdset; - int i; - struct stat finfo; - int ispiped; - - struct { - char name[256]; - uint16_t product_id; - uint16_t vendor_id; - } info_table[256]; - - fstat(1, &finfo); - ispiped = finfo.st_mode & S_IFIFO; - - for (i = 0; i < sz; i++) { - struct input_id info; - - int fd = fds[i]; - if (ioctl(fd, EVIOCGID, &info) == -1) { - perror("ioctl"); - exit(-1); - } - - info_table[fd].product_id = info.product; - info_table[fd].vendor_id = info.vendor; - - if (ioctl - (fd, EVIOCGNAME(sizeof(info_table[0].name)), - info_table[fd].name) == -1) { - perror("ioctl"); - exit(-1); - } + const char *name = keycode_table[code].name; + + if (name) { + printf("%s\t%04x:%04x\t%s %s\n", + dev->name, + dev->vendor_id, + dev->product_id, + name, + pressed ? "down" : "up"); } - while (1) { - int i; - int maxfd = 1; - - FD_ZERO(&fdset); - - /* - * Proactively monitor stdout for pipe closures instead of waiting - * for a failed write to generate SIGPIPE. - */ - if (ispiped) - FD_SET(1, &fdset); - - for (i = 0; i < sz; i++) { - if (maxfd < fds[i]) - maxfd = fds[i]; - FD_SET(fds[i], &fdset); - } + return 0; +} - select(maxfd + 1, &fdset, NULL, NULL, NULL); - if (FD_ISSET(1, &fdset) && read(1, NULL, 0) == -1) { /* STDOUT closed. */ - /* Re-enable echo. */ - exit(0); - } +static void set_echo(int set) +{ + struct termios tinfo; - for (i = 0; i < sz; i++) { - int fd = fds[i]; + tcgetattr(1, &tinfo); - if (FD_ISSET(fd, &fdset)) { - while (read(fd, &ev, sizeof(ev)) > 0) { - if (ev.code >= MAX_KEYS) { - info("Out of bounds evdev keycode: %d", ev.code); - continue; - } + if (set) + tinfo.c_lflag |= ECHO; + else + tinfo.c_lflag &= ~ECHO; - if (ev.type == EV_KEY && ev.value != 2) { - const char *name = keycode_table[ev.code].name; - if (name) { - printf("%s\t%04x:%04x\t%s %s\n", - info_table[fd].name, - info_table[fd].vendor_id, - info_table[fd].product_id, - name, ev.value == 0 ? "up" : "down"); - - fflush(stdout); - } else - info("Unrecognized keycode: %d", ev.code); - } else if (ev.type != EV_SYN) { - dbg("%s: Event: (%d, %d, %d)", info_table[fd].name, ev.type, ev.value, ev.code); - } - } - } - } - } + tcsetattr(1, TCSANOW, &tinfo); } -static int monitor_loop() +static long get_time_ms() { - char *devnodes[256]; - int sz, i; - int fd = -1; - int fds[256]; - int nfds = 0; - - struct termios tinfo; - - /* Disable terminal echo so keys don't appear twice. */ - tcgetattr(0, &tinfo); - tinfo.c_lflag &= ~ECHO; - tcsetattr(0, TCSANOW, &tinfo); - - signal(SIGINT, exit); - signal(SIGTERM, exit); - atexit(monitor_cleanup); + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec*1E3+ts.tv_nsec/1E6; +} - evdev_get_keyboard_nodes(devnodes, &sz); +static void chgid() +{ + struct group *g = getgrnam("keyd"); - for (i = 0; i < sz; i++) { - fd = open(devnodes[i], O_RDONLY | O_NONBLOCK); - if (fd < 0) { - perror("open"); + if (!g) { + fprintf(stderr, "WARNING: failed to set effective group to \"keyd\" (make sure the group exists)\n"); + } else { + if (setgid(g->gr_gid)) { + perror("setgid"); exit(-1); } - fds[nfds++] = fd; } - - evdev_monitor_loop(fds, nfds); - - return 0; } -static void usr1(int status) +static void init_devices(struct device devices[MAX_DEVICES], int exclude_vkbd) { - char c = ':'; - write(sigfds[1], &c, 1); -} + size_t i; -/* Relative time in ns. */ -long get_time() -{ - struct timespec tv; - clock_gettime(CLOCK_MONOTONIC, &tv); - return (tv.tv_sec*1E9)+tv.tv_nsec; -} + size_t n = device_scan(devices); -static int create_inotify_fd() -{ - int fd = inotify_init1(IN_NONBLOCK); - if (fd < 0) { - perror("inotify"); - exit(-1); - } + nr_devices = 0; + for (i = 0; i < n; i++) { + if (exclude_vkbd && devices[i].vendor_id == 0x0FAC) + continue; - int wd = inotify_add_watch(fd, "/dev/input", IN_CREATE | IN_DELETE); - if (wd < 0) { - perror("inotify"); - exit(-1); + devices[nr_devices++] = devices[i]; } - - return fd; } -static void process_inotify_events(int fd) +static int loop(int monitor_mode) { - int len; - char buf[4096]; + int timeout_start = 0; + int timeout = 0; - while (1) { - struct inotify_event *ev; - if ((len = read(fd, buf, sizeof buf)) <= 0) - return; + size_t i; - for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + ev->len) { - char path[1024]; - ev = (struct inotify_event*) ptr; + int monfd = devmon_create(); + int ipcfd = -1; - if (strstr(ev->name, "ev") != ev->name) - continue; + struct pollfd pfds[MAX_DEVICES]; - sprintf(path, "/dev/input/%s", ev->name); + int nfds = 0; - if (ev->mask & IN_CREATE && evdev_is_keyboard(path)) - manage_keyboard(path); - else if (ev->mask & IN_DELETE) - destroy_keyboard(path); - } + if (monitor_mode) { + init_devices(devices, 0); + } else { + init_devices(devices, 1); + ipcfd = ipc_create_server(socket_file); + printf("socket: %s\n", socket_file); } -} + pfds[nfds].fd = 1; + pfds[nfds++].events = POLLERR; -static void main_loop() -{ - struct keyboard *kbd; - int inotifyfd; - int sd; - - long timeout = 0; /* in ns */ - long last_ts = 0; - struct keyboard *timeout_kbd = NULL; + pfds[nfds].fd = monfd; + pfds[nfds++].events = POLLIN; - nice(-20); + pfds[nfds].fd = ipcfd; + pfds[nfds++].events = POLLIN; - scan_keyboards(); - - inotifyfd = create_inotify_fd(); - sd = create_server_socket(); - - pipe(sigfds); - signal(SIGUSR1, usr1); + for (i = 0; i < nr_devices; i++) + device_add_cb(&devices[i]); while (1) { - int maxfd; - fd_set fds; - int ret; - - struct timeval tv; + int prune = 0; + int poll_timeout = -1; - FD_ZERO(&fds); - FD_SET(inotifyfd, &fds); - FD_SET(sd, &fds); - FD_SET(sigfds[0], &fds); + for (i = 0; i < nr_devices; i++) { + pfds[i+nfds].fd = devices[i].fd; + pfds[i+nfds].events = POLLIN; + } - maxfd = inotifyfd > sigfds[0] ? inotifyfd : sigfds[0]; - maxfd = sd > maxfd ? sd : maxfd; + if (timeout) + poll_timeout = get_time_ms() - timeout_start; + else + poll_timeout = -1; - for (kbd = keyboards; kbd; kbd = kbd->next) { - int fd = kbd->fd; + poll(pfds, nr_devices+nfds, poll_timeout); - maxfd = maxfd > fd ? maxfd : fd; - FD_SET(fd, &fds); + if (timeout) { + int elapsed = get_time_ms() - timeout_start; + if (elapsed >= timeout) { + timeout = device_event_cb(NULL, 0, 0); + timeout_start = get_time_ms(); + } } - tv.tv_sec = (timeout/1E3) / 1E6; - tv.tv_usec = (long)(timeout/1E3) % (long)1E6; - - if ((ret=select(maxfd + 1, &fds, NULL, NULL, timeout > 0 ? &tv : NULL)) >= 0) { - long time = get_time(); - long elapsed; - - elapsed = time - last_ts; - last_ts = time; + /* pipe closed, proactively terminate. */ + if (pfds[0].revents) + exit(0); - timeout -= elapsed; + if (pfds[1].revents) { + struct device *dev; + while ((dev = devmon_read_device(monfd))) { + assert(nr_devices < MAX_DEVICES); - if (timeout_kbd && timeout <= 0) { - timeout = kbd_process_key_event(timeout_kbd, 0, 0) * 1E6; + if (!monitor_mode && dev->vendor_id == 0x0FAC) /* ignore virtual devices we own */ + continue; - if (timeout <= 0) - timeout_kbd = NULL; + devices[nr_devices] = *dev; + device_add_cb(&devices[nr_devices]); + nr_devices++; } + } - if (FD_ISSET(sd, &fds)) - server_process_connections(sd); - - if (FD_ISSET(sigfds[0], &fds)) { - char c; - read(sigfds[0], &c, 1); - info("Received SIGUSR1, reloading config"); - reload_config(); + if (pfds[2].revents) { + int con = accept(ipcfd, NULL, 0); + if (con < 0) { + perror("accept"); + continue; } - if (FD_ISSET(inotifyfd, &fds)) - process_inotify_events(inotifyfd); - - for (kbd = keyboards; kbd; kbd = kbd->next) { - int fd = kbd->fd; - - if (FD_ISSET(fd, &fds)) { - struct input_event ev; - - while (read(fd, &ev, sizeof(ev)) > 0) { - switch (ev.type) { - case EV_KEY: - switch (ev.code) { - case BTN_LEFT: - vkbd_send_button(vkbd, 1, ev.value); - break; - case BTN_MIDDLE: - vkbd_send_button(vkbd, 2, ev.value); - break; - case BTN_RIGHT: - vkbd_send_button(vkbd, 3, ev.value); - break; - default: - if (ev.code >= MAX_KEYS) { - dbg("Out of bounds evdev keycode: %d %d", ev.code, ev.value); - break; - } - - if (ev.value == 2) { - /* Wayland and X both ignore repeat events but VTs seem to require them. */ - send_repetitions(); - } else { - panic_check(ev.code, ev.value); - - active_keyboard = kbd; - timeout = kbd_process_key_event(kbd, ev.code, ev.value) * 1E6; - - if (timeout > 0) - timeout_kbd = kbd; - else - timeout_kbd = NULL; - } - break; - } - break; - case EV_REL: /* Pointer motion events */ - if (ev.code == REL_X) - vkbd_move_mouse(vkbd, ev.value, 0); - else if (ev.code == REL_Y) - vkbd_move_mouse(vkbd, 0, ev.value); - - break; - case EV_MSC: - case EV_SYN: - break; - default: - dbg("Unrecognized event: (type: %d, code: %d, value: %d)", ev.type, ev.code, ev.value); - } + ipc_server_process_connection(con, ipc_cb); + } + + for (i = 0; i < nr_devices; i++) { + if (pfds[i+nfds].revents) { + struct device *dev = &devices[i]; + struct device_event *ev = device_read_event(&devices[i]); + + if (ev) { + if (ev->type == DEV_REMOVED) { + device_remove_cb(dev); + devices[i].fd = -1; + prune = 1; + } else { + timeout = device_event_cb(dev, ev->code, ev->pressed); + timeout_start = get_time_ms(); } } } } + + if (prune) { + int n = 0; + for (i = 0; i < nr_devices; i++) { + if (devices[i].fd != -1) + devices[n++] = devices[i]; + } + + nr_devices = n; + } } } - static void cleanup() { - info("cleaning up and terminating..."); - - struct keyboard *kbd = keyboards; + size_t i; - while (kbd) { - struct keyboard *tmp = kbd; - kbd = kbd->next; - free(tmp); - } + for (i = 0; i < nr_devices; i++) + free(devices[i].data); free_vkbd(vkbd); - unlink(SOCKET); + + if (isatty(1)) + set_echo(1); } -static void daemonize() +static void print_keys() { - int fd; - - info("Daemonizing..."); - info("Log output will be stored in %s", LOG_FILE); - - fd = open(LOG_FILE, O_CREAT | O_APPEND | O_WRONLY, 0600); - - if (fd < 0) { - perror("Failed to open log file"); - exit(-1); + size_t i; + for (i = 0; i < 256; i++) { + const char *altname = keycode_table[i].alt_name; + const char *shiftedname = keycode_table[i].shifted_name; + const char *name = keycode_table[i].name; + + if (name) + printf("%s\n", name); + if (altname) + printf("%s\n", altname); + if (shiftedname) + printf("%s\n", shiftedname); } +} +static void print_version() +{ + printf("keyd "VERSION"\n"); +} - if (fork()) - exit(0); - if (fork()) - exit(0); - - close(0); - close(1); - close(2); - - dup2(fd, 1); - dup2(fd, 2); +static void print_help() +{ + printf("usage: keyd [option]\n\n" + "Options:\n" + " -m, --monitor Start keyd in monitor mode.\n" + " -l, --list-keys List key names.\n" + " -v, --version Print the current version and exit.\n" + " -h, --help Print help and exit.\n"); } -static void lock() +static void eval_expressions(char *exps[], int n) { - int fd; + int i; + int ret = 0; - if ((fd = open(LOCK_FILE, O_CREAT | O_RDWR, 0600)) == -1) { - perror("flock open"); - exit(1); + for (i = 0; i < n; i++) { + int rc; + if ((rc = ipc_run(socket_file, exps[i]))) + ret = rc; } - if (flock(fd, LOCK_EX | LOCK_NB) == -1) { - fprintf(stderr, "ERROR: Another instance of keyd is already running.\n"); - exit(-1); - } + exit(ret); } -static void chgid() -{ - struct group *g = getgrnam("keyd"); - if (!g) { - fprintf(stderr, "WARNING: failed to set effective group to \"keyd\" (make sure the group exists)\n"); - } else { - if (setgid(g->gr_gid)) { - perror("setgid"); - exit(-1); - } - } -} +#define setvar(var, name, default) \ + var = getenv(name); \ + if (!var) \ + var = default; int main(int argc, char *argv[]) { - int daemonize_flag = 0; - - if (getenv("KEYD_DEBUG")) - debug = atoi(getenv("KEYD_DEBUG")); - - dbg("Debug mode enabled."); - - if (argc > 1) { - if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { - fprintf(stderr, "keyd version: %s (%s)\n", VERSION, GIT_COMMIT_HASH); - return 0; - } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--daemonize")) { - daemonize_flag++; - ;; - } else if (!strcmp(argv[1], "-e") || !strcmp(argv[1], "--expression")) { - int i; - int rc = 0; - const int n = argc - 2; - argv+=2; - - if (!n) { - fprintf(stderr, "ERROR: -e must be followed by one or more arguments.\n"); - return -1; - } + int monitor_flag = 0; + setvar(virtual_keyboard_name, "KEYD_NAME", "keyd virtual device"); + setvar(config_dir, "KEYD_CONFIG_DIR", "/etc/keyd"); + setvar(socket_file, "KEYD_SOCKET", "/var/run/keyd.socket"); + debug_level = atoi(getenv("KEYD_DEBUG") ? getenv("KEYD_DEBUG") : ""); - if (n == 1 && !strcmp(argv[0], "ping")) - return client_send_message(MSG_PING, ""); + dbg("Debug mode activated"); - for (i = 0; i < n; i++) { - const char *mapping = argv[i]; + atexit(cleanup); - if (!strcmp(argv[i], "reset")) { - client_send_message(MSG_RESET, ""); - continue; - } + signal(SIGTERM, exit); + signal(SIGINT, exit); + signal(SIGPIPE, SIG_IGN); + + if (argc >= 2) { + if (!strcmp(argv[1], "-l") || !strcmp(argv[1], "--list-keys")) + print_keys(); + else if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) + print_version(); + else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--monitor")) + monitor_flag = 1; + else if (!strcmp(argv[1], "-e") || !strcmp(argv[1], "--expression")) + eval_expressions(argv+2, argc-2); + else + print_help(); + + if (!monitor_flag) + exit(0); + } - if (client_send_message(MSG_MAPPING, mapping)) - rc = -1; - } - return rc; - ;; - } else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--monitor")) { - return monitor_loop(); - } else if (!strcmp(argv[1], "-l") || !strcmp(argv[1], "--list")) { - size_t i; - - for (i = 0; i < MAX_KEYS; i++) - if (keycode_table[i].name) { - const struct keycode_table_ent *ent - = &keycode_table[i]; - printf("%s\n", ent->name); - if (ent->alt_name) - printf("%s\n", - ent->alt_name); - if (ent->shifted_name) - printf("%s\n", - ent->shifted_name); - } - return 0; - } else { - if (strcmp(argv[1], "-h") - && strcmp(argv[1], "--help")) - fprintf(stderr, - "%s is not a valid option.\n", - argv[1]); - - fprintf(stderr, - "Usage: %s [options]\n\n" - "Options:\n" - "\t-m, --monitor monitor mode\n" - "\t-e, --expression <mapping> [<mapping>...] add the supplied mappings to the current config\n" - "\t-l, --list list all key names\n" - "\t-d, --daemonize fork and start as a daemon\n" - "\t-v, --version print version\n" - "\t-h, --help print this help message\n", argv[0]); - - return 0; - } - } + setvbuf(stdout, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOLBF, 0); - chgid(); - lock(); + if (monitor_flag) { + device_add_cb = monitor_add_cb; + device_remove_cb = monitor_remove_cb; + device_event_cb = monitor_event_cb; - if (daemonize_flag) - daemonize(); + if (isatty(1)) + set_echo(0); - signal(SIGINT, exit); - signal(SIGTERM, exit); - atexit(cleanup); + loop(1); + } else { + device_add_cb = daemon_add_cb; + device_remove_cb = daemon_remove_cb; + device_event_cb = daemon_event_cb; + + vkbd = vkbd_init(virtual_keyboard_name); - info("Starting keyd v%s (%s).", VERSION, GIT_COMMIT_HASH); - vkbd = vkbd_init(VIRTUAL_KEYBOARD_NAME); + printf("Starting keyd "VERSION"\n"); - main_loop(); + chgid(); + loop(0); + } } diff --git a/src/keyd.h b/src/keyd.h index 2e66c54a..82ff3123 100644 --- a/src/keyd.h +++ b/src/keyd.h @@ -1,25 +1,47 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ #ifndef KEYD_H #define KEYD_H -#include "keys.h" +#include <assert.h> +#include <ctype.h> +#include <fcntl.h> +#include <poll.h> +#include <signal.h> #include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <termios.h> +#include <unistd.h> +#include <time.h> +#include <grp.h> -#define MAX_KEYBOARDS 256 -extern uint8_t keystate[MAX_KEYS]; +#include "device.h" +#include "keyboard.h" +#include "vkbd.h" +#include "ipc.h" -extern int debug; +#define MAX_MESSAGE_SIZE 4096 -void dbg(const char *fmt, ...); +#define dbg(fmt, ...) { \ + if (debug_level) \ + fprintf(stderr, "DEBUG: %s:%d: "fmt"\n", __FILE__, __LINE__, ##__VA_ARGS__); \ +} -void send_key(int code, int pressed); -void reload_config(); -void reset_keyboards(); -void reset_vkbd(); +#define err(fmt, ...) snprintf(errstr, sizeof(errstr), fmt, ##__VA_ARGS__); -int evdev_get_keyboard_nodes(char **devs, int *ndevs); -int evdev_is_keyboard(const char *devnode); -int evdev_device_id(const char *devnode, uint16_t *vendor, uint16_t *product); -const char *evdev_device_name(const char *devnode); -int evdev_grab_keyboard(int fd); +extern int debug_level; +extern char errstr[2048]; +extern struct vkbd *vkbd; + +int create_server_socket(const char *socket_file); #endif diff --git a/src/keys.c b/src/keys.c index b3364c8c..844d8843 100644 --- a/src/keys.c +++ b/src/keys.c @@ -1,28 +1,271 @@ -/* Copyright © 2019 Raheman Vaiya. +/* + * keyd - A key remapping daemon. * - * 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 (including the next - * paragraph) 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. + * © 2019 Raheman Vaiya (see also: LICENSE). */ - #include <stdint.h> #include "keys.h" +const struct modifier_table_ent modifier_table[MAX_MOD] = { + {"control", MOD_CTRL, KEY_LEFTCTRL, KEY_RIGHTCTRL}, + {"shift", MOD_SHIFT, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}, + {"meta", MOD_SUPER, KEY_LEFTMETA, KEY_RIGHTMETA}, + + {"alt", MOD_ALT, KEY_LEFTALT, 0}, + {"altgr", MOD_ALT_GR, KEY_RIGHTALT, 0}, +}; + +const struct keycode_table_ent keycode_table[256] = { + [KEY_ESC] = { "esc", "escape", NULL }, + [KEY_1] = { "1", NULL, "!" }, + [KEY_2] = { "2", NULL, "@" }, + [KEY_3] = { "3", NULL, "#" }, + [KEY_4] = { "4", NULL, "$" }, + [KEY_5] = { "5", NULL, "%" }, + [KEY_6] = { "6", NULL, "^" }, + [KEY_7] = { "7", NULL, "&" }, + [KEY_8] = { "8", NULL, "*" }, + [KEY_9] = { "9", NULL, "(" }, + [KEY_0] = { "0", NULL, ")" }, + [KEY_MINUS] = { "-", "minus", "_" }, + [KEY_EQUAL] = { "=", "equal", "+" }, + [KEY_BACKSPACE] = { "backspace", NULL, NULL }, + [KEY_TAB] = { "tab", NULL, NULL }, + [KEY_Q] = { "q", NULL, "Q" }, + [KEY_W] = { "w", NULL, "W" }, + [KEY_E] = { "e", NULL, "E" }, + [KEY_R] = { "r", NULL, "R" }, + [KEY_T] = { "t", NULL, "T" }, + [KEY_Y] = { "y", NULL, "Y" }, + [KEY_U] = { "u", NULL, "U" }, + [KEY_I] = { "i", NULL, "I" }, + [KEY_O] = { "o", NULL, "O" }, + [KEY_P] = { "p", NULL, "P" }, + [KEY_LEFTBRACE] = { "[", "leftbrace", "{" }, + [KEY_RIGHTBRACE] = { "]", "rightbrace", "}" }, + [KEY_ENTER] = { "enter", NULL, NULL }, + [KEY_LEFTCTRL] = { "leftcontrol", "", NULL }, + [84] = { "iso-level3-shift", NULL, NULL }, //Oddly missing from input-event-codes.h, appears to be used as altgr in an english keymap on X + [KEY_A] = { "a", NULL, "A" }, + [KEY_S] = { "s", NULL, "S" }, + [KEY_D] = { "d", NULL, "D" }, + [KEY_F] = { "f", NULL, "F" }, + [KEY_G] = { "g", NULL, "G" }, + [KEY_H] = { "h", NULL, "H" }, + [KEY_J] = { "j", NULL, "J" }, + [KEY_K] = { "k", NULL, "K" }, + [KEY_L] = { "l", NULL, "L" }, + [KEY_SEMICOLON] = { ";", "semicolon", ":" }, + [KEY_APOSTROPHE] = { "'", "apostrophe", "\"" }, + [KEY_GRAVE] = { "`", "grave", "~" }, + [KEY_LEFTSHIFT] = { "leftshift", "", NULL }, + [KEY_BACKSLASH] = { "\\", "backslash", "|" }, + [KEY_Z] = { "z", NULL, "Z" }, + [KEY_X] = { "x", NULL, "X" }, + [KEY_C] = { "c", NULL, "C" }, + [KEY_V] = { "v", NULL, "V" }, + [KEY_B] = { "b", NULL, "B" }, + [KEY_N] = { "n", NULL, "N" }, + [KEY_M] = { "m", NULL, "M" }, + [KEY_COMMA] = { ",", "comma", "<" }, + [KEY_DOT] = { ".", "dot", ">" }, + [KEY_SLASH] = { "/", "slash", "?" }, + [KEY_RIGHTSHIFT] = { "rightshift", NULL, NULL }, + [KEY_KPASTERISK] = { "kpasterisk", NULL, NULL }, + [KEY_LEFTALT] = { "leftalt", "", NULL }, + [KEY_SPACE] = { "space", NULL, NULL }, + [KEY_CAPSLOCK] = { "capslock", NULL, NULL }, + [KEY_F1] = { "f1", NULL, NULL }, + [KEY_F2] = { "f2", NULL, NULL }, + [KEY_F3] = { "f3", NULL, NULL }, + [KEY_F4] = { "f4", NULL, NULL }, + [KEY_F5] = { "f5", NULL, NULL }, + [KEY_F6] = { "f6", NULL, NULL }, + [KEY_F7] = { "f7", NULL, NULL }, + [KEY_F8] = { "f8", NULL, NULL }, + [KEY_F9] = { "f9", NULL, NULL }, + [KEY_F10] = { "f10", NULL, NULL }, + [KEY_NUMLOCK] = { "numlock", NULL, NULL }, + [KEY_SCROLLLOCK] = { "scrolllock", NULL, NULL }, + [KEY_KP7] = { "kp7", NULL, NULL }, + [KEY_KP8] = { "kp8", NULL, NULL }, + [KEY_KP9] = { "kp9", NULL, NULL }, + [KEY_KPMINUS] = { "kpminus", NULL, NULL }, + [KEY_KP4] = { "kp4", NULL, NULL }, + [KEY_KP5] = { "kp5", NULL, NULL }, + [KEY_KP6] = { "kp6", NULL, NULL }, + [KEY_KPPLUS] = { "kpplus", NULL, NULL }, + [KEY_KP1] = { "kp1", NULL, NULL }, + [KEY_KP2] = { "kp2", NULL, NULL }, + [KEY_KP3] = { "kp3", NULL, NULL }, + [KEY_KP0] = { "kp0", NULL, NULL }, + [KEY_KPDOT] = { "kpdot", NULL, NULL }, + [KEY_ZENKAKUHANKAKU] = { "zenkakuhankaku", NULL, NULL }, + [KEY_102ND] = { "102nd", NULL, NULL }, + [KEY_F11] = { "f11", NULL, NULL }, + [KEY_F12] = { "f12", NULL, NULL }, + [KEY_RO] = { "ro", NULL, NULL }, + [KEY_KATAKANA] = { "katakana", NULL, NULL }, + [KEY_HIRAGANA] = { "hiragana", NULL, NULL }, + [KEY_HENKAN] = { "henkan", NULL, NULL }, + [KEY_KATAKANAHIRAGANA] = { "katakanahiragana", NULL, NULL }, + [KEY_MUHENKAN] = { "muhenkan", NULL, NULL }, + [KEY_KPJPCOMMA] = { "kpjpcomma", NULL, NULL }, + [KEY_KPENTER] = { "kpenter", NULL, NULL }, + [KEY_RIGHTCTRL] = { "rightcontrol", NULL, NULL }, + [KEY_KPSLASH] = { "kpslash", NULL, NULL }, + [KEY_SYSRQ] = { "sysrq", NULL, NULL }, + [KEY_RIGHTALT] = { "rightalt", NULL, NULL }, + [KEY_LINEFEED] = { "linefeed", NULL, NULL }, + [KEY_HOME] = { "home", NULL, NULL }, + [KEY_UP] = { "up", NULL, NULL }, + [KEY_PAGEUP] = { "pageup", NULL, NULL }, + [KEY_LEFT] = { "left", NULL, NULL }, + [KEY_RIGHT] = { "right", NULL, NULL }, + [KEY_END] = { "end", NULL, NULL }, + [KEY_DOWN] = { "down", NULL, NULL }, + [KEY_PAGEDOWN] = { "pagedown", NULL, NULL }, + [KEY_INSERT] = { "insert", NULL, NULL }, + [KEY_DELETE] = { "delete", NULL, NULL }, + [KEY_MACRO] = { "macro", NULL, NULL }, + [KEY_MUTE] = { "mute", NULL, NULL }, + [KEY_VOLUMEDOWN] = { "volumedown", NULL, NULL }, + [KEY_VOLUMEUP] = { "volumeup", NULL, NULL }, + [KEY_POWER] = { "power", NULL, NULL }, + [KEY_KPEQUAL] = { "kpequal", NULL, NULL }, + [KEY_KPPLUSMINUS] = { "kpplusminus", NULL, NULL }, + [KEY_PAUSE] = { "pause", NULL, NULL }, + [KEY_SCALE] = { "scale", NULL, NULL }, + [KEY_KPCOMMA] = { "kpcomma", NULL, NULL }, + [KEY_HANGEUL] = { "hangeul", NULL, NULL }, + [KEY_HANJA] = { "hanja", NULL, NULL }, + [KEY_YEN] = { "yen", NULL, NULL }, + [KEY_LEFTMETA] = { "leftmeta", "", NULL }, + [KEY_RIGHTMETA] = { "rightmeta", NULL, NULL }, + [KEY_COMPOSE] = { "compose", NULL, NULL }, + [KEY_STOP] = { "stop", NULL, NULL }, + [KEY_AGAIN] = { "again", NULL, NULL }, + [KEY_PROPS] = { "props", NULL, NULL }, + [KEY_UNDO] = { "undo", NULL, NULL }, + [KEY_FRONT] = { "front", NULL, NULL }, + [KEY_COPY] = { "copy", NULL, NULL }, + [KEY_OPEN] = { "open", NULL, NULL }, + [KEY_PASTE] = { "paste", NULL, NULL }, + [KEY_FIND] = { "find", NULL, NULL }, + [KEY_CUT] = { "cut", NULL, NULL }, + [KEY_HELP] = { "help", NULL, NULL }, + [KEY_MENU] = { "menu", NULL, NULL }, + [KEY_CALC] = { "calc", NULL, NULL }, + [KEY_SETUP] = { "setup", NULL, NULL }, + [KEY_SLEEP] = { "sleep", NULL, NULL }, + [KEY_WAKEUP] = { "wakeup", NULL, NULL }, + [KEY_FILE] = { "file", NULL, NULL }, + [KEY_SENDFILE] = { "sendfile", NULL, NULL }, + [KEY_DELETEFILE] = { "deletefile", NULL, NULL }, + [KEY_XFER] = { "xfer", NULL, NULL }, + [KEY_PROG1] = { "prog1", NULL, NULL }, + [KEY_PROG2] = { "prog2", NULL, NULL }, + [KEY_WWW] = { "www", NULL, NULL }, + [KEY_MSDOS] = { "msdos", NULL, NULL }, + [KEY_COFFEE] = { "coffee", NULL, NULL }, + [KEY_ROTATE_DISPLAY] = { "display", NULL, NULL }, + [KEY_CYCLEWINDOWS] = { "cyclewindows", NULL, NULL }, + [KEY_MAIL] = { "mail", NULL, NULL }, + [KEY_BOOKMARKS] = { "bookmarks", NULL, NULL }, + [KEY_COMPUTER] = { "computer", NULL, NULL }, + [KEY_BACK] = { "back", NULL, NULL }, + [KEY_FORWARD] = { "forward", NULL, NULL }, + [KEY_CLOSECD] = { "closecd", NULL, NULL }, + [KEY_EJECTCD] = { "ejectcd", NULL, NULL }, + [KEY_EJECTCLOSECD] = { "ejectclosecd", NULL, NULL }, + [KEY_NEXTSONG] = { "nextsong", NULL, NULL }, + [KEY_PLAYPAUSE] = { "playpause", NULL, NULL }, + [KEY_PREVIOUSSONG] = { "previoussong", NULL, NULL }, + [KEY_STOPCD] = { "stopcd", NULL, NULL }, + [KEY_RECORD] = { "record", NULL, NULL }, + [KEY_REWIND] = { "rewind", NULL, NULL }, + [KEY_PHONE] = { "phone", NULL, NULL }, + [KEY_ISO] = { "iso", NULL, NULL }, + [KEY_CONFIG] = { "config", NULL, NULL }, + [KEY_HOMEPAGE] = { "homepage", NULL, NULL }, + [KEY_REFRESH] = { "refresh", NULL, NULL }, + [KEY_EXIT] = { "exit", NULL, NULL }, + [KEY_MOVE] = { "move", NULL, NULL }, + [KEY_EDIT] = { "edit", NULL, NULL }, + [KEY_SCROLLUP] = { "scrollup", NULL, NULL }, + [KEY_SCROLLDOWN] = { "scrolldown", NULL, NULL }, + [KEY_KPLEFTPAREN] = { "kpleftparen", NULL, NULL }, + [KEY_KPRIGHTPAREN] = { "kprightparen", NULL, NULL }, + [KEY_NEW] = { "new", NULL, NULL }, + [KEY_REDO] = { "redo", NULL, NULL }, + [KEY_F13] = { "f13", NULL, NULL }, + [KEY_F14] = { "f14", NULL, NULL }, + [KEY_F15] = { "f15", NULL, NULL }, + [KEY_F16] = { "f16", NULL, NULL }, + [KEY_F17] = { "f17", NULL, NULL }, + [KEY_F18] = { "f18", NULL, NULL }, + [KEY_F19] = { "f19", NULL, NULL }, + [KEY_F20] = { "f20", NULL, NULL }, + [KEY_F21] = { "f21", NULL, NULL }, + [KEY_F22] = { "f22", NULL, NULL }, + [KEY_F23] = { "f23", NULL, NULL }, + [KEY_F24] = { "f24", NULL, NULL }, + [KEY_PLAYCD] = { "playcd", NULL, NULL }, + [KEY_PAUSECD] = { "pausecd", NULL, NULL }, + [KEY_PROG3] = { "prog3", NULL, NULL }, + [KEY_PROG4] = { "prog4", NULL, NULL }, + [KEY_DASHBOARD] = { "dashboard", NULL, NULL }, + [KEY_SUSPEND] = { "suspend", NULL, NULL }, + [KEY_CLOSE] = { "close", NULL, NULL }, + [KEY_PLAY] = { "play", NULL, NULL }, + [KEY_FASTFORWARD] = { "fastforward", NULL, NULL }, + [KEY_BASSBOOST] = { "bassboost", NULL, NULL }, + [KEY_PRINT] = { "print", NULL, NULL }, + [KEY_HP] = { "hp", NULL, NULL }, + [KEY_CAMERA] = { "camera", NULL, NULL }, + [KEY_SOUND] = { "sound", NULL, NULL }, + [KEY_QUESTION] = { "question", NULL, NULL }, + [KEY_EMAIL] = { "email", NULL, NULL }, + [KEY_CHAT] = { "chat", NULL, NULL }, + [KEY_SEARCH] = { "search", NULL, NULL }, + [KEY_CONNECT] = { "connect", NULL, NULL }, + [KEY_FINANCE] = { "finance", NULL, NULL }, + [KEY_SPORT] = { "sport", NULL, NULL }, + [KEY_SHOP] = { "shop", NULL, NULL }, + [KEY_ALTERASE] = { "alterase", NULL, NULL }, + [KEY_CANCEL] = { "cancel", NULL, NULL }, + [KEY_BRIGHTNESSDOWN] = { "brightnessdown", NULL, NULL }, + [KEY_BRIGHTNESSUP] = { "brightnessup", NULL, NULL }, + [KEY_MEDIA] = { "media", NULL, NULL }, + [KEY_SWITCHVIDEOMODE] = { "switchvideomode", NULL, NULL }, + [KEY_KBDILLUMTOGGLE] = { "kbdillumtoggle", NULL, NULL }, + [KEY_KBDILLUMDOWN] = { "kbdillumdown", NULL, NULL }, + [KEY_KBDILLUMUP] = { "kbdillumup", NULL, NULL }, + [KEY_SEND] = { "send", NULL, NULL }, + [KEY_REPLY] = { "reply", NULL, NULL }, + [KEY_FORWARDMAIL] = { "forwardmail", NULL, NULL }, + [KEY_SAVE] = { "save", NULL, NULL }, + [KEY_DOCUMENTS] = { "documents", NULL, NULL }, + [KEY_BATTERY] = { "battery", NULL, NULL }, + [KEY_BLUETOOTH] = { "bluetooth", NULL, NULL }, + [KEY_WLAN] = { "wlan", NULL, NULL }, + [KEY_UWB] = { "uwb", NULL, NULL }, + [KEY_UNKNOWN] = { "unknown", NULL, NULL }, + [KEY_VIDEO_NEXT] = { "next", NULL, NULL }, + [KEY_VIDEO_PREV] = { "prev", NULL, NULL }, + [KEY_BRIGHTNESS_CYCLE] = { "cycle", NULL, NULL }, + [KEY_BRIGHTNESS_AUTO] = { "auto", NULL, NULL }, + [KEY_DISPLAY_OFF] = { "off", NULL, NULL }, + [KEY_WWAN] = { "wwan", NULL, NULL }, + [KEY_RFKILL] = { "rfkill", NULL, NULL }, + [KEY_MICMUTE] = { "micmute", NULL, NULL }, + [KEY_LEFT_MOUSE] = { "leftmouse", NULL, NULL }, + [KEY_RIGHT_MOUSE] = { "rightmouse", NULL, NULL }, + [KEY_MIDDLE_MOUSE] = { "middlemouse", NULL, NULL }, + [KEY_MOUSE_1] = { "mouse1", NULL, NULL }, + [KEY_MOUSE_2] = { "mouse2", NULL, NULL }, +}; + uint8_t keycode_to_mod(uint8_t code) { switch (code) { @@ -117,4 +360,3 @@ int parse_modset(const char *s, uint8_t *mods) return 0; } - diff --git a/src/keys.h b/src/keys.h index d4751aa0..9c7076df 100644 --- a/src/keys.h +++ b/src/keys.h @@ -1,25 +1,8 @@ -/* Copyright © 2019 Raheman Vaiya. +/* + * keyd - A key remapping daemon. * - * 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 (including the next - * paragraph) 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. + * © 2019 Raheman Vaiya (see also: LICENSE). */ - #ifndef _KEYS_H_ #define _KEYS_H_ #define _KEYS_H_ @@ -39,13 +22,11 @@ #define MOD_SUPER 0x2 #define MOD_ALT 0x1 -#define KEY_NOOP 0x27b +#define MAX_MOD 5 uint8_t keycode_to_mod(uint8_t code); int parse_modset(const char *s, uint8_t *mods); -const char *modstring(uint8_t mods); - struct keycode_table_ent { const char *name; const char *alt_name; @@ -60,261 +41,14 @@ struct modifier_table_ent { uint8_t code2; /* May be 0. */ }; -static struct modifier_table_ent modifier_table[] = { - {"control", MOD_CTRL, KEY_LEFTCTRL, KEY_RIGHTCTRL}, - {"shift", MOD_SHIFT, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}, - {"meta", MOD_SUPER, KEY_LEFTMETA, KEY_RIGHTMETA}, - - {"alt", MOD_ALT, KEY_LEFTALT, 0}, - {"altgr", MOD_ALT_GR, KEY_RIGHTALT, 0}, -}; - -static const struct keycode_table_ent keycode_table[256] = { - [KEY_ESC] = { "esc", "escape", NULL }, - [KEY_1] = { "1", NULL, "!" }, - [KEY_2] = { "2", NULL, "@" }, - [KEY_3] = { "3", NULL, "#" }, - [KEY_4] = { "4", NULL, "$" }, - [KEY_5] = { "5", NULL, "%" }, - [KEY_6] = { "6", NULL, "^" }, - [KEY_7] = { "7", NULL, "&" }, - [KEY_8] = { "8", NULL, "*" }, - [KEY_9] = { "9", NULL, "(" }, - [KEY_0] = { "0", NULL, ")" }, - [KEY_MINUS] = { "-", "minus", "_" }, - [KEY_EQUAL] = { "=", "equal", "+" }, - [KEY_BACKSPACE] = { "backspace", NULL, NULL }, - [KEY_TAB] = { "tab", NULL, NULL }, - [KEY_Q] = { "q", NULL, "Q" }, - [KEY_W] = { "w", NULL, "W" }, - [KEY_E] = { "e", NULL, "E" }, - [KEY_R] = { "r", NULL, "R" }, - [KEY_T] = { "t", NULL, "T" }, - [KEY_Y] = { "y", NULL, "Y" }, - [KEY_U] = { "u", NULL, "U" }, - [KEY_I] = { "i", NULL, "I" }, - [KEY_O] = { "o", NULL, "O" }, - [KEY_P] = { "p", NULL, "P" }, - [KEY_LEFTBRACE] = { "[", "leftbrace", "{" }, - [KEY_RIGHTBRACE] = { "]", "rightbrace", "}" }, - [KEY_ENTER] = { "enter", NULL, NULL }, - [KEY_LEFTCTRL] = { "leftcontrol", "", NULL }, - [84] = { "iso-level3-shift", NULL, NULL }, //Oddly missing from input-event-codes.h, appears to be used as altgr in an english keymap on X - [KEY_A] = { "a", NULL, "A" }, - [KEY_S] = { "s", NULL, "S" }, - [KEY_D] = { "d", NULL, "D" }, - [KEY_F] = { "f", NULL, "F" }, - [KEY_G] = { "g", NULL, "G" }, - [KEY_H] = { "h", NULL, "H" }, - [KEY_J] = { "j", NULL, "J" }, - [KEY_K] = { "k", NULL, "K" }, - [KEY_L] = { "l", NULL, "L" }, - [KEY_SEMICOLON] = { ";", "semicolon", ":" }, - [KEY_APOSTROPHE] = { "'", "apostrophe", "\"" }, - [KEY_GRAVE] = { "`", "grave", "~" }, - [KEY_LEFTSHIFT] = { "leftshift", "", NULL }, - [KEY_BACKSLASH] = { "\\", "backslash", "|" }, - [KEY_Z] = { "z", NULL, "Z" }, - [KEY_X] = { "x", NULL, "X" }, - [KEY_C] = { "c", NULL, "C" }, - [KEY_V] = { "v", NULL, "V" }, - [KEY_B] = { "b", NULL, "B" }, - [KEY_N] = { "n", NULL, "N" }, - [KEY_M] = { "m", NULL, "M" }, - [KEY_COMMA] = { ",", "comma", "<" }, - [KEY_DOT] = { ".", "dot", ">" }, - [KEY_SLASH] = { "/", "slash", "?" }, - [KEY_RIGHTSHIFT] = { "rightshift", NULL, NULL }, - [KEY_KPASTERISK] = { "kpasterisk", NULL, NULL }, - [KEY_LEFTALT] = { "leftalt", "", NULL }, - [KEY_SPACE] = { "space", NULL, NULL }, - [KEY_CAPSLOCK] = { "capslock", NULL, NULL }, - [KEY_F1] = { "f1", NULL, NULL }, - [KEY_F2] = { "f2", NULL, NULL }, - [KEY_F3] = { "f3", NULL, NULL }, - [KEY_F4] = { "f4", NULL, NULL }, - [KEY_F5] = { "f5", NULL, NULL }, - [KEY_F6] = { "f6", NULL, NULL }, - [KEY_F7] = { "f7", NULL, NULL }, - [KEY_F8] = { "f8", NULL, NULL }, - [KEY_F9] = { "f9", NULL, NULL }, - [KEY_F10] = { "f10", NULL, NULL }, - [KEY_NUMLOCK] = { "numlock", NULL, NULL }, - [KEY_SCROLLLOCK] = { "scrolllock", NULL, NULL }, - [KEY_KP7] = { "kp7", NULL, NULL }, - [KEY_KP8] = { "kp8", NULL, NULL }, - [KEY_KP9] = { "kp9", NULL, NULL }, - [KEY_KPMINUS] = { "kpminus", NULL, NULL }, - [KEY_KP4] = { "kp4", NULL, NULL }, - [KEY_KP5] = { "kp5", NULL, NULL }, - [KEY_KP6] = { "kp6", NULL, NULL }, - [KEY_KPPLUS] = { "kpplus", NULL, NULL }, - [KEY_KP1] = { "kp1", NULL, NULL }, - [KEY_KP2] = { "kp2", NULL, NULL }, - [KEY_KP3] = { "kp3", NULL, NULL }, - [KEY_KP0] = { "kp0", NULL, NULL }, - [KEY_KPDOT] = { "kpdot", NULL, NULL }, - [KEY_ZENKAKUHANKAKU] = { "zenkakuhankaku", NULL, NULL }, - [KEY_102ND] = { "102nd", NULL, NULL }, - [KEY_F11] = { "f11", NULL, NULL }, - [KEY_F12] = { "f12", NULL, NULL }, - [KEY_RO] = { "ro", NULL, NULL }, - [KEY_KATAKANA] = { "katakana", NULL, NULL }, - [KEY_HIRAGANA] = { "hiragana", NULL, NULL }, - [KEY_HENKAN] = { "henkan", NULL, NULL }, - [KEY_KATAKANAHIRAGANA] = { "katakanahiragana", NULL, NULL }, - [KEY_MUHENKAN] = { "muhenkan", NULL, NULL }, - [KEY_KPJPCOMMA] = { "kpjpcomma", NULL, NULL }, - [KEY_KPENTER] = { "kpenter", NULL, NULL }, - [KEY_RIGHTCTRL] = { "rightcontrol", NULL, NULL }, - [KEY_KPSLASH] = { "kpslash", NULL, NULL }, - [KEY_SYSRQ] = { "sysrq", NULL, NULL }, - [KEY_RIGHTALT] = { "rightalt", NULL, NULL }, - [KEY_LINEFEED] = { "linefeed", NULL, NULL }, - [KEY_HOME] = { "home", NULL, NULL }, - [KEY_UP] = { "up", NULL, NULL }, - [KEY_PAGEUP] = { "pageup", NULL, NULL }, - [KEY_LEFT] = { "left", NULL, NULL }, - [KEY_RIGHT] = { "right", NULL, NULL }, - [KEY_END] = { "end", NULL, NULL }, - [KEY_DOWN] = { "down", NULL, NULL }, - [KEY_PAGEDOWN] = { "pagedown", NULL, NULL }, - [KEY_INSERT] = { "insert", NULL, NULL }, - [KEY_DELETE] = { "delete", NULL, NULL }, - [KEY_MACRO] = { "macro", NULL, NULL }, - [KEY_MUTE] = { "mute", NULL, NULL }, - [KEY_VOLUMEDOWN] = { "volumedown", NULL, NULL }, - [KEY_VOLUMEUP] = { "volumeup", NULL, NULL }, - [KEY_POWER] = { "power", NULL, NULL }, - [KEY_KPEQUAL] = { "kpequal", NULL, NULL }, - [KEY_KPPLUSMINUS] = { "kpplusminus", NULL, NULL }, - [KEY_PAUSE] = { "pause", NULL, NULL }, - [KEY_SCALE] = { "scale", NULL, NULL }, - [KEY_KPCOMMA] = { "kpcomma", NULL, NULL }, - [KEY_HANGEUL] = { "hangeul", NULL, NULL }, - [KEY_HANJA] = { "hanja", NULL, NULL }, - [KEY_YEN] = { "yen", NULL, NULL }, - [KEY_LEFTMETA] = { "leftmeta", "", NULL }, - [KEY_RIGHTMETA] = { "rightmeta", NULL, NULL }, - [KEY_COMPOSE] = { "compose", NULL, NULL }, - [KEY_STOP] = { "stop", NULL, NULL }, - [KEY_AGAIN] = { "again", NULL, NULL }, - [KEY_PROPS] = { "props", NULL, NULL }, - [KEY_UNDO] = { "undo", NULL, NULL }, - [KEY_FRONT] = { "front", NULL, NULL }, - [KEY_COPY] = { "copy", NULL, NULL }, - [KEY_OPEN] = { "open", NULL, NULL }, - [KEY_PASTE] = { "paste", NULL, NULL }, - [KEY_FIND] = { "find", NULL, NULL }, - [KEY_CUT] = { "cut", NULL, NULL }, - [KEY_HELP] = { "help", NULL, NULL }, - [KEY_MENU] = { "menu", NULL, NULL }, - [KEY_CALC] = { "calc", NULL, NULL }, - [KEY_SETUP] = { "setup", NULL, NULL }, - [KEY_SLEEP] = { "sleep", NULL, NULL }, - [KEY_WAKEUP] = { "wakeup", NULL, NULL }, - [KEY_FILE] = { "file", NULL, NULL }, - [KEY_SENDFILE] = { "sendfile", NULL, NULL }, - [KEY_DELETEFILE] = { "deletefile", NULL, NULL }, - [KEY_XFER] = { "xfer", NULL, NULL }, - [KEY_PROG1] = { "prog1", NULL, NULL }, - [KEY_PROG2] = { "prog2", NULL, NULL }, - [KEY_WWW] = { "www", NULL, NULL }, - [KEY_MSDOS] = { "msdos", NULL, NULL }, - [KEY_COFFEE] = { "coffee", NULL, NULL }, - [KEY_ROTATE_DISPLAY] = { "display", NULL, NULL }, - [KEY_CYCLEWINDOWS] = { "cyclewindows", NULL, NULL }, - [KEY_MAIL] = { "mail", NULL, NULL }, - [KEY_BOOKMARKS] = { "bookmarks", NULL, NULL }, - [KEY_COMPUTER] = { "computer", NULL, NULL }, - [KEY_BACK] = { "back", NULL, NULL }, - [KEY_FORWARD] = { "forward", NULL, NULL }, - [KEY_CLOSECD] = { "closecd", NULL, NULL }, - [KEY_EJECTCD] = { "ejectcd", NULL, NULL }, - [KEY_EJECTCLOSECD] = { "ejectclosecd", NULL, NULL }, - [KEY_NEXTSONG] = { "nextsong", NULL, NULL }, - [KEY_PLAYPAUSE] = { "playpause", NULL, NULL }, - [KEY_PREVIOUSSONG] = { "previoussong", NULL, NULL }, - [KEY_STOPCD] = { "stopcd", NULL, NULL }, - [KEY_RECORD] = { "record", NULL, NULL }, - [KEY_REWIND] = { "rewind", NULL, NULL }, - [KEY_PHONE] = { "phone", NULL, NULL }, - [KEY_ISO] = { "iso", NULL, NULL }, - [KEY_CONFIG] = { "config", NULL, NULL }, - [KEY_HOMEPAGE] = { "homepage", NULL, NULL }, - [KEY_REFRESH] = { "refresh", NULL, NULL }, - [KEY_EXIT] = { "exit", NULL, NULL }, - [KEY_MOVE] = { "move", NULL, NULL }, - [KEY_EDIT] = { "edit", NULL, NULL }, - [KEY_SCROLLUP] = { "scrollup", NULL, NULL }, - [KEY_SCROLLDOWN] = { "scrolldown", NULL, NULL }, - [KEY_KPLEFTPAREN] = { "kpleftparen", NULL, NULL }, - [KEY_KPRIGHTPAREN] = { "kprightparen", NULL, NULL }, - [KEY_NEW] = { "new", NULL, NULL }, - [KEY_REDO] = { "redo", NULL, NULL }, - [KEY_F13] = { "f13", NULL, NULL }, - [KEY_F14] = { "f14", NULL, NULL }, - [KEY_F15] = { "f15", NULL, NULL }, - [KEY_F16] = { "f16", NULL, NULL }, - [KEY_F17] = { "f17", NULL, NULL }, - [KEY_F18] = { "f18", NULL, NULL }, - [KEY_F19] = { "f19", NULL, NULL }, - [KEY_F20] = { "f20", NULL, NULL }, - [KEY_F21] = { "f21", NULL, NULL }, - [KEY_F22] = { "f22", NULL, NULL }, - [KEY_F23] = { "f23", NULL, NULL }, - [KEY_F24] = { "f24", NULL, NULL }, - [KEY_PLAYCD] = { "playcd", NULL, NULL }, - [KEY_PAUSECD] = { "pausecd", NULL, NULL }, - [KEY_PROG3] = { "prog3", NULL, NULL }, - [KEY_PROG4] = { "prog4", NULL, NULL }, - [KEY_DASHBOARD] = { "dashboard", NULL, NULL }, - [KEY_SUSPEND] = { "suspend", NULL, NULL }, - [KEY_CLOSE] = { "close", NULL, NULL }, - [KEY_PLAY] = { "play", NULL, NULL }, - [KEY_FASTFORWARD] = { "fastforward", NULL, NULL }, - [KEY_BASSBOOST] = { "bassboost", NULL, NULL }, - [KEY_PRINT] = { "print", NULL, NULL }, - [KEY_HP] = { "hp", NULL, NULL }, - [KEY_CAMERA] = { "camera", NULL, NULL }, - [KEY_SOUND] = { "sound", NULL, NULL }, - [KEY_QUESTION] = { "question", NULL, NULL }, - [KEY_EMAIL] = { "email", NULL, NULL }, - [KEY_CHAT] = { "chat", NULL, NULL }, - [KEY_SEARCH] = { "search", NULL, NULL }, - [KEY_CONNECT] = { "connect", NULL, NULL }, - [KEY_FINANCE] = { "finance", NULL, NULL }, - [KEY_SPORT] = { "sport", NULL, NULL }, - [KEY_SHOP] = { "shop", NULL, NULL }, - [KEY_ALTERASE] = { "alterase", NULL, NULL }, - [KEY_CANCEL] = { "cancel", NULL, NULL }, - [KEY_BRIGHTNESSDOWN] = { "brightnessdown", NULL, NULL }, - [KEY_BRIGHTNESSUP] = { "brightnessup", NULL, NULL }, - [KEY_MEDIA] = { "media", NULL, NULL }, - [KEY_SWITCHVIDEOMODE] = { "switchvideomode", NULL, NULL }, - [KEY_KBDILLUMTOGGLE] = { "kbdillumtoggle", NULL, NULL }, - [KEY_KBDILLUMDOWN] = { "kbdillumdown", NULL, NULL }, - [KEY_KBDILLUMUP] = { "kbdillumup", NULL, NULL }, - [KEY_SEND] = { "send", NULL, NULL }, - [KEY_REPLY] = { "reply", NULL, NULL }, - [KEY_FORWARDMAIL] = { "forwardmail", NULL, NULL }, - [KEY_SAVE] = { "save", NULL, NULL }, - [KEY_DOCUMENTS] = { "documents", NULL, NULL }, - [KEY_BATTERY] = { "battery", NULL, NULL }, - [KEY_BLUETOOTH] = { "bluetooth", NULL, NULL }, - [KEY_WLAN] = { "wlan", NULL, NULL }, - [KEY_UWB] = { "uwb", NULL, NULL }, - [KEY_UNKNOWN] = { "unknown", NULL, NULL }, - [KEY_VIDEO_NEXT] = { "next", NULL, NULL }, - [KEY_VIDEO_PREV] = { "prev", NULL, NULL }, - [KEY_BRIGHTNESS_CYCLE] = { "cycle", NULL, NULL }, - [KEY_BRIGHTNESS_AUTO] = { "auto", NULL, NULL }, - [KEY_DISPLAY_OFF] = { "off", NULL, NULL }, - [KEY_WWAN] = { "wwan", NULL, NULL }, - [KEY_RFKILL] = { "rfkill", NULL, NULL }, - [KEY_MICMUTE] = { "micmute", NULL, NULL } -}; +/* Move evdev mouse codes into byte range. */ +#define KEY_LEFT_MOUSE KEY_MICMUTE+1 +#define KEY_RIGHT_MOUSE KEY_MICMUTE+2 +#define KEY_MIDDLE_MOUSE KEY_MICMUTE+3 +#define KEY_MOUSE_1 KEY_MICMUTE+4 +#define KEY_MOUSE_2 KEY_MICMUTE+5 -#define MAX_KEYS (sizeof(keycode_table) / sizeof(keycode_table[0])) +extern const struct modifier_table_ent modifier_table[MAX_MOD]; +extern const struct keycode_table_ent keycode_table[256]; #endif diff --git a/src/layer.h b/src/layer.h index b445535d..f1d0cf56 100644 --- a/src/layer.h +++ b/src/layer.h @@ -1,10 +1,29 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ #ifndef __H_LAYER_ #define __H_LAYER_ #include "keys.h" #include "descriptor.h" -#define MAX_LAYER_NAME_LEN 32 +#define MAX_LAYER_NAME_LEN 32 +#define MAX_COMPOSITE_LAYERS 8 +#define MAX_TIMEOUTS 32 + +#define MAX_MACRO_SIZE 128 +#define MAX_MACROS 64 + +#define LT_NORMAL 0 +#define LT_LAYOUT 1 +#define LT_COMPOSITE 2 + +#define LF_ACTIVE 0x1 +#define LF_TOGGLE 0x2 +#define LF_ONESHOT 0x4 +#define LF_ONESHOT_HELD 0x8 /* * A layer is a map from keycodes to descriptors. It may optionally @@ -17,10 +36,56 @@ struct layer { char name[MAX_LAYER_NAME_LEN]; - int is_layout; + size_t nr_layers; + int layers[MAX_COMPOSITE_LAYERS]; + + int type; uint8_t mods; - struct descriptor keymap[MAX_KEYS]; + struct descriptor keymap[256]; + + /* state */ + uint8_t flags; + long activation_time; +}; + +struct timeout { + uint16_t timeout; + struct descriptor d1; + struct descriptor d2; +}; + +struct macro_entry { + enum { + MACRO_KEYSEQUENCE, + MACRO_HOLD, + MACRO_RELEASE, + MACRO_TIMEOUT + } type; + + uint8_t code; + uint8_t mods; + uint16_t timeout; +}; + +/* + * A series of key sequences optionally punctuated by + * timeouts + */ +struct macro { + struct macro_entry entries[MAX_MACRO_SIZE]; + size_t sz; +}; + +struct layer_table { + struct layer layers[MAX_LAYERS]; + size_t nr; + + struct timeout timeouts[MAX_TIMEOUTS]; + struct macro macros[MAX_MACROS]; + + size_t nr_macros; + size_t nr_timeouts; }; #endif diff --git a/src/server.c b/src/server.c deleted file mode 100644 index c6508a9f..00000000 --- a/src/server.c +++ /dev/null @@ -1,111 +0,0 @@ -#include <stdio.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <string.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <unistd.h> -#include <stdlib.h> - -#include "config.h" -#include "server.h" -#include "keyd.h" -#include "keyboard.h" -#include "error.h" - -static int execute_mapping(const char *exp, char response[MAX_RESPONSE_SIZE]) -{ - if (!active_keyboard) { - fprintf(stderr, "Failed to determine active keyboard\n"); - return -1; - } - - if (kbd_execute_expression(active_keyboard, exp) < 0) { - sprintf(response, "ERROR parsing %s: %s\n", exp, errstr); - return -1; - } - - return 0; -} - -static int process_message(uint8_t type, - const char *payload, - char response[MAX_RESPONSE_SIZE]) -{ - switch (type) { - case MSG_PING: - strcpy(response, "PONG\n"); - break; - case MSG_RESET: - reset_keyboards(); - break; - case MSG_RELOAD: - reload_config(); - break; - case MSG_MAPPING: - return execute_mapping(payload, response); - break; - } - - return 0; -} - -int create_server_socket() -{ - int sd = socket(AF_UNIX, SOCK_SEQPACKET, 0); - struct sockaddr_un addr = {0}; - - if (sd < 0) { - perror("socket"); - exit(-1); - } - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, SOCKET, sizeof(addr.sun_path)-1); - - unlink(SOCKET); - if (bind(sd, (struct sockaddr *) &addr, sizeof addr) < 0) { - perror("bind"); - exit(-1); - } - - if (listen(sd, 20) < 0) { - perror("listen"); - exit(-1); - } - - chmod(SOCKET, 0660); - fcntl(sd, F_SETFL, O_NONBLOCK); - return sd; -} - -void server_process_connections(int sd) -{ - char payload[MAX_REQUEST_SIZE]; - char response[MAX_RESPONSE_SIZE] = ""; - int con; - - while((con = accept(sd, NULL, NULL)) > 0) { - int rc; - int sz; - uint8_t type; - - if (read(con, &type, sizeof(type)) < 0) { - perror("read"); - exit(-1); - } - - sz = read(con, &payload, sizeof(payload)); - if (sz <= 0) { - perror("read"); - exit(-1); - } - - payload[sz-1] = 0; - - rc = process_message(type, payload, response); - - write(con, response, strlen(response)); - write(con, &rc, sizeof rc); - close(con); - } -} diff --git a/src/server.h b/src/server.h deleted file mode 100644 index ac1f9f3c..00000000 --- a/src/server.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef SERVER_H -#define SERVER_H - -#include <stdint.h> - -#define MSG_PING 1 -#define MSG_MAPPING 2 -#define MSG_RELOAD 3 -#define MSG_RESET 4 - -#define MAX_RESPONSE_SIZE 4096 -#define MAX_REQUEST_SIZE 1024 - -int create_server_socket(); -void server_process_connections(int sd); - -int client_send_message(uint8_t type, const char *payload); -#endif diff --git a/src/vkbd.h b/src/vkbd.h index 8884d9fc..bb72148c 100644 --- a/src/vkbd.h +++ b/src/vkbd.h @@ -1,3 +1,8 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ #ifndef VIRTUAL_KEYBOARD_H #define VIRTUAL_KEYBOARD_H diff --git a/src/vkbd/stdout.c b/src/vkbd/stdout.c index 23dffec2..49424475 100644 --- a/src/vkbd/stdout.c +++ b/src/vkbd/stdout.c @@ -1,3 +1,8 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ /* Build with make vkbd-stdout. */ #include <stdio.h> diff --git a/src/vkbd/uinput.c b/src/vkbd/uinput.c index 3349cf1f..5a532eea 100644 --- a/src/vkbd/uinput.c +++ b/src/vkbd/uinput.c @@ -1,3 +1,8 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + */ #include <stdio.h> #include <string.h> #include <sys/ioctl.h> @@ -49,7 +54,7 @@ static int create_virtual_pointer(const char *name) memset(&usetup, 0, sizeof(usetup)); usetup.id.bustype = BUS_USB; usetup.id.vendor = 0x0FAC; - usetup.id.product = 0x0ADE; + usetup.id.product = 0x1ADE; strcpy(usetup.name, name); ioctl(fd, UI_DEV_SETUP, &usetup); @@ -73,7 +78,7 @@ static int create_virtual_keyboard(const char *name) ioctl(fd, UI_SET_EVBIT, EV_REL); ioctl(fd, UI_SET_EVBIT, EV_SYN); - for (code = 0; code < MAX_KEYS; code++) { + for (code = 0; code < 256; code++) { if (keycode_table[code].name) ioctl(fd, UI_SET_KEYBIT, code); } @@ -204,6 +209,8 @@ void vkbd_send_key(const struct vkbd *vkbd, uint8_t code, int state) void free_vkbd(struct vkbd *vkbd) { - close(vkbd->fd); - free(vkbd); + if (vkbd) { + close(vkbd->fd); + free(vkbd); + } } diff --git a/src/vkbd/usb-gadget.c b/src/vkbd/usb-gadget.c index 587ef562..a9b8cc9d 100644 --- a/src/vkbd/usb-gadget.c +++ b/src/vkbd/usb-gadget.c @@ -1,3 +1,9 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + * © 2021 Giorgi Chavchanidze + */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> diff --git a/src/vkbd/usb-gadget.h b/src/vkbd/usb-gadget.h index fc1a8a6d..73c6d543 100644 --- a/src/vkbd/usb-gadget.h +++ b/src/vkbd/usb-gadget.h @@ -1,3 +1,9 @@ +/* + * keyd - A key remapping daemon. + * + * © 2019 Raheman Vaiya (see also: LICENSE). + * © 2021 Giorgi Chavchanidze + */ #ifndef _USBGADGET_H_ #define _USBGADGET_H_ #define _USBGADGET_H_ diff --git a/t/layer.t b/t/layer.t index 72c41e47..fc8f134f 100644 --- a/t/layer.t +++ b/t/layer.t @@ -7,11 +7,9 @@ b up control down shift down -control up shift up a down a up -control down shift down shift up control up diff --git a/t/layer4.t b/t/layer4.t index 02a7ef80..dc39bbb5 100644 --- a/t/layer4.t +++ b/t/layer4.t @@ -1,13 +1,13 @@ s down meta down -c down -c up +i down +i up meta up s up shift down meta down -c down -c up +i down +i up meta up shift up diff --git a/t/layout.t b/t/layout.t index 6400905a..8128a64a 100644 --- a/t/layout.t +++ b/t/layout.t @@ -1,17 +1,27 @@ -8 down -8 up -a down -a up -8 down -8 up +- down +- up +x down +x up +- down +- up +alt down 9 down 9 up -a down -a up +alt up +x down +x up -b down -b up -8 down -8 up -a down -a up +o down +o up +alt down +control down +shift down +meta down +x down +x up +control up +shift up +meta up +alt up +x down +x up diff --git a/t/layout2.t b/t/layout2.t index 7364952c..775aedf1 100644 --- a/t/layout2.t +++ b/t/layout2.t @@ -1,19 +1,17 @@ - down - up -a down -a up +x down +x up control down -a down -a up +x down +x up control up - down - up -c down -c up -control down -control up -b down -b up +o down +o up control down +o down +o up control up diff --git a/t/oneshot.t b/t/oneshot.t index 163dab9b..f1b20ab2 100644 --- a/t/oneshot.t +++ b/t/oneshot.t @@ -1,9 +1,9 @@ -1 down -1 up -a down -a up +c down +c up +b down +b up control down -a down +b down control up -a up +b up diff --git a/t/oneshot10.t b/t/oneshot10.t index a7404797..c42f3d35 100644 --- a/t/oneshot10.t +++ b/t/oneshot10.t @@ -11,7 +11,5 @@ o down o up n down n up -shift down -shift up x down x up diff --git a/t/oneshot12.t b/t/oneshot12.t new file mode 100644 index 00000000..a529264e --- /dev/null +++ b/t/oneshot12.t @@ -0,0 +1,23 @@ +2 down +2 up +x down +x up +2 down +a down +a up +2 up +x down +x up + +shift down +shift up +o down +o up +n down +n up +shift down +a down +a up +shift up +x down +x up diff --git a/t/oneshot14.t b/t/oneshot14.t new file mode 100644 index 00000000..2751e5f7 --- /dev/null +++ b/t/oneshot14.t @@ -0,0 +1,15 @@ +2 down +w down +2 up +w up +b down +b up +b down +b up + +shift down +shift up +a down +a up +b down +b up diff --git a/t/oneshot2.t b/t/oneshot2.t index 038a58d8..90c93129 100644 --- a/t/oneshot2.t +++ b/t/oneshot2.t @@ -1,9 +1,9 @@ -1 down +2 down a down a up -1 up +2 up -control down +shift down a down a up -control up +shift up diff --git a/t/oneshot3.t b/t/oneshot3.t index fafa828b..87345507 100644 --- a/t/oneshot3.t +++ b/t/oneshot3.t @@ -1,17 +1,15 @@ -1 down +c down 2 down b down b up 2 up -1 up +c up control down shift down -control up shift up a down a up -control down shift down shift up control up diff --git a/t/oneshot4.t b/t/oneshot4.t index f4bf4032..d936b16b 100644 --- a/t/oneshot4.t +++ b/t/oneshot4.t @@ -1,13 +1,13 @@ -1 down +c down 2 down -a down -a up +i down +i up 2 up -1 up +c up control down shift down -a down -a up +i down +i up shift up control up diff --git a/t/oneshot5.t b/t/oneshot5.t index e1609d5c..9537acee 100644 --- a/t/oneshot5.t +++ b/t/oneshot5.t @@ -1,13 +1,13 @@ 2 down -1 down +c down 2 up -1 up -a down -a up +c up +i down +i up shift down control down -a down +i down control up shift up -a up +i up diff --git a/t/oneshot6.t b/t/oneshot6.t index c65a86b5..187fa082 100644 --- a/t/oneshot6.t +++ b/t/oneshot6.t @@ -1,13 +1,13 @@ 2 down 2 up -1 down -1 up -a down -a up +c down +c up +i down +i up shift down control down -a down +i down control up shift up -a up +i up diff --git a/t/oneshot9.t b/t/oneshot9.t index 163dab9b..42991d3a 100644 --- a/t/oneshot9.t +++ b/t/oneshot9.t @@ -1,9 +1,9 @@ -1 down -1 up -a down -a up +c down +c up +i down +i up control down -a down +i down control up -a up +i up diff --git a/t/oneshotn.t b/t/oneshotn.t index 163dab9b..3fed4903 100644 --- a/t/oneshotn.t +++ b/t/oneshotn.t @@ -1,9 +1,9 @@ -1 down -1 up +c down +c up a down a up control down -a down control up -a up +b down +b up diff --git a/t/oneshotn3.t b/t/oneshotn3.t index 0b116722..0624fe9c 100644 --- a/t/oneshotn3.t +++ b/t/oneshotn3.t @@ -1,13 +1,13 @@ -1 down +c down 2 down 2 up -1 up -a down -a up +c up +i down +i up control down shift down -a down +i down control up shift up -a up +i up diff --git a/t/overload-timeout.t b/t/overload-timeout.t deleted file mode 100644 index c4c1ba9b..00000000 --- a/t/overload-timeout.t +++ /dev/null @@ -1,9 +0,0 @@ -t down -a down -a up -t up - -esc down -esc up -a down -a up diff --git a/t/overload-timeout2.t b/t/overload-timeout2.t deleted file mode 100644 index 39bb50fe..00000000 --- a/t/overload-timeout2.t +++ /dev/null @@ -1,12 +0,0 @@ -t down -2ms -a down -a up -t up - -control down -control up -b down -b up -control down -control up diff --git a/t/reset.t b/t/reset.t deleted file mode 100644 index e14b3396..00000000 --- a/t/reset.t +++ /dev/null @@ -1,15 +0,0 @@ -q down -q up -2 down -2 up -r down -r up - -control down -meta down -alt down -shift down -control up -shift up -meta up -alt up diff --git a/t/run.sh b/t/run.sh index 8c4e3b49..9fba5bfa 100755 --- a/t/run.sh +++ b/t/run.sh @@ -2,30 +2,40 @@ # TODO: make this more robust +if [ `whoami` != "root" ]; then + echo "Must be run as root, restarting (sudo $0)" + sudo "$0" "$@" + exit $? +fi + +tmpdir=$(mktemp -d) + cleanup() { - sudo pkill keyd - sleep 1s - sudo systemctl restart keyd + rm -rf "$tmpdir" + kill $pid trap - EXIT exit } -trap cleanup INT EXIT +trap cleanup INT -cd "$(dirname $0)" +cd "$(dirname "$0")" +cp test.conf "$tmpdir" -sudo cp test.conf /etc/keyd -sudo pkill keyd -sleep 1s -sudo ../bin/keyd -d || exit -sleep 4s +KEYD_NAME="keyd test device" \ +KEYD_DEBUG=1 \ +KEYD_CONFIG_DIR="$tmpdir" \ +../bin/keyd > test.log 2>&1 & +pid=$! + +sleep .7s if [ $# -ne 0 ]; then test_files="$(echo "$@"|sed -e 's/ /.t /g').t" - sudo ./runner.py -v $test_files - exit + ./runner.py -v $test_files + cleanup fi -sudo ./runner.py -ev *.t || exit -sudo ./runner.py -ev $(seq 100|sed -e 's@.*@overload-timeout.t@') +./runner.py -ev *.t +cleanup diff --git a/t/runner.py b/t/runner.py index 530b847b..7b78caab 100755 --- a/t/runner.py +++ b/t/runner.py @@ -141,14 +141,14 @@ def get_ids(self, fh): (_, vendor, product, _) = struct.unpack("HHHH", resp) return (product, vendor) - def __init__(self, product, vendor): + def __init__(self, product=0x00, vendor=0x09, name=""): self.fh = None for f in glob.glob("/dev/input/event*"): fh = open(f, 'rb') fh.devname = self.get_name(fh) p, v = self.get_ids(fh) - if p == product and v == vendor: + if (p == product and v == vendor) or (name != "" and fh.devname == name): self.fh = fh if not self.fh: @@ -205,7 +205,7 @@ def on_timeout(a, b): signal.alarm(20) vkbd = VirtualKeyboard('test keyboard', vendor_id=0x2fac, product_id=0x2ade) -stream = KeyStream(product=0x0ade, vendor=0x0fac) +stream = KeyStream(name="keyd test device") # Grab the virtual keyboard so we don't # cause pandemonium. diff --git a/t/swap2.t b/t/swap2.t index 8f58c719..882989ee 100644 --- a/t/swap2.t +++ b/t/swap2.t @@ -15,6 +15,8 @@ alt down control down alt up control up +9 down +9 up shift down x down x up diff --git a/t/swap3.t b/t/swap3.t index 55bbe981..a28d479b 100644 --- a/t/swap3.t +++ b/t/swap3.t @@ -1,4 +1,4 @@ -1 down +c down alt down ` down ` up @@ -6,30 +6,27 @@ tab down tab up tab down tab up +c up a down a up x down x up alt up -1 up +c up control down alt down alt up -control up shift down x down x up shift up -control down -control up shift down x down x up shift up -control down control up b down b up -control down -control up +x down +x up diff --git a/t/swap4.t b/t/swap4.t index bc5ad386..7211c67f 100644 --- a/t/swap4.t +++ b/t/swap4.t @@ -31,3 +31,5 @@ x up shift up b down b up +x down +x up diff --git a/t/swap5.t b/t/swap5.t index 9c0ac877..e77d1952 100644 --- a/t/swap5.t +++ b/t/swap5.t @@ -2,26 +2,22 @@ meta down alt down 2 down 2 up -a down -a up +x down +x up alt up meta up meta down alt down control down -meta up alt up control up tab down tab up -meta down alt down control down alt up -control up -control down -a down -a up +x down +x up control up meta up diff --git a/t/test.conf b/t/test.conf index fda66eef..f51a630e 100644 --- a/t/test.conf +++ b/t/test.conf @@ -9,21 +9,22 @@ meta = layer(mymeta) leftalt = layer(myalt) capslock = layer(capslock) -1 = oneshot(C) +1 = layer(layer1) 2 = oneshot(customshift) -3 = layer(C) +w = oneshot(customshift) +3 = layer(layer3) 4 = toggle(test) 5 = layer(symbols) 6 = overload(6l, esc) -8 = layout(dvorak) 9 = M-C-S-x q = toggle(M-C-A) -r = reset() l = layer(test) t = overload(o, esc, 2) m = macro(C-h one) -- = layout(layout2) -s = leftshift +c = oneshot(control) +s = layer(shift) +- = toggle(dvorak) += = timeout(a, 300, b) [layout2:layout] @@ -41,7 +42,7 @@ a = b [6l:C] s = swap(tablayer) -o = overload(meta, \) +o = overload(meta, macro(\\)) m = macro(mac) e = macro(leftcontrol o+n leftcontrol+1) b = macro(leftcontrol+n) @@ -59,10 +60,9 @@ c = reset() a = b x = macro(mac) -[dvorak:layout] +[dvorak] -a = b -9 = layout(main) +x = o [myalt:A] m = macro(C-x m) @@ -90,11 +90,19 @@ b = S-[ [tablayer] +` = 9 tab = S-x a = b +[layer1] +h = 1 + +[layer3:C] +h = 3 + [customshift:S] +h = 1 t = toggle(customshift) b = a x = macro(o n) diff --git a/t/timeout1.t b/t/timeout1.t new file mode 100644 index 00000000..c0b63d0b --- /dev/null +++ b/t/timeout1.t @@ -0,0 +1,21 @@ += down +299ms += up += down +301ms += up += down +100ms +x down +200ms += up +x up + +a down +a up +b down +b up +a down +x down +a up +x up diff --git a/t/toggle2.t b/t/toggle2.t index 063fb5aa..74cd815b 100644 --- a/t/toggle2.t +++ b/t/toggle2.t @@ -11,6 +11,8 @@ t up b down b up +shift down +shift up shift down shift up a down