v2: tabs, chain builder, obfuscation modes, hop slider, Defender exclusion, firewall kept
Made-with: Cursor
This commit is contained in:
89
proxy_chain_manager/tray.py
Normal file
89
proxy_chain_manager/tray.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user