moses-palmer / pystray

GNU General Public License v3.0
461 stars 57 forks source link

Icon hang, can't destroy tkinter application #143

Closed emcek closed 1 year ago

emcek commented 1 year ago

I have similar short example as some other people. The problem is even I quit application (from system tray icon), root windows is destroy, but icon thread is still running (probably), so whole process hang. I need kill it manually. Do I miss something?

I use partial(), because GUI is crated in different part of my application and I can not use global state for tk.Tk()

from functools import partial
from pathlib import Path

from pystray import MenuItem, Icon
from PIL import Image
import tkinter as tk

def quit_window(window: tk.Tk, icon: Icon, *args, **kwargs):
    print('quit', args, kwargs)
    icon.visible = False
    icon.stop()
    window.destroy()

def show_window(window: tk.Tk, *args, **kwargs):
    print('show', args, kwargs)
    window.after(0, window.deiconify)

def withdraw_window(window: tk.Tk):
    window.withdraw()

def main():
    root = tk.Tk()
    root.title('DCSpy')
    image = Image.open(Path(__file__).resolve().with_name('dcspy.ico'))

    menu = (MenuItem('Quit', partial(quit_window, root)), MenuItem('Show', partial(show_window, root)))
    icon = Icon('dcspy', image, 'DCSpy', menu)
    icon.run_detached()
    root.protocol('WM_DELETE_WINDOW', partial(withdraw_window, root))
    root.mainloop()

if __name__ == '__main__':
    main()

I was thinking, may it is possible to add Event (from threading package) to get control over Icon thread. Then you can event.set() from outside and thread will be terminated.

emcek commented 1 year ago

solution is replace order of:

    root.protocol('WM_DELETE_WINDOW', partial(withdraw_window, root))
    icon.run_detached()

and change destroy() to quit()

def quit_window(window: tk.Tk, icon: Icon, *args, **kwargs):
    print('quit', args, kwargs)
    icon.visible = False
    icon.stop()
    window.quit()

Now quit_window() will close app properly.