-
Notifications
You must be signed in to change notification settings - Fork 63
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
Cannot catch KeyboardInterrupt on blocking icon.run() call #177
Comments
Thanks to #87 I was able to circumvent the issue for me: from PIL import Image, ImageOps, ImageDraw
import multiprocessing
import threading
icon = None
def gen_dot_img(size, color):
# generate an image and draw a pattern
image = Image.new('RGBA', (size, size), (255, 0, 0, 0))
dc = ImageDraw.Draw(image)
dc.circle((size // 2, size // 2), size // 5, fill=color)
return image
def main(shutdown_event):
while not shutdown_event.is_set():
print("Background Worker Running...")
def ui():
global icon
import pystray
icon = pystray.Icon(name, icon=gen_dot_img(64, 'orange'), menu=pystray.Menu(
pystray.MenuItem(
text="Exit", action=lambda: icon and icon.stop())
))
icon.run()
if __name__ == "__main__":
shutdown_event = threading.Event()
main_thread = threading.Thread(target=lambda: main(shutdown_event))
main_thread.start()
ui_process = multiprocessing.Process(target=ui)
ui_process.start()
try:
ui_process.join()
except KeyboardInterrupt:
pass
shutdown_event.set()
main_thread.join() So the key was to use a process for the UI instead of the thread, however I can only imagine this bringing serious limitations down the line, because now I would have to tackle process-to-process communication if I want more than a simple exit menu. Only using a separate process for the icon would allow me to run it detached and wait for it. Now my KeyboardInterrupt is triggered, I can ignore it and send my shutdown event. If I exit from the icon menu, it will just end the ui_process so its join() is satisfied and the shutdown event is also set. In both cases my main background thread can finally end gracefully. That's not what I should have to do, though, I hope we can find a fix for this, so I can just |
I just solved a very similiar problem. I hope my solution helps :) import subprocess
from threading import Thread
from PIL import Image, ImageDraw
from pystray import Icon, Menu, MenuItem
def gen_dot_img(size, color):
# generate an image and draw a pattern
image = Image.new('RGBA', (size, size), (255, 0, 0, 0))
dc = ImageDraw.Draw(image)
dc.circle((size // 2, size // 2), size // 5, fill=color)
return image
class DaemonThread(Thread):
def __init__(self, icon: Icon, name: str|None=None):
self.icon = icon
Thread.__init__(self, daemon=True, name=name, target=icon.run)
def join(self, timeout: float | None = None) -> None:
self.icon.stop()
return super().join(timeout)
def exit_app(icon: Icon, query):
"""
Callback for exiting KeyTA
:param icon: The object of the tray icon
:param query: The text that is displayed on the pressed menu item
"""
icon.stop()
process.terminate()
if __name__ == "__main__":
process = subprocess.Popen("irobot")
tray_icon = Icon(
name='TrayIcon',
icon=gen_dot_img(64, 'orange'),
menu=Menu(
MenuItem(
'Exit',
exit_app
)
)
)
icon_thread = DaemonThread(tray_icon)
icon_thread.start()
try:
process.wait()
except KeyboardInterrupt:
process.terminate() I took the idea for the DaemonThread from #99 . |
You can use From the base Icon class in _base.py . . .
From the Icon class in _win32.py which inherits the _base.Icon class.
_win32.py is a backend that is loaded in the main init.py if Windows is the OS on the computer. |
Hey :)
I want to use this in a tray-only script, so I attempt to block it. Some extra logic runs on a different thread that must be shutdown properly, so I have to catch ctrl+c, but it doesn't work:
The icon shows up, but hitting ctrl+c will crash icon.run and subsequently the entire process, icon.run does not raise KeyboardInterrupt or allows to continue operation, so all my sibling workers crash, too.
I tried to introduce signals, but icon.run is blocking on the main thread and thus takes precedence when sending the SIGINT. I also tried to spawn icon.run_detached in another thread, but this seems to only work if I have a mainloop in a GUI framework on the main thread, such as Tkinter, but I don't have that, so
icon.run()
should clearly be my mainloop.I'm testing this on MacOS.
The text was updated successfully, but these errors were encountered: