How Do i create hotkeys which call functions that interrupt eachother? - python

If you create a hotkey activated by '1' and a hotkey activated by '2' before hotkey 1's function is finished then the second hotkey won't call it's assigned function.
I can create hotkeys which call functions in python using the keyboard module. I have included an example in the following code which works for calling functions.
import keyboard
import time
def hk_print(word,word2):
time.sleep(5)
print(word,word)
keyboard.add_hotkey('1', hk_print, args= ('hotkey','test1'))
keyboard.add_hotkey('2', hk_print, args= ('hotkey','test2'))
keyboard.wait()
Is there anything I can add to this code to make the hotkey activated functions interrupt eachother? So If I press 1 and then 2 it will stop going through the first function and start doing the second one right away?

It's doable, but it'll take some effort. You're getting into async territory.
What I would suggest is using keyboard.record() and using threading.Timer() in lieu of time.sleep(). You would create a new callback which would start recording all the events as soon as the key is pressed and start a Timer() with some timeout, let's say 5 seconds.
After the time has elapsed, you would have to stop recording, then evaluate the events that were captured during the wait, drop all the 1s and 2s pressed save for the very last one, then call ANOTHER callback - hk_print with the right set of args, depending on what the final press was.

Related

Call python function with hotkey

I would like to call a python function or a python file to execute with a hotkey. My function is an infinite loop. I would like to terminate the execution as well with a hotkey. Is it possible?
Windows shortcuts is not an option, because I would like to choose 1 key to execute and not a combination of keys ( for example CRTL + SHIFT + L ) .
It would be nice if the hotkey would be the middle ( scroll ) button of the mouse.
You can setup a hotkey using the mouse module (pip install mouse). To run a function in a loop and then stop in on the hotkey again, you will need to create a new thread (intro to threading) that will run this loop. Then you will want to use an event to stop this thread's execution when the hotkey is pressed again. Below is an example of an implementation that does this.
import mouse # pip install mouse
import threading
import time
def your_function():
# the function you want to execute in the loop
print("Executing your function...")
time.sleep(0.9) # You will probably want to remove this
def repeat_function(kill_event):
print("Starting loop...")
while not kill_event.is_set():
your_function()
print("Exiting loop...")
while True:
print("Waiting for hotkey press...")
kill_event = threading.Event()
new_thread = threading.Thread(target=lambda: repeat_function(kill_event))
# set the hotkey that will start the loop to middle mouse button click
start_hook = mouse.on_middle_click(new_thread.start)
mouse.wait(mouse.MIDDLE, mouse.UP) # wait till the hotkey gets released
mouse.unhook(start_hook) # clear the old hotkey
# set the hotkey to kill the loop instead
end_hook = mouse.on_middle_click(kill_event.set)
mouse.wait(mouse.MIDDLE, mouse.UP)
new_thread.join() # wait for thread to fully finish, unnecessary
mouse.unhook(end_hook) # remove kill hotkey and start again

A function is running despite me changing a variable that should stop it

I was hoping someone could help me with this issue. I'm hoping it's fairly simple to fix, but I have been trying for a while to figure this out. I have trimmed my larger code to this, as I believe the issue here is the crux of the problem.
I have a raspberry pi and an external button. This is on python3 on Linux. I am using GPIOZero for the button. The code below I believe is simple to understand, but basically, I want a function to loop at all times. When I press a button I want another function to run, but only if a variable is a certain number. I describe what I ultimately want to happen in a comment below, but my code is unfinished and simplified just for this problem.
I only want button.when_pressed to work when timer = 0. The problem is, once the code naturally gets into the button.when_pressed function, it never "lets go" of the function again. When I successfully redefine the variable to timer = 1, it still prints button is pressed when I press the button. I don't know why. To me, it seems like it should only work once timer = 0.
Any suggestions? I think I have a misunderstanding of global variables I will plan on researching. I'm not sure if that's the issue. I have also tried using break and continue to try to get it "back on its loop" but that hasn't worked either. Also I want to use button.when_pressed instead of btn.is_pressed, because I want the program to only do something when I'm holding the button down once, and not loop when I'm holding it down. In this case, I want button is pressed to print a single time. If I did btn.is_pressed it would print button is pressed every two seconds, which I dont want.
Thanks for any help. I'm happy to learn.
#!/usr/bin/python3
from gpiozero import Button
from time import sleep
import time
button = Button(4)
timer = 0
def press():
print("Button is pressed")
global timer
timer = 1
def loop():
global timer
timer = 1
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
If you want to disable the callback you had set to button.when_pressed, you need to do another assignment with button.when_pressed = None. This is listed in the documentation:
when_released:
[...] Set this property to None (the default) to disable the event.
It's not exactly clear what behavior you want from your current code. If you want the button to be active for 2 seconds, then be deactivated indefinitely, you can use:
button.when_pressed = press
sleep(2)
button.when_pressed = None
There's no need for a loop, since you don't want to repeat anything.
If you only want the button to be active for a single button press, that needs to happen within 2 seconds, you could instead call button.wait_for_press(2). I hesitate to write a full block of code for that though, as the docs don't specify how a timeout is signaled (it might be by return value, or via an exception). I don't have a Raspberry Pi so I can't test myself, but you could try it out and see what happens.
Treat your whole piece of code as one "black box", ask yourself, what is the input/output? button press or timer mode? (because I don't quite understand what does timer variable mean in your code)
Your code implies timer mode is the top level input to control the flow,
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
Is it expected?
If you allow user to press the button at any time, suggest you make button press to be your top level input, change the logic to keep when_pressed callback always on, set flag once triggered, and then check if the button has been pressed and still is_pressed in your while loop.
pressed = False
def play_video_1():
pass
def play_video_2():
pass
def press():
print("Button is pressed")
global pressed
pressed = True
button.when_pressed = press
while True:
if pressed and not_playing_video2:
if is_pressed:
play_video_1()
else:
pressed = False
play_video_2()
else:
play_video_2()

How to trigger pynput's GlobalHotkeys when all of the keys are released?

With pynput we can listen for global hotkeys like this:
from pynput import keyboard
def on_activate_i():
print('<ctrl>+<alt>+i pressed')
with keyboard.GlobalHotKeys({
'<ctrl>+<alt>+i': on_activate_i}) as h:
h.join()
This will call on_activate_i() when Ctrl+Alt+i is pressed. Now how can we trigger an event when all of the Ctrl+Alt+i keys are pressed and released again?
PyPI has keyboard. I was looking through their git repo and saw this:
def add_hotkey(hotkey, callback, args=(), suppress=False, timeout=1, trigger_on_release=False):
"""
Invokes a callback every time a hotkey is pressed. The hotkey must
be in the format `ctrl+shift+a, s`. This would trigger when the user holds
ctrl, shift and "a" at once, releases, and then presses "s". To represent
literal commas, pluses, and spaces, use their names ('comma', 'plus',
'space').
- `args` is an optional list of arguments to passed to the callback during
each invocation.
- `suppress` defines if successful triggers should block the keys from being
sent to other programs.
- `timeout` is the amount of seconds allowed to pass between key presses.
- `trigger_on_release` if true, the callback is invoked on key release instead
of key press.
So maybe leave the pynput library for one that has the desired behavior built it. FYSA, I have not used either library yet, just doing research on how to implement keyboard hotkeys to control an OBS overlay. Hopefully this is helpful! Now I just need to pick which library I want to start working with for my project...

Tkinter auto-trigger callback continuously

I have a simple tkinter callback that scrubs forward through a video when a key is pressed or held down.
root.bind('<Right>', callback_scrubFwd)
root.mainloop()
This plays back the video very nicely. How can I trigger this callback to be called continuously, which is what happens when the key is held down by the user, only automatically? I've tried ordinary while loops or nested/timed function calls but these lock up the interface.
If you want a function to run continuously, at the end of the function you can call after to put another invocation of the callback on the event queue:
def callback_scrubFwd():
<your code here>
root.after(1000, callback_scrubFwd)
If you want to be able to stop the auto-repeat you can add a flag that you check for each time it is called:
def callback_scrubFwd():
<your code here>
if do_autorepeat:
root.after(1000, callback_scrubFwd)

GUI wait until BooleanVar() changes

how can I make my Tkinter GUI wait for a change of a BooleanVar()? The BooleanVar is controlled by a distance sensor. The GUI should wait until the variable changes to False and the move on.
I tried to use a while True - loop, but as expected it disturbed the mainloop and the programm crashed.
I've also considered to use one if the methods to wait for user-input, but I can't figure out how.
Is there any way to solve this?
Thanks!
I don't understand what you mean by "wait" here, because a GUI is always in a constant state of "wait". It waits for events, and then it acts on events.
If you have a BooleanVar that is set somehow, you can set a trace on that variable. When the value changes, the trace will call a callback of your choice. In that callback your code can do whatever you want.
self.sensor = tk.BooleanVar()
self.sensor.trace("w", self.on_sensor_change)
...
def on_sensor_change(self, *args):
print "the sensor changed:", self.sensor.get()

Categories