GlobalHotKeys pynput not working not reacting to function keys - python

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')

Related

Keyboard macro press X each few secons while i press other keys

I'm playing a game that i need to press X each 1 sec, while i press other keyboard keys, but my hands are responding to pain
first i tried to do it on javascript:
const robot = require("robotjs");
function Sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function Main() {
console.log("running...");
await Sleep(2500);
PressTogether();
await Main();
}
function PressTogether() {
robot.keyTap("x");
}
Main();
also on python
import pyautogui
import time
print("hey")
while True:
time.sleep(2.5)
pyautogui.press("x")
time.sleep(2.5)
pyautogui.press("x")
print("bye")
Both are pressing the X each 2.5 seconds, but both got the same problem
it freezes my keyboard, i cant press any other key
why this is happen? how to fix?
Your keyboard doesn't react since by using sleep, you suspend the whole thread. It then is not able to do anything except blocking execution.
After sleeping for 2.5 seconds you just do sending out a keyboard action (pressing x key).
Then you are suspending your thread again.
Doing so, your process feels like being frozen because you effectively block your only one main thread most of the time.
You can overcome to that behavior by introducing another thread, which is responsible for your keyboard inputs taking them from stdin.
Here is one approach for python
import pyautogui
from time import sleep
from threading import Thread
from pynput.keyboard import Key, Listener
# a custom function that blocks for a moment and simulates key press
def akp_task():
print("hey")
while True:
time.sleep(2.5)
pyautogui.press("x")
time.sleep(2.5) #this could be omitted
pyautogui.press("x") #this could be omitted
# another function that handles manual key press inputs
def mkpi_task(key):
print('\nmanual pressed: {0}'.format(key))
if key == Key.delete:
# Stop listener
return False
# create a thread for auto key press
akp_thread = Thread(target=akp_task)
# run the thread
akp_thread.start()
# create another thread for your manual key press inputs
with Listener(on_press = mkpi_task) as listener:
listener.join()
References:
threading
manual key press handling

The controller.type method does not work correctly inside the onpress event - Pynput

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.

detect key press in python, where each iteration can take more than a couple of seconds?

Edit: The below answer to use keyboard.on_press(callback, suppress=False) works fine in ubuntu without any issues.
But in Redhat/Amazon linux, it fails to work.
I have used the code snippet from this thread
import keyboard # using module keyboard
while True: # making a loop
try: # used try so that if user pressed other than the given key error will not be shown
if keyboard.is_pressed('q'): # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
except:
break # if user pressed a key other than the given key the loop will break
But the above code requires the each iteration to be executed in nano-seconds. It fails in the below case:
import keyboard # using module keyboard
import time
while True: # making a loop
try: # used try so that if user pressed other than the given key error will not be shown
print("sleeping")
time.sleep(5)
print("slept")
if keyboard.is_pressed('q'): # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
except:
print("#######")
break # if user pressed a key other than the given key the loop will break
You can make use of event handlers in keyboard module to achieve the desired result.
One such handler is keyboard.on_press(callback, suppress=False):
Which invokes a callback for every key_down event.
You can refer more at keyboard docs
Here is the code you can try:
import keyboard # using module keyboard
import time
stop = False
def onkeypress(event):
global stop
if event.name == 'q':
stop = True
# ---------> hook event handler
keyboard.on_press(onkeypress)
# --------->
while True: # making a loop
try: # used try so that if user pressed other than the given key error will not be shown
print("sleeping")
time.sleep(5)
print("slept")
if stop: # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
except:
print("#######")
break # if user pressed a key other than the given key the loop will break
for people that might need this in the future, you can use keyboard.wait() which will basically wait untill the key gets pressed
keyboard.wait("o")
print("you pressed the letter o")
Do keep in mind that it blocks code execution after it. if you want to run code if the key is not being pressed i'd suggest doing
if keyboard.is_pressed("0"):
#do stuff
else:
#do other stuff
Edit: never mind, the other answer uses pretty much the same approach
This is what i could come up with, using the same "keyboard" module, see in-code comments below
import keyboard, time
from queue import Queue
# keyboard keypress callback
def on_keypress(e):
keys_queue.put(e.name)
# tell keyboard module to tell us about keypresses via callback
# this callback happens on a separate thread
keys_queue = Queue()
keyboard.on_press(on_keypress)
try:
# run the main loop until some key is in the queue
while keys_queue.empty():
print("sleeping")
time.sleep(5)
print("slept")
# check if its the right key
if keys_queue.get()!='q':
raise Exception("terminated by bad key")
# still here? good, this means we've been stoped by the right key
print("terminated by correct key")
except:
# well, you can
print("#######")
finally:
# stop receiving the callback at this point
keyboard.unhook_all()
You could use a thread
import threading
class KeyboardEvent(threading.Thread):
def run(self):
if keyboard.is_pressed('q'): # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
keyread = KeyboardEvent()
keyread.start()
This would run in parallel to anything in the main thread and be dedicated to listening for that key press essentially.

Detecting a keypress in python while in the background

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()

Peeking a GTK event; gtk.gdk.event_peek always returns None

I need to check whether the Escape key has been pressed during execution of some non-GUI code. (The code is in Python, but can easily call into C if necessary.) The code received a function from the GUI that it occasionally calls to check whether it has been interrupted. The question is how to implement this check.
By looking at the documentation, gdk_event_peek seems like an excellent choice for this:
def _check_esc(self):
event = gtk.gdk.event_peek()
if event is None or event.type not in (gtk.gdk.KEY_PRESS, gtk.gdk.KEY_RELEASE):
return False
return gtk.gdk.keyval_name(event.keyval) == 'Escape'
This doesn't work, however: the event returned from gtk.gdk.event_peek() is always None when the main loop is not running. Changing it to gtk.gdk.display_get_default().peek_event() doesn't help either. I assume the events are in the X event queue and are not yet moved to the GDK event queue. The documentation says:
Note that this function will not get more events from the windowing
system. It only checks the events that have already been moved to the
GDK event queue.
So, how does one transfer the event to the GDK event queue or? In other words, when does gtk.gdk.peek_event() ever return an event? Calling gtk.events_pending() doesn't have any effect.
Here is a minimal program to test it:
import gtk, gobject
import time
def code(check):
while 1:
time.sleep(.1)
if check():
print 'interrupted'
return
def _check_esc():
event = gtk.gdk.event_peek()
print 'event:', event
if event is None or event.type not in (gtk.gdk.KEY_PRESS, gtk.gdk.KEY_RELEASE):
return False
return gtk.gdk.keyval_name(event.keyval) == 'Escape'
def runner():
code(_check_esc)
gtk.main_quit()
w = gtk.Window()
w.show()
gobject.idle_add(runner)
gtk.main()
When running the code, the event printed is always None, even if you press Escape or move the mouse.
I also considered installing a handler for Escape and having the checker process events with the while gtk.events_pending(): gtk.main_iteration() idiom. This results in unqueuing and dispatch of all pending events, including keyboard and mouse events. The effect is that the GUI is responsive enabled while the code runs, which doesn't look well and can severely interfere with the execution of the code. The only event processed during execution should be the escape key to interrupt it.
I came up with a runner implementation that satisfies the criteria put forward in the question:
def runner():
# _check_esc searches for Escape in our queue
def _check_esc():
oldpos = len(queue)
while gtk.events_pending():
gtk.main_iteration()
new = itertools.islice(queue, oldpos, None)
return any(event.type == gtk.gdk.KEY_PRESS \
and gtk.gdk.keyval_name(event.keyval) == 'Escape'
for event in new)
queue = []
# temporarily set the global event handler to queue
# the events
gtk.gdk.event_handler_set(queue.append)
try:
code(_check_esc)
finally:
# restore the handler and replay the events
handler = gtk.main_do_event
gtk.gdk.event_handler_set(gtk.main_do_event)
for event in queue:
handler(event)
gtk.main_quit()
Compared to a peek-based solution, its advantage is that it handles the case when another event arrives after the keypress. The disadvantage is that it requires fiddling with the global event handler.

Categories