Files
proxy-god/proxy_chain_manager/tray.py

90 lines
2.5 KiB
Python

"""System-tray icon: green = healthy chain, red = broken/stopped, yellow = connecting."""
from __future__ import annotations
import threading
from typing import Any, Callable
import pystray
from PIL import Image, ImageDraw
def _circle_icon(color: str, size: int = 64) -> Image.Image:
img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
pad = 4
draw.ellipse([pad, pad, size - pad, size - pad], fill=color)
return img
ICONS = {
"green": _circle_icon("#00e676"),
"red": _circle_icon("#ff1744"),
"yellow": _circle_icon("#ffab00"),
"gray": _circle_icon("#6c757d"),
}
TIPS = {
"green": "Proxy Chain: healthy",
"red": "Proxy Chain: broken / stopped",
"yellow": "Proxy Chain: connecting…",
"gray": "Proxy Chain: idle",
}
class TrayIcon:
def __init__(
self,
on_show: Callable[[], None],
on_quit: Callable[[], None],
on_rotate: Callable[[], None],
) -> None:
self._on_show = on_show
self._on_quit = on_quit
self._on_rotate = on_rotate
self._icon: pystray.Icon | None = None
self._thread: threading.Thread | None = None
self._state = "gray"
def start(self) -> None:
if self._thread and self._thread.is_alive():
return
menu = pystray.Menu(
pystray.MenuItem("Show", self._show, default=True),
pystray.MenuItem("Rotate now", self._rotate),
pystray.Menu.SEPARATOR,
pystray.MenuItem("Quit", self._quit),
)
self._icon = pystray.Icon(
"ProxyChainManager",
icon=ICONS[self._state],
title=TIPS[self._state],
menu=menu,
)
self._thread = threading.Thread(target=self._icon.run, daemon=True)
self._thread.start()
def stop(self) -> None:
if self._icon:
try:
self._icon.stop()
except Exception:
pass
def set_state(self, state: str) -> None:
"""state: 'green', 'red', 'yellow', 'gray'."""
if state not in ICONS:
state = "gray"
self._state = state
if self._icon:
self._icon.icon = ICONS[state]
self._icon.title = TIPS[state]
def _show(self, icon: Any = None, item: Any = None) -> None:
self._on_show()
def _rotate(self, icon: Any = None, item: Any = None) -> None:
self._on_rotate()
def _quit(self, icon: Any = None, item: Any = None) -> None:
self._on_quit()