From 03c4e3f0ae0c5190749fee6e71ed16a00100f24c Mon Sep 17 00:00:00 2001 From: GaimsDevSoftware Date: Mon, 29 Jun 2026 18:05:37 +0200 Subject: [PATCH] Use libappindicator for the tray so it works on GNOME/Wayland The tray relied on Gtk.StatusIcon, which is a no-op on GNOME and on Wayland generally, so `--tray` / `--start-hidden` produced a hidden window and an invisible icon (the app looked dead). Prefer a StatusNotifierItem via libappindicator (AyatanaAppIndicator3, falling back to AppIndicator3), which GNOME shows through the AppIndicator extension; keep Gtk.StatusIcon as a fallback for desktops where it still works. The tray menu is shared between both paths. Packaging: add libappindicator-gtk3 (RPM) / gir1.2-ayatanaappindicator3-0.1|gir1.2-appindicator3-0.1 (deb) as a dependency, and recommend gnome-shell-extension-appindicator on GNOME. Co-Authored-By: Claude Opus 4.8 --- scripts/build-deb | 4 ++-- scripts/build-rpm | 2 ++ src/tellykeys/app.py | 46 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/scripts/build-deb b/scripts/build-deb index 0d45f3d..311735c 100755 --- a/scripts/build-deb +++ b/scripts/build-deb @@ -50,8 +50,8 @@ Section: utils Priority: optional Architecture: all Maintainer: GaimsDevSoftware -Depends: python3 (>= 3.11), python3-gi, gir1.2-gtk-3.0, avahi-utils, python3-cryptography, python3-protobuf, pipewire-bin | pulseaudio-utils -Recommends: android-tools-adb +Depends: python3 (>= 3.11), python3-gi, gir1.2-gtk-3.0, avahi-utils, python3-cryptography, python3-protobuf, pipewire-bin | pulseaudio-utils, gir1.2-ayatanaappindicator3-0.1 | gir1.2-appindicator3-0.1 +Recommends: android-tools-adb, gnome-shell-extension-appindicator Description: Google TV and Android TV remote for Linux TellyKeys is a GTK desktop remote for Google TV and Android TV with pairing, D-pad controls, app shortcuts, text input, and experimental voice search. diff --git a/scripts/build-rpm b/scripts/build-rpm index c0dd7ad..66939af 100755 --- a/scripts/build-rpm +++ b/scripts/build-rpm @@ -65,7 +65,9 @@ Requires: avahi-tools Requires: python3-cryptography Requires: python3-protobuf Requires: pipewire-utils +Requires: libappindicator-gtk3 Recommends: android-tools +Recommends: gnome-shell-extension-appindicator %description TellyKeys is a GTK desktop remote for Google TV and Android TV with pairing, diff --git a/src/tellykeys/app.py b/src/tellykeys/app.py index 361bccf..1da35a8 100644 --- a/src/tellykeys/app.py +++ b/src/tellykeys/app.py @@ -2,6 +2,7 @@ import argparse import asyncio +import importlib import os import socket import threading @@ -175,6 +176,8 @@ def __init__(self, use_tray: bool = False, start_hidden: bool = False) -> None: self.current_status: TvStatus | None = None self.use_tray = use_tray self.tray_icon: Gtk.StatusIcon | None = None + self.app_indicator = None + self._tray_show_item: Gtk.MenuItem | None = None self.remote = TellyKeysRemote() self.remote.set_microphone_source(self.settings.microphone_source) self.remote.set_client_name(self.settings.remote_name or default_remote_name()) @@ -1762,6 +1765,35 @@ def launch(self, target: str) -> None: self.run_remote_call(self.remote.launch, target) def setup_tray(self) -> None: + # Prefer libappindicator (StatusNotifierItem). The legacy Gtk.StatusIcon + # is a no-op on GNOME and on Wayland generally, so it is only a fallback + # for desktops where it still works (e.g. some X11 setups). + if self._setup_app_indicator(): + return + self._setup_status_icon() + + def _setup_app_indicator(self) -> bool: + for name in ("AyatanaAppIndicator3", "AppIndicator3"): + try: + gi.require_version(name, "0.1") + ind = importlib.import_module(f"gi.repository.{name}") + except (ValueError, ImportError): + continue + self.app_indicator = ind.Indicator.new( + "tellykeys", + "tellykeys", + ind.IndicatorCategory.APPLICATION_STATUS, + ) + self.app_indicator.set_status(ind.IndicatorStatus.ACTIVE) + self.app_indicator.set_title("TellyKeys") + self.app_indicator.set_menu(self._build_tray_menu()) + # Where the host supports it, a middle-click toggles the window. + if self._tray_show_item is not None: + self.app_indicator.set_secondary_activate_target(self._tray_show_item) + return True + return False + + def _setup_status_icon(self) -> None: self.tray_icon = Gtk.StatusIcon.new_from_icon_name("tellykeys") self.tray_icon.set_title("TellyKeys") self.tray_icon.set_tooltip_text("TellyKeys") @@ -1769,10 +1801,7 @@ def setup_tray(self) -> None: self.tray_icon.connect("activate", self.on_tray_activate) self.tray_icon.connect("popup-menu", self.on_tray_popup) - def on_tray_activate(self, _icon: Gtk.StatusIcon) -> None: - self.toggle_window() - - def on_tray_popup(self, _icon: Gtk.StatusIcon, button: int, activate_time: int) -> None: + def _build_tray_menu(self) -> Gtk.Menu: menu = Gtk.Menu() items = [ ("Show TellyKeys", lambda *_: self.show_window()), @@ -1787,7 +1816,16 @@ def on_tray_popup(self, _icon: Gtk.StatusIcon, button: int, activate_time: int) item = Gtk.MenuItem(label=label) item.connect("activate", callback) menu.append(item) + if label == "Show TellyKeys": + self._tray_show_item = item menu.show_all() + return menu + + def on_tray_activate(self, _icon: Gtk.StatusIcon) -> None: + self.toggle_window() + + def on_tray_popup(self, _icon: Gtk.StatusIcon, button: int, activate_time: int) -> None: + menu = self._build_tray_menu() menu.popup(None, None, None, None, button, activate_time) def toggle_window(self) -> None: