Files
BackOne/app-tray/backone-tray.py
T
2026-06-30 13:10:35 +07:00

128 lines
3.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""BackOne system tray — opens /app UI and addon deep links."""
import os
import signal
import subprocess
import sys
import threading
import urllib.request
import gi
gi.require_version('Gtk', '3.0')
try:
gi.require_version('AyatanaAppIndicator3', '0.1')
from gi.repository import AyatanaAppIndicator3 as AppIndicator3
except (ImportError, ValueError):
gi.require_version('AppIndicator3', '0.1')
from gi.repository import AppIndicator3
from gi.repository import GLib, Gtk
APP_URL = os.environ.get('BACKONE_APP_URL', 'http://127.0.0.1:9993/app/')
HEALTH_URL = os.environ.get('BACKONE_HEALTH_URL', 'http://127.0.0.1:9993/health')
ICON_NAME = 'network-workgroup'
class BackOneTray:
def __init__(self):
self._windows = []
self.indicator = AppIndicator3.Indicator.new(
'backone-tray',
ICON_NAME,
AppIndicator3.IndicatorCategory.APPLICATION_STATUS,
)
self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
self.indicator.set_menu(self._build_menu())
try:
self.indicator.connect('activate', lambda *_: self._open_url(APP_URL))
except TypeError:
pass
self._update_status()
GLib.timeout_add_seconds(30, self._on_timer)
def _build_menu(self):
menu = Gtk.Menu()
def add_item(label, callback):
item = Gtk.MenuItem(label=label)
item.connect('activate', lambda *_: callback())
item.show()
menu.append(item)
return item
add_item('Open BackOne App', lambda: self._open_url(APP_URL))
add_item('Voice', lambda: self._open_url(APP_URL + '#/voice'))
add_item('Video', lambda: self._open_url(APP_URL + '#/video'))
add_item('Chat', lambda: self._open_url(APP_URL + '#/chat'))
menu.append(Gtk.SeparatorMenuItem())
add_item('Restart BackOne Service', self._restart_service)
add_item('Quit Tray', Gtk.main_quit)
menu.show()
return menu
def _service_ok(self):
try:
with urllib.request.urlopen(HEALTH_URL, timeout=2) as resp:
return resp.status == 200
except Exception:
return False
def _update_status(self):
title = 'BackOne (online)' if self._service_ok() else 'BackOne (offline)'
self.indicator.set_title(title)
def _on_timer(self):
self._update_status()
return True
def _open_url(self, url):
if self._try_webkit(url):
return
for cmd in (
['xdg-open', url],
['gio', 'open', url],
['sensible-browser', url],
):
try:
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return
except OSError:
continue
def _try_webkit(self, url):
try:
gi.require_version('WebKit2', '4.1')
except ValueError:
try:
gi.require_version('WebKit2', '4.0')
except ValueError:
return False
from gi.repository import WebKit2
win = Gtk.Window(title='BackOne')
win.set_default_size(900, 640)
view = WebKit2.WebView()
view.load_uri(url)
win.add(view)
win.connect('destroy', lambda w: self._windows.remove(w) if w in self._windows else None)
self._windows.append(win)
win.show_all()
return True
def _restart_service(self):
subprocess.Popen(
['pkexec', 'systemctl', 'restart', 'backone'],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
BackOneTray()
Gtk.main()
if __name__ == '__main__':
sys.exit(main())