diff --git a/appimage/AppImageBuilder.yml b/appimage/AppImageBuilder.yml index ba718333..284f47bd 100644 --- a/appimage/AppImageBuilder.yml +++ b/appimage/AppImageBuilder.yml @@ -19,7 +19,7 @@ script: # Package the app. - mkdir -p "$TARGET_APPDIR"/usr/{src,share/icons/hicolor/128x128/apps} - - cp -r "$SOURCE_DIR/../lang" "$SOURCE_DIR/../pickaxe.ico" "$SOURCE_DIR"/../*.py "$TARGET_APPDIR/usr/src" + - cp -r "$SOURCE_DIR/../lang" "$SOURCE_DIR/../icons" "$SOURCE_DIR"/../*.py "$TARGET_APPDIR/usr/src" - cp "$SOURCE_DIR/pickaxe.png" "$TARGET_APPDIR/usr/share/icons/hicolor/128x128/apps/io.github.devilxd.twitchdropsminer.png" # Install requirements. diff --git a/build.spec b/build.spec index e14aac63..4ff72244 100644 --- a/build.spec +++ b/build.spec @@ -17,7 +17,12 @@ if TYPE_CHECKING: # (source_path, dest_path, required) to_add: list[tuple[Path, str, bool]] = [ - (Path("pickaxe.ico"), '.', True), # icon file + # icon files + (Path("icons/pickaxe.ico"), "./icons", True), + (Path("icons/active.ico"), "./icons", True), + (Path("icons/idle.ico"), "./icons", True), + (Path("icons/error.ico"), "./icons", True), + (Path("icons/maint.ico"), "./icons", True), # SeleniumWire HTTPS/SSL cert file and key (Path(SITE_PACKAGES_PATH, "seleniumwire/ca.crt"), "./seleniumwire", False), (Path(SITE_PACKAGES_PATH, "seleniumwire/ca.key"), "./seleniumwire", False), @@ -99,10 +104,10 @@ exe = EXE( console=False, upx_exclude=[], target_arch=None, - icon="pickaxe.ico", runtime_tmpdir=None, codesign_identity=None, entitlements_file=None, + icon="icons/pickaxe.ico", bootloader_ignore_signals=False, disable_windowed_traceback=False, name="Twitch Drops Miner (by DevilXD)", diff --git a/gui.py b/gui.py index 5af35dc5..12cba140 100644 --- a/gui.py +++ b/gui.py @@ -1044,13 +1044,21 @@ class TrayIcon: def __init__(self, manager: GUIManager, master: ttk.Widget): self._manager = manager self.icon: pystray.Icon | None = None - self.icon_image = Image_module.open(resource_path("pickaxe.ico")) + self._icon_images: dict[str, Image_module.Image] = { + "pickaxe": Image_module.open(resource_path("icons/pickaxe.ico")), + "active": Image_module.open(resource_path("icons/active.ico")), + "idle": Image_module.open(resource_path("icons/idle.ico")), + "error": Image_module.open(resource_path("icons/error.ico")), + "maint": Image_module.open(resource_path("icons/maint.ico")), + } + self._icon_state: str = "pickaxe" self._button = ttk.Button(master, command=self.minimize, text=_("gui", "tray", "minimize")) self._button.grid(column=0, row=0, sticky="ne") def __del__(self) -> None: self.stop() - self.icon_image.close() + for icon_image in self._icon_images.values(): + icon_image.close() def _shorten(self, text: str, by_len: int, min_len: int) -> str: if (text_len := len(text)) <= min_len + 3 or by_len <= 0: @@ -1096,7 +1104,9 @@ def bridge(func): pystray.Menu.SEPARATOR, pystray.MenuItem(_("gui", "tray", "quit"), bridge(self.quit)), ) - self.icon = pystray.Icon("twitch_miner", self.icon_image, self.get_title(drop), menu) + self.icon = pystray.Icon( + "twitch_miner", self._icon_images[self._icon_state], self.get_title(drop), menu + ) # self.icon.run_detached() loop.run_in_executor(None, self.icon.run) @@ -1142,6 +1152,13 @@ def update_title(self, drop: TimedDrop | None): if self.icon is not None: self.icon.title = self.get_title(drop) + def change_icon(self, state: str): + if state not in self._icon_images: + raise ValueError("Invalid icon state") + self._icon_state = state + if self.icon is not None: + self.icon.icon = self._icon_images[state] + class Notebook: def __init__(self, manager: GUIManager, master: ttk.Widget): @@ -1937,7 +1954,7 @@ def __init__(self, twitch: Twitch): # withdraw immediately to prevent the window from flashing self._root.withdraw() # root.resizable(False, True) - set_root_icon(root, resource_path("pickaxe.ico")) + set_root_icon(root, resource_path("icons/pickaxe.ico")) root.title(WINDOW_TITLE) # window title root.bind_all("", self.unfocus) # pressing ESC unfocuses selection # Image cache for displaying images diff --git a/icons/active.ico b/icons/active.ico new file mode 100644 index 00000000..a0e3471b Binary files /dev/null and b/icons/active.ico differ diff --git a/icons/error.ico b/icons/error.ico new file mode 100644 index 00000000..9cd1ccd1 Binary files /dev/null and b/icons/error.ico differ diff --git a/icons/idle.ico b/icons/idle.ico new file mode 100644 index 00000000..7e91e101 Binary files /dev/null and b/icons/idle.ico differ diff --git a/icons/maint.ico b/icons/maint.ico new file mode 100644 index 00000000..e306840f Binary files /dev/null and b/icons/maint.ico differ diff --git a/pickaxe.ico b/icons/pickaxe.ico similarity index 100% rename from pickaxe.ico rename to icons/pickaxe.ico diff --git a/main.py b/main.py index f192ee9e..eb6bd85a 100644 --- a/main.py +++ b/main.py @@ -87,7 +87,7 @@ def debug_gql(self) -> int: root = tk.Tk() root.overrideredirect(True) root.withdraw() - set_root_icon(root, resource_path("pickaxe.ico")) + set_root_icon(root, resource_path("icons/pickaxe.ico")) root.update() parser = Parser( SELF_PATH.name, @@ -168,6 +168,7 @@ async def main(): await client.shutdown() if not client.gui.close_requested: # user didn't request the closure + client.gui.tray.change_icon("error") client.print(_("status", "terminated")) client.gui.status.update(_("gui", "status", "terminated")) # notify the user about the closure diff --git a/twitch.py b/twitch.py index 5436001f..975e3413 100644 --- a/twitch.py +++ b/twitch.py @@ -608,11 +608,13 @@ async def _run(self): if self.settings.dump: self.gui.close() continue + self.gui.tray.change_icon("idle") self.gui.status.update(_("gui", "status", "idle")) self.stop_watching() # clear the flag and wait until it's set again self._state_change.clear() elif self._state is State.INVENTORY_FETCH: + self.gui.tray.change_icon("maint") # ensure the websocket is running await self.websocket.start() await self.fetch_inventory() @@ -835,6 +837,7 @@ async def _run(self): self.change_state(State.IDLE) del new_watching, selected_channel, watching_channel elif self._state is State.EXIT: + self.gui.tray.change_icon("pickaxe") self.gui.status.update(_("gui", "status", "exiting")) # we've been requested to exit the application break @@ -982,6 +985,7 @@ def should_switch(self, channel: Channel) -> bool: ) def watch(self, channel: Channel, *, update_status: bool = True): + self.gui.tray.change_icon("active") self.gui.channels.set_watching(channel) self.watching_channel.set(channel) if update_status: