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 ..logging import log
  6. from ..run_executor import io_bound
  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 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