2.0 first commit

This commit is contained in:
louisnw
2024-03-30 15:38:34 +00:00
parent a91ea493d7
commit e9f21b3b0e
69 changed files with 4081 additions and 2107 deletions

View File

@ -1,37 +1,43 @@
import asyncio
import json
import multiprocessing as mp
import typing
import webview
from webview.errors import JavascriptException
from lightweight_charts import abstract
from .util import parse_event_message, FLOAT
import os
import threading
class CallbackAPI:
def __init__(self, emit_queue):
self.emit_q = emit_queue
self.emit_queue = emit_queue
def callback(self, message: str):
self.emit_q.put(message)
self.emit_queue.put(message)
class PyWV:
def __init__(self, q, start_ev, exit_ev, loaded, emit_queue, return_queue, html, debug,
width, height, x, y, screen, on_top, maximize, title):
def __init__(self, q, emit_q, return_q, loaded_event):
self.queue = q
self.return_queue = return_queue
self.exit = exit_ev
self.callback_api = CallbackAPI(emit_queue)
self.loaded: list = loaded
self.html = html
self.return_queue = return_q
self.emit_queue = emit_q
self.loaded_event = loaded_event
self.windows = []
self.create_window(width, height, x, y, screen, on_top, maximize, title)
self.is_alive = True
start_ev.wait()
webview.start(debug=debug)
self.exit.set()
self.callback_api = CallbackAPI(emit_q)
self.windows: typing.List[webview.Window] = []
self.loop()
def create_window(self, width, height, x, y, screen=None, on_top=False, maximize=False, title=''):
def create_window(
self, width, height, x, y, screen=None, on_top=False,
maximize=False, title=''
):
screen = webview.screens[screen] if screen is not None else None
if maximize:
if screen is None:
@ -39,64 +45,147 @@ class PyWV:
width, height = active_screen.width, active_screen.height
else:
width, height = screen.width, screen.height
self.windows.append(webview.create_window(
title, html=self.html, js_api=self.callback_api,
width=width, height=height, x=x, y=y, screen=screen,
on_top=on_top, background_color='#000000'))
self.windows[-1].events.loaded += lambda: self.loop(self.loaded[len(self.windows)-1])
def loop(self, loaded):
loaded.set()
while 1:
self.windows.append(webview.create_window(
title,
url=abstract.INDEX,
js_api=self.callback_api,
width=width,
height=height,
x=x,
y=y,
screen=screen,
on_top=on_top,
background_color='#000000')
)
def loop(self):
self.loaded_event.set()
while self.is_alive:
i, arg = self.queue.get()
if i == 'start':
webview.start(debug=arg, func=self.loop)
self.is_alive = False
self.emit_queue.put('exit')
return
if i == 'create_window':
self.create_window(*arg)
elif arg in ('show', 'hide'):
getattr(self.windows[i], arg)()
elif arg == 'exit':
self.exit.set()
continue
window = self.windows[i]
if arg == 'show':
window.show()
elif arg == 'hide':
window.hide()
# TODO make sure setup.py requires latest pywebview now
else:
try:
if '_~_~RETURN~_~_' in arg:
self.return_queue.put(self.windows[i].evaluate_js(arg[14:]))
self.return_queue.put(window.evaluate_js(arg[14:]))
else:
self.windows[i].evaluate_js(arg)
except KeyError:
window.evaluate_js(arg)
except KeyError as e:
return
except JavascriptException as e:
msg = eval(str(e))
raise JavascriptException(f"\n\nscript -> '{arg}',\nerror -> {msg['name']}[{msg['line']}:{msg['column']}]\n{msg['message']}")
class WebviewHandler():
def __init__(self) -> None:
self._reset()
self.debug = False
def _reset(self):
self.loaded_event = mp.Event()
self.return_queue = mp.Queue()
self.function_call_queue = mp.Queue()
self.emit_queue = mp.Queue()
self.wv_process = mp.Process(
target=PyWV, args=(
self.function_call_queue, self.emit_queue,
self.return_queue, self.loaded_event
),
daemon=True
)
self.max_window_num = -1
def create_window(
self, width, height, x, y, screen=None, on_top=False,
maximize=False, title=''
):
self.function_call_queue.put((
'create_window',
(width, height, x, y, screen, on_top, maximize, title)
))
self.max_window_num += 1
return self.max_window_num
def start(self):
self.loaded_event.clear()
self.wv_process.start()
self.function_call_queue.put(('start', self.debug))
self.loaded_event.wait()
def show(self, window_num):
self.function_call_queue.put((window_num, 'show'))
def hide(self, window_num):
self.function_call_queue.put((window_num, 'hide'))
def evaluate_js(self, window_num, script):
self.function_call_queue.put((window_num, script))
def exit(self):
if self.wv_process.is_alive():
self.wv_process.terminate()
self.wv_process.join()
self._reset()
class Chart(abstract.AbstractChart):
MAX_WINDOWS = 10
_window_num = 0
_main_window_handlers = None
_exit, _start = (mp.Event() for _ in range(2))
_q, _emit_q, _return_q = (mp.Queue() for _ in range(3))
_loaded_list = [mp.Event() for _ in range(MAX_WINDOWS)]
WV: WebviewHandler = WebviewHandler()
def __init__(
self,
width: int = 800,
height: int = 600,
x: int = None,
y: int = None,
title: str = '',
screen: int = None,
on_top: bool = False,
maximize: bool = False,
debug: bool = False,
toolbox: bool = False,
inner_width: float = 1.0,
inner_height: float = 1.0,
scale_candles_only: bool = False,
position: FLOAT = 'left'
):
Chart.WV.debug = debug
self._i = Chart.WV.create_window(
width, height, x, y, screen, on_top, maximize, title
)
window = abstract.Window(
script_func=lambda s: Chart.WV.evaluate_js(self._i, s),
js_api_code='pywebview.api.callback'
)
abstract.Window._return_q = Chart.WV.return_queue
def __init__(self, width: int = 800, height: int = 600, x: int = None, y: int = None, title: str = '',
screen: int = None, on_top: bool = False, maximize: bool = False, debug: bool = False,
toolbox: bool = False, inner_width: float = 1.0, inner_height: float = 1.0,
scale_candles_only: bool = False, position: FLOAT = 'left'):
self._i = Chart._window_num
self._loaded = Chart._loaded_list[self._i]
abstract.Window._return_q = Chart._return_q
Chart._window_num += 1
self.is_alive = True
window = abstract.Window(lambda s: self._q.put((self._i, s)), 'pywebview.api.callback')
if self._i == 0:
if Chart._main_window_handlers is None:
super().__init__(window, inner_width, inner_height, scale_candles_only, toolbox, position=position)
Chart._main_window_handlers = self.win.handlers
self._process = mp.Process(target=PyWV, args=(
self._q, self._start, self._exit, Chart._loaded_list,
self._emit_q, self._return_q, abstract.TEMPLATE, debug,
width, height, x, y, screen, on_top, maximize, title
), daemon=True)
self._process.start()
else:
window.handlers = Chart._main_window_handlers
super().__init__(window, inner_width, inner_height, scale_candles_only, toolbox, position=position)
self._q.put(('create_window', (width, height, x, y, screen, on_top, maximize, title)))
def show(self, block: bool = False):
"""
@ -104,34 +193,31 @@ class Chart(abstract.AbstractChart):
:param block: blocks execution until the chart is closed.
"""
if not self.win.loaded:
self._start.set()
self._loaded.wait()
Chart.WV.start()
self.win.on_js_load()
else:
self._q.put((self._i, 'show'))
Chart.WV.show(self._i)
if block:
asyncio.run(self.show_async(block=True))
asyncio.run(self.show_async())
async def show_async(self, block=False):
async def show_async(self):
self.show(block=False)
if not block:
asyncio.create_task(self.show_async(block=True))
return
try:
from lightweight_charts import polygon
[asyncio.create_task(self.polygon.async_set(*args)) for args in polygon._set_on_load]
while 1:
while self._emit_q.empty() and not self._exit.is_set():
while Chart.WV.emit_queue.empty() and self.is_alive:
await asyncio.sleep(0.05)
if self._exit.is_set():
self._exit.clear()
self.is_alive = False
self.exit()
if not self.is_alive:
return
elif not self._emit_q.empty():
func, args = parse_event_message(self.win, self._emit_q.get())
response = Chart.WV.emit_queue.get()
if response == 'exit':
Chart.WV.exit()
self.is_alive = False
return
else:
func, args = parse_event_message(self.win, response)
await func(*args) if asyncio.iscoroutinefunction(func) else func(*args)
continue
except KeyboardInterrupt:
return
@ -145,12 +231,5 @@ class Chart(abstract.AbstractChart):
"""
Exits and destroys the chart window.\n
"""
self._q.put((self._i, 'exit'))
self._exit.wait() if self.win.loaded else None
self._process.terminate()
Chart._main_window_handlers = None
Chart._window_num = 0
Chart._q = mp.Queue()
Chart._exit.clear(), Chart._start.clear()
Chart.WV.exit()
self.is_alive = False