From 089de1ff932b097cb37ea5e5a9fc685a1783eb4f Mon Sep 17 00:00:00 2001 From: Christoph Stahl Date: Thu, 10 Oct 2024 15:39:53 +0200 Subject: [PATCH] PyQt now also uses asyncio and its thread contains the main loop --- poetry.lock | 15 +++++++++++++-- pyproject.toml | 1 + syng/client.py | 8 ++++++-- syng/gui.py | 45 ++++++++++++++++++++++++++++++--------------- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8229284..e0b573b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -1452,6 +1452,17 @@ files = [ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +[[package]] +name = "qasync" +version = "0.27.1" +description = "Python library for using asyncio in Qt-based applications" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "qasync-0.27.1-py3-none-any.whl", hash = "sha256:5d57335723bc7d9b328dadd8cb2ed7978640e4bf2da184889ce50ee3ad2602c7"}, + {file = "qasync-0.27.1.tar.gz", hash = "sha256:8dc768fd1ee5de1044c7c305eccf2d39d24d87803ea71189d4024fb475f4985f"}, +] + [[package]] name = "qrcode" version = "7.4.2" @@ -1961,4 +1972,4 @@ server = ["alt-profanity-check"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "a53a034e3cccdb40b71d708df2e82b2e5840639414a3bc727a31cb27b7a0d286" +content-hash = "8d19f746a97720e770c47baed26571c2e53aa79a5e7a5a10f62450f519f211a8" diff --git a/pyproject.toml b/pyproject.toml index ab4b7ba..dee7197 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ pyyaml = { version = "^6.0.1", optional = true } alt-profanity-check = {version = "^1.4.1", optional = true} pyqt6 = {version="^6.7.1", optional = true} mpv = "^1.0.7" +qasync = "^0.27.1" [tool.poetry.group.dev.dependencies] types-pyyaml = "^6.0.12.12" diff --git a/syng/client.py b/syng/client.py index 97ee360..6354ccf 100644 --- a/syng/client.py +++ b/syng/client.py @@ -452,6 +452,7 @@ class Client: :type config: dict[str, Any] :rtype: None """ + print("Starting client") self.loop = asyncio.get_running_loop() @@ -495,7 +496,9 @@ class Client: def create_async_and_start_client( - config: dict[str, Any], queue: Optional[Queue[LogRecord]] = None + config: dict[str, Any], + queue: Optional[Queue[LogRecord]] = None, + client: Optional[Client] = None, ) -> None: """ Create an asyncio event loop and start the client. @@ -512,7 +515,8 @@ def create_async_and_start_client( if queue is not None: logger.addHandler(QueueHandler(queue)) - client = Client(config) + if client is None: + client = Client(config) asyncio.run(client.start_client(config)) diff --git a/syng/gui.py b/syng/gui.py index e8368db..3e624cd 100644 --- a/syng/gui.py +++ b/syng/gui.py @@ -1,15 +1,18 @@ +import asyncio from io import BytesIO import sys import logging from logging.handlers import QueueListener -from multiprocessing import Process, Queue + +# from multiprocessing import Process, Queue +from queue import Queue from collections.abc import Callable from datetime import datetime import os from functools import partial import random from typing import TYPE_CHECKING, Any, Optional -import multiprocessing +import threading import secrets import string import signal @@ -24,11 +27,11 @@ try: except ImportError: pass - +from qasync import QEventLoop, QApplication from PyQt6.QtCore import QTimer, Qt from PyQt6.QtGui import QCloseEvent, QIcon, QPixmap from PyQt6.QtWidgets import ( - QApplication, + # QApplication, QCheckBox, QComboBox, QDateTimeEdit, @@ -54,7 +57,7 @@ from qrcode.main import QRCode import platformdirs from . import resources # noqa -from .client import create_async_and_start_client, default_config +from .client import Client, create_async_and_start_client, default_config from .sources import available_sources from .config import ( @@ -626,8 +629,9 @@ class SyngGui(QMainWindow): if os.name != "nt": self.setWindowIcon(QIcon(":/icons/syng.ico")) - self.syng_server: Optional[Process] = None - self.syng_client: Optional[Process] = None + self.loop = asyncio.get_event_loop() + self.syng_server: Optional[threading.Thread] = None + self.syng_client: Optional[threading.Thread] = None self.syng_client_logging_listener: Optional[QueueListener] = None self.configfile = os.path.join(platformdirs.user_config_dir("syng"), "config.yaml") @@ -752,9 +756,11 @@ class SyngGui(QMainWindow): return if not self.syng_client.is_alive(): + print("Client is not running") self.syng_client = None self.set_client_button_start() else: + print("Client is running") self.set_client_button_stop() def set_client_button_stop(self) -> None: @@ -767,24 +773,28 @@ class SyngGui(QMainWindow): if self.syng_client is None or not self.syng_client.is_alive(): self.save_config() config = self.gather_config() - queue: Queue[logging.LogRecord] = multiprocessing.Queue() + queue: Queue[logging.LogRecord] = Queue() self.syng_client_logging_listener = QueueListener( queue, LoggingLabelHandler(self.notification_label) ) self.syng_client_logging_listener.start() - self.syng_client = multiprocessing.Process( - target=create_async_and_start_client, args=[config, queue] - ) - self.syng_client.start() + # self.syng_client = multiprocessing.Process( + # target=create_async_and_start_client, args=[config, queue] + # ) + self.client = Client(config) + asyncio.run_coroutine_threadsafe(self.client.start_client(config), self.loop) + # self.syng_client = threading.Thread( + # target=create_async_and_start_client, args=[config, queue, self.client] + # ) + # self.syng_client.start() self.notification_label.setText("") self.timer.start(500) self.set_client_button_stop() else: - self.syng_client.terminate() + self.client.quit_callback() self.syng_client.join(1.0) - self.syng_client.kill() self.set_client_button_start() # def start_syng_server(self) -> None: @@ -853,6 +863,9 @@ def run_gui() -> None: signal.signal(signal.SIGINT, signal.SIG_DFL) app = QApplication([]) + event_loop = QEventLoop(app) + asyncio.set_event_loop(event_loop) + if os.name == "nt": app.setWindowIcon(QIcon(os.path.join(base_dir, "syng.ico"))) else: @@ -861,7 +874,9 @@ def run_gui() -> None: app.setDesktopFileName("rocks.syng.Syng") window = SyngGui() window.show() - app.exec() + # app.exec() + with event_loop: + event_loop.run_forever() if __name__ == "__main__":