boppreh / keyboard

Hook and simulate global keyboard events on Windows and Linux.
MIT License
3.8k stars 434 forks source link

Issue with Windows localisation (was : Hotkeys containing Windows key not working) #279

Open TPO-POMGOM opened 5 years ago

TPO-POMGOM commented 5 years ago

Hi, I am trying out keyboard and really enjoy this very well thought-out and documented package.

I do have an issue, though : hotkeys containing the Windows key (left, right or either) never fire.

Here is my test script :

import keyboard

keyboard.add_hotkey('windows+z', lambda: print('windows+z'))
keyboard.hook(print)
keyboard.wait()

Output generated by keyboard.hook() shows that keyboard does "see" Windows key events, but the hotkey does not fire:

. . .
KeyboardEvent(windows gauche down)
KeyboardEvent(z down)
KeyboardEvent(z up)
KeyboardEvent(windows gauche up)
. . .

I have tried killing all running AHK scripts, to avoid any interference, to no avail.

I am running keyboard 0.13.3 on Python 3.7.2 on a Windows 10 laptop.

Any ideas what might be wrong ?

P.S. I have tried pyWinhook + pyhk, which work fine, including for hotkeys containing the Windows key, but I would rather continue using keyboard, for all the other functionnality it provides.

glitchassassin commented 5 years ago

I've had trouble in the past assigning certain hotkeys with the Windows key. Probably a silly question, but were you trying the same hotkey with both libraries?

TPO-POMGOM commented 5 years ago

@glitchassassin : indeed I have been trying the same hotkeys with both libraries.

I have done some more testing and have found out that while the scan code produced when I press the left Windows key is 91, it does not appear in the combinations generated by parse_hotkey() :

print(keyboard.parse_hotkey('left windows+z'))
(((57435,), (17,)),)

No wonder the hotkey does not fire !

An easy workaround is therefore to declare the hotkey by explicitely specifying the scan code for the left Windows key : keyboard.add_hotkey((91, 'z'), lambda: print('windows+z')), which works like a charm ! By adding suppress=True I can even prevent Windows from seeing it's system-wide hotkeys (Win-A, Win-R, Win-F, etc.), which I did not succeed doing with pyWinhook + pyhk.

Still, I was wondering why keyboard only generated an exotic scan code of 57435 for the left Windows key, and not the usual scan code 91. I have searched quite a lot into init.py and _winkeyboard.py, done a bit of testing with internal functions and have finally found out that _winkeyboard.py's variable from_name contains a dict of all known keys with all scan codes and modifier combinations for each. By inspecting the contents of from_name, I then found out that it contains an entry for both 'left windows' and 'windows gauche' (I use a French version of Windows), and that the entry for 'left windows' misses scan code 91. This can easily be verified using keyboard's API:

keyboard.key_to_scan_codes('left windows')  ->  (57435,)
keyboard.key_to_scan_codes('windows gauche'))  ->  (91, 57435)

The solution is then to use the localised name to define the hotkey : keyboard.add_hotkey('windows gauche+z', lambda: print('windows+z')), which works perfectly! This also means that 'windows+z' cannot be used, since keyboard translates it into two hotkeys, 'left windows+z' and 'right windows+z', which are both broken.

Bottom line: it seems the code in _winkeyboard.py / _setup_name_tables(), which does the auto-discovery of all known keys and their names, is broken when running on the French version of Windows (and possibly other localised versions).

@boppreh: I am changing the title of the issue acordingly. Hope this helps.