109 lines
3.2 KiB
Python
109 lines
3.2 KiB
Python
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""System-tray applet wiring. Imports pystray/PIL (the [tray] extra).
|
|
|
|
This module is the GUI shell only. All testable logic lives in state.py,
|
|
actions.py, and notify.py, which import no GUI libraries.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import threading
|
|
import time
|
|
|
|
import pystray
|
|
from PIL import Image, ImageDraw
|
|
|
|
from ..config import Config
|
|
from . import actions, notify
|
|
from .state import TrayState, parse_status
|
|
|
|
REFRESH_SECONDS = 15
|
|
|
|
_COLORS = {
|
|
"green": (46, 160, 67),
|
|
"amber": (210, 153, 34),
|
|
"grey": (110, 110, 110),
|
|
}
|
|
|
|
|
|
def _make_image(color: str) -> "Image.Image":
|
|
rgb = _COLORS.get(color, _COLORS["grey"])
|
|
img = Image.new("RGBA", (64, 64), (0, 0, 0, 0))
|
|
draw = ImageDraw.Draw(img)
|
|
draw.ellipse((8, 8, 56, 56), fill=rgb)
|
|
return img
|
|
|
|
|
|
class TrayApp:
|
|
def __init__(self, cfg: Config):
|
|
self.cfg = cfg
|
|
self.state: TrayState = parse_status(None)
|
|
self.icon = pystray.Icon(
|
|
"enodia-sentinel",
|
|
icon=_make_image(self.state.color),
|
|
title=self.state.tooltip,
|
|
menu=self._menu(),
|
|
)
|
|
|
|
def _menu(self) -> "pystray.Menu":
|
|
return pystray.Menu(
|
|
pystray.MenuItem(lambda _i: self.state.summary, None, enabled=False),
|
|
pystray.Menu.SEPARATOR,
|
|
pystray.MenuItem("Open dashboard", self._on_open),
|
|
pystray.Menu.SEPARATOR,
|
|
pystray.MenuItem("Start daemon", self._on_start),
|
|
pystray.MenuItem("Stop daemon", self._on_stop),
|
|
pystray.MenuItem("Restart daemon", self._on_restart),
|
|
pystray.Menu.SEPARATOR,
|
|
pystray.MenuItem("Run quick check", self._on_check),
|
|
pystray.Menu.SEPARATOR,
|
|
pystray.MenuItem("Quit", self._on_quit),
|
|
)
|
|
|
|
def _async(self, fn) -> None:
|
|
threading.Thread(target=fn, daemon=True).start()
|
|
|
|
def _do(self, action) -> None:
|
|
res = action()
|
|
notify.notify("Enodia Sentinel", res.message)
|
|
self.refresh()
|
|
|
|
def _on_open(self, _icon=None, _item=None):
|
|
self._async(lambda: self._do(lambda: actions.open_dashboard(self.cfg)))
|
|
|
|
def _on_start(self, _icon=None, _item=None):
|
|
self._async(lambda: self._do(actions.start_daemon))
|
|
|
|
def _on_stop(self, _icon=None, _item=None):
|
|
self._async(lambda: self._do(actions.stop_daemon))
|
|
|
|
def _on_restart(self, _icon=None, _item=None):
|
|
self._async(lambda: self._do(actions.restart_daemon))
|
|
|
|
def _on_check(self, _icon=None, _item=None):
|
|
self._async(lambda: self._do(actions.run_check))
|
|
|
|
def _on_quit(self, _icon=None, _item=None):
|
|
self.icon.stop()
|
|
|
|
def refresh(self) -> None:
|
|
self.state = parse_status(actions.fetch_status())
|
|
self.icon.icon = _make_image(self.state.color)
|
|
self.icon.title = self.state.tooltip
|
|
self.icon.update_menu()
|
|
|
|
def _poll_loop(self) -> None:
|
|
while True:
|
|
self.refresh()
|
|
time.sleep(REFRESH_SECONDS)
|
|
|
|
def run(self) -> None:
|
|
def _setup(icon):
|
|
icon.visible = True
|
|
threading.Thread(target=self._poll_loop, daemon=True).start()
|
|
self.icon.run(setup=_setup)
|
|
|
|
|
|
def main(argv=None) -> int:
|
|
cfg = Config.load()
|
|
TrayApp(cfg).run()
|
|
return 0
|