Detecting if a key is HELD down - python - python

My use case
I need to know when a (specific) key is pressed and held down. The use case after detection is fairly easy. When the key is released, send a signal to stop the callback (which I know already).
Desired behavior
Here is a rough scheme of how the algo looks like:
def the_callback():
if key_held == the_hotkey:
someObj.start() # this class Obj works totally well so no issues here on
elif key_released == the_hotkey:
someObj.stop()
else:
# we don't care. continue looking for Keyboard events
# here any kinda listener or just a loop which passes events to the callback
I should mention that any kinda listener which blocks the execution is okay as it will run in its own thread (already running pynput.keyboard.Listener in a thread so not a problem)
What I've tried
I used pynput and its pynput.keyboard.Listener to detect key-presses and invoke callbacks accordingly but I couldn't make that work to detect when a key is HELD down.
the current solution looks roughly like:
# not real code. just rough scheme
def on_pressed(key):
if key == my_hotkey:
if running_already: # this part works well already
obj.stop()
else:
obj.start()
else:
# we don't care
with pynput.keyboard.Listener(on_press=on_pressed) as listener:
listener.join() # blocking call until SystemExit, `return False` from callback or `listener.stop()`
I have a very strong feeling that I can make this work by adding on_release=another_callback_that_handles_releases (available within pynput.keyboard.listener).
Perhaps by storing the last known pressed keystroke, and checking if the key released was the same as the hotkey which was pressed earlier but I'm not sure how would I go about it and can that even work?
Then I decided to give keyboard (different lib) a go.
I wrote the below code for the same which can detect keys being held down. This below code achieves almost nearly what I want:
import keyboard as kb, time
while 1:
while kb.is_pressed('q'):
print('Key is held')
time.sleep(0.5) # sleep added just to stop it from spamming the stdout
else:
print('No it\'s Not')
time.sleep(0.5)
The issue with this solution is, it's not very well suited for OSX and Ubuntu. And it has some issues working with special keys. Moreover, I have the hotkey stored as pynput.keyboard.Key.f7 (for eg) or pynput.keyboard.KeyCode(char='s') # for character keys and these enums have different values than what keyboard uses to scan key IDs (using keyboard.hook()).
The final question
How should I go about detecting a key being HELD down. I'd prefer to achieve this using pynput as the rest of the code base uses it but 'keyboard is fine too.
Again I have a feeling that using on_press=a_callback and on_release=another_callback this might be achieved but I'm not entirely sure about it. Lastly, the solution is preferred to be cross platform (I'm fine with using three different functions depending on value of platform.system()).
How would you go about achieving it?
EDIT-1
HERE is what I wrote as an attempt (and MCVE) after suggestion by Isak. This works almost perfectly with just 1 flaw. And that is that it doesn't listen to keypresses right from the program start.
It takes some time for some unknown reason before it starts to actually detect any keypress. Good thing is that once it detects the keypress for the first time, it works flawlessly.
What am I missing there?

Try to check for the key_pressed event on the specific key until the event becomes key_released. So when you detect a click on the key you execute your code and when it detects the release of that key the code stops

I figured out why My Approach was taking a lot of time to initialize before starting the Listener. It was because of the while loop which didn't have any time.sleep() calls and it was probably messing with the system (although I wouldn't expect that to happen as it runs in its own thread but probably the while loop doesn't release the GIL as it's just in the loop doing literally nothing without any sort of delay).
I just added time.sleep(0.2) inside the while loop (the outer one). Any delay would do as that would release the GIL for some time and the Listener thread would be processed and made active.
Edit: Accepting the answer by Isak as accepted as that is the correct approach.

Related

time.sleep() crashes program when using while-loop in PyQt

When I use the function time.sleep() in the terminal, it is no problem.
When I use PyQt and want to use a label, it crashes and/or only shows the last number.
By the way: I want a function, that counts e.g. a year from 2020 to 2030 in various speed, that the user can change and the year should be shown in a label.
Thanks a lot for your help.
# timer that counts in the future with various speed // still crashing
def timer(self):
x=datetime.datetime.now()
z=x.ctime()
self.ui.labelDateTime.setText(z)
var=x.year
while True:
if var==2030:
break
else:
var+=1
y=x.replace(year=var)
z=y.ctime()
self.ui.labelDateTime.setText(z)
time.sleep(0.5)
print("You are dead")
You should call QtCore.QCoreApplication.processEvents() within your for loop to make the Qt's event loop proceed the incoming event (from keyboard or mouse or sleep).
Although calling QtCore.QCoreApplication.processEvents() works , I have read in many places on the web that it should be a last resort. Unfortunately none of the sources clearly explain why -- but see for example
How to make Qt work when main thread is busy?
Should I use QCoreApplication::processEvents() or QApplication::processEvents()?
How to implement a QThread that runs forever{} with a QWaitCondition but still needs to catch another Slot while doing that
http://qt-project.org/forums/viewthread/22967
So it does seem allowed, but in general, it seems to be better design to use QTimer or QThread.

Global hotkey to stop windows script

I'm working on a script that grabs control of my mouse and runs within a simple infinite while loop.
def main():
while True:
do_mouse_stuff()
Because of the mouse control, it's a pain to click on the python window and hit ctrl-c, so I've been looking for a way to implement a global hotkey in windows. I'm also a relative Python noob so I've probably missed an obvious answer. Stuff I've found:
pyhk - the closest I've gotten, but this module does nasty things to my computer for some reason (probably something I'm doing wrong), it introduces major mouse lag, complete input lockout, all kinds of stuff I'm not smart enough to deal with.
pyHook - Followed the tutorial, works fine but the infinite running message pump and my while loop appear to run exclusively and I haven't figured out how to make it work.
Another Method - I found this method as well, but I have the same problem as pyHook, the try loop and my while loop cannot coexist.
I've tried to figure out how to integrate my loop into these examples rather than maintaining a separate loop but I've not been able to make that work, again likely due to my noobishness. Would someone be able to straighten me out on how to make this work?
Perhaps using msvcrt? I'm not sure if it's "global", and I can't test it right now, unfortunately, but here's an example of detecting the Escape key (taken from this question), integrated with your keyboard stuff:
import msvcrt
def main():
while True:
do_mouse_stuff()
# Check if `Esc` has been pressed
if msvcrt.kbhit() and msvcrt.getch() == chr(27).encode():
aborted = True
break

pygame capture keyboard events when window not in focus

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.

Sleep Program Until Keypress

I have the following code
#!/usr/bin/python
import keybinder
def Mark(args):
print "Why, hello!"
keybinder.bind("<Super>m", Mark, "junk")
KEYBINDER.MAIN_LOOP_KEYPRESS()
In other words, I would like to make a program which sleeps silently in the background until a key combination is pressed anywhere in the system. Keybinder seems like a good way of getting the keypress, but I'm not sure how to do the sleeping part implied by the final line. It seems as though importing a large framework like GTk would be overkill for this application and I'd prefer to avoid a busy loop.
Any thoughts?
Maybe just:
while not key_pressed:
time.sleep(0.2)

Python3: Get keyboard input without effect from cursor blink rate

I am trying to find some sort of library or function so I can get fast keyboard input.
Right now, using the Conio.h input method, you can hold down a key, but you have to wait a half a second for it to start repeating, the same as in any text box. This seems to be dictated by the cursor repeat delay, shown here.
Any way to get realtime keyboard input rather than having to suffer this small delay?
I've heard of pyHook but that doesn't work for Python 3(.2). Thanks!
You'll need to do it the hard way, creating your own window and then listening for keydown and keyup events, using a timer to trigger the "repeat" of the keypress.
I eventually wrote a small DLL to use the Win32 function GetAsyncKeyState.

Categories

Resources