diff --git a/openfreebuds/driver/generic/spp.py b/openfreebuds/driver/generic/spp.py index 5b766f1..870561e 100644 --- a/openfreebuds/driver/generic/spp.py +++ b/openfreebuds/driver/generic/spp.py @@ -51,8 +51,8 @@ async def stop(self): await self.__task_recv self.__task_recv = None - self._writer.close() - # await self._writer.wait_closed() + if self._writer is not None: + self._writer.close() self._writer = None self.started = False diff --git a/openfreebuds_qt/app/dialog/first_run.py b/openfreebuds_qt/app/dialog/first_run.py index c241492..064b6a7 100644 --- a/openfreebuds_qt/app/dialog/first_run.py +++ b/openfreebuds_qt/app/dialog/first_run.py @@ -2,10 +2,11 @@ import webbrowser from PyQt6.QtCore import pyqtSlot -from PyQt6.QtWidgets import QDialog +from PyQt6.QtWidgets import QDialog, QSystemTrayIcon from qasync import asyncSlot import openfreebuds_backend +from openfreebuds import OfbEventKind from openfreebuds_qt.config import OfbQtConfigParser from openfreebuds_qt.constants import LINK_WEBSITE_HELP from openfreebuds_qt.designer.first_run_dialog import Ui_OfbQtFirstRunDialog @@ -26,7 +27,8 @@ def __init__(self, ctx): self.autostart_checkbox.setChecked(not self.config.is_containerized_app) self.autostart_checkbox.setEnabled(not self.config.is_containerized_app) - self.linux_notice.setVisible(sys.platform == 'linux') + self.background_checkbox.setChecked(QSystemTrayIcon.isSystemTrayAvailable()) + self.background_checkbox.setEnabled(QSystemTrayIcon.isSystemTrayAvailable()) preview_fn = "ofb_linux_preview" if sys.platform == 'linux' else "ofb_win32_preview" preview_image = get_img_colored(preview_fn, @@ -39,12 +41,16 @@ async def on_confirm(self): async with qt_error_handler("OfbQtFirstRunDialog_Confirm", self.ctx): self.hide() - if self.autostart_checkbox.isChecked(): - openfreebuds_backend.set_run_at_boot(True) - + openfreebuds_backend.set_run_at_boot(self.autostart_checkbox.isChecked()) + self.config.set("ui", "background", self.background_checkbox.isChecked()) self.config.set("ui", "first_run_finished", True) self.config.save() + if not self.background_checkbox.isChecked(): + self.ctx.main_window.show() + + await self.ctx.ofb.send_message(OfbEventKind.QT_SETTINGS_CHANGED) + @pyqtSlot() def on_faq_click(self): webbrowser.open(LINK_WEBSITE_HELP) diff --git a/openfreebuds_qt/app/main.py b/openfreebuds_qt/app/main.py index 9b61d03..5b618c1 100644 --- a/openfreebuds_qt/app/main.py +++ b/openfreebuds_qt/app/main.py @@ -5,8 +5,8 @@ from PyQt6.QtCore import pyqtSlot from PyQt6.QtGui import QIcon, QKeySequence -from PyQt6.QtWidgets import QMenu -from qasync import asyncSlot +from PyQt6.QtWidgets import QMenu, QSystemTrayIcon +from qasync import asyncSlot, asyncClose from openfreebuds import IOpenFreebuds, OfbEventKind from openfreebuds.utils.logger import create_logger @@ -34,6 +34,7 @@ def __init__(self, ctx: IOfbQtApplication): self.ctx = ctx self.ofb = ctx.ofb self.config = OfbQtConfigParser.get_instance() + self.tray_available = QSystemTrayIcon.isSystemTrayAvailable() self.setupUi(self) @@ -123,7 +124,7 @@ def _fill_extras_menu(self): hide_action = self.extra_menu.addAction(self.tr("Close this window")) hide_action.setShortcut(QKeySequence('Ctrl+W')) # noinspection PyUnresolvedReferences - hide_action.triggered.connect(self.hide) + hide_action.triggered.connect(self.hide_or_exit) exit_action = self.extra_menu.addAction(self.tr("Exit OpenFreebuds")) exit_action.setShortcut(QKeySequence('Ctrl+Q')) @@ -212,9 +213,14 @@ def _device_section_set_visible(self, visible): def closeEvent(self, e): if self.isVisible(): e.ignore() - self.hide() + self.hide_or_exit() return + def hide_or_exit(self): + self.hide() + if not self.config.get("ui", "background", True) or not self.tray_available: + self.on_exit() + def showEvent(self, e): e.accept() self._ui_update_task = asyncio.create_task(self._update_loop()) diff --git a/openfreebuds_qt/app/module/ui_settings.py b/openfreebuds_qt/app/module/ui_settings.py index 0325a76..5e3e002 100644 --- a/openfreebuds_qt/app/module/ui_settings.py +++ b/openfreebuds_qt/app/module/ui_settings.py @@ -1,6 +1,7 @@ import sys from PyQt6.QtCore import QLocale +from PyQt6.QtWidgets import QSystemTrayIcon from qasync import asyncSlot from openfreebuds import OfbEventKind @@ -11,7 +12,7 @@ from openfreebuds_qt.config import OfbQtConfigParser from openfreebuds_qt.designer.ui_settings import Ui_OfbQtUiSettingsModule from openfreebuds_qt.qt_i18n import get_shortcut_names -from openfreebuds_qt.utils import blocked_signals, list_available_locales +from openfreebuds_qt.utils import blocked_signals, list_available_locales, OfbCoreEvent log = create_logger("OfbQtUiSettingsModule") @@ -67,12 +68,25 @@ def __init__(self, *args, **kwargs): with blocked_signals(self.tray_dc_toggle): self.tray_dc_toggle.setChecked(self.config.get("ui", "tray_show_dual_connect", False)) + async def update_ui(self, event: OfbCoreEvent): + if not event.kind_match(OfbEventKind.QT_SETTINGS_CHANGED): + return + with blocked_signals(self.autostart_toggle): self.autostart_toggle.setChecked(is_run_at_boot()) if self.config.is_containerized_app: self.autostart_toggle.setVisible(False) + with blocked_signals(self.background_toggle): + self.background_toggle.setEnabled(QSystemTrayIcon.isSystemTrayAvailable()) + self.background_toggle.setChecked(self.config.get("ui", "background", True)) + + @asyncSlot(bool) + async def on_background_toggle(self, value: bool): + self.config.set("ui", "background", value) + self.config.save() + @asyncSlot(bool) async def on_autostart_toggle(self, value: bool): set_run_at_boot(value) diff --git a/openfreebuds_qt/designer/first_run_dialog.ui b/openfreebuds_qt/designer/first_run_dialog.ui index c73da20..4ffafca 100644 --- a/openfreebuds_qt/designer/first_run_dialog.ui +++ b/openfreebuds_qt/designer/first_run_dialog.ui @@ -131,26 +131,10 @@ - - - - - 0 - 0 - - - - If you're running under GNOME shell and can't find tray icon, please, check FAQ. - - - true - - - - Qt::Vertical + Qt::Orientation::Vertical @@ -170,6 +154,16 @@ + + + + Mininize to system tray instead of closing + + + true + + + @@ -204,7 +198,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal diff --git a/openfreebuds_qt/designer/main_window.ui b/openfreebuds_qt/designer/main_window.ui index aa29640..3fcbbdf 100644 --- a/openfreebuds_qt/designer/main_window.ui +++ b/openfreebuds_qt/designer/main_window.ui @@ -68,6 +68,9 @@ 16777215 + + Qt::ScrollBarPolicy::ScrollBarAlwaysOff + true @@ -76,8 +79,8 @@ 0 0 - 208 - 598 + 210 + 600 @@ -164,7 +167,7 @@ OpenFreebuds - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter @@ -334,7 +337,7 @@ - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -373,7 +376,7 @@ 10% - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -458,7 +461,7 @@ - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -497,7 +500,7 @@ 10% - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -582,7 +585,7 @@ - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -621,7 +624,7 @@ 10% - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -764,7 +767,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -854,8 +857,8 @@ 0 0 - 738 - 598 + 740 + 600 @@ -865,7 +868,7 @@ - true + false diff --git a/openfreebuds_qt/designer/ui_settings.ui b/openfreebuds_qt/designer/ui_settings.ui index 0f7119b..82ac63f 100644 --- a/openfreebuds_qt/designer/ui_settings.ui +++ b/openfreebuds_qt/designer/ui_settings.ui @@ -27,33 +27,17 @@ Main - - - - Launch at system startup - - - - - + + - Language: + Restart OpenFeebuds to apply changes - - - - - System - - - - - - + + - Restart OpenFeebuds to apply changes + Launch at system startup @@ -83,6 +67,29 @@ + + + + Language: + + + + + + + + System + + + + + + + + Mininize to system tray instead of closing + + + @@ -148,7 +155,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -274,6 +281,22 @@ + + background_toggle + toggled(bool) + OfbQtUiSettingsModule + on_background_toggle() + + + 247 + 202 + + + 247 + 261 + + + on_tray_eq_toggle(bool) @@ -283,5 +306,6 @@ on_tray_shortcut_choose(int) on_autostart_toggle(bool) on_updater_policy_choose(int) + on_background_toggle() diff --git a/openfreebuds_qt/main.py b/openfreebuds_qt/main.py index a5762b7..25874eb 100644 --- a/openfreebuds_qt/main.py +++ b/openfreebuds_qt/main.py @@ -6,7 +6,7 @@ from typing import Optional from PyQt6.QtCore import QLibraryInfo, QLocale, QTranslator, QT_VERSION_STR -from PyQt6.QtWidgets import QMessageBox +from PyQt6.QtWidgets import QMessageBox, QSystemTrayIcon from qasync import QEventLoop from openfreebuds import IOpenFreebuds, create as create_ofb, OfbEventKind @@ -30,6 +30,7 @@ def __init__(self, args): super().__init__(sys.argv) self.args = args + self.tray_available = QSystemTrayIcon.isSystemTrayAvailable() # Config folder if not STORAGE_PATH.is_dir(): @@ -117,9 +118,14 @@ async def boot(self): if float(".".join(QT_VERSION_STR.split(".")[:2])) < 6.7: self.show_old_qt_warning() + # System tray icon not available + if not self.tray_available: + self.show_no_tray_warning() + self.main_window.show() + # Show UI self.tray.show() - if self.args.settings: + if not self.config.get("ui", "background", True) or self.args.settings: self.main_window.show() if not self.config.get("ui", "first_run_finished", False): OfbQtFirstRunDialog(self).show() @@ -132,7 +138,7 @@ async def boot(self): async def exit(self, ret_code: int = 0): await self.tray.close() - self.main_window.close() + self.main_window.hide() if self.ofb.role == "standalone": await self.ofb.destroy() @@ -194,8 +200,28 @@ def exec_async(self): self.event_loop.run_until_complete(self.close_event.wait()) self.event_loop.close() + def show_no_tray_warning(self): + if self.config.get("warn", "no_tray", False): + return + + paragraph_1 = self.tr("System tray not available, application won't work in background. " + "This will make some features, like global hotkeys, unavailable.") + paragraph_2 = self.tr("If you're running under GNOME shell, please, check FAQ. " + "This warning will be shown only once.") + + QMessageBox( + QMessageBox.Icon.Warning, + "OpenFreebuds", + paragraph_1 + "\n\n" + paragraph_2, + QMessageBox.StandardButton.Ok, + self.main_window + ).show() + + self.config.set("warn", "no_tray", True) + self.config.save() + def show_old_qt_warning(self): - if self.config.get("ui", "old_qt", False): + if self.config.get("warn", "old_qt", False): return paragraph_1 = self.tr( @@ -214,5 +240,5 @@ def show_old_qt_warning(self): self.main_window ).show() - self.config.set("ui", "old_qt", True) + self.config.set("warn", "old_qt", True) self.config.save()