Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add toggle macro #427

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
25 changes: 25 additions & 0 deletions inputremapper/injection/macros/macro.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,27 @@ async def task(handler):

self.tasks.append(task)

def add_toggle(self, symbol):
"""Press when calling toggle the first time, release it next time."""
_type_check_symbol(symbol)

async def task(handler):
key = f"_toggle_{symbol}"

resolved_symbol = _resolve(symbol, [str])
code = _type_check_symbol(resolved_symbol)

if macro_variables[key]:
macro_variables[key] = False
handler(EV_KEY, code, 0)
await self._keycode_pause()
else:
macro_variables[key] = True
handler(EV_KEY, code, 1)
await self._keycode_pause()
Comment on lines +335 to +348
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can avoid using the global variable dict and all the overhead, and the potential name conflict with user variables:

Suggested change
async def task(handler):
key = f"_toggle_{symbol}"
resolved_symbol = _resolve(symbol, [str])
code = _type_check_symbol(resolved_symbol)
if macro_variables[key]:
macro_variables[key] = False
handler(EV_KEY, code, 0)
await self._keycode_pause()
else:
macro_variables[key] = True
handler(EV_KEY, code, 1)
await self._keycode_pause()
def f():
while True:
resolved_symbol = _resolve(symbol, [str])
code = _type_check_symbol(resolved_symbol)
yield EV_KEY, code, 1
resolved_symbol = _resolve(symbol, [str])
code = _type_check_symbol(resolved_symbol)
yield EV_KEY, code, 0
toggle = f()
async def task(handler):
handler(*next(toggle))
await self._keycode_pause()

I didn't test this yet but it avoids the global dict altogether and the current state is stored inside the generator, which is initialized when parsing the macro.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the generator would be unique for each call to toggle, wouldn't it?

I think pressing the key twice would correctly toggle it, but toggle(KEY_A).toggle(KEY_A) would inject two key-down events as far as I can tell

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah toggle(KEY_A).toggle(KEY_A) would send two key down events. and on the second press it would send two key up events.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toggle(KEY_A).toggle(KEY_A) can also be written like key_down(KEY_A).key_up(KEY_A), not sure if there are any potential other issues. But I am fine with this either way.


self.tasks.append(task)

def add_key_down(self, symbol):
"""Press the symbol."""
_type_check_symbol(symbol)
Expand Down Expand Up @@ -387,6 +408,10 @@ async def task(handler):
# not-releasing any key
await macro.run(handler)

# to avoid extremely fast loops that can freeze input-remapper
# while holding down, sleep for 1 ms
await asyncio.sleep(1 / 1000)

self.tasks.append(task)
self.child_macros.append(macro)

Expand Down
1 change: 1 addition & 0 deletions inputremapper/injection/macros/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def is_this_a_macro(output):
"key": Macro.add_key,
"key_down": Macro.add_key_down,
"key_up": Macro.add_key_up,
"toggle": Macro.add_toggle,
"event": Macro.add_event,
"wait": Macro.add_wait,
"hold": Macro.add_hold,
Expand Down
8 changes: 8 additions & 0 deletions readme/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,14 @@ Bear in mind that anti-cheat software might detect macros in games.
> if_single(key(KEY_A), key(KEY_B), timeout=1000)
> ```

### toggle

> Press it once to inject a key-down event, press it a second time to inject a key-up event.
>
> ```c#
> toggle(KEY_A)
> ```

## Syntax

Multiple functions are chained using `.`.
Expand Down
7 changes: 6 additions & 1 deletion tests/unit/test_keycode_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ def wait(func, timeout=1.0):


def calculate_event_number(holdtime, before, after):
"""
"""Calculate how many events a k(a).h(k(b)).k(c) macro would inject

Parameters
----------
holdtime : int
Expand All @@ -91,6 +92,10 @@ def calculate_event_number(holdtime, before, after):
# one initial k(a):
events = before * 2
holdtime -= keystroke_sleep * 2

# because it sleeps for a millisecond to prevent freezes if keystroke_sleep_ms is 0
holdtime -= 1

# hold events
events += (holdtime / (keystroke_sleep * 2)) * 2
# one trailing k(c)
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/test_macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,41 @@ async def test_fails(self):
# it might look like it without the string quotes.
self.assertIsNone(parse('"modify(a, b)"', self.context))

async def test_toggle(self):
macro = parse("toggle(KEY_B).key(KEY_A).toggle(KEY_B)", self.context)
code_a = system_mapping.get("a")
code_b = system_mapping.get("b")

await macro.run(self.handler)
self.assertListEqual(
self.result,
[
(EV_KEY, code_b, 1),
(EV_KEY, code_a, 1),
(EV_KEY, code_a, 0),
(EV_KEY, code_b, 0),
],
)
self.assertEqual(len(macro.child_macros), 0)

async def test_toggle_multiple_macro_runs(self):
macro = parse("toggle(KEY_A)", self.context)
code_a = system_mapping.get("a")

await macro.run(self.handler)
self.assertListEqual(self.result, [(EV_KEY, code_a, 1)])
self.assertEqual(len(macro.child_macros), 0)

await macro.run(self.handler)
self.assertListEqual(
self.result,
[
(EV_KEY, code_a, 1),
(EV_KEY, code_a, 0),
],
)
self.assertEqual(len(macro.child_macros), 0)

async def test_0(self):
macro = parse("key(1)", self.context)
one_code = system_mapping.get("1")
Expand Down