Kivy screenmanager: switching screen after timeout with signal
Question:
Goal is to move to a settings screen when no button is pressed, text is entered or whatever for a certain time.
In fact, functionality is like a screensaver of some sorts.
code version 1
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
sm.switch_to(setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
sm = ScreenManager()
setscreen = SettingsScreen(name='settings')
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return sm
if __name__ == "__main__":
wiscApp().run()
and the .kv
<MenuScreen>:
BoxLayout:
orientation: 'vertical'
BoxLayout:
Button:
text: "resettimeout"
on_press: app.resetscreensavertimeout()
Button:
text: "do other things"
Button:
text: 'settings'
on_press: root.manager.current = 'settings'
<SettingsScreen>:
BoxLayout:
Button:
text: "stop app"
on_press: app.stop()
Button:
text: 'Back to menu'
on_press: root.manager.current = 'menu'
This works perfectly up until the calling of sm.switch_to(setscreen)
in the setscreensaver function.
I tried the following:
code version 2
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
sm = ScreenManager()
setscreen = SettingsScreen(name='settings')
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
self.sm.switch_to(self.setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return self.sm
if __name__ == "__main__":
wiscApp().run()
But then the settings screen is BLANK!
In the first version of the code, I understand by it doens’t work: both sm and setscreen are undefined variables in that function.
In the second version, I don’t understand why the settings screen is blank.
edit
*** version 3 of the code***
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
self.sm.switch_to(self.setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
self.sm = ScreenManager()
self.setscreen = SettingsScreen(name='settings')
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return self.sm
if __name__ == "__main__":
wiscApp().run()
In this version 3, transitioning to the settings screen with the signal works fine, but if I then click the menu button, I get this error (this error does not appear in the other versions of the code):
kivy.uix.screenmanager.ScreenManagerException: No Screen with name "menu".
So, I have several questions
- how do I reset the timer every time a button is pressed, text is
entered of whatever, other than defining callbacks for every event
(e.g on_press: app.resetscreensavertimeout()) in the .kv code)?
- How do I switch to the settings screen in the first version of the
code?
- Why is the settings screen blank in code version 2?
- Why does the error occur in version 3?
- Is there another (better) way to code this?
thanks a lot!
Answers:
Here is a modified version of your code that uses Clock.schedule_once()
instead of signal
:
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
self.resetscreensavertimeout()
self.sm.current = 'settings'
def resetscreensavertimeout(self, *args):
print("resetting screensaver timer")
self.resetEvent.cancel()
self.resetEvent = Clock.schedule_once(self.setscreensaver, 5)
def build(self):
self.sm = ScreenManager()
self.setscreen = SettingsScreen(name='settings')
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
self.resetEvent = Clock.schedule_once(self.setscreensaver, 5)
Window.bind(on_touch_down=self.resetscreensavertimeout)
Window.bind(on_key_down=self.resetscreensavertimeout)
return self.sm
This also uses Window.bind()
to trigger the reset of the timeout whenever a button is pressed or a key is pressed.
I took this one step further and defined a general-purpose function to add this functionality to any app.
from typing import Callable
from kivy.app import App
from kivy.clock import Clock
from kivy.core.window import Window
def add_inactivity_timeout(app: App, do_timeout: Callable, inactivity_minutes=60):
"""
Sets up an inactivity timeout for the given Kivy app. "Activity" meaning
a touch (a button click) or a key press.
IMPORTANT: Call this from within the `build()` method, not `__init__()`.
This adds a `reset_timeout()` method to the app which can be called
directly by any other activity that needs to be considered (e.g.
periodically within a long-running computation, or while accessing an
external resource).
Alternatively, call `cancel_timeout()` to disable the timeout on the
way in to the long-running activity, and then call `reset_timeout()`
afterwards to re-enable it.
:param app: The kivy app to enhance.
:param do_timeout: The method to call if a timeout occurs.
:param inactivity_minutes: The timeout length in minutes. Defaults to 60.
"""
app.inactivity_minutes = inactivity_minutes
app.reset_event = None
def cancel_timeout(*args):
if app.reset_event:
app.reset_event.cancel()
def reset_timeout(*args):
cancel_timeout()
app.reset_event = Clock.schedule_once(do_timeout, app.inactivity_minutes * 60)
app.cancel_timeout = cancel_timeout
app.reset_timeout = reset_timeout
reset_timeout()
Window.bind(on_touch_down=reset_timeout)
Window.bind(on_key_down=reset_timeout)
To use it, create a callback method for the timeout:
def inactive(self, *args):
print("Timed out -- Inactive!")
self.stop()
and then call
add_inactivity_timeout(self, self.inactive, inactivity_minutes=15)
from within build()
.
Goal is to move to a settings screen when no button is pressed, text is entered or whatever for a certain time.
In fact, functionality is like a screensaver of some sorts.
code version 1
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
sm.switch_to(setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
sm = ScreenManager()
setscreen = SettingsScreen(name='settings')
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return sm
if __name__ == "__main__":
wiscApp().run()
and the .kv
<MenuScreen>:
BoxLayout:
orientation: 'vertical'
BoxLayout:
Button:
text: "resettimeout"
on_press: app.resetscreensavertimeout()
Button:
text: "do other things"
Button:
text: 'settings'
on_press: root.manager.current = 'settings'
<SettingsScreen>:
BoxLayout:
Button:
text: "stop app"
on_press: app.stop()
Button:
text: 'Back to menu'
on_press: root.manager.current = 'menu'
This works perfectly up until the calling of sm.switch_to(setscreen)
in the setscreensaver function.
I tried the following:
code version 2
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
sm = ScreenManager()
setscreen = SettingsScreen(name='settings')
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
self.sm.switch_to(self.setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return self.sm
if __name__ == "__main__":
wiscApp().run()
But then the settings screen is BLANK!
In the first version of the code, I understand by it doens’t work: both sm and setscreen are undefined variables in that function.
In the second version, I don’t understand why the settings screen is blank.
edit
*** version 3 of the code***
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
self.sm.switch_to(self.setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
self.sm = ScreenManager()
self.setscreen = SettingsScreen(name='settings')
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return self.sm
if __name__ == "__main__":
wiscApp().run()
In this version 3, transitioning to the settings screen with the signal works fine, but if I then click the menu button, I get this error (this error does not appear in the other versions of the code):
kivy.uix.screenmanager.ScreenManagerException: No Screen with name "menu".
So, I have several questions
- how do I reset the timer every time a button is pressed, text is
entered of whatever, other than defining callbacks for every event
(e.g on_press: app.resetscreensavertimeout()) in the .kv code)? - How do I switch to the settings screen in the first version of the
code? - Why is the settings screen blank in code version 2?
- Why does the error occur in version 3?
- Is there another (better) way to code this?
thanks a lot!
Here is a modified version of your code that uses Clock.schedule_once()
instead of signal
:
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
self.resetscreensavertimeout()
self.sm.current = 'settings'
def resetscreensavertimeout(self, *args):
print("resetting screensaver timer")
self.resetEvent.cancel()
self.resetEvent = Clock.schedule_once(self.setscreensaver, 5)
def build(self):
self.sm = ScreenManager()
self.setscreen = SettingsScreen(name='settings')
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
self.resetEvent = Clock.schedule_once(self.setscreensaver, 5)
Window.bind(on_touch_down=self.resetscreensavertimeout)
Window.bind(on_key_down=self.resetscreensavertimeout)
return self.sm
This also uses Window.bind()
to trigger the reset of the timeout whenever a button is pressed or a key is pressed.
I took this one step further and defined a general-purpose function to add this functionality to any app.
from typing import Callable
from kivy.app import App
from kivy.clock import Clock
from kivy.core.window import Window
def add_inactivity_timeout(app: App, do_timeout: Callable, inactivity_minutes=60):
"""
Sets up an inactivity timeout for the given Kivy app. "Activity" meaning
a touch (a button click) or a key press.
IMPORTANT: Call this from within the `build()` method, not `__init__()`.
This adds a `reset_timeout()` method to the app which can be called
directly by any other activity that needs to be considered (e.g.
periodically within a long-running computation, or while accessing an
external resource).
Alternatively, call `cancel_timeout()` to disable the timeout on the
way in to the long-running activity, and then call `reset_timeout()`
afterwards to re-enable it.
:param app: The kivy app to enhance.
:param do_timeout: The method to call if a timeout occurs.
:param inactivity_minutes: The timeout length in minutes. Defaults to 60.
"""
app.inactivity_minutes = inactivity_minutes
app.reset_event = None
def cancel_timeout(*args):
if app.reset_event:
app.reset_event.cancel()
def reset_timeout(*args):
cancel_timeout()
app.reset_event = Clock.schedule_once(do_timeout, app.inactivity_minutes * 60)
app.cancel_timeout = cancel_timeout
app.reset_timeout = reset_timeout
reset_timeout()
Window.bind(on_touch_down=reset_timeout)
Window.bind(on_key_down=reset_timeout)
To use it, create a callback method for the timeout:
def inactive(self, *args):
print("Timed out -- Inactive!")
self.stop()
and then call
add_inactivity_timeout(self, self.inactive, inactivity_minutes=15)
from within build()
.