Add support for chording

master
Raheman Vaiya 4 years ago
parent f721f1104b
commit afd6fb73a5
  1. BIN
      data/keyd-application-mapper.1.gz
  2. BIN
      data/keyd.1.gz
  3. 24
      docs/keyd.scdoc
  4. 102
      src/config.c
  5. 13
      src/config.h
  6. 2
      src/evloop.c
  7. 251
      src/keyboard.c
  8. 21
      src/keyboard.h
  9. 30
      t/chord-disambiguate.t
  10. 18
      t/chord-double.t
  11. 26
      t/chord-hold.t
  12. 25
      t/chord.t
  13. 19
      t/chord2.t
  14. 8
      t/test.conf

Binary file not shown.

Binary file not shown.

@ -350,6 +350,24 @@ b = c
```
## Chording
_Chords_ are groups of keys which are treated as a unit when simultaneously
depressed. A chord can be defined by using a group of + delimited key names as
a left hand value. The corresponding action will be activated if all keys are
struck within the chording interval (_chord_timeout_).
E.g
```
j+k = esc
```
will cause _esc_ to be produced if both _j_ and _k_ are simultaneously depressed.
Note: It may be desirable to change the default chording interval (50ms) to
account for the physical characteristics of your keyboard.
## Unicode Support
If keyd encounters a valid UTF8 sequence as a right hand value, it will try and
@ -516,6 +534,12 @@ any of the following options:
microseconds*) between each emitted key in a macro sequence. This is
useful to avoid overflowing the input buffer on some systems.
*chord_timeout:* The maximum time between successive keys
interpreted as part of a chord. (default: 50)
*chord_hold_timeout:* The length of time a chord
must be held before being activated. (default: 0)
(default: 0)
*Note:* Unicode characters and key sequences are treated as macros, and

@ -190,34 +190,101 @@ static uint8_t lookup_keycode(const char *name)
return 0;
}
static struct descriptor *layer_lookup_chord(struct layer *layer, uint8_t *keys, size_t n)
{
size_t i;
for (i = 0; i < layer->nr_chords; i++) {
size_t j;
size_t nm = 0;
struct chord *chord = &layer->chords[i];
for (j = 0; j < n; j++) {
size_t k;
for (k = 0; k < chord->sz; k++)
if (keys[j] == chord->keys[k]) {
nm++;
break;
}
}
if (nm == n)
return &chord->d;
}
return NULL;
}
/*
* Consumes a string of the form `[<layer>.]<key> = <descriptor>` and adds the
* mapping to the corresponding layer in the config.
*/
static int set_layer_entry(const struct config *config, struct layer *layer,
const char *key, const struct descriptor *d)
static int set_layer_entry(const struct config *config,
struct layer *layer, char *key,
const struct descriptor *d)
{
size_t i;
int found = 0;
for (i = 0; i < 256; i++) {
if (!strcmp(config->aliases[i], key)) {
layer->keymap[i] = *d;
found = 1;
if (strchr(key, '+')) {
//TODO: Handle aliases
char *tok;
struct descriptor *ld;
uint8_t keys[ARRAY_SIZE(layer->chords[0].keys)];
size_t n = 0;
for (tok = strtok(key, "+"); tok; tok = strtok(NULL, "+")) {
uint8_t code = lookup_keycode(tok);
if (!code) {
err("%s is not a valid key", tok);
return -1;
}
if (n >= ARRAY_SIZE(keys)) {
err("chords cannot contain more than %ld keys", n);
return -1;
}
keys[n++] = code;
}
}
if (!found) {
uint8_t code;
if (!(code = lookup_keycode(key))) {
err("%s is not a valid key or alias", key);
return -1;
if ((ld = layer_lookup_chord(layer, keys, n))) {
*ld = *d;
} else {
struct chord *chord;
if (layer->nr_chords >= ARRAY_SIZE(layer->chords)) {
err("max chords exceeded(%ld)", layer->nr_chords);
return -1;
}
chord = &layer->chords[layer->nr_chords];
memcpy(chord->keys, keys, sizeof keys);
chord->sz = n;
chord->d = *d;
layer->nr_chords++;
}
} else {
for (i = 0; i < 256; i++) {
if (!strcmp(config->aliases[i], key)) {
layer->keymap[i] = *d;
found = 1;
}
}
layer->keymap[code] = *d;
if (!found) {
uint8_t code;
if (!(code = lookup_keycode(key))) {
err("%s is not a valid key or alias", key);
return -1;
}
layer->keymap[code] = *d;
}
}
return 0;
@ -234,6 +301,8 @@ static int new_layer(char *s, const struct config *config, struct layer *layer)
strcpy(layer->name, name);
layer->nr_chords = 0;
if (strchr(name, '+')) {
char *layername;
int n = 0;
@ -348,6 +417,9 @@ static void config_init(struct config *config)
}
/* In ms */
config->chord_interkey_timeout = 50;
config->chord_hold_timeout = 0;
config->macro_timeout = 600;
config->macro_repeat_timeout = 50;
@ -658,6 +730,10 @@ static void parse_global_section(struct config *config, struct ini_section *sect
config->macro_timeout = atoi(ent->val);
else if (!strcmp(ent->key, "macro_sequence_timeout"))
config->macro_sequence_timeout = atoi(ent->val);
else if (!strcmp(ent->key, "chord_hold_timeout"))
config->chord_hold_timeout = atoi(ent->val);
else if (!strcmp(ent->key, "chord_timeout"))
config->chord_interkey_timeout = atoi(ent->val);
else if (!strcmp(ent->key, "default_layout"))
snprintf(config->default_layout, sizeof config->default_layout,
"%s", ent->val);

@ -60,6 +60,13 @@ struct descriptor {
union descriptor_arg args[MAX_DESCRIPTOR_ARGS];
};
struct chord {
uint8_t keys[8];
size_t sz;
struct descriptor d;
};
/*
* A layer is a map from keycodes to descriptors. It may optionally
* contain one or more modifiers which are applied to the base layout in
@ -80,6 +87,9 @@ struct layer {
uint8_t mods;
struct descriptor keymap[256];
struct chord chords[32];
size_t nr_chords;
/* Used for composite layers. */
size_t nr_constituents;
int constituents[8];
@ -114,6 +124,9 @@ struct config {
long macro_sequence_timeout;
long macro_repeat_timeout;
long chord_interkey_timeout;
long chord_hold_timeout;
uint8_t layer_indicator;
char default_layout[MAX_LAYER_NAME_LEN];
};

@ -80,6 +80,8 @@ int evloop(int (*event_handler) (struct event *ev))
if (timeout > 0 && elapsed >= timeout) {
ev.type = EV_TIMEOUT;
ev.dev = NULL;
ev.devev = NULL;
timeout = event_handler(&ev);
} else {
timeout -= elapsed;

@ -258,6 +258,100 @@ static void activate_layer(struct keyboard *kbd, uint8_t code, int idx)
kbd->layer_observer(kbd, kbd->config.layers[idx].name, 1);
}
/* Returns:
* 0 on no match
* 1 on partial match
* 2 on exact match
*/
static int chord_event_match(struct chord *chord, struct key_event *events, size_t nevents)
{
size_t i, j;
size_t n = 0;
size_t npressed = 0;
if (!nevents)
return 0;
for (i = 0; i < nevents; i++)
if (events[i].pressed) {
int found = 0;
npressed++;
for (j = 0; j < chord->sz; j++)
if (chord->keys[j] == events[i].code)
found = 1;
if (!found)
return 0;
else
n++;
}
if (npressed == 0)
return 0;
else
return n == chord->sz ? 2 : 1;
}
static void enqueue_chord_event(struct keyboard *kbd, uint8_t code, uint8_t pressed, long time)
{
if (!code)
return;
assert(kbd->chord.queue_sz < ARRAY_SIZE(kbd->chord.queue));
kbd->chord.queue[kbd->chord.queue_sz].code = code;
kbd->chord.queue[kbd->chord.queue_sz].pressed = pressed;
kbd->chord.queue[kbd->chord.queue_sz].timestamp = time;
kbd->chord.queue_sz++;
}
/* Returns:
* 0 in the case of no match
* 1 in the case of a partial match
* 2 in the case of an unambiguous match (populating d and dl)
* 3 in the case of an ambiguous match (populating d and dl)
*/
static int check_chord_match(struct keyboard *kbd, struct descriptor *d, int *dl)
{
size_t idx;
int full_match = 0;
int partial_match = 0;
long maxts = -1;
for (idx = 0; idx < kbd->config.nr_layers; idx++) {
size_t i;
struct layer *layer = &kbd->config.layers[idx];
if (!kbd->layer_state[idx].active)
continue;
for (i = 0; i < layer->nr_chords; i++) {
int ret = chord_event_match(&layer->chords[i],
kbd->chord.queue,
kbd->chord.queue_sz);
if (ret == 2 &&
maxts <= kbd->layer_state[idx].activation_time) {
*d = layer->chords[i].d;
*dl = idx;
full_match = 1;
maxts = kbd->layer_state[idx].activation_time;
} else if (ret == 1) {
partial_match = 1;
}
}
}
if (full_match)
return partial_match ? 3 : 2;
else if (partial_match)
return 1;
else
return 0;
}
static void execute_command(const char *cmd)
{
int fd;
@ -346,7 +440,7 @@ static void schedule_timeout(struct keyboard *kbd, long timeout)
kbd->timeouts[kbd->nr_timeouts++] = timeout;
}
static long calculate_main_loop_timeout(struct keyboard *kbd, long time)
static long calculate_main_loop_timeout(struct keyboard *kbd, long time)
{
size_t i;
long timeout = 0;
@ -365,7 +459,7 @@ static long calculate_main_loop_timeout(struct keyboard *kbd, long time)
}
static long process_descriptor(struct keyboard *kbd, uint8_t code,
struct descriptor *d, int dl,
const struct descriptor *d, int dl,
int pressed, long time)
{
int timeout = 0;
@ -421,7 +515,7 @@ static long process_descriptor(struct keyboard *kbd, uint8_t code,
struct descriptor *action = &kbd->config.descriptors[d->args[1].idx];
kbd->pending_key.code = code;
kbd->pending_key.behaviour =
kbd->pending_key.behaviour =
d->op == OP_OVERLOAD_TIMEOUT_TAP ?
PK_UNINTERRUPTIBLE_TAP_ACTION2 :
PK_UNINTERRUPTIBLE;
@ -647,12 +741,152 @@ struct keyboard *new_keyboard(struct config *config,
kbd->config.default_layout);
}
kbd->chord.queue_sz = 0;
kbd->chord.state = CHORD_INACTIVE;
kbd->output = sink;
kbd->layer_observer = layer_observer;
return kbd;
}
static int resolve_chord(struct keyboard *kbd)
{
if (kbd->chord.match_sz != 0) {
process_descriptor(kbd,
kbd->chord.start_code,
&kbd->chord.match,
kbd->chord.match_layer, 1, kbd->chord.last_code_time);
cache_set(kbd,
kbd->chord.start_code,
&kbd->chord.match, kbd->chord.match_layer);
}
kbd->chord.state = CHORD_RESOLVING;
kbd_process_events(kbd,
kbd->chord.queue + kbd->chord.match_sz,
kbd->chord.queue_sz - kbd->chord.match_sz);
kbd->chord.state = CHORD_INACTIVE;
return 1;
}
static int abort_chord(struct keyboard *kbd)
{
kbd->chord.match_sz = 0;
return resolve_chord(kbd);
}
static int handle_chord(struct keyboard *kbd,
uint8_t code, int pressed, long time)
{
const long interkey_timeout = kbd->config.chord_interkey_timeout;
const long hold_timeout = kbd->config.chord_hold_timeout;
switch (kbd->chord.state) {
case CHORD_RESOLVING:
return 0;
case CHORD_INACTIVE:
kbd->chord.queue_sz = 0;
kbd->chord.match_sz = 0;
kbd->chord.start_code = code;
enqueue_chord_event(kbd, code, pressed, time);
switch (check_chord_match(kbd, &kbd->chord.match, &kbd->chord.match_layer)) {
case 0:
return 0;
case 3:
kbd->chord.match_sz = kbd->chord.queue_sz;
case 1:
kbd->chord.state = CHORD_PENDING_DISAMBIGUATION;
kbd->chord.last_code_time = time;
schedule_timeout(kbd, time + interkey_timeout);
return 1;
default:
case 2:
kbd->chord.match_sz = kbd->chord.queue_sz;
kbd->chord.last_code_time = time;
if (hold_timeout) {
kbd->chord.state = CHORD_PENDING_HOLD_TIMEOUT;
schedule_timeout(kbd, time + hold_timeout);
} else {
return resolve_chord(kbd);
}
return 1;
}
case CHORD_PENDING_DISAMBIGUATION:
if (!code) {
if ((time - kbd->chord.last_code_time) >= interkey_timeout) {
if (kbd->chord.match_sz) {
long timeleft = hold_timeout - interkey_timeout;
if (timeleft > 0) {
schedule_timeout(kbd, time + timeleft);
kbd->chord.state = CHORD_PENDING_HOLD_TIMEOUT;
} else {
return resolve_chord(kbd);
}
} else {
return abort_chord(kbd);
}
return 1;
}
return 0;
}
enqueue_chord_event(kbd, code, pressed, time);
if (!pressed)
return abort_chord(kbd);
switch (check_chord_match(kbd, &kbd->chord.match, &kbd->chord.match_layer)) {
case 0:
return abort_chord(kbd);
case 3:
kbd->chord.match_sz = kbd->chord.queue_sz;
case 1:
kbd->chord.last_code_time = time;
kbd->chord.state = CHORD_PENDING_DISAMBIGUATION;
schedule_timeout(kbd, time + interkey_timeout);
return 1;
default:
case 2:
kbd->chord.last_code_time = time;
kbd->chord.match_sz = kbd->chord.queue_sz;
if (hold_timeout) {
kbd->chord.state = CHORD_PENDING_HOLD_TIMEOUT;
schedule_timeout(kbd, time + hold_timeout);
} else {
return resolve_chord(kbd);
}
return 1;
}
case CHORD_PENDING_HOLD_TIMEOUT:
if (!code) {
if ((time - kbd->chord.last_code_time) >= hold_timeout)
return resolve_chord(kbd);
return 0;
}
enqueue_chord_event(kbd, code, pressed, time);
if (!pressed) {
size_t i;
for (i = 0; i < kbd->chord.match_sz; i++)
if (kbd->chord.queue[i].code == code)
return abort_chord(kbd);
}
return 1;
}
return 0;
}
/*
* `code` may be 0 in the event of a timeout.
*
@ -724,6 +958,9 @@ static long process_event(struct keyboard *kbd, uint8_t code, int pressed, long
goto exit;
}
if (handle_chord(kbd, code, pressed, time))
goto exit;
if (kbd->active_macro) {
if (code) {
kbd->active_macro = NULL;
@ -732,7 +969,7 @@ static long process_event(struct keyboard *kbd, uint8_t code, int pressed, long
execute_macro(kbd, kbd->active_macro_layer, kbd->active_macro);
schedule_timeout(kbd, time+kbd->macro_repeat_timeout);
}
}
}
if (code) {
if (pressed) {
@ -774,14 +1011,14 @@ long kbd_process_events(struct keyboard *kbd, const struct key_event *events, si
while (i != n) {
const struct key_event *ev = &events[i];
if (timeout > 0 && timeout_ts < ev->timestamp) {
if (timeout > 0 && timeout_ts <= ev->timestamp) {
timeout = process_event(kbd, 0, 0, timeout_ts);
timeout_ts = timeout_ts + timeout;
} else {
timeout = process_event(kbd, ev->code, ev->pressed, ev->timestamp);
timeout_ts = ev->timestamp + timeout;
i++;
}
timeout_ts = ev->timestamp + timeout;
}
return timeout;

@ -50,9 +50,28 @@ struct keyboard {
long macro_repeat_timeout;
long timeouts[32];
long timeouts[64];
size_t nr_timeouts;
struct {
struct key_event queue[32];
size_t queue_sz;
struct descriptor match;
int match_layer;
size_t match_sz;
uint8_t start_code;
long last_code_time;
enum {
CHORD_RESOLVING,
CHORD_INACTIVE,
CHORD_PENDING_DISAMBIGUATION,
CHORD_PENDING_HOLD_TIMEOUT,
} state;
} chord;
struct {
uint8_t code;
uint8_t dl;

@ -0,0 +1,30 @@
a down
b down
200ms
a up
b up
a down
b down
d down
200ms
a up
b up
d up
a down
b down
d down
199ms
a up
b up
d up
control down
control up
shift down
shift up
a down
b down
d down
a up
b up
d up

@ -0,0 +1,18 @@
a down
20ms
b down
199ms
a up
b up
j down
k down
201ms
j up
k up
a down
b down
a up
b up
c down
c up

@ -0,0 +1,26 @@
a down
b down
199ms
x down
x up
a up
b up
1ms
a down
b down
200ms
x down
x up
a up
b up
a down
b down
x down
x up
a up
b up
control down
x down
x up
control up

@ -0,0 +1,25 @@
a down
b down
150ms
a up
b up
x down
x up
a down
b down
200ms
a up
b up
x down
x up
a down
b down
a up
b up
x down
x up
control down
control up
x down
x up

@ -0,0 +1,19 @@
j down
20ms
k down
200ms
j up
k up
j down
100ms
k down
200ms
j up
k up
c down
c up
j down
k down
j up
k up

@ -4,12 +4,20 @@
2fac:2ade
[global]
chord_interkey_timeout = 100
chord_hold_timeout = 200
[main]
esc = clear()
meta = layer(mymeta)
leftalt = layer(myalt)
capslock = layer(capslock)
a+b = layer(control)
j+k = c
a+b+d = layer(shift)
1 = layer(layer1)
2 = oneshot(customshift)
w = oneshot(customshift)

Loading…
Cancel
Save