pyobs-gui 2.7.0__tar.gz → 2.8.0__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.
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/CHANGELOG.md +12 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/PKG-INFO +2 -2
- pyobs_gui-2.8.0/pyobs_gui/appicon.py +19 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/base.py +4 -9
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/gui.py +2 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/README.md +13 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/pyobs-gui-128.png +0 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/pyobs-gui-16.png +0 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/pyobs-gui-24.png +0 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/pyobs-gui-256.png +0 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/pyobs-gui-32.png +0 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/pyobs-gui-48.png +0 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/pyobs-gui-512.png +0 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/pyobs-gui-64.png +0 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/pyobs-gui.ico +0 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/pyobs-gui.png +0 -0
- pyobs_gui-2.8.0/pyobs_gui/icons/pyobs-gui.svg +13 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/loginwindow.py +2 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/mainwindow.py +2 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/notifier_backend.py +3 -2
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/settingsdialog.py +2 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/windows_identity.py +26 -1
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyproject.toml +5 -2
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pysidedeploy.spec +2 -2
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/plans/2026-10-04-settings-dialog.md +1 -1
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_missing_vfs_root.py +5 -4
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_notifier_backend.py +11 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_windows_identity.py +53 -1
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/uv.lock +159 -118
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/.claude/skills/verify/SKILL.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/.github/dependabot.yml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/.github/workflows/dependabot-automerge.yml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/.github/workflows/pypi.yml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/.github/workflows/pyrefly.yml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/.github/workflows/ruff.yml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/.gitignore +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/.pre-commit-config.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/.readthedocs.yml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/CLAUDE.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/CONTRIBUTING.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/LICENSE +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/README.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/bin/mypyuic5 +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/docs/Makefile +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/docs/make.bat +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/docs/requirements.txt +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/docs/source/_static/pyobs.gif +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/docs/source/conf.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/docs/source/index.rst +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/mypy.ini +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/__init__.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/__main__.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/_nuitka_astropy_patch.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/accounts.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/acquisitionwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/autofocuswidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/autoguidingwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/camerawidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/commandinputwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/compassmovewidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/coolingwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/datadisplaywidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/eventswidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/filterwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/fitsheaderswidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/focuswidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/guisignals.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/livestream.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/login.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/logmodel.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/modewidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/modifiedmixin.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/modulegui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/movestack.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/notificationmanager.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/notifications.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/notifier.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/nowheelfilter.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/__init__.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/acquisitionwidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/acquisitionwidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/autofocuswidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/autofocuswidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/autoguidingwidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/autoguidingwidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/camerawidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/camerawidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/compassmoveplugin.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/compassmovewidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/compassmovewidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/compassmovewidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/compile.sh +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/coolingwidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/coolingwidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/datadisplayplugin.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/datadisplaywidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/datadisplaywidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/designer.sh +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/eventswidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/eventswidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/filterwidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/filterwidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/fitsheaderswidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/fitsheaderswidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/focuswidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/focuswidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/mainwindow.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/mainwindow_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/modewidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/modewidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/arrow-alt-circle-down-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/arrow-alt-circle-left-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/arrow-alt-circle-right-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/arrow-alt-circle-up-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/arrow-up-right-from-square-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/arrows-to-eye-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/chart-line-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/circle-question-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/cloud-sun-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/edit-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/search-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources/undo-solid.svg +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources.qrc +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/resources_rc.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/roboticwidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/roboticwidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/roofwidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/roofwidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/schedulewidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/schedulewidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/shellwidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/shellwidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/spectrographwidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/spectrographwidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/telescopewidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/telescopewidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/temperaturesplotwidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/temperaturesplotwidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/temperatureswidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/temperatureswidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/videograbwidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/videograbwidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/videowidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/videowidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/weatherwidget.ui +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/qt/weatherwidget_ui.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/roboticwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/roofwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/schedulewidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/settings.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/shellwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/spectrographwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/statuswidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/structuredconfigwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/telescopewidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/temperaturesplotwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/temperatureswidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/utils.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/videograbwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/videowidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/watchedlabel.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/pyobs_gui/weatherwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-08-20-gui-remote-call-error-handling.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-08-21-gui-widget-startup-responsiveness.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-08-28-gui-main-vs-sidebar-widgets.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-08-28-structuredconfig-widget.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-08-31-irobotic-widgets.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-09-01-gui-video-widget-split.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-09-14-fitswidget-toolbar-overflow.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-09-14-stacked-widget-scroll-fallback.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-09-29-live-view-mjpeg-raw.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-10-04-desktop-notifications.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-10-04-settings-dialog.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/2026-10-14-typed-config-choices.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/index.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/plans/2026-10-04-desktop-notifications.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/specs/plans/index.md +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/spike_standalone/main.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/spike_standalone/pysidedeploy.spec +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/acquisition.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/autofocus.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/camera.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/full.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/guiding.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/mode.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/robotic.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/roof.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/spectrograph.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/structuredconfig.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/telescope.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/telescope_acl.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/telescope_acl_denied.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/test/video.yaml +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/__init__.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/conftest.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_accounts.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_basewidget_discard.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_camerawidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_gui_notifications.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_import_order.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_livestream.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_loginwindow.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_mainwindow_startup.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_modewidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_multiwidget_pages.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_notificationmanager.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_notifications.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_roboticwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_schedulewidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_settings.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_settingsdialog.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_spectrographwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_structuredconfigwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_video_refresh.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_videograbwidget.py +0 -0
- {pyobs_gui-2.7.0 → pyobs_gui-2.8.0}/tests/test_videowidget.py +0 -0
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
Entries for releases before this file existed were generated from commit subjects.
|
|
9
9
|
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Recognize a missing VFS root by the new `pyobs.vfs.UnknownRootError` instead of matching the
|
|
15
|
+
error message. Requires pyobs-core 2.19.0 or newer.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Magenta observing-monitor app icon for Qt windows, desktop notifications and
|
|
20
|
+
Windows Start Menu shortcuts; update existing shortcuts without changing their targets (#197).
|
|
21
|
+
|
|
10
22
|
## [2.7.0] - 2026-10-15
|
|
11
23
|
|
|
12
24
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyobs-gui
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.8.0
|
|
4
4
|
Summary: A remote GUI for pyobs
|
|
5
5
|
Project-URL: Homepage, https://www.pyobs.org
|
|
6
6
|
Project-URL: Documentation, https://docs.pyobs.org
|
|
@@ -19,7 +19,7 @@ Requires-Dist: keyring<26,>=25.7.0
|
|
|
19
19
|
Requires-Dist: matplotlib<4,>=3.10.1
|
|
20
20
|
Requires-Dist: pandas>=2.2.3
|
|
21
21
|
Requires-Dist: pydantic<3,>=2.12.5
|
|
22
|
-
Requires-Dist: pyobs-core<3,>=2.
|
|
22
|
+
Requires-Dist: pyobs-core<3,>=2.19.0
|
|
23
23
|
Requires-Dist: pyside6-essentials>=6.10.1
|
|
24
24
|
Requires-Dist: pywin32>=312; sys_platform == 'win32'
|
|
25
25
|
Requires-Dist: pyyaml<7,>=6.0.2
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Shared app artwork as real files for Qt, notifications and Windows shortcuts."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING, Literal
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from PySide6.QtGui import QIcon
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def icon_path(format: Literal["png", "ico"] = "png") -> Path:
|
|
11
|
+
"""Installed package data, also placed beside this module by Nuitka."""
|
|
12
|
+
return Path(__file__).resolve().with_name("icons") / f"pyobs-gui.{format}"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def window_icon() -> "QIcon":
|
|
16
|
+
"""Qt reads all resolutions in the ICO, including the small window-title icon."""
|
|
17
|
+
from PySide6.QtGui import QIcon
|
|
18
|
+
|
|
19
|
+
return QIcon(str(icon_path("ico")))
|
|
@@ -46,17 +46,12 @@ async def show_remote_error(parent: QtWidgets.QWidget, exception: Exception) ->
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def missing_root_message(exception: Exception, path: str, module: str) -> str | None:
|
|
49
|
-
"""User-facing text if `exception` is the VFS's
|
|
49
|
+
"""User-facing text if `exception` is the VFS's `UnknownRootError`, else None."""
|
|
50
|
+
from pyobs.vfs import UnknownRootError
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
message prefix is the only way to tell it from other `ValueError`s.
|
|
53
|
-
"""
|
|
54
|
-
from pyobs.vfs import VirtualFileSystem
|
|
55
|
-
|
|
56
|
-
if not isinstance(exception, ValueError) or not str(exception).startswith("Could not find root"):
|
|
52
|
+
if not isinstance(exception, UnknownRootError):
|
|
57
53
|
return None
|
|
58
|
-
root
|
|
59
|
-
return f"Cannot open '{path}' from module '{module}': the VFS root '{root}' is not configured."
|
|
54
|
+
return f"Cannot open '{path}' from module '{module}': the VFS root '{exception.root}' is not configured."
|
|
60
55
|
|
|
61
56
|
|
|
62
57
|
async def cancel_and_drain(task: asyncio.Task[Any]) -> None:
|
|
@@ -12,6 +12,7 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from pyobs.interfaces import FitsHeaderEntry, IFitsHeaderBefore
|
|
13
13
|
from pyobs.modules import Module
|
|
14
14
|
|
|
15
|
+
from .appicon import window_icon
|
|
15
16
|
from .nowheelfilter import NoWheelWhenUnfocused
|
|
16
17
|
from .guisignals import gui_signals
|
|
17
18
|
from .notificationmanager import NotificationManager
|
|
@@ -147,6 +148,7 @@ class GUI(Module, IFitsHeaderBefore):
|
|
|
147
148
|
@staticmethod
|
|
148
149
|
def new_event_loop() -> asyncio.AbstractEventLoop:
|
|
149
150
|
GUI.app = QtWidgets.QApplication(sys.argv)
|
|
151
|
+
GUI.app.setWindowIcon(window_icon())
|
|
150
152
|
# standalone/login mode briefly has zero visible top-level windows while the login
|
|
151
153
|
# dialog closes and the (old or new) MainWindow hasn't opened yet -- Qt's default
|
|
152
154
|
# "quit when the last window closes" would tear down the whole event loop right there,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# pyobs GUI icon
|
|
2
|
+
|
|
3
|
+
White observing monitor and crosshair on magenta (`#d63384`), following the fleet's
|
|
4
|
+
white-symbol-on-colour app icons. Original artwork created for pyobs-gui and
|
|
5
|
+
approved on 2026-10-15; distributed under this repository's MIT licence.
|
|
6
|
+
|
|
7
|
+
`pyobs-gui.svg` is the editable source. PNG exports cover 16, 24, 32, 48, 64,
|
|
8
|
+
128, 256 and 512 pixels. `pyobs-gui.png` is the 256-pixel notification icon;
|
|
9
|
+
`pyobs-gui.ico` contains 16 through 256-pixel renditions for Qt and Windows.
|
|
10
|
+
|
|
11
|
+
These files must remain ordinary package data: desktop-notifier and the Windows
|
|
12
|
+
shortcut require filesystem paths, rather than Qt resource URLs. Nuitka includes
|
|
13
|
+
them with `--include-package-data=pyobs_gui` in `pysidedeploy.spec`.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
|
2
|
+
<title>pyobs GUI icon</title>
|
|
3
|
+
<desc>A white observing monitor with a crosshair on a magenta rounded square.</desc>
|
|
4
|
+
<rect width="256" height="256" rx="52" fill="#d63384"/>
|
|
5
|
+
<g fill="none" stroke="#fff" stroke-width="14" stroke-linecap="round" stroke-linejoin="round">
|
|
6
|
+
<rect x="42" y="53" width="172" height="119" rx="13"/>
|
|
7
|
+
<path d="M128 174v29M96 204h64"/>
|
|
8
|
+
</g>
|
|
9
|
+
<g fill="none" stroke="#fff" stroke-width="10" stroke-linecap="round">
|
|
10
|
+
<circle cx="128" cy="112" r="20"/>
|
|
11
|
+
<path d="M128 78v16M128 130v16M94 112h16M146 112h16"/>
|
|
12
|
+
</g>
|
|
13
|
+
</svg>
|
|
@@ -14,6 +14,7 @@ from dataclasses import dataclass
|
|
|
14
14
|
from PySide6 import QtCore, QtGui, QtWidgets
|
|
15
15
|
from pyobs.comm.xmpp import is_valid_jid
|
|
16
16
|
|
|
17
|
+
from .appicon import window_icon
|
|
17
18
|
from .accounts import SavedAccountsModel
|
|
18
19
|
|
|
19
20
|
log = logging.getLogger(__name__)
|
|
@@ -86,6 +87,7 @@ def build_connection_request(
|
|
|
86
87
|
class LoginWindow(QtWidgets.QDialog):
|
|
87
88
|
def __init__(self, accounts: SavedAccountsModel | None = None, parent: QtWidgets.QWidget | None = None) -> None:
|
|
88
89
|
super().__init__(parent)
|
|
90
|
+
self.setWindowIcon(window_icon())
|
|
89
91
|
self.setWindowTitle("pyobs - Sign in")
|
|
90
92
|
self.resize(640, 480)
|
|
91
93
|
|
|
@@ -37,6 +37,7 @@ from pyobs.interfaces import (
|
|
|
37
37
|
IStructuredConfig,
|
|
38
38
|
)
|
|
39
39
|
|
|
40
|
+
from .appicon import window_icon
|
|
40
41
|
from .base import BaseWindow, BaseWidget, cancel_and_drain
|
|
41
42
|
from .acquisitionwidget import AcquisitionWidget
|
|
42
43
|
from .autofocuswidget import AutoFocusWidget
|
|
@@ -641,6 +642,7 @@ class MainWindow(QtWidgets.QMainWindow, BaseWindow, Ui_MainWindow): # type: ign
|
|
|
641
642
|
QtWidgets.QMainWindow.__init__(self)
|
|
642
643
|
BaseWindow.__init__(self)
|
|
643
644
|
self.setupUi(self) # type: ignore
|
|
645
|
+
self.setWindowIcon(window_icon())
|
|
644
646
|
self.resize(1600, 900)
|
|
645
647
|
|
|
646
648
|
# store stuff
|
|
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
11
11
|
|
|
12
12
|
from PySide6 import QtCore # type: ignore
|
|
13
13
|
|
|
14
|
+
from .appicon import icon_path
|
|
14
15
|
from .notifier import NotifierError
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
@@ -77,9 +78,9 @@ class DesktopNotifierBackend:
|
|
|
77
78
|
if self._factory is not None:
|
|
78
79
|
self._notifier = self._factory()
|
|
79
80
|
else:
|
|
80
|
-
from desktop_notifier import DesktopNotifier
|
|
81
|
+
from desktop_notifier import DesktopNotifier, Icon
|
|
81
82
|
|
|
82
|
-
self._notifier = DesktopNotifier(app_name=APP_NAME)
|
|
83
|
+
self._notifier = DesktopNotifier(app_name=APP_NAME, app_icon=Icon(path=icon_path()))
|
|
83
84
|
return self._notifier
|
|
84
85
|
|
|
85
86
|
async def ensure_authorisation(self) -> None:
|
|
@@ -18,6 +18,7 @@ from pydantic import ValidationError
|
|
|
18
18
|
from pyobs.object import get_class_from_string
|
|
19
19
|
from PySide6 import QtWidgets # type: ignore
|
|
20
20
|
|
|
21
|
+
from .appicon import window_icon
|
|
21
22
|
from .notifier import NotifierError
|
|
22
23
|
from .settings import GuiSettings, NotificationSettings, VfsSettings
|
|
23
24
|
|
|
@@ -83,6 +84,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
|
|
83
84
|
parent: QtWidgets.QWidget | None = None,
|
|
84
85
|
) -> None:
|
|
85
86
|
super().__init__(parent)
|
|
87
|
+
self.setWindowIcon(window_icon())
|
|
86
88
|
self.setWindowTitle("Settings")
|
|
87
89
|
self.resize(640, 480)
|
|
88
90
|
self._on_apply = on_apply
|
|
@@ -19,6 +19,8 @@ from typing import TYPE_CHECKING
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
20
|
from collections.abc import Callable
|
|
21
21
|
|
|
22
|
+
from .appicon import icon_path
|
|
23
|
+
|
|
22
24
|
log = logging.getLogger(__name__)
|
|
23
25
|
|
|
24
26
|
SHORTCUT_NAME = "pyobs-gui.lnk"
|
|
@@ -52,6 +54,7 @@ def create_link_com(path: Path, target: str, arguments: str, app_id: str) -> Non
|
|
|
52
54
|
|
|
53
55
|
link = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None, pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink)
|
|
54
56
|
link.SetPath(target)
|
|
57
|
+
link.SetIconLocation(str(icon_path("ico")), 0)
|
|
55
58
|
if arguments:
|
|
56
59
|
link.SetArguments(arguments)
|
|
57
60
|
store = link.QueryInterface(propsys.IID_IPropertyStore)
|
|
@@ -60,15 +63,36 @@ def create_link_com(path: Path, target: str, arguments: str, app_id: str) -> Non
|
|
|
60
63
|
link.QueryInterface(pythoncom.IID_IPersistFile).Save(str(path), 0)
|
|
61
64
|
|
|
62
65
|
|
|
66
|
+
def update_link_icon_com(path: Path) -> bool:
|
|
67
|
+
"""Upgrade an existing shortcut's icon, preserving its target and app identity.
|
|
68
|
+
|
|
69
|
+
Like create_link_com(), this still needs verification on Windows (#192).
|
|
70
|
+
"""
|
|
71
|
+
import pythoncom # type: ignore[import-not-found]
|
|
72
|
+
from win32com.shell import shell # type: ignore[import-not-found]
|
|
73
|
+
|
|
74
|
+
link = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None, pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink)
|
|
75
|
+
persist = link.QueryInterface(pythoncom.IID_IPersistFile)
|
|
76
|
+
persist.Load(str(path))
|
|
77
|
+
location = (str(icon_path("ico")), 0)
|
|
78
|
+
if link.GetIconLocation() == location:
|
|
79
|
+
return False
|
|
80
|
+
link.SetIconLocation(*location)
|
|
81
|
+
persist.Save(str(path), 0)
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
|
|
63
85
|
def ensure_shortcut(
|
|
64
86
|
app_id: str,
|
|
65
87
|
path: Path | None = None,
|
|
66
88
|
command: tuple[str, str] | None = None,
|
|
67
89
|
create_link: Callable[[Path, str, str, str], None] = create_link_com,
|
|
90
|
+
update_icon: Callable[[Path], bool] = update_link_icon_com,
|
|
68
91
|
) -> bool:
|
|
69
92
|
"""Make sure the Start Menu shortcut with the AppUserModelID exists.
|
|
70
93
|
|
|
71
|
-
An existing shortcut
|
|
94
|
+
An existing shortcut keeps its target and identity; only its icon is updated.
|
|
95
|
+
Repeated starts leave the file alone when the icon already matches.
|
|
72
96
|
|
|
73
97
|
Returns:
|
|
74
98
|
Whether a shortcut was created.
|
|
@@ -78,6 +102,7 @@ def ensure_shortcut(
|
|
|
78
102
|
"""
|
|
79
103
|
path = shortcut_path() if path is None else path
|
|
80
104
|
if path.exists():
|
|
105
|
+
update_icon(path)
|
|
81
106
|
return False
|
|
82
107
|
target, arguments = launch_command() if command is None else command
|
|
83
108
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyobs-gui"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.8.0"
|
|
4
4
|
description = "A remote GUI for pyobs"
|
|
5
5
|
authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -15,7 +15,7 @@ dependencies = [
|
|
|
15
15
|
"astroquery>=0.4.10,<0.5",
|
|
16
16
|
"QtAwesome>=1.4.0,<2",
|
|
17
17
|
"qfitswidget>=1.2.0",
|
|
18
|
-
"pyobs-core>=2.
|
|
18
|
+
"pyobs-core>=2.19.0,<3",
|
|
19
19
|
"pyside6-essentials>=6.10.1",
|
|
20
20
|
"astroplan>=0.10.1",
|
|
21
21
|
"astropy>=7.0.1,<8",
|
|
@@ -58,6 +58,9 @@ dev = [
|
|
|
58
58
|
requires = ["hatchling"]
|
|
59
59
|
build-backend = "hatchling.build"
|
|
60
60
|
|
|
61
|
+
[tool.hatch.build.targets.wheel]
|
|
62
|
+
packages = ["pyobs_gui"]
|
|
63
|
+
|
|
61
64
|
[tool.black]
|
|
62
65
|
line-length = 120
|
|
63
66
|
target-version = ['py311']
|
|
@@ -16,7 +16,7 @@ exec_directory = dist
|
|
|
16
16
|
project_file =
|
|
17
17
|
|
|
18
18
|
# application icon
|
|
19
|
-
icon = /
|
|
19
|
+
icon = pyobs_gui/icons/pyobs-gui.png
|
|
20
20
|
|
|
21
21
|
[python]
|
|
22
22
|
|
|
@@ -68,7 +68,7 @@ macos.permissions =
|
|
|
68
68
|
mode = standalone
|
|
69
69
|
|
|
70
70
|
# specify any extra nuitka arguments
|
|
71
|
-
extra_args = --quiet --noinclude-qt-translations --python-flag=-m --include-package=astropy.units.format --include-package=astropy.units.format.generic_parsetab --include-package=astropy.units.format.generic_lextab --include-package=astropy.units.format.cds_parsetab --include-package=astropy.units.format.cds_lextab --include-package=astropy.units.format.ogip_parsetab --include-package=astropy.units.format.ogip_lextab --include-package=astropy.coordinates.angles --include-package=astropy.coordinates.angles.angle_parsetab --include-package=astropy.coordinates.angles.angle_lextab --include-package=astropy.extern.ply --include-package-data=astropy --include-package-data=sunpy --include-package-data=matplotlib --include-package-data=qfitswidget --include-package-data=astropy_iers_data --include-package=pyobs.vfs --include-package-data=astroquery --include-package=slixmpp --include-package=pyobs_gui --noinclude-dlls=*webengine* --noinclude-dlls=*WebEngine* --include-distribution-metadata=astropy --include-distribution-metadata=numpy --include-distribution-metadata=PyYAML --include-distribution-metadata=sunpy --include-distribution-metadata=matplotlib --include-distribution-metadata=qfitswidget --include-distribution-metadata=astroquery --include-distribution-metadata=pyobs-core --include-distribution-metadata=pyobs-gui
|
|
71
|
+
extra_args = --quiet --noinclude-qt-translations --python-flag=-m --include-package=astropy.units.format --include-package=astropy.units.format.generic_parsetab --include-package=astropy.units.format.generic_lextab --include-package=astropy.units.format.cds_parsetab --include-package=astropy.units.format.cds_lextab --include-package=astropy.units.format.ogip_parsetab --include-package=astropy.units.format.ogip_lextab --include-package=astropy.coordinates.angles --include-package=astropy.coordinates.angles.angle_parsetab --include-package=astropy.coordinates.angles.angle_lextab --include-package=astropy.extern.ply --include-package-data=astropy --include-package-data=sunpy --include-package-data=matplotlib --include-package-data=qfitswidget --include-package-data=astropy_iers_data --include-package=pyobs.vfs --include-package-data=astroquery --include-package=slixmpp --include-package=pyobs_gui --include-package-data=pyobs_gui --noinclude-dlls=*webengine* --noinclude-dlls=*WebEngine* --include-distribution-metadata=astropy --include-distribution-metadata=numpy --include-distribution-metadata=PyYAML --include-distribution-metadata=sunpy --include-distribution-metadata=matplotlib --include-distribution-metadata=qfitswidget --include-distribution-metadata=astroquery --include-distribution-metadata=pyobs-core --include-distribution-metadata=pyobs-gui
|
|
72
72
|
|
|
73
73
|
[buildozer]
|
|
74
74
|
|
|
@@ -21,7 +21,7 @@ settings).
|
|
|
21
21
|
`VideoWidget` (`_resolve_url` records it, `_init` shows it if no stream resolved).
|
|
22
22
|
- [x] Tests (`tests/test_missing_vfs_root.py`): message content, once-per-root, other
|
|
23
23
|
`ValueError`s still propagate, video init shows the box and stays disabled.
|
|
24
|
-
- [
|
|
24
|
+
- [x] Follow-up in core (pyobs-core#941): a dedicated exception for an unknown root, so the message-prefix match in
|
|
25
25
|
`missing_root_message()` can go.
|
|
26
26
|
|
|
27
27
|
## 3. Settings schema and store
|
|
@@ -2,19 +2,19 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
from pyobs.events import NewImageEvent
|
|
5
|
-
from pyobs.vfs import VirtualFileSystem
|
|
5
|
+
from pyobs.vfs import UnknownRootError, VirtualFileSystem
|
|
6
6
|
|
|
7
7
|
from pyobs_gui.base import missing_root_message
|
|
8
8
|
from pyobs_gui.datadisplaywidget import DataDisplayWidget
|
|
9
9
|
from pyobs_gui.videowidget import VideoWidget
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def _missing_root_error() ->
|
|
12
|
+
def _missing_root_error() -> UnknownRootError:
|
|
13
13
|
try:
|
|
14
14
|
VirtualFileSystem().open_file("/cache/img.fits", "rb")
|
|
15
|
-
except
|
|
15
|
+
except UnknownRootError as e:
|
|
16
16
|
return e
|
|
17
|
-
raise AssertionError("expected
|
|
17
|
+
raise AssertionError("expected UnknownRootError")
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def test_missing_root_message_names_root_path_and_module() -> None:
|
|
@@ -25,6 +25,7 @@ def test_missing_root_message_names_root_path_and_module() -> None:
|
|
|
25
25
|
|
|
26
26
|
def test_missing_root_message_ignores_other_errors() -> None:
|
|
27
27
|
assert missing_root_message(ValueError("something else"), "/cache/img.fits", "cam") is None
|
|
28
|
+
assert missing_root_message(ValueError("Could not find root cache for file."), "/cache/img.fits", "cam") is None
|
|
28
29
|
assert missing_root_message(OSError("Could not find root"), "/cache/img.fits", "cam") is None
|
|
29
30
|
|
|
30
31
|
|
|
@@ -203,3 +203,14 @@ async def test_guard_passes_everything_on() -> None:
|
|
|
203
203
|
|
|
204
204
|
def test_app_name_is_what_windows_uses_as_the_id() -> None:
|
|
205
205
|
assert APP_NAME == "pyobs-gui"
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_default_notifier_uses_packaged_app_icon(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
209
|
+
from desktop_notifier import Icon
|
|
210
|
+
from pyobs_gui.appicon import icon_path
|
|
211
|
+
|
|
212
|
+
factory = MagicMock()
|
|
213
|
+
monkeypatch.setattr("desktop_notifier.DesktopNotifier", factory)
|
|
214
|
+
backend = DesktopNotifierBackend(ensure_identity=MagicMock())
|
|
215
|
+
backend._get()
|
|
216
|
+
factory.assert_called_once_with(app_name=APP_NAME, app_icon=Icon(path=icon_path()))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Any
|
|
4
|
+
from unittest.mock import MagicMock
|
|
4
5
|
|
|
5
6
|
import pytest
|
|
6
7
|
|
|
@@ -34,7 +35,9 @@ def test_an_existing_shortcut_is_left_alone(tmp_path: Path) -> None:
|
|
|
34
35
|
path = tmp_path / SHORTCUT_NAME
|
|
35
36
|
path.write_text("mine")
|
|
36
37
|
create = Recorder()
|
|
37
|
-
|
|
38
|
+
update = MagicMock(return_value=True)
|
|
39
|
+
assert ensure_shortcut("pyobs-gui", path, ("x", ""), create, update) is False
|
|
40
|
+
update.assert_called_once_with(path)
|
|
38
41
|
assert create.calls == [] and path.read_text() == "mine"
|
|
39
42
|
|
|
40
43
|
|
|
@@ -67,3 +70,52 @@ def test_launch_command_for_the_pip_launcher(monkeypatch: pytest.MonkeyPatch, tm
|
|
|
67
70
|
def test_launch_command_otherwise_starts_the_module(monkeypatch: pytest.MonkeyPatch, argv0: Any) -> None:
|
|
68
71
|
monkeypatch.setattr(sys, "argv", [argv0])
|
|
69
72
|
assert launch_command() == (sys.executable, "-m pyobs_gui")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.fixture
|
|
76
|
+
def com_link(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
|
|
77
|
+
"""Exercise our COM calls without requiring a Windows notification service."""
|
|
78
|
+
from types import ModuleType
|
|
79
|
+
|
|
80
|
+
link = MagicMock()
|
|
81
|
+
pythoncom = MagicMock()
|
|
82
|
+
pythoncom.CoCreateInstance.return_value = link
|
|
83
|
+
monkeypatch.setitem(sys.modules, "pythoncom", pythoncom)
|
|
84
|
+
monkeypatch.setitem(sys.modules, "win32com", ModuleType("win32com"))
|
|
85
|
+
monkeypatch.setitem(sys.modules, "win32com.shell", MagicMock())
|
|
86
|
+
monkeypatch.setitem(sys.modules, "win32com.propsys", MagicMock())
|
|
87
|
+
return link
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_new_com_shortcut_has_the_packaged_icon(com_link: MagicMock, tmp_path: Path) -> None:
|
|
91
|
+
from pyobs_gui.appicon import icon_path
|
|
92
|
+
from pyobs_gui.windows_identity import create_link_com
|
|
93
|
+
|
|
94
|
+
create_link_com(tmp_path / SHORTCUT_NAME, "python.exe", "-m pyobs_gui", "pyobs-gui")
|
|
95
|
+
com_link.SetIconLocation.assert_called_once_with(str(icon_path("ico")), 0)
|
|
96
|
+
com_link.SetPath.assert_called_once_with("python.exe")
|
|
97
|
+
com_link.SetArguments.assert_called_once_with("-m pyobs_gui")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_existing_com_shortcut_gets_icon_without_changing_target(com_link: MagicMock, tmp_path: Path) -> None:
|
|
101
|
+
from pyobs_gui.appicon import icon_path
|
|
102
|
+
from pyobs_gui.windows_identity import update_link_icon_com
|
|
103
|
+
|
|
104
|
+
path = tmp_path / SHORTCUT_NAME
|
|
105
|
+
com_link.GetIconLocation.return_value = ("python.exe", 0)
|
|
106
|
+
assert update_link_icon_com(path) is True
|
|
107
|
+
com_link.QueryInterface.return_value.Load.assert_called_once_with(str(path))
|
|
108
|
+
com_link.SetIconLocation.assert_called_once_with(str(icon_path("ico")), 0)
|
|
109
|
+
com_link.QueryInterface.return_value.Save.assert_called_once_with(str(path), 0)
|
|
110
|
+
com_link.SetPath.assert_not_called()
|
|
111
|
+
com_link.SetArguments.assert_not_called()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_matching_com_shortcut_is_not_rewritten(com_link: MagicMock, tmp_path: Path) -> None:
|
|
115
|
+
from pyobs_gui.appicon import icon_path
|
|
116
|
+
from pyobs_gui.windows_identity import update_link_icon_com
|
|
117
|
+
|
|
118
|
+
com_link.GetIconLocation.return_value = (str(icon_path("ico")), 0)
|
|
119
|
+
assert update_link_icon_com(tmp_path / SHORTCUT_NAME) is False
|
|
120
|
+
com_link.SetIconLocation.assert_not_called()
|
|
121
|
+
com_link.QueryInterface.return_value.Save.assert_not_called()
|