|
@@ -1,13 +1,16 @@
|
|
|
import _thread
|
|
|
-import multiprocessing
|
|
|
+import logging
|
|
|
+import multiprocessing as mp
|
|
|
+import queue
|
|
|
import socket
|
|
|
import sys
|
|
|
import tempfile
|
|
|
import time
|
|
|
import warnings
|
|
|
-from threading import Thread
|
|
|
+from threading import Event, Thread
|
|
|
+from typing import Any, Callable, Dict, List, Tuple
|
|
|
|
|
|
-from . import globals, helpers
|
|
|
+from . import globals, helpers, native
|
|
|
|
|
|
try:
|
|
|
with warnings.catch_warnings():
|
|
@@ -18,7 +21,10 @@ except ModuleNotFoundError:
|
|
|
pass
|
|
|
|
|
|
|
|
|
-def open_window(host: str, port: int, title: str, width: int, height: int, fullscreen: bool) -> None:
|
|
|
+def open_window(
|
|
|
+ host: str, port: int, title: str, width: int, height: int, fullscreen: bool,
|
|
|
+ method_queue: mp.Queue, response_queue: mp.Queue,
|
|
|
+) -> None:
|
|
|
while not helpers.is_port_open(host, port):
|
|
|
time.sleep(0.1)
|
|
|
|
|
@@ -26,13 +32,60 @@ def open_window(host: str, port: int, title: str, width: int, height: int, fulls
|
|
|
window_kwargs.update(globals.app.native.window_args)
|
|
|
|
|
|
try:
|
|
|
- webview.create_window(**window_kwargs)
|
|
|
+ window = webview.create_window(**window_kwargs)
|
|
|
+ closing = Event()
|
|
|
+ window.events.closing += closing.set
|
|
|
+ start_window_method_executor(window, method_queue, response_queue, closing)
|
|
|
webview.start(storage_path=tempfile.mkdtemp(), **globals.app.native.start_args)
|
|
|
except NameError:
|
|
|
- print('Native mode is not supported in this configuration. Please install pywebview to use it.')
|
|
|
+ logging.error('Native mode is not supported in this configuration. Please install pywebview to use it.')
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
+def start_window_method_executor(
|
|
|
+ window: webview.Window, method_queue: mp.Queue, response_queue: mp.Queue, closing: Event
|
|
|
+) -> None:
|
|
|
+ def execute(method: Callable, args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> None:
|
|
|
+ try:
|
|
|
+ response = method(*args, **kwargs)
|
|
|
+ if response is not None or 'dialog' in method.__name__:
|
|
|
+ response_queue.put(response)
|
|
|
+ except Exception:
|
|
|
+ logging.exception(f'error in window.{method.__name__}')
|
|
|
+
|
|
|
+ def window_method_executor() -> None:
|
|
|
+ pending_executions: List[Thread] = []
|
|
|
+ while not closing.is_set():
|
|
|
+ try:
|
|
|
+ method_name, args, kwargs = method_queue.get(block=False)
|
|
|
+ if method_name == 'signal_server_shutdown':
|
|
|
+ if pending_executions:
|
|
|
+ logging.warning('shutdown is possibly blocked by opened dialogs like a file picker')
|
|
|
+ while pending_executions:
|
|
|
+ pending_executions.pop().join()
|
|
|
+ elif method_name == 'get_always_on_top':
|
|
|
+ response_queue.put(window.on_top)
|
|
|
+ elif method_name == 'set_always_on_top':
|
|
|
+ window.on_top = args[0]
|
|
|
+ elif method_name == 'get_position':
|
|
|
+ response_queue.put((int(window.x), int(window.y)))
|
|
|
+ elif method_name == 'get_size':
|
|
|
+ response_queue.put((int(window.width), int(window.height)))
|
|
|
+ else:
|
|
|
+ method = getattr(window, method_name)
|
|
|
+ if callable(method):
|
|
|
+ pending_executions.append(Thread(target=execute, args=(method, args, kwargs)))
|
|
|
+ pending_executions[-1].start()
|
|
|
+ else:
|
|
|
+ logging.error(f'window.{method_name} is not callable')
|
|
|
+ except queue.Empty:
|
|
|
+ time.sleep(0.01)
|
|
|
+ except Exception:
|
|
|
+ logging.exception(f'error in window.{method_name}')
|
|
|
+
|
|
|
+ Thread(target=window_method_executor).start()
|
|
|
+
|
|
|
+
|
|
|
def activate(host: str, port: int, title: str, width: int, height: int, fullscreen: bool) -> None:
|
|
|
def check_shutdown() -> None:
|
|
|
while process.is_alive():
|
|
@@ -42,10 +95,11 @@ def activate(host: str, port: int, title: str, width: int, height: int, fullscre
|
|
|
time.sleep(0.1)
|
|
|
_thread.interrupt_main()
|
|
|
|
|
|
- multiprocessing.freeze_support()
|
|
|
- process = multiprocessing.Process(target=open_window, args=(host, port, title, width, height, fullscreen),
|
|
|
- daemon=False)
|
|
|
+ mp.freeze_support()
|
|
|
+ args = host, port, title, width, height, fullscreen, native.method_queue, native.response_queue
|
|
|
+ process = mp.Process(target=open_window, args=args, daemon=False)
|
|
|
process.start()
|
|
|
+
|
|
|
Thread(target=check_shutdown, daemon=True).start()
|
|
|
|
|
|
|