mit neuen venv und exe-Files
This commit is contained in:
432
venv3_12/Lib/site-packages/eel/__init__.py
Normal file
432
venv3_12/Lib/site-packages/eel/__init__.py
Normal 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')
|
||||
39
venv3_12/Lib/site-packages/eel/__main__.py
Normal file
39
venv3_12/Lib/site-packages/eel/__main__.py
Normal 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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv3_12/Lib/site-packages/eel/__pycache__/edge.cpython-312.pyc
Normal file
BIN
venv3_12/Lib/site-packages/eel/__pycache__/edge.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
venv3_12/Lib/site-packages/eel/__pycache__/types.cpython-312.pyc
Normal file
BIN
venv3_12/Lib/site-packages/eel/__pycache__/types.cpython-312.pyc
Normal file
Binary file not shown.
90
venv3_12/Lib/site-packages/eel/browsers.py
Normal file
90
venv3_12/Lib/site-packages/eel/browsers.py
Normal 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)
|
||||
|
||||
90
venv3_12/Lib/site-packages/eel/chrome.py
Normal file
90
venv3_12/Lib/site-packages/eel/chrome.py
Normal 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
|
||||
20
venv3_12/Lib/site-packages/eel/edge.py
Normal file
20
venv3_12/Lib/site-packages/eel/edge.py
Normal 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
|
||||
173
venv3_12/Lib/site-packages/eel/eel.js
Normal file
173
venv3_12/Lib/site-packages/eel/eel.js
Normal 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;
|
||||
}
|
||||
30
venv3_12/Lib/site-packages/eel/electron.py
Normal file
30
venv3_12/Lib/site-packages/eel/electron.py
Normal 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
|
||||
|
||||
0
venv3_12/Lib/site-packages/eel/py.typed
Normal file
0
venv3_12/Lib/site-packages/eel/py.typed
Normal file
28
venv3_12/Lib/site-packages/eel/types.py
Normal file
28
venv3_12/Lib/site-packages/eel/types.py
Normal 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
|
||||
]
|
||||
]
|
||||
]
|
||||
Reference in New Issue
Block a user