2022-03-09 02:27:56 +01:00
|
|
|
import unittest
|
|
|
|
|
|
|
|
from kmk.keys import KC
|
2023-03-08 21:14:24 +01:00
|
|
|
from kmk.modules.combos import Chord, Combo, Combos, Sequence
|
2022-04-24 19:34:34 +02:00
|
|
|
from kmk.modules.layers import Layers
|
2022-03-09 02:27:56 +01:00
|
|
|
from tests.keyboard_test import KeyboardTest
|
|
|
|
|
|
|
|
|
|
|
|
class TestCombo(unittest.TestCase):
|
2022-06-05 22:58:01 +02:00
|
|
|
def setUp(self):
|
2023-03-08 21:14:24 +01:00
|
|
|
self.t_within = 2 * KeyboardTest.loop_delay_ms
|
|
|
|
self.t_after = 7 * KeyboardTest.loop_delay_ms
|
|
|
|
timeout = (self.t_after + self.t_within) // 2
|
|
|
|
|
|
|
|
# overide default timeouts
|
|
|
|
Combo.timeout = timeout
|
|
|
|
Sequence.timeout = timeout
|
|
|
|
|
2022-03-09 02:27:56 +01:00
|
|
|
combos = Combos()
|
2022-04-24 19:34:34 +02:00
|
|
|
layers = Layers()
|
|
|
|
KCMO = KC.MO(1)
|
2022-03-09 02:27:56 +01:00
|
|
|
combos.combos = [
|
|
|
|
Chord((KC.A, KC.B, KC.C), KC.Y),
|
|
|
|
Chord((KC.A, KC.B), KC.X),
|
2023-03-08 21:14:24 +01:00
|
|
|
Chord((KC.C, KC.D), KC.Z, timeout=2 * timeout),
|
2022-04-24 19:34:34 +02:00
|
|
|
Chord((KC.C, KCMO), KC.Z),
|
2023-03-08 21:14:24 +01:00
|
|
|
Chord((KC.F, KC.G), KC.Z, timeout=3 * timeout),
|
|
|
|
Sequence((KC.N1, KC.N2, KC.N3), KC.Y),
|
|
|
|
Sequence((KC.N1, KC.N2), KC.X),
|
|
|
|
Sequence((KC.N3, KC.N4), KC.Z, timeout=2 * timeout),
|
|
|
|
Sequence((KC.N1, KC.N1, KC.N1), KC.W),
|
|
|
|
Sequence((KC.N3, KC.N2, KC.N1), KC.Y, fast_reset=False),
|
|
|
|
Sequence((KC.LEADER, KC.N1), KC.V),
|
2022-03-09 02:27:56 +01:00
|
|
|
]
|
2022-06-05 22:58:01 +02:00
|
|
|
self.keyboard = KeyboardTest(
|
2022-04-24 19:34:34 +02:00
|
|
|
[combos, layers],
|
2022-03-09 02:27:56 +01:00
|
|
|
[
|
2022-07-24 14:28:36 +02:00
|
|
|
[KC.A, KC.B, KC.C, KC.D, KC.E, KCMO, KC.F, KC.G],
|
2022-03-09 02:27:56 +01:00
|
|
|
[KC.N1, KC.N2, KC.N3, KC.N4, KC.N5, KC.LEADER],
|
|
|
|
],
|
|
|
|
debug_enabled=False,
|
|
|
|
)
|
|
|
|
|
2022-06-05 22:58:01 +02:00
|
|
|
def test_chord(self):
|
|
|
|
keyboard = self.keyboard
|
|
|
|
t_within = self.t_within
|
|
|
|
t_after = self.t_after
|
2022-03-09 02:27:56 +01:00
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 2 combo, within timeout',
|
|
|
|
[(0, True), t_within, (1, True), (0, False), (1, False), t_after],
|
|
|
|
[{KC.X}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
2022-06-05 22:58:01 +02:00
|
|
|
'match: 3 combo, within timeout, shuffled',
|
2022-03-09 02:27:56 +01:00
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(2, True),
|
|
|
|
(1, True),
|
|
|
|
(1, False),
|
|
|
|
(0, False),
|
|
|
|
(2, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.Y}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 2 combo + overlap, after timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(1, True),
|
|
|
|
(0, False),
|
|
|
|
(1, False),
|
|
|
|
t_after,
|
|
|
|
(2, True),
|
|
|
|
(2, False),
|
2022-06-05 22:58:01 +02:00
|
|
|
t_after,
|
2022-03-09 02:27:56 +01:00
|
|
|
],
|
|
|
|
[{KC.X}, {}, {KC.C}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 2 combo + overlap, interleaved, after timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(1, True),
|
|
|
|
t_after,
|
|
|
|
(2, True),
|
|
|
|
2 * t_after,
|
|
|
|
(0, False),
|
|
|
|
(2, False),
|
|
|
|
(1, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.X}, {KC.X, KC.C}, {KC.C}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 2 combo hold + other, interleaved, after timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(1, True),
|
|
|
|
t_after,
|
|
|
|
(4, True),
|
|
|
|
(4, False),
|
|
|
|
(0, False),
|
|
|
|
(1, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.X}, {KC.X, KC.E}, {KC.X}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 2 combo hold + overlap, interleaved, after timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(1, True),
|
|
|
|
t_after,
|
|
|
|
(2, True),
|
|
|
|
(2, False),
|
|
|
|
2 * t_after,
|
|
|
|
(0, False),
|
|
|
|
(1, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.X}, {KC.X, KC.C}, {KC.X}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: other + 2 combo, after timeout',
|
|
|
|
[
|
|
|
|
(4, True),
|
|
|
|
t_after,
|
|
|
|
(0, True),
|
|
|
|
(1, True),
|
|
|
|
t_after,
|
|
|
|
(1, False),
|
|
|
|
(4, False),
|
|
|
|
(0, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.E}, {KC.E, KC.X}, {KC.E}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 2 combo + other, after timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(1, True),
|
|
|
|
t_after,
|
|
|
|
(4, True),
|
|
|
|
(1, False),
|
|
|
|
(4, False),
|
|
|
|
(0, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.X}, {KC.E, KC.X}, {KC.E}, {}],
|
|
|
|
)
|
|
|
|
|
2022-06-05 22:58:01 +02:00
|
|
|
keyboard.test(
|
|
|
|
'match: 2 combo, partial release and repeat',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(1, True),
|
|
|
|
t_after,
|
|
|
|
(1, False),
|
|
|
|
t_after,
|
|
|
|
(1, True),
|
|
|
|
(1, False),
|
|
|
|
(0, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.X}, {}, {KC.X}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 2 combo, partial release and repeat',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(2, True),
|
|
|
|
(1, True),
|
|
|
|
t_after,
|
|
|
|
(1, False),
|
|
|
|
(0, False),
|
|
|
|
t_after,
|
|
|
|
(1, True),
|
|
|
|
(0, True),
|
|
|
|
(1, False),
|
|
|
|
(0, False),
|
|
|
|
(2, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.Y}, {}, {KC.Y}, {}],
|
|
|
|
)
|
|
|
|
|
2022-03-09 02:27:56 +01:00
|
|
|
keyboard.test(
|
|
|
|
'match: 2 combo + 2 combo, after timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(1, True),
|
|
|
|
t_after,
|
|
|
|
(2, True),
|
|
|
|
(3, True),
|
|
|
|
(0, False),
|
|
|
|
(1, False),
|
|
|
|
(2, False),
|
|
|
|
(3, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.X}, {KC.X, KC.Z}, {KC.Z}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 2 combo hold + 2 combo, after timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(1, True),
|
|
|
|
t_after,
|
|
|
|
(2, True),
|
|
|
|
(3, True),
|
|
|
|
(2, False),
|
|
|
|
(3, False),
|
|
|
|
t_after,
|
|
|
|
(0, False),
|
|
|
|
(1, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.X}, {KC.X, KC.Z}, {KC.X}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
2022-06-05 22:58:01 +02:00
|
|
|
'no match: partial combo, after timeout',
|
2022-03-09 02:27:56 +01:00
|
|
|
[(0, True), (0, False), t_after],
|
|
|
|
[{KC.A}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'no match: partial combo, repeated',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
t_within,
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
t_within,
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.A}, {}, {KC.A}, {}, {KC.A}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'no match: partial combo, repeated',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
(1, True),
|
|
|
|
(1, False),
|
|
|
|
t_within,
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.A}, {}, {KC.B}, {}, {KC.A}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'no match: 3 combo after timout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(2, True),
|
|
|
|
t_after,
|
|
|
|
(1, True),
|
|
|
|
t_after,
|
|
|
|
(1, False),
|
|
|
|
(0, False),
|
|
|
|
(2, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.A}, {KC.A, KC.C}, {KC.A, KC.C, KC.B}, {KC.A, KC.C}, {KC.C}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'no match: other + 2 combo within timeout',
|
|
|
|
[
|
|
|
|
(4, True),
|
|
|
|
t_within,
|
|
|
|
(0, True),
|
|
|
|
(1, True),
|
|
|
|
t_after,
|
|
|
|
(1, False),
|
|
|
|
(4, False),
|
|
|
|
(0, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.E}, {KC.E, KC.A}, {KC.E, KC.A, KC.B}, {KC.E, KC.A}, {KC.A}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
2022-06-05 22:58:01 +02:00
|
|
|
'no match: 2 combo + other within timeout',
|
2022-03-09 02:27:56 +01:00
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
t_within,
|
|
|
|
(1, True),
|
|
|
|
(4, True),
|
|
|
|
t_after,
|
|
|
|
(1, False),
|
|
|
|
(4, False),
|
|
|
|
(0, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.A}, {KC.A, KC.B}, {KC.A, KC.B, KC.E}, {KC.A, KC.E}, {KC.A}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
2022-06-05 22:58:01 +02:00
|
|
|
'no match: 2 combo after timeout',
|
2022-03-09 02:27:56 +01:00
|
|
|
[(0, True), (0, False), t_after, (1, True), (1, False), t_after],
|
|
|
|
[{KC.A}, {}, {KC.B}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'no match: Combo + other, within timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(1, True),
|
|
|
|
(4, True),
|
|
|
|
(0, False),
|
|
|
|
(1, False),
|
|
|
|
(4, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.A}, {KC.A, KC.B}, {KC.A, KC.B, KC.E}, {KC.B, KC.E}, {KC.E}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'no match: Combo + other, within timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(4, True),
|
|
|
|
(1, True),
|
|
|
|
(0, False),
|
|
|
|
(1, False),
|
|
|
|
(4, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.A}, {KC.A, KC.E}, {KC.A, KC.B, KC.E}, {KC.B, KC.E}, {KC.E}, {}],
|
|
|
|
)
|
|
|
|
|
2022-04-24 19:34:34 +02:00
|
|
|
# test combos with layer switch
|
|
|
|
keyboard.test(
|
|
|
|
'match: Combo containing layer switch, within timeout',
|
|
|
|
[
|
|
|
|
(5, True),
|
|
|
|
(2, True),
|
|
|
|
(2, False),
|
|
|
|
(5, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.Z}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'no match: Combo containing layer switch + other, within timeout',
|
|
|
|
[
|
|
|
|
(5, True),
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
(5, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.N1}, {}],
|
|
|
|
)
|
|
|
|
|
2022-07-24 14:28:36 +02:00
|
|
|
keyboard.test(
|
|
|
|
'match: Other pressed and released before combo, delay after other press but within the combo timeout',
|
|
|
|
[
|
|
|
|
(1, True),
|
|
|
|
t_within,
|
|
|
|
t_within,
|
|
|
|
(1, False),
|
|
|
|
(7, True),
|
|
|
|
(6, True),
|
|
|
|
(6, False),
|
|
|
|
(7, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.B}, {}, {KC.Z}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: Other pressed and released before combo, delay after other release but within the combo timeout',
|
|
|
|
[
|
|
|
|
(1, True),
|
|
|
|
(1, False),
|
|
|
|
t_within,
|
|
|
|
t_within,
|
|
|
|
(7, True),
|
|
|
|
(6, True),
|
|
|
|
(6, False),
|
|
|
|
(7, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.B}, {}, {KC.Z}, {}],
|
|
|
|
)
|
|
|
|
keyboard.test(
|
|
|
|
'match: Other pressed and released before combo, delay after other pressed but within the combo timeout, other is part of another combo',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
t_within,
|
|
|
|
t_within,
|
|
|
|
(0, False),
|
|
|
|
(7, True),
|
|
|
|
(6, True),
|
|
|
|
(6, False),
|
|
|
|
(7, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.A}, {}, {KC.Z}, {}],
|
|
|
|
)
|
|
|
|
keyboard.test(
|
|
|
|
'match: Other pressed and released before combo, delay after other release but within the combo timeout, other is part of another combo',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
t_within,
|
|
|
|
t_within,
|
|
|
|
(7, True),
|
|
|
|
(6, True),
|
|
|
|
(6, False),
|
|
|
|
(7, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.A}, {}, {KC.Z}, {}],
|
|
|
|
)
|
|
|
|
|
2022-06-05 22:58:01 +02:00
|
|
|
def test_sequence(self):
|
|
|
|
keyboard = self.keyboard
|
|
|
|
t_within = self.t_within
|
|
|
|
t_after = self.t_after
|
|
|
|
|
2022-03-09 02:27:56 +01:00
|
|
|
keyboard.keyboard.active_layers = [1]
|
2022-06-05 22:58:01 +02:00
|
|
|
|
2022-03-09 02:27:56 +01:00
|
|
|
keyboard.test(
|
|
|
|
'match: leader sequence, within timeout',
|
|
|
|
[(5, True), (5, False), t_within, (0, True), (0, False), t_after],
|
|
|
|
[{KC.V}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 2 sequence, within timeout',
|
|
|
|
[(0, True), (0, False), t_within, (1, True), (1, False), t_after],
|
|
|
|
[{KC.X}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 2 sequence, within long timeout',
|
|
|
|
[(2, True), (2, False), 2 * t_within, (3, True), (3, False), 2 * t_after],
|
|
|
|
[{KC.Z}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 3 sequence, within timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
t_within,
|
|
|
|
(1, True),
|
|
|
|
(1, False),
|
|
|
|
t_within,
|
|
|
|
(2, True),
|
|
|
|
(2, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.Y}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 3 sequence, same key, within timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
t_within,
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
t_within,
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.W}, {}],
|
|
|
|
)
|
|
|
|
|
2022-06-05 22:58:01 +02:00
|
|
|
keyboard.test(
|
|
|
|
'match: 3 sequence hold + other, within timeout',
|
|
|
|
[
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
(0, True),
|
|
|
|
t_after,
|
|
|
|
(4, True),
|
|
|
|
(0, False),
|
|
|
|
(4, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.W}, {KC.W, KC.N5}, {KC.N5}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'match: 3 sequence, partial release and repeat',
|
|
|
|
[
|
|
|
|
(2, True),
|
|
|
|
(1, True),
|
|
|
|
(0, True),
|
|
|
|
(0, False),
|
|
|
|
(1, False),
|
|
|
|
(1, True),
|
|
|
|
(0, True),
|
|
|
|
(1, False),
|
|
|
|
(2, False),
|
|
|
|
(0, False),
|
|
|
|
t_after,
|
|
|
|
],
|
|
|
|
[{KC.Y}, {}, {KC.Y}, {}],
|
|
|
|
)
|
|
|
|
|
2022-03-09 02:27:56 +01:00
|
|
|
keyboard.test(
|
|
|
|
'no match: 2 sequence, after timeout',
|
|
|
|
[(0, True), (0, False), t_after, (1, True), (1, False), t_after],
|
|
|
|
[{KC.N1}, {}, {KC.N2}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard.test(
|
|
|
|
'no match: 2 sequence, out of order',
|
|
|
|
[(1, True), (1, False), t_within, (0, True), (0, False), t_after],
|
|
|
|
[{KC.N2}, {}, {KC.N1}, {}],
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
unittest.main()
|