1313import logging
1414import shutil
1515import subprocess
16- import tempfile
1716from pathlib import Path
1817from typing import Any
1918
@@ -172,73 +171,13 @@ def _appindicator_install_hint() -> str:
172171
173172_POLL_INTERVAL_SECONDS = 5
174173
175- # Fallback themed icons used for notifications (these don't appear in panel)
176- _NOTIFY_ICON_LOCKED = "system-lock-screen-symbolic"
177- _NOTIFY_ICON_UNLOCKED = "security-high-symbolic"
178- _NOTIFY_ICON_DISCONNECTED = "network-offline-symbolic"
179-
180- # ---------------------------------------------------------------------------
181- # Icon generation — SVG icons using absolute paths
182- # ---------------------------------------------------------------------------
183-
184- # We write SVG files to a temp directory and pass their *absolute paths*
185- # (without extension) as icon names to AppIndicator3. This is the most
186- # reliable approach across desktop environments — it avoids icon theme
187- # lookup issues where the tray (a separate process) cannot find the icons.
188- #
189- # The SVGs use a medium grey (#5a5a5a) fill which is visible on both dark
190- # and light panels. On dark panels it appears as a mid-tone; on light
191- # panels it's clearly dark enough to see.
192-
193- _LOCKED_SVG = """\
194- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
195- viewBox="0 0 24 24">
196- <g fill="none" stroke="#5a5a5a" stroke-width="1.8"
197- stroke-linecap="round" stroke-linejoin="round">
198- <path d="M8 11V7a4 4 0 0 1 8 0v4"/>
199- <rect x="5" y="11" width="14" height="10" rx="2"
200- fill="#5a5a5a" stroke="none"/>
201- </g>
202- </svg>"""
203-
204- _UNLOCKED_SVG = """\
205- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
206- viewBox="0 0 24 24">
207- <g fill="none" stroke="#5a5a5a" stroke-width="1.8"
208- stroke-linecap="round" stroke-linejoin="round">
209- <path d="M5 12l1.2 7.5A2 2 0 0 0 8.2 21h7.6a2 2 0 0 0 2-1.5L19 12z"/>
210- <path d="M5 12L12 3l7 9"/>
211- <path d="M9.5 15.5l2 2 3.5-4.5"/>
212- </g>
213- </svg>"""
214-
215- _DISCONNECTED_SVG = """\
216- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
217- <g fill="none" stroke="#5a5a5a" stroke-width="1.8" stroke-linecap="round">
218- <circle cx="12" cy="12" r="9"/>
219- <line x1="5.6" y1="5.6" x2="18.4" y2="18.4"/>
220- </g>
221- </svg>"""
222-
223-
224- def _create_icon_dir () -> tuple [Path , str , str , str ]:
225- """Create a temp directory with SVG icons and return absolute paths.
226-
227- Returns ``(icon_dir, locked_path, unlocked_path, disconnected_path)``
228- where each ``*_path`` is an absolute path **without** the ``.svg``
229- extension, suitable for passing directly to ``set_icon_full``.
230- """
231- icon_dir = Path (tempfile .mkdtemp (prefix = "bwssh-icons-" ))
232-
233- locked = icon_dir / "bwssh-locked"
234- unlocked = icon_dir / "bwssh-unlocked"
235- disconnected = icon_dir / "bwssh-disconnected"
236-
237- locked .with_suffix (".svg" ).write_text (_LOCKED_SVG )
238- unlocked .with_suffix (".svg" ).write_text (_UNLOCKED_SVG )
239- disconnected .with_suffix (".svg" ).write_text (_DISCONNECTED_SVG )
240-
241- return icon_dir , str (locked ), str (unlocked ), str (disconnected )
174+ # Symbolic icon names from the freedesktop icon theme. The ``-symbolic``
175+ # suffix tells the desktop environment to recolor them automatically for
176+ # the current panel theme (dark or light) — no manual theme detection
177+ # or embedded SVGs needed.
178+ _ICON_LOCKED = "system-lock-screen-symbolic"
179+ _ICON_UNLOCKED = "security-high-symbolic"
180+ _ICON_DISCONNECTED = "network-offline-symbolic"
242181
243182
244183# ---------------------------------------------------------------------------
@@ -280,19 +219,11 @@ def __init__(self, socket_path: Path) -> None:
280219 except Exception :
281220 logger .debug ("Failed to initialise libnotify" , exc_info = True )
282221
283- # Generate SVG icons and get their absolute paths (without extension)
284- (
285- self ._icon_dir ,
286- self ._icon_locked ,
287- self ._icon_unlocked ,
288- self ._icon_disconnected ,
289- ) = _create_icon_dir ()
290-
291- # Build the indicator — use absolute path so AppIndicator finds the
292- # icon immediately without needing an icon theme lookup.
222+ # Build the indicator using a symbolic icon name — the desktop
223+ # environment recolors -symbolic icons for the panel theme.
293224 self ._indicator = AppIndicator3 .Indicator .new (
294225 "bwssh" ,
295- self . _icon_disconnected ,
226+ _ICON_DISCONNECTED ,
296227 AppIndicator3 .IndicatorCategory .APPLICATION_STATUS ,
297228 )
298229 self ._indicator .set_status (AppIndicator3 .IndicatorStatus .ACTIVE )
@@ -362,13 +293,13 @@ def _notify_state_change(
362293 if not prev_connected and self ._connected :
363294 if self ._locked :
364295 self ._send_notification (
365- "Agent Connected" , "Vault is locked" , _NOTIFY_ICON_LOCKED
296+ "Agent Connected" , "Vault is locked" , _ICON_LOCKED
366297 )
367298 else :
368299 self ._send_notification (
369300 "Agent Connected" ,
370301 f"Vault is unlocked ({ self ._key_count } keys)" ,
371- _NOTIFY_ICON_UNLOCKED ,
302+ _ICON_UNLOCKED ,
372303 )
373304 return
374305
@@ -377,7 +308,7 @@ def _notify_state_change(
377308 self ._send_notification (
378309 "Agent Disconnected" ,
379310 "Daemon is not running" ,
380- _NOTIFY_ICON_DISCONNECTED ,
311+ _ICON_DISCONNECTED ,
381312 )
382313 return
383314
@@ -386,15 +317,13 @@ def _notify_state_change(
386317 self ._send_notification (
387318 "Vault Unlocked" ,
388319 f"{ self ._key_count } SSH key(s) loaded" ,
389- _NOTIFY_ICON_UNLOCKED ,
320+ _ICON_UNLOCKED ,
390321 )
391322 return
392323
393324 # Unlocked -> Locked
394325 if self ._connected and prev_locked is False and self ._locked is True :
395- self ._send_notification (
396- "Vault Locked" , "SSH keys cleared" , _NOTIFY_ICON_LOCKED
397- )
326+ self ._send_notification ("Vault Locked" , "SSH keys cleared" , _ICON_LOCKED )
398327
399328 def _send_notification (self , summary : str , body : str , icon : str ) -> None :
400329 """Show a desktop notification via libnotify."""
@@ -409,11 +338,11 @@ def _send_notification(self, summary: str, body: str, icon: str) -> None:
409338 def _update_icon (self ) -> None :
410339 """Set the tray icon based on current state."""
411340 if not self ._connected :
412- icon = self . _icon_disconnected
341+ icon = _ICON_DISCONNECTED
413342 elif self ._locked :
414- icon = self . _icon_locked
343+ icon = _ICON_LOCKED
415344 else :
416- icon = self . _icon_unlocked
345+ icon = _ICON_UNLOCKED
417346
418347 self ._indicator .set_icon_full (icon , self ._status_text ())
419348
@@ -510,6 +439,4 @@ def _on_quit(self, _item: Any) -> None:
510439 """Exit the tray application."""
511440 if self ._notifications_enabled :
512441 _Notify .uninit ()
513- # Clean up generated icon files
514- shutil .rmtree (self ._icon_dir , ignore_errors = True )
515442 Gtk .main_quit ()
0 commit comments