native.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import inspect
  2. import warnings
  3. from multiprocessing import Queue
  4. from typing import Any, Callable, Tuple
  5. from .. import run
  6. from ..logging import log
  7. method_queue: Queue = Queue()
  8. response_queue: Queue = Queue()
  9. try:
  10. with warnings.catch_warnings():
  11. # webview depends on bottle which uses the deprecated CGI function (https://github.com/bottlepy/bottle/issues/1403)
  12. warnings.filterwarnings('ignore', category=DeprecationWarning)
  13. import webview
  14. from webview.window import FixPoint
  15. class WindowProxy(webview.Window):
  16. def __init__(self) -> None: # pylint: disable=super-init-not-called
  17. pass # NOTE we don't call super().__init__ here because this is just a proxy to the actual window
  18. async def get_always_on_top(self) -> bool:
  19. """Get whether the window is always on top."""
  20. return await self._request()
  21. def set_always_on_top(self, on_top: bool) -> None:
  22. """Set whether the window is always on top."""
  23. self._send(on_top)
  24. async def get_size(self) -> Tuple[int, int]:
  25. """Get the window size as tuple (width, height)."""
  26. return await self._request()
  27. async def get_position(self) -> Tuple[int, int]:
  28. """Get the window position as tuple (x, y)."""
  29. return await self._request()
  30. def load_url(self, url: str) -> None:
  31. self._send(url)
  32. def load_html(self, content: str, base_uri: str = ...) -> None: # type: ignore
  33. self._send(content, base_uri)
  34. def load_css(self, stylesheet: str) -> None:
  35. self._send(stylesheet)
  36. def set_title(self, title: str) -> None:
  37. self._send(title)
  38. async def get_cookies(self) -> Any: # pylint: disable=invalid-overridden-method
  39. return await self._request()
  40. async def get_current_url(self) -> str: # pylint: disable=invalid-overridden-method
  41. return await self._request()
  42. def destroy(self) -> None:
  43. self._send()
  44. def show(self) -> None:
  45. self._send()
  46. def hide(self) -> None:
  47. self._send()
  48. def set_window_size(self, width: int, height: int) -> None:
  49. self._send(width, height)
  50. def resize(self, width: int, height: int, fix_point: FixPoint = FixPoint.NORTH | FixPoint.WEST) -> None:
  51. self._send(width, height, fix_point)
  52. def minimize(self) -> None:
  53. self._send()
  54. def restore(self) -> None:
  55. self._send()
  56. def toggle_fullscreen(self) -> None:
  57. self._send()
  58. def move(self, x: int, y: int) -> None:
  59. self._send(x, y)
  60. async def evaluate_js(self, script: str) -> str: # pylint: disable=arguments-differ,invalid-overridden-method
  61. return await self._request(script)
  62. async def create_confirmation_dialog(self, title: str, message: str) -> bool: # pylint: disable=invalid-overridden-method
  63. return await self._request(title, message)
  64. async def create_file_dialog( # pylint: disable=invalid-overridden-method
  65. self,
  66. dialog_type: int = webview.OPEN_DIALOG,
  67. directory: str = '',
  68. allow_multiple: bool = False,
  69. save_filename: str = '',
  70. file_types: Tuple[str, ...] = (),
  71. ) -> Tuple[str, ...]:
  72. return await self._request(
  73. dialog_type=dialog_type,
  74. directory=directory,
  75. allow_multiple=allow_multiple,
  76. save_filename=save_filename,
  77. file_types=file_types,
  78. )
  79. def expose(self, function: Callable) -> None: # pylint: disable=arguments-differ
  80. raise NotImplementedError(f'exposing "{function}" is not supported')
  81. def _send(self, *args: Any, **kwargs: Any) -> None:
  82. name = inspect.currentframe().f_back.f_code.co_name # type: ignore
  83. method_queue.put((name, args, kwargs))
  84. async def _request(self, *args: Any, **kwargs: Any) -> Any:
  85. def wrapper(*args: Any, **kwargs: Any) -> Any:
  86. try:
  87. method_queue.put((name, args, kwargs))
  88. return response_queue.get() # wait for the method to be called and writing its result to the queue
  89. except Exception:
  90. log.exception(f'error in {name}')
  91. return None
  92. name = inspect.currentframe().f_back.f_code.co_name # type: ignore
  93. return await run.io_bound(wrapper, *args, **kwargs)
  94. def signal_server_shutdown(self) -> None:
  95. """Signal the server shutdown."""
  96. self._send()
  97. except ModuleNotFoundError:
  98. class WindowProxy: # type: ignore
  99. pass # just a dummy if webview is not installed