diff --git a/openfreebuds_qt/app/dialog/first_run.py b/openfreebuds_qt/app/dialog/first_run.py new file mode 100644 index 0000000..486156e --- /dev/null +++ b/openfreebuds_qt/app/dialog/first_run.py @@ -0,0 +1,46 @@ +import sys +import webbrowser + +from PyQt6.QtCore import pyqtSlot +from PyQt6.QtWidgets import QDialog +from qasync import asyncSlot + +import openfreebuds_backend +from openfreebuds_qt.config import OfbQtConfigParser +from openfreebuds_qt.constants import LINK_WEBSITE_HELP +from openfreebuds_qt.designer.first_run_dialog import Ui_OfbQtFirstRunDialog +from openfreebuds_qt.utils import get_img_colored, qt_error_handler + + +class OfbQtFirstRunDialog(Ui_OfbQtFirstRunDialog, QDialog): + def __init__(self, ctx): + super().__init__(ctx.main_window) + + self.ctx = ctx + self.config = OfbQtConfigParser.get_instance() + + self.setupUi(self) + + self.autostart_checkbox.setEnabled(not self.config.is_containerized_app) + self.linux_notice.setVisible(sys.platform == 'linux') + + preview_fn = "ofb_linux_preview" if sys.platform == 'linux' else "ofb_win32_preview" + preview_image = get_img_colored(preview_fn, + color=self.palette().text().color().getRgb(), + base_dir="image") + self.preview_root.setPixmap(preview_image) + + @asyncSlot() + 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) + + self.config.set("ui", "first_run_finished", True) + self.config.save() + + @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 0f0b72c..7514bdc 100644 --- a/openfreebuds_qt/app/main.py +++ b/openfreebuds_qt/app/main.py @@ -21,7 +21,7 @@ from openfreebuds_qt.constants import ASSETS_PATH, LINK_RPC_HELP, LINK_WEBSITE_HELP from openfreebuds_qt.designer.main_window import Ui_OfbMainWindowDesign from openfreebuds_qt.generic import IOfbQtApplication, IOfbMainWindow -from openfreebuds_qt.utils import qt_error_handler, OfbCoreEvent, OfbQtReportTool, get_qt_icon_colored +from openfreebuds_qt.utils import qt_error_handler, OfbCoreEvent, OfbQtReportTool, get_img_colored log = create_logger("OfbQtMainWindow") @@ -45,7 +45,7 @@ def __init__(self, ctx: IOfbQtApplication): # Extras button self.extra_options_button.setIcon( - get_qt_icon_colored("settings", self.palette().text().color().getRgb()) + QIcon(get_img_colored("settings", self.palette().text().color().getRgb())) ) self.extra_menu = QMenu() diff --git a/openfreebuds_qt/app/module/sound_quality.py b/openfreebuds_qt/app/module/sound_quality.py index b134e12..09a2424 100644 --- a/openfreebuds_qt/app/module/sound_quality.py +++ b/openfreebuds_qt/app/module/sound_quality.py @@ -1,7 +1,6 @@ import asyncio import json -from PIL import Image, ImageQt from PyQt6.QtCore import Qt from PyQt6.QtGui import QIcon from PyQt6.QtWidgets import QSlider, QMenu, QInputDialog, QMessageBox, QFileDialog @@ -10,10 +9,9 @@ from openfreebuds.exceptions import OfbTooManyItemsError from openfreebuds.utils.logger import create_logger from openfreebuds_qt.app.module.common import OfbQtCommonModule -from openfreebuds_qt.constants import ASSETS_PATH from openfreebuds_qt.designer.sound_quality import Ui_OfbQtSoundQualityModule from openfreebuds_qt.qt_i18n import get_eq_preset_names -from openfreebuds_qt.utils import get_qt_icon_colored +from openfreebuds_qt.utils import get_img_colored from openfreebuds_qt.utils.async_dialog import run_dialog_async from openfreebuds_qt.utils.core_event import OfbCoreEvent from openfreebuds_qt.utils.qt_utils import fill_combo_box, blocked_signals, qt_error_handler @@ -42,7 +40,7 @@ def __init__(self, *args, **kwargs): self.setupUi(self) self.undo_btn.setIcon( - get_qt_icon_colored("undo", self.palette().text().color().getRgb()) + QIcon(get_img_colored("undo", self.palette().text().color().getRgb())) ) self.custom_menu = QMenu() diff --git a/openfreebuds_qt/assets/image/ofb_linux_preview.png b/openfreebuds_qt/assets/image/ofb_linux_preview.png new file mode 100644 index 0000000..01e7005 Binary files /dev/null and b/openfreebuds_qt/assets/image/ofb_linux_preview.png differ diff --git a/openfreebuds_qt/assets/image/ofb_win32_preview.png b/openfreebuds_qt/assets/image/ofb_win32_preview.png new file mode 100644 index 0000000..c263a61 Binary files /dev/null and b/openfreebuds_qt/assets/image/ofb_win32_preview.png differ diff --git a/openfreebuds_qt/config/main.py b/openfreebuds_qt/config/main.py index 352122b..4d26787 100644 --- a/openfreebuds_qt/config/main.py +++ b/openfreebuds_qt/config/main.py @@ -68,7 +68,7 @@ def get_tray_icon_theme(self): return "dark" if not backend_theme else "light" # Auto-detect using qt - return self.qt_is_dark_theme + return "dark" if not self.qt_is_dark_theme else "light" @cached_property def is_containerized_app(self): diff --git a/openfreebuds_qt/designer/first_run_dialog.ui b/openfreebuds_qt/designer/first_run_dialog.ui new file mode 100644 index 0000000..06d8bc7 --- /dev/null +++ b/openfreebuds_qt/designer/first_run_dialog.ui @@ -0,0 +1,278 @@ + + + OfbQtFirstRunDialog + + + + 0 + 0 + 570 + 320 + + + + + 16777215 + 320 + + + + Welcome + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 180 + 120 + + + + + 16777215 + 16777215 + + + + Image + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + This application allows you to manage your HUAWEI Bluetooth earphones. To access them, look for headphones icon in system tray panel, near other icons. + + + true + + + + + + + + 0 + 0 + + + + Left-click on this icon will cycle through noise cancellation modes (can be configured), right-click will provide access to full battery status and main options. Settings window provides access to all features. + + + true + + + + + + + + 0 + 0 + + + + If you're running under GNOME shell and can't find tray icon, please, check FAQ. + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Launch OpenFreebuds at system boot + + + true + + + + + + + You could change this options anytime later in settings. + + + true + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + FAQ + + + true + + + + + + + Get started + + + + + + + + + + + + faq_btn + clicked() + OfbQtFirstRunDialog + on_faq_click() + + + 436 + 300 + + + 285 + 160 + + + + + confirm_btn + clicked() + OfbQtFirstRunDialog + on_confirm() + + + 522 + 300 + + + 285 + 160 + + + + + + on_faq_click() + on_confirm() + + diff --git a/openfreebuds_qt/main.py b/openfreebuds_qt/main.py index c823f96..bea6c1e 100644 --- a/openfreebuds_qt/main.py +++ b/openfreebuds_qt/main.py @@ -12,6 +12,7 @@ from openfreebuds import IOpenFreebuds, create as create_ofb, OfbEventKind from openfreebuds.constants import STORAGE_PATH from openfreebuds.utils.logger import setup_logging, screen_handler, create_logger +from openfreebuds_qt.app.dialog.first_run import OfbQtFirstRunDialog from openfreebuds_qt.app.main import OfbQtMainWindow from openfreebuds_qt.config import OfbQtConfigParser, ConfigLock from openfreebuds_qt.constants import IGNORED_LOG_TAGS, I18N_PATH @@ -120,10 +121,14 @@ async def boot(self): self.tray.show() if self.args.settings: self.main_window.show() + if not self.config.get("ui", "first_run_finished", False): + OfbQtFirstRunDialog(self).show() except SystemExit as e: self.qt_app.exit(e.args[0]) ConfigLock.release() return + except Exception: + log.exception("Boot failure") async def exit(self, ret_code: int = 0): await self.tray.close() diff --git a/openfreebuds_qt/tray/main.py b/openfreebuds_qt/tray/main.py index 087155f..76f64ec 100644 --- a/openfreebuds_qt/tray/main.py +++ b/openfreebuds_qt/tray/main.py @@ -75,6 +75,7 @@ async def _update_ui(self, event: OfbCoreEvent): """ state = await self.ofb.get_state() + log.info(f"theme={self.config.get_tray_icon_theme()}") # Update icon icon = create_tray_icon(self.config.get_tray_icon_theme(), diff --git a/openfreebuds_qt/utils/icon/__init__.py b/openfreebuds_qt/utils/icon/__init__.py index f05f282..721fadb 100644 --- a/openfreebuds_qt/utils/icon/__init__.py +++ b/openfreebuds_qt/utils/icon/__init__.py @@ -1,3 +1,3 @@ from .tray_factory import create_tray_icon from .dual_connect_device import create_dual_connect_icon -from .qt_icon import get_qt_icon_colored +from .qt_icon import get_img_colored diff --git a/openfreebuds_qt/utils/icon/qt_icon.py b/openfreebuds_qt/utils/icon/qt_icon.py index 6857c54..1a9e91f 100644 --- a/openfreebuds_qt/utils/icon/qt_icon.py +++ b/openfreebuds_qt/utils/icon/qt_icon.py @@ -1,20 +1,17 @@ import functools from PIL import Image, ImageQt -from PyQt6.QtGui import QIcon +from PyQt6.QtGui import QPixmap from openfreebuds_qt.constants import ASSETS_PATH from openfreebuds_qt.utils.draw import image_combine_mask -ICON_SIZE = (64, 64) -PRESET_TRANSPARENT = Image.new("RGBA", ICON_SIZE, color="#00000000") - @functools.cache -def get_qt_icon_colored(name: str, color) -> QIcon: - image = Image.open(ASSETS_PATH / f"icon/action/{name}.png") +def get_img_colored(name: str, color, base_dir: str = "icon/action") -> QPixmap: + image = Image.open(ASSETS_PATH / f"{base_dir}/{name}.png") image = image_combine_mask(image, - foreground=Image.new("RGBA", ICON_SIZE, color=color), - background=PRESET_TRANSPARENT) + foreground=Image.new("RGBA", image.size, color=color), + background=Image.new("RGBA", image.size, color="#00000000")) - return QIcon(ImageQt.toqpixmap(image)) + return ImageQt.toqpixmap(image)