Implement a dynamic tray icon status picture

This commit is contained in:
DevilXD
2024-07-01 21:20:27 +02:00
parent 14a9154366
commit 35c46c2b78
10 changed files with 35 additions and 8 deletions

View File

@@ -19,7 +19,7 @@ script:
# Package the app. # Package the app.
- mkdir -p "$TARGET_APPDIR"/usr/{src,share/icons/hicolor/128x128/apps} - 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" - cp "$SOURCE_DIR/pickaxe.png" "$TARGET_APPDIR/usr/share/icons/hicolor/128x128/apps/io.github.devilxd.twitchdropsminer.png"
# Install requirements. # Install requirements.

View File

@@ -17,7 +17,12 @@ if TYPE_CHECKING:
# (source_path, dest_path, required) # (source_path, dest_path, required)
to_add: list[tuple[Path, str, bool]] = [ 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 # SeleniumWire HTTPS/SSL cert file and key
(Path(SITE_PACKAGES_PATH, "seleniumwire/ca.crt"), "./seleniumwire", False), (Path(SITE_PACKAGES_PATH, "seleniumwire/ca.crt"), "./seleniumwire", False),
(Path(SITE_PACKAGES_PATH, "seleniumwire/ca.key"), "./seleniumwire", False), (Path(SITE_PACKAGES_PATH, "seleniumwire/ca.key"), "./seleniumwire", False),
@@ -99,10 +104,10 @@ exe = EXE(
console=False, console=False,
upx_exclude=[], upx_exclude=[],
target_arch=None, target_arch=None,
icon="pickaxe.ico",
runtime_tmpdir=None, runtime_tmpdir=None,
codesign_identity=None, codesign_identity=None,
entitlements_file=None, entitlements_file=None,
icon="icons/pickaxe.ico",
bootloader_ignore_signals=False, bootloader_ignore_signals=False,
disable_windowed_traceback=False, disable_windowed_traceback=False,
name="Twitch Drops Miner (by DevilXD)", name="Twitch Drops Miner (by DevilXD)",

25
gui.py
View File

@@ -1044,13 +1044,21 @@ class TrayIcon:
def __init__(self, manager: GUIManager, master: ttk.Widget): def __init__(self, manager: GUIManager, master: ttk.Widget):
self._manager = manager self._manager = manager
self.icon: pystray.Icon | None = None 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 = ttk.Button(master, command=self.minimize, text=_("gui", "tray", "minimize"))
self._button.grid(column=0, row=0, sticky="ne") self._button.grid(column=0, row=0, sticky="ne")
def __del__(self) -> None: def __del__(self) -> None:
self.stop() 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: def _shorten(self, text: str, by_len: int, min_len: int) -> str:
if (text_len := len(text)) <= min_len + 3 or by_len <= 0: if (text_len := len(text)) <= min_len + 3 or by_len <= 0:
@@ -1096,7 +1104,9 @@ class TrayIcon:
pystray.Menu.SEPARATOR, pystray.Menu.SEPARATOR,
pystray.MenuItem(_("gui", "tray", "quit"), bridge(self.quit)), 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() # self.icon.run_detached()
loop.run_in_executor(None, self.icon.run) loop.run_in_executor(None, self.icon.run)
@@ -1142,6 +1152,13 @@ class TrayIcon:
if self.icon is not None: if self.icon is not None:
self.icon.title = self.get_title(drop) 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: class Notebook:
def __init__(self, manager: GUIManager, master: ttk.Widget): def __init__(self, manager: GUIManager, master: ttk.Widget):
@@ -1937,7 +1954,7 @@ class GUIManager:
# withdraw immediately to prevent the window from flashing # withdraw immediately to prevent the window from flashing
self._root.withdraw() self._root.withdraw()
# root.resizable(False, True) # 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.title(WINDOW_TITLE) # window title
root.bind_all("<KeyPress-Escape>", self.unfocus) # pressing ESC unfocuses selection root.bind_all("<KeyPress-Escape>", self.unfocus) # pressing ESC unfocuses selection
# Image cache for displaying images # Image cache for displaying images

BIN
icons/active.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
icons/error.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
icons/idle.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
icons/maint.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -87,7 +87,7 @@ if __name__ == "__main__":
root = tk.Tk() root = tk.Tk()
root.overrideredirect(True) root.overrideredirect(True)
root.withdraw() root.withdraw()
set_root_icon(root, resource_path("pickaxe.ico")) set_root_icon(root, resource_path("icons/pickaxe.ico"))
root.update() root.update()
parser = Parser( parser = Parser(
SELF_PATH.name, SELF_PATH.name,
@@ -168,6 +168,7 @@ if __name__ == "__main__":
await client.shutdown() await client.shutdown()
if not client.gui.close_requested: if not client.gui.close_requested:
# user didn't request the closure # user didn't request the closure
client.gui.tray.change_icon("error")
client.print(_("status", "terminated")) client.print(_("status", "terminated"))
client.gui.status.update(_("gui", "status", "terminated")) client.gui.status.update(_("gui", "status", "terminated"))
# notify the user about the closure # notify the user about the closure

View File

@@ -608,11 +608,13 @@ class Twitch:
if self.settings.dump: if self.settings.dump:
self.gui.close() self.gui.close()
continue continue
self.gui.tray.change_icon("idle")
self.gui.status.update(_("gui", "status", "idle")) self.gui.status.update(_("gui", "status", "idle"))
self.stop_watching() self.stop_watching()
# clear the flag and wait until it's set again # clear the flag and wait until it's set again
self._state_change.clear() self._state_change.clear()
elif self._state is State.INVENTORY_FETCH: elif self._state is State.INVENTORY_FETCH:
self.gui.tray.change_icon("maint")
# ensure the websocket is running # ensure the websocket is running
await self.websocket.start() await self.websocket.start()
await self.fetch_inventory() await self.fetch_inventory()
@@ -835,6 +837,7 @@ class Twitch:
self.change_state(State.IDLE) self.change_state(State.IDLE)
del new_watching, selected_channel, watching_channel del new_watching, selected_channel, watching_channel
elif self._state is State.EXIT: elif self._state is State.EXIT:
self.gui.tray.change_icon("pickaxe")
self.gui.status.update(_("gui", "status", "exiting")) self.gui.status.update(_("gui", "status", "exiting"))
# we've been requested to exit the application # we've been requested to exit the application
break break
@@ -982,6 +985,7 @@ class Twitch:
) )
def watch(self, channel: Channel, *, update_status: bool = True): def watch(self, channel: Channel, *, update_status: bool = True):
self.gui.tray.change_icon("active")
self.gui.channels.set_watching(channel) self.gui.channels.set_watching(channel)
self.watching_channel.set(channel) self.watching_channel.set(channel)
if update_status: if update_status: