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()