moses-palmer / pynput

Sends virtual input commands
GNU Lesser General Public License v3.0
1.78k stars 247 forks source link

Press cmd_r and release with cmd pressed will produce two presses instead of press + release #589

Open tisonkun opened 7 months ago

tisonkun commented 7 months ago

Description

from dotenv import load_dotenv
from pynput import keyboard
from pynput.keyboard import Key

import logging
import os
import sys

MODIFIERS = {
    Key.shift, Key.shift_l, Key.shift_r,
    Key.alt, Key.alt_l, Key.alt_r, Key.alt_gr,
    Key.ctrl, Key.ctrl_l, Key.ctrl_r,
    Key.cmd, Key.cmd_l, Key.cmd_r,
}

TABLE = sqlalchemy.Table(
    'keyboard_monitor',
    sqlalchemy.MetaData(),
    sqlalchemy.Column('hits', sqlalchemy.String),
    sqlalchemy.Column('ts', sqlalchemy.DateTime),
)

if __name__ == '__main__':
    load_dotenv()
    logging.basicConfig(filename='agent.log', encoding='utf-8', level=logging.DEBUG)
    logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))

    current_modifiers = set()

    def record_combos(keys):
        hits = '+'.join(keys)
        logging.info(f'recoding: {hits}')

    def on_press(key):
        if key in MODIFIERS:
            current_modifiers.add(key)
        else:
            record_combos(sorted([ str(key) for key in current_modifiers ]) + [ str(key) ])
        logging.debug(f'{key} pressed, current_modifiers: {current_modifiers}')

    def on_release(key):
        if key in MODIFIERS:
            try:
                current_modifiers.remove(key)
            except KeyError:
                logging.warn(f'Key {key} not in current_modifiers {current_modifiers}')
        logging.debug(f'{key} released, current_modifiers: {current_modifiers}')

    with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
        try:
            listener.join()
        except KeyboardInterrupt:
            logging.info("Exiting...")

Platform and pynput version

pynput: 1.7.6
os: Darwin tisondeMacBook-Pro.local 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:53:19 PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6020 arm64
MacOS Ventura 13.4

To Reproduce

Keep the left command pressed, and press the right command and release it, then release the left command:

Key.cmd pressed, current_modifiers: {<Key.cmd: <55>>}
Key.cmd_r pressed, current_modifiers: {<Key.cmd_r: <54>>, <Key.cmd: <55>>}
Key.cmd_r pressed, current_modifiers: {<Key.cmd_r: <54>>, <Key.cmd: <55>>}
Key.cmd released, current_modifiers: {<Key.cmd_r: <54>>}

As you can see, two "Key.cmd_r pressed" events were emitted instead of a press and a release.

tisonkun commented 7 months ago

For my use case, I may want only an API to get the current pressed key ..

tisonkun commented 7 months ago

This bug can be reproduced if I switch the command order, that is:

Key.cmd_r pressed, current_modifiers: {<Key.cmd_r: <54>>}
Key.cmd pressed, current_modifiers: {<Key.cmd: <55>>, <Key.cmd_r: <54>>}
Key.cmd pressed, current_modifiers: {<Key.cmd: <55>>, <Key.cmd_r: <54>>}
moses-palmer commented 5 months ago

Thank you for your report.

Unfortunately I no longer have access to a macOS system, so I cannot test this, but reading through the code here reveals a possible cause; the modifier keys do not emit proper keyboard events, but only flag changed events, and this library uses heuristics to determine whether a key has been pressed or released. shift and ctrl should exhibit the same behaviour.

I am uncertain about how to proceed, as I have trouble finding any reasonable way to determine the pressed state.