fix(tray): make PTT tray icon turn red on every hold (#809)

* fix(tray): refresh PTT icon when AppIndicator reuses a path

Right-Alt push-to-talk turned the tray red only on the first hold.
Later holds dictated fine but left the icon gray until restart.

StatusNotifier hosts often skip a redraw when IconName returns to an
absolute path already used in the session. Nudge IconThemePath before
set_icon_full so gray-red-gray-red actually turns red again.

* test(tray): assert alternating IconThemePath nudge args

Greptile P2: the PTT redraw regression only checked set_icon_theme_path
call count, so identical paths would still pass. Assert the alternating
ICON_DIR / ICON_DIR+os.sep arguments that drive the host reload.
This commit is contained in:
Jatin K Malik
2026-09-08 20:56:18 -07:00
committed by GitHub
parent ab1400b7c1
commit 94c2eaac08
2 changed files with 74 additions and 4 deletions
+27 -4
View File
@@ -161,6 +161,11 @@ class TrayIndicator:
self.icon_names["processing"] if FLATPAK_ID else self.icon_paths["processing"]
),
}
# Last IconName applied this session. StatusNotifier hosts skip a
# redraw when the same path is reused; _set_indicator_icon nudges
# IconThemePath so gray→red→gray→red actually turns red again.
self._last_icon_key: Optional[str] = None
self._icon_theme_nudge = False
# Register for speech recognition state changes
self.speech_engine.register_state_callback(self._on_recognition_state_changed)
@@ -691,6 +696,24 @@ class TrayIndicator:
self.speech_engine.end_download()
self._model_download_active = False
def _set_indicator_icon(self, icon_key: str, description: str) -> None:
"""Apply a tray icon, forcing a host redraw when cycling reused paths.
Ayatana/GNOME StatusNotifier hosts often skip a redraw when IconName
returns to a path already used earlier in the session. Alternating
IconThemePath between ICON_DIR and ICON_DIR + os.sep triggers a reload
while keeping absolute paths (theme names would regress the stale
~/.local/share/icons hicolor placeholder). Flatpak uses themed names
and does not need the nudge.
"""
if not FLATPAK_ID and self._last_icon_key is not None:
nudge = getattr(self.indicator, "set_icon_theme_path", None)
if callable(nudge):
self._icon_theme_nudge = not self._icon_theme_nudge
nudge(ICON_DIR + os.sep if self._icon_theme_nudge else ICON_DIR)
self.indicator.set_icon_full(icon_key, description)
self._last_icon_key = icon_key
def _update_ui(self, state: RecognitionState):
"""
Update the UI based on the recognition state.
@@ -708,19 +731,19 @@ class TrayIndicator:
state = engine_state
if state == RecognitionState.IDLE:
self.indicator.set_icon_full(self._icon_keys["default"], "Microphone off")
self._set_indicator_icon(self._icon_keys["default"], "Microphone off")
self._set_menu_item_enabled("Start Voice Typing", True)
self._set_menu_item_enabled("Stop Voice Typing", False)
elif state == RecognitionState.LISTENING:
self.indicator.set_icon_full(self._icon_keys["active"], "Microphone on")
self._set_indicator_icon(self._icon_keys["active"], "Microphone on")
self._set_menu_item_enabled("Start Voice Typing", False)
self._set_menu_item_enabled("Stop Voice Typing", True)
elif state == RecognitionState.PROCESSING:
self.indicator.set_icon_full(self._icon_keys["processing"], "Processing speech")
self._set_indicator_icon(self._icon_keys["processing"], "Processing speech")
self._set_menu_item_enabled("Start Voice Typing", False)
self._set_menu_item_enabled("Stop Voice Typing", True)
elif state == RecognitionState.ERROR:
self.indicator.set_icon_full(self._icon_keys["default"], "Error")
self._set_indicator_icon(self._icon_keys["default"], "Error")
self._set_menu_item_enabled("Start Voice Typing", True)
self._set_menu_item_enabled("Stop Voice Typing", False)