From 37dab36fc9c5a37a49a0ce723bb6d86e843f8c91 Mon Sep 17 00:00:00 2001 From: arabcoders Date: Mon, 7 Jul 2025 18:37:11 +0300 Subject: [PATCH] support running in pywebview in windows. --- .vscode/settings.json | 1 + app/library/PackageInstaller.py | 12 ++++++++++-- app/library/Segments.py | 3 +++ app/library/config.py | 8 +++++++- app/library/ffprobe.py | 10 +++++++++- app/main.py | 3 +++ app/native.py | 24 +++++++++++++++++------- app/routes/socket/terminal.py | 4 +++- 8 files changed, 53 insertions(+), 12 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e5d1c852..63b8efe4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,7 @@ "consoletitle", "cookiesfrombrowser", "copyts", + "creationflags", "cronsim", "datas", "dateparser", diff --git a/app/library/PackageInstaller.py b/app/library/PackageInstaller.py index 352e99ac..ef6faa97 100644 --- a/app/library/PackageInstaller.py +++ b/app/library/PackageInstaller.py @@ -42,10 +42,18 @@ class PackageInstaller: return LOG.info(f"'{pkg}' is already installed. Checking for upgrades...") - subprocess.run([sys.executable, "-m", "pip", "install", "--upgrade", pkg], check=True) # noqa: S603 + subprocess.run( + [sys.executable, "-m", "pip", "install", "--upgrade", pkg], + check=True, + creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0, + ) except ImportError: LOG.info(f"'{pkg}' is not installed. Installing...") - subprocess.run([sys.executable, "-m", "pip", "install", pkg], check=True) # noqa: S603 + subprocess.run( + [sys.executable, "-m", "pip", "install", pkg], + check=True, + creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0, + ) def check(self, pkgs: Packages): """ diff --git a/app/library/Segments.py b/app/library/Segments.py index 8473e0ff..963ffda8 100644 --- a/app/library/Segments.py +++ b/app/library/Segments.py @@ -1,6 +1,8 @@ import asyncio import hashlib import logging +import os +import subprocess import tempfile from pathlib import Path @@ -86,6 +88,7 @@ class Segments: stdin=asyncio.subprocess.DEVNULL, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0, ) client_disconnected = False diff --git a/app/library/config.py b/app/library/config.py index 271feb07..0708bfcf 100644 --- a/app/library/config.py +++ b/app/library/config.py @@ -348,7 +348,10 @@ class Config: raise TypeError(msg) coloredlogs.install( - level=numeric_level, fmt="%(asctime)s [%(name)s] [%(levelname)-5.5s] %(message)s", datefmt="%H:%M:%S" + level=numeric_level, + fmt="%(asctime)s [%(name)s] [%(levelname)-5.5s] %(message)s", + datefmt="%H:%M:%S", + encoding="utf-8", ) LOG = logging.getLogger("config") @@ -427,6 +430,7 @@ class Config: filename=loggingPath / "app.log", when="midnight", backupCount=3, + encoding="utf-8", ) handler.setLevel(log_level_file) @@ -545,6 +549,7 @@ class Config: capture_output=True, text=True, check=False, + creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0, ) if 0 != branch_result.returncode: @@ -562,6 +567,7 @@ class Config: capture_output=True, text=True, check=False, + creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0, ) if 0 != commit_result.returncode: diff --git a/app/library/ffprobe.py b/app/library/ffprobe.py index cb7d18da..94c2e1df 100644 --- a/app/library/ffprobe.py +++ b/app/library/ffprobe.py @@ -7,6 +7,7 @@ import functools import json import operator import os +import subprocess from pathlib import Path import anyio @@ -267,7 +268,13 @@ async def ffprobe(file: str) -> FFProbeResult: """ try: async with await anyio.open_file(os.devnull, "w") as tempf: - await asyncio.create_subprocess_exec("ffprobe", "-h", stdout=tempf, stderr=tempf) + await asyncio.create_subprocess_exec( + "ffprobe", + "-h", + stdout=tempf, + stderr=tempf, + creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0, + ) except FileNotFoundError as e: msg = "ffprobe not found." raise OSError(msg) from e @@ -283,6 +290,7 @@ async def ffprobe(file: str) -> FFProbeResult: *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0, ) exitCode = await p.wait() diff --git a/app/main.py b/app/main.py index 55be9872..702082ec 100644 --- a/app/main.py +++ b/app/main.py @@ -155,4 +155,7 @@ class Main: if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) + from multiprocessing import freeze_support + + freeze_support() Main().start() diff --git a/app/native.py b/app/native.py index eacb67da..b486522a 100644 --- a/app/native.py +++ b/app/native.py @@ -107,26 +107,33 @@ def run_backend(host: str, port: int, ready_event: threading.Event, error_queue: ready_event.set() +def get_usable_port(host: str = "127.0.0.1") -> int: + """ + Get a usable port on the specified host. + If no port is available, it will raise an OSError. + """ + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((host, 0)) + port = s.getsockname()[1] + s.close() + + return port + + def main(): host = "127.0.0.1" set_env() cfg_path: Path = Path(os.getenv("YTP_CONFIG_PATH")) / "webview.json" - port = None win_conf: dict[str, int] = {} if cfg_path.exists(): data = json.loads(cfg_path.read_text()) - port = data.get("port") for key in ("width", "height", "x", "y"): if key in data: win_conf[key] = data[key] - if not port: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind((host, 0)) - port = s.getsockname()[1] - cfg_path.write_text(json.dumps({"port": port})) + port = get_usable_port(host) ready = threading.Event() errors: queue.Queue = queue.Queue() @@ -170,6 +177,9 @@ def main(): if __name__ == "__main__": try: + from multiprocessing import freeze_support + + freeze_support() main() except Exception as e: logging.exception(e) diff --git a/app/routes/socket/terminal.py b/app/routes/socket/terminal.py index 4ff563ce..17c2214b 100644 --- a/app/routes/socket/terminal.py +++ b/app/routes/socket/terminal.py @@ -1,4 +1,5 @@ import logging +import os from typing import TYPE_CHECKING from app.library.config import Config @@ -25,8 +26,8 @@ async def cli_post(config: Config, notify: EventBus, sid: str, data: str): import asyncio import errno - import os import shlex + import subprocess # ignore try: LOG.info(f"Cli command from client '{sid}'. '{data}'") @@ -66,6 +67,7 @@ async def cli_post(config: Config, notify: EventBus, sid: str, data: str): stdout=stdout_arg, stderr=stderr_arg, env=_env, + creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0, ) if use_pty: