Skip to content

Commit

Permalink
Merge pull request python-eel#33 from ahopkins/master
Browse files Browse the repository at this point in the history
Using dict or string to set browser location to any arbitrary location
  • Loading branch information
ChrisKnott authored Apr 3, 2018
2 parents 0268cce + f6af67d commit 507879d
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 80 deletions.
120 changes: 82 additions & 38 deletions eel/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
from __future__ import print_function
from builtins import range
from io import open
import json as jsn, bottle as btl, bottle.ext.websocket as wbs, gevent as gvt
import re as rgx, os, subprocess as sps, eel.browsers as brw, random as rnd, sys
import json as jsn
import bottle as btl
import bottle.ext.websocket as wbs
import gevent as gvt
import re as rgx
import os
# import subprocess as sps
import eel.browsers as brw
import random as rnd
import sys
import pkg_resources as pkg

_eel_js_file = pkg.resource_filename('eel', 'eel.js')
Expand All @@ -25,12 +33,15 @@

# Public functions

def expose(name_or_function = None):
if name_or_function == None: # Deal with '@eel.expose()' - treat as '@eel.expose'

def expose(name_or_function=None):
# Deal with '@eel.expose()' - treat as '@eel.expose'
if name_or_function is None:
return expose

if type(name_or_function) == str: # Called as '@eel.expose("my_name")'
name = name_or_function
name = name_or_function

def decorator(function):
_expose(name, function)
return function
Expand All @@ -39,7 +50,8 @@ def decorator(function):
function = name_or_function
_expose(function.__name__, function)
return function



def init(path):
global root_path, _js_functions
root_path = _get_real_path(path)
Expand All @@ -50,14 +62,17 @@ def init(path):
allowed_extensions = '.js .html .txt .htm .xhtml'.split()
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()
for expose_call in rgx.findall(r'eel\.expose\((.*)\)', contents):
finder = rgx.findall(r'eel\.expose\((.*)\)', contents)
for expose_call in finder:
expose_call = expose_call.strip()
assert rgx.findall(r'[\(=]', expose_call) == [], "eel.expose() call contains '(' or '='"
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:
Expand All @@ -67,6 +82,7 @@ def init(path):
for js_function in _js_functions:
_mock_js_function(js_function)


def start(*start_urls, **kwargs):
block = kwargs.pop('block', True)
options = kwargs.pop('options', {})
Expand All @@ -77,57 +93,75 @@ def start(*start_urls, **kwargs):
for k, v in list(_default_options.items()):
if k not in options:
options[k] = v

_start_geometry['default'] = {'size': size, 'position': position}
_start_geometry['pages'] = geometry

brw.open(start_urls, options)

run_lambda = lambda: btl.run(host=options['host'], port=options['port'], server=wbs.GeventWebSocketServer, quiet=True)
def run_lambda():
return btl.run(
host=options['host'],
port=options['port'],
server=wbs.GeventWebSocketServer,
quiet=True)
if block:
run_lambda()
else:
spawn(run_lambda)


def sleep(seconds):
gvt.sleep(seconds)


def spawn(function):
gvt.spawn(function)

# Bottle Routes


@btl.route('/eel.js')
def _eel():
page = _eel_js.replace('/** _py_functions **/', '_py_functions: %s,' % list(_exposed_functions.keys()))
page = page.replace('/** _start_geometry **/', '_start_geometry: %s,' % jsn.dumps(_start_geometry))
funcs = list(_exposed_functions.keys())
page = _eel_js.replace('/** _py_functions **/',
'_py_functions: %s,' % funcs)
page = page.replace('/** _start_geometry **/',
'_start_geometry: %s,' % jsn.dumps(_start_geometry))
return page



@btl.route('/<path:path>')
def _static(path):
return btl.static_file(path, root=root_path)
return btl.static_file(path, root=root_path)


@btl.get('/eel', apply=[wbs.websocket])
def _websocket(ws):
global _websockets
_websockets += [ws]

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:
ws.send(jsn.dumps(call))
_mock_queue_done.add(page)

while True:
msg = ws.receive()
if msg != None:
if msg is not None:
message = jsn.loads(msg)
if 'call' in message:
return_val = _exposed_functions[message['name']](*message['args'])
ws.send(jsn.dumps({'return': message['call'], 'value': return_val}))
return_val = _exposed_functions[message['name']](
*message['args'])
ws.send(
jsn.dumps({
'return': message['call'],
'value': return_val
}))
elif 'return' in message:
call_id = message['return']
if call_id in _call_return_callbacks:
Expand All @@ -138,63 +172,73 @@ def _websocket(ws):
else:
print('Invalid message received: ', message)
else:
_websockets.remove(ws)
_websockets.remove(ws)
break

_websocket_close()

# Private functions


def _get_real_path(path):
if getattr(sys, 'frozen', False):
return os.path.join(sys._MEIPASS, path)
else:
return os.path.abspath(path)


def _mock_js_function(f):
exec('%s = lambda *args: _mock_call("%s", args)' % (f, f), globals())



def _import_js_function(f):
exec('%s = lambda *args: _js_call("%s", args)' % (f, f), globals())



def _call_object(name, args):
global _call_number
_call_number += 1
call_id = _call_number + rnd.random()
call_id = _call_number + rnd.random()
return {'call': call_id, 'name': name, 'args': args}


def _mock_call(name, args):
call_object = _call_object(name, args)
global _mock_queue
_mock_queue += [call_object]
return _call_return(call_object)



def _js_call(name, args):
call_object = _call_object(name, args)
call_object = _call_object(name, args)
for ws in _websockets:
ws.send(jsn.dumps(call_object))
return _call_return(call_object)


def _call_return(call):
call_id, name, args = call['call'], call['name'], call['args']
def return_func(callback = None):
if callback != None:
call_id = call['call']

def return_func(callback=None):
if callback is None:
_call_return_callbacks[call_id] = callback
else:
for w in range(10000):
if call_id in _call_return_values:
return _call_return_values.pop(call_id)
sleep(0.001)
return return_func



def _expose(name, function):
assert name not in _exposed_functions, 'Already exposed function with name "%s"' % name
msg = 'Already exposed function with name "%s"' % name
assert name not in _exposed_functions, msg
_exposed_functions[name] = function


def _websocket_close():
# a websocket just closed
# TODO: user definable behavior here
sleep(1.0)
if len(_websockets) == 0:
sys.exit()

11 changes: 8 additions & 3 deletions eel/__main__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
from __future__ import print_function
import sys, pkg_resources as pkg, PyInstaller.__main__ as pyi, os
import sys
import pkg_resources as pkg
import PyInstaller.__main__ as pyi
import os

args = sys.argv[1:]
main_script = args.pop(0)
web_folder = args.pop(0)

print("Building executable with main script '%s' and web folder '%s'...\n" % (main_script, web_folder))
print("Building executable with main script '%s' and web folder '%s'...\n" %
(main_script, web_folder))

eel_js_file = pkg.resource_filename('eel', 'eel.js')
js_file_arg = '%s%seel' % (eel_js_file, os.pathsep)
web_folder_arg = '%s%s%s' % (web_folder, os.pathsep, web_folder)

needed_args = ['--hidden-import', 'bottle_websocket', '--add-data', js_file_arg, '--add-data', web_folder_arg]
needed_args = ['--hidden-import', 'bottle_websocket',
'--add-data', js_file_arg, '--add-data', web_folder_arg]
full_args = [main_script] + needed_args + args

print('Running:\npyinstaller', ' '.join(full_args), '\n')
Expand Down
56 changes: 43 additions & 13 deletions eel/browsers.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,58 @@
import webbrowser as wbr, sys, subprocess as sps, os
from whichcraft import which

def open(start_pages, options):
def _build_url_from_dict(page, options):
scheme = page.get('scheme', 'http')
host = page.get('host', 'localhost')
port = page.get('port', 8000)
path = page.get('path', '')
return '%s://%s:%d/%s' % (scheme, host, port, path)


def _build_url_from_string(page, options):
base_url = 'http://%s:%d/' % (options['host'], options['port'])
start_urls = [base_url + page for page in start_pages]
return base_url + page


def _build_urls(start_pages, options):
urls = []

for page in start_pages:
method = _build_url_from_dict if isinstance(
page, dict) else _build_url_from_string
url = method(page, options)
urls.append(url)

return urls


def open(start_pages, options):
start_urls = _build_urls(start_pages, options)

if options['mode'] in ['chrome', 'chrome-app']:
chrome_path = find_chrome()
if chrome_path != None:

if chrome_path is not None:
if options['mode'] == 'chrome-app':
for url in start_urls:
sps.Popen([chrome_path, '--app=%s' % url] + options['chromeFlags'], stdout=sps.PIPE, stderr=sps.PIPE)
sps.Popen([chrome_path, '--app=%s' % url] +
options['chromeFlags'],
stdout=sps.PIPE,
stderr=sps.PIPE)
else:
args = options['chromeFlags'] + start_urls
sps.Popen([chrome_path, '--new-window'] + args, stdout=sps.PIPE, stderr=sps.PIPE)
sps.Popen([chrome_path, '--new-window'] + args,
stdout=sps.PIPE, stderr=sps.PIPE)
else:
raise EnvironmentError("Can't find Chrome or Chromium installation")
raise EnvironmentError(
"Can't find Chrome or Chromium installation")
elif options['mode'] in [None, False]:
pass # Don't open a browser
pass # Don't open a browser
else:
# Use system default browser
for url in start_urls:
wbr.open(url)


def find_chrome():
if sys.platform in ['win32', 'win64']:
return find_chrome_win()
Expand All @@ -34,25 +63,28 @@ def find_chrome():
else:
return None


def find_chrome_mac():
default_dir = r'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
if os.path.exists(default_dir):
return default_dir
return None


def find_chrome_linux():
import shutil as shu
import whichcraft as wch
chrome_names = ['chromium-browser',
'chromium',
'google-chrome',
'google-chrome-stable']

for name in chrome_names:
chrome = which(name)
chrome = wch.which(name)
if chrome is not None:
return chrome
return None


def find_chrome_win():
import winreg as reg
reg_path = r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe'
Expand All @@ -64,7 +96,5 @@ def find_chrome_win():
reg_key.Close()
except WindowsError:
pass


return chrome_path

Loading

0 comments on commit 507879d

Please sign in to comment.