keyboard icon indicating copy to clipboard operation
keyboard copied to clipboard

Arrow keys trigger with number keys

Open immiProgrammer opened this issue 1 year ago • 2 comments

when add callback for 2, 4, 6, 8 keys, the callback also trigger with arrow keys how to fix it

immiProgrammer avatar Dec 08 '24 08:12 immiProgrammer

I have the same issue! Any ideas, anyone?

Kunstbanause avatar Jul 25 '25 19:07 Kunstbanause

I have the same issue! Any ideas, anyone?

It's been some time I came across this so take this with a grain of salt; But i used on_key_down / up events only and implemented my own hotkey manager checking for the names of the arrow keys (I think). Im not sure if i still have access to the code.

Excerpt:

class Keys(IntEnum):
    ARROW_DOWN = 80
    NUMPAD_2 = 80

def keys_to_hotkey_string(keys: tuple | list | set) -> str:
    """
     python sort is stable so we can safely use it as hotkey unique identifier.
     We sort it to make it more resilient to developer input
    """
    return "+".join(sorted(keys))

class Hotkey:
    def __init__(self, *keys: str | Keys, display_text: str | None = None, is_numpad: bool = False):
        """
        Hotkeys are firstly converted in a "key:is_numpad" format. 
        "key" is either the Key-Enum value which is the actual keycode.
        If anything but a Key instance is passed this code tries to convert it to a keycode using 
        the keyboard lib.
        """
        self._hotkey: str = keys_to_hotkey_string([
            f"{key}:{1 if is_numpad else 0}" if isinstance(key, Keys)
            else f"{keyboard.key_to_scan_codes(key.lower())[0]}:{1 if is_numpad else 0}"
            for key in keys
        ])


def _event_to_unique(event: KeyboardEvent) -> str:
    return f"{event.scan_code}:{int(event.is_keypad)}"


class KeyInput:
    @classmethod
    def _on_keyboard_event(cls, event: KeyboardEvent) -> None:
        event_as_unique: str = cls._event_to_unique(event)

        logger.debug(f"KeyEvent: {event_as_unique}:{event}")

        match event.event_type:
            case keyboard.KEY_DOWN:
                cls._on_key_down(event, event_as_unique)

            case keyboard.KEY_UP:
                cls._on_key_up(event, event_as_unique)

    @classmethod
    def _on_key_down(cls, event: KeyboardEvent, unique: str) -> None:
        cls._key_state.add(unique)
        if (hotkey := cls._hotkeys.get(cls.keys_to_hotkey_string(cls._key_state))) is not None:
            if not hotkey.is_disabled():
                logger.debug(f"Is hotkey: {hotkey} <{cls._key_state}>")
                hotkey.invoke()
                cls._key_state.clear()
                return

        cls._key_down_wrapper(event)

    @classmethod
    def _on_key_up(cls, event: KeyboardEvent, unique: str) -> None:
        cls._key_state.discard(unique)
        cls.on_keyboard_key_up.emit(text=event.name, keycode=event.scan_code)



if __name__ == "__main__":
    arrow_down_hotkey: Hotkey = Hotkey(Keys.ARROW_DOWN)
    arrow_down_hotkey.link(None, lambda: print("ARROW INVOKED"))
    KeyInput.register_hotkey(arrow_down_hotkey)

    numpad_2: Hotkey = Hotkey(Keys.NUMPAD_2, is_numpad=True)
    numpad_2.link(None, lambda: print("NUMPAD_2 INVOKED"))
    KeyInput.register_hotkey(numpad_2)
    KeyInput.hook()

    keyboard.hook(KeyInput._on_keyboard_event)

I have left out some methods as they are not really essential to this solution. Essentially just storing a callable in a list/dict and invoke it once the hotkey is detected in _on_key_down

CoreTaxxe avatar Jul 26 '25 10:07 CoreTaxxe