2

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

  1. 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)?
  2. How do I switch to the settings screen in the first version of the code?
  3. Why is the settings screen blank in code version 2?
  4. Why does the error occur in version 3?
  5. Is there another (better) way to code this?

thanks a lot!

Wannes
  • 41
  • 5

2 Answers2

1

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.

John Anderson
  • 35,991
  • 4
  • 13
  • 36
  • great, thanks! clock & the on_touch/key events are really usefull -- the sm.current works for switching screens, but I don't understand why the sm.switch_to doenst work – Wannes Jan 22 '21 at 08:20
  • 1
    The [documentation](https://kivy.org/doc/stable/api-kivy.uix.screenmanager.html#kivy.uix.screenmanager.ScreenManager.switch_to) for `switch_to` is terrible. It fails to mention that that the old `Screen` is removed from the `ScreenManager`. You can either add all the `Screens` to the `ScreenManager` and use `sm.current` to switch, or do not add any `Screens` to the `ScreenManager` and use `sm.switch_to()`. Mixing the two approaches can only be successful with great care. – John Anderson Jan 22 '21 at 14:30
0

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().

Craig Jones
  • 2,428
  • 2
  • 17
  • 11