-
Notifications
You must be signed in to change notification settings - Fork 0
/
flaskwebgui.py
354 lines (247 loc) · 9.8 KB
/
flaskwebgui.py
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# ORIGINAL: https://github.com/ClimenteA/flaskwebgui
# MODIFICATION: Removed --no-sandbox flag, add flags to make edge run more seamlessly
__version__ = "0.3.4"
import os
import sys
import time
from datetime import datetime
import logging
import tempfile
import socketserver
import subprocess as sps
from inspect import isfunction
from threading import Lock, Thread
logging.basicConfig(level=logging.INFO, format='flaskwebgui - [%(levelname)s] - %(message)s')
# UTILS
def find_chrome_mac():
chrome_names = ['Google Chrome', 'Chromium']
for chrome_name in chrome_names:
default_dir = r'/Applications/{}.app/Contents/MacOS/{}'.format(chrome_name, chrome_name)
if os.path.exists(default_dir):
return default_dir
# use mdfind ci to locate Chrome in alternate locations and return the first one
name = '{}.app'.format(chrome_name)
alternate_dirs = [x for x in sps.check_output(["mdfind", name]).decode().split('\n') if x.endswith(name)]
if len(alternate_dirs):
return alternate_dirs[0] + '/Contents/MacOS/{}'.format(chrome_name)
return None
def find_chrome_linux():
try:
import whichcraft as wch
except Exception as e:
raise Exception("whichcraft module is not installed/found \
please fill browser_path parameter or install whichcraft!") from e
chrome_names = ['chromium-browser',
'chromium',
'google-chrome',
'google-chrome-stable']
for name in chrome_names:
chrome = wch.which(name)
if chrome is not None:
return chrome
return None
def find_chrome_win():
#using edge by default since it's build on chromium
edge_path = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
if os.path.exists(edge_path):
return edge_path
import winreg as reg
reg_path = r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe'
chrome_path = None
last_exception = None
for install_type in reg.HKEY_CURRENT_USER, reg.HKEY_LOCAL_MACHINE:
try:
reg_key = reg.OpenKey(install_type, reg_path, 0, reg.KEY_READ)
chrome_path = reg.QueryValue(reg_key, None)
reg_key.Close()
except WindowsError as e:
last_exception = e
else:
if chrome_path and len(chrome_path) > 0:
break
# Only log some debug info if we failed completely to find chrome
if not chrome_path:
logging.exception(last_exception)
logging.error("Failed to detect chrome location from registry")
else:
logging.info(f"Chrome path detected as: {chrome_path}")
return chrome_path
def get_default_chrome_path():
"""
Credits for get_instance_path, find_chrome_mac, find_chrome_linux, find_chrome_win funcs
got from: https://github.com/ChrisKnott/Eel/blob/master/eel/chrome.py
"""
if sys.platform in ['win32', 'win64']:
return find_chrome_win()
elif sys.platform in ['darwin']:
return find_chrome_mac()
elif sys.platform.startswith('linux'):
return find_chrome_linux()
# class FlaskwebguiDjangoMiddleware:
#TODO help needed here
# def __init__(self, get_response=None):
# self.get_response = get_response
# def __call__(self, request):
# response = self.get_response(request)
# return response
current_timestamp = None
class FlaskUI:
def __init__(self,
app,
start_server='flask',
width=800,
height=600,
maximized=False,
fullscreen=False,
browser_path=None,
socketio=None,
on_exit=None,
idle_interval=5,
close_server_on_exit=True
) -> None:
self.app = app
self.start_server = str(start_server).lower()
self.width = str(width)
self.height= str(height)
self.fullscreen = fullscreen
self.maximized = maximized
self.browser_path = browser_path if browser_path else get_default_chrome_path()
self.socketio = socketio
self.on_exit = on_exit
self.idle_interval = idle_interval
self.close_server_on_exit = close_server_on_exit
self.set_url()
self.webserver_dispacher = {
"flask": self.start_flask,
"flask-socketio": self.start_flask_socketio,
"django": self.start_django,
"fastapi": self.start_fastapi
}
self.supported_frameworks = list(self.webserver_dispacher.keys())
if self.close_server_on_exit:
self.lock = Lock()
def update_timestamp(self):
self.lock.acquire()
global current_timestamp
current_timestamp = datetime.now()
self.lock.release()
def run(self):
"""
Starts 3 threads one for webframework server and one for browser gui
"""
if self.close_server_on_exit:
self.update_timestamp()
t_start_webserver = Thread(target=self.start_webserver)
t_open_chromium = Thread(target=self.open_chromium)
t_stop_webserver = Thread(target=self.stop_webserver)
threads = [t_start_webserver, t_open_chromium, t_stop_webserver]
for t in threads: t.start()
for t in threads: t.join()
def set_url(self):
with socketserver.TCPServer(("localhost", 0), None) as s:
free_port = s.server_address[1]
self.host = '127.0.0.1'
self.port = free_port
self.localhost = f"http://{self.host}:{self.port}"
def start_webserver(self):
if isfunction(self.start_server):
self.start_server()
if self.start_server not in self.supported_frameworks:
raise Exception(f"'start_server'({self.start_server}) not in {','.join(self.supported_frameworks)} and also not a function which starts the webframework")
self.webserver_dispacher[self.start_server]()
def add_flask_middleware(self):
@self.app.after_request
def keep_alive_after_request(response):
self.keep_server_running()
return response
@self.app.route("/flaskwebgui-keep-server-alive")
def keep_alive_pooling():
self.keep_server_running()
return "ok"
def start_flask(self):
if self.close_server_on_exit:
self.add_flask_middleware()
try:
import waitress
waitress.serve(self.app, host=self.host, port=self.port)
except:
self.app.run(host=self.host, port=self.port)
def start_flask_socketio(self):
if self.close_server_on_exit:
self.add_flask_middleware()
self.socketio.run(self.app, host=self.host, port=self.port, debug=False)
def start_django(self):
try:
import waitress
waitress.serve(self.app, host=self.host, port=self.port)
except:
try:#linux and mac
os.system(f"python3 manage.py runserver {self.port}")
except:#windows
os.system(f"python manage.py runserver {self.port}")
def add_fastapi_middleware(self):
@self.app.middleware("http")
async def keep_alive_after_request(request, call_next):
response = await call_next(request)
self.keep_server_running()
return response
@self.app.route("/flaskwebgui-keep-server-alive")
async def keep_alive_pooling():
self.keep_server_running()
return "ok"
def start_fastapi(self):
import uvicorn
if self.close_server_on_exit:
self.add_fastapi_middleware()
uvicorn.run(self.app, host=self.host, port=self.port, log_level="warning")
def open_chromium(self):
"""
Open the browser selected (by default it looks for chrome)
# https://peter.sh/experiments/chromium-command-line-switches/
"""
logging.info(f"Opening browser at {self.localhost}")
temp_profile_dir = os.path.join(tempfile.gettempdir(), "flaskwebgui")
if self.browser_path:
launch_options = None
if self.fullscreen:
launch_options = ["--start-fullscreen"]
elif self.maximized:
launch_options = ["--start-maximized"]
else:
launch_options = [f"--window-size={self.width},{self.height}"]
options = [
self.browser_path,
f"--user-data-dir={temp_profile_dir}",
"--new-window",
#"--no-sandbox",
"--no-first-run",
"--no-default-browser-check",
# "--window-position=0,0"
] + launch_options + [f'--app={self.localhost}']
sps.Popen(options, stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE)
else:
import webbrowser
webbrowser.open_new(self.localhost)
def stop_webserver(self):
if self.close_server_on_exit is False: return
#TODO add middleware for Django
if self.start_server == 'django':
logging.info("Middleware not implemented (yet) for Django.")
return
while True:
self.lock.acquire()
global current_timestamp
delta_seconds = (datetime.now() - current_timestamp).total_seconds()
self.lock.release()
if delta_seconds > self.idle_interval:
logging.info("App closed")
break
time.sleep(self.idle_interval)
if isfunction(self.on_exit):
logging.info(f"Executing {self.on_exit.__name__} function...")
self.on_exit()
logging.info("Closing connections...")
os.kill(os.getpid(), 9)
def keep_server_running(self):
self.update_timestamp()
return "Ok"