native_mode.py 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. import _thread
  2. import multiprocessing
  3. import socket
  4. import sys
  5. import tempfile
  6. import time
  7. import warnings
  8. from threading import Thread
  9. from . import globals, helpers
  10. try:
  11. with warnings.catch_warnings():
  12. # webview depends on bottle which uses the deprecated CGI function (https://github.com/bottlepy/bottle/issues/1403)
  13. warnings.filterwarnings('ignore', category=DeprecationWarning)
  14. import webview
  15. except ModuleNotFoundError:
  16. pass
  17. def open_window(host: str, port: int, title: str, width: int, height: int, fullscreen: bool) -> None:
  18. while not helpers.is_port_open(host, port):
  19. time.sleep(0.1)
  20. window_kwargs = dict(url=f'http://{host}:{port}', title=title, width=width, height=height, fullscreen=fullscreen)
  21. window_kwargs.update(globals.app.native.window_args)
  22. try:
  23. webview.create_window(**window_kwargs)
  24. webview.start(storage_path=tempfile.mkdtemp(), **globals.app.native.start_args)
  25. except NameError:
  26. print('Native mode is not supported in this configuration. Please install pywebview to use it.')
  27. sys.exit(1)
  28. def activate(host: str, port: int, title: str, width: int, height: int, fullscreen: bool) -> None:
  29. def check_shutdown() -> None:
  30. while process.is_alive():
  31. time.sleep(0.1)
  32. globals.server.should_exit = True
  33. while globals.state != globals.State.STOPPED:
  34. time.sleep(0.1)
  35. _thread.interrupt_main()
  36. multiprocessing.freeze_support()
  37. process = multiprocessing.Process(target=open_window, args=(host, port, title, width, height, fullscreen),
  38. daemon=False)
  39. process.start()
  40. Thread(target=check_shutdown, daemon=True).start()
  41. def find_open_port(start_port: int = 8000, end_port: int = 8999) -> int:
  42. """Reliably find an open port in a given range.
  43. This function will actually try to open the port to ensure no firewall blocks it.
  44. This is better than, e.g., passing port=0 to uvicorn.
  45. """
  46. for port in range(start_port, end_port + 1):
  47. try:
  48. with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
  49. s.bind(('localhost', port))
  50. return port
  51. except OSError:
  52. pass
  53. raise OSError('No open port found')