diff --git a/data/keyd-application-mapper.1.gz b/data/keyd-application-mapper.1.gz index 8506383..18212e7 100644 Binary files a/data/keyd-application-mapper.1.gz and b/data/keyd-application-mapper.1.gz differ diff --git a/data/keyd.1.gz b/data/keyd.1.gz index c6e6702..51161bb 100644 Binary files a/data/keyd.1.gz and b/data/keyd.1.gz differ diff --git a/docs/keyd.scdoc b/docs/keyd.scdoc index 553f370..b972c57 100644 --- a/docs/keyd.scdoc +++ b/docs/keyd.scdoc @@ -738,16 +738,20 @@ A key may optionally be bound to an _action_ which accepts zero or more argument *overloadi(, overloadt2(, , ), )* *timeout(, , )* - If the key is held in isolation for more than _ ms_, activate the second - action, if the key is held for less than _ ms_ or another key is struck - before ms expires, execute the first action. + If no key events occur within _ ms_, execute , otherwise + execute . 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. + Will cause the assigned key to behave as control if it is held for more than + 500 ms without any keys being struck in the interval. + + *NOTE:* This is an older option with numerous subtle implications. It is + mainly intended to be used in combination with other actions to achieve + things which are not otherwise possible. Most users will want to use + one of overload functions for simple tap/hold behaviour. *macro2(, , )* Creates a macro with the given timeout and repeat timeout. If a timeout value of 0 is used, diff --git a/src/keyboard.c b/src/keyboard.c index 7e648a1..3e0705a 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -571,19 +571,16 @@ static long process_descriptor(struct keyboard *kbd, uint8_t code, uint8_t layer = d->args[0].idx; struct descriptor *action = &kbd->config.descriptors[d->args[1].idx]; - kbd->pending_key.code = code; - kbd->pending_key.behaviour = - d->op == OP_OVERLOAD_TIMEOUT_TAP ? - PK_UNINTERRUPTIBLE_TAP_ACTION2 : - PK_UNINTERRUPTIBLE; - - kbd->pending_key.dl = dl; - kbd->pending_key.action1 = *action; - kbd->pending_key.action2.op = OP_LAYER; - kbd->pending_key.action2.args[0].idx = layer; - kbd->pending_key.expire = time+d->args[2].timeout; - - schedule_timeout(kbd, kbd->pending_key.expire); + kbd->pending_overload.code = code; + kbd->pending_overload.resolve_on_interrupt = d->op == OP_OVERLOAD_TIMEOUT_TAP; + + kbd->pending_overload.dl = dl; + kbd->pending_overload.action1 = *action; + kbd->pending_overload.action2.op = OP_LAYER; + kbd->pending_overload.action2.args[0].idx = layer; + kbd->pending_overload.expiration = time + d->args[2].timeout; + + schedule_timeout(kbd, kbd->pending_overload.expiration); } break; @@ -720,18 +717,23 @@ static long process_descriptor(struct keyboard *kbd, uint8_t code, break; case OP_TIMEOUT: + struct pending_timeout *pt = &kbd->pending_timeout; + if (pressed) { - kbd->pending_key.action1 = kbd->config.descriptors[d->args[0].idx]; - kbd->pending_key.action2 = kbd->config.descriptors[d->args[2].idx]; + pt->code = code; + pt->dl = dl; - kbd->pending_key.code = code; - kbd->pending_key.dl = dl; - kbd->pending_key.expire = time + d->args[1].timeout; - kbd->pending_key.behaviour = PK_INTERRUPT_ACTION1; + pt->action1 = kbd->config.descriptors[d->args[0].idx]; + pt->expiration = time + d->args[1].timeout; + pt->action2 = kbd->config.descriptors[d->args[2].idx]; - schedule_timeout(kbd, kbd->pending_key.expire); - } + pt->activation_time = time; + pt->spontaneous = 0; + schedule_timeout(kbd, pt->expiration); + } else if (time == kbd->pending_timeout.activation_time) { + pt->spontaneous = 1; + } break; case OP_COMMAND: if (pressed) { @@ -1032,77 +1034,92 @@ static int handle_chord(struct keyboard *kbd, return 0; } -int handle_pending_key(struct keyboard *kbd, uint8_t code, int pressed, long time) +int handle_pending_timeout(struct keyboard *kbd, uint8_t event_code, int pressed, long time) { - if (!kbd->pending_key.code) + struct pending_timeout pt = kbd->pending_timeout; + + if (!pt.code || (!pressed && pt.code == event_code && time == pt.activation_time)) return 0; - struct descriptor action = {0}; + if (pt.spontaneous) { + if ((time >= pt.expiration) || event_code) { + struct descriptor action = time >= pt.expiration ? pt.action2 : pt.action1; + kbd->pending_timeout.code = 0; + + process_descriptor(kbd, pt.code, &action, pt.dl, 1, time); + process_descriptor(kbd, pt.code, &action, pt.dl, 0, time); + } + } else if (time >= pt.expiration || (event_code && (pressed || event_code == pt.code))) { + struct descriptor action = time >= pt.expiration ? pt.action2 : pt.action1; + kbd->pending_timeout.code = 0; + + cache_set(kbd, pt.code, &(struct cache_entry){ + .code = pt.code, + .dl = pt.dl, + .d = action, + }); + process_descriptor(kbd, pt.code, &action, pt.dl, 1, time); + } + + return 0; +} + +int handle_pending_overload(struct keyboard *kbd, uint8_t code, int pressed, long time) +{ + struct descriptor action; + + if (!kbd->pending_overload.code) + return 0; if (code) { struct key_event *ev; - assert(kbd->pending_key.queue_sz < ARRAY_SIZE(kbd->pending_key.queue)); + assert(kbd->pending_overload.queue_sz < ARRAY_SIZE(kbd->pending_overload.queue)); if (!pressed) { size_t i; int found = 0; - for (i = 0; i < kbd->pending_key.queue_sz; i++) - if (kbd->pending_key.queue[i].code == code) + for (i = 0; i < kbd->pending_overload.queue_sz; i++) + if (kbd->pending_overload.queue[i].code == code) found = 1; /* Propagate key up events for keys which were struck before the pending key. */ - if (!found && code != kbd->pending_key.code) + if (!found && code != kbd->pending_overload.code) return 0; } - ev = &kbd->pending_key.queue[kbd->pending_key.queue_sz]; + ev = &kbd->pending_overload.queue[kbd->pending_overload.queue_sz]; ev->code = code; ev->pressed = pressed; ev->timestamp = time; - kbd->pending_key.queue_sz++; + kbd->pending_overload.queue_sz++; } - if (time >= kbd->pending_key.expire) { - action = kbd->pending_key.action2; - } else if (code == kbd->pending_key.code) { - if (kbd->pending_key.tap_expiry && time >= kbd->pending_key.tap_expiry) { - action.op = OP_KEYSEQUENCE; - action.args[0].code = KEYD_NOOP; - } else { - action = kbd->pending_key.action1; - } - } else if (code && pressed && kbd->pending_key.behaviour == PK_INTERRUPT_ACTION1) { - action = kbd->pending_key.action1; - } else if (code && pressed && kbd->pending_key.behaviour == PK_INTERRUPT_ACTION2) { - action = kbd->pending_key.action2; - } else if (kbd->pending_key.behaviour == PK_UNINTERRUPTIBLE_TAP_ACTION2 && !pressed) { - size_t i; - - for (i = 0; i < kbd->pending_key.queue_sz; i++) - if (kbd->pending_key.queue[i].code == code) { - action = kbd->pending_key.action2; - break; - } - } + if (time >= kbd->pending_overload.expiration) + action = kbd->pending_overload.action2; + else if (code == kbd->pending_overload.code) + action = kbd->pending_overload.action1; + else if (kbd->pending_overload.resolve_on_interrupt && !pressed) + action = kbd->pending_overload.action2; + else + action.op = 0; if (action.op) { /* Create a copy of the queue on the stack to allow for recursive pending key processing. */ - struct key_event queue[ARRAY_SIZE(kbd->pending_key.queue)]; - size_t queue_sz = kbd->pending_key.queue_sz; + struct key_event queue[ARRAY_SIZE(kbd->pending_overload.queue)]; + size_t queue_sz = kbd->pending_overload.queue_sz; - uint8_t code = kbd->pending_key.code; - int dl = kbd->pending_key.dl; + uint8_t code = kbd->pending_overload.code; + int dl = kbd->pending_overload.dl; - memcpy(queue, kbd->pending_key.queue, sizeof kbd->pending_key.queue); + memcpy(queue, kbd->pending_overload.queue, sizeof kbd->pending_overload.queue); - kbd->pending_key.code = 0; - kbd->pending_key.queue_sz = 0; - kbd->pending_key.tap_expiry = 0; + kbd->pending_overload.code = 0; + kbd->pending_overload.queue_sz = 0; cache_set(kbd, code, &(struct cache_entry) { .d = action, @@ -1133,7 +1150,10 @@ static long process_event(struct keyboard *kbd, uint8_t code, int pressed, long if (handle_chord(kbd, code, pressed, time)) goto exit; - if (handle_pending_key(kbd, code, pressed, time)) + if (handle_pending_timeout(kbd, code, pressed, time)) + goto exit; + + if (handle_pending_overload(kbd, code, pressed, time)) goto exit; if (kbd->oneshot_timeout && time >= kbd->oneshot_timeout) { diff --git a/src/keyboard.h b/src/keyboard.h index dc94075..4b1c7a1 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -67,7 +67,7 @@ struct keyboard { long last_simple_key_time; - long timeouts[64]; + long timeouts[128]; size_t nr_timeouts; struct active_chord { @@ -94,25 +94,31 @@ struct keyboard { } state; } chord; - struct { + struct pending_timeout { uint8_t code; uint8_t dl; - long expire; - long tap_expiry; + uint8_t spontaneous; - enum { - PK_INTERRUPT_ACTION1, - PK_INTERRUPT_ACTION2, - PK_UNINTERRUPTIBLE, - PK_UNINTERRUPTIBLE_TAP_ACTION2, - } behaviour; + long expiration; + long activation_time; + + struct descriptor action1; + struct descriptor action2; + } pending_timeout; + + struct pending_overload { + uint8_t code; + uint8_t dl; + long expiration; + + int resolve_on_interrupt; struct key_event queue[32]; size_t queue_sz; struct descriptor action1; struct descriptor action2; - } pending_key; + } pending_overload; struct { long activation_time; diff --git a/t/double-tap.t b/t/double-tap.t new file mode 100644 index 0000000..c904619 --- /dev/null +++ b/t/double-tap.t @@ -0,0 +1,24 @@ +up down +1ms +up up +up down +1ms +up up +up down +200ms +up up +up down +1ms +up up +100ms +x down +x up + +a down +a up +c down +c up +b down +b up +x down +x up diff --git a/t/test.conf b/t/test.conf index 03bd453..d6cec40 100644 --- a/t/test.conf +++ b/t/test.conf @@ -6,7 +6,6 @@ [global] -chord_interkey_timeout = 100 chord_hold_timeout = 200 overload_tap_timeout = 5 @@ -29,7 +28,7 @@ p = layerm(shift, macro(on)) 5 = layer(symbols) 6 = overload(6l, esc) 7 = overload(meta, oneshot(control)) -8 = timeout(overload(control, a), 1, b) +8 = timeout(overload(control, a), 2, b) 9 = M-C-S-x 1+2 = oneshot(test) l = layer(test) @@ -45,6 +44,14 @@ o = overloadt(control, a, 10) [ = togglem(control, macro(one)) z = overload(control, enter) / = z +left = timeout(a, 100, timeout(b, 200, c)) +right = timeout(timeout(a, 100, b), 200, c) +up = timeout(timeout(oneshot(double), 100, b), 200, c) +delete = overloadt(control, timeout(a, 100, b), 100) + +[double] + +up = a [altgr] diff --git a/t/timeout-nested-1.t b/t/timeout-nested-1.t new file mode 100644 index 0000000..41b8ef9 --- /dev/null +++ b/t/timeout-nested-1.t @@ -0,0 +1,20 @@ +left down +99ms +left up +left down +299ms +left up +left down +300ms +left up +x down +x up + +a down +a up +b down +b up +c down +c up +x down +x up diff --git a/t/timeout-nested-2.t b/t/timeout-nested-2.t new file mode 100644 index 0000000..f0bd1ff --- /dev/null +++ b/t/timeout-nested-2.t @@ -0,0 +1,31 @@ +right down +200ms +right up +right down +199ms +right up +99ms +x down +x up +right down +200ms +right up +right down +199ms +right up +100ms +x down +x up + +c down +c up +a down +a up +x down +x up +c down +c up +b down +b up +x down +x up diff --git a/t/timeout-overloadt.t b/t/timeout-overloadt.t new file mode 100644 index 0000000..b661927 --- /dev/null +++ b/t/timeout-overloadt.t @@ -0,0 +1,21 @@ +delete down +100ms +delete up +delete down +99ms +delete up +delete down +99ms +delete up +100ms +x down +x up + +leftcontrol down +leftcontrol up +a down +a up +b down +b up +x down +x up diff --git a/t/timeout1.t b/t/timeout1.t index c0b63d0..77107fe 100644 --- a/t/timeout1.t +++ b/t/timeout1.t @@ -10,6 +10,11 @@ x down 200ms = up x up += down +300ms +x down += up +x up a down a up @@ -19,3 +24,7 @@ a down x down a up x up +b down +x down +b up +x up diff --git a/t/timeout2.t b/t/timeout2.t index cb41267..fad15d3 100644 --- a/t/timeout2.t +++ b/t/timeout2.t @@ -3,6 +3,7 @@ x down x up 8 up 8 down +1ms 8 up 8 down 2ms diff --git a/t/timeout3.t b/t/timeout3.t new file mode 100644 index 0000000..3eef4f6 --- /dev/null +++ b/t/timeout3.t @@ -0,0 +1,19 @@ += down += up +299ms +x down +x up += down += up +300ms +x down +x up + +a down +a up +x down +x up +b down +b up +x down +x up