1

I wonder if there is there a way to test/check if tkinter mainloop is running somehow? Issuing multiple tk.Tk().mainloop() commands will break the GUI, but I haven't found a way to check if mainloop has been started for the tk._default_root?

martineau
  • 119,623
  • 25
  • 170
  • 301
JacobP
  • 561
  • 3
  • 21
  • 1
    Since it's your code that is starting the mainloop, have you tried setting a flag to know when you've started it? Though, you should be calling it exactly once so it's not clear why you need to know if it's running or not. – Bryan Oakley Mar 31 '21 at 19:10
  • @Bryan Oakley I'm trying to write a library/framework which should be able to be used both with the event loop running or start it itself it not, so it would be convenient if a run check would be possible (as I can check that tk._default_root exists but not if it has been started or not) – JacobP Apr 01 '21 at 06:33

3 Answers3

2

Annoyingly, while Tk().willdispatch() exists (though it appears undocumented) to lie and say the mainloop is running when it isn't (I've used it to allow an asyncio task to interlace asyncio events with tkinter events), there is no Python-level API to query the underlying flag (C level struct member dispatching) it sets.

The only place the flag is tested is in functions that automatically marshal calls from non-main threads to the mainloop thread, and it's not practical to use this as a mechanism of detection (it requires spawning threads solely to perform the test, involves a timeout, and throws an exception when it fails, making it ugly even if you could make it work).

In short, it's going to be on you. A solution you might use would be to make a subclass that intercepts the call to mainloop and records that it has been called:

class CheckableTk(Tk):
    def __init__(self, *args, **kwargs):
        self.running = False
        super().__init__(*args, **kwargs)

    def mainloop(self, *args, **kwargs):
        self.running = True
        try:
            return super().mainloop(*args, **kwargs)
        finally:
            self.running = False

    def willdispatch(self, *args, **kwargs):
        self.running = True  # Lie just like willdispatch lies
        return super().willdispatch(*args, **kwargs)

It's not a great solution, and I'd discourage it in general. The real answer is, have one, and only one, single place that runs the mainloop, instead of splitting up the possible launch points all over your program.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
0

Posting a threading based solution from matplotlib source code:

     lib/matplotlib/backends/_backend_tk.py

def is_tk_mainloop_running():
    import threading
    dispatching = sentinel = object()

    def target():
        nonlocal dispatching
        try:
            tk._default_root.call('while', '0', '{}')
        except RuntimeError as e:
            if str(e) == "main thread is not in main loop":
                print('not dispatching')
                dispatching = False
            else:
                raise
        except BaseException as e:
            print(e)
            raise
        else:
            dispatching = True
            print('dispatching')

    t = threading.Thread(target=target, daemon=True)
    t.start()
    tk._default_root.update()
    t.join()

    if dispatching is sentinel:
        raise RuntimeError('thread failed to determine dispatching value')

    return dispatching
martineau
  • 119,623
  • 25
  • 170
  • 301
JacobP
  • 561
  • 3
  • 21
-3

If you know the process name, in Windows, you can use psutil to check if a process is running.

Import psutill:

import psutil

build a function to check if a process is running:

def is_running(name: str) -> bool:
    name = name if name.endswith('.exe') else name + '.exe'

    return name in [process.name() for process in psutil.process_iter()]

or a function to kill a process:

def kill(name: str) -> bool:
    name = name if name.endswith('.exe') else name + '.exe'

    for process in psutil.process_iter():
        if process.name() == name:
            process.kill()
            return True

    return False

and then you can simply use them like this

if __name__ == "__main__":
    app_name = 'chrome'

    if is_running(name=app_name):
        if kill(name=app_name):
            print(f'{app_name} killed!')

    else:
        print(f'{app_name} is not running!')
Funpy97
  • 282
  • 2
  • 9
  • Literally none of this answers the OP's question. The `tkinter` `mainloop` may or may not be running at any time during the lifetime of the process. – ShadowRanger Apr 01 '21 at 00:45