-
Notifications
You must be signed in to change notification settings - Fork 0
/
flaskwebgui.py
255 lines (208 loc) · 9.56 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
from http.server import BaseHTTPRequestHandler, HTTPServer
import os, time, signal
import sys, subprocess as sps
import logging
import tempfile
from threading import Thread
from datetime import datetime
temp_dir = tempfile.TemporaryDirectory()
keepalive_file = os.path.join(temp_dir.name, 'bo.txt')
# server_log = logging.getLogger('BaseHTTPRequestHandler')
# log = logging.getLogger('flaskwebgui')
log = logging.getLogger('werkzeug')
log.disabled = True
class S(BaseHTTPRequestHandler):
def log_message(self, format_str, *args):
"""
Overrides logging in server.py so it doesn't spit out get requests to stdout.
This allows the caller to filter out what appears on the console.
"""
#server_log.debug(f"{self.address_string()} - {format_str % args}")
pass
def _set_response(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_GET(self):
self._set_response()
# self.wfile.write("GET request for {}".format(self.path).encode('utf-8'))
with open(keepalive_file, "w") as f:
f.write(f"{datetime.now()}")
class FlaskUI:
"""
This class opens in 3 threads the browser, the flask server, and a thread which closes the server if GUI is not opened
Described Parameters:
app, ==> flask class instance
width=800 ==> default width 800
height=600 ==> default height 600
fullscreen=False, ==> start app in fullscreen mode
maximized=False, ==> start app in maximized window
app_mode=True ==> by default it will start the application in chrome app mode
browser_path="", ==> full path to browser.exe ("C:/browser_folder/chrome.exe")
(needed if you want to start a specific browser)
server="flask" ==> the default backend framework is flask, but you can add a function which starts
the desired server for your choosed framework (django, bottle, web2py pyramid etc)
host="localhost" ==> specify other if needed
port=5000 ==> specify other if needed
socketio ==> specify flask-socketio instance if you are using flask with socketio
on_exit ==> specify on-exit function which will be run before closing the app
"""
def __init__(self, app=None, width=800, height=600, fullscreen=False, maximized=False, app_mode=True, browser_path="", server="flask", host="127.0.0.1", port=5000, socketio=None, on_exit=None):
self.flask_app = app
self.width = str(width)
self.height= str(height)
self.fullscreen = fullscreen
self.maximized = maximized
self.app_mode = app_mode
self.browser_path = browser_path if browser_path else self.get_default_chrome_path()
self.server = server
self.host = host
self.port = port
self.socketio = socketio
self.on_exit = on_exit
self.localhost = "http://{}:{}/".format(host, port) # http://127.0.0.1:5000/
self.flask_thread = Thread(target=self.run_flask) #daemon doesn't work...
self.browser_thread = Thread(target=self.open_browser)
self.close_server_thread = Thread(target=self.close_server)
self.BROWSER_PROCESS = None
def run(self):
"""
Start the flask and gui threads instantiated in the constructor func
"""
self.flask_thread.start()
self.browser_thread.start()
self.close_server_thread.start()
self.browser_thread.join()
self.flask_thread.join()
self.close_server_thread.join()
def run_flask(self):
"""
Run flask or other framework specified
"""
if isinstance(self.server, str):
if self.server.lower() == "flask":
if self.socketio:
self.socketio.run(self.flask_app, host=self.host, port=self.port)
else:
self.flask_app.run(host=self.host, port=self.port)
elif self.server.lower() == "django":
if sys.platform in ['win32', 'win64']:
os.system("python manage.py runserver {}:{}".format(self.host, self.port))
else:
os.system("python3 manage.py runserver {}:{}".format(self.host, self.port))
else:
raise Exception("{} must be a function which starts the webframework server!".format(self.server))
else:
self.server()
def get_default_chrome_path(self):
"""
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 FlaskUI.find_chrome_win()
elif sys.platform == 'darwin':
return FlaskUI.find_chrome_mac()
elif sys.platform.startswith('linux'):
return FlaskUI.find_chrome_linux()
@staticmethod
def find_chrome_mac():
default_dir = r'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
if os.path.exists(default_dir):
return default_dir
# use mdfind ci to locate Chrome in alternate locations and return the first one
name = 'Google Chrome.app'
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/Google Chrome'
return None
@staticmethod
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
@staticmethod
def find_chrome_win():
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:
log.exception(last_exception)
log.error("Failed to detect chrome location from registry")
else:
log.debug(f"Chrome path detected as: {chrome_path}")
return chrome_path
def open_browser(self):
"""
Open the browser selected (by default it looks for chrome)
"""
if self.app_mode:
launch_options = None
if self.fullscreen:
launch_options = ["--start-fullscreen"]
elif self.maximized:
launch_options = ["--start-maximized"]
else:
launch_options = ["--window-size={},{}".format(self.width, self.height)]
options = [self.browser_path, "--incognito", "--new-window", '--app={}'.format(self.localhost)]
options.extend(launch_options)
log.debug(f"Opening chrome browser with: {options}")
self.BROWSER_PROCESS = sps.Popen(options,
stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE)
else:
import webbrowser
log.debug(f"Opening python web browser")
webbrowser.open_new(self.localhost)
def close_server(self):
"""
If no get request comes from browser on port + 1
then after 10 seconds the server will be closed
"""
httpd = HTTPServer(('', self.port+1), S)
httpd.timeout = 10
while True:
httpd.handle_request()
log.debug("Checking Gui status")
if os.path.isfile(keepalive_file):
with open(keepalive_file, "r") as f:
bo = f.read().splitlines()[0]
diff = datetime.now() - datetime.strptime(bo, "%Y-%m-%d %H:%M:%S.%f")
if diff.total_seconds() > 10:
log.info("Gui was closed.")
break
log.debug("Gui still open.")
time.sleep(2)
if self.on_exit:
self.on_exit()
# Kill current python process
if os.path.isfile(keepalive_file):
# bo.txt is used to save timestamp used to check if browser is open
os.remove(keepalive_file)
try:
import psutil
psutil.Process(os.getpid()).kill()
except:
os.kill(os.getpid(), signal.SIGTERM)