timeout: Add new behaviour when used as a tap target (#879, #738, #944)

This patch expands the semantics of timeout() to cover the case in which it is
used as a tap target. This facilitates a number of novel use cases, like
discriminating between single/double tap or implementing per-key oneshot
timeouts.

Specifically timeout() is now defined in terms of arbitrary key events rather
than the behaviour of the key to which it is bound (if any). The new definition
executes the second action if no key events occur before the timeout expires.

The implication of this is that when timeout() is executed as the result of a
tap action, action2 will be executed after the timeout expires unless another
key is struck in the interval. Note that this is backward compatible with the
old definition, since a key up event (i.e a tap) will result in a resolution to
the first action if timeout() is directly bound to a key.

For Example:

	tab = overload(control, timeout(a, 100, b))

will presently produce no effect when 'tab' is tapped. Under the expanded
definition, tapping tab will produce 'b' if 100 milliseconds elapse without an
interceding key event.
master
Raheman Vaiya 1 year ago
parent f20bd7a441
commit 6b4f9f12ff
  1. BIN
      data/keyd-application-mapper.1.gz
  2. BIN
      data/keyd.1.gz
  3. 14
      docs/keyd.scdoc
  4. 142
      src/keyboard.c
  5. 28
      src/keyboard.h
  6. 24
      t/double-tap.t
  7. 11
      t/test.conf
  8. 20
      t/timeout-nested-1.t
  9. 31
      t/timeout-nested-2.t
  10. 21
      t/timeout-overloadt.t
  11. 9
      t/timeout1.t
  12. 1
      t/timeout2.t
  13. 19
      t/timeout3.t

Binary file not shown.

Binary file not shown.

@ -738,16 +738,20 @@ A key may optionally be bound to an _action_ which accepts zero or more argument
*overloadi(<key>, overloadt2(<layer>, <key>, <hold timeout>), <idle timeout>)*
*timeout(<action 1>, <timeout>, <action 2>)*
If the key is held in isolation for more than _<timeout> ms_, activate the second
action, if the key is held for less than _<timeout> ms_ or another key is struck
before <timeout> ms expires, execute the first action.
If no key events occur within _<timeout> ms_, execute <action1>, otherwise
execute <action1>.
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(<timeout>, <repeat timeout>, <macro>)*
Creates a macro with the given timeout and repeat timeout. If a timeout value of 0 is used,

@ -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) {

@ -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;

@ -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

@ -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]

@ -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

@ -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

@ -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

@ -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

@ -3,6 +3,7 @@ x down
x up
8 up
8 down
1ms
8 up
8 down
2ms

@ -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
Loading…
Cancel
Save