diff --git a/syng/client.py b/syng/client.py index 7f66cc3..c47d0ef 100644 --- a/syng/client.py +++ b/syng/client.py @@ -36,7 +36,7 @@ from socketio.exceptions import ConnectionError import engineio from yaml import load, Loader -from syng.player_libmpv import Player +from syng.player_libmpv import Player, QRPosition from . import jsonencoder from .entry import Entry @@ -60,6 +60,8 @@ def default_config() -> dict[str, Optional[int | str]]: "waiting_room_policy": None, "key": None, "buffer_in_advance": 2, + "qr_box_size": 5, + "qr_position": "bottom-left", "show_advanced": False, } @@ -102,6 +104,15 @@ class State: - `None`, performers are always added to the queue. * `buffer_in_advance` (`int`): The number of songs, that are buffered in advance. + * `qr_box_size` (`int`): The size of one box in the QR code. + * `qr_position` (`str`): The position of the QR code on the screen. One of: + - `top-left` + - `top-right` + - `bottom-left` + - `bottom-right` + * `show_advanced` (`bool`): If the advanced options should be shown in the + gui. + :type config: dict[str, Any]: """ @@ -128,7 +139,10 @@ class Client: self.currentLock = asyncio.Semaphore(0) self.buffer_in_advance = config["config"]["buffer_in_advance"] self.player = Player( - f"{config['config']['server']}/{config['config']['room']}", self.quit_callback + f"{config['config']['server']}/{config['config']['room']}", + 1 if config["config"]["qr_box_size"] < 1 else config["config"]["qr_box_size"], + QRPosition.from_string(config["config"]["qr_position"]), + self.quit_callback, ) self.register_handlers() diff --git a/syng/gui.py b/syng/gui.py index f1329c3..33b222c 100644 --- a/syng/gui.py +++ b/syng/gui.py @@ -330,6 +330,17 @@ class OptionFrame(QWidget): self.date_time_options: dict[str, tuple[QDateTimeEdit, QCheckBox]] = {} self.rows: dict[str, tuple[QLabel, QWidget | QLayout]] = {} + @property + def option_names(self) -> set[str]: + return set( + self.string_options.keys() + | self.int_options.keys() + | self.choose_options.keys() + | self.bool_options.keys() + | self.list_options.keys() + | self.date_time_options.keys() + ) + def get_config(self) -> dict[str, Any]: config: dict[str, Any] = {} for name, textbox in self.string_options.items(): @@ -446,15 +457,18 @@ class GeneralConfig(OptionFrame): "Buffer the next songs in advance", int(config["buffer_in_advance"]), ) + self.add_int_option("qr_box_size", "QR Code Box Size", int(config["qr_box_size"])) + self.add_choose_option( + "qr_position", + "QR Code Position", + ["top-left", "top-right", "bottom-left", "bottom-right"], + config["qr_position"], + ) + + self.simple_options = ["server", "room", "secret"] if not config["show_advanced"]: - for option in [ - "waiting_room_policy", - "last_song", - "preview_duration", - "key", - "buffer_in_advance", - ]: + for option in self.option_names.difference(self.simple_options): self.rows[option][0].setVisible(False) widget_or_layout = self.rows[option][1] if isinstance(widget_or_layout, QWidget): @@ -518,13 +532,9 @@ class SyngGui(QMainWindow): self.exportbutton.setVisible(state) self.importbutton.setVisible(state) - for option in [ - "waiting_room_policy", - "last_song", - "preview_duration", - "key", - "buffer_in_advance", - ]: + for option in self.general_config.option_names.difference( + self.general_config.simple_options + ): self.general_config.rows[option][0].setVisible(state) widget_or_layout = self.general_config.rows[option][1] if isinstance(widget_or_layout, QWidget): diff --git a/syng/player_libmpv.py b/syng/player_libmpv.py index 193ddd6..871528f 100644 --- a/syng/player_libmpv.py +++ b/syng/player_libmpv.py @@ -1,4 +1,5 @@ import asyncio +from enum import Enum import locale import sys from typing import Callable, Iterable, Optional, cast @@ -9,8 +10,35 @@ import os from .entry import Entry +class QRPosition(Enum): + TOP_LEFT = 1 + TOP_RIGHT = 2 + BOTTOM_LEFT = 3 + BOTTOM_RIGHT = 4 + + @staticmethod + def from_string(value: str) -> "QRPosition": + match value: + case "top-left": + return QRPosition.TOP_LEFT + case "top-right": + return QRPosition.TOP_RIGHT + case "bottom-left": + return QRPosition.BOTTOM_LEFT + case "bottom-right": + return QRPosition.BOTTOM_RIGHT + case _: + return QRPosition.BOTTOM_LEFT + + class Player: - def __init__(self, qr_string: str, quit_callback: Callable[[], None]) -> None: + def __init__( + self, + qr_string: str, + qr_box_size: int, + qr_position: QRPosition, + quit_callback: Callable[[], None], + ) -> None: locale.setlocale(locale.LC_ALL, "C") self.base_dir = f"{os.path.dirname(__file__)}/static" @@ -19,6 +47,8 @@ class Player: self.closing = False self.mpv: Optional[mpv.MPV] = None self.qr_overlay: Optional[mpv.ImageOverlay] = None + self.qr_box_size = qr_box_size + self.qr_position = qr_position self.update_qr( qr_string, ) @@ -47,7 +77,7 @@ class Player: self.quit_callback() def update_qr(self, qr_string: str) -> None: - qr = QRCode(box_size=5, border=1) + qr = QRCode(box_size=self.qr_box_size, border=1) qr.add_data(qr_string) qr.make() self.qr = qr.make_image().convert("RGBA") @@ -62,8 +92,19 @@ class Player: osd_width: int = cast(int, self.mpv.osd_width) osd_height: int = cast(int, self.mpv.osd_height) - x_pos = osd_width - self.qr.width - 10 - y_pos = osd_height - self.qr.height - 10 + match self.qr_position: + case QRPosition.BOTTOM_LEFT: + x_pos = osd_width - self.qr.width - 10 + y_pos = osd_height - self.qr.height - 10 + case QRPosition.BOTTOM_RIGHT: + x_pos = 10 + y_pos = osd_height - self.qr.height - 10 + case QRPosition.TOP_LEFT: + x_pos = osd_width - self.qr.width - 10 + y_pos = 10 + case QRPosition.TOP_RIGHT: + x_pos = 10 + y_pos = 10 self.qr_overlay = self.mpv.create_image_overlay(self.qr, pos=(x_pos, y_pos))