from http.server import BaseHTTPRequestHandler, HTTPServer import os, time, signal import sys, subprocess as sps from threading import Thread from datetime import datetime class S(BaseHTTPRequestHandler): 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("bo.txt", "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 self.find_chrome_win() elif sys.platform == 'darwin': return self.find_chrome_mac() elif sys.platform.startswith('linux'): return self.find_chrome_linux() def find_chrome_mac(self): 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 def find_chrome_linux(self): try: import whichcraft as wch except: raise Exception("whichcraft module is not installed/found \ please fill browser_path parameter or install whichcraft!") 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(self): import winreg as reg reg_path = r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe' 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: chrome_path = None else: break return chrome_path def open_browser(self): """ Open the browser selected (by default it looks for chrome) """ if self.app_mode and self.fullscreen: self.BROWSER_PROCESS = sps.Popen([self.browser_path, "--new-window", "--start-fullscreen", '--app={}'.format(self.localhost)], stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE) elif self.app_mode and self.maximized: self.BROWSER_PROCESS = sps.Popen([self.browser_path, "--new-window", "--start-maximized", '--app={}'.format(self.localhost)], stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE) elif self.app_mode: self.BROWSER_PROCESS = sps.Popen([self.browser_path, "--new-window", "--window-size={},{}".format(self.width, self.height), '--app={}'.format(self.localhost)], stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE) else: import webbrowser 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() print("Checking Gui status") if os.path.isfile("bo.txt"): with open("bo.txt", "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: print("Gui was closed.") break print("Gui still open.") time.sleep(2) if self.on_exit: self.on_exit() #Kill current python process if os.path.isfile("bo.txt"): #bo.txt is used to save timestamp used to check if browser is open os.remove("bo.txt") try: import psutil psutil.Process(os.getpid()).kill() except: os.kill(os.getpid(), signal.SIGSTOP)