Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion inputremapper/injection/macros/tasks/hold_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,26 @@ async def run(self, callback) -> None:

codes = [keyboard_layout.get(symbol) for symbol in symbols]

held_codes = set()
for code in codes:
if code in held_codes:
# Release and re-press to support duplicate keys in a sequence
callback(EV_KEY, code, 0)
await self.keycode_pause()
callback(EV_KEY, code, 1)
held_codes.add(code)
await self.keycode_pause()

await self._trigger_release_event.wait()

for code in codes[::-1]:
# Release each unique code once, in reverse order of first appearance
seen = set()
unique_codes_reversed = []
for code in reversed(codes):
if code not in seen:
seen.add(code)
unique_codes_reversed.append(code)

for code in unique_codes_reversed:
callback(EV_KEY, code, 0)
await self.keycode_pause()
72 changes: 72 additions & 0 deletions tests/unit/test_macros/test_hold_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,78 @@ async def test_run_plus_syntax(self):
self.assertEqual(self.result[6], (EV_KEY, keyboard_layout.get("b"), 0))
self.assertEqual(self.result[7], (EV_KEY, keyboard_layout.get("a"), 0))

async def test_hold_keys_with_duplicates(self):
macro = Parser.parse(
"hold_keys(KEY_4, KEY_3, KEY_5, KEY_5, KEY_9)", self.context, DummyMapping
)

code_4 = keyboard_layout.get("KEY_4")
code_3 = keyboard_layout.get("KEY_3")
code_5 = keyboard_layout.get("KEY_5")
code_9 = keyboard_layout.get("KEY_9")

macro.press_trigger()
asyncio.ensure_future(macro.run(self.handler))
await asyncio.sleep(0.2)

# Duplicate KEY_5 should be released and re-pressed
self.assertListEqual(
self.result,
[
(EV_KEY, code_4, 1),
(EV_KEY, code_3, 1),
(EV_KEY, code_5, 1),
(EV_KEY, code_5, 0),
(EV_KEY, code_5, 1),
(EV_KEY, code_9, 1),
],
)

macro.release_trigger()
await asyncio.sleep(0.2)

# Each unique code released once, in reverse order of first appearance
self.assertListEqual(
self.result[6:],
[
(EV_KEY, code_9, 0),
(EV_KEY, code_5, 0),
(EV_KEY, code_3, 0),
(EV_KEY, code_4, 0),
],
)

async def test_plus_syntax_with_duplicates(self):
macro = Parser.parse("KEY_5 + KEY_5 + KEY_3", self.context, DummyMapping)

code_5 = keyboard_layout.get("KEY_5")
code_3 = keyboard_layout.get("KEY_3")

macro.press_trigger()
asyncio.ensure_future(macro.run(self.handler))
await asyncio.sleep(0.2)

self.assertListEqual(
self.result,
[
(EV_KEY, code_5, 1),
(EV_KEY, code_5, 0),
(EV_KEY, code_5, 1),
(EV_KEY, code_3, 1),
],
)

macro.release_trigger()
await asyncio.sleep(0.2)

self.assertListEqual(
self.result[4:],
[
(EV_KEY, code_3, 0),
(EV_KEY, code_5, 0),
],
)

async def test_raises_error(self):
self.assertRaises(
MacroError, Parser.parse, "hold_keys(a, broken, b)", self.context
Expand Down
Loading