⌨️

Revamped Keyboard Capture

 
So the code I was using was dearpygui’s built-in keyboard capture. It works for letters, but every other key does not work properly. Windows is [ , . is , ,etc. It’s a nightmare. Someone saved the keymap to this pastebin: https://pastebin.com/MraQxMeC but I’m not entirely sure what to do with it yet!
It seems like it’s for keypress handlers more than anything - I’m trying to get it to output all keypresses as numerical IDs, and those are wrong for some reason. Hm. Maybe it’s the arduino code, actually. Please hold. Nope, it’s the python code.
Ok, this is very odd. If the key = 190 (which is the “.>” key), doing print(key, chr(key)) results in this:
notion image
Like. what?
It’s ASCII. Of course. I need the windows key codes, not the ASCII codes.
import win32api key = 191 key_name = win32api.GetKeyName(key) print(key_name)
 
I might just use pywinhook for all keyboard input anyways, but this is a good test.
Ok, it looks like that doesn’t work anyways. Pywinhook it is, I guess! Hopefully it’ll output codes, not ascii characters.
Oh wait. Pygame outputs the ascii characters. OH SO THAT’S WHY THE GUY WAS REMAPPING EVERYTHING! They keys are all in decimal format??
pygame: E 101 | tab 179
dpg: E 69 | tab 9
So DPG is using the actual keyboard labels, which I need to translate to ascii. Who knew! That’s why the keyboard map was required I think - it’s because the modifier keys don’t have ascii equivalents. Dearpygui doesn’t denote between left and right control though, it seems :/
I’ve got it! Ok, so the arduino code relies on these mappings:
arduino mappings
//=============================== //=================================== // Keyboard #define KEY_LEFT_CTRL 0x80 #define KEY_LEFT_SHIFT 0x81 #define KEY_LEFT_ALT 0x82 #define KEY_LEFT_GUI 0x83 #define KEY_RIGHT_CTRL 0x84 #define KEY_RIGHT_SHIFT 0x85 #define KEY_RIGHT_ALT 0x86 #define KEY_RIGHT_GUI 0x87 #define KEY_UP_ARROW 0xDA #define KEY_DOWN_ARROW 0xD9 #define KEY_LEFT_ARROW 0xD8 #define KEY_RIGHT_ARROW 0xD7 #define KEY_BACKSPACE 0xB2 #define KEY_TAB 0xB3 #define KEY_RETURN 0xB0 #define KEY_ESC 0xB1 #define KEY_INSERT 0xD1 #define KEY_DELETE 0xD4 #define KEY_PAGE_UP 0xD3 #define KEY_PAGE_DOWN 0xD6 #define KEY_HOME 0xD2 #define KEY_END 0xD5 #define KEY_CAPS_LOCK 0xC1 #define KEY_F1 0xC2 #define KEY_F2 0xC3 #define KEY_F3 0xC4 #define KEY_F4 0xC5 #define KEY_F5 0xC6 #define KEY_F6 0xC7 #define KEY_F7 0xC8 #define KEY_F8 0xC9 #define KEY_F9 0xCA #define KEY_F10 0xCB #define KEY_F11 0xCC #define KEY_F12 0xCD
python mappings
orig_map = {"KEY_LEFT_CTRL":128, "KEY_LEFT_SHIFT":129, "KEY_LEFT_ALT":130, "KEY_LEFT_GUI":131, "KEY_RIGHT_CTRL":132, "KEY_RIGHT_SHIFT":133, "KEY_RIGHT_ALT":134, "KEY_RIGHT_GUI":135, "KEY_UP_ARROW":218, "KEY_DOWN_ARROW":217, "KEY_LEFT_ARROW":216, "KEY_RIGHT_ARROW":215, "KEY_BACKSPACE":178, "KEY_TAB":179, "KEY_RETURN":176, "KEY_ESC":177, "KEY_INSERT":209, "KEY_DELETE":212, "KEY_PAGE_UP":211, "KEY_PAGE_DOWN":214, "KEY_HOME":210, "KEY_END":213, "KEY_CAPS_LOCK":193, "KEY_F1":194, "KEY_F2":195, "KEY_F3":196, "KEY_F4":197, "KEY_F5":198, "KEY_F6":199, "KEY_F7":200, "KEY_F8":201, "KEY_F9":202, "KEY_F10":203, "KEY_F11":204, "KEY_F12":205}
So in key_map, the original author dude mapped those keys to integer numbers
hex 0x80 is just 128! and so on. This makes it play nice with the Arduino code. I have to make my own map tho because I’m not using pygame, and I don’t want to make a different callback for every key in DPG.
 
Using “e” key (python #101) as example
Python reads keyboard, converts 101 back to “e”, sends over serial. That’s why the bit shows the actual character being sent!
key pressed
pygame event.key value
pywinhook Ascii Char:
pywinhook keyID
DPG
e
101
101
69
69
a
97
97
65
65
h
104
104
72
72
l (L)
108
108
76
76
o
111
111
79
79
rshift
161
16
Lshift
160
16
Lcontrol
162
17 (fuck)
so:
def os_to_hid_key_id(key_ASCII_ID): if key_ASCII_ID == 0: #or: in os_to_hid_mapping: key_to_arduino = ASCII_to_hid_mapping[key_ASCII_ID] print({key_ASCII_ID}, "remapped to", {key_to_arduino}") else if ey_OS_ID != 0: key_to_arduino = key_ASCII_ID print({key_ASCII_ID}, "passed through") return key_to_arduino
Pywinhook only reports presses. Options:
…I think I need to completely eliminate the handler code and go with DPG, simply because of the press/release issue. Obv still need DPG to hook keys, but I need to do some tests to see if it’s possible to have PWH hook the key (and block it from the OS) but still have DPG read the press/release events. lordy! Update: pywinhook has up AND down hooks. We’re in business!
import pyWinhook import pythoncom def OnKeyboardEvent(event): print('Key:', event.Key) print('KeyID:', event.KeyID) print('MessageName:', event.MessageName) return True hm = pyWinhook.HookManager() # set the hook for key down hm.KeyDown = OnKeyboardEvent # set the hook for key up hm.KeyUp = OnKeyboardEvent hm.HookKeyboard() pythoncom.PumpMessages()
I looked up how to use it to handle key releases, which didn’t help at all. jeez this is frustrating, the wiki doesn’t even seem to mention KeyUp. Looks like there’s a helpful “wiki” here for the future:
http://www.cs.unc.edu/Research/assist/doc/pyhook/public/pyHook.HookManager-module.html
notion image
I missed this one somehow. it literally SAYS KeyDown. The state is reported by event.MessageName , which returns either “key up” or “key down”. I just have to convert those to true/false! yay!
Now the issue is getting those presses not to be repeated? I think? no I think that’s because they’re being held downnnnoh no. yeah. gotta fix that. something like what I did before with storing key_states maybe? store the last state in a dictionary, and if the new state matches the old state, don’t print a new line. I just need to re-add serial handling and make sure I can toggle the modifier keys from being blocked from the host OS - I removed the return True statement at the end of the OnKeyboardEvent and the code crashed. When I was testing in a smaller file, that code is what allowed keys to be passed through, but I don’t think that’ll be too hard to fix. Should be able to filter those in keyboard_handler.py. Also, maybe try and move the new stuff in there, otherwise rename the handler file to “key_remapper.” Calling it a night! good stuff :)
ooo fun idea: add text stats at top of screen next to checkbox (mouse position, list of keys+buttons held down, fps, uhhh and more!)
 
oh and for the key capture variable- try putting it in keyboard_handler.py, and using it in other files like keyboard_handler.key_capture_toggle
The fix:
 
keyboard_handler.py
import pyWinhook from keymap_v1 import * from debug_settings import * #danger! from dearpyGUI_serial_comms import * import globalVars key_states = {} # Initialize dictionary of keypress states outputKey = None def OnKeyboardEvent(event): key = event.KeyID state = 1 if 'down' in event.MessageName else 0 if key not in key_states or key_states[key] != state: #this means the key has changed state key_states[key] = state #sets the key state if event.KeyID in keymap_modifiers: #this means it's a modifier key outputKey = keymap_modifiers[event.KeyID] print(f"in modifier map ({event.KeyID}), remapped to {outputKey}") elif event.KeyID in keymap_others: outputKey = keymap_others[event.KeyID] print(f"in other map ({event.KeyID}), remapped to {outputKey}") elif event.KeyID in keymap_alphanumeric: outputKey = keymap_alphanumeric[event.KeyID] print(f"in other map ({event.KeyID}), remapped to {outputKey}") if windowFocused: print(state, outputKey, 100, 100, 0) serial_send(state, outputKey, 100, 100, 0) if event.Key and event.Key.lower() in ['lwin','Lcontrol', 'tab', 'lmenu'] and globalVars.key_capture == False: #can also use blocked_keys dictionary, both work but this might be more readable key_block = True # block these keys else: key_block = False return key_block #TRUE MAKES THE KEYPRESSES ACTUALLY WORK AND THE PROGRAM NOT CRASH
keymap_v1.py
keymap_modifiers = { 27:177, #Escape 112:194, #F1 113:195, #F2 114:196, #F3 115:197, #F4 116:198, #F5 117:199, #F6 118:200, #F7 119:201, #F8 120:202, #F9 121:203, #F10 122:204, #F11 123:205, #F12 36:210, #Home 35:213, #End 45:209, #Insert 46:212, #Delete 8:178, #Back (backspace) 9:179, #Tab 20:193, #Capital (caps lock) 13:176, #Return 160:129, #Lshift 161:133, #Rshift 162:128, #Lcontrol 163:132, #Rcontrol 91:131, #Lwin 164:130, #Lmenu 165:134, #Rmenu 32:32, #Space (ASCII value) 44:206, #Snapshot (printscrn) 33:211, #Prior (pdgup) 34:214, #Next (pdgwn) 38:218, #Up 37:216, #Left 40:217, #Down 39:215, #Right 189: 222, #Oem_Minus (-) } keymap_alphanumeric = { 49:49, #1 50:50, #2 51:51, #3 52:52, #4 53:53, #5 54:54, #6 55:55, #7 56:56, #8 57:57, #9 48:48, #0 81:113, #Q 87:119, #W 69:101, #E 82:114, #R 84:116, #T 89:121, #Y 85:117, #U 73:105, #I 79:111, #O 80:112, #P 65:97, #A 83:115, #S 68:100, #D 70:102, #F 71:103, #G 72:104, #H 74:106, #J 75:107, #K 76:108, #L 90:122, #Z 88:120, #X 67:99, #C 86:118, #V 66:98, #B 78:110, #N 77:109, #M } keymap_others = { 192:96, #Oem_3 ` 187:61, #Oem_Plus = 219:91, #Oem_4 [ 221:93, #Oem_6 ] 220:92, #Oem_5 \ 186:59, #Oem_1 ; 222:39, #Oem_7 ' 188:44, #Oem_Comma , 190:46, #Oem_Period . 191:47, #Oem_2 / }
I basically needed to make sure every keyboard key was mapped. did with a combo of chatgpt and spreadsheets. Keyboard Modifiers and Special Keys - Arduino Reference. Future plans include making a “remapper” script that allows the user to just type keys one-by-one to get a keymap going, if the default isn’t working so well. But thankfully I have a list of all of the (most of the) keys people would ever want to use, so I think we’re golden!
Caps lock indicator? not sure how we’d get the state…
“release” all modifier keys on startup? would that disable them? I have no idea
key press label for fun information - the one that I worked for ages on lol
This was helpful, btw: Key Code Table (foreui.com)
 
Alright. If you do k_down = dpg.add_key_down_handler() that handles ALL keypresses, AND there’s the same for a key release handler with k_release = dpg.add_key_release_handler() oh wait. I need keyboard HOOKING. Ok cool, maybe I can use the built-in for everything, then use pywinhook for keyboard hooking on windows? That should allow for cross platform support without a nasty keymap, and a nice-to-have on windows that I can implement on linux later.