diff --git a/README.md b/README.md index 00fe1fd7..6a640a1b 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,10 @@ sent upstream. What this fork changes: -- The virtual device is the PyDevices MicroPython WASM build (lvgl, - pdwidgets, pygraphics and palettes built in; `displaydev`, `board_config` - and the rest of `pydevices-desktop` install from mip at boot), reached from +- The virtual device is the PyDevices MicroPython WASM build, with the whole + PyDevices stack frozen in (lvgl, pdwidgets, pygraphics, palettes, + `displaydev`, the desktop `board_config` and the rest; nothing installs at + boot), reached from its own toolbar button alongside USB/Bluetooth/WebREPL. It gets a real display stage — resolution and shape presets including round watch faces, live resize, audio, pointer and keyboard input — and boots with example diff --git a/assets/pydevices/micropython.wasm b/assets/pydevices/micropython.wasm index db07c4fc..bc581f4e 100755 Binary files a/assets/pydevices/micropython.wasm and b/assets/pydevices/micropython.wasm differ diff --git a/assets/pydevices/micropython.wasm.provenance b/assets/pydevices/micropython.wasm.provenance index 19dcfbf8..1f431288 100644 --- a/assets/pydevices/micropython.wasm.provenance +++ b/assets/pydevices/micropython.wasm.provenance @@ -1,32 +1,44 @@ { "binary": { - "mtime": "2026-09-26T07:55:58-05:00", + "mtime": "2026-10-05T17:03:35-05:00", "name": "micropython.wasm", - "sha256": "a7ddaf21a96e81aac63497b48382e7aa0d098044ab15550b6389feedbd73a4bd", - "size": 5880566 + "sha256": "4cc43ae8a82e709e3bfbc56272bab1396ea73e0c5b433bd93d39e49e9993bb39", + "size": 6714453 }, "format": 1, "install_only": false, "overlays": [ + "0001-micropython-windows-enable-networking-sockets-select-ssl-msvc.patch", + "0002-micropython-unix-raise-MICROPY_SCHEDULER_DEPTH-for-desktop-SDL.patch", + "0003-micropython-windows-enable-ffi-modffi-libffi.patch", "0004-micropython-webassembly-allow-Asyncify-in-runPythonAsync.patch", "0005-micropython-webassembly-fix-node-hook-ccall-signatures.patch", "0006-micropython-webassembly-support-soft-reinitialization.patch", "0007-micropython-webassembly-inert-jsffi-callbacks-across-reinit.patch", - "0008-micropython-webassembly-fix-empty-single-input-lexer-eof-primer.patch" + "0008-micropython-webassembly-fix-empty-single-input-lexer-eof-primer.patch", + "0009-micropython-esp32s3-add-SPIRAM_OCT_DEBUG-variant.patch", + "0010-micropython-esp32-i2s-mck-pin.patch", + "0011-micropython-esp32-webrepl-ctrl-c-in-tight-loops.patch", + "0012-micropython-unix-variant-configurable-default-heap-size.patch", + "0013-micropython-windows-read-piped-stdin-in-the-repl.patch", + "0014-micropython-esp32-tinyusb-0.21.patch", + "0015-micropython-windows-timing-module-and-idle-console.patch", + "0016-micropython-esp32-machine-timer-fire-during-reinit.patch", + "0017-micropython-py-makeqstrdefs-incremental-build-header-qstr.patch" ], "sources": { "audiodsp": { - "committed": "2026-09-25T06:49:25-05:00", - "describe": "v0.0.3-279-g094dde5", + "committed": "2026-10-05T15:26:05-05:00", + "describe": "v0.0.3-309-g79616ba", "dirty": false, - "head": "094dde5203dadcbf30fe5f2b6a88c16e413799b8", + "head": "79616bae165b63069ccecc6d335c00ca334b193c", "path": "/home/brad/gh/pydevices/audiodsp" }, "audioif": { - "committed": "2026-09-24T09:44:37-05:00", - "describe": "v0.1.1", + "committed": "2026-10-05T16:59:29-05:00", + "describe": "v0.1.1-1-gb6b35a4", "dirty": false, - "head": "71c78419c7199258933ea555591ed4e6f20c0e89", + "head": "b6b35a48a0cd23ddc549f8c0643d77ffe778da28", "path": "/home/brad/gh/pydevices/audioif" }, "cameraif": { @@ -44,55 +56,55 @@ "path": "/home/brad/gh/pydevices/circuitpython" }, "displayif": { - "committed": "2026-09-25T18:57:38-05:00", - "describe": "6ea4d30", + "committed": "2026-10-05T15:58:30-05:00", + "describe": "370dad6", "dirty": false, - "head": "6ea4d30e5562bd55c3aee43a422aec439ca095ba", + "head": "370dad6fb7119ed80a35d782302d4ae52a0f50c5", "path": "/home/brad/gh/pydevices/displayif" }, "lvgl-circuitpython": { - "committed": "2026-09-26T07:13:38-05:00", - "describe": "3b6e652", + "committed": "2026-09-26T18:07:40-05:00", + "describe": "caaf6dc", "dirty": false, - "head": "3b6e652e1d37f1a1081d7f91e3a7dcd8738d8dd6", + "head": "caaf6dc8e3c5975fe61488814a9176bcd2cf138f", "path": "/home/brad/gh/pydevices/lvgl-circuitpython" }, "lvgl-micropython": { - "committed": "2026-09-26T07:13:33-05:00", - "describe": "1024064", + "committed": "2026-10-05T16:59:29-05:00", + "describe": "821fc45", "dirty": false, - "head": "1024064e0e0ed4e986383bf2ad08416b3767d51b", + "head": "821fc459e6210cf0ee441fc9a65a13a223ab8d84", "path": "/home/brad/gh/pydevices/lvgl-micropython" }, "micropython": { - "committed": "2026-09-26T01:50:11-05:00", - "describe": "v1.29.0-1-g152121c", + "committed": "2026-10-05T16:10:55-05:00", + "describe": "v1.29.0-1-g8a22473", "dirty": false, - "head": "152121ce6a427ef8a83c7b2f68e4bf7aa39b69cd", + "head": "8a224736515fca0ef6288059fa9635560858d5a5", "path": "/home/brad/gh/pydevices/micropython" }, "micropython-pydevices": { - "committed": "2026-09-26T06:17:21-05:00", - "describe": "6e29120", - "dirty": true, - "head": "6e29120c80995dca606ad344179e39c6d7523319", + "committed": "2026-10-05T16:59:10-05:00", + "describe": "3fa0688", + "dirty": false, + "head": "3fa068873cb135566356386d2810ed0e86a6758e", "path": "/home/brad/gh/pydevices/micropython-pydevices" }, "pygraphics": { - "committed": "2026-09-23T22:38:03-05:00", - "describe": "v0.0.32-84-g04f5b44", + "committed": "2026-09-26T19:35:02-05:00", + "describe": "v0.0.32-86-gb4d4c49", "dirty": false, - "head": "04f5b44850fcb8aa83f6798eb68fb57fb1578d88", + "head": "b4d4c49cc2a22cdd2632f2eea2593b4cc4df4db1", "path": "/home/brad/gh/pydevices/pygraphics" }, "usbif": { - "committed": "2026-09-25T22:28:49-05:00", - "describe": "c37ce17", - "dirty": true, - "head": "c37ce17f281ded56da8f04b9f9a8c29a8cba298d", + "committed": "2026-09-26T17:28:03-05:00", + "describe": "4c01722", + "dirty": false, + "head": "4c01722e9e0474697dcb2734fb0953bb3019e99c", "path": "/home/brad/gh/pydevices/usbif" } }, - "stamped": "2026-09-26T07:55:59-05:00", + "stamped": "2026-10-05T17:03:36-05:00", "target": "mp-wasm" } diff --git a/src/pydevices/runtime.js b/src/pydevices/runtime.js index baa9698e..d43b0784 100644 --- a/src/pydevices/runtime.js +++ b/src/pydevices/runtime.js @@ -36,8 +36,6 @@ export function setBootSize(width, height) { bootSize = { width, height } } -const MIP_INDEX = 'https://PyDevices.github.io/mip' - // The runtime is ~6 MB, so it is fetched on first connect rather than bundled. // The import has to survive Rollup: an IIFE bundle cannot code-split, and a // literal specifier would be inlined (or fail to resolve). Going through @@ -61,19 +59,14 @@ async function loadPyDevicesMicroPython(mpOpts) { } /* - * Boot script, mirroring what the PyDevices simulator runs before handing over - * the REPL: tell displaydev how big the panel is, put the runtime's own - * directories on sys.path, then make sure the desktop support package is there. - * - * board_config is the entry point user code imports, so its presence is what - * decides whether the package still needs installing - checked as a file rather - * than an import, which would build a display as a side effect. The install - * needs the network; failing it leaves a usable REPL without the PyDevices - * libraries, and the next connect tries again. + * Boot script: put the runtime's own directories on sys.path, then tell + * displaydev how big the panel is. * - * Order matters: displaydev arrives with the package, so the panel size can only - * be set once the install has run. Builds that freeze the libraries in will - * satisfy the import either way. + * The PyDevices stack - displaydev, appdev, the desktop board_config and the + * rest - is frozen into the WASM runtime, and workbench never installs any of + * it. If it is missing, this is a runtime built without it, and the import + * below fails loudly rather than reaching for the network. Installing other + * packages (examples and the like) is still the package manager's job. */ function bootScript(width, height) { return ` @@ -82,20 +75,9 @@ import os, sys # target by looking for a sys.path entry that ends with "/lib", and a # relative "lib" left it refusing to install anything on the simulator. sys.path[:] = [".", ".frozen", "/lib", "/utils"] -try: - os.stat("/lib/board_config.mpy") -except OSError: - try: - import mip - mip.install("pydevices-desktop", index=${JSON.stringify(MIP_INDEX)}, target="/lib") - except Exception as exc: - print("PyDevices libraries unavailable offline:", exc) -try: - from displaydev import env_set - env_set("PYDEVICES_WIDTH", ${Number(width)}) - env_set("PYDEVICES_HEIGHT", ${Number(height)}) -except ImportError: - print("displaydev unavailable - the display will use its own default size") +from displaydev import env_set +env_set("PYDEVICES_WIDTH", ${Number(width)}) +env_set("PYDEVICES_HEIGHT", ${Number(height)}) os.chdir("/") ` } diff --git a/src/vm_vfs/00_touch_paint.py b/src/vm_vfs/00_touch_paint.py index 455710aa..6a72b790 100644 --- a/src/vm_vfs/00_touch_paint.py +++ b/src/vm_vfs/00_touch_paint.py @@ -5,7 +5,7 @@ display driver, the same events. Connect hardware over USB, copy this file across, and it runs unchanged. -Only needs pydevices-desktop, which the simulator installs at boot. +Only needs the PyDevices stack, which the simulator has frozen in. """ import board_config diff --git a/test/browser/boot-installs-nothing.spec.js b/test/browser/boot-installs-nothing.spec.js new file mode 100644 index 00000000..fe30086e --- /dev/null +++ b/test/browser/boot-installs-nothing.spec.js @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +// +// The PyDevices stack is frozen into the WASM runtime, so connecting the +// simulator installs nothing: no request to any mip index while it boots. +// Boot used to mip-install pydevices-desktop on every connect; this keeps +// that from coming back unnoticed. +import { test, expect } from '@playwright/test' +import { connectSimulator, focusTerminal, terminalText } from './helpers.js' + +const MIP_INDEXES = [/pydevices\.github\.io\/mip/i, /micropython\.org\/pi\//i] + +test('connecting the simulator installs nothing and the stack is there', async ({ page }) => { + const indexRequests = [] + page.on('request', (request) => { + if (MIP_INDEXES.some((pattern) => pattern.test(request.url()))) { + indexRequests.push(request.url()) + } + }) + + await connectSimulator(page) + + await focusTerminal(page) + const before = await terminalText(page) + await page.keyboard.type('import os, board_peripherals, displaydev; print("stack-ok", os.listdir("/lib"))') + await page.keyboard.press('Enter') + await expect + .poll(async () => (await terminalText(page)).slice(before.length)) + .toContain('stack-ok') + + expect(indexRequests).toEqual([]) +}) diff --git a/test/browser/helpers.js b/test/browser/helpers.js index ad01daa9..db92ebe3 100644 --- a/test/browser/helpers.js +++ b/test/browser/helpers.js @@ -2,10 +2,8 @@ import { expect } from '@playwright/test' /* - * Connects the PyDevices virtual device and waits for a real prompt - not a - * fixed delay, since the mip install this triggers (~55 requests for - * pydevices-desktop) varies with network conditions far more than the WASM - * boot itself does. + * Connects the PyDevices virtual device and waits for a real prompt, not a + * fixed delay: the WASM boot's time varies with the machine. */ export async function connectSimulator(page) { await page.goto('/') diff --git a/test/browser/playwright.config.js b/test/browser/playwright.config.js index 3eb38edb..5fd0ab63 100644 --- a/test/browser/playwright.config.js +++ b/test/browser/playwright.config.js @@ -11,8 +11,8 @@ import { defineConfig } from '@playwright/test' export default defineConfig({ testDir: '.', timeout: 60_000, - // The PyDevices VM installs pydevices-desktop from mip on every connect - // (~55 small requests) before it prints a prompt; give it room. + // The PyDevices VM boots the WASM runtime before it prints a prompt; give + // it room on a slow machine. expect: { timeout: 45_000 }, fullyParallel: false, workers: 1, diff --git a/test/browser/templates.spec.js b/test/browser/templates.spec.js index 973645ae..da2c6dff 100644 --- a/test/browser/templates.spec.js +++ b/test/browser/templates.spec.js @@ -2,9 +2,9 @@ // // Every example the VM ships runs clean and paints something. One connect // and one page for the whole file, each example run as a step within it - -// booting the simulator (the mip install alone is ~55 requests) dominates -// the cost of this suite far more than running six short programs does, and -// nothing here mutates state a later example depends on. +// booting the simulator dominates the cost of this suite far more than +// running six short programs does, and nothing here mutates state a later +// example depends on. import { test, expect } from '@playwright/test' import { connectSimulator, runFile, distinctCanvasColors, clickCanvas } from './helpers.js'