File: desktop.py

package info (click to toggle)
python-trame-server 3.6.1-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 480 kB
  • sloc: python: 4,075; javascript: 5; sh: 4; makefile: 3
file content (172 lines) | stat: -rw-r--r-- 4,945 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import asyncio
import threading
from multiprocessing import Process, Queue

from .asynchronous import handle_task_result

try:
    import webview
except ImportError as err:
    msg = "server.start(exec_mode='desktop', ...) requires pywebview>=3.4"
    raise ImportError(msg) from err

WINDOW_ARGS = [
    "html",
    "js_api",
    "width",
    "height",
    "x",
    "y",
    "screen",
    "resizable",
    "fullscreen",
    "min_size",
    "hidden",
    "frameless",
    "easy_drag",
    "focus",
    "minimized",
    "maximized",
    "on_top",
    "confirm_close",
    "background_color",
    "transparent",
    "text_select",
    "zoomable",
    "draggable",
    "vibrancy",
    "server",
    "server_args",
    "localization",
]


def filter_dict(dict_to_filter, key_list):
    return {k: dict_to_filter[k] for k in key_list if k in dict_to_filter}


def to_menu(menu_struct, fn_menu_click):
    if isinstance(menu_struct, list):
        return [to_menu(item, fn_menu_click) for item in menu_struct]
    if isinstance(menu_struct, str):
        return webview.menu.MenuSeparator()
    if isinstance(menu_struct, tuple):
        label, item = menu_struct
        if isinstance(item, list):
            return webview.menu.Menu(label, to_menu(item, fn_menu_click))
        if isinstance(item, str):
            return webview.menu.MenuAction(label, lambda: fn_menu_click(item))

    return None


class BrowserProcess(Process):
    def __init__(
        self,
        title=None,
        port=None,
        msg_queue=None,
        action_queue=None,
        debug=False,
        gui=None,
        menu=None,
        **kwargs,
    ):
        Process.__init__(self)
        self._title = title
        self._port = port
        self._msg_queue = msg_queue
        self._action_queue = action_queue
        self._menu = menu or []
        self._window_args = filter_dict(kwargs, WINDOW_ARGS)
        self._main_window = None
        # start args
        self._debug = debug
        self._gui = gui
        self._monitoring = True

    def exit(self):
        # It does not appear that we need to destroy the window
        self._monitoring = False
        self._msg_queue.put("closing")

    def menu_click(self, name):
        self._msg_queue.put(f"menu:{name}")

    async def _monitor_action_requests(self):
        while self._monitoring:
            await asyncio.sleep(0.5)
            if not self._action_queue.empty():
                msg = self._action_queue.get_nowait()
                action = msg.get("action")
                args = msg.get("args", [])
                kwargs = msg.get("kwargs", {})
                fn = getattr(self._main_window, action)
                if action == "destroy":
                    self.exit()
                fn(*args, **kwargs)

    def run_in_thread(self, loop):
        asyncio.set_event_loop(loop)
        task = loop.create_task(self._monitor_action_requests())
        task.add_done_callback(handle_task_result)
        loop.run_until_complete(task)

    def run(self):
        self._main_window = webview.create_window(
            title=self._title,
            url=f"http://localhost:{self._port}/index.html",
            **self._window_args,
        )
        if hasattr(self._main_window, "events"):
            # Newer versions of pywebview (>=3.6) use window.events.closing
            self._main_window.events.closing += self.exit
        else:
            # Older versions (around pywebview<=3.5) use window.closing
            self._main_window.closing += self.exit

        event_loop = asyncio.new_event_loop()
        thread = threading.Thread(target=lambda: self.run_in_thread(event_loop))

        webview.start(
            menu=to_menu(self._menu, self.menu_click),
            func=lambda: thread.start(),
            debug=self._debug,
            gui=self._gui,
        )


def start_browser(server, **kwargs):
    _msg_queue = Queue()
    _window_action_queue = Queue()
    _on_msg = kwargs.get("on_message")

    async def process_msg():
        keep_processing = True
        while keep_processing:
            await asyncio.sleep(0.5)
            if not _msg_queue.empty():
                msg = _msg_queue.get_nowait()
                if _on_msg:
                    _on_msg(msg)
                if msg == "closing":
                    keep_processing = False
                    await server.stop()

    task = asyncio.create_task(process_msg())
    task.add_done_callback(handle_task_result)

    client_process = BrowserProcess(
        title=server.state.trame__title,
        port=server.port,
        msg_queue=_msg_queue,
        action_queue=_window_action_queue,
        debug=server.options.get("desktop_debug"),
        **kwargs,
    )
    client_process.start()

    def window_call(action, *args, **kwargs):
        _window_action_queue.put({"action": action, "args": args, "kwargs": kwargs})

    server.controller.pywebview_window_call = window_call