import win32api import win32con import win32ui MAPVK_VK_TO_CHAR = 2 key_name_to_vk = {} key_code_to_name = {} _better_names = { "escape": "esc", "return": "enter", "back": "pgup", "next": "pgdn", } def _fillvkmap(): # Pull the VK_names from win32con names = [entry for entry in win32con.__dict__ if entry.startswith("VK_")] for name in names: code = getattr(win32con, name) n = name[3:].lower() key_name_to_vk[n] = code if n in _better_names: n = _better_names[n] key_name_to_vk[n] = code key_code_to_name[code] = n _fillvkmap() def get_vk(chardesc): if len(chardesc) == 1: # it is a character. info = win32api.VkKeyScan(chardesc) if info == -1: # Note: returning None, None causes an error when keyboard layout is non-English, see the report below # https://stackoverflow.com/questions/45138084/pythonwin-occasionally-gives-an-error-on-opening return 0, 0 vk = win32api.LOBYTE(info) state = win32api.HIBYTE(info) modifiers = 0 if state & 0x1: modifiers |= win32con.SHIFT_PRESSED if state & 0x2: modifiers |= win32con.LEFT_CTRL_PRESSED | win32con.RIGHT_CTRL_PRESSED if state & 0x4: modifiers |= win32con.LEFT_ALT_PRESSED | win32con.RIGHT_ALT_PRESSED return vk, modifiers # must be a 'key name' return key_name_to_vk.get(chardesc.lower()), 0 modifiers = { "alt": win32con.LEFT_ALT_PRESSED | win32con.RIGHT_ALT_PRESSED, "lalt": win32con.LEFT_ALT_PRESSED, "ralt": win32con.RIGHT_ALT_PRESSED, "ctrl": win32con.LEFT_CTRL_PRESSED | win32con.RIGHT_CTRL_PRESSED, "ctl": win32con.LEFT_CTRL_PRESSED | win32con.RIGHT_CTRL_PRESSED, "control": win32con.LEFT_CTRL_PRESSED | win32con.RIGHT_CTRL_PRESSED, "lctrl": win32con.LEFT_CTRL_PRESSED, "lctl": win32con.LEFT_CTRL_PRESSED, "rctrl": win32con.RIGHT_CTRL_PRESSED, "rctl": win32con.RIGHT_CTRL_PRESSED, "shift": win32con.SHIFT_PRESSED, "key": 0, # ignore key tag. } def parse_key_name(name): name = name + "-" # Add a sentinal start = pos = 0 max = len(name) toks = [] while pos < max: if name[pos] in "+-": tok = name[start:pos] # use the ascii lower() version of tok, so ascii chars require # an explicit shift modifier - ie 'Ctrl+G' should be treated as # 'ctrl+g' - 'ctrl+shift+g' would be needed if desired. # This is mainly to avoid changing all the old keystroke defs toks.append(tok.lower()) pos += 1 # skip the sep start = pos pos += 1 flags = 0 # do the modifiers for tok in toks[:-1]: mod = modifiers.get(tok.lower()) if mod is not None: flags |= mod # the key name vk, this_flags = get_vk(toks[-1]) return vk, flags | this_flags _checks = [ [ # Shift ("Shift", win32con.SHIFT_PRESSED), ], [ # Ctrl key ("Ctrl", win32con.LEFT_CTRL_PRESSED | win32con.RIGHT_CTRL_PRESSED), ("LCtrl", win32con.LEFT_CTRL_PRESSED), ("RCtrl", win32con.RIGHT_CTRL_PRESSED), ], [ # Alt key ("Alt", win32con.LEFT_ALT_PRESSED | win32con.RIGHT_ALT_PRESSED), ("LAlt", win32con.LEFT_ALT_PRESSED), ("RAlt", win32con.RIGHT_ALT_PRESSED), ], ] def make_key_name(vk, flags): # Check alt keys. flags_done = 0 parts = [] for moddata in _checks: for name, checkflag in moddata: if flags & checkflag: parts.append(name) flags_done = flags_done & checkflag break if flags_done & flags: parts.append(hex(flags & ~flags_done)) # Now the key name. if vk is None: parts.append("") else: try: parts.append(key_code_to_name[vk]) except KeyError: # Not in our virtual key map - ask Windows what character this # key corresponds to. scancode = win32api.MapVirtualKey(vk, MAPVK_VK_TO_CHAR) parts.append(chr(scancode)) sep = "+" if sep in parts: sep = "-" return sep.join([p.capitalize() for p in parts]) def _psc(char): sc, mods = get_vk(char) print("Char %s -> %d -> %s" % (repr(char), sc, key_code_to_name.get(sc))) def test1(): for ch in """aA0/?[{}];:'"`~_-+=\\|,<.>/?""": _psc(ch) for code in ["Home", "End", "Left", "Right", "Up", "Down", "Menu", "Next"]: _psc(code) def _pkn(n): vk, flags = parse_key_name(n) print("%s -> %s,%s -> %s" % (n, vk, flags, make_key_name(vk, flags))) def test2(): _pkn("ctrl+alt-shift+x") _pkn("ctrl-home") _pkn("Shift-+") _pkn("Shift--") _pkn("Shift+-") _pkn("Shift++") _pkn("LShift-+") _pkn("ctl+home") _pkn("ctl+enter") _pkn("alt+return") _pkn("Alt+/") _pkn("Alt+BadKeyName") _pkn("A") # an ascii char - should be seen as 'a' _pkn("a") _pkn("Shift-A") _pkn("Shift-a") _pkn("a") _pkn("(") _pkn("Ctrl+(") _pkn("Ctrl+Shift-8") _pkn("Ctrl+*") _pkn("{") _pkn("!") _pkn(".") if __name__ == "__main__": test2()