linux observer
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

sni: expose IconAccessibleDesc and AttentionAccessibleDesc

Modern SNI hosts such as the GNOME AppIndicator extension query IconAccessibleDesc and AttentionAccessibleDesc as optional properties. When they are absent, the host ends up logging missing-property warnings even though the rest of the indicator works.

Expose both properties on the StatusNotifierItem and update them from tray state so they track the current semantic status: recording, paused, idle, syncing, error, or stopped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+54
+20
src/solstone_linux/sni.py
··· 35 35 self._status = "Active" # Passive, Active, NeedsAttention 36 36 self._title = "solstone observer" 37 37 self._icon_name = "solstone-recording" 38 + self._icon_accessible_desc = "" 38 39 self._attention_icon_name = "" 40 + self._attention_accessible_desc = "" 39 41 self._overlay_icon_name = "" 40 42 self._tooltip_icon = "" 41 43 self._tooltip_title = "solstone observer" ··· 56 58 self._icon_name = icon_name 57 59 self.NewIcon() 58 60 61 + def set_icon_accessible_desc(self, desc: str): 62 + if self._icon_accessible_desc != desc: 63 + self._icon_accessible_desc = desc 64 + self.emit_properties_changed({"IconAccessibleDesc": desc}) 65 + 59 66 def set_status(self, status: str): 60 67 """Set Active, Passive, or NeedsAttention.""" 61 68 if self._status != status: ··· 78 85 self._attention_icon_name = icon_name 79 86 self.NewAttentionIcon() 80 87 88 + def set_attention_accessible_desc(self, desc: str): 89 + if self._attention_accessible_desc != desc: 90 + self._attention_accessible_desc = desc 91 + self.emit_properties_changed({"AttentionAccessibleDesc": desc}) 92 + 81 93 def set_overlay_icon(self, icon_name: str): 82 94 self._overlay_icon_name = icon_name 83 95 self.NewOverlayIcon() ··· 109 121 return self._icon_name 110 122 111 123 @dbus_property(access=PropertyAccess.READ) 124 + def IconAccessibleDesc(self) -> "s": 125 + return self._icon_accessible_desc 126 + 127 + @dbus_property(access=PropertyAccess.READ) 112 128 def IconPixmap(self) -> "a(iiay)": 113 129 return [] 114 130 ··· 123 139 @dbus_property(access=PropertyAccess.READ) 124 140 def AttentionIconName(self) -> "s": 125 141 return self._attention_icon_name 142 + 143 + @dbus_property(access=PropertyAccess.READ) 144 + def AttentionAccessibleDesc(self) -> "s": 145 + return self._attention_accessible_desc 126 146 127 147 @dbus_property(access=PropertyAccess.READ) 128 148 def AttentionIconPixmap(self) -> "a(iiay)":
+22
src/solstone_linux/tray.py
··· 124 124 125 125 # Set initial icon 126 126 self.sni.set_icon(ICONS["recording"]) 127 + self._update_accessible_descriptions() 127 128 self.sni.set_tooltip("solstone observer", "starting...") 128 129 129 130 # Build menu ··· 392 393 self.sni.set_status("NeedsAttention") 393 394 else: 394 395 self.sni.set_status("Active") 396 + self._update_accessible_descriptions() 395 397 396 398 log.info(f"Status -> {status} (icon: {icon})") 397 399 ··· 427 429 self.sni.set_icon(ICONS.get(self.status, ICONS["recording"])) 428 430 429 431 self.sni.set_tooltip("solstone observer", self._build_tooltip()) 432 + self._update_accessible_descriptions() 430 433 431 434 def _update_live_stats(self, segment_timer: int, pause_remaining: int): 432 435 """Update the live stats in the status submenu.""" ··· 488 491 parts.append(self.error) 489 492 490 493 return "\n".join(parts) 494 + 495 + def _update_accessible_descriptions(self): 496 + if self.error: 497 + desc = "Solstone observer — error" 498 + elif self.sync_status in ("syncing", "uploading", "retrying"): 499 + desc = "Solstone observer — syncing" 500 + elif self.status == "paused": 501 + desc = "Solstone observer — paused" 502 + elif self.status == "idle": 503 + desc = "Solstone observer — idle" 504 + elif self.status == "stopped": 505 + desc = "Solstone observer — stopped" 506 + else: 507 + desc = "Solstone observer — recording" 508 + if self.config.stream: 509 + desc = f"{desc} ({self.config.stream})" 510 + 511 + self.sni.set_icon_accessible_desc(desc) 512 + self.sni.set_attention_accessible_desc(desc) 491 513 492 514 # ── Menu callbacks ── 493 515
+12
tests/test_tray.py
··· 11 11 12 12 from solstone_linux.config import Config 13 13 from solstone_linux.dbusmenu import MenuItem, separator 14 + from solstone_linux.sni import StatusNotifierItem 14 15 from solstone_linux.tray import ( 15 16 AGENT_INSTRUCTIONS, 16 17 ICONS, ··· 289 290 tooltip = app._build_tooltip() 290 291 291 292 assert "sync: 2/5" in tooltip 293 + 294 + 295 + class TestStatusNotifierItem: 296 + def test_accessible_desc_properties(self): 297 + sni = StatusNotifierItem() 298 + 299 + sni.set_icon_accessible_desc("Solstone observer — recording") 300 + sni.set_attention_accessible_desc("Solstone observer — recording") 301 + 302 + assert sni.IconAccessibleDesc == "Solstone observer — recording" 303 + assert sni.AttentionAccessibleDesc == "Solstone observer — recording" 292 304 293 305 294 306 class TestUpdate: