Improve unicode support

The stock XCompose file which ships with most distros isn't exhaustive
and uses a number of layout specific keysyms intended to be easy to
memorize (rather than globally accessible). To circumvent this problem
we ship our own set of simplified compose definitions.
master
Raheman Vaiya 4 years ago
parent 013f1e55e1
commit e24de53d56
  1. 2
      .gitattributes
  2. 18
      Makefile
  3. BIN
      data/keyd-application-mapper.1.gz
  4. BIN
      data/keyd.1.gz
  5. 34492
      data/keyd.compose
  6. 34626
      data/unicode.txt
  7. BIN
      docs/keyd-application-mapper.1.gz
  8. BIN
      docs/keyd.1.gz
  9. 63
      docs/keyd.scdoc
  10. 55
      scripts/gen_aliases.py
  11. 55
      scripts/generate_xcompose
  12. 443
      src/aliases.h
  13. 242
      src/descriptor.c
  14. 42
      src/keyboard.c
  15. 5
      src/layer.h
  16. 17
      src/unicode.c
  17. 63
      src/unicode.h
  18. 25
      t/macro-unicode-2.t
  19. 15
      t/macro-unicode.t
  20. 1
      t/run.sh
  21. 2
      t/test.conf

2
.gitattributes vendored

@ -0,0 +1,2 @@
data/* -diff
src/unicode.c -diff

@ -1,4 +1,4 @@
.PHONY: all clean install uninstall debug assets
.PHONY: all clean install uninstall debug man compose
DESTDIR=
PREFIX=/usr
@ -25,10 +25,14 @@ all:
$(CC) $(CFLAGS) -O3 $(COMPAT_FILES) src/*.c src/vkbd/$(VKBD).c -o bin/keyd -lpthread $(LDFLAGS)
debug:
CFLAGS="-pedantic -Wall -Wextra -g" $(MAKE)
assets:
./scripts/gen_aliases.py > src/aliases.h
compose:
-mkdir data
./scripts/generate_xcompose
man:
for f in docs/*.scdoc; do \
scdoc < "$$f" | gzip > "$${f%%.scdoc}.1.gz"; \
target=$${f%%.scdoc}.1.gz; \
target=data/$${target##*/}; \
scdoc < "$$f" | gzip > "$$target"; \
done
install:
@if [ -e $(DESTDIR)$(PREFIX)/lib/systemd/ ]; then \
@ -48,6 +52,7 @@ install:
install -Dm755 src/vkbd/usb-gadget.sh $(DESTDIR)$(PREFIX)/bin/keyd-usb-gadget.sh; \
fi
mkdir -p $(DESTDIR)/etc/keyd
mkdir -p $(DESTDIR)$(PREFIX)/bin/
mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1/
mkdir -p $(DESTDIR)$(PREFIX)/share/doc/keyd/
@ -55,9 +60,10 @@ install:
-groupadd keyd
install -m755 bin/* $(DESTDIR)$(PREFIX)/bin/
install -m644 docs/*.1.gz $(DESTDIR)$(PREFIX)/share/man/man1/
-install -m644 docs/*.md $(DESTDIR)$(PREFIX)/share/doc/keyd/
install -m644 docs/*.md $(DESTDIR)$(PREFIX)/share/doc/keyd/
install -m644 examples/* $(DESTDIR)$(PREFIX)/share/doc/keyd/examples/
install -m644 data/*.1.gz $(DESTDIR)$(PREFIX)/share/man/man1/
install -m644 data/keyd.compose $(DESTDIR)/etc/keyd/
uninstall:
rm -rf $(DESTDIR)$(PREFIX)/share/libinput/30-keyd.quirks \

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

@ -251,63 +251,20 @@ and not
[layer2]
```
## International Characters
## Unicode Support
If keyd encounters a UTF8 sequence as a right hand value, it will try and
translate that sequence into a macro which emits the corresponding XKB
sequence.
If keyd encounters a valid UTF8 sequence as a right hand value, it will try and
translate that sequence into a macro which emits a keyd-specific XKB sequence.
For example:
```
a = ä
u = Ü
```
will produce the following mapping:
```
a = macro(compose a ")
U = macro(compose U ")
```
In order for this to produce the desired symbol, the xkb option compose:menu
must be set, so that the compose keycode is properly recognized.
This can be manually achieved with:
```
setxkbmap -option compose:menu
```
In order for this to work, the sequences defined in the compose file shipped
with keyd (_/etc/keyd/keyd.compose_) must be accessible. This can be achieved
globally by copying the file to the appropriate location in
_/usr/share/X11/locale_, or on a per-user basis by symlinking it to
~/.XCompose.
though the settings will be lost when the device is unplugged (i.e keyd is
restarted).
The changes can be made permanent by setting the relevant option in your
display server's configuration logic. XKB compose sequences are supported by X
as well as most wayland compositors.
E.G:
on X:
Populating _/etc/X11/xorg.conf.d/compose.conf_ with:
```
Section "InputClass"
Identifier "system-keyboard"
MatchIsKeyboard "on"
Option "XkbOptions" "compose:menu"
EndSection
```
on sway:
Setting the following environment variable:
E.G
```
XKB_DEFAULT_OPTIONS=compose:menu
```
ln -s /etc/keyd/keyd.compose ~/.XCompose
# MACROS

@ -1,55 +0,0 @@
#!/usr/bin/env python3
import re
import sys
import subprocess
keys = subprocess.check_output(['keyd', '-l']).decode('utf8').split('\n')
keymap = {k: k for k in keys}
keymap.update({
"quotedbl": "\"",
"apostrophe": "'",
"Multi_key": "compose",
"exclam": "!",
"question": "?",
"asciicircum": "^",
})
def create_macro(keys):
return f"macro({' '.join(keymap[k] for k in keys)})"
macros = {}
for line in open('/usr/share/X11/locale/en_US.UTF-8/Compose').readlines():
try:
keys, glyph = re.match('(\s*<.*?>+)\s*:\s*"(.*?)".*', line).groups()
keys = re.findall('<(.*?)>', keys)
macros[glyph] = create_macro(keys)
except:
pass
print('''/* GENERATED BY "%s" DO NOT EDIT BY HAND */
#ifndef ALIASES_H
#define ALIASES_H
struct alias {
const char *name;
const char *def;
};
static struct alias aliases[] = {
''' % sys.argv[0])
for glyph, macro in macros.items():
macro = macro.replace('"', '\\"')
print('\t{"%s", "%s"},' % (glyph, macro))
print('''
};
const size_t nr_aliases = sizeof(aliases)/sizeof(aliases[0]);
#endif
''')

@ -0,0 +1,55 @@
#!/usr/bin/env python3
import sys
codes = []
for line in open('data/unicode.txt').readlines(): # Original source: https://www.unicode.org/Public/14.0.0/ucd/UnicodeData.txt
try:
code = int(line.split(';')[0], 16)
# Ensure the character is encodable (surrogates are not)
chr(code).encode('utf8')
if (code >= 128):
codes.append(code)
except:
pass
# Generate the compose file
data = ''
for n, code in enumerate(codes):
data += '<Linefeed> '
data += ' '.join(f'<{c}>' for c in ('%05d' % n))
data += f' : "{chr(code)}"\n'
open('data/keyd.compose', 'w').write(data)
# Generate the corresponding src/unicode.c
# OPT: We could condense this and shave off lookup time by using an offset
# table to capitalize on codepoint contiguity, but 35k is small enough to
# warrant keeping the entire thing in memory.
open('src/unicode.c', 'w').write(f'''
/* GENERATED BY {sys.argv[0]}, DO NOT MODIFY BY HAND. */
#include <stdint.h>
#include <stdlib.h>
uint32_t unicode_table[] = {{ {','.join(map(str, codes))} }};
int lookup_xcompose_code(uint32_t codepoint) {{
size_t i = 0;
for(i = 0; i < sizeof(unicode_table)/sizeof(unicode_table[0]); i++) {{
if (unicode_table[i] == codepoint)
return i;
}}
return -1;
}}
'''
.replace('\n\t', '\n')
.lstrip()
)

@ -1,443 +0,0 @@
/* GENERATED BY "./scripts/gen_aliases.py" DO NOT EDIT BY HAND */
#ifndef ALIASES_H
#define ALIASES_H
struct alias {
const char *name;
const char *def;
};
static struct alias aliases[] = {
{"´", "macro(compose ' ')"},
{"¯", "macro(compose ^ minus)"},
{"¨", "macro(compose \" \")"},
{"­", "macro(compose minus minus space)"},
{"'", "macro(compose space ')"},
{"@", "macro(compose A T)"},
{"\\", "macro(compose slash slash)"},
{"^", "macro(compose space ^)"},
{"`", "macro(compose space grave)"},
{"¸", "macro(compose comma comma)"},
{"|", "macro(compose l v)"},
{"~", "macro(compose space minus)"},
{"<", "macro(compose l t)"},
{">", "macro(compose g t)"},
{" ", "macro(compose space space)"},
{"©", "macro(compose C O)"},
{"®", "macro(compose R O)"},
{"¦", "macro(compose ! ^)"},
{"¡", "macro(compose ! !)"},
{"", "macro(compose P P)"},
{"¿", "macro(compose ? ?)"},
{"ß", "macro(compose s s)"},
{"", "macro(compose S S)"},
{"œ", "macro(compose o e)"},
{"Œ", "macro(compose O E)"},
{"æ", "macro(compose a e)"},
{"Æ", "macro(compose A E)"},
{"", "macro(compose f f)"},
{"", "macro(compose f i)"},
{"", "macro(compose f l)"},
{"", "macro(compose F i)"},
{"", "macro(compose F l)"},
{"IJ", "macro(compose I j)"},
{"ij", "macro(compose i j)"},
{"°", "macro(compose o o)"},
{"", "macro(compose ' comma)"},
{"", "macro(compose \" comma)"},
{"", "macro(compose C E)"},
{"", "macro(compose slash C)"},
{"", "macro(compose C r)"},
{"", "macro(compose F r)"},
{"", "macro(compose equal L)"},
{"", "macro(compose slash m)"},
{"", "macro(compose equal N)"},
{"", "macro(compose P t)"},
{"", "macro(compose R s)"},
{"", "macro(compose equal W)"},
{"", "macro(compose equal d)"},
{"", "macro(compose equal e)"},
{"", "macro(compose equal p)"},
{"", "macro(compose equal r)"},
{"¢", "macro(compose slash c)"},
{"£", "macro(compose minus l)"},
{"¥", "macro(compose minus y)"},
{"ſ", "macro(compose f S)"},
{"", "macro(compose minus minus minus)"},
{"§", "macro(compose S !)"},
{"¤", "macro(compose x O)"},
{"", "macro(compose N O)"},
{"", "macro(compose ? !)"},
{"", "macro(compose ! ?)"},
{"", "macro(compose C C C P)"},
{"", "macro(compose O A)"},
{"🙌", "macro(compose backslash o slash)"},
{"💩", "macro(compose p o o)"},
{"🖕", "macro(compose F U)"},
{"🖖", "macro(compose L L A P)"},
{"¬", "macro(compose minus comma)"},
{"²", "macro(compose 2 ^)"},
{"³", "macro(compose 3 ^)"},
{"µ", "macro(compose u slash)"},
{"¹", "macro(compose 1 ^)"},
{"¼", "macro(compose 1 4)"},
{"½", "macro(compose 1 2)"},
{"¾", "macro(compose 3 4)"},
{"À", "macro(compose A grave)"},
{"Á", "macro(compose A ')"},
{"Â", "macro(compose A ^)"},
{"Ä", "macro(compose A \")"},
{"Å", "macro(compose A A)"},
{"Ç", "macro(compose C comma)"},
{"È", "macro(compose E grave)"},
{"É", "macro(compose E ')"},
{"Ê", "macro(compose E ^)"},
{"Ë", "macro(compose E \")"},
{"Ì", "macro(compose I grave)"},
{"Í", "macro(compose I ')"},
{"Î", "macro(compose I ^)"},
{"Ï", "macro(compose I \")"},
{"J́", "macro(compose J ')"},
{"Ð", "macro(compose D H)"},
{"Ò", "macro(compose O grave)"},
{"Ó", "macro(compose O ')"},
{"Ô", "macro(compose O ^)"},
{"Ö", "macro(compose O \")"},
{"×", "macro(compose x x)"},
{"Ø", "macro(compose O slash)"},
{"Ù", "macro(compose U grave)"},
{"Ú", "macro(compose U ')"},
{"Û", "macro(compose U ^)"},
{"Ü", "macro(compose U \")"},
{"Ý", "macro(compose Y ')"},
{"Þ", "macro(compose T H)"},
{"à", "macro(compose a grave)"},
{"á", "macro(compose a ')"},
{"â", "macro(compose a ^)"},
{"ä", "macro(compose a \")"},
{"å", "macro(compose a a)"},
{"ç", "macro(compose c comma)"},
{"è", "macro(compose e grave)"},
{"é", "macro(compose e ')"},
{"ê", "macro(compose e ^)"},
{"ë", "macro(compose e \")"},
{"ì", "macro(compose i grave)"},
{"í", "macro(compose i ')"},
{"î", "macro(compose i ^)"},
{"ï", "macro(compose i \")"},
{"j́", "macro(compose j ')"},
{"ð", "macro(compose d h)"},
{"ò", "macro(compose o grave)"},
{"ó", "macro(compose o ')"},
{"ô", "macro(compose o ^)"},
{"ö", "macro(compose o \")"},
{"ø", "macro(compose o slash)"},
{"ù", "macro(compose u grave)"},
{"ú", "macro(compose u ')"},
{"û", "macro(compose u ^)"},
{"ü", "macro(compose u \")"},
{"ý", "macro(compose y ')"},
{"þ", "macro(compose t h)"},
{"ÿ", "macro(compose y \")"},
{"Ā", "macro(compose A minus)"},
{"ā", "macro(compose a minus)"},
{"Ă", "macro(compose b A)"},
{"ă", "macro(compose b a)"},
{"Ą", "macro(compose A comma)"},
{"ą", "macro(compose a comma)"},
{"Ć", "macro(compose C ')"},
{"ć", "macro(compose c ')"},
{"Ĉ", "macro(compose ^ C)"},
{"ĉ", "macro(compose ^ c)"},
{"Č", "macro(compose v C)"},
{"č", "macro(compose v c)"},
{"Ď", "macro(compose v D)"},
{"ď", "macro(compose v d)"},
{"Đ", "macro(compose slash D)"},
{"đ", "macro(compose slash d)"},
{"Ē", "macro(compose E minus)"},
{"ē", "macro(compose e minus)"},
{"Ĕ", "macro(compose b E)"},
{"ĕ", "macro(compose b e)"},
{"Ę", "macro(compose E comma)"},
{"ę", "macro(compose e comma)"},
{"Ě", "macro(compose v E)"},
{"ě", "macro(compose v e)"},
{"Ĝ", "macro(compose ^ G)"},
{"ĝ", "macro(compose ^ g)"},
{"Ğ", "macro(compose b G)"},
{"ğ", "macro(compose b g)"},
{"Ģ", "macro(compose G comma)"},
{"ģ", "macro(compose g comma)"},
{"Ĥ", "macro(compose ^ H)"},
{"ĥ", "macro(compose ^ h)"},
{"Ħ", "macro(compose slash H)"},
{"ħ", "macro(compose slash h)"},
{"Ī", "macro(compose I minus)"},
{"ī", "macro(compose i minus)"},
{"Ĭ", "macro(compose b I)"},
{"ĭ", "macro(compose b i)"},
{"Į", "macro(compose I comma)"},
{"į", "macro(compose i comma)"},
{"Ĵ", "macro(compose ^ J)"},
{"ĵ", "macro(compose ^ j)"},
{"Ķ", "macro(compose K comma)"},
{"ķ", "macro(compose k comma)"},
{"ĸ", "macro(compose k k)"},
{"Ĺ", "macro(compose L ')"},
{"ĺ", "macro(compose l ')"},
{"Ļ", "macro(compose L comma)"},
{"ļ", "macro(compose l comma)"},
{"Ľ", "macro(compose c L)"},
{"ľ", "macro(compose c l)"},
{"Ł", "macro(compose L slash)"},
{"ł", "macro(compose l slash)"},
{"Ń", "macro(compose N ')"},
{"ń", "macro(compose n ')"},
{"Ņ", "macro(compose N comma)"},
{"ņ", "macro(compose n comma)"},
{"Ň", "macro(compose v N)"},
{"ň", "macro(compose v n)"},
{"Ŋ", "macro(compose N G)"},
{"ŋ", "macro(compose n g)"},
{"Ō", "macro(compose O minus)"},
{"ō", "macro(compose o minus)"},
{"Ŏ", "macro(compose b O)"},
{"ŏ", "macro(compose b o)"},
{"Ő", "macro(compose equal O)"},
{"ő", "macro(compose equal o)"},
{"Ŕ", "macro(compose R ')"},
{"ŕ", "macro(compose r ')"},
{"Ŗ", "macro(compose R comma)"},
{"ŗ", "macro(compose r comma)"},
{"Ř", "macro(compose v R)"},
{"ř", "macro(compose v r)"},
{"Ś", "macro(compose S ')"},
{"ś", "macro(compose s ')"},
{"Ŝ", "macro(compose ^ S)"},
{"ŝ", "macro(compose ^ s)"},
{"Ş", "macro(compose S comma)"},
{"ş", "macro(compose s comma)"},
{"Š", "macro(compose v S)"},
{"š", "macro(compose v s)"},
{"Ţ", "macro(compose T comma)"},
{"ţ", "macro(compose t comma)"},
{"Ť", "macro(compose v T)"},
{"ť", "macro(compose v t)"},
{"Ŧ", "macro(compose T minus)"},
{"ŧ", "macro(compose t minus)"},
{"Ū", "macro(compose U minus)"},
{"ū", "macro(compose u minus)"},
{"Ŭ", "macro(compose b U)"},
{"ŭ", "macro(compose b u)"},
{"Ů", "macro(compose o U)"},
{"ů", "macro(compose o u)"},
{"Ű", "macro(compose equal U)"},
{"ű", "macro(compose equal u)"},
{"Ų", "macro(compose U comma)"},
{"ų", "macro(compose u comma)"},
{"Ŵ", "macro(compose W ^)"},
{"ŵ", "macro(compose w ^)"},
{"Ŷ", "macro(compose Y ^)"},
{"ŷ", "macro(compose y ^)"},
{"Ÿ", "macro(compose Y \")"},
{"Ź", "macro(compose Z ')"},
{"ź", "macro(compose z ')"},
{"Ž", "macro(compose v Z)"},
{"ž", "macro(compose v z)"},
{"ƀ", "macro(compose slash b)"},
{"Ɨ", "macro(compose slash I)"},
{"Ƶ", "macro(compose slash Z)"},
{"ƶ", "macro(compose slash z)"},
{"Ǎ", "macro(compose v A)"},
{"ǎ", "macro(compose v a)"},
{"Ǐ", "macro(compose v I)"},
{"ǐ", "macro(compose v i)"},
{"Ǒ", "macro(compose v O)"},
{"ǒ", "macro(compose v o)"},
{"Ǔ", "macro(compose v U)"},
{"ǔ", "macro(compose v u)"},
{"Ǘ", "macro(compose ' \" U)"},
{"ǘ", "macro(compose ' \" u)"},
{"Ǚ", "macro(compose c \" U)"},
{"ǚ", "macro(compose c \" u)"},
{"Ǜ", "macro(compose grave \" U)"},
{"ǜ", "macro(compose grave \" u)"},
{"Ǥ", "macro(compose slash G)"},
{"ǥ", "macro(compose slash g)"},
{"Ǧ", "macro(compose v G)"},
{"ǧ", "macro(compose v g)"},
{"Ǩ", "macro(compose v K)"},
{"ǩ", "macro(compose v k)"},
{"Ǫ", "macro(compose O comma)"},
{"ǫ", "macro(compose o comma)"},
{"ǰ", "macro(compose v j)"},
{"Ǵ", "macro(compose ' G)"},
{"ǵ", "macro(compose ' g)"},
{"Ǹ", "macro(compose grave N)"},
{"ǹ", "macro(compose grave n)"},
{"Ǿ", "macro(compose ' slash O)"},
{"ǿ", "macro(compose ' slash o)"},
{"Ȟ", "macro(compose v H)"},
{"ȟ", "macro(compose v h)"},
{"ə", "macro(compose e e)"},
{"ɨ", "macro(compose slash i)"},
{"̈́", "macro(compose \" ')"},
{"΅", "macro(compose ' \" space)"},
{"", "macro(compose ! B)"},
{"", "macro(compose ! b)"},
{"", "macro(compose ! D)"},
{"", "macro(compose ! d)"},
{"", "macro(compose D comma)"},
{"", "macro(compose d comma)"},
{"", "macro(compose b comma E)"},
{"", "macro(compose b comma e)"},
{"", "macro(compose ! H)"},
{"", "macro(compose ! h)"},
{"", "macro(compose \" H)"},
{"", "macro(compose \" h)"},
{"", "macro(compose H comma)"},
{"", "macro(compose h comma)"},
{"", "macro(compose ' \" I)"},
{"", "macro(compose ' \" i)"},
{"", "macro(compose ' K)"},
{"", "macro(compose ' k)"},
{"", "macro(compose ! K)"},
{"", "macro(compose ! k)"},
{"", "macro(compose ! L)"},
{"", "macro(compose ! l)"},
{"", "macro(compose ' M)"},
{"ḿ", "macro(compose ' m)"},
{"", "macro(compose ! M)"},
{"", "macro(compose ! m)"},
{"", "macro(compose ! N)"},
{"", "macro(compose ! n)"},
{"", "macro(compose ' P)"},
{"", "macro(compose ' p)"},
{"", "macro(compose ! R)"},
{"", "macro(compose ! r)"},
{"", "macro(compose ! S)"},
{"", "macro(compose ! s)"},
{"", "macro(compose ! T)"},
{"", "macro(compose ! t)"},
{"", "macro(compose ! V)"},
{"ṿ", "macro(compose ! v)"},
{"", "macro(compose grave W)"},
{"", "macro(compose grave w)"},
{"", "macro(compose ' W)"},
{"", "macro(compose ' w)"},
{"", "macro(compose \" W)"},
{"", "macro(compose \" w)"},
{"", "macro(compose ! W)"},
{"", "macro(compose ! w)"},
{"", "macro(compose \" X)"},
{"", "macro(compose \" x)"},
{"", "macro(compose ^ Z)"},
{"", "macro(compose ^ z)"},
{"", "macro(compose ! Z)"},
{"", "macro(compose ! z)"},
{"", "macro(compose \" t)"},
{"", "macro(compose o w)"},
{"", "macro(compose o y)"},
{"", "macro(compose ! A)"},
{"", "macro(compose ! a)"},
{"", "macro(compose ? A)"},
{"", "macro(compose ? a)"},
{"", "macro(compose ' ^ A)"},
{"", "macro(compose ' ^ a)"},
{"", "macro(compose grave ^ A)"},
{"", "macro(compose grave ^ a)"},
{"", "macro(compose ? ^ A)"},
{"", "macro(compose ? ^ a)"},
{"", "macro(compose ^ ! A)"},
{"", "macro(compose ^ ! a)"},
{"", "macro(compose ' b A)"},
{"", "macro(compose ' b a)"},
{"", "macro(compose grave b A)"},
{"", "macro(compose grave b a)"},
{"", "macro(compose ? b A)"},
{"", "macro(compose ? b a)"},
{"", "macro(compose b ! A)"},
{"", "macro(compose b ! a)"},
{"", "macro(compose ! E)"},
{"", "macro(compose ! e)"},
{"", "macro(compose ? E)"},
{"", "macro(compose ? e)"},
{"", "macro(compose ' ^ E)"},
{"ế", "macro(compose ' ^ e)"},
{"", "macro(compose grave ^ E)"},
{"", "macro(compose grave ^ e)"},
{"", "macro(compose ? ^ E)"},
{"", "macro(compose ? ^ e)"},
{"", "macro(compose ^ ! E)"},
{"", "macro(compose ^ ! e)"},
{"", "macro(compose ? I)"},
{"", "macro(compose ? i)"},
{"", "macro(compose ! I)"},
{"", "macro(compose ! i)"},
{"", "macro(compose ! O)"},
{"", "macro(compose ! o)"},
{"", "macro(compose ? O)"},
{"", "macro(compose ? o)"},
{"", "macro(compose ' ^ O)"},
{"", "macro(compose ' ^ o)"},
{"", "macro(compose grave ^ O)"},
{"", "macro(compose grave ^ o)"},
{"", "macro(compose ? ^ O)"},
{"", "macro(compose ? ^ o)"},
{"", "macro(compose ^ ! O)"},
{"", "macro(compose ^ ! o)"},
{"", "macro(compose ! U)"},
{"", "macro(compose ! u)"},
{"", "macro(compose ? U)"},
{"", "macro(compose ? u)"},
{"", "macro(compose grave Y)"},
{"", "macro(compose grave y)"},
{"", "macro(compose ! Y)"},
{"", "macro(compose ! y)"},
{"", "macro(compose ? Y)"},
{"", "macro(compose ? y)"},
{"", "macro(compose ^ 0)"},
{"", "macro(compose ^ 4)"},
{"", "macro(compose ^ 5)"},
{"", "macro(compose ^ 6)"},
{"", "macro(compose ^ 7)"},
{"", "macro(compose ^ 8)"},
{"", "macro(compose ^ 9)"},
{"", "macro(compose ^ equal)"},
{"", "macro(compose s m)"},
{"", "macro(compose t m)"},
{"", "macro(compose 1 7)"},
{"", "macro(compose 1 9)"},
{"", "macro(compose 1 1 0)"},
{"", "macro(compose 1 3)"},
{"", "macro(compose 2 3)"},
{"", "macro(compose 1 5)"},
{"", "macro(compose 2 5)"},
{"", "macro(compose 3 5)"},
{"", "macro(compose 4 5)"},
{"", "macro(compose 1 6)"},
{"", "macro(compose 5 6)"},
{"", "macro(compose 1 8)"},
{"", "macro(compose 3 8)"},
{"", "macro(compose 5 8)"},
{"", "macro(compose 7 8)"},
{"", "macro(compose 0 3)"},
{"", "macro(compose equal slash)"},
{"", "macro(compose d i)"},
{"Ș", "macro(compose S semicolon)"},
{"ș", "macro(compose s semicolon)"},
{"Ț", "macro(compose T semicolon)"},
{"ț", "macro(compose t semicolon)"},
{"", "macro(compose slash v)"},
{"", "macro(compose 8 8)"},
{"", "macro(compose minus slash)"},
{"", "macro(compose minus backslash)"},
};
const size_t nr_aliases = sizeof(aliases)/sizeof(aliases[0]);
#endif

@ -14,7 +14,7 @@
#include "keys.h"
#include "error.h"
#include "config.h"
#include "aliases.h"
#include "unicode.h"
#define MAX_ARGS 5
@ -221,89 +221,117 @@ static int parse_sequence(const char *s, uint8_t *codep, uint8_t *modsp)
return -1;
}
static int do_parse_macro(struct macro *macro, char *s)
/*
* Returns the character size in bytes, or 0 in the case of the empty string.
*/
static int utf8_read_char(const char *_s, uint32_t *code)
{
char *tok;
size_t sz = 0;
uint8_t code, mods;
macro->sz = 0;
for (tok = strtok(s, " "); tok; tok = strtok(NULL, " ")) {
struct macro_entry ent;
size_t len = strlen(tok);
const unsigned char *s = (const unsigned char*)_s;
if (!s[0])
return 0;
if (s[0] >= 0xF0) {
assert(s[1]);
assert(s[2]);
assert(s[3]);
*code = (s[0] & 0x07) << 18 | (s[1] & 0x3F) << 12 | (s[2] & 0x3F) << 6 | (s[3] & 0x3F);
return 4;
} else if (s[0] >= 0xE0) {
assert(s[1]);
assert(s[2]);
*code = (s[0] & 0x0F) << 12 | (s[1] & 0x3F) << 6 | (s[2] & 0x3F);
return 3;
} else if (s[0] >= 0xC0) {
assert(s[1]);
*code = (s[0] & 0x1F) << 6 | (s[1] & 0x3F);
return 2;
} else {
*code = s[0] & 0x7F;
return 1;
}
}
if (!parse_sequence(tok, &code, &mods)) {
assert(sz < MAX_MACRO_SIZE);
static int utf8_strlen(const char *s)
{
uint32_t code;
int csz;
int n = 0;
ent.type = MACRO_KEYSEQUENCE;
ent.code = code;
ent.mods = mods;
while ((csz = utf8_read_char(s, &code))) {
n++;
s+=csz;
}
macro->entries[sz++] = ent;
} else if (len > 1 && tok[len-2] == 'm' && tok[len-1] == 's') {
int len = atoi(tok);
return n;
}
ent.type = MACRO_TIMEOUT;
ent.timeout = len;
static void macro_add(struct macro *m, uint8_t type, uint16_t data)
{
assert(m->sz < MAX_MACRO_SIZE);
assert(sz < MAX_MACRO_SIZE);
macro->entries[sz++] = ent;
} else {
char *c;
m->entries[m->sz].type = type;
m->entries[m->sz].data = data;
if (strchr(tok, '+')) {
char *saveptr;
char *key;
m->sz++;
}
for (key = strtok_r(tok, "+", &saveptr); key; key = strtok_r(NULL, "+", &saveptr)) {
if (parse_sequence(key, &code, &mods) < 0) {
return -1;
}
static int do_parse_macro(struct macro *macro, char *s)
{
char *tok;
macro->sz = 0;
ent.type = MACRO_HOLD;
ent.code = code;
ent.mods = mods;
for (tok = strtok(s, " "); tok; tok = strtok(NULL, " ")) {
uint8_t code, mods;
size_t len = strlen(tok);
assert(sz < MAX_MACRO_SIZE);
macro->entries[sz++] = ent;
}
if (!parse_sequence(tok, &code, &mods)) {
macro_add(macro, MACRO_KEYSEQUENCE, (mods << 8) | code);
} else if (len > 1 && tok[len-2] == 'm' && tok[len-1] == 's') {
macro_add(macro, MACRO_TIMEOUT, atoi(tok));
} else if (strchr(tok, '+')) {
char *saveptr;
char *key;
ent.type = MACRO_RELEASE;
assert(sz < MAX_MACRO_SIZE);
macro->entries[sz++] = ent;
} else {
for (c = tok; *c; c++) {
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;
}
for (key = strtok_r(tok, "+", &saveptr); key; key = strtok_r(NULL, "+", &saveptr)) {
if (parse_sequence(key, &code, &mods))
return -1;
if (parse_sequence(s, &code, &mods) < 0)
return -1;
macro_add(macro, MACRO_HOLD, code);
}
ent.type = MACRO_KEYSEQUENCE;
ent.code = code;
ent.mods = mods;
macro_add(macro, MACRO_RELEASE, 0);
} else {
uint32_t codepoint;
int chrsz;
while ((chrsz=utf8_read_char(tok, &codepoint))) {
int i;
int xcode;
if (chrsz == 1 && codepoint < 128) {
for (i = 0; i < 256; i++) {
const char *name = keycode_table[i].name;
const char *shiftname = keycode_table[i].shifted_name;
if (name && name[0] == tok[0] && name[1] == 0) {
macro_add(macro, MACRO_KEYSEQUENCE, i);
break;
}
if (shiftname && shiftname[0] == tok[0] && shiftname[1] == 0) {
macro_add(macro, MACRO_KEYSEQUENCE, (MOD_SHIFT << 8) | i);
break;
}
}
} else if ((xcode = lookup_xcompose_code(codepoint)) > 0)
macro_add(macro, MACRO_UNICODE, xcode);
assert(sz < MAX_MACRO_SIZE);
macro->entries[sz++] = ent;
}
tok += chrsz;
}
}
}
macro->sz = sz;
return 0;
}
@ -350,6 +378,7 @@ static size_t escape(char *s)
return n;
}
static int parse_macro(const char *exp, struct macro *macro)
{
char s[MAX_MACROEXP_LEN];
@ -553,6 +582,32 @@ int create_layer(struct layer *layer, const char *desc, const struct layer_table
return 0;
}
/*
* Returns:
*
* > 0 if exp is a valid macro but the macro table is full
* < 0 in the case of an invalid macro
* 0 on success
*/
int set_macro_arg(struct descriptor *d, int idx, struct layer_table *lt, const char *exp)
{
if (lt->nr_macros >= MAX_MACROS) {
err("max macros (%d), exceeded", MAX_MACROS);
return 1;
}
if (parse_macro(exp, &lt->macros[lt->nr_macros]) < 0) {
err("\"%s\" is not a valid macro", exp);
return -1;
}
d->args[idx].idx = lt->nr_macros;
lt->nr_macros++;
return 0;
}
/*
* Modifies the input string. Layers names within the descriptor
* are resolved using the provided layer table.
@ -564,19 +619,20 @@ int parse_descriptor(const char *descstr,
char *fn = NULL;
char *args[MAX_ARGS];
size_t nargs = 0;
struct macro macro;
uint8_t code;
int idx;
int ret;
char fnstr[MAX_DESCRIPTOR_LEN+1];
if (strlen(descstr) > MAX_DESCRIPTOR_LEN) {
err("maximum descriptor length exceeded");
return -1;
}
char s[MAX_DESCRIPTOR_LEN+1];
strcpy(s, descstr);
strcpy(fnstr, descstr);
if ((code = parse_code(s))) {
if ((code = parse_code(descstr))) {
d->op = OP_KEYCODE;
d->args[0].code = code;
@ -584,19 +640,12 @@ int parse_descriptor(const char *descstr,
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 (!parse_macro(s, &macro)) {
if (lt->nr_macros >= MAX_MACROS) {
err("max macros (%d), exceeded", MAX_MACROS);
} else if ((ret=set_macro_arg(d, 0, lt, descstr)) >= 0) {
if (ret > 0)
return -1;
}
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)) {
else
d->op = OP_MACRO;
} else if (!parse_fn(fnstr, &fn, args, &nargs)) {
if (!strcmp(fn, "layer"))
d->op = OP_LAYER;
else if (!strcmp(fn, "toggle"))
@ -610,7 +659,7 @@ int parse_descriptor(const char *descstr,
} else if (!strcmp(fn, "timeout")) {
d->op = OP_TIMEOUT;
} else {
err("\"%s\" is not a valid action or macro.", s);
err("\"%s\" is not a valid action or macro.", descstr);
return -1;
}
@ -659,27 +708,16 @@ int parse_descriptor(const char *descstr,
d->args[0].idx = idx;
d->args[1].idx = -1;
if (nargs > 1) {
if (lt->nr_macros >= MAX_MACROS) {
err("max macros (%d), exceeded", MAX_MACROS);
return -1;
}
if (parse_macro(args[1], &lt->macros[lt->nr_macros]) < 0) {
err("\"%s\" is not a valid macro", args[1]);
return -1;
}
if (nargs > 1)
return set_macro_arg(d, 1, lt, args[1]);
} else if (utf8_strlen(descstr) == 1) {
char buf[32];
sprintf(buf, "macro(%s)", descstr);
d->op = OP_MACRO;
d->args[1].idx = lt->nr_macros;
lt->nr_macros++;
}
if (set_macro_arg(d, 0, lt, buf))
return -1;
} else {
size_t i;
for (i = 0; i < nr_aliases; i++)
if (!strcmp(aliases[i].name, descstr)) {
return parse_descriptor(aliases[i].def, d, lt);
}
err("invalid key or action");
return -1;
}

@ -113,11 +113,15 @@ static void execute_macro(struct keyboard *kbd, const struct macro *macro)
const struct macro_entry *ent = &macro->entries[i];
switch (ent->type) {
size_t j;
uint16_t n;
uint8_t code, mods;
case MACRO_HOLD:
if (hold_start == -1)
hold_start = i;
kbd_send_key(kbd, ent->code, 1);
kbd_send_key(kbd, ent->data, 1);
break;
case MACRO_RELEASE:
@ -126,22 +130,42 @@ static void execute_macro(struct keyboard *kbd, const struct macro *macro)
for (j = hold_start; j < i; j++) {
const struct macro_entry *ent = &macro->entries[j];
kbd_send_key(kbd, ent->code, 0);
kbd_send_key(kbd, ent->data, 0);
}
hold_start = -1;
}
break;
case MACRO_UNICODE:
n = ent->data;
kbd_send_key(kbd, KEYD_LINEFEED, 1);
kbd_send_key(kbd, KEYD_LINEFEED, 0);
for (j = 10000; j; j /= 10) {
int digit = n / j;
code = digit == 0 ? KEYD_0 : digit - 1 + KEYD_1;
kbd_send_key(kbd, code, 1);
kbd_send_key(kbd, code, 0);
n %= j;
}
break;
case MACRO_KEYSEQUENCE:
if (kbd->keystate[ent->code])
kbd_send_key(kbd, ent->code, 0);
code = ent->data;
mods = ent->data >> 8;
if (kbd->keystate[code])
kbd_send_key(kbd, 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(kbd, mods, 1);
kbd_send_key(kbd, code, 1);
kbd_send_key(kbd, code, 0);
send_mods(kbd, mods, 0);
break;
case MACRO_TIMEOUT:
usleep(ent->timeout*1E3);
usleep(ent->data*1E3);
break;
}

@ -60,12 +60,11 @@ struct macro_entry {
MACRO_KEYSEQUENCE,
MACRO_HOLD,
MACRO_RELEASE,
MACRO_UNICODE,
MACRO_TIMEOUT
} type;
uint8_t code;
uint8_t mods;
uint16_t timeout;
uint16_t data;
};
/*

File diff suppressed because one or more lines are too long

@ -0,0 +1,63 @@
/*
* keyd - A key remapping daemon.
*
* © 2019 Raheman Vaiya (see also: LICENSE).
*/
#ifndef UNICODE_H
#define UNICODE_H
/*
* Overview
*
* Unicode input is accomplished using one of several 'input methods' or
* 'IMEs'. The X input method (xim) is the name of the default input method
* which ships with X, and currently seems to be the most widely supported one.
* An emerging competitor called 'ibus' exists, but seems to be less
* ubiquitous, a notable advantage being that it allows codepoints to be input
* directly by their hex values (C-S-u).
*
* xim, by contrast, works by requiring the user to explicitly specify a
* mapping for each codepoint of interest in an XCompose(5) file, which maps a
* sequence of keysyms (usually beginning with a dedicated 'compose' key
* (<Multi_key>)) into a valid utf8 output string.
*
* Unfortunately xim doesn't provide a mechanism by which arbitrary unicode
* points can be input, so we have to construct an XCompose file containing
* explicit mappings between each sequence and the desired utf8 output.
*
* ~/.XCompose Constraints:
*
* To compound matters, every program/toolkit seems to have its own XCompose
* parser and consequently only supports a subset of the full spec. The
* following real-world constraints have been arrived at empirically:
*
* 1. Each sequence should be less than 6 characters since some programs (e.g
* chrome) seem to have a maximum sequence length.
*
* 2. No sequence should be a subset of another sequence since some programs
* don't handle this properly (e.g kitty)
*
* 3. Sequences should confine themselves to keysyms available on all layouts
* (e.g no a-f (hex)).
*
* Approach
*
* In order to satisfy the above constraints, we create an XCompose file
* mapping each codepoint's index in a lookup table to the desired utf8
* sequence. The use of a table index instead of the codepoint value ensures
* all codepoints consist of a maximum of 5 decimal digits (since there are
* <35k of them). Each codepoint is zero-left padded to 5 characters to avoid
* the subset issue.
*
* Finally, we use linefeed as our compose prefix so the user doesn't have to
* faff about with XkbOptions. This technically introduces the possibility of a
* conflict, but I haven't found any evidence which suggests that Linefeed is
* anything other than a vestigial keypendage from a more glorious era.
*
* </end_verbiage>
*/
int lookup_xcompose_code(uint32_t codepoint);
#endif

@ -0,0 +1,25 @@
3 down
\ down
\ up
3 up
control down
control up
1 down
1 up
linefeed down
linefeed up
3 down
3 up
2 down
2 up
3 down
3 up
3 down
3 up
4 down
4 up
2 down
2 up
control down
control up

@ -0,0 +1,15 @@
\ down
\ up
linefeed down
linefeed up
3 down
3 up
2 down
2 up
3 down
3 up
3 down
3 up
4 down
4 up

@ -24,6 +24,7 @@ cd "$(dirname "$0")"
cp test.conf "$tmpdir"
KEYD_NAME="keyd test device" \
KEYD_SOCKET=/tmp/keyd_test.socket \
KEYD_DEBUG=1 \
KEYD_CONFIG_DIR="$tmpdir" \
../bin/keyd > test.log 2>&1 &

@ -25,6 +25,7 @@ c = oneshot(control)
s = layer(shift)
- = toggle(dvorak)
= = timeout(a, 300, b)
\ = 😄
[layout2:layout]
@ -99,6 +100,7 @@ h = 1
[layer3:C]
h = 3
\ = macro(1😄2)
[customshift:S]

Loading…
Cancel
Save