Infinidat / infi.systray

Windows system tray icon
BSD 3-Clause "New" or "Revised" License
216 stars 40 forks source link

SysTrayIcon.shutdown produces threading join exception #32

Closed Ari24-cb24 closed 3 years ago

Ari24-cb24 commented 3 years ago

Hey there,

Same issue as the one 2 years ago,

SystrayIcon.shutdown() produces a threading join error.


  File "C:\Users\bobmy\AppData\Local\Programs\Python\Python37\lib\site-packages\infi\systray\traybar.py", line 123, in shutdown
    self._message_loop_thread.join()
  File "C:\Users\bobmy\AppData\Local\Programs\Python\Python37\lib\threading.py", line 1041, in join
    raise RuntimeError("cannot join current thread")
RuntimeError: cannot join current thread```

Greets
Ari24-cb24 commented 3 years ago

Found out, that this only happens, when passing on_quit into SystrayIcon()

Ari24-cb24 commented 3 years ago

Fixed it with removing

menu_options = menu_options + (('Quit', None, SysTrayIcon.QUIT),)

in traybar.py and also building my own closing function with

import win32_adapter

menu = (
        ("Open", "./img/icon.ico", lambda st: print("Test")),
        ("Exit", None, self.__shutdown),
)

    self.stray = SysTrayIcon("./img/icon.ico", "Name", menu_options=menu)

def __shutdown(self, *args):
    win32_adapter.DestroyWindow(self.stray._hwnd)
    print("CLOSE")
    sys.exit(0)
pythonmcpi commented 3 years ago

Using SysTrayIcon.shutdown() in the on_quit will error because the event monitor thread calls your on_quit function, which calls the shutdown function, which tries the join the event loop thread (to wait for it to exit before returning). But if a thread joins itself to wait for it to exit, it would block forever, so the threading module just raises an exception. A way to fix this error without directly editing module code is to signal another thread to run the shutdown function. In my case, I was able to use the main thread, and just tell it to block until it gets the signal. TL;DR Call the shutdown() function in another thread to avoid the exception

Ari24-cb24 commented 3 years ago

Hey there again, sorry for the late answer,

I already tried calling the shutdown() function in another thread and I can't block the main Thread because I have other things running on the Mainthread.

But yeah, thanks for answering

dibakarbose commented 1 year ago

https://github.com/Infinidat/infi.systray/issues/32#issuecomment-723479818

Call the shutdown() function in another thread to avoid the exception

Hi @pythonmcpi , I know this is very late comment but I am facing a similar situation.

Can you please provide a code example with respect to your comment. That would be really helpful

pythonmcpi commented 1 year ago

@dibakarbose I can try looking for my original project, but I don't know if I'll be able to find it. I have too many abandoned projects :(

EDIT So I somehow found it in the first project folder I looked in...

The code below is from a project that I ended up abandoning on November 7, 2020, exactly 2 hours and 5 minutes after writing my original comment. It probably requires an icon.ico file to be present in the directory. (My original ico file can be found here - Github doesn't let me attach .ico files) I have no idea if my old code works or not, and the code quality isn't great.

Good luck.

#import pystray
import contextlib
with contextlib.redirect_stdout(None):
    import pygame
import sys
import subprocess
import os
from PIL import Image, ImageDraw
from infi.systray import SysTrayIcon as sti
import time

#from pystray import Icon as picon, Menu as pmenu, MenuItem as pitem

class Launcher(object):
    def __init__(self, width=860, height=680):
##        self.icon = picon('Launcher', self.generate_icon(), menu=pmenu(
##            pitem(
##                'Quit',
##                self.quit
##                )
##            )
##                          )
        self.width = width
        self.height = height
        self.hidden = False
        self.menu = (("Toggle Visibility", None, lambda e: self.toggle_visible()),)
        self.icon = sti("icon.ico", "Launcher", self.menu, on_quit=lambda e: self.quit())
        pygame.init()

    def run(self):
        self.running = True
        #self.icon.run()
        self.icon.start()
        self.screen = pygame.display.set_mode((self.width, self.height), pygame.NOFRAME)
        self.mainloop()

    def generate_icon(self):
        # Checkerboard Pattern
        width = 32
        height = 32
        color1 = (255, 255, 255)
        color2 = (0, 0, 0)
        icon = Image.new('RGB', (width, height), color1)
        dc = ImageDraw.Draw(icon)
        dc.rectangle(
            (width // 2, 0, width, height // 2),
            fill=color2)
        dc.rectangle(
            (0, height // 2, width // 2, height),
            fill=color2)

        return icon

    def toggle_visible(self):
        if not self.running:
            return
        self.hidden = not self.hidden
        if self.hidden:
            pygame.display.iconify()
        else:
            pygame.display.deiconify()

    def mainloop(self):
        while self.running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:

        self.icon.shutdown()

    def quit(self):
        self.running = False
        #self.icon.stop()
        #self.icon.shutdown() # Shutdown has a line that joins the event monitor thread, but this function is called by the monitor thread, causing a RuntimeError. This line can be put in mainloop (which runs on main thread)
        pygame.quit()

if __name__ == "__main__":
    launcher = Launcher()
    launcher.run()
    #launcher.generate_icon().save("icon.ico", "ICO") # Save icon file