moses-palmer / pynput

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

Global Hotkeys Stuck #504

Closed fregapple closed 1 year ago

fregapple commented 1 year ago

HI!

I am trying to make a script that can load hotkeys from a json file. I have it working as expected, however, keys get stuck and produce weird behavior.

if JSON file were to be:

{
    "<cmd>+<alt>+h" : [
        "run",
        "readme.txt"
    ],
    "<cmd>+<alt>+n" : [
        "run",
        "eventvwr"
    ]
}

and Code to be:

import json, os
from pynput import keyboard
from functools import partial

def load_script():
    global hotkeys, hot_k_list, hot_k_action, new_hk
    with open('hotkeys.json') as f:
       hotkeys = json.load(f)
    hk = [ ]
    cmd = [ ] 
    arg = [ ] 

    for x, y in hotkeys.items():
        hk.append(x)
        cmd.append(y[0])
        arg.append(y[1])

    z = 0
    new_hkeys = { }

    for combination in hk:
        if cmd[z] == 'run':
            new_hkeys[combination] = partial(run, arg[z])
            z+=1
        elif cmd[z] == 'enter':
            new_hkeys[combination] = partial(enter, arg[z])
            z+=1

def run(args):
    try:
        os.startfile(args)
    except:
        print('Error')

def enter(args):
    try:
        keyboard.write(args)
    except:
        print('Error')

with keyboard.GlobalHotKeys(new_hkeys) as h:
    h.join()

So to be precise, If I press windows + alt + h, it will open the text file. However, what then seems to happen is that the 'h' is stuck pressed. This causes an issue as if I press windows + alt again, it will just open the text file.

THEN if I pressed windows + alt + n, it will open Event Viewer AND the text file. To which if I press Alt + Windows again, it will open them both!

The only way to stop this, is if I press h after I press the hotkey combination.

While typing this out, I have constructed a temporary fix:

def hot_key_splitter(key):
    key = key.split('+')
    return key[-1]

def load_script():
    ...
    hk = [ ]
    ....
    key_fix = [ ]
    .....
    for x, y in hot_keys.items():
        key_fix.append(hot_key_splitter(x))

    .....
    for comination in hk:
        .......
            nhk[combination] = partial(run, arg[z], key_fix[z])

def run(args, key)
    try:
        os.startfile(args)
        keyboard.Controller().press(key)
        keyboard.Controller().release(key)

    ....

It isn't an ideal solution, but any insight on why it is getting stuck in the first place would be great!!!

Thanks

fregapple commented 1 year ago

Also. Is there a way I can either

1) use keycodes for hotkeys. I have tried using:

hotkey = keyboard.HotKey(keyboard.HotKey.parse('<68>+<160>'), test)

This will parse properly, however hitting the hotkey will not do anything. If I change it to ..('d+'). Then the HotKey will run.

2) If I can not use keycodes like that. Is there a way I can convert the keycode back to char. EG <68> to 'd'.

moses-palmer commented 1 year ago

Thank you for your report.

I have tested your script, and I can reproduce your issue. If I remove the call to os.startfile, however, the script works as expected with regards to hot-keys. By adding logging to the pynput code I have verified that the release messages are never delivered.

As noted in the documentation, the listener callbacks should not perform any long running code, and process creation is such a case on Windows. The documentation should be clarified. The documentation linked contains a suggestion for how to work around this limitation.