You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
5.1 KiB
140 lines
5.1 KiB
import asyncio |
|
import inspect |
|
import warnings |
|
from dataclasses import dataclass, field |
|
from functools import partial |
|
from multiprocessing import Queue |
|
from typing import Any, Callable, Dict, Optional, Tuple |
|
|
|
from .dataclasses import KWONLY_SLOTS |
|
from .globals import log |
|
|
|
method_queue: Queue = Queue() |
|
response_queue: Queue = Queue() |
|
|
|
try: |
|
with warnings.catch_warnings(): |
|
# webview depends on bottle which uses the deprecated CGI function (https://github.com/bottlepy/bottle/issues/1403) |
|
warnings.filterwarnings('ignore', category=DeprecationWarning) |
|
import webview |
|
from webview.window import FixPoint |
|
|
|
class WindowProxy(webview.Window): |
|
|
|
def __init__(self) -> None: # pylint: disable=super-init-not-called |
|
pass # NOTE we don't call super().__init__ here because this is just a proxy to the actual window |
|
|
|
async def get_always_on_top(self) -> bool: |
|
"""Get whether the window is always on top.""" |
|
return await self._request() |
|
|
|
def set_always_on_top(self, on_top: bool) -> None: |
|
"""Set whether the window is always on top.""" |
|
self._send(on_top) |
|
|
|
async def get_size(self) -> Tuple[int, int]: |
|
"""Get the window size as tuple (width, height).""" |
|
return await self._request() |
|
|
|
async def get_position(self) -> Tuple[int, int]: |
|
"""Get the window position as tuple (x, y).""" |
|
return await self._request() |
|
|
|
def load_url(self, url: str) -> None: |
|
self._send(url) |
|
|
|
def load_html(self, content: str, base_uri: str = ...) -> None: # type: ignore |
|
self._send(content, base_uri) |
|
|
|
def load_css(self, stylesheet: str) -> None: |
|
self._send(stylesheet) |
|
|
|
def set_title(self, title: str) -> None: |
|
self._send(title) |
|
|
|
async def get_cookies(self) -> Any: # pylint: disable=invalid-overridden-method |
|
return await self._request() |
|
|
|
async def get_current_url(self) -> str: # pylint: disable=invalid-overridden-method |
|
return await self._request() |
|
|
|
def destroy(self) -> None: |
|
self._send() |
|
|
|
def show(self) -> None: |
|
self._send() |
|
|
|
def hide(self) -> None: |
|
self._send() |
|
|
|
def set_window_size(self, width: int, height: int) -> None: |
|
self._send(width, height) |
|
|
|
def resize(self, width: int, height: int, fix_point: FixPoint = FixPoint.NORTH | FixPoint.WEST) -> None: |
|
self._send(width, height, fix_point) |
|
|
|
def minimize(self) -> None: |
|
self._send() |
|
|
|
def restore(self) -> None: |
|
self._send() |
|
|
|
def toggle_fullscreen(self) -> None: |
|
self._send() |
|
|
|
def move(self, x: int, y: int) -> None: |
|
self._send(x, y) |
|
|
|
async def evaluate_js(self, script: str) -> str: # pylint: disable=arguments-differ,invalid-overridden-method |
|
return await self._request(script) |
|
|
|
async def create_confirmation_dialog(self, title: str, message: str) -> bool: # pylint: disable=invalid-overridden-method |
|
return await self._request(title, message) |
|
|
|
async def create_file_dialog( # pylint: disable=invalid-overridden-method |
|
self, |
|
dialog_type: int = webview.OPEN_DIALOG, |
|
directory: str = '', |
|
allow_multiple: bool = False, |
|
save_filename: str = '', |
|
file_types: Tuple[str, ...] = (), |
|
) -> Tuple[str, ...]: |
|
return await self._request( |
|
dialog_type=dialog_type, |
|
directory=directory, |
|
allow_multiple=allow_multiple, |
|
save_filename=save_filename, |
|
file_types=file_types, |
|
) |
|
|
|
def expose(self, function: Callable) -> None: # pylint: disable=arguments-differ |
|
raise NotImplementedError(f'exposing "{function}" is not supported') |
|
|
|
def _send(self, *args: Any, **kwargs: Any) -> None: |
|
name = inspect.currentframe().f_back.f_code.co_name # type: ignore |
|
method_queue.put((name, args, kwargs)) |
|
|
|
async def _request(self, *args: Any, **kwargs: Any) -> Any: |
|
def wrapper(*args: Any, **kwargs: Any) -> Any: |
|
try: |
|
method_queue.put((name, args, kwargs)) |
|
return response_queue.get() # wait for the method to be called and writing its result to the queue |
|
except Exception: |
|
log.exception(f'error in {name}') |
|
return None |
|
name = inspect.currentframe().f_back.f_code.co_name # type: ignore |
|
return await asyncio.get_event_loop().run_in_executor(None, partial(wrapper, *args, **kwargs)) |
|
|
|
def signal_server_shutdown(self) -> None: |
|
self._send() |
|
|
|
except ModuleNotFoundError: |
|
class WindowProxy: # type: ignore |
|
pass # just a dummy if webview is not installed |
|
|
|
|
|
@dataclass(**KWONLY_SLOTS) |
|
class Native: |
|
start_args: Dict[str, Any] = field(default_factory=dict) |
|
window_args: Dict[str, Any] = field(default_factory=dict) |
|
main_window: Optional[WindowProxy] = None
|
|
|