diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 7ff7f6c..c148b20 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -4,92 +4,92 @@ name: Build and Package # Velopack on each platform. Produces one artifact per platform. on: - workflow_call: - inputs: - version: - description: "Version string for the package" - required: true - type: string - channel: - description: "Velopack channel base (stable or dev)" - required: true - type: string + workflow_call: + inputs: + version: + description: "Version string for the package" + required: true + type: string + channel: + description: "Velopack channel base (stable or dev)" + required: true + type: string env: - VELOPACK_APP_ID: synodic - VELOPACK_APP_TITLE: Synodic Client + VELOPACK_APP_ID: synodic + VELOPACK_APP_TITLE: Synodic Client jobs: - build-and-package: - if: github.repository_owner == 'synodic' - runs-on: ${{ matrix.runner }} - strategy: - matrix: - include: - - runner: windows-latest - main_exe: synodic.exe - platform: win - - runner: ubuntu-latest - main_exe: synodic - platform: linux - - runner: macos-latest - main_exe: synodic - platform: osx - steps: - - name: Checkout - uses: actions/checkout@v6 + build-and-package: + if: github.repository_owner == 'synodic' + runs-on: ${{ matrix.runner }} + strategy: + matrix: + include: + - runner: windows-latest + main_exe: synodic.exe + platform: win + - runner: ubuntu-latest + main_exe: synodic + platform: linux + - runner: macos-latest + main_exe: synodic + platform: osx + steps: + - name: Checkout + uses: actions/checkout@v6 - - name: Install system dependencies - if: matrix.platform == 'linux' - run: | - sudo apt-get update - sudo apt-get install -y libxcb-cursor0 libxkbcommon-x11-0 libfuse2 + - name: Install system dependencies + if: matrix.platform == 'linux' + run: | + sudo apt-get update + sudo apt-get install -y libxcb-cursor0 libxkbcommon-x11-0 libfuse2 - - name: Install PDM - uses: pdm-project/setup-pdm@v4 - with: - python-version: "3.14" - cache: true + - name: Install PDM + uses: pdm-project/setup-pdm@v4 + with: + python-version: "3.14" + cache: true - - name: Install dependencies - run: pdm install -G build + - name: Install dependencies + run: pdm install -G build - - name: Build executable - run: pdm run pyinstaller tool/pyinstaller/synodic.spec --distpath dist + - name: Build executable + run: pdm run pyinstaller tool/pyinstaller/synodic.spec --distpath dist - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: "9.0" + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "9.0" - - name: Install Velopack CLI - run: dotnet tool install -g vpk + - name: Install Velopack CLI + run: dotnet tool install -g vpk - - name: Download previous release (for delta packages) - continue-on-error: true - shell: bash - run: | - vpk download github \ - --repoUrl https://github.com/${{ github.repository }} \ - --channel ${{ inputs.channel }}-${{ matrix.platform }} \ - --token ${{ secrets.GITHUB_TOKEN }} \ - ${{ inputs.channel == 'dev' && '--pre' || '' }} + - name: Download previous release (for delta packages) + continue-on-error: true + shell: bash + run: | + vpk download github \ + --repoUrl https://github.com/${{ github.repository }} \ + --channel ${{ inputs.channel }}-${{ matrix.platform }} \ + --token ${{ secrets.GITHUB_TOKEN }} \ + ${{ inputs.channel == 'dev' && '--pre' || '' }} - - name: Create Velopack package - shell: bash - run: | - vpk pack \ - --packId ${{ env.VELOPACK_APP_ID }} \ - --packVersion ${{ inputs.version }} \ - --packDir dist/synodic \ - --mainExe ${{ matrix.main_exe }} \ - --packTitle "${{ env.VELOPACK_APP_TITLE }}" \ - --channel ${{ inputs.channel }}-${{ matrix.platform }} \ - --shortcutLocations StartMenuRoot \ - --outputDir releases + - name: Create Velopack package + shell: bash + run: | + vpk pack \ + --packId ${{ env.VELOPACK_APP_ID }} \ + --packVersion ${{ inputs.version }} \ + --packDir dist/synodic \ + --mainExe ${{ matrix.main_exe }} \ + --packTitle "${{ env.VELOPACK_APP_TITLE }}" \ + --channel ${{ inputs.channel }}-${{ matrix.platform }} \ + ${{ matrix.platform == 'win' && '--shortcuts StartMenuRoot' || '' }} \ + --outputDir releases - - name: Upload release artifacts - uses: actions/upload-artifact@v4 - with: - name: velopack-release-${{ matrix.platform }} - path: releases/* + - name: Upload release artifacts + uses: actions/upload-artifact@v4 + with: + name: velopack-release-${{ matrix.platform }} + path: releases/* diff --git a/pdm.lock b/pdm.lock index 5c90775..3ced3b8 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "build", "lint", "test"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:fbc1bb821435f99f3181eaefe90bc228dfc13d4cd61c750bc96a47a8c2045217" +content_hash = "sha256:3f2234856ac016b0fd426f5c92125dfd41995bf17e832f7b0cdf3f26cd3747cc" [[metadata.targets]] requires_python = ">=3.14,<3.15" @@ -626,74 +626,74 @@ files = [ [[package]] name = "pyrefly" -version = "0.57.1" +version = "0.58.0" requires_python = ">=3.8" summary = "A fast type checker and language server for Python with powerful IDE features" groups = ["lint"] files = [ - {file = "pyrefly-0.57.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:91974bfbe951eebf5a7bc959c1f3921f0371c789cad84761511d695e9ab2265f"}, - {file = "pyrefly-0.57.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:808087298537c70f5e7cdccb5bbaad482e7e056e947c0adf00fb612cbace9fdc"}, - {file = "pyrefly-0.57.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b01f454fa5539e070c0cba17ddec46b3d2107d571d519bd8eca8f3142ba02a6"}, - {file = "pyrefly-0.57.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02ad59ea722191f51635f23e37574662116b82ca9d814529f7cb5528f041f381"}, - {file = "pyrefly-0.57.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54bc0afe56776145e37733ff763e7e9679ee8a76c467b617dc3f227d4124a9e2"}, - {file = "pyrefly-0.57.1-py3-none-win32.whl", hash = "sha256:468e5839144b25bb0dce839bfc5fd879c9f38e68ebf5de561f30bed9ae19d8ca"}, - {file = "pyrefly-0.57.1-py3-none-win_amd64.whl", hash = "sha256:46db9c97093673c4fb7fab96d610e74d140661d54688a92d8e75ad885a56c141"}, - {file = "pyrefly-0.57.1-py3-none-win_arm64.whl", hash = "sha256:feb1bbe3b0d8d5a70121dcdf1476e6a99cc056a26a49379a156f040729244dcb"}, - {file = "pyrefly-0.57.1.tar.gz", hash = "sha256:b05f6f5ee3a6a5d502ca19d84cb9ab62d67f05083819964a48c1510f2993efc6"}, + {file = "pyrefly-0.58.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6521e3924d98c2fe118109de625c075d970f9d4a9682f11afa4f7c6b5f11ca1f"}, + {file = "pyrefly-0.58.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd52dd8f330e08f3829791b914f170769f97e8a0e377abf554710decbf799f20"}, + {file = "pyrefly-0.58.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2334bea7d7b43a4d9061cce8e48ee5da2de185511637ed691dca5d649633f514"}, + {file = "pyrefly-0.58.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb7bf956beb01f2664a1b20ca7303dc617bc454457b869237945edd5a52fc39b"}, + {file = "pyrefly-0.58.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce58ec7d4a5bc4fdb4a8d90329259c15e221e840ce1da946c698c4954aa4098b"}, + {file = "pyrefly-0.58.0-py3-none-win32.whl", hash = "sha256:43fa3b1abc0b55503c6238d09f333ce4f7a341cb9d5113b9d5e3089b8e0901b6"}, + {file = "pyrefly-0.58.0-py3-none-win_amd64.whl", hash = "sha256:5dbd270d6841ec7b772ee41dd2d83f376ad24b279b747ddb8c11550ad3648dde"}, + {file = "pyrefly-0.58.0-py3-none-win_arm64.whl", hash = "sha256:86425dfb43607027960ffed13722504992e83b45becbb83853d8f961c715dac2"}, + {file = "pyrefly-0.58.0.tar.gz", hash = "sha256:4512b89cd8db95e8994537895ff41ad60e6211643442f8e33ed93bb59f88a256"}, ] [[package]] name = "pyside6" -version = "6.10.2" -requires_python = "<3.15,>=3.9" +version = "6.11.0" +requires_python = "<3.15,>=3.10" summary = "Python bindings for the Qt cross-platform application and UI framework" groups = ["default"] dependencies = [ - "PySide6-Addons==6.10.2", - "PySide6-Essentials==6.10.2", - "shiboken6==6.10.2", + "PySide6-Addons==6.11.0", + "PySide6-Essentials==6.11.0", + "shiboken6==6.11.0", ] files = [ - {file = "pyside6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:4b084293caa7845d0064aaf6af258e0f7caae03a14a33537d0a552131afddaf0"}, - {file = "pyside6-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1b89ce8558d4b4f35b85bff1db90d680912e4d3ce9e79ff804d6fef1d1a151ef"}, - {file = "pyside6-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:0439f5e9b10ebe6177981bac9e219096ec970ac6ec215bef055279802ba50601"}, - {file = "pyside6-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:032bad6b18a17fcbf4dddd0397f49b07f8aae7f1a45b7e4de7037bf7fd6e0edf"}, - {file = "pyside6-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:65a59ad0bc92525639e3268d590948ce07a80ee97b55e7a9200db41d493cac31"}, + {file = "pyside6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:1f2735dc4f2bd4ec452ae50502c8a22128bba0aced35358a2bbc58384b820c6f"}, + {file = "pyside6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c642e2d25704ca746fd37f56feacf25c5aecc4cd40bef23d18eec81f87d9dc00"}, + {file = "pyside6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:267b344c73580ac938ca63c611881fb42a3922ebfe043e271005f4f06c372c4e"}, + {file = "pyside6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:9092cb002ca43c64006afb2e0d0f6f51aef17aa737c33a45e502326a081ddcbc"}, + {file = "pyside6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:b15f39acc2b8f46251a630acad0d97f9a0a0461f2baffcd66d7adfada8eb641e"}, ] [[package]] name = "pyside6-addons" -version = "6.10.2" -requires_python = "<3.15,>=3.9" +version = "6.11.0" +requires_python = "<3.15,>=3.10" summary = "Python bindings for the Qt cross-platform application and UI framework (Addons)" groups = ["default"] dependencies = [ - "PySide6-Essentials==6.10.2", - "shiboken6==6.10.2", + "PySide6-Essentials==6.11.0", + "shiboken6==6.11.0", ] files = [ - {file = "pyside6_addons-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:0de7d0c9535e17d5e3b634b61314a1867f3b0f6d35c3d7cdc99efc353192faff"}, - {file = "pyside6_addons-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:030a851163b51dbf0063be59e9ddb6a9e760bde89a28e461ccc81a224d286eaf"}, - {file = "pyside6_addons-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:fcee0373e3fd7b98f014094e5e37b4a39e4de7c5a47c13f654a7d557d4a426ad"}, - {file = "pyside6_addons-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:c20150068525a17494f3b6576c5d61c417cf9a5870659e29f5ebd83cd20a78ea"}, - {file = "pyside6_addons-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:3d18db739b46946ba7b722d8ad4cc2097135033aa6ea57076e64d591e6a345f3"}, + {file = "pyside6_addons-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d5eaa4643302e3a0fa94c5766234bee4073d7d5ab9c2b7fd222692a176faf182"}, + {file = "pyside6_addons-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ac6fe3d4ef4497dde3efc5e896b0acd53ff6c93be4bf485f045690f919419f35"}, + {file = "pyside6_addons-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:8ffb40222456078930816ebcac2f2511716d2acbc11716dd5acc5c365179a753"}, + {file = "pyside6_addons-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:413e6121c24f5ffdce376298059eddecff74aa6d638e94e0f6015b33d29b889e"}, + {file = "pyside6_addons-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:aaaee83385977a0fe134b2f4fbfb92b45a880d5b656e4d90a708eef10b1b6de8"}, ] [[package]] name = "pyside6-essentials" -version = "6.10.2" -requires_python = "<3.15,>=3.9" +version = "6.11.0" +requires_python = "<3.15,>=3.10" summary = "Python bindings for the Qt cross-platform application and UI framework (Essentials)" groups = ["default"] dependencies = [ - "shiboken6==6.10.2", + "shiboken6==6.11.0", ] files = [ - {file = "pyside6_essentials-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:1dee2cb9803ff135f881dadeb5c0edcef793d1ec4f8a9140a1348cecb71074e1"}, - {file = "pyside6_essentials-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:660aea45bfa36f1e06f799b934c2a7df963bd31abc5083e8bb8a5bfaef45686b"}, - {file = "pyside6_essentials-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:c2b028e4c6f8047a02c31f373408e23b4eedfd405f56c6aba8d0525c29472835"}, - {file = "pyside6_essentials-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:0741018c2b6395038cad4c41775cfae3f13a409e87995ac9f7d89e5b1fb6b22a"}, - {file = "pyside6_essentials-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:db5f4913648bb6afddb8b347edae151ee2378f12bceb03c8b2515a530a4b38d9"}, + {file = "pyside6_essentials-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:85d6ca87ef35fa6565d385ede72ae48420dd3f63113929d10fc800f6b0360e01"}, + {file = "pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:dc20e7afd5fc6fe51297db91cef997ce60844be578f7a49fc61b7ab9657a8849"}, + {file = "pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:4854cb0a1b061e7a576d8fb7bb7cf9f49540d558b1acb7df0742a7afefe61e4e"}, + {file = "pyside6_essentials-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:3b3362882ad9389357a80504e600180006a957731fec05786fced7b038461fdf"}, + {file = "pyside6_essentials-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:81ca603dbf21bc39f89bb42db215c25ebe0c879a1a4c387625c321d2730ec187"}, ] [[package]] @@ -835,16 +835,16 @@ files = [ [[package]] name = "shiboken6" -version = "6.10.2" -requires_python = "<3.15,>=3.9" +version = "6.11.0" +requires_python = "<3.15,>=3.10" summary = "Python/C++ bindings helper module" groups = ["default"] files = [ - {file = "shiboken6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:3bd4e94e9a3c8c1fa8362fd752d399ef39265d5264e4e37bae61cdaa2a00c8c7"}, - {file = "shiboken6-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ace0790032d9cb0adda644b94ee28d59410180d9773643bb6cf8438c361987ad"}, - {file = "shiboken6-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:f74d3ed1f92658077d0630c39e694eb043aeb1d830a5d275176c45d07147427f"}, - {file = "shiboken6-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:10f3c8c5e1b8bee779346f21c10dbc14cff068f0b0b4e62420c82a6bf36ac2e7"}, - {file = "shiboken6-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:20c671645d70835af212ee05df60361d734c5305edb2746e9875c6a31283f963"}, + {file = "shiboken6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d88e8a1eb705f2b9ad21db08a61ae1dc0c773e5cd86a069de0754c4cf1f9b43b"}, + {file = "shiboken6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad54e64f8192ddbdff0c54ac82b89edcd62ed623f502ea21c960541d19514053"}, + {file = "shiboken6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a10dc7718104ea2dc15d5b0b96909b77162ce1c76fcc6968e6df692b947a00e9"}, + {file = "shiboken6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:483ff78a73c7b3189ca924abc694318084f078bcfeaffa68e32024ff2d025ee1"}, + {file = "shiboken6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:3bd76cf56105ab2d62ecaff630366f11264f69b88d488f10f048da9a065781f4"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 7e7984c..23df0f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dynamic = ["version"] requires-python = ">=3.14, <3.15" dependencies = [ - "pyside6>=6.10.2", + "pyside6>=6.11.0", "packaging>=26.0", "porringer>=0.2.1.dev88", "qasync>=0.28.0", @@ -30,7 +30,7 @@ build = [ ] lint = [ "ruff>=0.15.7", - "pyrefly>=0.57.1", + "pyrefly>=0.58.0", ] test = [ "pytest>=9.0.2", diff --git a/synodic_client/application/screen/tray.py b/synodic_client/application/screen/tray.py index 5e6dd0a..9552264 100644 --- a/synodic_client/application/screen/tray.py +++ b/synodic_client/application/screen/tray.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from PySide6.QtCore import QTimer -from PySide6.QtGui import QAction +from PySide6.QtGui import QAction, QCursor from PySide6.QtWidgets import ( QApplication, QMainWindow, @@ -126,10 +126,10 @@ def _build_menu(self, app: QApplication, window: MainWindow) -> None: self._menu.addSeparator() self._quit_action = QAction('Quit', self._menu) - self._quit_action.triggered.connect(app.quit) + self._quit_action.triggered.connect(self._on_quit_triggered) self._menu.addAction(self._quit_action) - self.tray.setContextMenu(self._menu) + self._menu.aboutToShow.connect(lambda: logger.debug('Tray context menu about to show')) # Maximum number of tray-visibility retries at startup. _TRAY_MAX_RETRIES = 5 @@ -162,12 +162,29 @@ def _show_tray_icon(self) -> None: ) self.tray.setVisible(True) + # Delay before showing the context menu, in milliseconds. + # Absorbs residual mouse-up events from touchpad two-finger taps + # that would otherwise land on a menu item (typically "Quit"). + _MENU_POPUP_DELAY_MS = 80 + def _on_tray_activated(self, reason: QSystemTrayIcon.ActivationReason) -> None: - """Handle tray icon activation (e.g. double-click).""" + """Handle tray icon activation.""" + logger.debug('Tray activated: reason=%s', reason.name) if reason == QSystemTrayIcon.ActivationReason.DoubleClick: self._window.show() self._window.raise_() self._window.activateWindow() + elif reason == QSystemTrayIcon.ActivationReason.Context: + QTimer.singleShot(self._MENU_POPUP_DELAY_MS, self._show_tray_menu) + + def _show_tray_menu(self) -> None: + """Show the tray context menu at the current cursor position.""" + self._menu.exec(QCursor.pos()) + + def _on_quit_triggered(self) -> None: + """Handle the Quit menu action.""" + logger.info('Quit requested via tray menu') + self._app.quit() def _show_settings(self) -> None: """Show the settings window.""" diff --git a/tests/unit/qt/test_tray_window_show.py b/tests/unit/qt/test_tray_window_show.py index 06e2646..f43d227 100644 --- a/tests/unit/qt/test_tray_window_show.py +++ b/tests/unit/qt/test_tray_window_show.py @@ -5,6 +5,7 @@ from unittest.mock import MagicMock, patch import pytest +from PySide6.QtWidgets import QSystemTrayIcon from synodic_client.application.screen.schema import UpdateTarget from synodic_client.application.screen.tray import TrayScreen @@ -69,3 +70,32 @@ def test_auto_update_with_no_changes_does_not_show(tray_screen) -> None: result = UpdateResult(manifests_processed=1, already_latest=['pkg']) tray_screen._tool_orchestrator._on_tool_update_finished(result) tray_screen._window.show.assert_not_called() + + +class TestTrayActivation: + """_on_tray_activated should dispatch correctly by reason.""" + + @staticmethod + def test_double_click_shows_window(tray_screen) -> None: + """Double-clicking the tray icon should show and raise the window.""" + tray_screen._on_tray_activated(QSystemTrayIcon.ActivationReason.DoubleClick) + tray_screen._window.show.assert_called_once() + tray_screen._window.raise_.assert_called_once() + tray_screen._window.activateWindow.assert_called_once() + + @staticmethod + def test_context_defers_menu_via_timer(tray_screen) -> None: + """Right-clicking should defer the menu popup via a single-shot timer.""" + with patch('synodic_client.application.screen.tray.QTimer') as mock_timer: + tray_screen._on_tray_activated(QSystemTrayIcon.ActivationReason.Context) + mock_timer.singleShot.assert_called_once() + delay, callback = mock_timer.singleShot.call_args[0] + assert delay == TrayScreen._MENU_POPUP_DELAY_MS + assert callback == tray_screen._show_tray_menu + + @staticmethod + def test_context_does_not_show_window(tray_screen) -> None: + """Right-clicking should not bring the main window forward.""" + with patch('synodic_client.application.screen.tray.QTimer'): + tray_screen._on_tray_activated(QSystemTrayIcon.ActivationReason.Context) + tray_screen._window.show.assert_not_called()