2

I'm attempting to track the number of keypresses from the user, but I've come to realise that anything to do with Steam, whether it's the client itself or a game launched through it, it stops working. If using GetKeyState instead of GetAsyncKeyState, it can pick up the bits around the edge of the keyboard, such as ctrl, shift, tab, backspace, enter, but nothing else. I've also found the same behaviour on a small number of other programs, such as Task Manager and Discord.

I've tried with both ctypes and pywin32, using GetKeyState and GetAsyncKeyState.

Also after IInspectable suggested looking at low level keyboard hooks, I tried two different versions and found they suffered the exact same problem.

Here's some code to demonstrate the problem with all of the functions:

import ctypes
try:   
    import win32api
except ImportError:
    win32api = None

def get_key_press1(key):
    return win32api.GetAsyncKeyState(key)

def get_key_press2(key):
    return win32api.GetKeyState(key) < 0

def get_key_press3(key):
    return ctypes.windll.user32.GetKeyState(key) > 1

def get_key_press4(key):
    return ctypes.windll.user32.GetAsyncKeyState(key)

KEYS = {
    'BACK': 8,
    'TAB': 9,
    'CLEAR': 12,
    'RETURN': 13,
    'PAUSE': 19,
    'CAPSLOCK': 20,
    'ESC': 27,
    'SPACE': 32,
    'PGUP': 33,
    'PGDOWN': 34,
    'END': 35,
    'HOME': 36,
    'LEFT': 37,
    'UP': 38,
    'RIGHT': 39,
    'DOWN': 40,
    'INSERT': 45,
    'DELETE': 46,
    'LWIN': 91,
    'RWIN': 92,
    'MENU': 93,
    'NUM0': 96,
    'NUM1': 97,
    'NUM2': 98,
    'NUM3': 99,
    'NUM4': 100,
    'NUM5': 101,
    'NUM6': 102,
    'NUM7': 103,
    'NUM8': 104,
    'NUM9': 105,
    'MULTIPLY': 106,
    'ADD': 107,
    'SUBTRACT': 109,
    'DECIMAL': 110,
    'DIVIDE': 111,
    'F1': 112,
    'F2': 113,
    'F3': 114,
    'F4': 115,
    'F5': 116,
    'F6': 117,
    'F7': 118,
    'F8': 119,
    'F9': 120,
    'F10': 121,
    'F11': 122,
    'F12': 123,
    'F13': 124,
    'F14': 125,
    'F15': 126,
    'F16': 127,
    'F17': 128,
    'F18': 129,
    'F19': 130,
    'F20': 131,
    'F21': 132,
    'F22': 133,
    'F23': 134,
    'F24': 135,
    'NUMLOCK': 144,
    'SCROLLLOCK': 145,
    'LSHIFT': 160,
    'RSHIFT': 161,
    'LCTRL': 162,
    'RCTRL': 163,
    'LALT': 164,
    'RALT': 165,
    'COLON': 186,
    'EQUALS': 187,
    'COMMA': 188,
    'UNDERSCORE': 189,
    'PERIOD': 190,
    'FORWARDSLASH': 191,
    'AT': 192,
    'LBRACKET': 219,
    'BACKSLASH': 220,
    'RBRACKET': 221,
    'HASH': 222,
    'TILDE': 223
}
for c in list('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'):
    KEYS[c] = ord(c)


while True:
    all_pressed = []
    for k in KEYS:
        if win32api:
            if get_key_press1(KEYS[k]):
                all_pressed.append((k, 1)) 
            if get_key_press2(KEYS[k]):
                all_pressed.append((k, 2))
        if get_key_press3(KEYS[k]):
            all_pressed.append((k, 3)) 
        if get_key_press4(KEYS[k]):
            all_pressed.append((k, 4))
    if all_pressed:
        print all_pressed

From anywhere on the computer, if you were to press "c" for example, you would get [('C', 1), ('C', 2), ('C', 3), ('C', 4)], which shows all 4 functions are picking it up. Press that when focused in anything related to Steam and nothing shows. If however you were to hit one of the working buttons, you get [('LCTRL', 2), ('LCTRL', 3)], which means only the GetKeyState functions are picking it up.

I've tested the script on roughly 30 games over the past couple of months and without fail the Steam ones are the only ones that don't have working keyboard tracking. I even just downloaded the standalone version of Factorio to compare to the Steam version, and it worked flawlessly.

Is there something obvious I'm missing, or is there a better way to get key presses? It's a little annoying as it should be the most simple part of the script, yet it's the part which appears unfixable.

Just to clarify, I'd ideally like to stick with a function to return True/False depending on if the key is being pressed at the time.

Peter
  • 3,186
  • 3
  • 26
  • 59
  • If you wish to monitor keyboard input, do monitor keyboard input. The right tool for the job is a low-level keyboard hook. – IInspectable Jul 03 '17 at 16:33
  • Gave it a google, tried [this](https://stackoverflow.com/a/16430918/2403000) answer and [this](https://gist.github.com/ethanhs/80f0a7cc5c7881f5921f) example, but it has the same problem. I also just found Task Manager and Discord have the same issue though, so I'll update the question as it's not just related to Steam. – Peter Jul 03 '17 at 17:07
  • If a low-level keyboard hook cannot monitor input, then that is due to an application actively inhibiting it. The documentation explains, how to do that. At that point it's pretty much Game Over, from user-mode. – IInspectable Jul 03 '17 at 17:32

1 Answers1

1

After a whole 3 months and multiple attempts to find a fix, I've just found out the issue - it wasn't running as admin, and Steam was.

If anyone else stumbles across this, here is the code I've ended up with. You run elevate() at the start of the script, but make sure it's after freeze_support if you're using that.

pywin32:

def elevate(console=True):
    arg = 'forced_elevate'
    if sys.argv[-1] != arg and not win32com.shell.shell.IsUserAnAdmin():
        script = os.path.abspath(sys.argv[0])
        params = ' '.join([script] + sys.argv[1:] + [arg])
        try:
            win32com.shell.shell.ShellExecuteEx(lpVerb='runas', lpFile=sys.executable, lpParameters=params, nShow=5 if console else 0)
        except pywintypes.error:
            pass
        else:
            sys.exit(0)

ctypes:

def elevate(console=True):
    arg = 'forced_elevate'
    if sys.argv[-1] != arg and not ctypes.windll.shell32.IsUserAnAdmin():
        script = os.path.abspath(sys.argv[0])
        params = u' '.join([script] + sys.argv[1:] + [arg])
        ret = ctypes.windll.shell32.ShellExecuteW(None, u'runas', unicode(sys.executable), params, None, 5 if console else 0)
        if int(ret) > 32:
            sys.exit(0)
Peter
  • 3,186
  • 3
  • 26
  • 59