Source code for flexget.tray_icon
from __future__ import annotations
import logging
import webbrowser
from functools import partial, wraps
from pathlib import Path
from typing import TYPE_CHECKING
from loguru import logger
from flexget import __version__
if TYPE_CHECKING:
from collections.abc import Callable
logger = logger.bind(name='tray_icon')
try:
# If we are running outside of a graphical environment, these imports will fail
from PIL import Image
from pystray import Icon, Menu, MenuItem
_import_success = True
except Exception as e:
logger.debug('Could not load tray icon: {}', e)
_import_success = False
[docs]
def check_if_tray_is_active(f):
@wraps(f)
def wrapped(self, *args, **kwargs):
if not self.active:
return None
return f(self, *args, **kwargs)
return wrapped
image_path = Path(__file__).parent / 'resources' / 'flexget.png'
[docs]
class TrayIcon:
def __init__(self, path_to_image: Path = image_path):
# Silence PIL noisy logging
logging.getLogger('PIL.PngImagePlugin').setLevel(logging.INFO)
logging.getLogger('PIL.Image').setLevel(logging.INFO)
self.path_to_image: Path = path_to_image
self.icon: Icon | None = None
self._menu: Menu | None = None
self.menu_items: list[MenuItem] = []
self.active: bool = _import_success
self.running: bool = False
self.add_core_menu_items()
@property
def menu(self) -> Menu:
# This is lazy loaded since we'd like to delay the menu build until the tray is requested to run
if not self._menu:
self._menu = Menu(*self.menu_items)
return self._menu
[docs]
@check_if_tray_is_active
def run(self):
"""Run the tray icon. Must be run from the main thread and is blocking."""
try:
logger.verbose('Starting tray icon')
self.icon = Icon('Flexget', Image.open(self.path_to_image), menu=self.menu)
self.running = True
self.icon.run()
except Exception as e:
logger.warning('Could not run tray icon: {}', e)
self.running = False
[docs]
@check_if_tray_is_active
def stop(self):
if not self.running:
return
logger.verbose('Stopping tray icon')
self.icon.stop()
self.running = False
tray_icon = TrayIcon()