#!/usr/bin/env python3 """ PhoneStream Tray — иконка в системном трее. Запуск: python3 phonestream-tray.py Зависимости: gir1.2-appindicator3-0.1 (sudo apt install gir1.2-appindicator3-0.1) """ import subprocess import threading import signal import gi gi.require_version('Gtk', '3.0') gi.require_version('AppIndicator3', '0.1') from gi.repository import Gtk, GLib from gi.repository import AppIndicator3 as AppIndicator SCRIPT = "/home/nikolas/bin/phonestream/phonestream.sh" PHONE_ID = "c3c2c293" PROFILES = [ (0, "▶ Максимум (5MP 4:3)", "start", "0"), (1, "▶ Широкий (4MP 16:9)", "start", "1"), (2, "▶ 2.5K (5MP 4:3)", "start", "2"), (3, "▶ Full HD", "start", "3"), (4, "▶ Full HD 60fps", "start", "4"), (5, "▶ HD 120fps (slow-mo)", "start", "5"), (6, "▶ HD 240fps (slow-mo)", "start", "6"), ] def is_stream_active(): try: r = subprocess.run(["pgrep", "-f", "scrcpy.*v4l2-sink"], capture_output=True, text=True, timeout=2) return r.returncode == 0 except Exception: return False def is_phone_connected(): try: r = subprocess.run(["adb", "devices"], capture_output=True, text=True, timeout=3) return PHONE_ID in r.stdout except Exception: return False def run_script(args): def _run(): try: subprocess.run([SCRIPT] + args, capture_output=True, text=True, timeout=15) except Exception: pass threading.Thread(target=_run, daemon=True).start() def make_menu(): phone_ok = is_phone_connected() stream_active = is_stream_active() menu = Gtk.Menu() # Статус phone_item = Gtk.MenuItem(label=f"📱 Телефон: {'Подключён' if phone_ok else 'Нет'}") phone_item.set_sensitive(False) menu.append(phone_item) stream_item = Gtk.MenuItem(label=f"📷 Стрим: {'Активен ●' if stream_active else 'Стоп ○'}") stream_item.set_sensitive(False) menu.append(stream_item) menu.append(Gtk.SeparatorMenuItem()) if not stream_active: # Запуск с профилем for idx, label, action, profile_idx in PROFILES: item = Gtk.MenuItem(label=label) item.connect("activate", lambda i, p=profile_idx: run_script(["start", p])) menu.append(item) else: # Остановка stop_item = Gtk.MenuItem(label="⏹ Остановить") stop_item.connect("activate", lambda i: run_script(["stop"])) menu.append(stop_item) # Перезапуск menu.append(Gtk.SeparatorMenuItem()) for idx, label, action, profile_idx in PROFILES: restart_label = label.replace("▶", "🔄") item = Gtk.MenuItem(label=restart_label) item.connect("activate", lambda i, p=profile_idx: run_script(["restart", p])) menu.append(item) menu.append(Gtk.SeparatorMenuItem()) status_item = Gtk.MenuItem(label="📊 Статус") status_item.connect("activate", lambda i: run_script(["status"])) menu.append(status_item) quit_item = Gtk.MenuItem(label="✕ Выход из трея") quit_item.connect("activate", lambda i: Gtk.main_quit()) menu.append(quit_item) menu.show_all() return menu def main(): indicator = AppIndicator.Indicator.new( "phonestream", "camera-web", AppIndicator.IndicatorCategory.HARDWARE ) indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE) indicator.set_title("PhoneStream") indicator.set_menu(make_menu()) # Обновление меню каждые 3 сек def refresh(ind): ind.set_menu(make_menu()) return True GLib.timeout_add_seconds(3, refresh, indicator) signal.signal(signal.SIGINT, signal.SIG_DFL) Gtk.main() if __name__ == "__main__": main()