From e12238902cdeb780b306b076fec9257f1e23d1c9 Mon Sep 17 00:00:00 2001 From: pmp-p Date: Mon, 25 Sep 2023 03:24:12 +0200 Subject: [PATCH] fix #33 canvas keyboard initial(panda3d) and regain(pygame) focus --- pygbag/__init__.py | 12 +++-- pygbag/support/cross/__EMSCRIPTEN__.py | 11 ++++- pygbag/support/cross/aio/pep0723.py | 8 +++- pygbag/support/pythonrc.py | 40 ++++++++++++---- scripts/build-loader.sh | 11 +++++ static/pythons.js | 65 +++++++++++++++++++------- support/__EMSCRIPTEN__.c | 19 +++++++- 7 files changed, 132 insertions(+), 34 deletions(-) diff --git a/pygbag/__init__.py b/pygbag/__init__.py index 94506da..0dc6a6e 100644 --- a/pygbag/__init__.py +++ b/pygbag/__init__.py @@ -1,6 +1,8 @@ """ packager+server for pygbag wasm loader """ import sys + +# some Linux distro are stuck in the past. Better safe than sorry sys.stdout.reconfigure(encoding='utf-8') from pathlib import Path @@ -25,13 +27,15 @@ sys.path.append(str(Path(__file__).parent / "support/cross")) -# WaPy=>CPython compat - -import builtins +# WaPy<=>CPython compat try: + # embed builtin module handles I/O on wasm + import embed + # aio function implemented only on stackless WaPy sched_yield except: + import builtins builtins.sched_yield = lambda: None import sys, traceback @@ -57,5 +61,3 @@ def CSI(*argv): ESC(f"[{arg}") -builtins.ESC = ESC -builtins.CSI = CSI diff --git a/pygbag/support/cross/__EMSCRIPTEN__.py b/pygbag/support/cross/__EMSCRIPTEN__.py index 3fd99da..88cd675 100644 --- a/pygbag/support/cross/__EMSCRIPTEN__.py +++ b/pygbag/support/cross/__EMSCRIPTEN__.py @@ -149,11 +149,16 @@ class EventTarget: events = [] def addEventListener(self, host, type, listener, options=None, useCapture=None): - cli = self.clients.setdefault(type, []) + cli = self.__class__.clients.setdefault(type, []) cli.append(listener) def build(self, evt_name, jsondata): - self.events.append([evt_name, json.loads(jsondata)]) + try: + self.__class__.events.append([evt_name, json.loads(jsondata.strip('"'))]) + except Exception as e: + sys.print_exception(e) + print(jsondata) + # def dispatchEvent async def rpc(self, method, *argv): @@ -170,6 +175,8 @@ async def rpc(self, method, *argv): else: print(f"RPC not found: {method}{argv}") + # This is a green thread handling events from js -> python + async def process(self): import inspect from types import SimpleNamespace diff --git a/pygbag/support/cross/aio/pep0723.py b/pygbag/support/cross/aio/pep0723.py index ab0415c..7828c43 100644 --- a/pygbag/support/cross/aio/pep0723.py +++ b/pygbag/support/cross/aio/pep0723.py @@ -157,6 +157,12 @@ async def async_repos(): if repo not in Config.pkg_repolist: Config.pkg_repolist.append(repo) + if not aio.cross.simulator: + if window.location.href.startswith('https://pmp-p.ddns.net/pygbag'): + print(" =============== REDIRECTION TO DEV HOST ================ ") + for idx, repo in enumerate(PyConfig.pkg_repolist): + repo["-CDN-"] = "https://pmp-p.ddns.net/archives/repo/" + if Config.dev_mode > 0: for idx, repo in enumerate(Config.pkg_repolist): try: @@ -201,7 +207,7 @@ async def pip_install(pkg, sconf={}): wheel_pkg, wheel_hash = wheel_url.rsplit("/", 1)[-1].split("#", 1) await install_pkg(sconf, wheel_url, wheel_pkg) except: - print("ERROR", wheel_url) + print("INVALID", pkg, 'from',wheel_url) async def parse_code(code, env): diff --git a/pygbag/support/pythonrc.py b/pygbag/support/pythonrc.py index 42d9078..1799736 100644 --- a/pygbag/support/pythonrc.py +++ b/pygbag/support/pythonrc.py @@ -629,11 +629,11 @@ async def preload_code(cls, code, callback=None, hint=""): DBG(f"628: aio.pep0723.check_list {env=}") deps = await aio.pep0723.parse_code(code, env) DBG(f"629: aio.pep0723.pip_install {deps=}") - + for dep in deps: + await aio.pep0723.pip_install(dep) else: # sim use a local folder venv model - await aio.pep0723.check_list(code=code, filename=None) @@ -909,7 +909,7 @@ def browser_open_new_tab(url): def browser_open_file(target=None, accept="*"): if target: - platform.EventTarget.addEventListener("upload", target) + platform.EventTarget.addEventListener(window, "upload", target) platform.window.dlg_multifile.click() webbrowser.open_file = browser_open_file @@ -1274,15 +1274,17 @@ async def async_imports_init(cls): async with platform.fopen(Path(cdn) / cls.repodata) as source: cls.repos.append(json.loads(source.read())) - # print(json.dumps(cls.repos[0]["packages"], sort_keys=True, indent=4)) - DBG("referenced packages :", len(cls.repos[0]["packages"])) if not len(PyConfig.pkg_repolist): await cls.async_repos() - # print("1117: remapping ?", PyConfig.dev_mode) - if PyConfig.pygbag > 0: + if window.location.href.startswith('https://pmp-p.ddns.net/pygbag/'): + print(" =============== REDIRECTION TO DEV HOST ================ ") + for idx, repo in enumerate(PyConfig.pkg_repolist): + repo["-CDN-"] = "https://pmp-p.ddns.net/archives/repo/" + elif PyConfig.pygbag > 0: +# if PyConfig.pygbag > 0: for idx, repo in enumerate(PyConfig.pkg_repolist): DBG("1264:", repo["-CDN-"], "REMAPPED TO", PyConfig.pkg_indexes[-1]) repo["-CDN-"] = PyConfig.pkg_indexes[-1] @@ -1616,11 +1618,31 @@ def patch_panda3d_showbase(): import panda3d.core from direct.showbase.ShowBase import ShowBase - print("panda3d: apply model path patch") + print(f"panda3d: apply model path {os.getcwd()} patch") panda3d.core.get_model_path().append_directory(os.getcwd()) + panda3d.core.loadPrcFileData("", "win-size 1024 600") + panda3d.core.loadPrcFileData("", "support-threads #f") + panda3d.core.loadPrcFileData("", "textures-power-2 down") + panda3d.core.loadPrcFileData("", "textures-square down") def run(*argv, **env): - print("ShowBase.run patched") + print("ShowBase.run patched to launch asyncio.run(main())") + import direct.task.TaskManagerGlobal + async def main(): + try: + print('1633: auto resizing') + platform.window.window_resize() + except: + ... + while not asyncio.get_running_loop().is_closed(): + try: + direct.task.TaskManagerGlobal.taskMgr.step() + except SystemExit: + print('87: Panda3D stopped',file= sys.stderr) + break + # go to host + await asyncio.sleep(0) + asyncio.run(main()) print("panda3d: apply ShowBase.run patch") ShowBase.run = run diff --git a/scripts/build-loader.sh b/scripts/build-loader.sh index 0d57d0c..7eea166 100755 --- a/scripts/build-loader.sh +++ b/scripts/build-loader.sh @@ -209,6 +209,14 @@ done echo CPY_CFLAGS=$CPY_CFLAGS +#\ +# -I/opt/python-wasm-sdk/emsdk/upstream/emscripten/cache/sysroot/include/freetype2 -lfreetype\ +# -lopenal \ +#\ + + + + if emcc -fPIC -std=gnu99 -D__PYDK__=1 -DNDEBUG $CPY_CFLAGS $CF_SDL $CPOPTS \ -c -fwrapv -Wall -Werror=implicit-function-declaration -fvisibility=hidden\ -I${PYDIR}/internal -I${PYDIR} -I./support -DPy_BUILD_CORE\ @@ -241,8 +249,11 @@ then #LDFLAGS="$LD_VENDOR -sUSE_GLFW=3 -sUSE_WEBGL2 -sMIN_WEBGL_VERSION=2 -sOFFSCREENCANVAS_SUPPORT=1 -sFULL_ES2 -sFULL_ES3" LDFLAGS="$LD_SDL2" + LDFLAGS="-sUSE_GLFW=3 -sUSE_WEBGL2 -sMIN_WEBGL_VERSION=2 -sOFFSCREENCANVAS_SUPPORT=1 -sFULL_ES2 -sFULL_ES3" +# -sUSE_FREETYPE -sUSE_HARFBUZZ" + if echo ${PYBUILD}|grep -q 10$ then diff --git a/static/pythons.js b/static/pythons.js index de4aa3e..581aa4f 100644 --- a/static/pythons.js +++ b/static/pythons.js @@ -635,15 +635,11 @@ console.warn("TODO: user defined canvas") } vm.canvas3d = canvas3d -/* + canvas.addEventListener("click", MM.focus_handler) +/* - function event_canvas_regain(event) { - console.log("entering canvas") - canvas.focus(); - } - canvas.addEventListener('mouseenter', event_canvas_regain, false); function event_fullscreen(event){ if (!event.target.hasAttribute('fullscreen')) return; @@ -1015,14 +1011,46 @@ function feat_stdout() { // TODO make a queue, python is not always ready to receive those events // right after page load -function feat_lifecycle() { - window.addEventListener("focus", function(e){ - queue_event("focus", e ) - }) - window.addEventListener("blur", function(e){ - queue_event("blur", e ) - }) +function focus_handler(ev) { + if (ev.type == "click") { + canvas.removeEventListener("click", MM.focus_handler) + canvas.focus() + return + } + + if (ev.type == "mouseenter") { + canvas.focus() + console.log("canvas focus set") + canvas.removeEventListener("mouseenter", MM.focus_handler) + return + } + + if (ev.type == "focus") { + queue_event("focus", ev ) + console.log("focus set") + canvas.focus() + return + } + + // for autofocus + if (ev.type == "blur") { + // remove initial focuser that may still be there + try { + canvas.removeEventListener("click", MM.focus_handler) + } catch (x ) {} + + canvas.addEventListener("click", MM.focus_handler) + canvas.addEventListener("mouseenter", MM.focus_handler) + queue_event("blur", ev ) + return + } +} + + +function feat_lifecycle() { + window.addEventListener("focus", MM.focus_handler) + window.addEventListener("blur", MM.focus_handler) if (!vm.config.can_close) { window.onbeforeunload = function() { @@ -1033,6 +1061,7 @@ function feat_lifecycle() { } } + function feat_snd() { // to set user media engagement status and possibly make it blocking MM.UME = !vm.config.ume_block @@ -1092,9 +1121,13 @@ function download(diskfile, filename) { - - -window.MM = { tracks : 0, UME : true, download : download, camera : {} } +window.MM = { + tracks : 0, + UME : true, + download : download, + focus_handler : focus_handler, + camera : {} +} async function media_prepare(trackid) { const track = MM[trackid] diff --git a/support/__EMSCRIPTEN__.c b/support/__EMSCRIPTEN__.c index ac0bf82..817c072 100644 --- a/support/__EMSCRIPTEN__.c +++ b/support/__EMSCRIPTEN__.c @@ -714,6 +714,11 @@ embed_webgl(PyObject *self, PyObject *args, PyObject *kwds) PyStatus status; +#if defined(FT) +#include +#include FT_FREETYPE_H +#endif + int main(int argc, char **argv) @@ -870,7 +875,19 @@ EM_ASM({ PyRun_SimpleString("import sys, os, json, builtins, shutil, time"); - //PyRun_SimpleString("import universal;print('HPy init done')"); + //PyRun_SimpleString("import hpy;import hpy.universal;print('HPy init done')"); +#if defined(FT) + int error; + + FT_Library library; + error = FT_Init_FreeType(&library); + if (error) { + printf("FT error %d\n", error); + } else { + puts(" @@@@@@@@@@@@@@@@@@@@@ FT OK @@@@@@@@@@@@@@@@@@@@"); + } +#endif + #if SDL2 // SDL2 basic init {