2

Related question here

I've discovered a slave property for the runTouchApp function that prevents the Kivy's event loop from running and forces to update it from somewhere else.
Here's a part of app.py where that property is used:

# we are in a slave mode, don't do dispatching.
if slave:
    return

try:
    if EventLoop.window is None:
        _run_mainloop()
    else:
        EventLoop.window.mainloop()
finally:
    stopTouchApp()

Here, if the app is not being run in slave mode, we have two choices on how to run the mainloop.
The first one, _run_mainloop() function works pretty straight-forward - it simply calls the EventLoop.run(), which in turn infinitely calls EventLoop.idle().
That could lead us to believe that to keep the GUI running, we only need to call idle.

But then there's the second option, which calls the kivy.core.window.WindowSDL's method mainloop.
That method works by calling another method, the _mainloop and this is where it gets interesting. The definition of said method is huge and it handles all sorts of events.

So okay, I ran my app in slave mode:

class TestApp(App):
    def start_event(self):
        pass

    def build(self):
        return Button(text = "hello")

    def run(self):
        # This definition is copied from the superclass 
        # except for the start_event call and slave set to True

        if not self.built:
            self.load_config()
            self.load_kv(filename=self.kv_file)
            root = self.build()
            if root:
                self.root = root

        if self.root:
            Window.add_widget(self.root)

        window = EventLoop.window
        if window:
            self._app_window = window
            window.set_title(self.get_application_name())
            icon = self.get_application_icon()
            if icon:
                window.set_icon(icon)
            self._install_settings_keys(window)


        self.dispatch('on_start')
        runTouchApp(slave = True)
        self.start_event()  # Here we start updating
        self.stop()

Now, if I put this in the start_event method (by expectations):

def start_event(self):
    while True:
        EventLoop.idle()

Guess what, the app doesn't respond to touch events and freezes.
So I tried to call the Window's mainloop instead:

def start_event(self):
    EventLoop.window.mainloop()

And suddenly everything started working normally again. But the problem here is that such a call blocks forever, as it is an infinite loop, so there's no one-time update call like EventLoop.idle

How to keep the app running using such one-time calls?

Community
  • 1
  • 1
illright
  • 3,991
  • 2
  • 29
  • 54

1 Answers1

1

Well, this is Python so assuming you want to stick to WindowSDL provider, you can always monkey patch this mainloop function so it won't be infinite:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.base import EventLoop

Builder.load_string('''
<MyWidget>:
    Button:
        text: 'test'
        on_press: print('doing my job')
''')

# https://github.com/kivy/kivy/blob/master/kivy/core/window/window_sdl2.py#L630
def mainloop(self):
    # replaced while with if
    if not EventLoop.quit and EventLoop.status == 'started':
        try:
            self._mainloop()
        except EventLoop.BaseException as inst:
            # use exception manager first
            r = EventLoop.ExceptionManager.handle_exception(inst)
            if r == EventLoop.ExceptionManager.RAISE:
                EventLoop.stopTouchApp()
                raise
            else:
                pass


class MyWidget(BoxLayout):
    pass


if __name__ == '__main__':
    from kivy.base import runTouchApp
    runTouchApp(MyWidget(), slave=True)
    # monkey patch
    EventLoop.window.mainloop = mainloop
    while True:
        EventLoop.window.mainloop(EventLoop.window)
        print('do the other stuff')
        if EventLoop.quit:
            break

It's really hacky though and thus I'd not recommend running something like that in a production code.

Nykakin
  • 8,657
  • 2
  • 29
  • 42