mit neuen venv und exe-Files

This commit is contained in:
2024-11-03 17:26:54 +01:00
parent 07c05a338a
commit 0c373ff593
15115 changed files with 1998469 additions and 0 deletions

View File

@@ -0,0 +1,432 @@
from builtins import range
import traceback
from io import open
from typing import Union, Any, Dict, List, Set, Tuple, Optional, Callable, TYPE_CHECKING
if TYPE_CHECKING:
from eel.types import OptionsDictT, WebSocketT
else:
WebSocketT = Any
OptionsDictT = Any
from gevent.threading import Timer
import gevent as gvt
import json as jsn
import bottle as btl
try:
import bottle_websocket as wbs
except ImportError:
import bottle.ext.websocket as wbs
import re as rgx
import os
import eel.browsers as brw
import pyparsing as pp
import random as rnd
import sys
import pkg_resources as pkg
import socket
import mimetypes
mimetypes.add_type('application/javascript', '.js')
_eel_js_file: str = pkg.resource_filename('eel', 'eel.js')
_eel_js: str = open(_eel_js_file, encoding='utf-8').read()
_websockets: List[Tuple[Any, WebSocketT]] = []
_call_return_values: Dict[Any, Any] = {}
_call_return_callbacks: Dict[float, Tuple[Callable[..., Any], Optional[Callable[..., Any]]]] = {}
_call_number: int = 0
_exposed_functions: Dict[Any, Any] = {}
_js_functions: List[Any] = []
_mock_queue: List[Any] = []
_mock_queue_done: Set[Any] = set()
_shutdown: Optional[gvt.Greenlet] = None # Later assigned as global by _websocket_close()
root_path: str # Later assigned as global by init()
# The maximum time (in milliseconds) that Python will try to retrieve a return value for functions executing in JS
# Can be overridden through `eel.init` with the kwarg `js_result_timeout` (default: 10000)
_js_result_timeout: int = 10000
# All start() options must provide a default value and explanation here
_start_args: OptionsDictT = {
'mode': 'chrome', # What browser is used
'host': 'localhost', # Hostname use for Bottle server
'port': 8000, # Port used for Bottle server (use 0 for auto)
'block': True, # Whether start() blocks calling thread
'jinja_templates': None, # Folder for jinja2 templates
'cmdline_args': ['--disable-http-cache'], # Extra cmdline flags to pass to browser start
'size': None, # (width, height) of main window
'position': None, # (left, top) of main window
'geometry': {}, # Dictionary of size/position for all windows
'close_callback': None, # Callback for when all windows have closed
'app_mode': True, # (Chrome specific option)
'all_interfaces': False, # Allow bottle server to listen for connections on all interfaces
'disable_cache': True, # Sets the no-store response header when serving assets
'default_path': 'index.html', # The default file to retrieve for the root URL
'app': btl.default_app(), # Allows passing in a custom Bottle instance, e.g. with middleware
'shutdown_delay': 1.0 # how long to wait after a websocket closes before detecting complete shutdown
}
# == Temporary (suppressible) error message to inform users of breaking API change for v1.0.0 ===
_start_args['suppress_error'] = False
api_error_message: str = '''
----------------------------------------------------------------------------------
'options' argument deprecated in v1.0.0, see https://github.com/ChrisKnott/Eel
To suppress this error, add 'suppress_error=True' to start() call.
This option will be removed in future versions
----------------------------------------------------------------------------------
'''
# ===============================================================================================
# Public functions
def expose(name_or_function: Optional[Callable[..., Any]] = None) -> Callable[..., Any]:
# Deal with '@eel.expose()' - treat as '@eel.expose'
if name_or_function is None:
return expose
if isinstance(name_or_function, str): # Called as '@eel.expose("my_name")'
name = name_or_function
def decorator(function: Callable[..., Any]) -> Any:
_expose(name, function)
return function
return decorator
else:
function = name_or_function
_expose(function.__name__, function)
return function
# PyParsing grammar for parsing exposed functions in JavaScript code
# Examples: `eel.expose(w, "func_name")`, `eel.expose(func_name)`, `eel.expose((function (e){}), "func_name")`
EXPOSED_JS_FUNCTIONS: pp.ZeroOrMore = pp.ZeroOrMore(
pp.Suppress(
pp.SkipTo(pp.Literal('eel.expose('))
+ pp.Literal('eel.expose(')
+ pp.Optional(
pp.Or([pp.nestedExpr(), pp.Word(pp.printables, excludeChars=',')]) + pp.Literal(',')
)
)
+ pp.Suppress(pp.Regex(r'["\']?'))
+ pp.Word(pp.printables, excludeChars='"\')')
+ pp.Suppress(pp.Regex(r'["\']?\s*\)')),
)
def init(path: str, allowed_extensions: List[str] = ['.js', '.html', '.txt', '.htm',
'.xhtml', '.vue'], js_result_timeout: int = 10000) -> None:
global root_path, _js_functions, _js_result_timeout
root_path = _get_real_path(path)
js_functions = set()
for root, _, files in os.walk(root_path):
for name in files:
if not any(name.endswith(ext) for ext in allowed_extensions):
continue
try:
with open(os.path.join(root, name), encoding='utf-8') as file:
contents = file.read()
expose_calls = set()
matches = EXPOSED_JS_FUNCTIONS.parseString(contents).asList()
for expose_call in matches:
# Verify that function name is valid
msg = "eel.expose() call contains '(' or '='"
assert rgx.findall(r'[\(=]', expose_call) == [], msg
expose_calls.add(expose_call)
js_functions.update(expose_calls)
except UnicodeDecodeError:
pass # Malformed file probably
_js_functions = list(js_functions)
for js_function in _js_functions:
_mock_js_function(js_function)
_js_result_timeout = js_result_timeout
def start(*start_urls: str, **kwargs: Any) -> None:
_start_args.update(kwargs)
if 'options' in kwargs:
if _start_args['suppress_error']:
_start_args.update(kwargs['options'])
else:
raise RuntimeError(api_error_message)
if _start_args['port'] == 0:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 0))
_start_args['port'] = sock.getsockname()[1]
sock.close()
if _start_args['jinja_templates'] != None:
from jinja2 import Environment, FileSystemLoader, select_autoescape
if not isinstance(_start_args['jinja_templates'], str):
raise TypeError("'jinja_templates start_arg/option must be of type str'")
templates_path = os.path.join(root_path, _start_args['jinja_templates'])
_start_args['jinja_env'] = Environment(loader=FileSystemLoader(templates_path),
autoescape=select_autoescape(['html', 'xml']))
# verify shutdown_delay is correct value
if not isinstance(_start_args['shutdown_delay'], (int, float)):
raise ValueError("`shutdown_delay` must be a number, "\
"got a {}".format(type(_start_args['shutdown_delay'])))
# Launch the browser to the starting URLs
show(*start_urls)
def run_lambda() -> None:
if _start_args['all_interfaces'] == True:
HOST = '0.0.0.0'
else:
if not isinstance(_start_args['host'], str):
raise TypeError("'host' start_arg/option must be of type str")
HOST = _start_args['host']
app = _start_args['app']
if isinstance(app, btl.Bottle):
register_eel_routes(app)
else:
register_eel_routes(btl.default_app())
btl.run(
host=HOST,
port=_start_args['port'],
server=wbs.GeventWebSocketServer,
quiet=True,
app=app) # Always returns None
# Start the webserver
if _start_args['block']:
run_lambda()
else:
spawn(run_lambda)
def show(*start_urls: str) -> None:
brw.open(list(start_urls), _start_args)
def sleep(seconds: Union[int, float]) -> None:
gvt.sleep(seconds)
def spawn(function: Callable[..., Any], *args: Any, **kwargs: Any) -> gvt.Greenlet:
return gvt.spawn(function, *args, **kwargs)
# Bottle Routes
def _eel() -> str:
start_geometry = {'default': {'size': _start_args['size'],
'position': _start_args['position']},
'pages': _start_args['geometry']}
page = _eel_js.replace('/** _py_functions **/',
'_py_functions: %s,' % list(_exposed_functions.keys()))
page = page.replace('/** _start_geometry **/',
'_start_geometry: %s,' % _safe_json(start_geometry))
btl.response.content_type = 'application/javascript'
_set_response_headers(btl.response)
return page
def _root() -> Optional[btl.Response]:
if not isinstance(_start_args['default_path'], str):
raise TypeError("'default_path' start_arg/option must be of type str")
return _static(_start_args['default_path'])
def _static(path: str) -> Optional[btl.Response]:
response = None
if 'jinja_env' in _start_args and 'jinja_templates' in _start_args:
if not isinstance(_start_args['jinja_templates'], str):
raise TypeError("'jinja_templates' start_arg/option must be of type str")
template_prefix = _start_args['jinja_templates'] + '/'
if path.startswith(template_prefix):
n = len(template_prefix)
template = _start_args['jinja_env'].get_template(path[n:]) # type: ignore # depends on conditional import in start()
response = btl.HTTPResponse(template.render())
if response is None:
response = btl.static_file(path, root=root_path)
_set_response_headers(response)
return response
def _websocket(ws: WebSocketT) -> None:
global _websockets
for js_function in _js_functions:
_import_js_function(js_function)
page = btl.request.query.page
if page not in _mock_queue_done:
for call in _mock_queue:
_repeated_send(ws, _safe_json(call))
_mock_queue_done.add(page)
_websockets += [(page, ws)]
while True:
msg = ws.receive()
if msg is not None:
message = jsn.loads(msg)
spawn(_process_message, message, ws)
else:
_websockets.remove((page, ws))
break
_websocket_close(page)
BOTTLE_ROUTES: Dict[str, Tuple[Callable[..., Any], Dict[Any, Any]]] = {
"/eel.js": (_eel, dict()),
"/": (_root, dict()),
"/<path:path>": (_static, dict()),
"/eel": (_websocket, dict(apply=[wbs.websocket]))
}
def register_eel_routes(app: btl.Bottle) -> None:
'''
Adds eel routes to `app`. Only needed if you are passing something besides `bottle.Bottle` to `eel.start()`.
Ex:
app = bottle.Bottle()
eel.register_eel_routes(app)
middleware = beaker.middleware.SessionMiddleware(app)
eel.start(app=middleware)
'''
for route_path, route_params in BOTTLE_ROUTES.items():
route_func, route_kwargs = route_params
app.route(path=route_path, callback=route_func, **route_kwargs)
# Private functions
def _safe_json(obj: Any) -> str:
return jsn.dumps(obj, default=lambda o: None)
def _repeated_send(ws: WebSocketT, msg: str) -> None:
for attempt in range(100):
try:
ws.send(msg)
break
except Exception:
sleep(0.001)
def _process_message(message: Dict[str, Any], ws: WebSocketT) -> None:
if 'call' in message:
error_info = {}
try:
return_val = _exposed_functions[message['name']](*message['args'])
status = 'ok'
except Exception as e:
err_traceback = traceback.format_exc()
traceback.print_exc()
return_val = None
status = 'error'
error_info['errorText'] = repr(e)
error_info['errorTraceback'] = err_traceback
_repeated_send(ws, _safe_json({ 'return': message['call'],
'status': status,
'value': return_val,
'error': error_info,}))
elif 'return' in message:
call_id = message['return']
if call_id in _call_return_callbacks:
callback, error_callback = _call_return_callbacks.pop(call_id)
if message['status'] == 'ok':
callback(message['value'])
elif message['status'] == 'error' and error_callback is not None:
error_callback(message['error'], message['stack'])
else:
_call_return_values[call_id] = message['value']
else:
print('Invalid message received: ', message)
def _get_real_path(path: str) -> str:
if getattr(sys, 'frozen', False):
return os.path.join(sys._MEIPASS, path) # type: ignore # sys._MEIPASS is dynamically added by PyInstaller
else:
return os.path.abspath(path)
def _mock_js_function(f: str) -> None:
exec('%s = lambda *args: _mock_call("%s", args)' % (f, f), globals())
def _import_js_function(f: str) -> None:
exec('%s = lambda *args: _js_call("%s", args)' % (f, f), globals())
def _call_object(name: str, args: Any) -> Dict[str, Any]:
global _call_number
_call_number += 1
call_id = _call_number + rnd.random()
return {'call': call_id, 'name': name, 'args': args}
def _mock_call(name: str, args: Any) -> Callable[[Optional[Callable[..., Any]], Optional[Callable[..., Any]]], Any]:
call_object = _call_object(name, args)
global _mock_queue
_mock_queue += [call_object]
return _call_return(call_object)
def _js_call(name: str, args: Any) -> Callable[[Optional[Callable[..., Any]], Optional[Callable[..., Any]]], Any]:
call_object = _call_object(name, args)
for _, ws in _websockets:
_repeated_send(ws, _safe_json(call_object))
return _call_return(call_object)
def _call_return(call: Dict[str, Any]) -> Callable[[Optional[Callable[..., Any]], Optional[Callable[..., Any]]], Any]:
global _js_result_timeout
call_id = call['call']
def return_func(callback: Optional[Callable[..., Any]] = None,
error_callback: Optional[Callable[..., Any]] = None) -> Any:
if callback is not None:
_call_return_callbacks[call_id] = (callback, error_callback)
else:
for w in range(_js_result_timeout):
if call_id in _call_return_values:
return _call_return_values.pop(call_id)
sleep(0.001)
return return_func
def _expose(name: str, function: Callable[..., Any]) -> None:
msg = 'Already exposed function with name "%s"' % name
assert name not in _exposed_functions, msg
_exposed_functions[name] = function
def _detect_shutdown() -> None:
if len(_websockets) == 0:
sys.exit()
def _websocket_close(page: str) -> None:
global _shutdown
close_callback = _start_args.get('close_callback')
if close_callback is not None:
if not callable(close_callback):
raise TypeError("'close_callback' start_arg/option must be callable or None")
sockets = [p for _, p in _websockets]
close_callback(page, sockets)
else:
if isinstance(_shutdown, gvt.Greenlet):
_shutdown.kill()
_shutdown = gvt.spawn_later(_start_args['shutdown_delay'], _detect_shutdown)
def _set_response_headers(response: btl.Response) -> None:
if _start_args['disable_cache']:
# https://stackoverflow.com/a/24748094/280852
response.set_header('Cache-Control', 'no-store')

View File

@@ -0,0 +1,39 @@
import pkg_resources as pkg
import PyInstaller.__main__ as pyi
import os
from argparse import ArgumentParser, Namespace
from typing import List
parser: ArgumentParser = ArgumentParser(description="""
Eel is a little Python library for making simple Electron-like offline HTML/JS GUI apps,
with full access to Python capabilities and libraries.
""")
parser.add_argument(
"main_script",
type=str,
help="Main python file to run app from"
)
parser.add_argument(
"web_folder",
type=str,
help="Folder including all web files including file as html, css, ico, etc."
)
args: Namespace
unknown_args: List[str]
args, unknown_args = parser.parse_known_args()
main_script: str = args.main_script
web_folder: str = args.web_folder
print("Building executable with main script '%s' and web folder '%s'...\n" %
(main_script, web_folder))
eel_js_file: str = pkg.resource_filename('eel', 'eel.js')
js_file_arg: str = '%s%seel' % (eel_js_file, os.pathsep)
web_folder_arg: str = '%s%s%s' % (web_folder, os.pathsep, web_folder)
needed_args: List[str] = ['--hidden-import', 'bottle_websocket',
'--add-data', js_file_arg, '--add-data', web_folder_arg]
full_args: List[str] = [main_script] + needed_args + unknown_args
print('Running:\npyinstaller', ' '.join(full_args), '\n')
pyi.run(full_args)

View File

@@ -0,0 +1,90 @@
import subprocess as sps
import webbrowser as wbr
from typing import Union, List, Dict, Iterable, Optional
from types import ModuleType
from eel.types import OptionsDictT
import eel.chrome as chm
import eel.electron as ele
import eel.edge as edge
#import eel.firefox as ffx TODO
#import eel.safari as saf TODO
_browser_paths: Dict[str, str] = {}
_browser_modules: Dict[str, ModuleType] = {'chrome': chm,
'electron': ele,
'edge': edge}
def _build_url_from_dict(page: Dict[str, str], options: OptionsDictT) -> str:
scheme = page.get('scheme', 'http')
host = page.get('host', 'localhost')
port = page.get('port', options["port"])
path = page.get('path', '')
if not isinstance(port, (int, str)):
raise TypeError("'port' option must be an integer")
return '%s://%s:%d/%s' % (scheme, host, int(port), path)
def _build_url_from_string(page: str, options: OptionsDictT) -> str:
if not isinstance(options['port'], (int, str)):
raise TypeError("'port' option must be an integer")
base_url = 'http://%s:%d/' % (options['host'], int(options['port']))
return base_url + page
def _build_urls(start_pages: Iterable[Union[str, Dict[str, str]]], options: OptionsDictT) -> List[str]:
urls: List[str] = []
for page in start_pages:
if isinstance(page, dict):
url = _build_url_from_dict(page, options)
else:
url = _build_url_from_string(page, options)
urls.append(url)
return urls
def open(start_pages: Iterable[Union[str, Dict[str, str]]], options: OptionsDictT) -> None:
# Build full URLs for starting pages (including host and port)
start_urls = _build_urls(start_pages, options)
mode = options.get('mode')
if not isinstance(mode, (str, bool, type(None))) or mode is True:
raise TypeError("'mode' option must by either a string, False, or None")
if mode is None or mode is False:
# Don't open a browser
pass
elif mode == 'custom':
# Just run whatever command the user provided
if not isinstance(options['cmdline_args'], list):
raise TypeError("'cmdline_args' option must be of type List[str]")
sps.Popen(options['cmdline_args'],
stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE)
elif mode in _browser_modules:
# Run with a specific browser
browser_module = _browser_modules[mode]
path = _browser_paths.get(mode)
if path is None:
# Don't know this browser's path, try and find it ourselves
path = browser_module.find_path()
_browser_paths[mode] = path
if path is not None:
browser_module.run(path, options, start_urls)
else:
raise EnvironmentError("Can't find %s installation" % browser_module.name)
else:
# Fall back to system default browser
for url in start_urls:
wbr.open(url)
def set_path(browser_name: str, path: str) -> None:
_browser_paths[browser_name] = path
def get_path(browser_name: str) -> Optional[str]:
return _browser_paths.get(browser_name)

View File

@@ -0,0 +1,90 @@
import sys, subprocess as sps, os
from typing import List, Optional
from eel.types import OptionsDictT
# Every browser specific module must define run(), find_path() and name like this
name: str = 'Google Chrome/Chromium'
def run(path: str, options: OptionsDictT, start_urls: List[str]) -> None:
if not isinstance(options['cmdline_args'], list):
raise TypeError("'cmdline_args' option must be of type List[str]")
if options['app_mode']:
for url in start_urls:
sps.Popen([path, '--app=%s' % url] +
options['cmdline_args'],
stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE)
else:
args: List[str] = options['cmdline_args'] + start_urls
sps.Popen([path, '--new-window'] + args,
stdout=sps.PIPE, stderr=sys.stderr, stdin=sps.PIPE)
def find_path() -> Optional[str]:
if sys.platform in ['win32', 'win64']:
return _find_chrome_win()
elif sys.platform == 'darwin':
return _find_chrome_mac() or _find_chromium_mac()
elif sys.platform.startswith('linux'):
return _find_chrome_linux()
else:
return None
def _find_chrome_mac() -> Optional[str]:
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_chromium_mac() -> Optional[str]:
default_dir = r'/Applications/Chromium.app/Contents/MacOS/Chromium'
if os.path.exists(default_dir):
return default_dir
# use mdfind ci to locate Chromium in alternate locations and return the first one
name = 'Chromium.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/Chromium'
return None
def _find_chrome_linux() -> Optional[str]:
import whichcraft as wch
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 # type: ignore # whichcraft doesn't currently have type hints
return None
def _find_chrome_win() -> Optional[str]:
import winreg as reg
reg_path = r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe'
chrome_path: Optional[str] = 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()
if not os.path.isfile(chrome_path):
continue
except WindowsError:
chrome_path = None
else:
break
return chrome_path

View File

@@ -0,0 +1,20 @@
import platform
import subprocess as sps
import sys
from typing import List
from eel.types import OptionsDictT
name: str = 'Edge'
def run(_path: str, options: OptionsDictT, start_urls: List[str]) -> None:
cmd = 'start microsoft-edge:{}'.format(start_urls[0])
sps.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=sps.PIPE, shell=True)
def find_path() -> bool:
if platform.system() == 'Windows':
return True
return False

View File

@@ -0,0 +1,173 @@
eel = {
_host: window.location.origin,
set_host: function (hostname) {
eel._host = hostname
},
expose: function(f, name) {
if(name === undefined){
name = f.toString();
let i = 'function '.length, j = name.indexOf('(');
name = name.substring(i, j).trim();
}
eel._exposed_functions[name] = f;
},
guid: function() {
return eel._guid;
},
// These get dynamically added by library when file is served
/** _py_functions **/
/** _start_geometry **/
_guid: ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
),
_exposed_functions: {},
_mock_queue: [],
_mock_py_functions: function() {
for(let i = 0; i < eel._py_functions.length; i++) {
let name = eel._py_functions[i];
eel[name] = function() {
let call_object = eel._call_object(name, arguments);
eel._mock_queue.push(call_object);
return eel._call_return(call_object);
}
}
},
_import_py_function: function(name) {
let func_name = name;
eel[name] = function() {
let call_object = eel._call_object(func_name, arguments);
eel._websocket.send(eel._toJSON(call_object));
return eel._call_return(call_object);
}
},
_call_number: 0,
_call_return_callbacks: {},
_call_object: function(name, args) {
let arg_array = [];
for(let i = 0; i < args.length; i++){
arg_array.push(args[i]);
}
let call_id = (eel._call_number += 1) + Math.random();
return {'call': call_id, 'name': name, 'args': arg_array};
},
_sleep: function(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
},
_toJSON: function(obj) {
return JSON.stringify(obj, (k, v) => v === undefined ? null : v);
},
_call_return: function(call) {
return function(callback = null) {
if(callback != null) {
eel._call_return_callbacks[call.call] = {resolve: callback};
} else {
return new Promise(function(resolve, reject) {
eel._call_return_callbacks[call.call] = {resolve: resolve, reject: reject};
});
}
}
},
_position_window: function(page) {
let size = eel._start_geometry['default'].size;
let position = eel._start_geometry['default'].position;
if(page in eel._start_geometry.pages) {
size = eel._start_geometry.pages[page].size;
position = eel._start_geometry.pages[page].position;
}
if(size != null){
window.resizeTo(size[0], size[1]);
}
if(position != null){
window.moveTo(position[0], position[1]);
}
},
_init: function() {
eel._mock_py_functions();
document.addEventListener("DOMContentLoaded", function(event) {
let page = window.location.pathname.substring(1);
eel._position_window(page);
let websocket_addr = (eel._host + '/eel').replace('http', 'ws');
websocket_addr += ('?page=' + page);
eel._websocket = new WebSocket(websocket_addr);
eel._websocket.onopen = function() {
for(let i = 0; i < eel._py_functions.length; i++){
let py_function = eel._py_functions[i];
eel._import_py_function(py_function);
}
while(eel._mock_queue.length > 0) {
let call = eel._mock_queue.shift();
eel._websocket.send(eel._toJSON(call));
}
};
eel._websocket.onmessage = function (e) {
let message = JSON.parse(e.data);
if(message.hasOwnProperty('call') ) {
// Python making a function call into us
if(message.name in eel._exposed_functions) {
try {
let return_val = eel._exposed_functions[message.name](...message.args);
eel._websocket.send(eel._toJSON({'return': message.call, 'status':'ok', 'value': return_val}));
} catch(err) {
debugger
eel._websocket.send(eel._toJSON(
{'return': message.call,
'status':'error',
'error': err.message,
'stack': err.stack}));
}
}
} else if(message.hasOwnProperty('return')) {
// Python returning a value to us
if(message['return'] in eel._call_return_callbacks) {
if(message['status']==='ok'){
eel._call_return_callbacks[message['return']].resolve(message.value);
}
else if(message['status']==='error' && eel._call_return_callbacks[message['return']].reject) {
eel._call_return_callbacks[message['return']].reject(message['error']);
}
}
} else {
throw 'Invalid message ' + message;
}
};
});
}
};
eel._init();
if(typeof require !== 'undefined'){
// Avoid name collisions when using Electron, so jQuery etc work normally
window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;
}

View File

@@ -0,0 +1,30 @@
#from __future__ import annotations
import sys
import os
import subprocess as sps
import whichcraft as wch
from typing import List, Optional
from eel.types import OptionsDictT
name: str = 'Electron'
def run(path: str, options: OptionsDictT, start_urls: List[str]) -> None:
if not isinstance(options['cmdline_args'], list):
raise TypeError("'cmdline_args' option must be of type List[str]")
cmd = [path] + options['cmdline_args']
cmd += ['.', ';'.join(start_urls)]
sps.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=sps.PIPE)
def find_path() -> Optional[str]:
if sys.platform in ['win32', 'win64']:
# It doesn't work well passing the .bat file to Popen, so we get the actual .exe
bat_path = wch.which('electron')
return os.path.join(bat_path, r'..\node_modules\electron\dist\electron.exe')
elif sys.platform in ['darwin', 'linux']:
# This should work find...
return wch.which('electron') # type: ignore # whichcraft doesn't currently have type hints
else:
return None

View File

View File

@@ -0,0 +1,28 @@
from typing import Union, Dict, List, Tuple, Callable, Optional, Any, TYPE_CHECKING
# This business is slightly awkward, but needed for backward compatibility,
# because Python < 3.7 doesn't have __future__/annotations, and <3.10 doesn't
# support TypeAlias.
if TYPE_CHECKING:
from jinja2 import Environment
try:
from typing import TypeAlias # Introduced in Python 3.10
JinjaEnvironmentT: TypeAlias = Environment
except ImportError:
JinjaEnvironmentT = Environment # type: ignore
from geventwebsocket.websocket import WebSocket
WebSocketT = WebSocket
else:
JinjaEnvironmentT = None
WebSocketT = Any
OptionsDictT = Dict[
str,
Optional[
Union[
str, bool, int, float,
List[str], Tuple[int, int], Dict[str, Tuple[int, int]],
Callable[..., Any], JinjaEnvironmentT
]
]
]