synodic-client 0.0.1.dev81__tar.gz → 0.0.1.dev82__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/PKG-INFO +2 -2
  2. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/pyproject.toml +3 -3
  3. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/tray.py +21 -4
  4. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_tray_window_show.py +30 -0
  5. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/LICENSE.md +0 -0
  6. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/README.md +0 -0
  7. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/__init__.py +0 -0
  8. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/__main__.py +0 -0
  9. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/__init__.py +0 -0
  10. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/bootstrap.py +0 -0
  11. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/config_store.py +0 -0
  12. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/data.py +0 -0
  13. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/debug.py +0 -0
  14. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/icon.py +0 -0
  15. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/init.py +0 -0
  16. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/instance.py +0 -0
  17. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/package_state.py +0 -0
  18. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/qt.py +0 -0
  19. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/schema.py +0 -0
  20. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/__init__.py +0 -0
  21. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/action_card.py +0 -0
  22. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/card.py +0 -0
  23. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/install.py +0 -0
  24. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/install_workers.py +0 -0
  25. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/log_panel.py +0 -0
  26. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/plugin_row.py +0 -0
  27. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/projects.py +0 -0
  28. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/schema.py +0 -0
  29. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/screen.py +0 -0
  30. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/settings.py +0 -0
  31. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/sidebar.py +0 -0
  32. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/spinner.py +0 -0
  33. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/tool_update_controller.py +0 -0
  34. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/update_banner.py +0 -0
  35. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/screen/wsl.py +0 -0
  36. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/theme.py +0 -0
  37. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/update_controller.py +0 -0
  38. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/update_model.py +0 -0
  39. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/application/uri.py +0 -0
  40. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/cli/__init__.py +0 -0
  41. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/cli/config.py +0 -0
  42. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/cli/context.py +0 -0
  43. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/cli/debug.py +0 -0
  44. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/cli/install.py +0 -0
  45. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/cli/output.py +0 -0
  46. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/cli/project.py +0 -0
  47. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/cli/tool.py +0 -0
  48. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/cli/update.py +0 -0
  49. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/client.py +0 -0
  50. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/config.py +0 -0
  51. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/logging.py +0 -0
  52. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/operations/__init__.py +0 -0
  53. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/operations/bootstrap.py +0 -0
  54. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/operations/config.py +0 -0
  55. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/operations/install.py +0 -0
  56. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/operations/project.py +0 -0
  57. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/operations/schema.py +0 -0
  58. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/operations/tool.py +0 -0
  59. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/operations/update.py +0 -0
  60. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/protocol.py +0 -0
  61. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/py.typed +0 -0
  62. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/resolution.py +0 -0
  63. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/schema.py +0 -0
  64. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/startup.py +0 -0
  65. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/subprocess_patch.py +0 -0
  66. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/synodic_client/updater.py +0 -0
  67. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/__init__.py +0 -0
  68. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/conftest.py +0 -0
  69. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/__init__.py +0 -0
  70. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/operations/__init__.py +0 -0
  71. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/operations/test_config.py +0 -0
  72. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/operations/test_install.py +0 -0
  73. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/operations/test_install_plan.py +0 -0
  74. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/operations/test_project.py +0 -0
  75. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/operations/test_tool.py +0 -0
  76. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/operations/test_update.py +0 -0
  77. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/__init__.py +0 -0
  78. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/conftest.py +0 -0
  79. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_action_card.py +0 -0
  80. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_gather_packages.py +0 -0
  81. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_install_preview.py +0 -0
  82. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_log_panel.py +0 -0
  83. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_logging.py +0 -0
  84. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_package_state.py +0 -0
  85. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_preview_model.py +0 -0
  86. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_settings.py +0 -0
  87. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_sidebar.py +0 -0
  88. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_update_banner.py +0 -0
  89. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_update_controller.py +0 -0
  90. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/qt/test_update_feedback.py +0 -0
  91. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_bootstrap.py +0 -0
  92. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_cli.py +0 -0
  93. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_client_updater.py +0 -0
  94. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_client_version.py +0 -0
  95. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_config.py +0 -0
  96. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_examples.py +0 -0
  97. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_init.py +0 -0
  98. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_install.py +0 -0
  99. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_resolution.py +0 -0
  100. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_updater.py +0 -0
  101. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_uri.py +0 -0
  102. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/test_workers.py +0 -0
  103. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/windows/__init__.py +0 -0
  104. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/windows/conftest.py +0 -0
  105. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/windows/test_protocol.py +0 -0
  106. {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev82}/tests/unit/windows/test_startup.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: synodic_client
3
- Version: 0.0.1.dev81
3
+ Version: 0.0.1.dev82
4
4
  Author-Email: Synodic Software <contact@synodic.software>
5
5
  License: LGPL-3.0-or-later
6
6
  Project-URL: homepage, https://github.com/synodic/synodic-client
7
7
  Project-URL: repository, https://github.com/synodic/synodic-client
8
8
  Requires-Python: <3.15,>=3.14
9
- Requires-Dist: pyside6>=6.10.2
9
+ Requires-Dist: pyside6>=6.11.0
10
10
  Requires-Dist: packaging>=26.0
11
11
  Requires-Dist: porringer>=0.2.1.dev88
12
12
  Requires-Dist: qasync>=0.28.0
@@ -8,14 +8,14 @@ readme = "README.md"
8
8
  dynamic = []
9
9
  requires-python = ">=3.14, <3.15"
10
10
  dependencies = [
11
- "pyside6>=6.10.2",
11
+ "pyside6>=6.11.0",
12
12
  "packaging>=26.0",
13
13
  "porringer>=0.2.1.dev88",
14
14
  "qasync>=0.28.0",
15
15
  "velopack>=0.0.1535.dev45597",
16
16
  "typer>=0.24.1",
17
17
  ]
18
- version = "0.0.1.dev81"
18
+ version = "0.0.1.dev82"
19
19
 
20
20
  [project.license]
21
21
  text = "LGPL-3.0-or-later"
@@ -36,7 +36,7 @@ build = [
36
36
  ]
37
37
  lint = [
38
38
  "ruff>=0.15.7",
39
- "pyrefly>=0.57.1",
39
+ "pyrefly>=0.58.0",
40
40
  ]
41
41
  test = [
42
42
  "pytest>=9.0.2",
@@ -4,7 +4,7 @@ import logging
4
4
  from typing import TYPE_CHECKING
5
5
 
6
6
  from PySide6.QtCore import QTimer
7
- from PySide6.QtGui import QAction
7
+ from PySide6.QtGui import QAction, QCursor
8
8
  from PySide6.QtWidgets import (
9
9
  QApplication,
10
10
  QMainWindow,
@@ -126,10 +126,10 @@ class TrayScreen:
126
126
  self._menu.addSeparator()
127
127
 
128
128
  self._quit_action = QAction('Quit', self._menu)
129
- self._quit_action.triggered.connect(app.quit)
129
+ self._quit_action.triggered.connect(self._on_quit_triggered)
130
130
  self._menu.addAction(self._quit_action)
131
131
 
132
- self.tray.setContextMenu(self._menu)
132
+ self._menu.aboutToShow.connect(lambda: logger.debug('Tray context menu about to show'))
133
133
 
134
134
  # Maximum number of tray-visibility retries at startup.
135
135
  _TRAY_MAX_RETRIES = 5
@@ -162,12 +162,29 @@ class TrayScreen:
162
162
  )
163
163
  self.tray.setVisible(True)
164
164
 
165
+ # Delay before showing the context menu, in milliseconds.
166
+ # Absorbs residual mouse-up events from touchpad two-finger taps
167
+ # that would otherwise land on a menu item (typically "Quit").
168
+ _MENU_POPUP_DELAY_MS = 80
169
+
165
170
  def _on_tray_activated(self, reason: QSystemTrayIcon.ActivationReason) -> None:
166
- """Handle tray icon activation (e.g. double-click)."""
171
+ """Handle tray icon activation."""
172
+ logger.debug('Tray activated: reason=%s', reason.name)
167
173
  if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
168
174
  self._window.show()
169
175
  self._window.raise_()
170
176
  self._window.activateWindow()
177
+ elif reason == QSystemTrayIcon.ActivationReason.Context:
178
+ QTimer.singleShot(self._MENU_POPUP_DELAY_MS, self._show_tray_menu)
179
+
180
+ def _show_tray_menu(self) -> None:
181
+ """Show the tray context menu at the current cursor position."""
182
+ self._menu.exec(QCursor.pos())
183
+
184
+ def _on_quit_triggered(self) -> None:
185
+ """Handle the Quit menu action."""
186
+ logger.info('Quit requested via tray menu')
187
+ self._app.quit()
171
188
 
172
189
  def _show_settings(self) -> None:
173
190
  """Show the settings window."""
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from unittest.mock import MagicMock, patch
6
6
 
7
7
  import pytest
8
+ from PySide6.QtWidgets import QSystemTrayIcon
8
9
 
9
10
  from synodic_client.application.screen.schema import UpdateTarget
10
11
  from synodic_client.application.screen.tray import TrayScreen
@@ -69,3 +70,32 @@ class TestToolUpdateWindowShow:
69
70
  result = UpdateResult(manifests_processed=1, already_latest=['pkg'])
70
71
  tray_screen._tool_orchestrator._on_tool_update_finished(result)
71
72
  tray_screen._window.show.assert_not_called()
73
+
74
+
75
+ class TestTrayActivation:
76
+ """_on_tray_activated should dispatch correctly by reason."""
77
+
78
+ @staticmethod
79
+ def test_double_click_shows_window(tray_screen) -> None:
80
+ """Double-clicking the tray icon should show and raise the window."""
81
+ tray_screen._on_tray_activated(QSystemTrayIcon.ActivationReason.DoubleClick)
82
+ tray_screen._window.show.assert_called_once()
83
+ tray_screen._window.raise_.assert_called_once()
84
+ tray_screen._window.activateWindow.assert_called_once()
85
+
86
+ @staticmethod
87
+ def test_context_defers_menu_via_timer(tray_screen) -> None:
88
+ """Right-clicking should defer the menu popup via a single-shot timer."""
89
+ with patch('synodic_client.application.screen.tray.QTimer') as mock_timer:
90
+ tray_screen._on_tray_activated(QSystemTrayIcon.ActivationReason.Context)
91
+ mock_timer.singleShot.assert_called_once()
92
+ delay, callback = mock_timer.singleShot.call_args[0]
93
+ assert delay == TrayScreen._MENU_POPUP_DELAY_MS
94
+ assert callback == tray_screen._show_tray_menu
95
+
96
+ @staticmethod
97
+ def test_context_does_not_show_window(tray_screen) -> None:
98
+ """Right-clicking should not bring the main window forward."""
99
+ with patch('synodic_client.application.screen.tray.QTimer'):
100
+ tray_screen._on_tray_activated(QSystemTrayIcon.ActivationReason.Context)
101
+ tray_screen._window.show.assert_not_called()