I am trying to find a way to detect a keypress and then run a method depending on what key it is.
I can already do this with Tkinter. But what I can't do is detect the keypress while the window is in the background. I will be running this program in the background while I play a game. I need it to be able to detect inputs while I'm in the game.
Is there any way I can do this with Tkinter or something else? Preferably I would like to not have to download anything external as I would like to distribute this to some other people.
pyHook seems like it would work well for this (mentioned by furas)
from pyHook import HookManager
from win32gui import PumpMessages, PostQuitMessage
class Keystroke_Watcher(object):
def __init__(self):
self.hm = HookManager()
self.hm.KeyDown = self.on_keyboard_event
self.hm.HookKeyboard()
def on_keyboard_event(self, event):
try:
if event.KeyID == keycode_youre_looking_for:
self.your_method()
finally:
return True
def your_method(self):
pass
def shutdown(self):
PostQuitMessage(0)
self.hm.UnhookKeyboard()
watcher = Keystroke_Watcher()
PumpMessages()
I suggest not to use pyHook anymore, because it's an old, not maintained library (last updated in 2008)
Alternatively there are many other libraries, which are actively maintained for example pynput:
from pynput.keyboard import Key, Listener
def on_press(key):
print('{0} pressed'.format(key))
def on_release(key):
print('{0} release'.format(key))
if key == Key.esc:
return False
with Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
keyboard can be an another alternative which may be worth considering.
Too get all the properties from your key press event. you can do the following
import pythoncom, pyHook
def OnKeyboardEvent(event):
print('MessageName:',event.MessageName)
print('Message:',event.Message)
print('Time:',event.Time)
print('Window:',event.Window)
print('WindowName:',event.WindowName)
print('Ascii:', event.Ascii, chr(event.Ascii))
print('Key:', event.Key)
print('KeyID:', event.KeyID)
print('ScanCode:', event.ScanCode)
print('Extended:', event.Extended)
print('Injected:', event.Injected)
print('Alt', event.Alt)
print('Transition', event.Transition)
print('---')
# return True to pass the event to other handlers
return True
# create a hook manager
hm = pyHook.HookManager()
# watch for all mouse events
hm.KeyDown = OnKeyboardEvent
# set the hook
hm.HookKeyboard()
# wait forever
pythoncom.PumpMessages()
Now know all the details of the key press and do operation on top of this.
pressing 's' would look like this
MessageName: key down
Message: 256
Time: 449145375
Window: 2558060
WindowName: "file name"
Ascii: 115 s
Key: S
KeyID: 83
ScanCode: 31
Extended: 0
Injected: 0
Alt 0
Transition 0
See pynput.
def on_release(key):
global running
if key == keyboard.Key.esc:
running = False;
running = True
listener = keyboard.Listener(on_release=on_release)
# run listener in background so that the while loop gets executed
listener.start()
while running
print("running some code")
listener.stop()
Related
I know how to get key input directly with the keyboard module but it needs a while loop around it specifically. if I use this in my code obviously it stops it in its tracks!
while True:
event = keyboard.read_event()
if event.event_type == keyboard.KEY_DOWN:
print(event.name)
Use a keyboard.listener in a non-blocking fashion (not in a with statement), as per the documentation:
from pynput import keyboard
def on_press(key):
try:
print('alphanumeric key {0} pressed'.format(
key.char))
except AttributeError:
print('special key {0} pressed'.format(
key))
def on_release(key):
print('{0} released'.format(
key))
if key == keyboard.Key.esc:
# Stop listener
return False
# ...or, in a non-blocking fashion:
listener = keyboard.Listener(
on_press=on_press,
on_release=on_release)
listener.start()
# execution immediately continues past listener.start()
When using the non-blocking version above, the current thread will continue executing. This might be necessary when integrating with other GUI frameworks that incorporate a main-loop, but when run from a script, this will cause the program to terminate immediately.
community. I'm trying to put together a quick hotkey script in python here.
For some reason it doesn't react to function keys, meaning the expression '<ctrl>+<F2>': function_1 doesn't work.
I was not able to find any clues in the official documentation or other examples online. Any thoughts?
Here is the script for testing.
from pynput import keyboard
def function_1():
print('Function 1 activated')
def function_2():
print('Function 2 activated')
with keyboard.GlobalHotKeys({
'<ctrl>+<F2>': function_1,
'<ctrl>+t': function_2}) as h:
h.join()
You could try setting up a generic handler to see what events are generated:
from pynput import keyboard
from pynput.keyboard import Controller
keyboard_controller = Controller()
# The event listener will be running in this block
with keyboard.Events() as events:
for event in events:
if event.key == keyboard.Key.esc:
break
else:
print(f"Received event {event}")
Running this script will then print the key press events, pressing Esc will exit the script:
Received event Press(key='a')
Received event Release(key='a')
Received event Press(key=Key.media_volume_up)
Received event Release(key=Key.media_volume_up)
Received event Press(key=Key.f3)
Received event Release(key=Key.f3)
Received event Press(key=Key.ctrl)
Received event Press(key=Key.media_volume_up)
Received event Release(key=Key.media_volume_up)
Received event Release(key=Key.ctrl)
Note that this won't handle hot keys and I don't see some key presses, e.g. to get Key.media_volume_up events I press Fn + F3.
You could potentially use keyboard.Events() handling to trigger your own hotkey equivalent.
I solved this issue by moving away from pynput Global Hotkeys and using just keyboard instead. I still don't understand the nature of this issue with global hotkeys not recognizing F1 key..
Here is the solution I used. I also added the passthrough of values with lambda function for each hotkey.
import keyboard
def func1(key):
print("F1 is pressed with value: ", key)
def func2(key):
print("F2 is pressed with value: ", key)
# define hotkeys
keyboard.add_hotkey('F1', lambda: func1("value1"))
keyboard.add_hotkey('F2', lambda: func2("value2"))
# run the program until 'F12' is pressed
keyboard.wait('F12')
I'm building a rudimentary autocomplete for my work, and it was doing fine, until I ran into this problem.
See example
import threading
from pynput import keyboard
from pynput.keyboard import Controller
controll = Controller()
def on_press(key):
if(key == keyboard.Key.alt_l):
controll.type("RIGHT !!!")
if(key == keyboard.Key.delete or key == keyboard.Key.enter):
return False
def handleKey():
x = input('INPUT> ')
print(x)
handle = threading.Thread(target=handleKey)
handle.start()
controll.type("RIGHT !!!")
with keyboard.Listener(
on_press=on_press) as listener:
listener.join()
When you press alt, you will see the output with the wrong string;
It must be [2] RIGHT !, but it was pressed [2]ight
But note that this only happens with the method call inside the event.
Is this a known issue? Am I using it wrong? I'm really confused
I believe that only with python3 and pip3 install pynput, this will make the example work
Tested with Windows 10
I've had a similar issue when trying to press a key while listening to a key. By my experience it is next to impossible. I have even tried to use other libraries' press functions such as pyautogui and keyboard inside of the pynput on_press function.
def on_press(key):
if key = keyboard.Key.esc:
return False
else:
if len(words) > i:
# p = keyboard.KeyCode.from_char(words[i])
pyautogui.press(word[i]) // also used keyboard.press(word[i])
i += 1
else:
keyboard.Controller.press(keyboard.Key.space)
with keyboard.Listener(on_press=on_press, suppress=True) as listener:
listener.join()
I feel like since the press action and the listen action are in different threads the program has to finish one before the other. At least that is what I gathered as I also have not found a solution to this. I also tried putting the press line after the listener.join() like this:
def on_press(key):
if key != keyboard.Key.esc:
return True
with keyboard.Listener(on_press=on_press, suppress=True) as listener:
listener.join()
if len(words) > i:
# p = keyboard.KeyCode.from_char(words[i])
pyautogui.press(words[i])
i += 1
else:
keyboard.Controller.press(keyboard.Key.space)
However, this did not solve the issue either because the program finished after listening to one input and then pressing words[i] once.
Please let me know if you find something. However I may not be able to comment bc of low reputation.
I had a similar issue as well, and something like this worked for me (on Ubuntu 20.04):
from pynput import keyboard
import threading
from time import sleep
event_queue = []
event_to_callback = {
# put your own key-to-function mapping here
# keyboard.KeyCode.from_char('c') : deal_with_c
}
def process_events():
while True:
for c in event_queue:
event_to_callback[c]()
event_queue.clear()
sleep(0.2)
keyboard.Listener(on_press=lambda k: event_queue.append(k)).start()
threading.Thread(target=process_events, args=()).start()
This decouples the listener's thread from the controller's thread; any callbacks that invoke pynput.keyboard.Controller methods will now be executed on a separate thread, different from the one of the listener.
This has been keeping me up quite a bit. When using the Python pynput module with keyboard.Listener, whatever is typed whilst the keyboard is listening is printed out in Terminal after the keyboard Listener has stopped.
I've tried termios.tcflush(sys.stdout, termios.TCIOFLUSH) but it doesn't seem to clear the buffer and stop characters being printed into the Terminal window.
Any help massively appreciated! Script below -
import sys
from pynput import keyboard
import termios
global go_to_selection
def on_press(key):
global go_to_selection
if key != keyboard.Key.tab and key != keyboard.Key.shift and key != keyboard.Key.enter:
termios.tcflush(sys.stdout, termios.TCIOFLUSH)
go_to_selection = True
return False
with keyboard.Listener(on_press=on_press) as listener:
listener.join()
if go_to_selection == True:
_user_choice = input('\r\nSelection: ')
print(' Chosen: '+ str(_user_choice))
Edit:
with keyboard.Listener(on_press=on_press, suppress=True ) as listener:
listener.join()
fixes this actually, but appears to cause a 1-2 second delay before the keyboard becomes responsive again. Is this expected?
Thanks!
How can I capture a key press (key logging) in Linux?
For Windows exist pyHook library but I dont know how to do it in Linux.
You can use pyxhook:
#!/usr/bin/env python
import pyxhook
def OnKeyPress(event):
print (event.Key)
if event.Ascii == 32:
exit(0)
hm = pyxhook.HookManager()
hm.KeyDown = OnKeyPress
hm.HookKeyboard()
hm.start()
sudo apt-get install python-xlib
https://github.com/JeffHoogland/pyxhook
#!/usr/bin/env python
import pyxhook
import time
#This function is called every time a key is presssed
def kbevent( event ):
#print key info
print event
#If the ascii value matches spacebar, terminate the while loop
if event.Ascii == 32:
global running
running = False
#Create hookmanager
hookman = pyxhook.HookManager()
#Define our callback to fire when a key is pressed down
hookman.KeyDown = kbevent
#Hook the keyboard
hookman.HookKeyboard()
#Start our listener
hookman.start()
#Create a loop to keep the application running
running = True
while running:
time.sleep(0.1)
#Close the listener when we are done
hookman.cancel()