do something after a period of gui user inactivity tkinter - python-3.x

What is the general method for 'doing something' after a period of user inactivity in tkinter? In my case the 'do something' will be to go to the start screen (tk.frame) that is already instantiated.

The simplest solution I can think of looks something like this:
start a timer
set a binding on any key press or any button click to reset the timer
if the timer goes off, do something
Create a function to call when the user is inactive:
def user_is_inactive():
<your code here>
Create a function to reset the timer.
We want to be able to call it from an event or directly, so the event argument needs to be optional:
timer = None
def reset_timer(event=None):
global timer
# cancel the previous event
if timer is not None:
root.after_cancel(timer)
# create new timer
timer = root.after(10000, user_is_inactive)
Set up bindings to reset the timer
Using bind_all means that every widget can potentially handle these events:
root.bind_all('<Any-KeyPress>', reset_timer)
root.bind_all('<Any-ButtonPress>', reset_timer)
Start the timer
A good time to do this is right before calling mainloop.
reset_timer()
root.mainloop()

I will admit that I have no experience in this, but maybe a sort of watchdog timer could work? A timer would count up to your desired time, but anytime an element is activated it would reset the counter. This concept is used in micro-controllers a lot but I'm not sure how you would apply it to python.

Related

How do I manually restart a one shot Timer?

I've added a one shot Timer and hooked up a function to listen for the timeout() signal. After some action occurs in my game, I want to restart the timer at its original Wait Time value.
I've tried resetting the timer to the Wait Time I set in the inspector like this:
$Timer.time_left = $Timer.wait_time
This results in an error:
Invalid set index 'time_left' (on base: 'Timer') with value of type 'float'.
How do I set the time back to its original Wait Time value in the inspector?
Looking at the documentation for Timer, it says that you can't set the time_left property and should instead use start:
Note: You cannot set this value. To change the timer's remaining time, use start.
Calling start without any parameters restarts the timer to its original Wait Time value:
$Timer.start()
The start method also takes in an optional time_sec parameter that could instead restart the timer to a new wait time:
$Timer.start(0.5)

Control close event pyglet

I have a program that has multiple windows in pyglet, and I want to make one window unclosable, rather to set its visibility to false. Is there a way I can access the event handle for close, to make it instead of closing the window, possibly return a bool or a string like "should close."?
Found the answer to my own question, first define a custom event loop with window_event_loop = pyglet.app.EventLoop(), then create a handler for .event for the event loop
#window_event_loop.event
def on_window_close(window):
# Extra code here
return pyglet.event.EVENT_HANDLED

How to tell if user has released slider

There's an issue on the github to add a released() signal to the slider node, but how would I do the same thing without it?
I want to have a slider, and when the user moves it it says "Value is now X" on a label on the screen. But when I do it based on the 'value_changed(x)' it calls many times while the slider is being dragged. I want it to set my only label when the player releases after sliding, or when presses and releases an area on the slider's range to select a new value without using the grabber.
Okay, this is what I've come up with. It doesn't literally let me know when the slider is released, but it tells me when the player stops editing the slider. It still sends an alert if you pause briefly, but that is okay for my game. It doesn't send continuous alerts like if you just use _on_HSlider_value_changed(), which is what I wanted to avoid.
var old = self.value #start value of slider
var timer_on = false
#will be called continuously while editing timer
func editing_slider(new):
#only start a timer, if there isn't one already or you'll have a million
if not timer_on:
#start timer
timer_on = true
yield(get_tree().create_timer(.2), "timeout" )
timer_on = false
#if still editing, re call function
if old != new:
editing_slider(new)
#done editing
else:
print("slider set to " + str(value))
old = new
func _on_HSlider_value_changed(value):
editing_slider(value)
If you wanted to avoid the alert being called when the user pauses but hasn't released, you'd have to do do some kind of InputEvent check.
You can achieve what you want by overriding the _gui_input function. Attach a script to your slider, and then add this code:
func _gui_input(event):
if (event is InputEventMouseButton) && !event.pressed && (event.button_index == BUTTON_LEFT):
print("Released")
This will work whether the user releases the grabber or "releases an area on the slider's range to select a new value without using the grabber", and achieves what you want. However, if the code is meant to run on a device with a keyboard (e.g. a PC), then the user can also change the value via the cursor keys on the keyboard, and you may want to add support for that too.

Repeat function call while button is pressed PyQt5

I started with some code like this which connects a function to a GUI button.
def on_click():
call_other_funct()
time.sleep(1)
button = QPushButton('Do the thing', self)
button.pressed.connect(on_click)
The problem is I need to repeatedly call on_click() every second for the duration of the mouse being held down on the button. I've searched quite a bit but haven't found a solution using PyQt.
I've been trying to fix this using a timer interval
def on_release():
self.timer.stop()
def on_click():
self.timer.start(1000)
self.timer.timeout.connect(on_click())
print('click')
button.pressed.connect(on_click)
button.released.connect(on_release)
This sort of works, but for some reason there seem to be and exponential number of on_click() calls happening. (on the first call, "click" prints once, then twice, then 4 times, then 8 etc). Is there a way to make this work correctly so each call only calls itself again once?
Or is there an all together better way to be doing this?
I suspect that the "exponential growth" comes from the fact that in the event handler on_click, you create a connection between the timer and the event handler itself. So I would expect something like this to happen:
on_click is executed once and the timer is connected once to on_click
after a second, the timer runs out and triggers on_click. During execution of on_click, the timer is connected to on_click again.
after a second, the timer runs out and triggers on_click twice (due to 2 connections). Which in turn then generate 2 more connections.
etc.
What you should do is connect your timer to another function which actually does the thing you want to execute every second while the mouse button is down.
def on_release():
self.timer.stop()
def on_press():
self.timer.start(1000)
def every_second_while_pressed():
print('click')
button.pressed.connect(on_press)
button.released.connect(on_release)
self.timer.timeout.connect(every_second_while_pressed)

Linux x11 XGrabKeyboard() cause keyboard to be frozen

I am writing a program which need to listen the user keyboard stroks.
I use function XGrabKeyboard() and this is my code:
XGrabKeyboard(pDisplay, DefaultRootWindow(pDisplay), True, GrabModeAsync, GrabModeAsync, CurrentTime);
XEvent event;
while (true)
{
XNextEvent(pDisplay, &event);
switch (event.type)
{
...
}
}
But it causes the keyboard and cursor to be frozen.
I looked up the man page, it only says: "The third parameter specifies a Boolean value that indicates whether the keyboard events are to be reported as usual."
I tried both true or false or the 3rd param, both GrabModeAsync and GrabModeSync for the 4th and 5th param, but it doesn't work.
After calling XGrabKeyboard(), the keyboard is frozen and mouse click doesn't response.
Any ideas?
XGrabKeyboard() (if successful - be sure to check the return value), redirects all key events to your client.
So if your "..." inside the while(true) does not properly handle those key events, or does not ever ungrab (XUngrabKeyboard) or release sync events (XAllowEvents, only applies to GrabModeSync), then the keyboard would appear to lock up.
The boolean parameter is owner_events which indicates whether to report key events always to the window provided to XGrabKeyboard, or report them to the window they normally would have gone to without the grab. Typically you want False (report to the grab window).
For typical uses of XGrabKeyboard (I don't know your use-case) the parameters you would want are:
grab window = some window in your app that relates to the reason for the grab
owner_events=False to send all events to that window
pointer_mode=Async to not screw with the pointer
keyboard_mode=Async to just redirect all key events and avoid need for AllowEvents
time=the timestamp from the event triggering the grab, ideally, or one generated by changing a property and grabbing the timestamp off the PropertyNotify
But, it depends. To give any definitive answer you'd probably need to post a compilable program, I think the bug is likely in the "..." part of your code. Try narrowing your app down to a single-file test case that can be run by others perhaps. Or explain more why you are grabbing and what you're trying to accomplish in the big picture.
I cant help with the XGrabKeyboard function - I havent used it before and dont know how it works - but I can suggest another way of getting the keyboard events.
When creating my window using XCreateWindow, the last argument is a XSetWindowAttributes object. This object has a member event_mask, which you can use to choose which events your window will receive.
I set mine like this:
XSetWindowAttributes setWindAttrs
setWindAttrs.event_mask = ExposureMask
| KeyPressMask
| KeyReleaseMask
| ButtonPressMask
| ButtonReleaseMask;
That will mean you receive events for keyboard key presses and mouse button clicks if you pass this object to XCreateWindow on window creation.
Also another note you can use XPending(pDisplay) to check if there are still events waiting to be handled - so it could replace true in your while(true) line.
Edit: Also your freezing issue could be that you dont return false anywhere in your while loop? It may be stuck in an infinite loop, unless you just removed that bit for the post. Try replacing true with xpending as I suggested above and it may fix the issue, or just returning false after handling the event, but this would only handle one event per frame rather than handling all the currently pending events like XPending would do, and I assume that is what you want to do.

Resources