Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error with systray.shutdown() #26

Open
RayneDance opened this issue Jul 4, 2020 · 9 comments
Open

Error with systray.shutdown() #26

RayneDance opened this issue Jul 4, 2020 · 9 comments

Comments

@RayneDance
Copy link

Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 237, in 'calling callback function'
  File "C:\Users\jorda\AppData\Local\Programs\Python\Python38-32\lib\site-packages\infi\systray\traybar.py", line 79, in WndProc
    self._message_dict[msg](hwnd, msg, wparam.value, lparam.value)
  File "C:\Users\jorda\AppData\Local\Programs\Python\Python38-32\lib\site-packages\infi\systray\traybar.py", line 195, in _destroy
    self._on_quit(self)
  File "d:/coding/python/mouse-milage/mouse-milage.py", line 9, in qcallback
    systray.shutdown()
  File "C:\Users\jorda\AppData\Local\Programs\Python\Python38-32\lib\site-packages\infi\systray\traybar.py", line 123, in shutdown
    self._message_loop_thread.join()
  File "C:\Users\jorda\AppData\Local\Programs\Python\Python38-32\lib\threading.py", line 1008, in join
    raise RuntimeError("cannot join current thread")
RuntimeError: cannot join current thread

Code:

import win32gui
from infi.systray import SysTrayIcon
from time import sleep
import sys

def say_hello(systray):
    print("Hello, World!")

def qcallback(systray):
    systray.shutdown()
    sys.exit()

menu_options = (("Say Hello", None, say_hello),)
systray = SysTrayIcon("icon.ico", "Example tray icon", menu_options, on_quit=qcallback)
systray.start()

while 1:

    i = 1

Not sure what happens, but this also appears to thoroughly bone the parent process. Terminal will accept ctrl+x but then no further input.

@RayneDance
Copy link
Author

Error appears to be in traybar.py, 123:

    def shutdown(self):
        if not self._hwnd:
            return      # not started
        PostMessage(self._hwnd, WM_CLOSE, 0, 0)
        self._message_loop_thread.join()

I got around it by just catching the runtime exception and moving on with my life, but I'm curious if this is intended functionality, or if maybe that should be wrapped in a try/except RuntimeException block?

@abgrac
Copy link

abgrac commented Jul 9, 2020

Same here.
Annoying is that the tray keeps visible after catching exception and closing the script.
Nice lib anyway! Thanks

@Ari24-cb24
Copy link

Here is a way to get around it:
#32

@burbilog
Copy link

Here is a way to get around it:
#32

Wow, thanks for the hint to use win32_adapter.DestroyWindow(self.stray._hwnd), this is the only way I was able to remove dead icons from the tray.

@sotpotatis
Copy link

Is there any plan to fix this officially or?

@wiggin15
Copy link
Contributor

From what I understand, this issue happens when calling shutdown from the on_quit callback. There is no need to do that because on_quit is called when the tray bar icon shuts down anyway (it's called when the program receives the WM_DESTROY message, normally through DestroyWindow, which is what clicking "Quit" calls).

The example in the original description will "work" if not calling systray.shutdown(). There is another problem with the example, which is that sys.exit() will not terminate the program, because it's not called in the primary thread. In fact, calling sys.exit terminates the traybar thread abruptly, interfering with the destroy process and causing the icon to be left in the tray. In other words, sys.exit isn't the right way to go about exiting the program. One will have to signal the main thread to exit (e.g. using threading.Event.

If there is a use case I'm missing or a different way we can get this traceback, please post a complete and minimal reproducible example.

@sotpotatis
Copy link

From what I understand, this issue happens when calling shutdown from the on_quit callback. There is no need to do that because on_quit is called when the tray bar icon shuts down anyway (it's called when the program receives the WM_DESTROY message, normally through DestroyWindow, which is what clicking "Quit" calls).

The example in the original description will "work" if not calling systray.shutdown(). There is another problem with the example, which is that sys.exit() will not terminate the program, because it's not called in the primary thread. In fact, calling sys.exit terminates the traybar thread abruptly, interfering with the destroy process and causing the icon to be left in the tray. In other words, sys.exit isn't the right way to go about exiting the program. One will have to signal the main thread to exit (e.g. using threading.Event.

If there is a use case I'm missing or a different way we can get this traceback, please post a complete and minimal reproducible example.

I see. Makes sense. What I want to do is shut down several processes when someone presses "Quit" on the traybar - some websocket connections. But, I'm showing the systray by using the "with"-method. So technically, when I press quit, the "with" statement will be broken out of, and I can just put my "shutdown websocket connections" code at the bottom of my code, after the "with" statement?

@wiggin15
Copy link
Contributor

So technically, when I press quit, the "with" statement will be broken out of, and I can just put my "shutdown websocket connections" code at the bottom of my code, after the "with" statement?

No and yes. "Quit" doesn't cause the main thread to do anything by itself, so it will not cause an exit from the "with" statement. It only shuts down the icon thread. If you use the "with" statement in your main thread, it means that if you exit from the block, the icon gets shut down. So if you don't "listen" for Quit from the main thread, the program may simply exit the block, kill the icon without user interaction and then get to the cleanup part. i.e. you still need to do some sort of signaling from on_quit and it's up to the main program to not exit from the "with" block and have some "main loop" part. The main thread can either finish by itself and clear the icon (by using shutdown/exiting with) or finish when on_quit signals something.
I hope it's not too confusing.

Here's an example of such signaling:

from infi.systray import SysTrayIcon
from threading import Event

quit_event = Event()

def say_hello(systray):
    print("Hello, World!")

def qcallback(systray):
    quit_event.set()

menu_options = (("Say Hello", None, say_hello),)
systray = SysTrayIcon("icon.ico", "Example tray icon", menu_options, on_quit=qcallback)

with systray:
    # main program goes here - normally with some sort of loop
    # For this example we just wait until time passes or the quit event is set
    print('I will quit in 30 seconds, unless "Quit" is selected from the tray bar icon')
    quit_event.wait(timeout=30)     # returns True if quit_event is set and False if not and timeout reached

# "shutdown" is called by the exit from the with block
# It's safe to call "shutdown" whether quit was pressed (so icon is already destroyed) or not.

# cleanup goes here
print("ended properly!")

@thestampr
Copy link

thestampr commented Sep 23, 2021

You need to shutdown from main thread like:

systray.start()
main() #do_something here
systray.shutdown() #stop systray before exit
exit(0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants