linux observer
0
fork

Configure Feed

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

Refresh tray immediately on pause/resume and during pause

The in-process SNI tray was frozen once the observer entered a
paused state: the two call sites that previously drove tray
refreshes (post-boundary mode change and end-of-normal-iteration)
are unreachable from the paused branch, so the icon, status
submenu, and pause/resume menu items never reflected the new state.

Add a small Observer._refresh_tray() helper that wraps the
existing try/except + disable pattern, and invoke it from three
new points:
- Observer.pause() — immediate icon/menu flip when a pause is
initiated from the tray.
- Observer.resume() — immediate flip back on manual resume.
- Paused branch of main_loop() — runs once per 5s tick so the
"resume (N minutes remaining)" label counts down live and any
sync-status change during pause is reflected.

The three existing tray-update sites migrate to the same helper
for consistency (no behavior change at those sites).

Not manually smoke-tested on a GNOME Wayland session in this
change — coverage relies on the new pause/resume unit tests and
code review.

Co-Authored-By: Codex <noreply@openai.com>

+38 -24
+16 -24
src/solstone_linux/observer.py
··· 439 439 stream=self.stream, 440 440 ) 441 441 442 + def _refresh_tray(self): 443 + """Refresh the SNI tray UI. Safe when tray is unavailable; disables on failure.""" 444 + if self._tray is None: 445 + return 446 + try: 447 + self._tray.update() 448 + except Exception: 449 + logger.warning("Tray update failed, disabling tray", exc_info=True) 450 + self._tray = None 451 + 442 452 def pause(self, duration_seconds: int): 443 453 """Pause capture. duration_seconds=0 means indefinite.""" 444 454 self._paused = True ··· 449 459 if self._dbus_service: 450 460 self._dbus_service.StatusChanged("paused") 451 461 logger.info("Paused for %ss", duration_seconds) 462 + self._refresh_tray() 452 463 453 464 def resume(self): 454 465 """Resume capture from pause.""" ··· 459 470 "recording" if self.current_mode == MODE_SCREENCAST else "idle" 460 471 ) 461 472 logger.info("Resumed") 473 + self._refresh_tray() 462 474 463 475 async def main_loop(self): 464 476 """Run the main observer loop with background sync.""" ··· 519 531 else "idle" 520 532 ) 521 533 logger.info("Auto-resumed from timed pause") 522 - if self._tray: 523 - try: 524 - self._tray.update() 525 - except Exception: 526 - logger.warning( 527 - "Tray update failed, disabling tray", exc_info=True 528 - ) 529 - self._tray = None 534 + self._refresh_tray() 530 535 531 536 # Handle paused state 532 537 if self._paused: ··· 548 553 self._sync.trigger() 549 554 self.audio_recorder.get_buffers() 550 555 self.emit_status() 556 + self._refresh_tray() 551 557 continue 552 558 553 559 # Resume: start new segment if needed (segment_dir is None after pause) ··· 642 648 if mode_changed and self._dbus_service: 643 649 status = "recording" if new_mode == MODE_SCREENCAST else "idle" 644 650 self._dbus_service.StatusChanged(status) 645 - if self._tray: 646 - try: 647 - self._tray.update() 648 - except Exception: 649 - logger.warning( 650 - "Tray update failed, disabling tray", exc_info=True 651 - ) 652 - self._tray = None 651 + self._refresh_tray() 653 652 654 653 # Emit status event 655 654 self.emit_status() 656 - if self._tray: 657 - try: 658 - self._tray.update() 659 - except Exception: 660 - logger.warning( 661 - "Tray update failed, disabling tray", exc_info=True 662 - ) 663 - self._tray = None 655 + self._refresh_tray() 664 656 finally: 665 657 # Cleanup on exit 666 658 logger.info("Observer loop stopped, cleaning up...")
+22
tests/test_observer.py
··· 56 56 57 57 assert hasattr(observer, "_paused") 58 58 assert hasattr(observer, "_pause_until") 59 + 60 + def test_pause_refreshes_tray(self, tmp_path: Path): 61 + from unittest.mock import MagicMock 62 + 63 + config = Config(base_dir=tmp_path) 64 + observer = Observer(config) 65 + observer._tray = MagicMock() 66 + 67 + observer.pause(900) 68 + 69 + assert observer._tray.update.called is True 70 + 71 + def test_resume_refreshes_tray(self, tmp_path: Path): 72 + from unittest.mock import MagicMock 73 + 74 + config = Config(base_dir=tmp_path) 75 + observer = Observer(config) 76 + observer._tray = MagicMock() 77 + 78 + observer.resume() 79 + 80 + assert observer._tray.update.called is True