My use case is, I'm using pynput/Python to make a reaction time registration application. The essence is that someone will press a control key to mark the first time epoch, and then the reactor hits any other key, and the time difference between the two keystrokes will be computed and displayed.
In order to alert the reactor to press a key, I am playing a tone that is about 1 second long when the initiator releases their key. However, since reaction time is on the order of milliseconds, I don't want them to have to wait for the tone to complete to press the button as they currently must, or even initiate a new set of two keystrokes. As soon as the sound fires, you should be free to press more keys.
In this sense, I can't figure out how to make the events non-blocking. Using the listener.start() paradigm doesn't seem to help me here. Code is below:
from pynput import keyboard
from datetime import datetime
from playsound import playsound
INITIATE = {keyboard.Key.enter}
def on_press(key):
global initiated_time
pressed_time = None
reaction_time = None
if key in INITIATE:
initiated_time = datetime.now()
return initiated_time
else:
pressed_time = datetime.now()
reaction_time = pressed_time - initiated_time
reaction_time_ms = int(reaction_time.seconds) * 1000 + int(reaction_time.microseconds)/1000
print('Reaction time %s ms' % (reaction_time_ms))
def on_release(key):
if key in INITIATE:
playsound('bong.mp3')
print('')
with keyboard.Listener(
on_press=on_press,
on_release=on_release
) as listener:
listener.join()
According to the docs here, playsound has a default argument, block, which will play the sound asynchronously if set to False, meaning it will not prevent your script from continuing to run.
Related
import keyboard
import pygame
import mouse
import time
def press_X():
time.sleep(0.2)
keyboard.press('x')
time.sleep(0.6)
keyboard.release('x')
print('Command Executed - press_X')
#SA_R_X V_1.0
#------------------------------------------
while True:
try:
keyboard.add_hotkey('r', press_X)
time.sleep(0.5)
break
except:
keyboard.add_hotkey('r', press_X)
time.sleep(0.5)
break
the problem is the code cannot detect if 'r' is pressed when i am holding 'w' and/or 'space'... (well any key really)
I tried to use a try and except to handle a combination of any key + 'r'. But it did not work. All I need is for the code to be able to detect an 'r' input even if I am pressing/ holding another key at the same time. Then after this the code waits 0.2 seconds before holding down the 'x' key for 0.6 seconds and releasing. Any help is appreciated and it would be very helpful if you included a short explanation on where I went wrong and how you fixed it.
The documentation for this module can be found here. This is where all relevant information can be found.
The best way to do this, from my understanding, is to use an alternative function. If you want the program to continue to run, even though the key has not been pressed, then I suggest using the keyboard.on_press_key() function. This would mean that the rest of your program could still run, and your press_X() function could be run as a callback. Here is an example of how this code could be implemented.
import keyboard
import pygame
import mouse
import time
class App:
running = True
def press_X():
time.sleep(0.2)
keyboard.press('x')
time.sleep(0.6)
keyboard.release('x')
print('Command Executed - press_X')
App.running = False
#SA_R_X V_1.0
#------------------------------------------
keyboard.on_press_key('r', lambda x: press_X()) ## Adds an event listener for the r key
## This will stop execution if there is no code after this point
If you want it to stop the program and wait for the r key to be pressed, then you could use keyboard.wait(). This will basically pause your program until the key is pressed, after which your function would be run. For example, to replace the keyboard.on_press_key('r', lambda x: press_X()):
keyboard.wait('r')
press_X()
From my understanding, keyboard.add_hotkey() does not work in your situation because it is looking for an exact combination of keys being pressed, such as Ctrl+C, and will only go if only the keys in the hotkey are pressed.
I hope this helps, good luck!
I wrote a simple python script that gives control over the cursor to a joystick. My way to find out how this works is documented here. Now that works flawlessly but, as soon as I start the script to use the joystick, the mouse is useless, because my python routine sets the value back to its original, whenever a new joystick event comes in.
Thus I want my joystick events to be ignored as long as a key of the keyboard is pressed. I came across the pygame.key.get_pressed() method but this seems to work only, if the pygame window is in focus. I want this script running in background. Should I start using non-pygame events to listen to the keyboard or are there ways to keep track of the keyboard events analogue to the joystick events, which are recognized in background, via pygame?
I expect pygame sets up its own "sandbox" so that it's hard to detect input from outside its window. Your previous question indicates that you are also using the win32api module. We can use that to detect global key presses.
The correct way to detect key presses at the global scope is to set up a keyboard hook using SetWindowsHookEx. Unfortunately, win32api does not expose that method, so we'll have to use a less efficient method.
The GetKeyState method can be used to determine whether a key is down or up. You can continuously check the state of a key to see if the user has pressed or released it lately.
import win32api
import time
def keyWasUnPressed():
print "enabling joystick..."
#enable joystick here
def keyWasPressed():
print "disabling joystick..."
#disable joystick here
def isKeyPressed(key):
#"if the high-order bit is 1, the key is down; otherwise, it is up."
return (win32api.GetKeyState(key) & (1 << 7)) != 0
key = ord('A')
wasKeyPressedTheLastTimeWeChecked = False
while True:
keyIsPressed = isKeyPressed(key)
if keyIsPressed and not wasKeyPressedTheLastTimeWeChecked:
keyWasPressed()
if not keyIsPressed and wasKeyPressedTheLastTimeWeChecked:
keyWasUnPressed()
wasKeyPressedTheLastTimeWeChecked = keyIsPressed
time.sleep(0.01)
Warning: as with any "while True sleep and then check" loop, this method may use more CPU cycles than the equivalent "set a callback and wait" method. You can extend the length of the sleep period to ameliorate this, but the key detection will take longer. For example, if you sleep for a full second, it may take up to one second between when you press a key and when the joystick is disabled.
when your window gains or looses focus you get an ACTIVEEVENT. It's gain and state attributes tell you which state you've gained or lost. The easisest solution would probably be to catch this events in your main event loop and use them to keep track weather you have focus or not. Then you can just ignore joystick events if you don't have the focus.
I just found out about the pynput library which is exactly what I have been looking for. My goal is to capure the keys the user is typing and whenever a specific sequence of keys was captured I want the computer to write a sequence to the current carret's position. After that I want to capture the usersĀ“s keys again until another noteworthy sequence occurs. And so on.
The problem is that the simulated key strokes of keyboard.write() are also considered by the Listener which leads to an infinite loop which was funny the first time it occurred but I am trying to get rid of it now obv.
My approach is to stop the Listener and create a new one after the computer is done typing but this process slows down a lot after the first start_listener() invocation and isn't optimal in the first place I think. And I am out of further ideas so I was hoping someone could help here.
Here is my code so far:
import keyboard
from pynput.keyboard import Key, Listener
def on_press(key):
stop_listener()
keyboard.write("Hello", 0.05)
start_listener()
def on_release(key):
if key == Key.esc:
return False
def start_listener():
global listener
listener = Listener(on_press=on_press, on_release=on_release)
listener.start()
listener.join()
def stop_listener():
global listener
listener.stop()
with Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
There is a simple solution to the problem. I actually missed that I used two packages that have nothing to do with each other. Thanks to the ease to install them on my IDE.
Anyway my solution is now to only use the already present keyboard package https://pypi.org/project/keyboard/ and not pynput.keyboard
It turn out to be really easy when not using two separate packages :).
Here is the code:
# coding=utf8
import keyboard as k
def on_press(key = k.KeyboardEvent):
k.write(key.name, 0.0)
k.on_press(on_press)
k.wait()
On my Linux Mint I need admin privileges to run keyboard but I can do the same with pynput.keyboard.Controller and methods press(), release(), time.sleep()
Instead of stoping and starting listenere I would use global variable paused = False to control when on_press should runs code.
from pynput.keyboard import Key, Listener, Controller
import time
keyboard = Controller()
def on_press(key):
global paused # inform function to assign new value to global variable (instead of creating local variable)
if not paused:
paused = True
#keyboard.type("Hello") # module pynput.keyboard.Controller
for char in "Hello":
keyboard.press(char) # pynput.keyboard.Controller
keyboard.release(char) # pynput.keyboard.Controller
time.sleep(0.05)
paused = False
def on_release(key):
if not paused:
if key == Key.esc:
return False
# global variable with default value at start
paused = False
with Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
Tested on Linux Mint 20, Python 3.8.5, pynput 1.7.3
I want to make the function mouse.record() run until a key is pressed, rather than a mouse button.
The mouse.record() is a function from the python module mouse:
(mouse/__init__.py)
def record(button=RIGHT, target_types=(DOWN,)):
"""
Records all mouse events until the user presses the given button. Then returns the list of events recorded. Pairs well with `play(events)`.
Note: this is a blocking function. Note: for more details on the mouse hook and events see `hook`.
"""
recorded = []
hook(recorded.append)
wait(button=button, target_types=target_types)
unhook(recorded.append)
return recorded
I've thought that I could merge the mouse module with the keyboard module to achieve a function that records the mouse movement until a keyboard event. There is a similar keyboard function that could be handy:
(keyboard/__init__.py)
def record(until='escape', suppress=False, trigger_on_release=False):
"""
Records all keyboard events from all keyboards until the user presses the given hotkey. Then returns the list of events recorded, of type `keyboard.KeyboardEvent`. Pairs well with `play(events)`.
Note: this is a blocking function. Note: for more details on the keyboard hook and events see `hook`.
"""
start_recording()
wait(until, suppress=suppress, trigger_on_release=trigger_on_release)
return stop_recording()
So, to sum it up, what I want to achieve is a function that records the mouse movement until a keyboard event using the Python modules mouse and keyboard.
Is this possible?
You can merge both without messing up with the module files:
1) Use mouse.hook() to record events without waiting (like it happens with mouse.record() ). It takes a function and return it that event.
2) Use keyboard.wait(key) to wait for the key to be pressed
3) Use mouse.unhook() to stop recording.
Here is an example code:
import mouse
import keyboard
events = [] #This is the list where all the events will be stored
mouse.hook(events.append) #starting the recording
keyboard.wait("a") #Waiting for 'a' to be pressed
mouse.unhook(events.append) #Stopping the recording
You can also use threading to join keyboard and mouse together. The following code would record the mouse events and keyboard events and then play it back. You can stop monitoring by pressing Escape button.
import threading
import mouse
import keyboard
def monitorMouseKeyboardEvents():
#These are the list where all the events will be stored
mouse_events = []
keyboard_events = []
#Start recording
mouse.hook(mouse_events.append) #starting the mouse recording
# keyboard.hook(lambda _: keyboard_events.append(_))
keyboard.start_recording()
keyboard.wait("esc") #Waiting for 'Esc' button to be pressed
#Stopping recording
mouse.unhook(mouse_events.append)
# keyboard.unhook(keyboard_events.append)
keyboard_events = keyboard.stop_recording()
return mouse_events, keyboard_events
def playMouseMouseKeyboardEvents(mouse_events, keyboard_events):
'''
Playing the recorded events at the same time
'''
k_thread = threading.Thread(target = lambda :keyboard.play(keyboard_events))
k_thread.start()
#Mouse threadings:
m_thread = threading.Thread(target = lambda :mouse.play(mouse_events))
m_thread.start()
#waiting for both threadings to be completed
k_thread.join()
m_thread.join()
if __name__ == "__main__":
mouse_events, keyboard_events=monitorMouseKeyboardEvents()
playMouseMouseKeyboardEvents(mouse_events, keyboard_events)
Been using the pyautogui module to do most of my things, but I have come across one problem:
I cannot hold down a key for a certain length of time.
Does anyone know any modules that can do this, or have a solution without downloading any modules?
For example (perfect for me):
I go into word, and run my code. Word should just be receiving (w pressed down), with the w's slowly increasing - (after a while holding adds like 5 a half sec).
You can use the following example:
>>> pyautogui.keyDown('shift') # hold down the shift key
>>> pyautogui.press('left') # press the left arrow key
>>> pyautogui.press('left') # press the left arrow key
>>> pyautogui.press('left') # press the left arrow key
>>> pyautogui.keyUp('shift') # release the shift key
In your case you'd use the keyDown function and a timer or equivelent to trigger keyUp.
You can find more information in regards to using timers here or better yet use Timer from the threading library - especially if you want to the processing to continue.
Example of using threading.Timer below.
def hello():
print("hello, world")
t = Timer(30.0, hello)
t.start() # after 30 seconds, "hello, world" will be printed
In the keyDown documentation one can note the following:
NOTE: For some reason, this does not seem to cause key repeats like
would happen if a keyboard key was held down on a text field.
An alternative to using the keyDown function is to repeat the press function; in cases where keyDown is not satisfying the behaviour required by the developer and/or user.
def hold_key(key, hold_time):
import time, pyautogui
start_time = time.time()
while time.time() - start_time < hold_time:
pyautogui.press(key)
or
import pyautogui
while True:
pyautogui.press('w')
The above code is not tested.