removed last of tk, communication between gui and client
This commit is contained in:
parent
fdec53a884
commit
d33497d09c
1 changed files with 106 additions and 75 deletions
181
syng/gui.py
181
syng/gui.py
|
@ -6,12 +6,16 @@ from datetime import datetime
|
||||||
import os
|
import os
|
||||||
import builtins
|
import builtins
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
import random
|
||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import secrets
|
import secrets
|
||||||
import string
|
import string
|
||||||
|
import subprocess
|
||||||
|
import signal
|
||||||
|
|
||||||
|
from PyQt6.QtCore import QTimer
|
||||||
from PyQt6.QtGui import QCloseEvent, QIcon, QPixmap
|
from PyQt6.QtGui import QCloseEvent, QIcon, QPixmap
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
|
@ -83,68 +87,45 @@ class OptionFrame(QWidget):
|
||||||
name: str,
|
name: str,
|
||||||
element: QLineEdit,
|
element: QLineEdit,
|
||||||
line: QWidget,
|
line: QWidget,
|
||||||
frame: QWidget,
|
layout: QVBoxLayout,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.list_options[name].remove(element)
|
self.list_options[name].remove(element)
|
||||||
layout = frame.layout()
|
|
||||||
if layout is None:
|
|
||||||
raise ValueError("No layout found")
|
|
||||||
layout.removeWidget(line)
|
layout.removeWidget(line)
|
||||||
line.deleteLater()
|
line.deleteLater()
|
||||||
|
|
||||||
def add_list_element(
|
def add_list_element(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
frame: QWidget,
|
layout: QVBoxLayout,
|
||||||
init: str,
|
init: str,
|
||||||
callback: Optional[Callable[..., None]],
|
callback: Optional[Callable[..., None]],
|
||||||
) -> None:
|
) -> None:
|
||||||
input_and_minus = QWidget(frame)
|
input_and_minus = QWidget()
|
||||||
input_and_minus_layout = QHBoxLayout(input_and_minus)
|
input_and_minus_layout = QHBoxLayout(input_and_minus)
|
||||||
input_and_minus.setLayout(input_and_minus_layout)
|
input_and_minus.setLayout(input_and_minus_layout)
|
||||||
|
|
||||||
input_field = QLineEdit(frame)
|
input_and_minus_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
input_field = QLineEdit(input_and_minus)
|
||||||
input_field.setText(init)
|
input_field.setText(init)
|
||||||
input_field.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
input_field.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
input_and_minus_layout.addWidget(input_field)
|
input_and_minus_layout.addWidget(input_field)
|
||||||
if callback is not None:
|
if callback is not None:
|
||||||
input_field.textChanged.connect(callback)
|
input_field.textChanged.connect(callback)
|
||||||
|
|
||||||
minus_button = QPushButton("-", frame)
|
minus_button = QPushButton("-", input_and_minus)
|
||||||
minus_button.clicked.connect(
|
minus_button.clicked.connect(
|
||||||
partial(self.del_list_element, name, input_field, input_and_minus, frame)
|
partial(self.del_list_element, name, input_field, input_and_minus, layout)
|
||||||
)
|
)
|
||||||
minus_button.setFixedWidth(40)
|
minus_button.setFixedWidth(40)
|
||||||
minus_button.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
minus_button.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||||
input_and_minus_layout.addWidget(minus_button)
|
input_and_minus_layout.addWidget(minus_button)
|
||||||
|
|
||||||
layout = frame.layout()
|
|
||||||
|
|
||||||
if not isinstance(layout, QVBoxLayout):
|
|
||||||
raise ValueError("No layout found")
|
|
||||||
# insert second from last
|
|
||||||
layout.insertWidget(layout.count() - 1, input_and_minus)
|
layout.insertWidget(layout.count() - 1, input_and_minus)
|
||||||
|
|
||||||
self.list_options[name].append(input_field)
|
self.list_options[name].append(input_field)
|
||||||
|
|
||||||
# input_and_minus = customtkinter.CTkFrame(frame)
|
|
||||||
# input_and_minus.pack(side="top", fill="x", expand=True)
|
|
||||||
# input_field = customtkinter.CTkTextbox(input_and_minus, wrap="none", height=1)
|
|
||||||
# input_field.pack(side="left", fill="x", expand=True)
|
|
||||||
# input_field.insert("0.0", init)
|
|
||||||
# if callback is not None:
|
|
||||||
# input_field.bind("<KeyRelease>", callback)
|
|
||||||
# input_field.bind("<ButtonRelease>", callback)
|
|
||||||
#
|
|
||||||
# minus_button = customtkinter.CTkButton(
|
|
||||||
# input_and_minus,
|
|
||||||
# text="-",
|
|
||||||
# width=40,
|
|
||||||
# command=partial(self.del_list_element, name, input_field, input_and_minus),
|
|
||||||
# )
|
|
||||||
# minus_button.pack(side="right")
|
|
||||||
# self.list_options[name].append(input_field)
|
|
||||||
|
|
||||||
def add_list_option(
|
def add_list_option(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
|
@ -154,29 +135,20 @@ class OptionFrame(QWidget):
|
||||||
) -> None:
|
) -> None:
|
||||||
label = QLabel(description, self)
|
label = QLabel(description, self)
|
||||||
|
|
||||||
container = QWidget(self)
|
container_layout = QVBoxLayout()
|
||||||
container_layout = QVBoxLayout(container)
|
|
||||||
container.setLayout(container_layout)
|
|
||||||
|
|
||||||
self.form_layout.addRow(label, container)
|
self.form_layout.addRow(label, container_layout)
|
||||||
|
|
||||||
# frame = customtkinter.CTkFrame(self)
|
|
||||||
# frame.grid(column=1, row=self.number_of_options, sticky="EW")
|
|
||||||
|
|
||||||
self.list_options[name] = []
|
self.list_options[name] = []
|
||||||
for v in value:
|
for v in value:
|
||||||
self.add_list_element(name, container, v, callback)
|
self.add_list_element(name, container_layout, v, callback)
|
||||||
plus_button = QPushButton("+", self)
|
plus_button = QPushButton("+", self)
|
||||||
plus_button.clicked.connect(partial(self.add_list_element, name, container, "", callback))
|
plus_button.clicked.connect(
|
||||||
|
partial(self.add_list_element, name, container_layout, "", callback)
|
||||||
|
)
|
||||||
plus_button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
plus_button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
|
|
||||||
# customtkinter.CTkButton(
|
|
||||||
# frame,
|
|
||||||
# text="+",
|
|
||||||
# command=partial(self.add_list_element, name, frame, "", callback),
|
|
||||||
# )
|
|
||||||
container_layout.addWidget(plus_button)
|
container_layout.addWidget(plus_button)
|
||||||
# plus_button.pack(side="bottom", fill="x", expand=True)
|
|
||||||
|
|
||||||
self.number_of_options += 1
|
self.number_of_options += 1
|
||||||
|
|
||||||
|
@ -193,13 +165,27 @@ class OptionFrame(QWidget):
|
||||||
|
|
||||||
def add_date_time_option(self, name: str, description: str, value: str) -> None:
|
def add_date_time_option(self, name: str, description: str, value: str) -> None:
|
||||||
label = QLabel(description, self)
|
label = QLabel(description, self)
|
||||||
self.date_time_options[name] = QDateTimeEdit(self)
|
date_time_layout = QHBoxLayout()
|
||||||
try:
|
date_time_widget = QDateTimeEdit(self)
|
||||||
self.date_time_options[name].setDateTime(datetime.fromisoformat(value))
|
date_time_enabled = QCheckBox("Enabled", self)
|
||||||
except (TypeError, ValueError):
|
date_time_enabled.stateChanged.connect(
|
||||||
self.date_time_options[name].setDateTime(datetime.now()) # TODO
|
lambda: date_time_widget.setEnabled(date_time_enabled.isChecked())
|
||||||
|
)
|
||||||
|
|
||||||
self.form_layout.addRow(label, self.date_time_options[name])
|
self.date_time_options[name] = (date_time_widget, date_time_enabled)
|
||||||
|
date_time_widget.setCalendarPopup(True)
|
||||||
|
try:
|
||||||
|
date_time_widget.setDateTime(datetime.fromisoformat(value))
|
||||||
|
date_time_enabled.setChecked(True)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
date_time_widget.setDateTime(datetime.now())
|
||||||
|
date_time_widget.setEnabled(False)
|
||||||
|
date_time_enabled.setChecked(False)
|
||||||
|
|
||||||
|
date_time_layout.addWidget(date_time_widget)
|
||||||
|
date_time_layout.addWidget(date_time_enabled)
|
||||||
|
|
||||||
|
self.form_layout.addRow(label, date_time_layout)
|
||||||
|
|
||||||
self.number_of_options += 1
|
self.number_of_options += 1
|
||||||
|
|
||||||
|
@ -212,7 +198,7 @@ class OptionFrame(QWidget):
|
||||||
self.choose_options: dict[str, QComboBox] = {}
|
self.choose_options: dict[str, QComboBox] = {}
|
||||||
self.bool_options: dict[str, QCheckBox] = {}
|
self.bool_options: dict[str, QCheckBox] = {}
|
||||||
self.list_options: dict[str, list[QLineEdit]] = {}
|
self.list_options: dict[str, list[QLineEdit]] = {}
|
||||||
self.date_time_options: dict[str, QDateTimeEdit] = {}
|
self.date_time_options: dict[str, tuple[QDateTimeEdit, QCheckBox]] = {}
|
||||||
|
|
||||||
def get_config(self) -> dict[str, Any]:
|
def get_config(self) -> dict[str, Any]:
|
||||||
config: dict[str, Any] = {}
|
config: dict[str, Any] = {}
|
||||||
|
@ -230,7 +216,10 @@ class OptionFrame(QWidget):
|
||||||
for textbox in textboxes:
|
for textbox in textboxes:
|
||||||
config[name].append(textbox.text().strip())
|
config[name].append(textbox.text().strip())
|
||||||
|
|
||||||
for name, picker in self.date_time_options.items():
|
for name, (picker, checkbox) in self.date_time_options.items():
|
||||||
|
if not checkbox.isChecked():
|
||||||
|
config[name] = None
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
config[name] = picker.dateTime().toPyDateTime().isoformat()
|
config[name] = picker.dateTime().toPyDateTime().isoformat()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -266,18 +255,18 @@ class GeneralConfig(OptionFrame):
|
||||||
|
|
||||||
self.add_string_option("server", "Server", config["server"], callback)
|
self.add_string_option("server", "Server", config["server"], callback)
|
||||||
self.add_string_option("room", "Room", config["room"], callback)
|
self.add_string_option("room", "Room", config["room"], callback)
|
||||||
self.add_string_option("secret", "Secret", config["secret"])
|
self.add_string_option("secret", "Admin Password", config["secret"])
|
||||||
self.add_choose_option(
|
self.add_choose_option(
|
||||||
"waiting_room_policy",
|
"waiting_room_policy",
|
||||||
"Waiting room policy",
|
"Waiting room policy",
|
||||||
["forced", "optional", "none"],
|
["forced", "optional", "none"],
|
||||||
str(config["waiting_room_policy"]).lower(),
|
str(config["waiting_room_policy"]).lower(),
|
||||||
)
|
)
|
||||||
self.add_date_time_option("last_song", "Time of last song", config["last_song"])
|
self.add_date_time_option("last_song", "Last song ends at", config["last_song"])
|
||||||
self.add_string_option(
|
self.add_string_option(
|
||||||
"preview_duration", "Preview duration in seconds", str(config["preview_duration"])
|
"preview_duration", "Preview duration in seconds", str(config["preview_duration"])
|
||||||
)
|
)
|
||||||
self.add_string_option("key", "Key for server", config["key"])
|
self.add_string_option("key", "Key for server (if necessary)", config["key"])
|
||||||
|
|
||||||
def get_config(self) -> dict[str, Any]:
|
def get_config(self) -> dict[str, Any]:
|
||||||
config = super().get_config()
|
config = super().get_config()
|
||||||
|
@ -297,9 +286,9 @@ class SyngGui(QMainWindow):
|
||||||
|
|
||||||
if self.syng_client is not None:
|
if self.syng_client is not None:
|
||||||
self.syng_client.terminate()
|
self.syng_client.terminate()
|
||||||
self.syng_client.join()
|
self.syng_client.wait(1.0)
|
||||||
|
self.syng_client.kill()
|
||||||
|
|
||||||
# self.withdraw()
|
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def add_buttons(self) -> None:
|
def add_buttons(self) -> None:
|
||||||
|
@ -311,13 +300,18 @@ class SyngGui(QMainWindow):
|
||||||
self.buttons_layout.addWidget(self.startsyng_serverbutton)
|
self.buttons_layout.addWidget(self.startsyng_serverbutton)
|
||||||
|
|
||||||
spacer_item = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
spacer_item = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||||
|
self.notification_label = QLabel("", self)
|
||||||
|
spacer_item2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||||
self.buttons_layout.addItem(spacer_item)
|
self.buttons_layout.addItem(spacer_item)
|
||||||
|
self.buttons_layout.addWidget(self.notification_label)
|
||||||
|
self.buttons_layout.addItem(spacer_item2)
|
||||||
|
|
||||||
self.savebutton = QPushButton("Save")
|
self.savebutton = QPushButton("Save")
|
||||||
self.savebutton.clicked.connect(self.save_config)
|
self.savebutton.clicked.connect(self.save_config)
|
||||||
self.buttons_layout.addWidget(self.savebutton)
|
self.buttons_layout.addWidget(self.savebutton)
|
||||||
|
|
||||||
self.startbutton = QPushButton("Save and Start")
|
self.startbutton = QPushButton("Save and Start")
|
||||||
|
|
||||||
self.startbutton.clicked.connect(self.start_syng_client)
|
self.startbutton.clicked.connect(self.start_syng_client)
|
||||||
self.buttons_layout.addWidget(self.startbutton)
|
self.buttons_layout.addWidget(self.startbutton)
|
||||||
|
|
||||||
|
@ -369,11 +363,11 @@ class SyngGui(QMainWindow):
|
||||||
|
|
||||||
rel_path = os.path.dirname(__file__)
|
rel_path = os.path.dirname(__file__)
|
||||||
qt_img = QPixmap(os.path.join(rel_path, "static/syng.png"))
|
qt_img = QPixmap(os.path.join(rel_path, "static/syng.png"))
|
||||||
qt_icon = QIcon(qt_img)
|
self.qt_icon = QIcon(qt_img)
|
||||||
self.setWindowIcon(qt_icon)
|
self.setWindowIcon(self.qt_icon)
|
||||||
|
|
||||||
self.syng_server: Optional[Process] = None
|
self.syng_server: Optional[Process] = None
|
||||||
self.syng_client: Optional[Process] = None
|
self.syng_client: Optional[subprocess.Popen] = None
|
||||||
|
|
||||||
self.configfile = os.path.join(platformdirs.user_config_dir("syng"), "config.yaml")
|
self.configfile = os.path.join(platformdirs.user_config_dir("syng"), "config.yaml")
|
||||||
|
|
||||||
|
@ -394,6 +388,12 @@ class SyngGui(QMainWindow):
|
||||||
config["config"]["secret"] = "".join(
|
config["config"]["secret"] = "".join(
|
||||||
secrets.choice(string.ascii_letters + string.digits) for _ in range(8)
|
secrets.choice(string.ascii_letters + string.digits) for _ in range(8)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if config["config"]["room"] == "":
|
||||||
|
config["config"]["room"] = "".join(
|
||||||
|
[random.choice(string.ascii_letters) for _ in range(6)]
|
||||||
|
).upper()
|
||||||
|
|
||||||
self.central_widget = QWidget(parent=self)
|
self.central_widget = QWidget(parent=self)
|
||||||
self.central_layout = QVBoxLayout(self.central_widget)
|
self.central_layout = QVBoxLayout(self.central_widget)
|
||||||
|
|
||||||
|
@ -416,6 +416,10 @@ class SyngGui(QMainWindow):
|
||||||
|
|
||||||
self.setCentralWidget(self.central_widget)
|
self.setCentralWidget(self.central_widget)
|
||||||
|
|
||||||
|
# check every 100 ms if client is running
|
||||||
|
self.timer = QTimer()
|
||||||
|
self.timer.timeout.connect(self.check_if_client_is_running)
|
||||||
|
|
||||||
def save_config(self) -> None:
|
def save_config(self) -> None:
|
||||||
os.makedirs(os.path.dirname(self.configfile), exist_ok=True)
|
os.makedirs(os.path.dirname(self.configfile), exist_ok=True)
|
||||||
|
|
||||||
|
@ -431,21 +435,43 @@ class SyngGui(QMainWindow):
|
||||||
|
|
||||||
return {"sources": sources, "config": general_config}
|
return {"sources": sources, "config": general_config}
|
||||||
|
|
||||||
def start_syng_client(self) -> None:
|
def check_if_client_is_running(self) -> None:
|
||||||
if self.syng_client is None:
|
if self.syng_client is None:
|
||||||
|
self.timer.stop()
|
||||||
|
return
|
||||||
|
|
||||||
|
ret = self.syng_client.poll()
|
||||||
|
if ret is not None:
|
||||||
|
_, stderr = self.syng_client.communicate()
|
||||||
|
stderr_lines = stderr.decode("utf-8").strip().split("\n")
|
||||||
|
if stderr_lines and stderr_lines[-1].startswith("Warning"):
|
||||||
|
self.notification_label.setText(stderr_lines[-1])
|
||||||
|
else:
|
||||||
|
self.notification_label.setText("")
|
||||||
|
self.syng_client.wait()
|
||||||
|
self.syng_client = None
|
||||||
|
self.set_client_button_start()
|
||||||
|
else:
|
||||||
|
self.set_client_button_stop()
|
||||||
|
|
||||||
|
def set_client_button_stop(self) -> None:
|
||||||
|
self.startbutton.setText("Stop")
|
||||||
|
|
||||||
|
def set_client_button_start(self) -> None:
|
||||||
|
self.startbutton.setText("Save and Start")
|
||||||
|
|
||||||
|
def start_syng_client(self) -> None:
|
||||||
|
if self.syng_client is None or self.syng_client.poll() is not None:
|
||||||
self.save_config()
|
self.save_config()
|
||||||
config = self.gather_config()
|
self.syng_client = subprocess.Popen(["syng", "client"], stderr=subprocess.PIPE)
|
||||||
self.syng_client = multiprocessing.Process(
|
self.notification_label.setText("")
|
||||||
target=create_async_and_start_client, args=(config,)
|
self.timer.start()
|
||||||
)
|
self.set_client_button_stop()
|
||||||
self.syng_client.start()
|
|
||||||
self.startbutton.setText("Stop")
|
|
||||||
else:
|
else:
|
||||||
self.syng_client.terminate()
|
self.syng_client.terminate()
|
||||||
self.syng_client.join()
|
self.syng_client.wait(1.0)
|
||||||
self.syng_client = None
|
self.syng_client.kill()
|
||||||
# self.startbutton.configure(text="Save and Start")
|
self.set_client_button_start()
|
||||||
self.startbutton.setText("Save and Start")
|
|
||||||
|
|
||||||
def start_syng_server(self) -> None:
|
def start_syng_server(self) -> None:
|
||||||
if self.syng_server is None:
|
if self.syng_server is None:
|
||||||
|
@ -494,11 +520,16 @@ class SyngGui(QMainWindow):
|
||||||
syng_server = config["server"]
|
syng_server = config["server"]
|
||||||
syng_server += "" if syng_server.endswith("/") else "/"
|
syng_server += "" if syng_server.endswith("/") else "/"
|
||||||
room = config["room"]
|
room = config["room"]
|
||||||
self.linklabel.setText(f'<a href="{syng_server + room}">{syng_server + room}</a>')
|
self.linklabel.setText(
|
||||||
|
f'<center><a href="{syng_server + room}">{syng_server + room}</a><center>'
|
||||||
|
)
|
||||||
|
self.linklabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
self.change_qr(syng_server + room)
|
self.change_qr(syng_server + room)
|
||||||
|
|
||||||
|
|
||||||
def run_gui() -> None:
|
def run_gui() -> None:
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
app.setApplicationName("Syng")
|
app.setApplicationName("Syng")
|
||||||
app.setDesktopFileName("rocks.syng.Syng")
|
app.setDesktopFileName("rocks.syng.Syng")
|
||||||
|
|
Loading…
Add table
Reference in a new issue