diff --git a/EDMarketConnector.py b/EDMarketConnector.py index f019f8f9..30ddc857 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -9,6 +9,7 @@ import locale import pathlib import re import sys +import threading import webbrowser from builtins import object, str from os import chdir, environ @@ -263,6 +264,7 @@ if __name__ == '__main__': # noqa: C901 if TYPE_CHECKING: from logging import trace, TRACE # type: ignore # noqa: F401 import update + from infi.systray import SysTrayIcon # isort: on def _(x: str) -> str: @@ -330,6 +332,17 @@ class AppWindow(object): self.prefsdialog = None + if platform == 'win32': + from infi.systray import SysTrayIcon + + def open_window(systray: 'SysTrayIcon') -> None: + self.w.deiconify() + + menu_options = (("Open", None, open_window),) + # Method associated with on_quit is called whenever the systray is closing + self.systray = SysTrayIcon("EDMarketConnector.ico", applongname, menu_options, on_quit=self.exit_tray) + self.systray.start() + plug.load_plugins(master) if platform != 'darwin': @@ -1371,8 +1384,19 @@ class AppWindow(object): logger.debug('"other" exception', exc_info=e) self.status['text'] = str(e) + def exit_tray(self, systray: 'SysTrayIcon') -> None: + """Tray icon is shutting down.""" + exit_thread = threading.Thread(target=self.onexit) + exit_thread.setDaemon(True) + exit_thread.start() + def onexit(self, event=None) -> None: """Application shutdown procedure.""" + if platform == 'win32': + shutdown_thread = threading.Thread(target=self.systray.shutdown) + shutdown_thread.setDaemon(True) + shutdown_thread.start() + config.set_shutdown() # Signal we're in shutdown now. # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 @@ -1439,12 +1463,17 @@ class AppWindow(object): self.drag_offset = (None, None) def oniconify(self, event=None) -> None: - """Handle iconification of the application.""" - self.w.overrideredirect(0) # Can't iconize while overrideredirect - self.w.iconify() - self.w.update_idletasks() # Size and windows styles get recalculated here - self.w.wait_visibility() # Need main window to be re-created before returning - theme.active = None # So theme will be re-applied on map + """Handle minimization of the application.""" + value = config.get_bool('minimize_system_tray') + if platform == 'win32' and value is not None and value: + self.w.withdraw() + + else: + self.w.overrideredirect(0) # Can't iconize while overrideredirect + self.w.iconify() + self.w.update_idletasks() # Size and windows styles get recalculated here + self.w.wait_visibility() # Need main window to be re-created before returning + theme.active = None # So theme will be re-applied on map # TODO: Confirm this is unused and remove. def onmap(self, event=None) -> None: diff --git a/L10n/en.template b/L10n/en.template index dd54a762..c7379ba7 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -19,6 +19,9 @@ /* Appearance setting. [EDMarketConnector.py] */ "Always on top" = "Always on top"; +/* Appearance setting. [EDMarketConnector.py] */ +"Minimize to system tray" = "Minimize to system tray"; + /* CQC rank. [stats.py] */ "Amateur" = "Amateur"; diff --git a/prefs.py b/prefs.py index 81c850c8..119b727f 100644 --- a/prefs.py +++ b/prefs.py @@ -636,6 +636,7 @@ class PreferencesDialog(tk.Toplevel): # Appearance theme and language setting self.lang = tk.StringVar(value=self.languages.get(config.get_str('language'), _('Default'))) self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop'))) + self.minimize_system_tray = tk.BooleanVar(value=config.get_bool('minimize_system_tray')) self.theme = tk.IntVar(value=config.get_int('theme')) self.theme_colors = [config.get_str('dark_text'), config.get_str('dark_highlight')] self.theme_prompts = [ @@ -791,6 +792,14 @@ class PreferencesDialog(tk.Toplevel): ) self.ontop_button.grid(columnspan=3, padx=self.BUTTONX, sticky=tk.W, row=row.get()) # Appearance setting + if platform == 'win32': + nb.Checkbutton( + appearance_frame, + text=_('Minimize to system tray'), + variable=self.minimize_system_tray, + command=self.themevarchanged + ).grid(columnspan=3, padx=self.BUTTONX, sticky=tk.W, row=row.get()) # Appearance setting + nb.Label(appearance_frame).grid(sticky=tk.W) # big spacer notebook.add(appearance_frame, text=_('Appearance')) # Tab heading in settings @@ -1163,6 +1172,7 @@ class PreferencesDialog(tk.Toplevel): config.set('ui_scale', self.ui_scale.get()) config.set('ui_transparency', self.transparency.get()) config.set('always_ontop', self.always_ontop.get()) + config.set('minimize_system_tray', self.minimize_system_tray.get()) config.set('theme', self.theme.get()) config.set('dark_text', self.theme_colors[0]) config.set('dark_highlight', self.theme_colors[1]) diff --git a/requirements.txt b/requirements.txt index f7c83c73..cc3d8c4d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ certifi==2020.12.5 requests==2.25.1 watchdog==2.0.3 +infi.systray==0.1.12; sys_platform == 'win32' # argh==0.26.2 watchdog dep # pyyaml==5.3.1 watchdog dep semantic-version==2.8.5