From 83c63415140831e0c4a61cede0b2835392309e04 Mon Sep 17 00:00:00 2001 From: HalfSweet Date: Tue, 18 Aug 2026 23:23:51 +0800 Subject: [PATCH 1/2] feat(blackberry): add the Classic native QNX and Android Runtime hosts Add the BlackBerry Classic (SQC100, BlackBerry 10.3) as a private target family with two hosts over one shared contract: - hosts/blackberry-qnx: BB10 Core Native host (libscreen + BPS + EGL/GLES2), unsigned development BAR built and packaged in the digest-pinned BBNDK image, installed with blackberry-deploy on a rooted Classic. First device run recorded (SQC100-4, 10.3.3.3216). - hosts/blackberry-android: Android Runtime (API 18) host, v1-signed APK with a JNI armeabi-v7a library over the same QuickJS bridge and Rust core, sideloaded on a stock Classic. - tools/blackberry-classic-profile.ts registers blackberry-qnx-dev and blackberry-android-dev (720x720 physical, 360x360 logical at density 2, host ABI 9); apps/blackberry-classic-demo is the Hero wrapper both hosts build; tools/native-host-build.ts holds the pinned QuickJS checkout, guest bundle, and package-identity steps native host tools share. - The resolved plan carries app.version next to id and title, and HostBuildInputs exposes app {id, title, version}: the Android manifest, strings, and BAR descriptor are templates rendered from that identity (id with `-` -> `_`, version verbatim, versionCode/buildId from the version), so no host holds a second copy of the package identity. - hosts/iphone2g/pocket_spec.h is generated from contracts/spec/spec.ts (gen-c.ts, drift-guarded by tests/contract.ts); both hosts take the portable button mask from it. - hosts/iphone2g/pocket_input.{c,h}: the input state machine both hosts feed (key edges, relative-axis pulses, primary button, one tracked touch contact whose latch fires only on down), unit-tested with the host compiler in tests/pocket-input.test.ts. - hosts/iphone2g/pocket_runtime.c gains pocket_runtime_tick (one guest turn + one core tick, the frame contract new hosts call) and pocket_runtime_gl_reset; the N-tick form stays internal behind the legacy iPhone/Windows CE entry points. - Both native hosts take the logical viewport, target id, host ABI, and raster density from the resolved plan. - tools/blackberry-android.ts unpacks the pinned SDK archives per host OS (Platform 18, Build-Tools 35.0.0, NDK r23c; published SHA-1s), pulls the pinned JDK image, and picks the NDK prebuilt by platform; tools/ blackberry-qnx.ts builds offline in the container and deploys over the device address. - tests/blackberry-classic.test.ts, docs/BLACKBERRY_CLASSIC.md, and the package/test registrations. --- apps/blackberry-classic-demo/app.tsx | 21 + apps/blackberry-classic-demo/main.tsx | 5 + apps/blackberry-classic-demo/pocket.json | 25 + contracts/spec/gen-c.ts | 46 ++ docs/BLACKBERRY_CLASSIC.md | 306 ++++++++ docs/STRUCTURE.md | 2 +- framework/src/manifest/host-build-inputs.ts | 18 +- framework/src/manifest/plan.ts | 4 +- framework/src/manifest/resolve.ts | 1 + .../app/AndroidManifest.xml | 30 + hosts/blackberry-android/app/jni/runtime.c | 307 ++++++++ .../app/res/values/strings.xml | 5 + .../blackberry/PocketActivity.java | 268 +++++++ hosts/blackberry-qnx/armv7-qnx-eabi.json | 24 + hosts/blackberry-qnx/bar-descriptor.xml | 33 + hosts/blackberry-qnx/main.c | 547 ++++++++++++++ hosts/iphone2g/pocket_input.c | 115 +++ hosts/iphone2g/pocket_input.h | 82 ++ hosts/iphone2g/pocket_runtime.c | 37 +- hosts/iphone2g/pocket_runtime.h | 29 +- hosts/iphone2g/pocket_spec.h | 26 + hosts/iphone2g/rust_eh_personality.c | 12 + package.json | 6 + tests/blackberry-classic.test.ts | 163 ++++ tests/contract.ts | 13 + tests/fixtures/plans/portable-psp.plan.json | 3 +- tests/fixtures/plans/portable-vita.plan.json | 3 +- tests/fixtures/pocket-input-test.c | 140 ++++ tests/host-build-inputs.test.ts | 4 + tests/npm-package.test.ts | 14 + tests/platform-contracts.test.ts | 2 + tests/pocket-input.test.ts | 40 + tests/test-suite.test.ts | 11 + tools/blackberry-android.ts | 700 ++++++++++++++++++ tools/blackberry-classic-profile.ts | 82 ++ tools/blackberry-qnx.ts | 591 +++++++++++++++ tools/blackberry-qnx/build.sh | 117 +++ tools/blackberry-qnx/quickjs-qnx.patch | 21 + tools/cli/blackberry-android-toolchain.json | 68 ++ tools/cli/blackberry-qnx-toolchain.json | 31 + tools/native-host-build.ts | 299 ++++++++ tools/test.ts | 2 + 42 files changed, 4241 insertions(+), 12 deletions(-) create mode 100644 apps/blackberry-classic-demo/app.tsx create mode 100644 apps/blackberry-classic-demo/main.tsx create mode 100644 apps/blackberry-classic-demo/pocket.json create mode 100644 contracts/spec/gen-c.ts create mode 100644 docs/BLACKBERRY_CLASSIC.md create mode 100644 hosts/blackberry-android/app/AndroidManifest.xml create mode 100644 hosts/blackberry-android/app/jni/runtime.c create mode 100644 hosts/blackberry-android/app/res/values/strings.xml create mode 100644 hosts/blackberry-android/app/src/dev/pocketstack/blackberry/PocketActivity.java create mode 100644 hosts/blackberry-qnx/armv7-qnx-eabi.json create mode 100644 hosts/blackberry-qnx/bar-descriptor.xml create mode 100644 hosts/blackberry-qnx/main.c create mode 100644 hosts/iphone2g/pocket_input.c create mode 100644 hosts/iphone2g/pocket_input.h create mode 100644 hosts/iphone2g/pocket_spec.h create mode 100644 hosts/iphone2g/rust_eh_personality.c create mode 100644 tests/blackberry-classic.test.ts create mode 100644 tests/fixtures/pocket-input-test.c create mode 100644 tests/pocket-input.test.ts create mode 100644 tools/blackberry-android.ts create mode 100644 tools/blackberry-classic-profile.ts create mode 100644 tools/blackberry-qnx.ts create mode 100755 tools/blackberry-qnx/build.sh create mode 100644 tools/blackberry-qnx/quickjs-qnx.patch create mode 100644 tools/cli/blackberry-android-toolchain.json create mode 100644 tools/cli/blackberry-qnx-toolchain.json create mode 100644 tools/native-host-build.ts diff --git a/apps/blackberry-classic-demo/app.tsx b/apps/blackberry-classic-demo/app.tsx new file mode 100644 index 00000000..035c94f9 --- /dev/null +++ b/apps/blackberry-classic-demo/app.tsx @@ -0,0 +1,21 @@ +import Hero from "../hero/app.tsx"; +import { reportAppAction } from "@pocketjs/framework/host"; + +/** + * The same guest bundle mounts under both Classic hosts (the native QNX + * runtime and the Android Runtime shell); nothing here may depend on which + * one is running it. + */ +export default function BlackBerryClassicHero() { + return ( + reportAppAction("hero_press", count)} + presentationHz={60} + runtimeLabel="RUST + QUICKJS + GLES2" + spinnerFrameStep={6} + /> + ); +} diff --git a/apps/blackberry-classic-demo/main.tsx b/apps/blackberry-classic-demo/main.tsx new file mode 100644 index 00000000..8532811e --- /dev/null +++ b/apps/blackberry-classic-demo/main.tsx @@ -0,0 +1,5 @@ +// @title PocketJS: BlackBerry Classic Hero +import { mount } from "@pocketjs/framework/solid"; +import BlackBerryClassicHero from "./app.tsx"; + +mount(() => ); diff --git a/apps/blackberry-classic-demo/pocket.json b/apps/blackberry-classic-demo/pocket.json new file mode 100644 index 00000000..b4fe8155 --- /dev/null +++ b/apps/blackberry-classic-demo/pocket.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://pocketjs.dev/schema/pocket-2.json", + "pocket": 2, + "id": "dev.pocket-stack.blackberry-classic-demo", + "name": "pocketjs-blackberry-classic-hero", + "title": "PocketJS: BlackBerry Classic Hero", + "version": "0.1.1", + "engine": { + "capabilities": { + "requires": ["input.buttons", "text.glyphs.baked"], + "enhances": ["input.touch"] + } + }, + "app": { + "entry": "apps/blackberry-classic-demo/main.tsx", + "output": "blackberry-classic-main", + "framework": "solid", + "viewport": { + "fixed": { + "logical": [360, 360], + "presentation": "native" + } + } + } +} diff --git a/contracts/spec/gen-c.ts b/contracts/spec/gen-c.ts new file mode 100644 index 00000000..e717c0d9 --- /dev/null +++ b/contracts/spec/gen-c.ts @@ -0,0 +1,46 @@ +// Deterministic codegen: contracts/spec/spec.ts -> hosts/iphone2g/pocket_spec.h, +// the C header every native host includes for the cross-language input +// constants (button bitmask, analog center). Hosts keep their own platform +// key codes; the portable mask they map onto comes only from here. +// +// Run from PocketJS/: bun contracts/spec/gen-c.ts +// +// tests/contract.ts imports generateC() and byte-compares its output against +// the committed header, so the C constants can never drift from spec.ts. Keep +// this generator free of anything non-deterministic. + +import { ANALOG_CENTER, BTN } from "./spec.ts"; + +function hex(n: number, pad = 4): string { + return "0x" + (n >>> 0).toString(16).toUpperCase().padStart(pad, "0"); +} + +export function generateC(): string { + const lines: string[] = []; + const put = (line = "") => lines.push(line); + + put("/* Generated by contracts/spec/gen-c.ts from contracts/spec/spec.ts."); + put(" * Do not edit: run `bun contracts/spec/gen-c.ts` and commit the result;"); + put(" * tests/contract.ts byte-compares this file against the generator. */"); + put("#ifndef POCKET_SPEC_H"); + put("#define POCKET_SPEC_H"); + put(); + put("/* Portable button bitmask — identical on every host. Native hosts map"); + put(" * their platform key codes onto these bits; the guest only sees the mask. */"); + for (const [name, value] of Object.entries(BTN)) { + put(`#define POCKET_BTN_${name} ${hex(value)}U`); + } + put(); + put("/* frame(buttons, analog): analog packs a stick as (x << 8) | y, each axis"); + put(" * 0..255 with 128 = center. Hosts without a stick pass this value. */"); + put(`#define POCKET_ANALOG_CENTER ${hex(ANALOG_CENTER)}U`); + put(); + put("#endif"); + return lines.join("\n") + "\n"; +} + +if (import.meta.main) { + const out = new URL("../../hosts/iphone2g/pocket_spec.h", import.meta.url).pathname; + await Bun.write(out, generateC()); + console.log(`wrote ${out}`); +} diff --git a/docs/BLACKBERRY_CLASSIC.md b/docs/BLACKBERRY_CLASSIC.md new file mode 100644 index 00000000..8799bd65 --- /dev/null +++ b/docs/BLACKBERRY_CLASSIC.md @@ -0,0 +1,306 @@ +# BlackBerry Classic + +PocketJS runs on the BlackBerry Classic (SQC100, BlackBerry 10.3) through two +hosts that share everything above the operating-system boundary. **Both mount +the same guest bundle shape, the same no-std Rust UI core with its GLES2 +DrawList backend, and the same QuickJS bridge (`hosts/iphone2g/pocket_runtime.c`) +against one private device profile: 720×720 physical, 360×360 logical at +raster density 2, 60 Hz fixed simulation time, `input.buttons`, `input.touch`, +and `text.glyphs.baked`.** They differ only in how the process is packaged, +installed, and fed input: + +| | Native QNX host — `hosts/blackberry-qnx` | Android Runtime host — `hosts/blackberry-android` | +| --- | --- | --- | +| Process | BlackBerry 10 Core Native ELF: libscreen window, EGL, OpenGL ES 2, BPS event loop | Android 4.3 (API 18) APK: a `GLSurfaceView` Activity over one JNI `armeabi-v7a` library | +| Package | unsigned development BAR (`blackberry-nativepackager -devMode`) | v1-signed APK | +| Install requirement | **a rooted Classic**: a stock device accepts an unsigned development BAR only with a BlackBerry debug token, and the service that issued tokens is retired | **a stock Classic**: BlackBerry 10.3 sideloads APKs from the file manager once “Allow apps from other sources” is enabled | +| Input source | libscreen keyboard, multi-touch, and `SCREEN_EVENT_JOYSTICK` trackpad events; navigator system keys | Android `KeyEvent`, touch `MotionEvent`, and generic-motion/trackball events from the Android Runtime | +| Toolchain | digest-pinned BBNDK Docker image (compile, package, deploy) | Android SDK Platform 18 + Build-Tools 35.0.0 + NDK r23c unpacked by `setup`, JDK 17 in Docker | +| Hardware status | **first device run recorded** (below) | **no device result is recorded here yet** | +| Command | `bun blackberry-qnx …` | `bun blackberry-android …` | + +Use the native host when the device is rooted: it presents directly through +libscreen and receives the trackpad as its own event class. Use the Android +Runtime host on an unmodified device, or to compare the Android Runtime's input +mapping against the native one on the same hardware. + +Both targets stay private (`tools/blackberry-classic-profile.ts`) and outside +`POCKET_TARGETS` until installation, boot, presentation, touch, keyboard, +trackpad, background/resume, and repeatable delivery are all recorded per host. + +## Shared contract + +`apps/blackberry-classic-demo` is the Hero wrapper both hosts build. The +profile module registers `blackberry-qnx-dev` and `blackberry-android-dev` +with the same display, capabilities, and **host ABI 9**; the target id is +compiled into both the guest and the native host and checked at boot, which is +why they are two targets rather than one. + +**Package identity has one source: the resolved plan.** `plan.app` carries the +manifest's `id`, `title`, and `version`; `extractHostBuildInputs` hands them to +the host tools, and `packageIdentity` (`tools/native-host-build.ts`) maps them +onto the platform: the package id is the manifest id with `-` replaced by `_` +(`dev.pocket_stack.blackberry_classic_demo` — a valid Android package name and +BAR id), the version string is used verbatim, and the integer the platforms +need (Android `versionCode`, BAR `buildId`) is `major·1 000 000 + minor·1 000 ++ patch` (0.1.1 → 1001). `AndroidManifest.xml`, `strings.xml`, and +`bar-descriptor.xml` are templates with `@POCKET_…@` placeholders rendered at +build time; neither host directory holds a second copy of the id or version. + +Input reaches the guest only through the portable button mask and touch +snapshot; no Android or QNX concept crosses the boundary. **The mask constants +come from `hosts/iphone2g/pocket_spec.h`, generated from +`contracts/spec/spec.ts` by `contracts/spec/gen-c.ts` and byte-compared by +`tests/contract.ts`**, and both hosts feed their platform events into the same +state machine, `hosts/iphone2g/pocket_input.c` (unit-tested with the host +compiler in `tests/pocket-input.test.ts`): + +| Physical input | Portable input | +| --- | --- | +| trackpad movement | one d-pad focus pulse per threshold crossing of the accumulated motion, then the axis resets (QNX feeds the integer `SCREEN_PROPERTY_DISPLACEMENT` with threshold 1, so every non-zero event pulses; Android feeds scroll-axis/trackball deltas with threshold 0.35 — provisional, see below) | +| trackpad click | the press button (`CIRCLE`), held while the button is down, tracked separately from keys | +| Enter/Return, d-pad center | the press button | +| arrow keys | d-pad; a key down is one press edge, platform auto-repeat does not re-press | +| Space | `START` | +| Menu | `TRIANGLE` | +| Send (QNX navigator system key) | a one-shot press edge; End and Back stay with the system | +| touchscreen | one tracked contact (a second finger never becomes input), divided into 360×360 logical coordinates, with the host-resolved bounds hit fact; **a contact that went down and up between two frames still reports one down frame, and a release is reported at the very next frame** | + +The frame call is `pocket_runtime_tick(&input)` in +`hosts/iphone2g/pocket_runtime.c`: **exactly one guest turn followed by one +core tick per presented frame** (docs/RUNTIMES.md, law 3), taking the mask, +the sampled contact, and its hit fact. The older `pocket_runtime_frame` / +`pocket_runtime_frame_ticks` entry points stay for the original iPhone host +(two core ticks per 30 Hz guest turn) and the Windows CE host; new hosts do +not call them. `pocket_runtime_gl_reset` drops GL resources so the backend can +be re-initialized after the platform recreates the context (Android does on +pause/resume). + +The Rust core is `pocketjs-symbian-core` (`engine/symbian`): the no-std C-ABI +build of `pocketjs-core` plus the GLES2 DrawList backend that the Nokia E7, +iPhone 2G/4S, and Meizu M8 hosts already link. Both Classic hosts build it +with the `bare-platform` feature. The QNX build uses the checked-in +`hosts/blackberry-qnx/armv7-qnx-eabi.json` target (ARMv7, VFPv3, soft-float +ABI, PIC, `build-std`); the Android build uses the stock +`armv7-linux-androideabi` target. + +## Host requirements + +Both tools need Bun, git, `zip`/`unzip`, `patch`, Docker with a running +daemon, and rustup with **`nightly-2026-07-02`**: + +```sh +rustup toolchain install nightly-2026-07-02 --profile minimal --component rust-src +``` + +`rust-src` feeds the QNX `build-std` link; `bun blackberry-android setup` adds +the `armv7-linux-androideabi` target to that toolchain for the Android link. +**QuickJS is `pocket-stack/quickjs-rs` at `ba5bdd0dc013518768e76cd9e05cd30ed53dd35b` (version 2026-06-04) for both hosts**; +`setup` clones it under each tool's cache with `--filter=blob:none` and every +build refuses a checkout at another revision or with local changes. + +**Both tools were developed and run on Linux x86-64, and both complete +builds — the unsigned BAR and the Hero APK — have also been run on macOS on +Apple silicon with the same commands.** Device installation +from macOS has not been exercised. What is host-specific: + +- **QNX**: the compiler, BAR packager, and `blackberry-deploy` run inside + `accupara/bbndk` (linux/amd64, pinned by digest, about 2.9 GB compressed). + On Apple silicon Docker Desktop runs that image under the emulation it + registers itself (Rosetta for x86-64, QEMU for the BBNDK's 32-bit x86 host + tools); the QuickJS and host compile, link, and BAR packaging complete + there without extra setup, only more slowly. USB deployment (below) uses + `udevadm` and `ip route` and is Linux-only; other hosts skip the interface + check and reach the device by whatever address `POCKETJS_BLACKBERRY_DEVICE` + names. +- **Android**: `aapt2`, `aapt`, `zipalign`, and the NDK clang run on the host + from the SDK directory, so `setup` unpacks the host OS's own archives + (Linux or macOS build-tools and NDK; the NDK prebuilt is `linux-x86_64` or + `darwin-x86_64`, the latter running under Rosetta on Apple silicon). + `javac`, `d8`, `apksigner`, and `keytool` run inside + `eclipse-temurin:17-jdk-jammy` (pinned by digest, multi-arch), so no host + JDK is needed at any step. + +Caches live under `~/.cache/pocket-stack/`: `blackberry-qnx/` (QuickJS +checkout, Rust target directory) and `android/` (`sdk/`, `downloads/`, +`signing/`, QuickJS checkout). Nothing from either cache is copied into the +repository. + +## Native QNX host + +### Toolchain + +`tools/cli/blackberry-qnx-toolchain.json` pins: + +- BBNDK target API **10.3.1.995**, host tools **10.3.1.12**; +- `qcc` **GCC 4.8.3** for `armle-v7`; +- the `accupara/bbndk` image digest; +- the QuickJS revision and the Rust nightly and target spec. + +The image's default entry point is an interactive shell for a different user; +the tool overrides the entry point, runs as the calling uid/gid, publishes no +ports, and compiles and packages with **container networking disabled**. + +QuickJS needs two QNX-specific changes (`tools/blackberry-qnx/quickjs-qnx.patch`): +BlackBerry's C library has no ``, so the single-threaded host +omits the Atomics intrinsic, and it has no `malloc_usable_size()`, so the +allocator reports usable size as zero. + +```sh +bun blackberry-qnx setup # pulls the image, clones QuickJS, runs doctor +bun blackberry-qnx doctor +bun blackberry-qnx build # build-demo + build-runtime +``` + +`build-demo` resolves the manifest against `blackberry-qnx-dev`, writes the +plan to `.pocket/blackberry-qnx/`, and compiles the guest into +`dist/blackberry-qnx/guest/`. `build-runtime` builds the Rust core, compiles +QuickJS, `pocket_runtime.c`, and `hosts/blackberry-qnx/main.c` with the plan's +target id, host ABI, raster density, and logical viewport, links the PIE ELF +against `libbps`, `libscreen`, `libEGL`, and `libGLESv2` with `--no-undefined`, +and packages the unsigned BAR from the rendered `hosts/blackberry-qnx/bar-descriptor.xml` +template. +**The tool rejects a build whose ELF is not ARM, lacks the QNX dynamic loader +or one of the four libraries, whose BAR manifest does not carry the +plan-derived package name and version, or whose BAR embeds a different +executable than the one it linked.** + +```text +dist/blackberry-qnx/pocketjs-blackberry-classic-hero.bar +dist/blackberry-qnx/build-receipt.json +``` + +The receipt records the resolved host contract, image digest, QuickJS and +Rust pins, build id, `readelf` output, and SHA-256 of every native input and +output. + +### Install and device acceptance + +Installing or launching changes device state and is not part of `build`. +Enable Development Mode on the Classic (Settings › Security and Privacy › +Development Mode), which assigns the USB address `169.254.0.1`, then: + +```sh +export POCKETJS_BLACKBERRY_DEVICE=169.254.0.1 +export POCKETJS_BLACKBERRY_PASSWORD='device-password' # omit when the rooted transport takes none +bun blackberry-qnx device-info +bun blackberry-qnx install # -installApp -launchApp +bun blackberry-qnx device-status # reads data/pocketjs-qnx.status from the app sandbox +``` + +On Linux the Classic appears as a CDC-NCM network interface (USB vendor +`0fca`); the tool refuses to deploy until that interface carries a link-local +route and prints the `sudo ip address replace 169.254.0.2/16 dev …` command +that adds one. `blackberry-deploy` runs in the same image with `--network +host`; **on Docker Desktop that is the Linux VM's network, so use the device's +Wi-Fi development address if the USB link-local address is unreachable.** + +The host rewrites `data/pocketjs-qnx.status` whenever its content changes: +build id, lifecycle stage, frame count, raw keyboard and trackpad facts, event +totals, and the latest reported Hero action. + +The first hardware run must show: the Hero fills the 720×720 display through +GLES2; the spinner and underline animate at the fixed 60 Hz step; a tap +activates the button; trackpad movement focuses it and a click activates it; +Enter and Send activate it; background and resume stop and restart +presentation without losing state; repeated installs keep a usable sandbox. + +### First Classic hardware result + +**BlackBerry Classic SQC100-4, BlackBerry 10.3.3.3216.** The unsigned +development BAR installed and launched through the rooted device transport. +The live status record confirmed: + +- **720×720 GLES2 presentation with the 360×360 density-2 guest**; +- **2,747 rendered frames** across foreground, background, and resume; +- **12 touchscreen events**; +- **56 trackpad joystick events and 4 trackpad clicks**; +- **8 completed `hero_press` actions**. + +This accepts native loading, the QuickJS and Rust runtime, rendering, touch, +trackpad navigation and click, and lifecycle resume. Physical keyboard +symbols, navigation-key policy, repeated upgrade delivery, and a captured +screen remain open, so the target stays private. **The input path changed +after that run** — the host now feeds the shared `pocket_input` state machine, +which reports a touch release at the next frame instead of one frame later +and ignores a second finger — **and the host was re-accepted on the same +device with that path** (`device-status`: tap, trackpad focus and click, and +release timing as specified). + +## Android Runtime host + +### Toolchain + +`tools/cli/blackberry-android-toolchain.json` pins: + +- Android SDK Platform **18** (Android 4.3.1 — the Android Runtime in + BlackBerry 10.3); +- Build-Tools **35.0.0**; +- **NDK r23c (23.2.8568313), the last NDK series that still targets API 18**; +- `armeabi-v7a` with clang target `armv7a-linux-androideabi18`; +- the JDK image digest, the QuickJS revision, and the Rust nightly and target. + +`setup` unpacks the three SDK components into +`~/.cache/pocket-stack/android/sdk` from the archives Google's repository +serves for the host OS — `android-18_r03.zip`, `build-tools_r35_{linux,macosx}.zip`, +`android-ndk-r23c-{linux,darwin}.zip` — after checking each against the SHA-1 +published in `repository2-3.xml` (the same files and checksums `sdkmanager` +uses, so no host JDK is needed). A component whose directory already exists +is left alone; `POCKETJS_ANDROID_SDK_ROOT` points the tool at an SDK that +already holds `platforms;android-18`, `build-tools;35.0.0`, and +`ndk;23.2.8568313`. + +```sh +bun blackberry-android setup # SDK archives, JDK image, QuickJS checkout, Rust target, then doctor +bun blackberry-android doctor +bun blackberry-android build # build-demo + build-app +``` + +`build` compiles the guest against `blackberry-android-dev`, builds the Rust +core, compiles QuickJS, `pocket_runtime.c`, `pocket_input.c`, and +`hosts/blackberry-android/app/jni/runtime.c` with the plan's target id, host +ABI, raster density, and logical viewport, links `lib/armeabi-v7a/libpocketjs.so` +against `libGLESv2`, `liblog`, `libdl`, and `libm`, renders the manifest and +string templates with the plan-derived package id, version, and title, and +**rejects an APK whose badging does not report exactly those values**. **The +library does not link `libandroid.so`, and `--no-undefined` turns any missing +native symbol into a link failure.** `PocketActivity.java` owns only the +Android lifecycle, the APK asset reads for `app.js` and `app.pak`, and the +raw key, touch, and trackpad callbacks; the JNI layer feeds them into the +shared input state machine under one mutex and drives one guest tick per +`onDrawFrame`. + +**Android 4.3 verifies only the JAR (v1) signature scheme**, so `apksigner` +runs with v2, v3, and v4 disabled. The self-generated key in +`~/.cache/pocket-stack/android/signing/` signs the APK; keep it, because +Android upgrades an installed package only when the new APK carries the same +signing identity. + +```text +dist/blackberry-android/pocketjs-blackberry-classic.apk +dist/blackberry-android/pocketjs-blackberry-classic.receipt.json +``` + +The receipt records the plan hash, target, host ABI, viewport, guest and +native-library digests, `llvm-readelf` output, the QuickJS pin, the +`apksigner verify` report, and `aapt dump badging`. + +### Install and device acceptance + +Copy the APK to the Classic (USB mass storage, BlackBerry Link, or a network +share) and open it from the device file manager. No debug token, BAR +conversion, or root is involved. + +**The Android trackpad mapping is provisional.** `PocketActivity` forwards +generic-motion scroll axes and trackball deltas as relative movement and the +primary button state as the press; which of those callbacks the Classic's +Android Runtime actually delivers for the trackpad, and whether it presents +the trackpad as a pointer instead, has not been observed on a device yet. +Adjust `hosts/blackberry-android/app/jni/runtime.c` from the first device +run before accepting the Hero APK. + +The Hero APK must show the same list as the native host: 720×720 GLES2 +presentation, the 60 Hz animation, tap, trackpad focus and click, Enter, +background and resume, and repeated upgrades. The Activity prints the boot or +runtime error on screen when the native library, the guest, or the GLES2 +backend fails, so a failed run leaves a readable reason. diff --git a/docs/STRUCTURE.md b/docs/STRUCTURE.md index 3611eeb3..9b5ce01a 100644 --- a/docs/STRUCTURE.md +++ b/docs/STRUCTURE.md @@ -34,7 +34,7 @@ pocketjs/ │ └─ compiler/ the interpreted-path build pipeline (jsx-plugin, tailwind, pak) ├─ vapor/ Pocket Vapor: the AOT compiler family (Vue Vapor subset → GBA/GB/NES) ├─ contracts/ single sources of truth binding the layers -│ ├─ spec/ op contract, platform contracts, manifest + package spec, gen-rust +│ ├─ spec/ op contract, platform contracts, manifest + package spec, gen-rust + gen-c │ └─ schema/ published JSON schemas (pocket-2.json) ├─ apps/ demo apps (pocket.json manifests; built by tools/build.ts) ├─ tools/ every command: build/dev/device/release bun scripts (flat), diff --git a/framework/src/manifest/host-build-inputs.ts b/framework/src/manifest/host-build-inputs.ts index 844bd7d8..ea9290c5 100644 --- a/framework/src/manifest/host-build-inputs.ts +++ b/framework/src/manifest/host-build-inputs.ts @@ -8,6 +8,13 @@ import { verifyPlanHash, type ResolvedBuildPlan } from "./plan.ts"; /** Stable subset of the internal build plan consumed by custom native hosts. */ export interface HostBuildInputs { readonly appOutput: string; + /** Package identity from the manifest, as the plan resolved it. Hosts map + * it onto their platform's package id and version scheme. */ + readonly app: { + readonly id: string; + readonly title: string; + readonly version: string; + }; readonly target: string; readonly hostAbi: number; readonly viewport: { @@ -42,7 +49,8 @@ function hasHostInputShape(input: unknown): input is ResolvedBuildPlan { if (!isRecord(input.viewport) || !isRecord(input.features)) return false; if ( typeof input.app.id !== "string" || input.app.id.length === 0 || - typeof input.app.title !== "string" || input.app.title.length === 0 + typeof input.app.title !== "string" || input.app.title.length === 0 || + typeof input.app.version !== "string" || input.app.version.length === 0 ) return false; if (typeof input.app.output !== "string" || input.app.output.length === 0) return false; if (typeof input.target.id !== "string" || input.target.id.length === 0) return false; @@ -91,6 +99,11 @@ export function extractHostBuildInputs( } return { appOutput: plan.app.output, + app: { + id: plan.app.id, + title: plan.app.title, + version: plan.app.version, + }, target: plan.target.id, hostAbi: plan.target.hostAbi, viewport: { @@ -109,6 +122,9 @@ export function hostBuildEnvironment( ): Readonly> { return { POCKETJS_APP_OUTPUT: inputs.appOutput, + POCKETJS_APP_ID: inputs.app.id, + POCKETJS_APP_TITLE: inputs.app.title, + POCKETJS_APP_VERSION: inputs.app.version, POCKETJS_EMBED_APP: options.embedApp ? "1" : "0", POCKETJS_OUTPUT_DIR: options.outputDirectory, POCKETJS_TARGET: inputs.target, diff --git a/framework/src/manifest/plan.ts b/framework/src/manifest/plan.ts index fa821c19..f5d4ff54 100644 --- a/framework/src/manifest/plan.ts +++ b/framework/src/manifest/plan.ts @@ -3,7 +3,9 @@ import type { PocketManifestV2 } from "../../../contracts/spec/pocket-manifest.t import type { PresentationMode, Viewport } from "../../../contracts/spec/platforms.ts"; export interface ResolvedBuildPlanContent { - readonly app: Pick & + /** Package identity travels with the plan: native hosts derive their + * platform package id and version from here, never from a second copy. */ + readonly app: Pick & Pick & { readonly output: string; }; diff --git a/framework/src/manifest/resolve.ts b/framework/src/manifest/resolve.ts index dba647da..0ed26c05 100644 --- a/framework/src/manifest/resolve.ts +++ b/framework/src/manifest/resolve.ts @@ -362,6 +362,7 @@ export function resolveBuildPlan( app: { id: manifest.id, title: manifest.title, + version: manifest.version, entry: manifest.app.entry, output, framework: manifest.app.framework, diff --git a/hosts/blackberry-android/app/AndroidManifest.xml b/hosts/blackberry-android/app/AndroidManifest.xml new file mode 100644 index 00000000..29f1de83 --- /dev/null +++ b/hosts/blackberry-android/app/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + diff --git a/hosts/blackberry-android/app/jni/runtime.c b/hosts/blackberry-android/app/jni/runtime.c new file mode 100644 index 00000000..790dbdcb --- /dev/null +++ b/hosts/blackberry-android/app/jni/runtime.c @@ -0,0 +1,307 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "pocket_input.h" +#include "pocket_runtime.h" +#include "pocket_spec.h" + +#define LOG_TAG "PocketJSClassic" +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +/* The logical viewport comes from the resolved build plan (blackberry-android.ts); + * the defaults match the private blackberry-android-dev profile. */ +#ifndef POCKET_LOGICAL_WIDTH +#define POCKET_LOGICAL_WIDTH 360 +#endif +#ifndef POCKET_LOGICAL_HEIGHT +#define POCKET_LOGICAL_HEIGHT 360 +#endif + +#define KEYCODE_BACK 4 +#define KEYCODE_DPAD_UP 19 +#define KEYCODE_DPAD_DOWN 20 +#define KEYCODE_DPAD_LEFT 21 +#define KEYCODE_DPAD_RIGHT 22 +#define KEYCODE_DPAD_CENTER 23 +#define KEYCODE_SPACE 62 +#define KEYCODE_ENTER 66 +#define KEYCODE_MENU 82 +#define KEYCODE_NUMPAD_ENTER 160 + +#define ACTION_DOWN 0 +#define ACTION_UP 1 +#define ACTION_CANCEL 3 +#define ACTION_POINTER_DOWN 5 +#define ACTION_POINTER_UP 6 +#define BUTTON_PRIMARY 1 + +/* Trackball and scroll-axis deltas are fractional; this much accumulated + * motion is one focus pulse (provisional until a device run records the + * Android Runtime's actual trackpad events). */ +#define RELATIVE_PULSE_THRESHOLD 0.35f + +static pthread_mutex_t input_mutex = PTHREAD_MUTEX_INITIALIZER; +static PocketInputState input; +static int input_ready; +static int surface_width = 720; +static int surface_height = 720; + +static uint8_t *guest_js; +static size_t guest_js_length; +static uint8_t *guest_pack; +static size_t guest_pack_length; +static int runtime_booted; +static int gl_initialized; +static char android_error[512]; + +static void set_android_error(const char *message) +{ + size_t length = message == NULL ? 0 : strlen(message); + if (length >= sizeof(android_error)) length = sizeof(android_error) - 1; + if (length > 0) memcpy(android_error, message, length); + android_error[length] = '\0'; + LOGE("%s", android_error); +} + +static uint8_t *copy_java_bytes( + JNIEnv *env, + jbyteArray source, + size_t *length +) +{ + if (source == NULL) return NULL; + jsize source_length = (*env)->GetArrayLength(env, source); + if (source_length <= 0) return NULL; + uint8_t *bytes = (uint8_t *)malloc((size_t)source_length); + if (bytes == NULL) return NULL; + (*env)->GetByteArrayRegion(env, source, 0, source_length, (jbyte *)bytes); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionClear(env); + free(bytes); + return NULL; + } + *length = (size_t)source_length; + return bytes; +} + +/* Android key codes onto the portable mask (pocket_spec.h). */ +static uint32_t button_for_key(int key_code) +{ + switch (key_code) { + case KEYCODE_DPAD_UP: return POCKET_BTN_UP; + case KEYCODE_DPAD_RIGHT: return POCKET_BTN_RIGHT; + case KEYCODE_DPAD_DOWN: return POCKET_BTN_DOWN; + case KEYCODE_DPAD_LEFT: return POCKET_BTN_LEFT; + case KEYCODE_DPAD_CENTER: + case KEYCODE_ENTER: + case KEYCODE_NUMPAD_ENTER: + return POCKET_BTN_CIRCLE; + case KEYCODE_SPACE: return POCKET_BTN_START; + case KEYCODE_MENU: return POCKET_BTN_TRIANGLE; + default: return 0; + } +} + +/* Input callbacks can arrive before the surface exists; the state machine is + * initialized lazily under the mutex. */ +static void ensure_input(void) +{ + if (input_ready) return; + pocket_input_init(&input, RELATIVE_PULSE_THRESHOLD); + input_ready = 1; +} + +JNIEXPORT jstring JNICALL +Java_dev_pocketstack_blackberry_PocketActivity_nativeSurfaceCreated( + JNIEnv *env, + jclass owner, + jbyteArray guest_java_script, + jbyteArray guest_asset_pack +) +{ + (void)owner; + android_error[0] = '\0'; + if (runtime_booted) { + pocket_runtime_gl_reset(); + gl_initialized = pocket_runtime_gl_initialize(); + if (!gl_initialized) set_android_error("GLES2 backend reinitialization failed"); + return (*env)->NewStringUTF(env, gl_initialized ? "ok" : android_error); + } + + uint8_t *new_guest_js = copy_java_bytes( + env, + guest_java_script, + &guest_js_length + ); + uint8_t *new_guest_pack = copy_java_bytes( + env, + guest_asset_pack, + &guest_pack_length + ); + if (new_guest_js == NULL || new_guest_pack == NULL) { + free(new_guest_js); + free(new_guest_pack); + set_android_error("APK assets/app.js or assets/app.pak could not be copied"); + return (*env)->NewStringUTF(env, android_error); + } + free(guest_js); + free(guest_pack); + guest_js = new_guest_js; + guest_pack = new_guest_pack; + + if (!pocket_runtime_boot( + (const char *)guest_js, + guest_js_length, + guest_pack, + guest_pack_length, + POCKET_LOGICAL_WIDTH, + POCKET_LOGICAL_HEIGHT + )) { + set_android_error(pocket_runtime_error()); + return (*env)->NewStringUTF(env, android_error); + } + runtime_booted = 1; + gl_initialized = pocket_runtime_gl_initialize(); + if (!gl_initialized) { + set_android_error("PocketJS GLES2 backend initialization failed"); + } + return (*env)->NewStringUTF(env, gl_initialized ? "ok" : android_error); +} + +JNIEXPORT void JNICALL +Java_dev_pocketstack_blackberry_PocketActivity_nativeSurfaceChanged( + JNIEnv *env, + jclass owner, + jint width, + jint height +) +{ + (void)env; + (void)owner; + pthread_mutex_lock(&input_mutex); + surface_width = width > 0 ? width : 1; + surface_height = height > 0 ? height : 1; + pthread_mutex_unlock(&input_mutex); +} + +JNIEXPORT jboolean JNICALL +Java_dev_pocketstack_blackberry_PocketActivity_nativeFrame( + JNIEnv *env, + jclass owner +) +{ + (void)env; + (void)owner; + if (!runtime_booted || !gl_initialized) return JNI_FALSE; + + PocketInputSample sample; + PocketRuntimeInput frame; + int width; + int height; + pthread_mutex_lock(&input_mutex); + ensure_input(); + pocket_input_sample(&input, &sample); + width = surface_width; + height = surface_height; + pthread_mutex_unlock(&input_mutex); + + frame.buttons = sample.buttons; + frame.touch_down = sample.touch_down; + frame.touch_x = (int)(sample.touch_x * POCKET_LOGICAL_WIDTH / width); + frame.touch_y = (int)(sample.touch_y * POCKET_LOGICAL_HEIGHT / height); + frame.touch_hit = sample.touch_down + ? pocket_runtime_hit_test_bounds((float)frame.touch_x, (float)frame.touch_y) + : 0; + if (!pocket_runtime_tick(&frame)) { + set_android_error(pocket_runtime_error()); + return JNI_FALSE; + } + if (!pocket_runtime_gl_render(width, height)) { + set_android_error("PocketJS GLES2 frame submission failed"); + return JNI_FALSE; + } + return JNI_TRUE; +} + +JNIEXPORT jstring JNICALL +Java_dev_pocketstack_blackberry_PocketActivity_nativeError(JNIEnv *env, jclass owner) +{ + (void)owner; + const char *message = android_error[0] != '\0' + ? android_error + : pocket_runtime_error(); + return (*env)->NewStringUTF(env, message == NULL ? "unknown error" : message); +} + +JNIEXPORT void JNICALL +Java_dev_pocketstack_blackberry_PocketActivity_nativeKey( + JNIEnv *env, + jclass owner, + jint action, + jint key_code, + jint scan_code, + jint unicode, + jint repeat +) +{ + (void)env; + (void)owner; + (void)scan_code; + (void)unicode; + uint32_t button = button_for_key(key_code); + if (button == 0 || key_code == KEYCODE_BACK) return; + if (action != ACTION_DOWN && action != ACTION_UP) return; + pthread_mutex_lock(&input_mutex); + ensure_input(); + pocket_input_button(&input, button, action == ACTION_DOWN, repeat != 0); + pthread_mutex_unlock(&input_mutex); +} + +JNIEXPORT void JNICALL +Java_dev_pocketstack_blackberry_PocketActivity_nativeTouch( + JNIEnv *env, + jclass owner, + jint action, + jint pointer_id, + jfloat x, + jfloat y +) +{ + (void)env; + (void)owner; + PocketTouchPhase phase; + if (action == ACTION_DOWN || action == ACTION_POINTER_DOWN) phase = POCKET_TOUCH_DOWN; + else if (action == ACTION_UP || action == ACTION_POINTER_UP) phase = POCKET_TOUCH_UP; + else if (action == ACTION_CANCEL) phase = POCKET_TOUCH_CANCEL; + else phase = POCKET_TOUCH_MOVE; + pthread_mutex_lock(&input_mutex); + ensure_input(); + pocket_input_touch(&input, phase, pointer_id, x, y); + pthread_mutex_unlock(&input_mutex); +} + +JNIEXPORT void JNICALL +Java_dev_pocketstack_blackberry_PocketActivity_nativeRelative( + JNIEnv *env, + jclass owner, + jfloat delta_x, + jfloat delta_y, + jint action, + jint button_state +) +{ + (void)env; + (void)owner; + int primary = (button_state & BUTTON_PRIMARY) != 0 || action == ACTION_DOWN; + if (action == ACTION_UP || action == ACTION_CANCEL) primary = 0; + pthread_mutex_lock(&input_mutex); + ensure_input(); + pocket_input_relative(&input, delta_x, delta_y); + pocket_input_primary(&input, primary); + pthread_mutex_unlock(&input_mutex); +} diff --git a/hosts/blackberry-android/app/res/values/strings.xml b/hosts/blackberry-android/app/res/values/strings.xml new file mode 100644 index 00000000..55762cc6 --- /dev/null +++ b/hosts/blackberry-android/app/res/values/strings.xml @@ -0,0 +1,5 @@ + + + + @POCKET_TITLE@ + diff --git a/hosts/blackberry-android/app/src/dev/pocketstack/blackberry/PocketActivity.java b/hosts/blackberry-android/app/src/dev/pocketstack/blackberry/PocketActivity.java new file mode 100644 index 00000000..10f63d1e --- /dev/null +++ b/hosts/blackberry-android/app/src/dev/pocketstack/blackberry/PocketActivity.java @@ -0,0 +1,268 @@ +package dev.pocketstack.blackberry; + +import android.app.Activity; +import android.graphics.Color; +import android.graphics.Typeface; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.TextView; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** Android 4.3 shell for the BlackBerry Classic PocketJS guest. */ +public final class PocketActivity extends Activity { + private static boolean nativeLoaded; + private static String nativeLoadError = ""; + + static { + try { + System.loadLibrary("pocketjs"); + nativeLoaded = true; + } catch (Throwable error) { + nativeLoaded = false; + nativeLoadError = error.getClass().getSimpleName() + ": " + error.getMessage(); + } + } + + private PocketSurfaceView surfaceView; + private TextView errorView; + + private static native String nativeSurfaceCreated(byte[] guestJavaScript, byte[] guestPack); + private static native void nativeSurfaceChanged(int width, int height); + private static native boolean nativeFrame(); + private static native String nativeError(); + private static native void nativeKey( + int action, + int keyCode, + int scanCode, + int unicode, + int repeat + ); + private static native void nativeTouch(int action, int pointerId, float x, float y); + private static native void nativeRelative( + float deltaX, + float deltaY, + int action, + int buttonState + ); + + @Override + protected void onCreate(Bundle state) { + super.onCreate(state); + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ); + + FrameLayout root = new FrameLayout(this); + surfaceView = new PocketSurfaceView(); + root.addView( + surfaceView, + new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ); + + errorView = new TextView(this); + errorView.setTextColor(Color.WHITE); + errorView.setTextSize(15.0f); + errorView.setTypeface(Typeface.MONOSPACE); + errorView.setGravity(Gravity.CENTER); + errorView.setPadding(28, 28, 28, 28); + errorView.setBackgroundColor(0xff250d12); + errorView.setText(nativeLoaded ? "BOOTING POCKETJS…" : "JNI FAILED\n" + nativeLoadError); + root.addView( + errorView, + new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ); + setContentView(root); + } + + @Override + protected void onResume() { + super.onResume(); + surfaceView.onResume(); + surfaceView.requestFocus(); + } + + @Override + protected void onPause() { + surfaceView.onPause(); + super.onPause(); + } + + @Override + public void onWindowFocusChanged(boolean focused) { + super.onWindowFocusChanged(focused); + if (focused) surfaceView.requestFocus(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (nativeLoaded) { + nativeKey( + event.getAction(), + event.getKeyCode(), + event.getScanCode(), + event.getUnicodeChar(event.getMetaState()), + event.getRepeatCount() + ); + } + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK || + event.getKeyCode() == KeyEvent.KEYCODE_HOME || + event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP || + event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN) { + return super.dispatchKeyEvent(event); + } + return true; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (!nativeLoaded) return super.dispatchTouchEvent(event); + int action = event.getActionMasked(); + int changed = event.getActionIndex(); + if (action == MotionEvent.ACTION_MOVE) { + for (int index = 0; index < event.getPointerCount(); index++) { + nativeTouch( + action, + event.getPointerId(index), + event.getX(index), + event.getY(index) + ); + } + } else if (action == MotionEvent.ACTION_CANCEL) { + for (int index = 0; index < event.getPointerCount(); index++) { + nativeTouch( + action, + event.getPointerId(index), + event.getX(index), + event.getY(index) + ); + } + } else { + nativeTouch( + action, + event.getPointerId(changed), + event.getX(changed), + event.getY(changed) + ); + } + return true; + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent event) { + if (!nativeLoaded) return super.dispatchGenericMotionEvent(event); + float horizontal = event.getAxisValue(MotionEvent.AXIS_HSCROLL); + float vertical = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + nativeRelative(horizontal, vertical, event.getActionMasked(), event.getButtonState()); + return true; + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + if (!nativeLoaded) return super.onTrackballEvent(event); + nativeRelative(event.getX(), event.getY(), event.getActionMasked(), event.getButtonState()); + return true; + } + + private void showBootResult(final String result) { + runOnUiThread(new Runnable() { + public void run() { + if ("ok".equals(result)) { + errorView.setVisibility(View.GONE); + } else { + errorView.setText("POCKETJS BOOT FAILED\n\n" + result); + errorView.setVisibility(View.VISIBLE); + } + } + }); + } + + private void showRuntimeError(final String error) { + runOnUiThread(new Runnable() { + public void run() { + errorView.setText("POCKETJS RUNTIME FAILED\n\n" + error); + errorView.setVisibility(View.VISIBLE); + } + }); + } + + private byte[] readAsset(String name) throws IOException { + InputStream input = getAssets().open(name); + try { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + int count; + while ((count = input.read(buffer)) != -1) { + output.write(buffer, 0, count); + } + return output.toByteArray(); + } finally { + input.close(); + } + } + + private final class PocketSurfaceView extends GLSurfaceView { + PocketSurfaceView() { + super(PocketActivity.this); + setEGLContextClientVersion(2); + setPreserveEGLContextOnPause(true); + setFocusable(true); + setFocusableInTouchMode(true); + setRenderer(new PocketRenderer()); + setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + } + } + + private final class PocketRenderer implements GLSurfaceView.Renderer { + private boolean failed; + + public void onSurfaceCreated( + javax.microedition.khronos.opengles.GL10 ignored, + javax.microedition.khronos.egl.EGLConfig config + ) { + if (!nativeLoaded) return; + String result; + try { + result = nativeSurfaceCreated(readAsset("app.js"), readAsset("app.pak")); + } catch (IOException error) { + result = "APK asset read failed: " + error.getMessage(); + } + failed = !"ok".equals(result); + showBootResult(result); + } + + public void onSurfaceChanged( + javax.microedition.khronos.opengles.GL10 ignored, + int width, + int height + ) { + if (nativeLoaded) nativeSurfaceChanged(width, height); + } + + public void onDrawFrame(javax.microedition.khronos.opengles.GL10 ignored) { + if (!nativeLoaded || failed) return; + if (!nativeFrame()) { + failed = true; + showRuntimeError(nativeError()); + } + } + } +} diff --git a/hosts/blackberry-qnx/armv7-qnx-eabi.json b/hosts/blackberry-qnx/armv7-qnx-eabi.json new file mode 100644 index 00000000..7e21ac4d --- /dev/null +++ b/hosts/blackberry-qnx/armv7-qnx-eabi.json @@ -0,0 +1,24 @@ +{ + "abi": "eabi", + "arch": "arm", + "c-enum-min-bits": 32, + "cpu": "cortex-a9", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", + "emit-debug-gdb-scripts": false, + "features": "+v7,+vfp3,+neon,+strict-align", + "frame-pointer": "always", + "function-sections": true, + "has-thumb-interworking": true, + "linker": "rust-lld", + "linker-flavor": "gnu-lld", + "llvm-floatabi": "soft", + "llvm-target": "armv7-none-eabi", + "max-atomic-width": 64, + "os": "none", + "panic-strategy": "abort", + "relocation-model": "pic", + "target-endian": "little", + "target-pointer-width": 32, + "vendor": "unknown" +} diff --git a/hosts/blackberry-qnx/bar-descriptor.xml b/hosts/blackberry-qnx/bar-descriptor.xml new file mode 100644 index 00000000..811e7868 --- /dev/null +++ b/hosts/blackberry-qnx/bar-descriptor.xml @@ -0,0 +1,33 @@ + + + + @POCKET_ID@ + @POCKET_TITLE@ + @POCKET_VERSION@ + @POCKET_BUILD_ID@ + PocketJS Hero running as a native BlackBerry 10 application. + PocketJS + 10.3.1.995 + + icon.png + app.js + app.pak + + + armle-v7 + pocketjs-classic + + + + false + none + + + + icon.png + + + + diff --git a/hosts/blackberry-qnx/main.c b/hosts/blackberry-qnx/main.c new file mode 100644 index 00000000..83ba1302 --- /dev/null +++ b/hosts/blackberry-qnx/main.c @@ -0,0 +1,547 @@ +#include "pocket_input.h" +#include "pocket_runtime.h" +#include "pocket_spec.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef POCKET_BUILD_ID +#error "POCKET_BUILD_ID must identify the concrete BlackBerry QNX build" +#endif + +/* The logical viewport comes from the resolved build plan (build.sh); the + * defaults match the private blackberry-qnx-dev profile. */ +#ifndef POCKET_LOGICAL_WIDTH +#define POCKET_LOGICAL_WIDTH 360 +#endif +#ifndef POCKET_LOGICAL_HEIGHT +#define POCKET_LOGICAL_HEIGHT 360 +#endif +#define DEFAULT_SURFACE_WIDTH 720 +#define DEFAULT_SURFACE_HEIGHT 720 + +#define STATUS_PATH "data/pocketjs-qnx.status" + +typedef struct { + unsigned char *bytes; + size_t length; +} Asset; + +static screen_context_t screen_context; +static screen_window_t screen_window; +static EGLDisplay egl_display = EGL_NO_DISPLAY; +static EGLSurface egl_surface = EGL_NO_SURFACE; +static EGLContext egl_context = EGL_NO_CONTEXT; + +static int surface_width = DEFAULT_SURFACE_WIDTH; +static int surface_height = DEFAULT_SURFACE_HEIGHT; +static int app_active = 1; +static int app_shutdown; +static int runtime_ready; +static PocketInputState input; + +static unsigned long frame_count; +static unsigned long touch_event_count; +static unsigned long keyboard_event_count; +static unsigned long trackpad_event_count; +static unsigned long trackpad_click_count; +static int last_trackpad_dx; +static int last_trackpad_dy; +static int last_trackpad_x; +static int last_trackpad_y; +static int last_trackpad_buttons; +static int last_key_sym; +static int last_key_scan; +static int last_key_flags; +static unsigned long last_action_sequence; +static char status_stage[32] = "starting"; +static char status_detail[256] = "host entry"; +static int status_dirty = 1; +static char executable_directory[PATH_MAX]; + +static void set_error(const char *message) +{ + snprintf(status_detail, sizeof(status_detail), "%s", message == NULL ? "unknown error" : message); + snprintf(status_stage, sizeof(status_stage), "%s", "error"); + status_dirty = 1; + fprintf(stderr, "PocketJS Classic: %s\n", status_detail); +} + +static void set_status(const char *stage, const char *detail) +{ + snprintf(status_stage, sizeof(status_stage), "%s", stage == NULL ? "unknown" : stage); + snprintf(status_detail, sizeof(status_detail), "%s", detail == NULL ? "" : detail); + status_dirty = 1; + fprintf(stderr, "PocketJS Classic: %s: %s\n", status_stage, status_detail); +} + +static void write_status(void) +{ + FILE *file; + const char *action_name; + if (!status_dirty) return; + file = fopen(STATUS_PATH, "wb"); + if (file == NULL) { + fprintf(stderr, "PocketJS Classic: cannot write %s: %s\n", STATUS_PATH, strerror(errno)); + return; + } + action_name = runtime_ready ? pocket_runtime_action_name() : ""; + fprintf(file, "schema=1\n"); + fprintf(file, "build_id=%s\n", POCKET_BUILD_ID); + fprintf(file, "stage=%s\n", status_stage); + fprintf(file, "detail=%s\n", status_detail); + fprintf(file, "surface=%dx%d\n", surface_width, surface_height); + fprintf(file, "logical=%dx%d\n", POCKET_LOGICAL_WIDTH, POCKET_LOGICAL_HEIGHT); + fprintf(file, "frames=%lu\n", frame_count); + fprintf(file, "touch_events=%lu\n", touch_event_count); + fprintf(file, "keyboard_events=%lu\n", keyboard_event_count); + fprintf(file, "trackpad_events=%lu\n", trackpad_event_count); + fprintf(file, "trackpad_clicks=%lu\n", trackpad_click_count); + fprintf(file, "trackpad_displacement=%d,%d\n", last_trackpad_dx, last_trackpad_dy); + fprintf(file, "trackpad_position=%d,%d\n", last_trackpad_x, last_trackpad_y); + fprintf(file, "trackpad_buttons=%d\n", last_trackpad_buttons); + fprintf(file, "key_sym=%d\n", last_key_sym); + fprintf(file, "key_scan=%d\n", last_key_scan); + fprintf(file, "key_flags=%d\n", last_key_flags); + fprintf(file, "action_sequence=%lu\n", runtime_ready ? pocket_runtime_action_sequence() : 0UL); + fprintf(file, "action_name=%s\n", action_name == NULL ? "" : action_name); + fprintf(file, "action_value=%d\n", runtime_ready ? pocket_runtime_action_value() : 0); + fclose(file); + status_dirty = 0; +} + +static int integer_environment(const char *name, int fallback) +{ + const char *text = getenv(name); + char *end = NULL; + long value; + if (text == NULL || text[0] == '\0') return fallback; + value = strtol(text, &end, 10); + if (end == text || *end != '\0' || value <= 0 || value > INT_MAX) return fallback; + return (int)value; +} + +static void initialize_executable_directory(const char *argv0) +{ + const char *slash; + size_t length; + executable_directory[0] = '\0'; + if (argv0 == NULL) return; + slash = strrchr(argv0, '/'); + if (slash == NULL) return; + length = (size_t)(slash - argv0); + if (length == 0 || length >= sizeof(executable_directory)) return; + memcpy(executable_directory, argv0, length); + executable_directory[length] = '\0'; +} + +static int read_file(const char *path, Asset *asset) +{ + FILE *file; + long end; + unsigned char *bytes; + size_t length; + file = fopen(path, "rb"); + if (file == NULL) return 0; + if (fseek(file, 0, SEEK_END) != 0) { + fclose(file); + return 0; + } + end = ftell(file); + if (end <= 0 || end > 64L * 1024L * 1024L || fseek(file, 0, SEEK_SET) != 0) { + fclose(file); + return 0; + } + length = (size_t)end; + bytes = (unsigned char *)malloc(length); + if (bytes == NULL) { + fclose(file); + return 0; + } + if (fread(bytes, 1, length, file) != length) { + free(bytes); + fclose(file); + return 0; + } + fclose(file); + asset->bytes = bytes; + asset->length = length; + return 1; +} + +static int read_asset(const char *name, Asset *asset) +{ + char path[PATH_MAX]; + const char *fallbacks[2]; + size_t index; + asset->bytes = NULL; + asset->length = 0; + if (executable_directory[0] != '\0') { + if (snprintf(path, sizeof(path), "%s/%s", executable_directory, name) < (int)sizeof(path) && + read_file(path, asset)) return 1; + } + fallbacks[0] = "app/native"; + fallbacks[1] = "."; + for (index = 0; index < sizeof(fallbacks) / sizeof(fallbacks[0]); index += 1) { + if (snprintf(path, sizeof(path), "%s/%s", fallbacks[index], name) >= (int)sizeof(path)) continue; + if (read_file(path, asset)) return 1; + } + return 0; +} + +static void destroy_graphics(void) +{ + if (egl_display != EGL_NO_DISPLAY) { + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (egl_surface != EGL_NO_SURFACE) eglDestroySurface(egl_display, egl_surface); + if (egl_context != EGL_NO_CONTEXT) eglDestroyContext(egl_display, egl_context); + eglTerminate(egl_display); + } + egl_surface = EGL_NO_SURFACE; + egl_context = EGL_NO_CONTEXT; + egl_display = EGL_NO_DISPLAY; + if (screen_window != NULL) screen_destroy_window(screen_window); + if (screen_context != NULL) screen_destroy_context(screen_context); + screen_window = NULL; + screen_context = NULL; + eglReleaseThread(); +} + +static int initialize_graphics(void) +{ + EGLConfig config; + EGLint config_count = 0; + EGLint config_attributes[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + EGLint context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + int format = SCREEN_FORMAT_RGBX8888; + int usage = SCREEN_USAGE_OPENGL_ES2; + int size[2]; + char group[32]; + + surface_width = integer_environment("WIDTH", DEFAULT_SURFACE_WIDTH); + surface_height = integer_environment("HEIGHT", DEFAULT_SURFACE_HEIGHT); + size[0] = surface_width; + size[1] = surface_height; + + if (screen_create_context(&screen_context, SCREEN_APPLICATION_CONTEXT) != 0) { + set_error("screen_create_context failed"); + return 0; + } + egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (egl_display == EGL_NO_DISPLAY || !eglInitialize(egl_display, NULL, NULL) || + !eglBindAPI(EGL_OPENGL_ES_API) || + !eglChooseConfig(egl_display, config_attributes, &config, 1, &config_count) || + config_count != 1) { + set_error("EGL display or configuration initialization failed"); + return 0; + } + egl_context = eglCreateContext(egl_display, config, EGL_NO_CONTEXT, context_attributes); + if (egl_context == EGL_NO_CONTEXT) { + set_error("eglCreateContext for OpenGL ES 2 failed"); + return 0; + } + if (screen_create_window(&screen_window, screen_context) != 0) { + set_error("screen_create_window failed"); + return 0; + } + snprintf(group, sizeof(group), "pocketjs-%ld", (long)getpid()); + if (screen_create_window_group(screen_window, group) != 0 || + screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_FORMAT, &format) != 0 || + screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_USAGE, &usage) != 0 || + screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_BUFFER_SIZE, size) != 0 || + screen_create_window_buffers(screen_window, 2) != 0) { + set_error("libscreen window configuration failed"); + return 0; + } + egl_surface = eglCreateWindowSurface(egl_display, config, screen_window, NULL); + if (egl_surface == EGL_NO_SURFACE || + !eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context) || + !eglSwapInterval(egl_display, 1)) { + set_error("EGL window surface initialization failed"); + return 0; + } + glViewport(0, 0, surface_width, surface_height); + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + return 1; +} + +/* libscreen key symbols onto the portable mask (pocket_spec.h). */ +static uint32_t button_for_key(int symbol) +{ + switch (symbol) { + case KEYCODE_UP: return POCKET_BTN_UP; + case KEYCODE_RIGHT: return POCKET_BTN_RIGHT; + case KEYCODE_DOWN: return POCKET_BTN_DOWN; + case KEYCODE_LEFT: return POCKET_BTN_LEFT; + case KEYCODE_RETURN: return POCKET_BTN_CIRCLE; + case KEYCODE_SPACE: return POCKET_BTN_START; + case KEYCODE_MENU: return POCKET_BTN_TRIANGLE; + default: return 0; + } +} + +static void handle_keyboard(screen_event_t event) +{ + int flags = 0; + int symbol = 0; + int scan = 0; + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_FLAGS, &flags) != 0) return; + screen_get_event_property_iv(event, SCREEN_PROPERTY_SYM, &symbol); + screen_get_event_property_iv(event, SCREEN_PROPERTY_SCAN, &scan); + keyboard_event_count += 1; + last_key_sym = symbol; + last_key_scan = scan; + last_key_flags = flags; + status_dirty = 1; + pocket_input_button( + &input, + button_for_key(symbol), + (flags & SCREEN_FLAG_KEY_DOWN) != 0, + (flags & SCREEN_FLAG_KEY_REPEAT) != 0 + ); +} + +static void handle_touch(screen_event_t event, int type) +{ + int position[2] = {0, 0}; + int id = -1; + PocketTouchPhase phase; + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_SOURCE_POSITION, position) != 0 && + screen_get_event_property_iv(event, SCREEN_PROPERTY_POSITION, position) != 0) return; + screen_get_event_property_iv(event, SCREEN_PROPERTY_TOUCH_ID, &id); + touch_event_count += 1; + status_dirty = 1; + phase = type == SCREEN_EVENT_MTOUCH_TOUCH ? POCKET_TOUCH_DOWN + : type == SCREEN_EVENT_MTOUCH_RELEASE ? POCKET_TOUCH_UP + : POCKET_TOUCH_MOVE; + pocket_input_touch(&input, phase, id, (float)position[0], (float)position[1]); +} + +static void handle_trackpad(screen_event_t event) +{ + int displacement[2] = {0, 0}; + int position[2] = {0, 0}; + int buttons = 0; + int primary; + screen_get_event_property_iv(event, SCREEN_PROPERTY_DISPLACEMENT, displacement); + screen_get_event_property_iv(event, SCREEN_PROPERTY_POSITION, position); + screen_get_event_property_iv(event, SCREEN_PROPERTY_BUTTONS, &buttons); + trackpad_event_count += 1; + last_trackpad_dx = displacement[0]; + last_trackpad_dy = displacement[1]; + last_trackpad_x = position[0]; + last_trackpad_y = position[1]; + last_trackpad_buttons = buttons; + status_dirty = 1; + /* Integer joystick displacement: every non-zero event is one focus pulse. */ + pocket_input_relative(&input, (float)displacement[0], (float)displacement[1]); + primary = buttons != 0; + if (primary && !input.primary_down) trackpad_click_count += 1; + pocket_input_primary(&input, primary); +} + +static void handle_screen_event(bps_event_t *event) +{ + screen_event_t screen_event = screen_event_get_event(event); + int type = SCREEN_EVENT_NONE; + if (screen_event == NULL || + screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &type) != 0) return; + switch (type) { + case SCREEN_EVENT_KEYBOARD: + handle_keyboard(screen_event); + break; + case SCREEN_EVENT_MTOUCH_TOUCH: + case SCREEN_EVENT_MTOUCH_MOVE: + case SCREEN_EVENT_MTOUCH_RELEASE: + handle_touch(screen_event, type); + break; + case SCREEN_EVENT_JOYSTICK: + handle_trackpad(screen_event); + break; + default: + break; + } +} + +static void handle_navigator_event(bps_event_t *event) +{ + int code = bps_event_get_code(event); + switch (code) { + case NAVIGATOR_EXIT: + app_shutdown = 1; + break; + case NAVIGATOR_WINDOW_ACTIVE: + app_active = 1; + set_status("running", "window active"); + break; + case NAVIGATOR_WINDOW_INACTIVE: + app_active = 0; + set_status("inactive", "window inactive"); + break; + case NAVIGATOR_ORIENTATION_CHECK: + navigator_orientation_check_response(event, false); + break; + case NAVIGATOR_SYSKEY_PRESS: { + int key = navigator_event_get_syskey_key(event); + const char *id = navigator_event_get_syskey_id(event); + int handled = key == NAVIGATOR_SYSKEY_SEND; + if (handled) pocket_input_pulse(&input, POCKET_BTN_CIRCLE); + if (id != NULL) navigator_syskey_press_response(id, handled != 0); + status_dirty = 1; + break; + } + default: + break; + } +} + +static void handle_event(bps_event_t *event) +{ + int domain; + if (event == NULL) return; + domain = bps_event_get_domain(event); + if (domain == screen_get_domain()) handle_screen_event(event); + else if (domain == navigator_get_domain()) handle_navigator_event(event); +} + +static int render_frame(void) +{ + PocketInputSample sample; + PocketRuntimeInput frame; + unsigned long action_sequence; + + pocket_input_sample(&input, &sample); + frame.buttons = sample.buttons; + frame.touch_down = sample.touch_down; + frame.touch_x = (int)(sample.touch_x * POCKET_LOGICAL_WIDTH / (surface_width > 0 ? surface_width : 1)); + frame.touch_y = (int)(sample.touch_y * POCKET_LOGICAL_HEIGHT / (surface_height > 0 ? surface_height : 1)); + frame.touch_hit = sample.touch_down + ? pocket_runtime_hit_test_bounds((float)frame.touch_x, (float)frame.touch_y) + : 0; + if (!pocket_runtime_tick(&frame)) { + set_error(pocket_runtime_error()); + return 0; + } + if (!pocket_runtime_gl_render(surface_width, surface_height) || + !eglSwapBuffers(egl_display, egl_surface)) { + set_error("OpenGL ES frame presentation failed"); + return 0; + } + frame_count += 1; + action_sequence = pocket_runtime_action_sequence(); + if (action_sequence != last_action_sequence) { + last_action_sequence = action_sequence; + set_status("action", pocket_runtime_action_name()); + } + write_status(); + return 1; +} + +int main(int argc, char **argv) +{ + Asset java_script = {NULL, 0}; + Asset pack = {NULL, 0}; + bps_event_t *event = NULL; + int exit_code = EXIT_FAILURE; + (void)argc; + + initialize_executable_directory(argv == NULL ? NULL : argv[0]); + pocket_input_init(&input, 1.0f); + write_status(); + if (bps_initialize() != BPS_SUCCESS) { + set_error("bps_initialize failed"); + goto cleanup; + } + if (!initialize_graphics()) goto cleanup_bps; + if (screen_request_events(screen_context) != BPS_SUCCESS || + navigator_request_events(0) != BPS_SUCCESS) { + set_error("BPS screen or navigator event registration failed"); + goto cleanup_graphics; + } + if (!read_asset("app.js", &java_script) || !read_asset("app.pak", &pack)) { + set_error("cannot read packaged app.js or app.pak"); + goto cleanup_graphics; + } + if (!pocket_runtime_boot( + (const char *)java_script.bytes, + java_script.length, + pack.bytes, + pack.length, + POCKET_LOGICAL_WIDTH, + POCKET_LOGICAL_HEIGHT + )) { + set_error(pocket_runtime_error()); + goto cleanup_graphics; + } + runtime_ready = 1; + if (!pocket_runtime_gl_initialize()) { + set_error("PocketJS GLES2 backend initialization failed"); + goto cleanup_runtime; + } + set_status("running", "Hero mounted"); + write_status(); + + while (!app_shutdown) { + int timeout = app_active ? 0 : -1; + if (bps_get_event(&event, timeout) != BPS_SUCCESS) { + set_error("bps_get_event failed"); + break; + } + handle_event(event); + if (app_active) { + do { + event = NULL; + if (bps_get_event(&event, 0) != BPS_SUCCESS) { + set_error("bps_get_event drain failed"); + app_shutdown = 1; + break; + } + handle_event(event); + } while (event != NULL && !app_shutdown && app_active); + if (!app_shutdown && !render_frame()) app_shutdown = 1; + } else { + write_status(); + } + } + exit_code = status_stage[0] == 'e' ? EXIT_FAILURE : EXIT_SUCCESS; + +cleanup_runtime: + if (runtime_ready) { + pocket_runtime_gl_shutdown(); + pocket_runtime_shutdown(); + runtime_ready = 0; + } +cleanup_graphics: + destroy_graphics(); +cleanup_bps: + bps_shutdown(); +cleanup: + free(pack.bytes); + free(java_script.bytes); + if (exit_code == EXIT_SUCCESS) set_status("stopped", "navigator exit"); + write_status(); + return exit_code; +} diff --git a/hosts/iphone2g/pocket_input.c b/hosts/iphone2g/pocket_input.c new file mode 100644 index 00000000..7707bea2 --- /dev/null +++ b/hosts/iphone2g/pocket_input.c @@ -0,0 +1,115 @@ +#include "pocket_input.h" + +#include "pocket_spec.h" + +#include + +void pocket_input_init(PocketInputState *state, float relative_threshold) +{ + memset(state, 0, sizeof(*state)); + state->relative_threshold = relative_threshold > 0.0f ? relative_threshold : 1.0f; + state->touch_id = -1; +} + +void pocket_input_button(PocketInputState *state, uint32_t button, int down, int repeat) +{ + if (button == 0) return; + if (down) { + state->held_keys |= button; + if (!repeat) state->pressed |= button; + } else { + state->held_keys &= ~button; + } +} + +void pocket_input_pulse(PocketInputState *state, uint32_t button) +{ + state->pressed |= button; +} + +void pocket_input_relative(PocketInputState *state, float delta_x, float delta_y) +{ + const float threshold = state->relative_threshold; + state->relative_x += delta_x; + state->relative_y += delta_y; + if (state->relative_x <= -threshold) { + state->pressed |= POCKET_BTN_LEFT; + state->relative_x = 0.0f; + } else if (state->relative_x >= threshold) { + state->pressed |= POCKET_BTN_RIGHT; + state->relative_x = 0.0f; + } + if (state->relative_y <= -threshold) { + state->pressed |= POCKET_BTN_UP; + state->relative_y = 0.0f; + } else if (state->relative_y >= threshold) { + state->pressed |= POCKET_BTN_DOWN; + state->relative_y = 0.0f; + } +} + +void pocket_input_primary(PocketInputState *state, int down) +{ + if (down && !state->primary_down) { + state->held_primary = POCKET_BTN_CIRCLE; + state->pressed |= POCKET_BTN_CIRCLE; + } else if (!down && state->primary_down) { + state->held_primary = 0; + } + state->primary_down = down != 0; +} + +void pocket_input_touch( + PocketInputState *state, + PocketTouchPhase phase, + int id, + float x, + float y +) +{ + switch (phase) { + case POCKET_TOUCH_DOWN: + /* The first contact is tracked; a second finger never becomes input. + * The same id going down again before the release was sampled simply + * continues the contact. */ + if (state->touch_id < 0 || state->touch_id == id) { + state->touch_id = id; + state->touch_down = 1; + state->touch_latched = 1; + state->touch_x = x; + state->touch_y = y; + } + break; + case POCKET_TOUCH_MOVE: + if (state->touch_id == id) { + state->touch_x = x; + state->touch_y = y; + } + break; + case POCKET_TOUCH_UP: + /* Only the down edge latches: a release is reported at the next sample + * and never re-arms a down frame. */ + if (state->touch_id == id) { + state->touch_x = x; + state->touch_y = y; + state->touch_down = 0; + } + break; + case POCKET_TOUCH_CANCEL: + state->touch_id = -1; + state->touch_down = 0; + state->touch_latched = 0; + break; + } +} + +void pocket_input_sample(PocketInputState *state, PocketInputSample *out) +{ + out->buttons = state->held_keys | state->held_primary | state->pressed; + out->touch_down = state->touch_down || state->touch_latched; + out->touch_x = state->touch_x; + out->touch_y = state->touch_y; + state->pressed = 0; + state->touch_latched = 0; + if (!state->touch_down) state->touch_id = -1; +} diff --git a/hosts/iphone2g/pocket_input.h b/hosts/iphone2g/pocket_input.h new file mode 100644 index 00000000..d25b5d46 --- /dev/null +++ b/hosts/iphone2g/pocket_input.h @@ -0,0 +1,82 @@ +#ifndef POCKET_INPUT_H +#define POCKET_INPUT_H + +#include + +/* + * Host-side input state for native hosts with a keyboard, a relative pointing + * device (trackpad, trackball), and one tracked touch contact. Platform event + * callbacks feed it through the functions below; the frame loop samples it + * exactly once per guest turn. The module is plain C with no platform headers + * so it compiles with the host compiler for the unit test in + * tests/fixtures/pocket-input-test.c. + * + * Semantics the two BlackBerry Classic hosts share: + * - a key down produces one press edge (platform key repeats do not) and + * holds the button until the key goes up; + * - relative motion accumulates per axis; crossing the threshold emits one + * d-pad pulse in that direction and resets that axis; + * - the relative device's primary button is the press button (CIRCLE), with + * its own held state so it cannot release a key that holds the same bit; + * - one contact is tracked from its DOWN to its UP; other contacts are + * ignored; a contact that went down and up between two samples still + * reports exactly one down sample (the latch), and a release is reported + * at the very next sample. + */ + +typedef enum { + POCKET_TOUCH_DOWN, + POCKET_TOUCH_MOVE, + POCKET_TOUCH_UP, + POCKET_TOUCH_CANCEL +} PocketTouchPhase; + +typedef struct { + uint32_t held_keys; + uint32_t held_primary; + uint32_t pressed; + float relative_x; + float relative_y; + float relative_threshold; + int primary_down; + int touch_id; + int touch_down; + int touch_latched; + float touch_x; + float touch_y; +} PocketInputState; + +typedef struct { + uint32_t buttons; + int touch_down; + float touch_x; + float touch_y; +} PocketInputSample; + +void pocket_input_init(PocketInputState *state, float relative_threshold); + +/* A platform key already mapped onto a portable button bit (0 = unmapped). */ +void pocket_input_button(PocketInputState *state, uint32_t button, int down, int repeat); + +/* A one-shot press edge for inputs without a release event (system keys). */ +void pocket_input_pulse(PocketInputState *state, uint32_t button); + +/* Relative pointing motion; d-pad pulses on threshold crossings. */ +void pocket_input_relative(PocketInputState *state, float delta_x, float delta_y); + +/* The relative device's primary button, level-triggered. */ +void pocket_input_primary(PocketInputState *state, int down); + +/* One contact with a platform id and position in any consistent unit. */ +void pocket_input_touch( + PocketInputState *state, + PocketTouchPhase phase, + int id, + float x, + float y +); + +/* The per-frame sample; clears press edges and the touch latch. */ +void pocket_input_sample(PocketInputState *state, PocketInputSample *out); + +#endif diff --git a/hosts/iphone2g/pocket_runtime.c b/hosts/iphone2g/pocket_runtime.c index 857df472..7ad62356 100644 --- a/hosts/iphone2g/pocket_runtime.c +++ b/hosts/iphone2g/pocket_runtime.c @@ -1,6 +1,7 @@ #include "pocket_runtime.h" #include "pocket_core.h" +#include "pocket_spec.h" #include "quickjs.h" #include @@ -17,7 +18,6 @@ #define POCKET_RASTER_DENSITY 1 #endif #define POCKETJS_SIMULATION_HZ 60 -#define POCKETJS_ANALOG_CENTER 32896 #define POCKETJS_ACTION_NAME_CAPACITY 64 #if defined(POCKET_RUNTIME_REPORT_BOOT_STAGE) @@ -629,7 +629,9 @@ int pocket_runtime_boot( return 1; } -int pocket_runtime_frame_ticks( +/* One guest turn (frame call + job drain), then `tick_count` core ticks. */ +static int run_frame( + uint32_t buttons, int touch_down, int touch_x, int touch_y, @@ -673,8 +675,8 @@ int pocket_runtime_frame_ticks( } } JSValue arguments[4] = { - JS_NewInt32(context, 0), - JS_NewInt32(context, POCKETJS_ANALOG_CENTER), + JS_NewUint32(context, buttons), + JS_NewInt32(context, POCKET_ANALOG_CENTER), touch_array, hit_array, }; @@ -695,6 +697,28 @@ int pocket_runtime_frame_ticks( return 1; } +int pocket_runtime_tick(const PocketRuntimeInput *input) { + if (input == 0) return 0; + return run_frame( + input->buttons, + input->touch_down, + input->touch_x, + input->touch_y, + input->touch_hit, + 1 + ); +} + +int pocket_runtime_frame_ticks( + int touch_down, + int touch_x, + int touch_y, + int touch_hit, + unsigned int tick_count +) { + return run_frame(0, touch_down, touch_x, touch_y, touch_hit, tick_count); +} + int pocket_runtime_frame(int touch_down, int touch_x, int touch_y, int touch_hit) { /* The original iPhone host presents at 30 Hz and advances two 60 Hz ticks. */ return pocket_runtime_frame_ticks(touch_down, touch_x, touch_y, touch_hit, 2); @@ -763,6 +787,11 @@ int pocket_runtime_gl_initialize(void) { return ui_gl_initialize() != 0; } +void pocket_runtime_gl_reset(void) { + if (runtime == 0 || context == 0) return; + ui_gl_reset_resources(); +} + int pocket_runtime_gl_render(int width, int height) { if (runtime == 0 || context == 0 || runtime_failed) return 0; if (width <= 0 || height <= 0) return 0; diff --git a/hosts/iphone2g/pocket_runtime.h b/hosts/iphone2g/pocket_runtime.h index a0e2f2e5..765125d0 100644 --- a/hosts/iphone2g/pocket_runtime.h +++ b/hosts/iphone2g/pocket_runtime.h @@ -13,6 +13,28 @@ int pocket_runtime_boot( int height ); /* `pack` is borrowed by QuickJS and must remain valid until shutdown. */ +/* + * One guest turn followed by exactly one core tick — the frame contract + * (docs/RUNTIMES.md, law 3). Hosts call it once per presented frame with the + * portable button mask (pocket_spec.h) and the sampled touch contact in + * logical pixels; `touch_hit` is the host-resolved bounds hit for that + * contact (pocket_runtime_hit_test_bounds) or zero. + */ +typedef struct { + uint32_t buttons; + int touch_down; + int touch_x; + int touch_y; + int touch_hit; +} PocketRuntimeInput; +int pocket_runtime_tick(const PocketRuntimeInput *input); + +/* + * Legacy frame entry points for the original iPhone host, which presents at + * 30 Hz and advances two core ticks per guest turn, and for the Windows CE + * host's tick-count form. They pass an empty button mask. New hosts call + * pocket_runtime_tick. + */ int pocket_runtime_frame(int touch_down, int touch_x, int touch_y, int touch_hit); int pocket_runtime_frame_ticks( int touch_down, @@ -44,13 +66,14 @@ unsigned long pocket_runtime_damage_pixels(void); int pocket_runtime_damage_bounds(int *bounds); /* - * Hardware path. `pocket_runtime_gl_initialize` needs a current OpenGL ES 1.1 - * context and returns zero if the GPU pipeline cannot be established, which is - * the host's signal to keep using the software rasterizer above. + * Hardware path. `pocket_runtime_gl_initialize` needs a current OpenGL ES + * context matching the core backend selected at compile time and returns zero + * if the GPU pipeline cannot be established. * `pocket_runtime_gl_render` draws the current retained tree into the bound * framebuffer; the CPU never rasterizes a pixel on this path. */ int pocket_runtime_gl_initialize(void); +void pocket_runtime_gl_reset(void); int pocket_runtime_gl_render(int width, int height); void pocket_runtime_gl_shutdown(void); diff --git a/hosts/iphone2g/pocket_spec.h b/hosts/iphone2g/pocket_spec.h new file mode 100644 index 00000000..b794591f --- /dev/null +++ b/hosts/iphone2g/pocket_spec.h @@ -0,0 +1,26 @@ +/* Generated by contracts/spec/gen-c.ts from contracts/spec/spec.ts. + * Do not edit: run `bun contracts/spec/gen-c.ts` and commit the result; + * tests/contract.ts byte-compares this file against the generator. */ +#ifndef POCKET_SPEC_H +#define POCKET_SPEC_H + +/* Portable button bitmask — identical on every host. Native hosts map + * their platform key codes onto these bits; the guest only sees the mask. */ +#define POCKET_BTN_SELECT 0x0001U +#define POCKET_BTN_START 0x0008U +#define POCKET_BTN_UP 0x0010U +#define POCKET_BTN_RIGHT 0x0020U +#define POCKET_BTN_DOWN 0x0040U +#define POCKET_BTN_LEFT 0x0080U +#define POCKET_BTN_LTRIGGER 0x0100U +#define POCKET_BTN_RTRIGGER 0x0200U +#define POCKET_BTN_TRIANGLE 0x1000U +#define POCKET_BTN_CIRCLE 0x2000U +#define POCKET_BTN_CROSS 0x4000U +#define POCKET_BTN_SQUARE 0x8000U + +/* frame(buttons, analog): analog packs a stick as (x << 8) | y, each axis + * 0..255 with 128 = center. Hosts without a stick pass this value. */ +#define POCKET_ANALOG_CENTER 0x8080U + +#endif diff --git a/hosts/iphone2g/rust_eh_personality.c b/hosts/iphone2g/rust_eh_personality.c new file mode 100644 index 00000000..b85bba15 --- /dev/null +++ b/hosts/iphone2g/rust_eh_personality.c @@ -0,0 +1,12 @@ +#include + +/* + * The no-std Rust core is built with panic=abort, but its objects still + * reference the unwinding personality symbol on targets whose system + * libraries expect one. Defining it here keeps the native link free of an + * unwinder; reaching it would already be a fatal defect, so it aborts. + */ +__attribute__((noreturn)) void rust_eh_personality(void) +{ + abort(); +} diff --git a/package.json b/package.json index 6841920b..809c7bd3 100644 --- a/package.json +++ b/package.json @@ -31,18 +31,22 @@ "apps/iphone4s-demo", "apps/ipodtouch-demo", "apps/meizu-m8-demo", + "apps/blackberry-classic-demo", "apps/nsengine", "hosts/apple", "hosts/iphone2g", "hosts/iphone4s", "hosts/ipodtouch", "hosts/meizu-m8", + "hosts/blackberry-android", + "hosts/blackberry-qnx", "hosts/web", "docs/APPLE.md", "docs/IPHONE2G.md", "docs/IPHONE4S.md", "docs/IPODTOUCH.md", "docs/MEIZU_M8.md", + "docs/BLACKBERRY_CLASSIC.md", "assets/brand", "assets/fonts", "assets/images/logo.png", @@ -205,6 +209,8 @@ "iphone4s": "bun tools/iphone4s.ts", "ipodtouch": "bun tools/ipodtouch.ts", "meizu-m8": "bun tools/meizu-m8.ts", + "blackberry-android": "bun tools/blackberry-android.ts", + "blackberry-qnx": "bun tools/blackberry-qnx.ts", "vita:art": "bun tools/generate-vita-livearea.ts", "vita:art:check": "bun tools/generate-vita-livearea.ts --check", "e2e:vita": "bun tests/e2e/vita3k.ts", diff --git a/tests/blackberry-classic.test.ts b/tests/blackberry-classic.test.ts new file mode 100644 index 00000000..35b9d119 --- /dev/null +++ b/tests/blackberry-classic.test.ts @@ -0,0 +1,163 @@ +import { describe, expect, test } from "bun:test"; +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { POCKET_TARGETS } from "../contracts/spec/platforms.ts"; +import { checkAppTypes } from "../framework/compiler/app-check.ts"; +import { verifyPlanHash } from "../framework/src/manifest/plan.ts"; +import { extractHostBuildInputs } from "../framework/src/manifest/host-build-inputs.ts"; +import { packageIdentity, renderTemplate } from "../tools/native-host-build.ts"; +import { + BLACKBERRY_ANDROID_DEV_TARGET_ID, + BLACKBERRY_CLASSIC_DEV_CONTRACTS, + BLACKBERRY_CLASSIC_HOST_ABI, + BLACKBERRY_CLASSIC_LOGICAL_VIEWPORT, + BLACKBERRY_CLASSIC_PHYSICAL_VIEWPORT, + BLACKBERRY_CLASSIC_RASTER_DENSITY, + BLACKBERRY_QNX_DEV_TARGET_ID, + resolveBlackBerryClassicBuildPlan, +} from "../tools/blackberry-classic-profile.ts"; + +const repository = join(import.meta.dir, ".."); +const manifestPath = join(repository, "apps/blackberry-classic-demo/pocket.json"); +const targets = [BLACKBERRY_QNX_DEV_TARGET_ID, BLACKBERRY_ANDROID_DEV_TARGET_ID] as const; + +function manifest(): Record { + return JSON.parse(readFileSync(manifestPath, "utf8")); +} + +describe("private BlackBerry Classic profiles", () => { + test("register both hosts privately with one square density-2 contract", () => { + for (const target of targets) { + expect(POCKET_TARGETS).not.toHaveProperty(target); + expect(BLACKBERRY_CLASSIC_DEV_CONTRACTS.targets[target]).toEqual({ + hostAbi: BLACKBERRY_CLASSIC_HOST_ABI, + platform: target === BLACKBERRY_QNX_DEV_TARGET_ID + ? "blackberry10-qnx" + : "blackberry10-android", + form: "takeover", + display: { + physicalViewport: BLACKBERRY_CLASSIC_PHYSICAL_VIEWPORT, + logicalViewports: [BLACKBERRY_CLASSIC_LOGICAL_VIEWPORT], + presentations: ["native"], + rasterDensity: BLACKBERRY_CLASSIC_RASTER_DENSITY, + }, + capabilities: ["input.buttons", "input.touch", "text.glyphs.baked"], + }); + } + }); + + test("resolve the Hero demo to the exact device plan for each host", () => { + for (const target of targets) { + const plan = resolveBlackBerryClassicBuildPlan(manifest(), target); + expect(plan.target).toEqual({ id: target, hostAbi: BLACKBERRY_CLASSIC_HOST_ABI }); + expect(plan.viewport).toEqual({ + logical: BLACKBERRY_CLASSIC_LOGICAL_VIEWPORT, + physical: BLACKBERRY_CLASSIC_PHYSICAL_VIEWPORT, + presentation: "native", + rasterDensity: BLACKBERRY_CLASSIC_RASTER_DENSITY, + policy: "fixed", + }); + expect(plan.features).toEqual({ + "input.buttons": true, + "input.touch": true, + "text.glyphs.baked": true, + }); + expect(plan.companions).toEqual([]); + expect(plan.app.output).toBe("blackberry-classic-main"); + expect(verifyPlanHash(plan)).toBe(true); + } + }); + + test("derive the platform package identity from the plan, not from a second copy", () => { + const plan = resolveBlackBerryClassicBuildPlan(manifest(), BLACKBERRY_QNX_DEV_TARGET_ID); + const inputs = extractHostBuildInputs(plan, { expectedTarget: BLACKBERRY_QNX_DEV_TARGET_ID }); + expect(inputs.app).toEqual({ + id: "dev.pocket-stack.blackberry-classic-demo", + title: "PocketJS: BlackBerry Classic Hero", + version: "0.1.1", + }); + expect(packageIdentity(inputs.app)).toEqual({ + packageId: "dev.pocket_stack.blackberry_classic_demo", + version: "0.1.1", + versionCode: 1001, + title: "PocketJS: BlackBerry Classic Hero", + }); + expect(() => packageIdentity({ ...inputs.app, id: "dev.9bad.segment" })).toThrow("package name"); + expect(() => packageIdentity({ ...inputs.app, version: "1.2" })).toThrow("major.minor.patch"); + expect(packageIdentity({ ...inputs.app, version: "2.34.5-rc.1" }).versionCode).toBe(2_034_005); + expect(renderTemplate("@POCKET_ID@", { ID: "a.b" })).toBe("a.b"); + expect(() => renderTemplate("@POCKET_ID@", {})).toThrow("@POCKET_ID@"); + }); + + test("reject capabilities and viewports the Classic hosts do not implement", () => { + const needsIme = manifest(); + needsIme.engine.capabilities.requires.push("input.ime"); + expect(() => resolveBlackBerryClassicBuildPlan(needsIme, BLACKBERRY_QNX_DEV_TARGET_ID)) + .toThrow("input.ime"); + + const stretched = manifest(); + stretched.app.viewport.fixed.logical = [320, 480]; + expect(() => resolveBlackBerryClassicBuildPlan(stretched, BLACKBERRY_ANDROID_DEV_TARGET_ID)) + .toThrow("320x480"); + }); + + test("type-check the explicit Solid and PocketJS imports of the demo", () => { + const result = checkAppTypes({ + entry: join(repository, "apps/blackberry-classic-demo/main.tsx"), + tsconfigPath: join(repository, "tsconfig.json"), + declarationFiles: [join(repository, "framework/src/jsx.d.ts")], + }); + expect( + result.diagnostics + .filter((diagnostic) => diagnostic.category === "error") + .map((diagnostic) => diagnostic.message), + ).toEqual([]); + expect(result.ok).toBe(true); + }); +}); + +describe("BlackBerry Classic toolchain pins", () => { + const qnx = JSON.parse( + readFileSync(join(repository, "tools/cli/blackberry-qnx-toolchain.json"), "utf8"), + ); + const android = JSON.parse( + readFileSync(join(repository, "tools/cli/blackberry-android-toolchain.json"), "utf8"), + ); + + test("both hosts build against the same QuickJS revision and Rust nightly", () => { + expect(qnx.quickjs.revision).toMatch(/^[0-9a-f]{40}$/); + expect(android.quickjs).toEqual(qnx.quickjs); + expect(android.rust.toolchain).toBe(qnx.rust.toolchain); + expect(android.app.manifest).toBe("apps/blackberry-classic-demo/pocket.json"); + expect(qnx.app.manifest).toBe("apps/blackberry-classic-demo/pocket.json"); + }); + + test("the QNX host pins the BBNDK image by digest and ships its Rust target", () => { + expect(qnx.image.digest).toMatch(/^sha256:[0-9a-f]{64}$/); + expect(qnx.image.platform).toBe("linux/amd64"); + expect(qnx.qnx.architecture).toBe("armle-v7"); + expect(existsSync(join(repository, qnx.rust.target))).toBe(true); + }); + + test("the Android host pins API 18, the last Jelly Bean NDK, and the v1-only ABI", () => { + expect(android.android).toMatchObject({ + apiLevel: 18, + ndkVersion: "23.2.8568313", + abi: "armeabi-v7a", + clangTarget: "armv7a-linux-androideabi18", + }); + expect(android.android.packages.map((pkg: { id: string }) => pkg.id)).toEqual([ + "platforms;android-18", + "build-tools;35.0.0", + "ndk;23.2.8568313", + ]); + for (const pkg of android.android.packages) { + for (const archive of Object.values(pkg.archives) as { asset: string; sha1: string }[]) { + expect(archive.asset).toMatch(/\.zip$/); + expect(archive.sha1).toMatch(/^[0-9a-f]{40}$/); + } + } + expect(android.javaImage).toMatch(/@sha256:[0-9a-f]{64}$/); + expect(android.rust.target).toBe("armv7-linux-androideabi"); + }); +}); diff --git a/tests/contract.ts b/tests/contract.ts index d133b93d..9f1c9bb6 100644 --- a/tests/contract.ts +++ b/tests/contract.ts @@ -8,7 +8,10 @@ // (c) Regenerates package.json's exports block from the subpath registry // (framework/compiler/subpaths.ts) and byte-compares: the npm surface // can never drift from the one declaration. Fix = `bun tools/gen-exports.ts`. +// (d) Regenerates hosts/iphone2g/pocket_spec.h (the C input constants native +// hosts include) from spec.ts and byte-compares. Fix = `bun contracts/spec/gen-c.ts`. +import { generateC } from "../contracts/spec/gen-c.ts"; import { generateRust } from "../contracts/spec/gen-rust.ts"; import { withGeneratedExports } from "../tools/gen-exports.ts"; import { @@ -47,6 +50,16 @@ check( "run `bun contracts/spec/gen-rust.ts` and commit the result", ); +// ---- (d) generated pocket_spec.h is in sync ----------------------------------- + +const specHPath = new URL("../hosts/iphone2g/pocket_spec.h", import.meta.url).pathname; +const committedHeader = await Bun.file(specHPath).text().catch(() => null); +check( + committedHeader !== null && committedHeader === generateC(), + "hosts/iphone2g/pocket_spec.h matches spec.ts", + "run `bun contracts/spec/gen-c.ts` and commit the result", +); + // ---- (c) package.json exports match the subpath registry --------------------- const pkgPath = new URL("../package.json", import.meta.url).pathname; diff --git a/tests/fixtures/plans/portable-psp.plan.json b/tests/fixtures/plans/portable-psp.plan.json index 1d5c7142..9297e694 100644 --- a/tests/fixtures/plans/portable-psp.plan.json +++ b/tests/fixtures/plans/portable-psp.plan.json @@ -2,6 +2,7 @@ "app": { "id": "dev.pocket-stack.telemetry", "title": "Pocket Telemetry", + "version": "0.1.0", "entry": "app/main.tsx", "output": "main", "framework": "solid" @@ -29,5 +30,5 @@ "text.glyphs.baked": true }, "companions": [], - "planHash": "sha256:e3a257d89114161e6faecb37249f3cba37f444034a951c80a8ffcaed5a593ddf" + "planHash": "sha256:fc32497b58bf5b9fab7c827ba8f6073cd749cb92c6cec6d37829fca7cf7e2085" } diff --git a/tests/fixtures/plans/portable-vita.plan.json b/tests/fixtures/plans/portable-vita.plan.json index 1528e858..8398f55f 100644 --- a/tests/fixtures/plans/portable-vita.plan.json +++ b/tests/fixtures/plans/portable-vita.plan.json @@ -2,6 +2,7 @@ "app": { "id": "dev.pocket-stack.telemetry", "title": "Pocket Telemetry", + "version": "0.1.0", "entry": "app/main.tsx", "output": "main", "framework": "solid" @@ -29,5 +30,5 @@ "text.glyphs.baked": true }, "companions": [], - "planHash": "sha256:5b2ab23a3d3b54e0ef54bdd706092cd8350561d978f6cb1280a0c72afa647e96" + "planHash": "sha256:ce59ea6fe09e18d80c3c1df83915487a4f58d082b2ff11b59c95b7d214a3a8ba" } diff --git a/tests/fixtures/pocket-input-test.c b/tests/fixtures/pocket-input-test.c new file mode 100644 index 00000000..dbb242ff --- /dev/null +++ b/tests/fixtures/pocket-input-test.c @@ -0,0 +1,140 @@ +/* Behavioural test for hosts/iphone2g/pocket_input.c, compiled and run by + * tests/pocket-input.test.ts with the host compiler. Every scenario is one + * host event sequence followed by the frame samples the guest would see. */ +#include "../../hosts/iphone2g/pocket_input.h" +#include "../../hosts/iphone2g/pocket_spec.h" + +#include +#include + +static int failures; + +static void expect(int condition, const char *label) +{ + if (condition) return; + failures += 1; + fprintf(stderr, "FAIL %s\n", label); +} + +static PocketInputSample sample(PocketInputState *state) +{ + PocketInputSample out; + pocket_input_sample(state, &out); + return out; +} + +static void keyboard_edges(void) +{ + PocketInputState s; + pocket_input_init(&s, 1.0f); + pocket_input_button(&s, POCKET_BTN_CIRCLE, 1, 0); + expect(sample(&s).buttons == POCKET_BTN_CIRCLE, "key down is pressed and held on the first sample"); + pocket_input_button(&s, POCKET_BTN_CIRCLE, 1, 1); /* platform auto-repeat */ + expect(sample(&s).buttons == POCKET_BTN_CIRCLE, "a held key stays held across samples"); + pocket_input_button(&s, POCKET_BTN_CIRCLE, 0, 0); + expect(sample(&s).buttons == 0, "key up releases the button"); + pocket_input_button(&s, 0, 1, 0); + expect(sample(&s).buttons == 0, "an unmapped key (0) is ignored"); + pocket_input_pulse(&s, POCKET_BTN_CIRCLE); + expect(sample(&s).buttons == POCKET_BTN_CIRCLE, "a pulse presses for one sample"); + expect(sample(&s).buttons == 0, "a pulse does not hold"); +} + +static void relative_axis(void) +{ + PocketInputState s; + pocket_input_init(&s, 1.0f); /* integer displacements: a pulse per event */ + pocket_input_relative(&s, 3.0f, 0.0f); + expect(sample(&s).buttons == POCKET_BTN_RIGHT, "positive x displacement pulses RIGHT once"); + expect(sample(&s).buttons == 0, "a relative pulse does not hold"); + pocket_input_relative(&s, -1.0f, 0.0f); + expect(sample(&s).buttons == POCKET_BTN_LEFT, "the remainder of a large move does not flip the next pulse"); + pocket_input_relative(&s, 0.0f, -1.0f); + pocket_input_relative(&s, 0.0f, 2.0f); + expect(sample(&s).buttons == (POCKET_BTN_UP | POCKET_BTN_DOWN), "each event is its own pulse"); + + pocket_input_init(&s, 0.35f); /* fractional deltas accumulate */ + pocket_input_relative(&s, 0.2f, 0.0f); + expect(sample(&s).buttons == 0, "sub-threshold motion does not pulse"); + pocket_input_relative(&s, 0.2f, 0.0f); + expect(sample(&s).buttons == POCKET_BTN_RIGHT, "accumulated motion pulses once"); + pocket_input_relative(&s, 0.2f, 0.0f); + expect(sample(&s).buttons == 0, "a pulse resets the axis"); + pocket_input_relative(&s, -0.2f, 0.3f); + pocket_input_relative(&s, 0.0f, 0.1f); + expect(sample(&s).buttons == POCKET_BTN_DOWN, "opposite motion cancels; the other axis pulses DOWN"); +} + +static void primary_button(void) +{ + PocketInputState s; + pocket_input_init(&s, 1.0f); + pocket_input_primary(&s, 1); + expect(sample(&s).buttons == POCKET_BTN_CIRCLE, "primary down presses CIRCLE"); + pocket_input_primary(&s, 1); + expect(sample(&s).buttons == POCKET_BTN_CIRCLE, "primary level holds CIRCLE without a second edge"); + pocket_input_primary(&s, 0); + expect(sample(&s).buttons == 0, "primary up releases CIRCLE"); + pocket_input_button(&s, POCKET_BTN_CIRCLE, 1, 0); + sample(&s); + pocket_input_primary(&s, 1); + pocket_input_primary(&s, 0); + expect(sample(&s).buttons == POCKET_BTN_CIRCLE, "a click cannot release a key that holds the same bit"); +} + +static void touch_contact(void) +{ + PocketInputState s; + PocketInputSample out; + pocket_input_init(&s, 1.0f); + + /* A tap that goes down and up between two samples is still one press. */ + pocket_input_touch(&s, POCKET_TOUCH_DOWN, 0, 10.0f, 20.0f); + pocket_input_touch(&s, POCKET_TOUCH_UP, 0, 11.0f, 21.0f); + out = sample(&s); + expect(out.touch_down == 1 && out.touch_x == 11.0f && out.touch_y == 21.0f, "tap between samples reports one down sample"); + expect(sample(&s).touch_down == 0, "the sample after a tap is up"); + + /* A long press: the release is reported at the very next sample. */ + pocket_input_touch(&s, POCKET_TOUCH_DOWN, 0, 1.0f, 1.0f); + expect(sample(&s).touch_down == 1, "contact down"); + pocket_input_touch(&s, POCKET_TOUCH_MOVE, 0, 2.0f, 3.0f); + out = sample(&s); + expect(out.touch_down == 1 && out.touch_x == 2.0f && out.touch_y == 3.0f, "move updates the held contact"); + pocket_input_touch(&s, POCKET_TOUCH_UP, 0, 2.0f, 3.0f); + expect(sample(&s).touch_down == 0, "release is reported immediately, not one frame later"); + + /* A second finger never becomes input. */ + pocket_input_touch(&s, POCKET_TOUCH_DOWN, 0, 5.0f, 5.0f); + sample(&s); + pocket_input_touch(&s, POCKET_TOUCH_DOWN, 1, 50.0f, 50.0f); + pocket_input_touch(&s, POCKET_TOUCH_MOVE, 1, 60.0f, 60.0f); + out = sample(&s); + expect(out.touch_down == 1 && out.touch_x == 5.0f, "a second contact does not move the tracked one"); + pocket_input_touch(&s, POCKET_TOUCH_UP, 1, 60.0f, 60.0f); + expect(sample(&s).touch_down == 1, "a second contact's release does not lift the tracked one"); + pocket_input_touch(&s, POCKET_TOUCH_UP, 0, 5.0f, 5.0f); + expect(sample(&s).touch_down == 0, "the tracked contact's release lifts"); + pocket_input_touch(&s, POCKET_TOUCH_MOVE, 1, 70.0f, 70.0f); + expect(sample(&s).touch_down == 0, "a stray move from an untracked contact is not a press"); + + /* Cancel drops everything, and the next contact is tracked again. */ + pocket_input_touch(&s, POCKET_TOUCH_DOWN, 0, 1.0f, 1.0f); + pocket_input_touch(&s, POCKET_TOUCH_CANCEL, 0, 0.0f, 0.0f); + expect(sample(&s).touch_down == 0, "cancel clears the latch"); + pocket_input_touch(&s, POCKET_TOUCH_DOWN, 0, 9.0f, 9.0f); + expect(sample(&s).touch_down == 1, "a new contact after cancel is tracked"); +} + +int main(void) +{ + keyboard_edges(); + relative_axis(); + primary_button(); + touch_contact(); + if (failures != 0) { + fprintf(stderr, "%d pocket_input expectation(s) failed\n", failures); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/tests/host-build-inputs.test.ts b/tests/host-build-inputs.test.ts index 3a0d030b..ab6bc0c6 100644 --- a/tests/host-build-inputs.test.ts +++ b/tests/host-build-inputs.test.ts @@ -20,6 +20,7 @@ describe("custom host build boundary", () => { const plan = portablePlan(); expect(extractHostBuildInputs(plan, { expectedTarget: "psp" })).toEqual({ appOutput: "main", + app: { id: "dev.pocket-stack.telemetry", title: "Pocket Telemetry", version: "0.1.0" }, target: "psp", hostAbi: 1, viewport: { @@ -46,6 +47,9 @@ describe("custom host build boundary", () => { embedApp: false, })).toEqual({ POCKETJS_APP_OUTPUT: "main", + POCKETJS_APP_ID: "dev.pocket-stack.telemetry", + POCKETJS_APP_TITLE: "Pocket Telemetry", + POCKETJS_APP_VERSION: "0.1.0", POCKETJS_EMBED_APP: "0", POCKETJS_OUTPUT_DIR: "/tmp/pocket", POCKETJS_TARGET: "psp", diff --git a/tests/npm-package.test.ts b/tests/npm-package.test.ts index df614444..f3af8ec7 100644 --- a/tests/npm-package.test.ts +++ b/tests/npm-package.test.ts @@ -66,18 +66,22 @@ describe("published npm artifacts", () => { "apps/iphone4s-demo", "apps/ipodtouch-demo", "apps/meizu-m8-demo", + "apps/blackberry-classic-demo", "apps/nsengine", "hosts/apple", "hosts/iphone2g", "hosts/iphone4s", "hosts/ipodtouch", "hosts/meizu-m8", + "hosts/blackberry-android", + "hosts/blackberry-qnx", "hosts/web", "docs/APPLE.md", "docs/IPHONE2G.md", "docs/IPHONE4S.md", "docs/IPODTOUCH.md", "docs/MEIZU_M8.md", + "docs/BLACKBERRY_CLASSIC.md", "assets/brand", "assets/fonts", "assets/images/logo.png", @@ -170,11 +174,18 @@ describe("published npm artifacts", () => { "apps/hero/app.tsx", "apps/iphone2g-demo/pocket.json", "apps/iphone4s-demo/pocket.json", + "apps/blackberry-classic-demo/pocket.json", "hosts/iphone2g/device_tool.c", "hosts/iphone2g/armv6-apple-ios.json", "hosts/iphone4s/armv7-apple-ios.json", + "hosts/blackberry-android/app/AndroidManifest.xml", + "hosts/blackberry-android/app/jni/runtime.c", + "hosts/blackberry-qnx/main.c", + "hosts/blackberry-qnx/bar-descriptor.xml", + "hosts/blackberry-qnx/armv7-qnx-eabi.json", "docs/IPHONE2G.md", "docs/IPHONE4S.md", + "docs/BLACKBERRY_CLASSIC.md", "assets/images/logo.png", "assets/images/spinner-00.svg", "assets/images/spinner-01.svg", @@ -211,6 +222,9 @@ describe("published npm artifacts", () => { "engine/crates/pocket-ui-surface/Cargo.toml", "engine/crates/pocket-ui-surface/src/lib.rs", "tools/cli/symbian-toolchain.json", + "tools/cli/blackberry-android-toolchain.json", + "tools/cli/blackberry-qnx-toolchain.json", + "tools/blackberry-qnx/build.sh", "tools/symbian/coda-usb-probe.c", "tools/symbian/Dockerfile.dockerignore", "engine/pocket3d/crates/pocket3d-vita/Cargo.toml", diff --git a/tests/platform-contracts.test.ts b/tests/platform-contracts.test.ts index 583775e7..89e7a076 100644 --- a/tests/platform-contracts.test.ts +++ b/tests/platform-contracts.test.ts @@ -314,6 +314,7 @@ describe("semantic resolution", () => { expect(result.plan.app).toEqual({ id: "dev.pocket-stack.telemetry", title: "Pocket Telemetry", + version: "0.1.0", entry: "app/main.tsx", output: "main", framework: "solid", @@ -399,6 +400,7 @@ describe("semantic resolution", () => { // note is dynamic-only. A new demo missing here fails the test on // purpose. const expected: Record = { + "blackberry-classic-demo": [false, false, false, true], // built by the private blackberry-{qnx,android}-dev profiles; macos-app also admits its fixed 360x360 buttons+glyphs contract cafe: [true, true, false, true], cards: [true, true, false, true], chrome: [true, true, false, true], diff --git a/tests/pocket-input.test.ts b/tests/pocket-input.test.ts new file mode 100644 index 00000000..705b98a1 --- /dev/null +++ b/tests/pocket-input.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, test } from "bun:test"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const repository = join(import.meta.dir, ".."); + +describe("native host input state machine", () => { + test("keyboard edges, relative-axis pulses, the primary button, and the touch latch", () => { + const compiler = Bun.which("cc"); + expect(compiler, "cc is required to build the pocket_input test").toBeTruthy(); + if (!compiler) return; + const root = mkdtempSync(join(tmpdir(), "pocketjs-pocket-input-")); + try { + const executable = join(root, "pocket-input-test"); + const compiled = Bun.spawnSync([ + compiler, + "-std=c11", + "-Wall", + "-Wextra", + "-Werror", + "-I", + join(repository, "hosts/iphone2g"), + join(repository, "hosts/iphone2g/pocket_input.c"), + join(repository, "tests/fixtures/pocket-input-test.c"), + "-o", + executable, + ], { cwd: repository }); + expect(compiled.exitCode, compiled.stderr.toString()).toBe(0); + if (process.platform === "darwin") { + const xattr = Bun.which("xattr"); + if (xattr) Bun.spawnSync([xattr, "-d", "com.apple.provenance", executable]); + } + const ran = Bun.spawnSync([executable], { cwd: repository }); + expect(ran.exitCode, ran.stderr.toString()).toBe(0); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); +}); diff --git a/tests/test-suite.test.ts b/tests/test-suite.test.ts index 572b9c93..12bd5b6c 100644 --- a/tests/test-suite.test.ts +++ b/tests/test-suite.test.ts @@ -53,6 +53,17 @@ describe("declared test suite", () => { expect(meizuM8Tests.filter((file) => !declared.has(file))).toEqual([]); }); + test("runs every BlackBerry Classic test in the CI unit stage", () => { + const declared = unitTestFiles(); + const blackberryTests = readdirSync(join(repository, "tests")) + .filter((file) => /^blackberry-.*\.test\.ts$/.test(file)) + .map((file) => `tests/${file}`) + .sort(); + + expect(blackberryTests).not.toHaveLength(0); + expect(blackberryTests.filter((file) => !declared.has(file))).toEqual([]); + }); + test("runs every iPhone 4S test in the CI unit stage", () => { const declared = unitTestFiles(); const iphone4sTests = readdirSync(join(repository, "tests")) diff --git a/tools/blackberry-android.ts b/tools/blackberry-android.ts new file mode 100644 index 00000000..b10a724a --- /dev/null +++ b/tools/blackberry-android.ts @@ -0,0 +1,700 @@ +import { createHash } from "node:crypto"; +import { + cpSync, + copyFileSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + renameSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { homedir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { + BLACKBERRY_ANDROID_DEV_TARGET_ID, + resolveBlackBerryClassicBuildPlan, +} from "./blackberry-classic-profile.ts"; +import { + buildGuestBundle, + ensureQuickJsCheckout, + type GuestBundle, + type GuestBundleRequest, + mustRunCommand, + packageIdentity, + type PackageIdentity, + printCheck, + quickJsCheckout, + quickJsCheckoutStatus, + readGuestBundle, + renderTemplate, + runCommand, + sha256File, + xmlEscape, +} from "./native-host-build.ts"; + +const LABEL = "PocketJS BlackBerry Android"; +const repository = fileURLToPath(new URL("..", import.meta.url)); +const command = Bun.argv[2] ?? "doctor"; +const toolchain = JSON.parse( + readFileSync( + join(repository, "tools/cli/blackberry-android-toolchain.json"), + "utf8", + ), +) as { + readonly toolchainVersion: string; + readonly cachePath: string; + readonly javaImage: string; + readonly quickjs: { + readonly version: string; + readonly repository: string; + readonly revision: string; + }; + readonly rust: { + readonly toolchain: string; + readonly target: string; + }; + readonly android: { + readonly apiLevel: number; + readonly platformVersion: string; + readonly buildToolsVersion: string; + readonly ndkVersion: string; + readonly abi: string; + readonly clangTarget: string; + readonly repository: string; + readonly packages: readonly SdkPackage[]; + }; + readonly app: { + readonly manifest: string; + readonly output: string; + }; +}; + +interface SdkArchive { + readonly asset: string; + /** The checksum Google publishes in repository2-3.xml (what sdkmanager checks). */ + readonly sha1: string; +} + +/** One SDK component: the archive per host OS and where it unpacks. */ +interface SdkPackage { + readonly id: string; + readonly path: string; + readonly archives: Readonly>>; +} + +/** + * The NDK ships one LLVM prebuilt per host operating system. Linux x86-64 is + * the verified host; the macOS prebuilt is x86-64 as well and runs under + * Rosetta on Apple silicon. + */ +function ndkHostTag(): string { + switch (process.platform) { + case "linux": + return "linux-x86_64"; + case "darwin": + return "darwin-x86_64"; + default: + throw new Error(`${LABEL}: no NDK r23c prebuilt for host ${process.platform}`); + } +} + +const cache = join(homedir(), ".cache/pocket-stack", toolchain.cachePath); +const sdk = process.env.POCKETJS_ANDROID_SDK_ROOT ?? join(cache, "sdk"); +const buildTools = join(sdk, "build-tools", toolchain.android.buildToolsVersion); +const ndk = join(sdk, "ndk", toolchain.android.ndkVersion); +const llvm = join(ndk, "toolchains/llvm/prebuilt", ndkHostTag(), "bin"); +const clang = join(llvm, `${toolchain.android.clangTarget}-clang`); +const readelf = join(llvm, "llvm-readelf"); +const androidJar = join( + sdk, + "platforms", + `android-${toolchain.android.apiLevel}`, + "android.jar", +); +const appHost = join(repository, "hosts/blackberry-android/app"); +const build = join(repository, ".pocket-build/blackberry-android"); +const staging = join(build, "staging"); +const appOutput = join(repository, toolchain.app.output); +const signing = join(cache, "signing"); +/* One local key signs every APK; Android upgrades an installed package only + * when the new APK carries the same signing identity, so a key generated by + * an earlier version of this tool keeps being used under its old name. */ +const keystoreName = existsSync(join(signing, "blackberry-android-probe.jks")) + ? "blackberry-android-probe.jks" + : "blackberry-classic.jks"; +const keystore = join(signing, keystoreName); +const quickJs = quickJsCheckout(join(cache, "sources/quickjs-rs")); +const guest: GuestBundleRequest = { + label: LABEL, + repository, + target: BLACKBERRY_ANDROID_DEV_TARGET_ID, + resolvePlan: (manifest) => + resolveBlackBerryClassicBuildPlan(manifest, BLACKBERRY_ANDROID_DEV_TARGET_ID), + manifestPath: join(repository, toolchain.app.manifest), + planPath: join(repository, ".pocket/blackberry-android/app.plan.json"), + outputDirectory: join(repository, "dist/blackberry-android/guest"), +}; + +function run(program: string, args: readonly string[]) { + return runCommand(program, args, repository); +} + +function mustRun( + program: string, + args: readonly string[], + cwd = repository, + env: NodeJS.ProcessEnv = process.env, +): string { + return mustRunCommand(LABEL, program, args, cwd, env); +} + +function dockerJava(args: readonly string[]): string { + const uid = process.getuid?.() ?? 1000; + const gid = process.getgid?.() ?? 1000; + return mustRun("docker", [ + "run", + "--rm", + "--user", + `${uid}:${gid}`, + "-e", + "HOME=/tmp", + "-v", + `${repository}:/repo:ro`, + "-v", + `${sdk}:/android-sdk:ro`, + "-v", + `${build}:/build`, + "-v", + `${signing}:/signing`, + toolchain.javaImage, + ...args, + ]); +} + +function javaImagePresent(): boolean { + return run("docker", ["image", "inspect", toolchain.javaImage]).exitCode === 0; +} + +/** + * Checks the target's std directory in the pinned toolchain's sysroot. `rustup + * target list --toolchain X` would install a missing X on the spot, which a + * doctor must not do. + */ +function rustTargetInstalled(): boolean { + const sysroot = run("rustup", ["run", toolchain.rust.toolchain, "rustc", "--print", "sysroot"]); + if (sysroot.exitCode !== 0) return false; + return existsSync( + join(sysroot.stdout.trim(), "lib/rustlib", toolchain.rust.target, "lib"), + ); +} + +function checkPath(label: string, path: string): boolean { + return printCheck(label, existsSync(path), path); +} + +function doctor(): void { + const rust = run("rustup", ["run", toolchain.rust.toolchain, "rustc", "--version"]); + const quickjs = quickJsCheckoutStatus(quickJs.root, toolchain.quickjs); + const sdkChecks = [ + checkPath(`Android SDK Platform ${toolchain.android.apiLevel}`, androidJar), + checkPath(`NDK ${toolchain.android.ndkVersion} clang`, clang), + checkPath("NDK llvm-readelf", readelf), + checkPath("aapt2", join(buildTools, "aapt2")), + checkPath("aapt", join(buildTools, "aapt")), + checkPath("d8", join(buildTools, "d8")), + checkPath("zipalign", join(buildTools, "zipalign")), + checkPath("apksigner", join(buildTools, "apksigner")), + ]; + const checks = [ + ...sdkChecks, + printCheck("Java image", javaImagePresent(), toolchain.javaImage), + printCheck( + "Rust nightly", + rust.exitCode === 0, + rust.stdout.trim() || toolchain.rust.toolchain, + ), + printCheck( + "Rust Android target", + rustTargetInstalled(), + `${toolchain.rust.target} on ${toolchain.rust.toolchain}`, + ), + printCheck("pinned QuickJS", quickjs.ok, quickjs.detail), + ]; + if (sdkChecks.some((ok) => !ok)) { + console.log( + `Run \`bun blackberry-android setup\` to unpack the pinned SDK archives into ${sdk}, ` + + `or point POCKETJS_ANDROID_SDK_ROOT at an SDK that already holds ` + + `${toolchain.android.packages.map((pkg) => pkg.id).join(", ")}.`, + ); + } + if (checks.some((ok) => !ok)) process.exitCode = 1; + else console.log(`[ok] toolchain: ${toolchain.toolchainVersion}`); +} + +function requireToolchain(): void { + doctor(); + if (process.exitCode) { + throw new Error(`${LABEL}: toolchain is incomplete; see the doctor report above`); + } +} + +async function sha1File(path: string): Promise { + const hash = createHash("sha1"); + for await (const chunk of Bun.file(path).stream()) hash.update(chunk); + return hash.digest("hex"); +} + +/** + * Unpacks the pinned SDK archives (the same files `sdkmanager` installs) into + * the SDK root, one host-OS archive per component; nothing is downloaded for + * a component whose directory already exists. + */ +async function installSdkPackages(): Promise { + const downloads = join(cache, "downloads"); + for (const pkg of toolchain.android.packages) { + const target = join(sdk, pkg.path); + if (existsSync(target)) continue; + const archive = + pkg.archives[process.platform as "linux" | "darwin"] ?? pkg.archives.any; + if (!archive) { + throw new Error(`${LABEL}: ${pkg.id} has no archive for host ${process.platform}`); + } + mkdirSync(downloads, { recursive: true }); + const download = join(downloads, archive.asset); + if (!existsSync(download) || (await sha1File(download)) !== archive.sha1) { + const url = `${toolchain.android.repository}${archive.asset}`; + console.log(`${LABEL}: downloading ${url}`); + const response = await fetch(url); + if (!response.ok || response.body === null) { + throw new Error(`${LABEL}: ${url} failed (${response.status})`); + } + const partial = `${download}.part`; + const sink = Bun.file(partial).writer(); + let received = 0; + for await (const chunk of response.body) { + sink.write(chunk); + received += chunk.byteLength; + } + await sink.end(); + renameSync(partial, download); + console.log(`${LABEL}: ${archive.asset} ${received} bytes`); + const digest = await sha1File(download); + if (digest !== archive.sha1) { + rmSync(download, { force: true }); + throw new Error( + `${LABEL}: ${archive.asset} sha1 ${digest} does not match the pinned ${archive.sha1}`, + ); + } + } + const scratch = mkdtempSync(join(downloads, "unpack-")); + try { + mustRun("unzip", ["-q", download, "-d", scratch]); + const entries = readdirSync(scratch).filter((name) => !name.startsWith(".")); + if (entries.length !== 1) { + throw new Error( + `${LABEL}: ${archive.asset} unpacked to ${entries.length} entries, expected one directory`, + ); + } + mkdirSync(dirname(target), { recursive: true }); + renameSync(join(scratch, entries[0]), target); + console.log(`${LABEL}: ${pkg.id} -> ${target}`); + } finally { + rmSync(scratch, { recursive: true, force: true }); + } + } +} + +async function setup(): Promise { + await installSdkPackages(); + if (!javaImagePresent()) mustRun("docker", ["pull", toolchain.javaImage]); + ensureQuickJsCheckout(LABEL, quickJs.root, toolchain.quickjs); + if (!rustTargetInstalled()) { + mustRun("rustup", [ + "target", + "add", + toolchain.rust.target, + "--toolchain", + toolchain.rust.toolchain, + ]); + } + doctor(); +} + +function ensureKeystore(): void { + mkdirSync(signing, { recursive: true }); + if (existsSync(keystore)) return; + dockerJava([ + "keytool", + "-genkeypair", + "-noprompt", + "-keystore", + `/signing/${keystoreName}`, + "-storepass", + "android", + "-alias", + "androiddebugkey", + "-keypass", + "android", + "-dname", + "CN=PocketJS BlackBerry Classic,O=PocketJS,C=HK", + "-keyalg", + "RSA", + "-keysize", + "2048", + "-validity", + "10000", + ]); +} + +/** javac → jar → d8 for PocketActivity, into staging/classes.dex. */ +function compileActivity(): void { + mkdirSync(join(build, "classes"), { recursive: true }); + mkdirSync(join(build, "dex"), { recursive: true }); + dockerJava([ + "javac", + "-encoding", + "UTF-8", + "-source", + "7", + "-target", + "7", + "-bootclasspath", + `/android-sdk/platforms/android-${toolchain.android.apiLevel}/android.jar`, + "-d", + "/build/classes", + "/repo/hosts/blackberry-android/app/src/dev/pocketstack/blackberry/PocketActivity.java", + ]); + dockerJava(["jar", "cf", "/build/classes.jar", "-C", "/build/classes", "."]); + dockerJava([ + `/android-sdk/build-tools/${toolchain.android.buildToolsVersion}/d8`, + "--min-api", + String(toolchain.android.apiLevel), + "--output", + "/build/dex", + "/build/classes.jar", + ]); + copyFileSync(join(build, "dex/classes.dex"), join(staging, "classes.dex")); +} + +/** + * aapt2 → zipalign → apksigner over staging/ (classes.dex + lib/) plus the + * guest assets. Android 4.3 verifies only the JAR (v1) signature scheme, so + * every newer scheme is disabled explicitly. + */ +function packageApk(identity: PackageIdentity, resources: string, assets: string): { + readonly signature: string; + readonly badging: string; +} { + const compiled = join(build, "app-res.zip"); + mustRun(join(buildTools, "aapt2"), ["compile", "--dir", resources, "-o", compiled]); + const manifest = join(build, "AndroidManifest.xml"); + writeFileSync( + manifest, + renderTemplate(readFileSync(join(appHost, "AndroidManifest.xml"), "utf8"), { + PACKAGE: identity.packageId, + VERSION_CODE: identity.versionCode, + VERSION_NAME: identity.version, + }), + ); + const unsigned = join(build, "app-unsigned.apk"); + mustRun(join(buildTools, "aapt2"), [ + "link", + "-o", + unsigned, + "--manifest", + manifest, + "-I", + androidJar, + "-A", + assets, + "--min-sdk-version", + String(toolchain.android.apiLevel), + "--target-sdk-version", + String(toolchain.android.apiLevel), + compiled, + ]); + mustRun("zip", ["-q", "-r", unsigned, "classes.dex", "lib"], staging); + const aligned = join(build, "app-aligned.apk"); + mustRun(join(buildTools, "zipalign"), ["-f", "-p", "4", unsigned, aligned]); + ensureKeystore(); + dockerJava([ + `/android-sdk/build-tools/${toolchain.android.buildToolsVersion}/apksigner`, + "sign", + "--ks", + `/signing/${keystoreName}`, + "--ks-key-alias", + "androiddebugkey", + "--ks-pass", + "pass:android", + "--key-pass", + "pass:android", + "--min-sdk-version", + String(toolchain.android.apiLevel), + "--v1-signing-enabled", + "true", + "--v2-signing-enabled", + "false", + "--v3-signing-enabled", + "false", + "--v4-signing-enabled", + "false", + "--out", + "/build/app-signed.apk", + "/build/app-aligned.apk", + ]); + mkdirSync(dirname(appOutput), { recursive: true }); + copyFileSync(join(build, "app-signed.apk"), appOutput); + const signature = dockerJava([ + `/android-sdk/build-tools/${toolchain.android.buildToolsVersion}/apksigner`, + "verify", + "--verbose", + "--print-certs", + "--min-sdk-version", + String(toolchain.android.apiLevel), + "/build/app-signed.apk", + ]); + const badging = mustRun(join(buildTools, "aapt"), ["dump", "badging", appOutput]); + return { signature, badging }; +} + +function resetBuild(): void { + rmSync(build, { recursive: true, force: true }); + mkdirSync(join(staging, "lib", toolchain.android.abi), { recursive: true }); +} + +function buildRustCore(): string { + const rustTarget = join(build, "rust"); + mustRun( + "rustup", + [ + "run", + toolchain.rust.toolchain, + "cargo", + "build", + "--release", + "--locked", + "--target", + toolchain.rust.target, + "--features", + "bare-platform", + "--target-dir", + rustTarget, + ], + join(repository, "engine/symbian"), + { + ...process.env, + CARGO_PROFILE_RELEASE_LTO: "false", + CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER: clang, + }, + ); + const library = join(rustTarget, toolchain.rust.target, "release/libpocketjs_symbian_core.a"); + if (!existsSync(library)) { + throw new Error(`${LABEL}: Rust core archive is absent: ${library}`); + } + return library; +} + +function buildQuickJs(): string { + const objects = join(build, "objects/quickjs"); + mkdirSync(objects, { recursive: true }); + const flags = [ + "-std=gnu11", + "-O2", + "-fPIC", + "-funsigned-char", + "-fno-strict-aliasing", + "-ffunction-sections", + "-fdata-sections", + "-D_GNU_SOURCE", + `-DCONFIG_VERSION="${toolchain.quickjs.version}"`, + `-I${quickJs.source}`, + ]; + const objectPaths: string[] = []; + for (const source of ["cutils.c", "dtoa.c", "libregexp.c", "libunicode.c", "quickjs.c"]) { + const object = join(objects, source.replace(/\.c$/, ".o")); + mustRun(clang, [...flags, "-c", join(quickJs.source, source), "-o", object]); + objectPaths.push(object); + } + const staticFunctions = join(objects, "static-functions.o"); + mustRun(clang, [...flags, "-c", quickJs.staticFunctions, "-o", staticFunctions]); + objectPaths.push(staticFunctions); + const library = join(build, "libquickjs.a"); + mustRun(join(llvm, "llvm-ar"), ["rcs", library, ...objectPaths]); + return library; +} + +function buildNativeLibrary(bundle: GuestBundle, quickJsLibrary: string, coreLibrary: string): string { + const objects = join(build, "objects"); + const cFlags = [ + "-std=gnu11", + "-Os", + "-fPIC", + "-fno-strict-aliasing", + "-ffunction-sections", + "-fdata-sections", + "-fvisibility=hidden", + "-Wall", + "-Wextra", + "-Werror", + "-Wno-unused-parameter", + ]; + const portableRuntime = join(objects, "pocket_runtime.o"); + mustRun(clang, [ + ...cFlags, + `-DPOCKETJS_TARGET_ID="${bundle.inputs.target}"`, + `-DPOCKETJS_HOST_ABI=${bundle.inputs.hostAbi}`, + `-DPOCKET_RASTER_DENSITY=${bundle.inputs.viewport.rasterDensity}`, + `-I${join(repository, "hosts/iphone2g")}`, + `-I${quickJs.source}`, + "-c", + join(repository, "hosts/iphone2g/pocket_runtime.c"), + "-o", + portableRuntime, + ]); + const androidRuntime = join(objects, "android_runtime.o"); + mustRun(clang, [ + ...cFlags, + `-DPOCKET_LOGICAL_WIDTH=${bundle.inputs.viewport.logical[0]}`, + `-DPOCKET_LOGICAL_HEIGHT=${bundle.inputs.viewport.logical[1]}`, + `-I${join(repository, "hosts/iphone2g")}`, + "-c", + join(appHost, "jni/runtime.c"), + "-o", + androidRuntime, + ]); + const sharedObjects = ["pocket_input", "rust_eh_personality"].map((name) => { + const object = join(objects, `${name}.o`); + mustRun(clang, [ + ...cFlags, + `-I${join(repository, "hosts/iphone2g")}`, + "-c", + join(repository, `hosts/iphone2g/${name}.c`), + "-o", + object, + ]); + return object; + }); + const nativeLibrary = join(staging, "lib", toolchain.android.abi, "libpocketjs.so"); + /* No -landroid: the library needs nothing beyond GLESv2/log/dl/m/c, and + * --no-undefined turns any missing native symbol into a link failure. */ + mustRun(clang, [ + "-shared", + "-Wl,--build-id=none", + "-Wl,--gc-sections", + "-Wl,--exclude-libs,ALL", + "-Wl,--no-undefined", + androidRuntime, + portableRuntime, + ...sharedObjects, + quickJsLibrary, + coreLibrary, + "-o", + nativeLibrary, + "-lGLESv2", + "-llog", + "-ldl", + "-lm", + ]); + return nativeLibrary; +} + +function buildApp(): void { + requireToolchain(); + const bundle = readGuestBundle(guest); + resetBuild(); + const coreLibrary = buildRustCore(); + const quickJsLibrary = buildQuickJs(); + const nativeLibrary = buildNativeLibrary(bundle, quickJsLibrary, coreLibrary); + compileActivity(); + + const assets = join(build, "assets"); + mkdirSync(assets, { recursive: true }); + copyFileSync(bundle.javaScript, join(assets, "app.js")); + copyFileSync(bundle.pack, join(assets, "app.pak")); + const identity = packageIdentity(bundle.inputs.app); + const resources = join(build, "resources"); + cpSync(join(appHost, "res"), resources, { recursive: true }); + writeFileSync( + join(resources, "values/strings.xml"), + renderTemplate(readFileSync(join(appHost, "res/values/strings.xml"), "utf8"), { + /* Android string resources also need apostrophes escaped. */ + TITLE: xmlEscape(identity.title).replace(/'/g, "\\'"), + }), + ); + mkdirSync(join(resources, "drawable"), { recursive: true }); + copyFileSync( + join(repository, "assets/images/logo.png"), + join(resources, "drawable/icon.png"), + ); + const { signature, badging } = packageApk(identity, resources, assets); + for (const marker of [ + `package: name='${identity.packageId}' versionCode='${identity.versionCode}' versionName='${identity.version}'`, + `sdkVersion:'${toolchain.android.apiLevel}'`, + ]) { + if (!badging.includes(marker)) { + throw new Error(`${LABEL}: APK badging is missing ${marker}`); + } + } + const receipt = { + schema: 1, + toolchain: toolchain.toolchainVersion, + planHash: bundle.plan.planHash, + package: identity, + target: bundle.inputs.target, + hostAbi: bundle.inputs.hostAbi, + viewport: bundle.inputs.viewport, + apk: { + path: toolchain.app.output, + bytes: readFileSync(appOutput).byteLength, + sha256: sha256File(appOutput), + }, + guest: { + javaScript: sha256File(bundle.javaScript), + pack: sha256File(bundle.pack), + }, + nativeLibrary: { + bytes: readFileSync(nativeLibrary).byteLength, + sha256: sha256File(nativeLibrary), + elf: mustRun(readelf, ["-h", "-A", "-d", nativeLibrary]), + }, + quickjs: { + version: toolchain.quickjs.version, + revision: toolchain.quickjs.revision, + }, + rust: toolchain.rust, + signature, + badging, + }; + const receiptPath = join(dirname(appOutput), "pocketjs-blackberry-classic.receipt.json"); + writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`); + console.log(`${LABEL}: Hero APK -> ${appOutput}`); + console.log(`SHA-256: ${receipt.apk.sha256}`); + console.log(`Receipt: ${receiptPath}`); +} + +switch (command) { + case "doctor": + doctor(); + break; + case "setup": + await setup(); + break; + case "build-demo": + buildGuestBundle(guest); + break; + case "build-app": + buildApp(); + break; + case "build": + buildGuestBundle(guest); + buildApp(); + break; + default: + throw new Error( + "usage: bun tools/blackberry-android.ts ", + ); +} diff --git a/tools/blackberry-classic-profile.ts b/tools/blackberry-classic-profile.ts new file mode 100644 index 00000000..5d562b2a --- /dev/null +++ b/tools/blackberry-classic-profile.ts @@ -0,0 +1,82 @@ +import { + POCKET_CAPABILITIES, + definePlatformContractRegistry, + defineTargetRegistry, +} from "../contracts/spec/platforms.ts"; +import type { ResolvedBuildPlan } from "../framework/src/manifest/plan.ts"; +import { validateAndResolveBuildPlan } from "../framework/src/manifest/resolve.ts"; + +/** + * Private exact-device profiles for the BlackBerry Classic (SQC100). + * + * The Classic has two PocketJS hosts that differ only below the QuickJS + * bridge: `hosts/blackberry-qnx` is a BlackBerry 10 Core Native application, + * `hosts/blackberry-android` runs inside the BlackBerry 10 Android Runtime. + * Both present the same 720×720 panel, take the same keyboard, trackpad, and + * touch input through the portable button and touch contracts, and mount the + * same guest bundle shape, so they share one display and capability contract + * and one host ABI. They are registered as two targets because the target id + * is compiled into the guest and the native host and checked at boot. + */ +export const BLACKBERRY_QNX_DEV_TARGET_ID = "blackberry-qnx-dev"; +export const BLACKBERRY_ANDROID_DEV_TARGET_ID = "blackberry-android-dev"; +export type BlackBerryClassicTargetId = + | typeof BLACKBERRY_QNX_DEV_TARGET_ID + | typeof BLACKBERRY_ANDROID_DEV_TARGET_ID; + +export const BLACKBERRY_CLASSIC_HOST_ABI = 9; +export const BLACKBERRY_CLASSIC_LOGICAL_VIEWPORT = [360, 360] as const; +export const BLACKBERRY_CLASSIC_PHYSICAL_VIEWPORT = [720, 720] as const; +export const BLACKBERRY_CLASSIC_RASTER_DENSITY = 2; + +const CLASSIC_DISPLAY = { + physicalViewport: BLACKBERRY_CLASSIC_PHYSICAL_VIEWPORT, + logicalViewports: [BLACKBERRY_CLASSIC_LOGICAL_VIEWPORT], + presentations: ["native"], + rasterDensity: BLACKBERRY_CLASSIC_RASTER_DENSITY, +} as const; + +const CLASSIC_CAPABILITIES = [ + "input.buttons", + "input.touch", + "text.glyphs.baked", +] as const; + +export const BLACKBERRY_CLASSIC_DEV_CONTRACTS = definePlatformContractRegistry( + POCKET_CAPABILITIES, + defineTargetRegistry({ + [BLACKBERRY_QNX_DEV_TARGET_ID]: { + hostAbi: BLACKBERRY_CLASSIC_HOST_ABI, + platform: "blackberry10-qnx", + form: "takeover", + display: CLASSIC_DISPLAY, + capabilities: CLASSIC_CAPABILITIES, + }, + [BLACKBERRY_ANDROID_DEV_TARGET_ID]: { + hostAbi: BLACKBERRY_CLASSIC_HOST_ABI, + platform: "blackberry10-android", + form: "takeover", + display: CLASSIC_DISPLAY, + capabilities: CLASSIC_CAPABILITIES, + }, + }), +); + +export function resolveBlackBerryClassicBuildPlan( + input: unknown, + target: BlackBerryClassicTargetId, +): ResolvedBuildPlan { + const resolution = validateAndResolveBuildPlan( + input, + { target }, + BLACKBERRY_CLASSIC_DEV_CONTRACTS, + ); + if (!resolution.ok) { + throw new Error( + `pocket blackberry-classic: manifest did not resolve for ${target}: ${resolution.diagnostics + .map((diagnostic) => `${diagnostic.path || "/"}: ${diagnostic.message}`) + .join("; ")}`, + ); + } + return resolution.plan; +} diff --git a/tools/blackberry-qnx.ts b/tools/blackberry-qnx.ts new file mode 100644 index 00000000..6e5a5b0b --- /dev/null +++ b/tools/blackberry-qnx.ts @@ -0,0 +1,591 @@ +import { createHash, randomBytes } from "node:crypto"; +import { + copyFileSync, + cpSync, + existsSync, + lstatSync, + mkdirSync, + readFileSync, + readdirSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { homedir } from "node:os"; +import { basename, dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import type { HostBuildInputs } from "../framework/src/manifest/host-build-inputs.ts"; +import { + BLACKBERRY_QNX_DEV_TARGET_ID, + resolveBlackBerryClassicBuildPlan, +} from "./blackberry-classic-profile.ts"; +import { + buildGuestBundle, + ensureQuickJsCheckout, + type GuestBundleRequest, + mustRunCommand, + packageIdentity, + printCheck, + quickJsCheckoutStatus, + readGuestBundle, + renderTemplate, + runCommand, + sha256File, + xmlEscape, +} from "./native-host-build.ts"; + +const LABEL = "PocketJS BlackBerry QNX"; +const repository = fileURLToPath(new URL("..", import.meta.url)); +const command = Bun.argv[2] ?? "doctor"; +const toolchain = JSON.parse( + readFileSync( + join(repository, "tools/cli/blackberry-qnx-toolchain.json"), + "utf8", + ), +) as { + readonly toolchainVersion: string; + readonly cachePath: string; + readonly image: { + readonly name: string; + readonly digest: string; + readonly platform: string; + }; + readonly qnx: { + readonly apiLevel: string; + readonly hostVersion: string; + readonly compiler: string; + readonly architecture: string; + readonly dynamicLoader: string; + }; + readonly quickjs: { + readonly version: string; + readonly repository: string; + readonly revision: string; + }; + readonly rust: { + readonly toolchain: string; + readonly target: string; + }; + readonly app: { + readonly manifest: string; + readonly binary: string; + readonly bar: string; + }; +}; + +const image = `${toolchain.image.name}@${toolchain.image.digest}`; +const cache = join(homedir(), ".cache/pocket-stack", toolchain.cachePath); +const quickJsRoot = join(cache, "sources/quickjs-rs"); +const nativeBuild = join(repository, ".pocket-build/blackberry-qnx/runtime"); +const rustTarget = join(cache, "build/rust-target"); +const outputBar = join(repository, toolchain.app.bar); +const outputReceipt = join(dirname(outputBar), "build-receipt.json"); +const rustTargetSpec = join(repository, toolchain.rust.target); +const rustTargetName = basename(toolchain.rust.target, ".json"); +const guest: GuestBundleRequest = { + label: LABEL, + repository, + target: BLACKBERRY_QNX_DEV_TARGET_ID, + resolvePlan: (manifest) => + resolveBlackBerryClassicBuildPlan(manifest, BLACKBERRY_QNX_DEV_TARGET_ID), + manifestPath: join(repository, toolchain.app.manifest), + planPath: join(repository, ".pocket/blackberry-qnx/blackberry-classic.plan.json"), + outputDirectory: join(repository, "dist/blackberry-qnx/guest"), +}; + +function run(program: string, args: readonly string[]) { + return runCommand(program, args, repository); +} + +function mustRun( + program: string, + args: readonly string[], + cwd = repository, + env: NodeJS.ProcessEnv = process.env, +): string { + return mustRunCommand(LABEL, program, args, cwd, env); +} + +function imagePresent(): boolean { + return run("docker", ["image", "inspect", image]).exitCode === 0; +} + +function doctor(): void { + const docker = run("docker", ["version", "--format", "{{.Client.Version}}"]).exitCode === 0; + const rust = run("rustup", ["run", toolchain.rust.toolchain, "rustc", "--version"]); + const quickjs = quickJsCheckoutStatus(quickJsRoot, toolchain.quickjs); + const checks = [ + printCheck("Docker", docker, "docker client and daemon"), + printCheck("pinned BBNDK image", imagePresent(), image), + printCheck( + "Rust nightly", + rust.exitCode === 0, + rust.stdout.trim() || toolchain.rust.toolchain, + ), + printCheck("pinned QuickJS", quickjs.ok, quickjs.detail), + printCheck("QNX Rust target", existsSync(rustTargetSpec), rustTargetSpec), + ]; + if (imagePresent()) { + const qnx = run("docker", [ + "run", + "--rm", + "--network", + "none", + "--platform", + toolchain.image.platform, + "--entrypoint", + "/bin/bash", + image, + "-lc", + `qcc -V 2>&1 | grep -q '${toolchain.qnx.compiler}' && ` + + `blackberry-nativepackager -version 2>&1 | grep -q 'version 1.11'`, + ]); + checks.push( + printCheck( + "QNX compiler and BAR packager", + qnx.exitCode === 0, + `${toolchain.qnx.compiler}; blackberry-nativepackager 1.11`, + ), + ); + } + if (checks.some((ok) => !ok)) process.exitCode = 1; + else console.log(`[ok] toolchain: ${toolchain.toolchainVersion}`); +} + +function ensureImage(): void { + if (imagePresent()) return; + mustRun("docker", ["pull", "--platform", toolchain.image.platform, image]); +} + +function setup(): void { + ensureImage(); + ensureQuickJsCheckout(LABEL, quickJsRoot, toolchain.quickjs); + doctor(); +} + +function buildRustCore(): string { + mkdirSync(rustTarget, { recursive: true }); + mustRun( + "rustup", + [ + "run", + toolchain.rust.toolchain, + "cargo", + "build", + "--release", + "--locked", + "--features", + "bare-platform", + "--target", + rustTargetSpec, + "-Z", + "json-target-spec", + "-Z", + "build-std=core,alloc,compiler_builtins", + "-Z", + "build-std-features=compiler-builtins-mem", + ], + join(repository, "engine/symbian"), + { + ...process.env, + CARGO_PROFILE_RELEASE_LTO: "false", + CARGO_TARGET_DIR: rustTarget, + }, + ); + const library = join( + rustTarget, + `${rustTargetName}/release/libpocketjs_symbian_core.a`, + ); + if (!existsSync(library)) { + throw new Error(`${LABEL}: Rust core archive is absent: ${library}`); + } + return library; +} + +function dockerBuild(buildId: string, inputs: HostBuildInputs): void { + const uid = process.getuid?.() ?? 1000; + const gid = process.getgid?.() ?? 1000; + mustRun("docker", [ + "run", + "--rm", + "--network", + "none", + "--platform", + toolchain.image.platform, + "--user", + `${uid}:${gid}`, + "-e", + "HOME=/tmp", + "-e", + `POCKET_BUILD_ID=${buildId}`, + "-e", + `POCKETJS_TARGET_ID=${inputs.target}`, + "-e", + `POCKETJS_HOST_ABI=${inputs.hostAbi}`, + "-e", + `POCKET_RASTER_DENSITY=${inputs.viewport.rasterDensity}`, + "-e", + `POCKET_LOGICAL_WIDTH=${inputs.viewport.logical[0]}`, + "-e", + `POCKET_LOGICAL_HEIGHT=${inputs.viewport.logical[1]}`, + "-e", + `QNX_COMPILER=${toolchain.qnx.compiler}`, + "-e", + `QUICKJS_VERSION=${toolchain.quickjs.version}`, + "-v", + `${repository}:/repo:ro`, + "-v", + `${nativeBuild}:/build`, + "--entrypoint", + "/bin/bash", + image, + "/repo/tools/blackberry-qnx/build.sh", + ]); +} + +function readBarEntry(bar: string, entry: string): Buffer { + const result = Bun.spawnSync({ + cmd: ["unzip", "-p", bar, entry], + cwd: repository, + stdout: "pipe", + stderr: "pipe", + }); + if (result.exitCode !== 0) { + throw new Error( + `${LABEL}: cannot read ${entry} from BAR: ${result.stderr.toString().trim()}`, + ); + } + return Buffer.from(result.stdout); +} + +function buildRuntime(): void { + if (!imagePresent()) { + throw new Error( + `${LABEL}: pinned BBNDK image is absent; run \`bun blackberry-qnx setup\``, + ); + } + ensureQuickJsCheckout(LABEL, quickJsRoot, toolchain.quickjs); + const bundle = readGuestBundle(guest); + const coreLibrary = buildRustCore(); + const buildId = randomBytes(16).toString("hex"); + rmSync(nativeBuild, { recursive: true, force: true }); + mkdirSync(join(nativeBuild, "staging"), { recursive: true }); + mkdirSync(dirname(outputBar), { recursive: true }); + + cpSync( + join(quickJsRoot, "libquickjs-sys"), + join(nativeBuild, "quickjs-rs/libquickjs-sys"), + { recursive: true }, + ); + mustRun("patch", [ + "-d", + join(nativeBuild, "quickjs-rs"), + "-p1", + "-i", + join(repository, "tools/blackberry-qnx/quickjs-qnx.patch"), + ]); + copyFileSync(coreLibrary, join(nativeBuild, "libpocketjs_symbian_core.a")); + copyFileSync(bundle.javaScript, join(nativeBuild, "staging/app.js")); + copyFileSync(bundle.pack, join(nativeBuild, "staging/app.pak")); + copyFileSync( + join(repository, "assets/images/logo.png"), + join(nativeBuild, "staging/icon.png"), + ); + const identity = packageIdentity(bundle.inputs.app); + writeFileSync( + join(nativeBuild, "staging/bar-descriptor.xml"), + renderTemplate( + readFileSync(join(repository, "hosts/blackberry-qnx/bar-descriptor.xml"), "utf8"), + { + ID: identity.packageId, + TITLE: xmlEscape(identity.title), + VERSION: identity.version, + BUILD_ID: identity.versionCode, + }, + ), + ); + + dockerBuild(buildId, bundle.inputs); + const builtBar = join(nativeBuild, "pocketjs-blackberry-classic-hero.bar"); + const executable = join(nativeBuild, `staging/${toolchain.app.binary}`); + const elf = readFileSync( + join(nativeBuild, "pocketjs-classic.readelf.txt"), + "utf8", + ); + const symbols = readFileSync( + join(nativeBuild, "pocketjs-classic.symbols.txt"), + "utf8", + ); + for (const marker of [ + "Machine: ARM", + toolchain.qnx.dynamicLoader, + "libbps.so.3", + "libscreen.so.1", + "libEGL.so.1", + "libGLESv2.so.1", + ]) { + if (!elf.includes(marker)) { + throw new Error(`${LABEL}: linked ELF is missing ${marker}`); + } + } + for (const symbol of [ + " main", + " pocket_runtime_boot", + " pocket_runtime_tick", + " ui_gl_render", + ]) { + if (!symbols.includes(symbol)) { + throw new Error(`${LABEL}: linked ELF is missing${symbol}`); + } + } + const manifest = readBarEntry(builtBar, "META-INF/MANIFEST.MF").toString("utf8"); + for (const marker of [ + "Package-Architecture: armle-v7", + "Application-Development-Mode: true", + "Entry-Point-Type: Qnx/Elf", + "Entry-Point-System-Actions: run_native", + `Package-Name: ${identity.packageId}`, + `Package-Version: ${identity.version}.${identity.versionCode}`, + "Archive-Asset-Name: native/app.js", + "Archive-Asset-Name: native/app.pak", + ]) { + if (!manifest.includes(marker)) { + throw new Error(`${LABEL}: BAR manifest is missing ${marker}`); + } + } + const embeddedExecutable = readBarEntry( + builtBar, + `native/${toolchain.app.binary}`, + ); + if ( + createHash("sha256").update(embeddedExecutable).digest("hex") !== + sha256File(executable) + ) { + throw new Error(`${LABEL}: BAR embedded a different native executable`); + } + + copyFileSync(builtBar, outputBar); + const receipt = { + schemaVersion: 1, + toolchainVersion: toolchain.toolchainVersion, + buildId, + hostContract: bundle.inputs, + package: identity, + compilerImage: image, + qnx: toolchain.qnx, + rustToolchain: toolchain.rust.toolchain, + rustTarget: toolchain.rust.target, + quickJsRevision: toolchain.quickjs.revision, + quickJsVersion: toolchain.quickjs.version, + quickJsPatchSha256: sha256File( + join(repository, "tools/blackberry-qnx/quickjs-qnx.patch"), + ), + guestJavaScriptSha256: sha256File(bundle.javaScript), + guestPackSha256: sha256File(bundle.pack), + coreLibrarySha256: sha256File(coreLibrary), + executableSha256: sha256File(executable), + executableBytes: lstatSync(executable).size, + barSha256: sha256File(outputBar), + barBytes: lstatSync(outputBar).size, + elf, + }; + writeFileSync(outputReceipt, `${JSON.stringify(receipt, null, 2)}\n`); + console.log(`${LABEL}: Hero BAR -> ${outputBar}`); + console.log(`SHA-256: ${receipt.barSha256}`); + console.log(`Receipt: ${outputReceipt}`); +} + +function build(): void { + buildGuestBundle(guest); + buildRuntime(); +} + +function deviceAddress(): string { + const address = process.env.POCKETJS_BLACKBERRY_DEVICE?.trim(); + if (!address) { + throw new Error( + `${LABEL}: set POCKETJS_BLACKBERRY_DEVICE to the Classic development IP`, + ); + } + return address; +} + +function deviceCredentials(): string[] { + const args = ["-device", deviceAddress()]; + const password = process.env.POCKETJS_BLACKBERRY_PASSWORD; + if (password !== undefined && password !== "") { + args.push("-password", password); + } + return args; +} + +/** + * The Classic's USB network function (vendor 0fca, cdc_ncm) on Linux. Other + * hosts return undefined and skip the route check below. + */ +function blackberryUsbInterface(): string | undefined { + const networkDevices = "/sys/class/net"; + if (!existsSync(networkDevices)) return undefined; + let fallback: string | undefined; + for (const name of readdirSync(networkDevices).sort()) { + const info = run("udevadm", [ + "info", + "--query=property", + join(networkDevices, name), + ]); + if ( + info.exitCode !== 0 || + !info.stdout.includes("ID_VENDOR_ID=0fca") || + !info.stdout.includes("ID_NET_DRIVER=cdc_ncm") + ) { + continue; + } + fallback ??= name; + const carrier = join(networkDevices, name, "carrier"); + if (existsSync(carrier) && readFileSync(carrier, "utf8").trim() === "1") { + return name; + } + } + return fallback; +} + +function requireDeviceRoute(): void { + const address = deviceAddress(); + if (!address.startsWith("169.254.")) return; + const usb = blackberryUsbInterface(); + if (!usb) return; + const route = run("ip", ["route", "get", address]); + if ( + route.exitCode === 0 && + route.stdout.includes(`dev ${usb}`) && + route.stdout.includes("src 169.254.") + ) { + return; + } + throw new Error( + `${LABEL}: ${usb} is the connected BlackBerry USB interface but has no link-local IPv4 route. ` + + `Run \`sudo ip address replace 169.254.0.2/16 dev ${usb}\`, then retry.`, + ); +} + +function runBlackBerryDeploy(args: readonly string[], mountArtifacts = false): string { + requireDeviceRoute(); + const uid = process.getuid?.() ?? 1000; + const gid = process.getgid?.() ?? 1000; + const dockerArgs = [ + "run", + "--rm", + "--network", + "host", + "--platform", + toolchain.image.platform, + "--user", + `${uid}:${gid}`, + "-e", + "HOME=/tmp", + ]; + if (mountArtifacts) { + dockerArgs.push("-v", `${dirname(outputBar)}:/artifacts:ro`); + } + dockerArgs.push( + "--entrypoint", + "/home/admin/bin/bbndk/host_10_3_1_12/linux/x86/usr/bin/blackberry-deploy", + image, + ...args, + ); + const result = run("docker", dockerArgs); + if (result.exitCode !== 0) { + const detail = [result.stdout.trim(), result.stderr.trim()] + .filter(Boolean) + .join("\n"); + throw new Error( + `${LABEL}: blackberry-deploy failed (${result.exitCode})${ + detail ? `:\n${detail}` : "" + }`, + ); + } + return result.stdout.trim(); +} + +function requireBar(): void { + if (!existsSync(outputBar)) { + throw new Error(`${LABEL}: Hero BAR is absent; run \`bun blackberry-qnx build\``); + } +} + +function manifestField(name: string): string { + requireBar(); + const manifest = readBarEntry(outputBar, "META-INF/MANIFEST.MF").toString("utf8"); + const match = manifest.match(new RegExp(`^${name}: (.+)$`, "m")); + if (!match) { + throw new Error(`${LABEL}: BAR manifest has no ${name}`); + } + return match[1].trim(); +} + +function deviceInfo(): void { + console.log( + runBlackBerryDeploy(["-listDeviceInfo", ...deviceCredentials()]), + ); +} + +function install(): void { + requireBar(); + console.log( + runBlackBerryDeploy( + [ + "-installApp", + "-launchApp", + ...deviceCredentials(), + "-package", + `/artifacts/${basename(outputBar)}`, + ], + true, + ), + ); +} + +function deviceStatus(): void { + console.log( + runBlackBerryDeploy([ + "-getFile", + "data/pocketjs-qnx.status", + "-", + ...deviceCredentials(), + "-package-name", + manifestField("Package-Name"), + "-package-id", + manifestField("Package-Id"), + ]), + ); +} + +switch (command) { + case "doctor": + doctor(); + break; + case "setup": + setup(); + break; + case "build-demo": + buildGuestBundle(guest); + break; + case "build-runtime": + buildRuntime(); + break; + case "build": + build(); + break; + case "device-info": + deviceInfo(); + break; + case "install": + install(); + break; + case "device-status": + deviceStatus(); + break; + default: + throw new Error( + "usage: bun tools/blackberry-qnx.ts ", + ); +} diff --git a/tools/blackberry-qnx/build.sh b/tools/blackberry-qnx/build.sh new file mode 100755 index 00000000..857a2c11 --- /dev/null +++ b/tools/blackberry-qnx/build.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${POCKET_BUILD_ID:?missing POCKET_BUILD_ID}" +: "${POCKETJS_TARGET_ID:?missing POCKETJS_TARGET_ID}" +: "${POCKETJS_HOST_ABI:?missing POCKETJS_HOST_ABI}" +: "${POCKET_RASTER_DENSITY:?missing POCKET_RASTER_DENSITY}" +: "${POCKET_LOGICAL_WIDTH:?missing POCKET_LOGICAL_WIDTH}" +: "${POCKET_LOGICAL_HEIGHT:?missing POCKET_LOGICAL_HEIGHT}" +: "${QNX_COMPILER:?missing QNX_COMPILER}" +: "${QUICKJS_VERSION:?missing QUICKJS_VERSION}" + +qcc="$QNX_HOST/usr/bin/qcc" +ar="$QNX_HOST/usr/bin/ntoarmv7-ar" +readelf="$QNX_HOST/usr/bin/ntoarmv7-readelf" +nm="$QNX_HOST/usr/bin/ntoarmv7-nm" +objects=/build/objects +quickjs_objects="$objects/quickjs" +staging=/build/staging +quickjs=/build/quickjs-rs/libquickjs-sys/embed/quickjs +static_functions=/build/quickjs-rs/libquickjs-sys/embed/static-functions.c + +mkdir -p "$quickjs_objects" "$staging" + +qnx_target=(-V"$QNX_COMPILER") +quickjs_flags=( + "${qnx_target[@]}" + -std=gnu11 + -O2 + -fPIC + -funsigned-char + -fno-strict-aliasing + -ffunction-sections + -fdata-sections + -D_GNU_SOURCE + -DCONFIG_VERSION=\""$QUICKJS_VERSION"\" + -I"$quickjs" + -Wno-unused-parameter +) + +quickjs_object_paths=() +for source in cutils.c dtoa.c libregexp.c libunicode.c quickjs.c; do + object="$quickjs_objects/${source%.c}.o" + "$qcc" "${quickjs_flags[@]}" -c "$quickjs/$source" -o "$object" + quickjs_object_paths+=("$object") +done +static_object="$quickjs_objects/static-functions.o" +"$qcc" "${quickjs_flags[@]}" -c "$static_functions" -o "$static_object" +quickjs_object_paths+=("$static_object") +"$ar" rcs /build/libquickjs.a "${quickjs_object_paths[@]}" + +first_party_flags=( + "${qnx_target[@]}" + -std=gnu11 + -Os + -fPIE + -fno-strict-aliasing + -ffunction-sections + -fdata-sections + -Wall + -Wextra + -Werror + -Wno-unused-parameter +) + +"$qcc" "${first_party_flags[@]}" \ + -DPOCKETJS_TARGET_ID=\""$POCKETJS_TARGET_ID"\" \ + -DPOCKETJS_HOST_ABI="$POCKETJS_HOST_ABI" \ + -DPOCKET_RASTER_DENSITY="$POCKET_RASTER_DENSITY" \ + -I/repo/hosts/iphone2g \ + -I"$quickjs" \ + -c /repo/hosts/iphone2g/pocket_runtime.c \ + -o "$objects/pocket_runtime.o" + +for shared in pocket_input rust_eh_personality; do + "$qcc" "${first_party_flags[@]}" \ + -I/repo/hosts/iphone2g \ + -c "/repo/hosts/iphone2g/$shared.c" \ + -o "$objects/$shared.o" +done + +"$qcc" "${first_party_flags[@]}" \ + -DPOCKET_BUILD_ID=\""$POCKET_BUILD_ID"\" \ + -DPOCKET_LOGICAL_WIDTH="$POCKET_LOGICAL_WIDTH" \ + -DPOCKET_LOGICAL_HEIGHT="$POCKET_LOGICAL_HEIGHT" \ + -I/repo/hosts/iphone2g \ + -c /repo/hosts/blackberry-qnx/main.c \ + -o "$objects/main.o" + +"$qcc" "${qnx_target[@]}" \ + -pie \ + -Wl,-z,relro \ + -Wl,-z,now \ + -Wl,--gc-sections \ + -Wl,--no-undefined \ + -o "$staging/pocketjs-classic" \ + "$objects/main.o" \ + "$objects/pocket_runtime.o" \ + "$objects/pocket_input.o" \ + "$objects/rust_eh_personality.o" \ + /build/libquickjs.a \ + /build/libpocketjs_symbian_core.a \ + -lbps \ + -lscreen \ + -lEGL \ + -lGLESv2 \ + -lm + +"$readelf" -h -l -A -d "$staging/pocketjs-classic" > /build/pocketjs-classic.readelf.txt +"$nm" -g "$staging/pocketjs-classic" > /build/pocketjs-classic.symbols.txt + +cd "$staging" +blackberry-nativepackager \ + -package /build/pocketjs-blackberry-classic-hero.bar \ + -devMode \ + -configuration Device-Release \ + bar-descriptor.xml diff --git a/tools/blackberry-qnx/quickjs-qnx.patch b/tools/blackberry-qnx/quickjs-qnx.patch new file mode 100644 index 00000000..187a22dc --- /dev/null +++ b/tools/blackberry-qnx/quickjs-qnx.patch @@ -0,0 +1,21 @@ +diff --git a/libquickjs-sys/embed/quickjs/quickjs.c b/libquickjs-sys/embed/quickjs/quickjs.c +index dc296a7..3796ead 100644 +--- a/libquickjs-sys/embed/quickjs/quickjs.c ++++ b/libquickjs-sys/embed/quickjs/quickjs.c +@@ -97,6 +97,6 @@ + #if !defined(__EMSCRIPTEN__) + #define CONFIG_ATOMICS + #endif +-#if defined(__PSP__) || defined(__vita__) ++#if defined(__PSP__) || defined(__vita__) || defined(__QNXNTO__) + #undef CONFIG_ATOMICS + #endif +@@ -2172,6 +2172,8 @@ static size_t js_def_malloc_usable_size(const void *ptr) + return 0; + #elif defined(__vita__) + return malloc_usable_size((void *)ptr); ++#elif defined(__QNXNTO__) ++ return 0; + #elif defined(__linux__) || defined(__GLIBC__) + return malloc_usable_size((void *)ptr); + #else diff --git a/tools/cli/blackberry-android-toolchain.json b/tools/cli/blackberry-android-toolchain.json new file mode 100644 index 00000000..c9ece863 --- /dev/null +++ b/tools/cli/blackberry-android-toolchain.json @@ -0,0 +1,68 @@ +{ + "schemaVersion": 1, + "toolchainVersion": "blackberry-android-api18-v1", + "cachePath": "android", + "android": { + "apiLevel": 18, + "platformVersion": "4.3.1", + "buildToolsVersion": "35.0.0", + "ndkVersion": "23.2.8568313", + "abi": "armeabi-v7a", + "clangTarget": "armv7a-linux-androideabi18", + "repository": "https://dl.google.com/android/repository/", + "packages": [ + { + "id": "platforms;android-18", + "path": "platforms/android-18", + "archives": { + "any": { + "asset": "android-18_r03.zip", + "sha1": "e6b09b3505754cbbeb4a5622008b907262ee91cb" + } + } + }, + { + "id": "build-tools;35.0.0", + "path": "build-tools/35.0.0", + "archives": { + "linux": { + "asset": "build-tools_r35_linux.zip", + "sha1": "2cfaa0bbb2336e9ec18ed3ecea84fa2e2af607bc" + }, + "darwin": { + "asset": "build-tools_r35_macosx.zip", + "sha1": "93ab8ce91230e067b5add4bfa79919c52b27f072" + } + } + }, + { + "id": "ndk;23.2.8568313", + "path": "ndk/23.2.8568313", + "archives": { + "linux": { + "asset": "android-ndk-r23c-linux.zip", + "sha1": "e5053c126a47e84726d9f7173a04686a71f9a67a" + }, + "darwin": { + "asset": "android-ndk-r23c-darwin.zip", + "sha1": "1fc65d8f6083f3f5cd01e0cf97c6adc10f4f076f" + } + } + } + ] + }, + "javaImage": "eclipse-temurin:17-jdk-jammy@sha256:29467857e8bde40ab1f7befecbda0ea764b95afec1cc7f89aa90f7a766577e19", + "quickjs": { + "version": "2026-06-04", + "repository": "https://github.com/pocket-stack/quickjs-rs.git", + "revision": "ba5bdd0dc013518768e76cd9e05cd30ed53dd35b" + }, + "rust": { + "toolchain": "nightly-2026-07-02", + "target": "armv7-linux-androideabi" + }, + "app": { + "manifest": "apps/blackberry-classic-demo/pocket.json", + "output": "dist/blackberry-android/pocketjs-blackberry-classic.apk" + } +} diff --git a/tools/cli/blackberry-qnx-toolchain.json b/tools/cli/blackberry-qnx-toolchain.json new file mode 100644 index 00000000..2e311c2a --- /dev/null +++ b/tools/cli/blackberry-qnx-toolchain.json @@ -0,0 +1,31 @@ +{ + "schemaVersion": 1, + "toolchainVersion": "blackberry-qnx-10.3.1.995-v1", + "cachePath": "blackberry-qnx", + "image": { + "name": "accupara/bbndk", + "digest": "sha256:91268df2ead23a6fa9c17b600ffc6e04bf6b38eaa97f6a48c2f59ffc221ae6d2", + "platform": "linux/amd64" + }, + "qnx": { + "apiLevel": "10.3.1.995", + "hostVersion": "10.3.1.12", + "compiler": "4.8.3,gcc_ntoarmv7le", + "architecture": "armle-v7", + "dynamicLoader": "/usr/lib/ldqnx.so.2" + }, + "quickjs": { + "version": "2026-06-04", + "repository": "https://github.com/pocket-stack/quickjs-rs.git", + "revision": "ba5bdd0dc013518768e76cd9e05cd30ed53dd35b" + }, + "rust": { + "toolchain": "nightly-2026-07-02", + "target": "hosts/blackberry-qnx/armv7-qnx-eabi.json" + }, + "app": { + "manifest": "apps/blackberry-classic-demo/pocket.json", + "binary": "pocketjs-classic", + "bar": "dist/blackberry-qnx/pocketjs-blackberry-classic-hero.bar" + } +} diff --git a/tools/native-host-build.ts b/tools/native-host-build.ts new file mode 100644 index 00000000..76e52ebf --- /dev/null +++ b/tools/native-host-build.ts @@ -0,0 +1,299 @@ +import { createHash } from "node:crypto"; +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { + extractHostBuildInputs, + type HostBuildInputs, +} from "../framework/src/manifest/host-build-inputs.ts"; +import type { ResolvedBuildPlan } from "../framework/src/manifest/plan.ts"; + +/** + * Build steps shared by native host tools that embed a PocketJS guest next to + * the QuickJS bridge: process helpers, the pinned QuickJS checkout, the guest + * bundle built from a resolved plan, and the platform package identity derived + * from that plan. Used by the BlackBerry Classic tools today. + */ + +export interface CommandResult { + readonly exitCode: number; + readonly stdout: string; + readonly stderr: string; +} + +export function runCommand( + program: string, + args: readonly string[], + cwd: string, + env: NodeJS.ProcessEnv = process.env, +): CommandResult { + const result = Bun.spawnSync({ + cmd: [program, ...args], + cwd, + env, + stdout: "pipe", + stderr: "pipe", + }); + return { + exitCode: result.exitCode, + stdout: result.stdout.toString(), + stderr: result.stderr.toString(), + }; +} + +export function mustRunCommand( + label: string, + program: string, + args: readonly string[], + cwd: string, + env: NodeJS.ProcessEnv = process.env, +): string { + const result = runCommand(program, args, cwd, env); + if (result.exitCode !== 0) { + const detail = [result.stdout.trim(), result.stderr.trim()] + .filter(Boolean) + .join("\n"); + throw new Error( + `${label}: ${program} ${args.join(" ")} failed (${result.exitCode})${ + detail ? `:\n${detail}` : "" + }`, + ); + } + return result.stdout.trim(); +} + +export function sha256File(path: string): string { + return createHash("sha256").update(readFileSync(path)).digest("hex"); +} + +export function printCheck(label: string, ok: boolean, detail: string): boolean { + console.log(`${ok ? "[ok]" : "[missing]"} ${label}: ${detail}`); + return ok; +} + +export interface QuickJsPin { + readonly version: string; + readonly repository: string; + readonly revision: string; +} + +export interface QuickJsCheckout { + readonly root: string; + /** `libquickjs-sys/embed/quickjs` — the C sources both hosts compile. */ + readonly source: string; + readonly staticFunctions: string; +} + +export function quickJsCheckout(root: string): QuickJsCheckout { + return { + root, + source: join(root, "libquickjs-sys/embed/quickjs"), + staticFunctions: join(root, "libquickjs-sys/embed/static-functions.c"), + }; +} + +/** The checkout is usable only at the pinned revision with a clean tree. */ +export function quickJsCheckoutStatus( + root: string, + pin: QuickJsPin, +): { ok: boolean; detail: string } { + if (!existsSync(join(root, ".git"))) { + return { ok: false, detail: root }; + } + const revision = runCommand("git", ["-C", root, "rev-parse", "HEAD"], root); + const changes = runCommand( + "git", + ["-C", root, "status", "--porcelain=v1", "--untracked-files=all"], + root, + ); + const versionPath = join(quickJsCheckout(root).source, "VERSION"); + const version = existsSync(versionPath) + ? readFileSync(versionPath, "utf8").trim() + : ""; + const ok = + revision.exitCode === 0 && + revision.stdout.trim() === pin.revision && + changes.exitCode === 0 && + changes.stdout.trim() === "" && + version === pin.version; + return { + ok, + detail: `${root} (${revision.stdout.trim() || "missing"}, ${version || "no VERSION"})`, + }; +} + +export function ensureQuickJsCheckout( + label: string, + root: string, + pin: QuickJsPin, +): void { + const status = quickJsCheckoutStatus(root, pin); + if (status.ok) return; + if (existsSync(root)) { + throw new Error( + `${label}: refusing to replace an unverified QuickJS directory: ${status.detail}`, + ); + } + mkdirSync(dirname(root), { recursive: true }); + mustRunCommand( + label, + "git", + ["clone", "--filter=blob:none", "--no-checkout", pin.repository, root], + dirname(root), + ); + mustRunCommand( + label, + "git", + ["-C", root, "checkout", "--detach", pin.revision], + root, + ); + const verified = quickJsCheckoutStatus(root, pin); + if (!verified.ok) { + throw new Error(`${label}: QuickJS verification failed: ${verified.detail}`); + } +} + +export interface GuestBundle { + readonly plan: ResolvedBuildPlan; + readonly inputs: HostBuildInputs; + readonly javaScript: string; + readonly pack: string; +} + +export interface GuestBundleRequest { + readonly label: string; + readonly repository: string; + /** The private target id the manifest must resolve against. */ + readonly target: string; + /** Resolves the manifest to the plan for `target` (a profile module's resolver). */ + readonly resolvePlan: (manifest: unknown) => ResolvedBuildPlan; + readonly manifestPath: string; + /** Where the resolved plan is written for `tools/build.ts --plan`. */ + readonly planPath: string; + readonly outputDirectory: string; +} + +function locateGuestBundle( + request: GuestBundleRequest, + plan: ResolvedBuildPlan, +): GuestBundle { + const inputs = extractHostBuildInputs(plan, { expectedTarget: request.target }); + return { + plan, + inputs, + javaScript: join(request.outputDirectory, `${inputs.appOutput}.js`), + pack: join(request.outputDirectory, `${inputs.appOutput}.pak`), + }; +} + +export function currentGuestPlan(request: GuestBundleRequest): ResolvedBuildPlan { + return request.resolvePlan(JSON.parse(readFileSync(request.manifestPath, "utf8"))); +} + +/** Resolves the manifest for the target and compiles app.js + app.pak. */ +export function buildGuestBundle(request: GuestBundleRequest): GuestBundle { + const plan = currentGuestPlan(request); + mkdirSync(dirname(request.planPath), { recursive: true }); + rmSync(request.outputDirectory, { recursive: true, force: true }); + mkdirSync(request.outputDirectory, { recursive: true }); + writeFileSync(request.planPath, `${JSON.stringify(plan, null, 2)}\n`); + mustRunCommand( + request.label, + process.execPath, + [ + join(request.repository, "tools/build.ts"), + `--plan=${request.planPath}`, + `--project-root=${request.repository}`, + `--outdir=${request.outputDirectory}`, + ], + request.repository, + ); + const bundle = locateGuestBundle(request, plan); + if (!existsSync(bundle.javaScript) || !existsSync(bundle.pack)) { + throw new Error(`${request.label}: guest build did not emit app.js and app.pak`); + } + console.log(`${request.label}: guest bundle -> ${request.outputDirectory}`); + return bundle; +} + +/** Reads a previously built bundle and rejects it when the manifest moved on. */ +export function readGuestBundle(request: GuestBundleRequest): GuestBundle { + if (!existsSync(request.planPath)) { + throw new Error(`${request.label}: resolved plan is absent; run build-demo first`); + } + const stored = JSON.parse(readFileSync(request.planPath, "utf8")) as ResolvedBuildPlan; + if (stored.planHash !== currentGuestPlan(request).planHash) { + throw new Error(`${request.label}: resolved plan is stale; rerun build-demo`); + } + const bundle = locateGuestBundle(request, stored); + if (!existsSync(bundle.javaScript) || !existsSync(bundle.pack)) { + throw new Error( + `${request.label}: guest JavaScript or pack is absent; rerun build-demo`, + ); + } + return bundle; +} + +/** + * Platform package identity derived from the plan's manifest identity, so a + * host never carries a second hand-written copy of the app id or version. + */ +export interface PackageIdentity { + /** The manifest id with `-` replaced by `_` — a valid Android package name + * and BAR id; every other character must already be a legal segment. */ + readonly packageId: string; + /** The manifest version verbatim (Android versionName, BAR versionNumber). */ + readonly version: string; + /** `major * 1_000_000 + minor * 1_000 + patch` — a monotonic integer for + * Android versionCode and the BAR buildId, derived from the same version. */ + readonly versionCode: number; + readonly title: string; +} + +export function packageIdentity(app: HostBuildInputs["app"]): PackageIdentity { + const packageId = app.id.replace(/-/g, "_"); + if (!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+$/.test(packageId)) { + throw new Error( + `native host build: app id ${app.id} does not map onto a platform package name`, + ); + } + const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(app.version); + if (!match) { + throw new Error(`native host build: app version ${app.version} is not major.minor.patch`); + } + const [major, minor, patch] = match.slice(1, 4).map(Number); + if (minor >= 1000 || patch >= 1000 || major >= 2000) { + throw new Error( + `native host build: app version ${app.version} exceeds the numeric version code range`, + ); + } + return { + packageId, + version: app.version, + versionCode: major * 1_000_000 + minor * 1_000 + patch, + title: app.title, + }; +} + +/** + * Fills `@POCKET_NAME@` placeholders in a platform descriptor template and + * rejects a template whose placeholders are not all known. + */ +export function renderTemplate( + template: string, + values: Readonly>, +): string { + const rendered = template.replace(/@POCKET_([A-Z_]+)@/g, (token, name: string) => { + if (!(name in values)) throw new Error(`native host build: template has no value for ${token}`); + return String(values[name]); + }); + return rendered; +} + +/** XML attribute/text escaping for values rendered into descriptors. */ +export function xmlEscape(text: string): string { + return text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} diff --git a/tools/test.ts b/tools/test.ts index bbdb6ee5..3e1381cb 100644 --- a/tools/test.ts +++ b/tools/test.ts @@ -50,6 +50,8 @@ const SUITE: readonly Stage[] = [ "tests/iphone4s-profile.test.ts", "tests/ipodtouch-profile.test.ts", "tests/meizu-m8-profile.test.ts", + "tests/blackberry-classic.test.ts", + "tests/pocket-input.test.ts", "tests/ios-profile.test.ts", "tests/iphone2g-device-contract.test.ts", "tests/iphone2g-toolchain.test.ts", From 5d292a3d5ef2121f71511131db915228270b41f8 Mon Sep 17 00:00:00 2001 From: HalfSweet Date: Wed, 19 Aug 2026 16:00:42 +0800 Subject: [PATCH 2/2] docs(blog): PocketJS on the BlackBerry Classic --- .../blog/blackberry-classic-hero-720.png | Bin 0 -> 227384 bytes site/content/blog/blackberry-classic.md | 491 ++++++++++++++++++ site/nav.ts | 8 + 3 files changed, 499 insertions(+) create mode 100644 site/assets/blog/blackberry-classic-hero-720.png create mode 100644 site/content/blog/blackberry-classic.md diff --git a/site/assets/blog/blackberry-classic-hero-720.png b/site/assets/blog/blackberry-classic-hero-720.png new file mode 100644 index 0000000000000000000000000000000000000000..9f8b4bd0ecd8c03a4f598f01a18b99d0ab9e24d9 GIT binary patch literal 227384 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

=AJH*6f>gG5H~^&c@pDG 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

wT0yu<^B?nCj7HeOkl(w)svv6(ZQvz+> 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≺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 literal 0 HcmV?d00001 diff --git a/site/content/blog/blackberry-classic.md b/site/content/blog/blackberry-classic.md new file mode 100644 index 00000000..d6e2db56 --- /dev/null +++ b/site/content/blog/blackberry-classic.md @@ -0,0 +1,491 @@ +A real BlackBerry Classic on a wooden desk, its square screen running the PocketJS Hero demo: a PocketJS header with 60 FPS / 42 NODES / 9 DRAWS counters and 'ONE RUST CORE · ONE JSX APP', the headline 'JSX on Classic.', body text 'Flexbox, springs and baked type — running as a native BlackBerry 10 app.', a blue 'CLICK OR TAP' button, 'Count: 4', and 'Reactive on real hardware.' Below the square screen are the physical QWERTY keyboard and the tool belt with its optical trackpad. + +

The Hero demo running as a native BlackBerry 10 application on a real Classic — the square 720×720 screen above the tool belt and the physical keyboard.

+ +The BlackBerry Classic is the only machine we have ported to that will run the same PocketJS guest through **two completely different native stacks**. + +One is QNX-native: a BlackBerry 10 Core Native application that asks `libscreen` for a window, gets an OpenGL ES 2 context through EGL, and drives every frame from a BPS event loop. The other is the Android Runtime: an Android 4.3 (API 18) APK, a `GLSurfaceView` Activity sitting on one `armeabi-v7a` JNI library. + +The two paths diverge entirely **below** the QuickJS bridge. Above it, they mount the same guest bundle, the same no-std Rust UI core (with its GLES2 DrawList backend), the same QuickJS bridge (`hosts/iphone2g/pocket_runtime.c` — the filename is a historical accident; the iPhone 2G/4S and Meizu M8 hosts link it too), against one private device profile: **720×720 physical pixels, 360×360 logical, raster density 2, a fixed 60 Hz simulation clock, `input.buttons` + `input.touch` + `text.glyphs.baked`**. + +They differ in only three things: how the process is packaged, how it is installed, and how it is fed input. + +This post is about those three things, and what we ran into along the way: a phone that took security to the point where, in 2026, native development is nearly impossible; a community root project that pushed that door back open; the "everything is the network" way a BlackBerry talks to a computer; and a genuinely archaeological pair of protocols — **management and updates over HTTPS + CGI + XML/form-data, file management over SMB/CIFS** — so you can mount the phone's filesystem on your computer and manage it like local files. + +## The machine + + + BLACKBERRY CLASSIC · SQC100 / Q20 · DECEMBER 2014 + + + Screen3.5″ · 720×720 · ~294 ppi · 1:1 square + + SoCMSM8960 Snapdragon S4 Plus · 2×1.5 GHz Krait · Adreno 225 + + Memory2 GB RAM · 16 GB + microSD + + OSBlackBerry 10.3.3 · QNX-based (reports QNX 8.0.0) + + Inputphysical QWERTY + tool belt (optical trackpad + Menu/Back/Send/End) + + + PocketJS360×360 @density 2 → 720×720 · 60 Hz · input.buttons + input.touch + One device, one guest, two native stacks: QNX unsigned BAR (rooted) · Android Runtime v1-signed APK (stock) + + +The BlackBerry Classic (model SQC100, codename Q20) shipped in December 2014 as BlackBerry looking back in the all-touch era: it put the familiar physical shape from around 2011 back on. A full physical QWERTY keyboard, a navigation strip above it called the **tool belt** (Menu / Back / Send / End, with an optical trackpad in the middle), and a **square** screen. + +- **Screen**: 3.5 inches, **720×720**, about 294 ppi. The square screen is a BlackBerry keyboard-phone tradition — the display gives way to the keyboard, so its width equals its height. For us, square means the logical viewport is a square 360×360, scaled at raster density 2 to 720×720. +- **SoC**: Qualcomm Snapdragon S4 Plus **MSM8960**, dual-core 1.5 GHz Krait, Adreno 225 GPU. +- **Memory / storage**: 2 GB RAM, 16 GB storage, microSD slot. +- **OS**: BlackBerry 10.3, built on QNX; the unit we verified runs **10.3.3.3216**, the last generation of BB10 firmware. + +In the family of PocketJS targets the Classic is not weak — it has far more compute than the PSP or the Symbian E7. What makes it hard was never performance. It is **what you are allowed to do to get code onto it**. + +## Two stacks, one guest + +First, the two paths, side by side. + + + One guest · the fork is entirely below the QuickJS bridge + + + One guest bundle + Solid·JSX · no-std Rust core · GLES2 DrawList · QuickJS bridge + + + QNX-native host + Android Runtime host + + libscreen window + EGL / GLES2 + BPS event loop · navigator/screen + nativepackager -devMode → unsigned BAR + rooted Classicnative ELF process + GLSurfaceView Activity (API 18) + JNI .so · KeyEvent / MotionEvent + apksigner v1 → signed APK + stock ClassicAndroid runtime process + + + + + + + + 720×720 GLES2 present · portable button mask + touch snapshot + Above the QuickJS bridge: one core, renderer, fonts, reactivity — byte-for-byte identical + Two separate targets only because the target id is baked into guest and host and checked at boot. + + +| | QNX-native host (`hosts/blackberry-qnx`) | Android Runtime host (`hosts/blackberry-android`) | +| --- | --- | --- | +| Process | BlackBerry 10 Core Native ELF: `libscreen` window, EGL, OpenGL ES 2, BPS event loop | Android 4.3 (API 18) APK: a `GLSurfaceView` Activity over one `armeabi-v7a` JNI library | +| Package | **unsigned** development BAR (`blackberry-nativepackager -devMode`) | v1-signed APK | +| Install prerequisite | **a rooted Classic**: a stock device accepts an unsigned BAR only with a debug token, and the token-issuing service is gone | **a stock Classic**: with "allow other sources" enabled, BB10.3 sideloads APKs from the file manager | +| Input source | libscreen keyboard, multi-touch, `SCREEN_EVENT_JOYSTICK` trackpad events; navigator system keys | Android `KeyEvent`, touch `MotionEvent`, generic-motion / trackball events | +| Toolchain | a digest-pinned BBNDK Docker image (compile, package, deploy) | Android SDK Platform 18 + Build-Tools 35.0.0 + NDK r23c, unpacked at `setup`; JDK 17 in Docker | +| Device status | **verified on real hardware** | **no device result yet** | + +The one sentence that matters here: **they differ only below the QuickJS bridge**. + +`apps/blackberry-classic-demo` is the Hero wrapper both hosts build. The profile module (`tools/blackberry-classic-profile.ts`) registers `blackberry-qnx-dev` and `blackberry-android-dev` as two targets with an **identical display, capabilities, and host ABI (both 9)**. Why two targets and not one? Because the target id is compiled into the guest and into the native host and checked at boot — two different process paths each have to carry an identity that matches. + +The Rust core is `pocketjs-symbian-core` (under `engine/symbian` — another historical name): the no-std C-ABI build of `pocketjs-core` plus the GLES2 DrawList backend the Nokia E7, iPhone 2G/4S, and Meizu M8 hosts all link. Both Classic hosts build it with the `bare-platform` feature. The QNX side uses a hand-written target spec (`armv7-qnx-eabi.json`: ARMv7, VFPv3, soft-float ABI, PIC, `build-std`); the Android side uses the stock `armv7-linux-androideabi`. + +In other words, **the code this port actually adds is very thin**: two host processes (one `main.c`, one `PocketActivity.java` + `runtime.c`), one shared input state machine both of them feed (`pocket_input.c`), the glue for two toolchains, and one shared profile. The core, the renderer, the fonts, the reactivity — not a byte of it changed for BlackBerry. + +The guest side is thinner still — it is an ordinary Solid component that knows nothing about whether QNX or Android is underneath it: + +```tsx +import Hero from "../hero/app.tsx"; +import { reportAppAction } from "@pocketjs/framework/host"; + +// The same guest bundle is mounted by both Classic hosts; nothing in here is +// allowed to branch on which host is running it. +export default function BlackBerryClassicHero() { + return ( + reportAppAction("hero_press", count)} + /> + ); +} +``` + +That `reportAppAction("hero_press", …)` writes each press into a line of status inside the device sandbox — from a tap in TSX, to a record on the device, through an entire native stack, but the same code runs on both sides. + +## A phone that carved distrust into every layer + +BlackBerry's seriousness about security is the kind that stops a developer who just wants to run a demo dead in their tracks. + +On BB10, a native `.bar` application has only two legal ways onto a stock device: + +- **Release**: sign the BAR with keys issued by BlackBerry's signing authority (RDK/PBDT, later the BlackBerry ID token). Signing is online — you trade for it against BlackBerry's servers. +- **Development**: put the device in Development Mode and install a **debug token** — a credential signed by BlackBerry's servers, **bound to the device PIN and valid for 30 days**. Only then will the device run an unsigned `-devMode` BAR. When the token expires, unsigned apps stop running and you have to go ask the servers for a new one. + +The design was coherent for its time: even a developer's local debugging needs a time-limited, device-bound, officially blessed pass, and malware has almost nowhere to stand. But it has one fatal premise for its era — **BlackBerry's online services have to be alive**. + +They are not. BlackBerry gave notice back in September 2020: **after January 4, 2022**, the whole set of legacy services for BB7-and-earlier, BB10, and PlayBook OS 2.1-and-earlier would shut down. That did not just cut off data, calls, and texts — **the signing authority, debug-token issuance, BlackBerry ID, BlackBerry Link, and BlackBerry Blend all went with it**. + +So in 2026 a stock Classic is caught in a deadlock: to install an unsigned development BAR you need a debug token, and the server that issues tokens went dark four years ago. Release signing is the same — that path leads to a server that no longer exists. **Through official channels, you cannot install a single application you wrote onto a stock BlackBerry 10 device.** + +### The project that reopened the door + +Then, not long ago, someone pushed the door back open. + + is a project called **"BlackBerry 10 root and more"**, credited to **Oleksandr** (handle `bb10root`), with **guizmox** and **sw7ft** among those who contributed and supported it. It uses known vulnerabilities in BB10 to get **root** on the device and — the part that matters most to us — **bypasses the BAR signature check so unsigned `.bar` files install directly** (one of its routes exploits the `install_apk` command path skipping the bar signature check, plus packages with certain prefixes skipping verification). It has publicly verified 10.3.3.3216 — the firmware on the unit in our hands. + +Our QNX-native host reaches the device exactly this way. `blackberry-nativepackager -devMode` produces an unsigned BAR, `blackberry-deploy` pushes it to a rooted Classic, installs it, and launches it. No debug token, no signing authority, not a single living BlackBerry server anywhere in the loop. + +The people who do this kind of work usually get nothing back for it. They reverse-engineer a platform its own maker has sentenced to death, to serve a small group of people still tinkering with these old machines. And yet it is exactly that work that lets a square-screened 2014 phone run freshly written code in 2026. + +**A salute to the developers who put this work in.** Without bb10.root.sx the QNX half of this post would not exist at all — it would be stuck forever at "the build passes, but it installs on no real device." + +(The other half — the Android Runtime host — needs no root. BB10.3 already lets you sideload APKs from the file manager once "allow other sources" is on. That is one reason we keep both paths: one native path for rooted devices, one Android-compatibility path for stock ones.) + +## Everything is the network: how the phone talks to a computer + +Plug the Classic into USB expecting a storage disk, and what shows up on the computer instead is **a network adapter**. + +This is BlackBerry 10's default way of talking to a computer: the device's USB function enumerates as a CDC-NCM Ethernet adapter (USB vendor `0x0fca` — Research In Motion). In Development Mode the device puts itself at the link-local address **`169.254.0.1`** and the computer takes `169.254.0.2`. Everything after that — device info, installing apps, backup, OS updates, debugging — **all runs over that USB-Ethernet link, over HTTP/HTTPS**. ("Connect to Windows" mode switches the adapter to RNDIS so Windows' native driver can take over; the standard/Mac mode is CDC-NCM/ECM.) + +Our deploy tool follows exactly that path. On Linux, `tools/blackberry-qnx.ts` first uses `udevadm` to find the adapter whose vendor is `0fca` and whose driver is `cdc_ncm`, confirms it carries a `169.254.x` link-local route, and only then pushes the BAR; if the route is missing it simply prints `sudo ip address replace 169.254.0.2/16 dev …` for you to add. The `blackberry-deploy` that does the real work runs inside the BBNDK image with `--network host`, reaches the device's development service over `169.254.0.1`, and `-installApp -launchApp` installs and launches. + +**Debugging is the network, too.** Turn on Development Mode and the device starts a **`qconnDoor`** service **listening on TCP 4455** with challenge-response authentication; `blackberry-connect` / `blackberry-deploy` all connect to the device IP, and SSH runs on port 22 where only the unprivileged `devuser` account can log in. That underlying channel is a binary protocol over TCP with an RSA-1024 challenge and an AES-128-CBC session (in the reverse-engineered tooling the permission handshake is literally named `QCONNDOOR_PERMISSIONS`). No serial cable, no adb — to a computer, a BlackBerry is first of all a host on the network. + +## BlackBerry Link, and a little protocol archaeology + +To manage the files on the device, the official tool is **BlackBerry Link**. It is nice, but for us in 2026 on an Apple Silicon Mac it is basically a relic: + +- **Link only ships for Windows and macOS.** The macOS build stopped at **1.2.1 (April 2014)**, requires OS X 10.7+, and is **32-bit Intel**. Apple stopped running 32-bit binaries as of macOS Catalina (10.15) — so on today's macOS 26.6 on Apple Silicon **it simply will not launch**. The Windows build went a little further, to 1.2.3.56 (June 2014), and stopped there. +- **Even if you have an old machine that can run Link, its servers are gone.** Per BlackBerry's own EOL notice, after January 4, 2022 the "download system is no longer available," and Link/Blend/Desktop Manager/BlackBerry World were reduced to "limited functionality." Anything that depends on the official servers — downloads, updates — is dead. + +Fortunately the community left reverse-engineered tools behind. The archetype is **Sachesi** — a cross-platform tool by **Sacha Refshauge (GitHub `xsacha`)**, GPL-3.0, formerly "Dingleberry," open-sourced in May 2014. It searches, downloads, and extracts firmware; installs and uninstalls `.bar`; backs up and restores; wipes; reboots; reads device info — and it needs **no Development Mode**. It is written in Qt and still builds and runs from source today on Apple Silicon with a current Qt — which is how we read its source and saw the protocol clearly. + +And once you do see the protocol clearly, something interesting shows up: **BlackBerry uses two completely different protocols for "device management" and "file management."** + +### Management and updates: HTTPS + CGI + XML + +Device info, installing apps, backup, OS updates — this class of operation runs over an **HTTPS + CGI** interface: requests go to `/cgi-bin/*.cgi` on the device, and **every response is XML rooted at ``**. A few of the key endpoints: + +- `discovery.cgi` (plaintext HTTP:80): returns `` — PIN, model, `OsType`, `PlatformVersion`, `DeveloperModeEnabled`, and so on. +- `login.cgi` (HTTPS:443): a challenge-response login. The device answers with an `` (`Salt`, `Challenge`, iteration count `ICount`); the client runs **iterated SHA-512** (hashing `counter ∥ salt ∥ password` repeatedly, folding the challenge in on the last round) and sends the result back. +- `dynamicProperties.cgi`: `POST` a `Get Dynamic Properties=Get Dynamic Properties` form and get back the list of every application on the device (os/radio/application), the battery level, `HardwareID`, and more. +- `update.cgi`: this is how a `.bar` gets installed. First `POST` an `application/x-www-form-urlencoded` `mode=bar&size=` to start the install; the device answers ``; then the client POSTs the **raw bytes of the `.bar` directly as the body**, and the device drives it with a run of `` (each with a `` and a percentage), finishing with ``. + +Laid out, a single `.bar` install looks like this (endpoints, fields, and XML shapes taken from Sachesi's implementation): + +```text +# 1) Start the install: form-encoded, declaring the mode and total size +POST /cgi-bin/update.cgi Content-Type: application/x-www-form-urlencoded +mode=bar&size=4193095 + → + +# 2) POST the raw .bar bytes as the body (note: not multipart) +POST /cgi-bin/update.cgi?type=bar Content-Type: application/octet-stream +<…raw .bar bytes…> + +# 3) The device drives progress with a run of XML until it finishes + → InProgress42 + → InProgress88 + → Success +``` + +(One small correction so "form-data" doesn't mislead you: **the kickoff request is form-encoded, but the actual body is a raw octet-stream, not multipart.** The skeleton of "HTTPS + CGI + XML responses + a form kickoff" is right.) These tools also spoof their `User-Agent` as `QNXWebClient/1.0` and **deliberately ignore TLS certificate errors** — because the device uses a self-signed certificate. The login step (`login.cgi`) is a challenge-response: the device returns an `` carrying a `Salt` and iteration count, and the client hashes `counter ∥ salt ∥ password` with **iterated SHA-512**, folds the challenge in on the last round, and sends the result back. + +### File management: SMB/CIFS, mountable directly + +For "manage the files on the device," BlackBerry switches to a completely different protocol: **SMB/CIFS**. The device runs an SMB service (it identifies as **Samba 3.0.x** — and note, it runs on **QNX**, not Linux; people who sniffed the traffic years ago saw Samba and assumed Linux, which is wrong) listening on ports **139/445**, exporting a few shares: + +- **`media`**: internal user storage; +- **`removeable_sdcard`** (yes, the device misspells "removable"): present only when an SD card is inserted; +- **`certs`**: certificates. + +The username is set on the device under **Settings → Storage and Access → "Identification on Network"**, and the password is the **"Wi-Fi storage password"** there. It works over both USB and Wi-Fi. + +Which means something genuinely nice: **you can mount the BlackBerry's filesystem straight onto your computer and manage it like local files.** On macOS, `smb://169.254.0.1/media` in Finder connects; on Linux: + +```sh +sudo mount -t cifs //169.254.0.1/media /mnt/bb \ + -o user=,password=,vers=1.0,sec=ntlm +``` + +(Because the other end is SMB1-era Samba 3.0.37 and modern kernels disable SMB1 by default, you have to add `vers=1.0` explicitly.) A 2014 phone, and its filesystem is just a drive on your desktop. + +## MSC, and the MTP that was never chosen + +BlackBerry also supports USB Mass Storage (MSC), but sparingly: **it exposes only the external SD card as an MSC block device**, never the internal storage. + +And there is a knock-on limit: **once the SD card is enumerated as MSC, the SMB file management above can no longer mount that card.** The reason is not BlackBerry but MSC itself — **MSC is fundamentally a "dumb" block-device protocol** (Bulk-Only Transport + SCSI commands) that hands the computer **exclusive block-level ownership** of the whole device. The filesystem layer can have only one owner: either the computer has it mounted or the phone does, never both. So the moment MSC is on, the card has to be unmounted on the phone side, and the phone's apps — and the SMB path — can no longer see it. + +The period comparison is the interesting part. On most Android devices of that era, **MTP** was already the norm — and MTP works at the **file-object** layer, not the block layer, so a phone can expose files to the computer while it keeps reading and writing the same storage itself, and it can expose internal storage without switching to a block device. BlackBerry had a more modern option available and **did not pick MTP**, going instead with "SMB for internal storage + MSC for the SD card only." Why exactly, I could not find an official statement; but the result is that to fully manage this device's files you go over the network with SMB, not by plugging in USB as a thumb drive. + +## QNX's graphics, and a different design philosophy + +To understand why this phone is "everything is the network, everything is a service," you have to start with the QNX underneath it. + +QNX is a **microkernel** real-time operating system, built in 1980 by Quantum Software Systems (Dan Dodge and Gordon Bell) in Kanata, Canada (first commercial release in 1982, renamed QNX in 1984). It goes the opposite way from the **monolithic** kernels you know, Linux/XNU: **the kernel itself (`procnto`) is tiny**, doing only a few things — CPU scheduling, **synchronous message passing** (`MsgSend`/`MsgReceive`/`MsgReply`), interrupt redirection, timers. The filesystem, the network stack, device drivers, **and even the graphics** are all ordinary **user-space processes**. + +This has two consequences. First, **isolation**: when a driver crashes, only a user-space process crashed — it can be restarted, the kernel is untouched. This is exactly why QNX runs in cars, medical devices, industrial control, and routers — places that "can't die" (over 275 million vehicles run QNX today; it is exactly what RIM wanted — Harman bought QNX in 2004, RIM bought it from Harman in April 2010 — which is how there came to be a PlayBook and BB10). Second, **everything is a message**: opening a device, reading a file, asking for a display buffer — underneath, all of it is sending a message to some user-space **service process**. QNX registers those services as **pathnames** in one unified namespace (a resource manager), so it is very "Unix" at heart, but implemented as client-server message round-trips: + +```c +// Client: send a message, then block until the server replies. That is almost +// the whole of QNX IPC. +MsgSend(server_conn, &request, sizeof request, &reply, sizeof reply); + +// Server (in another process): receive, do the work, reply. +int rcvid = MsgReceive(channel, &request, sizeof request, NULL); +/* …do the work… (open/read/write on a device is exactly this round-trip) */ +MsgReply(rcvid, EOK, &reply, sizeof reply); +``` + +The `open()`/`read()` you write in POSIX get translated by the C library into a `MsgSend` like this, sent to the user-space service that registered the matching pathname. The kernel's only job is to move the message from one process to another — even the filesystem and the network card live outside it. + +Graphics is the same model. BB10's windowing and composition is handled by the **Screen graphics subsystem** (`libscreen`) — `screen` is itself a user-space service (it exposes pathnames under `/dev/screen`, exactly the resource-manager pattern) that owns the displays, the display pipelines, and composition. Applications are its clients: + +- An app creates a `screen_context_t`, a `screen_window_t`, allocates buffers (`screen_create_window_buffers`), and then either draws into them with the CPU and calls `screen_post_window`, or binds the buffers to EGL and draws with OpenGL ES — **our QNX host takes the latter**: `libscreen` window + EGL + GLES2. +- What actually stacks all the windows into the final image is the `screen` **compositor** service, using hardware display layers/overlays where it can and falling back to GPU composition when it can't. (One detail that happens to apply to us: in the official docs, when the whole screen has only one fullscreen application, Screen **bypasses composition entirely** — and our host is exactly one fullscreen takeover application.) +- And a very BB10 concept: **window groups** (`screen_create_window_group` / `screen_join_window_group`). One process can embed **another process's window** inside its own. Video, Cascades child windows, **and even the entire Android runtime's window** are composited into the picture this way — the system shell (the **navigator** process) owns top-level composition, and applications "join" its group. + + + QNX SCREEN · every window is an off-screen buffer; compositing is a service's job + + + Window group · navigator (shell) owns top-level composition · each client draws into its own buffer + + native app windowour QNX host · EGL/GLES2 + Cascades scenescene graph · own render thread + Android runtime windowall of Android = one QNX process + + + + + screen compositor service + owns displays & pipelines · composites via HW overlays or GPU · bypasses composition for one fullscreen app + + + 720×720 panelone final frame + Clients draw only into their own buffer; combining them into one screen is the screen service's job. + + +Our host takes the most direct path in there: ask `screen` for a window, bind its buffer to EGL, and then draw purely with GLES2. Condensed, the bring-up looks like this: + +```c +/* 1) Ask the screen service for a context; EGL gets a GLES2 context */ +screen_create_context(&screen_ctx, SCREEN_APPLICATION_CONTEXT); +egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); +eglInitialize(egl_display, NULL, NULL); +eglBindAPI(EGL_OPENGL_ES_API); +eglChooseConfig(egl_display, config_attrs, &config, 1, &n); +egl_context = eglCreateContext(egl_display, config, EGL_NO_CONTEXT, + (EGLint[]){ EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }); + +/* 2) Create the window, open our own window group, configure it as one + 720×720 double-buffered GLES2 target */ +screen_create_window(&screen_win, screen_ctx); +screen_create_window_group(screen_win, group_name); +screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_FORMAT, &format); +screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_USAGE, &(int){ SCREEN_USAGE_OPENGL_ES2 }); +screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, (int[]){ 720, 720 }); +screen_create_window_buffers(screen_win, 2); + +/* 3) Bind that window buffer as an EGL surface; from here it's just GLES2 */ +egl_surface = eglCreateWindowSurface(egl_display, config, screen_win, NULL); +eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); +eglSwapInterval(egl_display, 1); /* follow vsync */ +``` + +Notice the "window" is, all the way through, **a buffer you asked a service for** — not a slab of video memory the kernel handed you. That is exactly what the resource-manager model looks like when applied to graphics. + +In this architecture, events arrive through **BPS** (BlackBerry Platform Services): a C library that folds screen, navigator, sensors, and the rest into a **single event queue**. Our host uses it in the plainest possible way — `bps_initialize()`, register for screen and navigator events, then loop on `bps_get_event`. Window activate/deactivate, orientation, exit, and system keys all arrive as events from the navigator shell process. (System state lives in a separate mechanism, **PPS** — a pile of readable/writable "objects" under `/pps`, where writing publishes and reading subscribes.) + +Our main loop is plain, too — **drain everything currently queued, then run exactly one frame**: + +```c +while (!app_shutdown) { + int timeout = app_active ? 0 : -1; /* foreground: take without blocking; background: block for a wake */ + bps_get_event(&event, timeout); + handle_event(event); /* screen (touch/keys/trackpad) + navigator (lifecycle/system keys) */ + + if (app_active) { + do { /* drain everything still queued this instant… */ + bps_get_event(&event, 0); + handle_event(event); + } while (event != NULL); + render_frame(); /* …then advance exactly one 60 Hz tick, then eglSwapBuffers */ + } +} +``` + +This design philosophy is quite unlike the other mobile systems of the era: + +- **iOS / Android** are built on monolithic kernels (XNU / Linux), the display driver lives in the kernel, the app's main thread drives UIKit / the view system directly, and a system compositor (iOS's render server, Android's SurfaceFlinger) puts it all on screen. +- **QNX / BB10** breaks all of that into message round-trips between user-space services; graphics is "just another service," and cross-process **window-group composition** is a first-class citizen. It carries the **isolation-and-determinism** genes it built up in cars and medical devices straight into a phone. +- Even BB10's own native UI framework, **Cascades** (from TAT, the Swedish design house RIM acquired, built on Qt/QML), follows the same bent: **it draws on a separate rendering thread, over a retained scene graph**, so a busy app thread never stalls the animation. Together with bezel gestures, Peek/Flow, and Active Frames, BB10's interaction itself grows out of "the shell owns the edges, rendering owns its own thread." + +What is striking is how naturally PocketJS lands on this structure. Our core is already **one pure frame function that must return quickly**, with the host owning the event pump — the same shape QNX wants ("drain the events, run a frame, return"), as naturally as it did on Symbian's active object. QNX wants to be asked politely, and PocketJS's host only knows how to ask politely. + +### While we're here: the Android compatibility layer + +This is also why the Classic can have a second stack at all. BB10's **Android runtime** is essentially **an entire Android userspace (Dalvik + frameworks) running as a QNX process**; its window is composited into the picture by `screen` through the window groups above, alongside native apps. It grew up over the course of BB10: Android 2.3.3 on PlayBook OS 2.0, up to 4.2.2 on BB10.2 (from 10.2.1 you could also sideload `.apk` straight from the file manager, and native C/C++ apps were supported), and Android 4.3 (API 18) on BB10.3, which also preloaded the Amazon Appstore. There is no Google Play Services; earlier Android apps had to be repackaged as `.bar` first (the community called the tool `apk2bar`). + +Our Android host is exactly an **API-18 APK**, aimed at that 4.3 runtime. So the same guest reaches the screen two ways: one path is QNX-native, asking `screen` for a window directly; the other is an Android app, hosted by the Android runtime and then composited by `screen` — **one compositor, one square screen, two completely different ways of getting there.** + +## The key in the middle: from wheel to trackpad + + + The key under your thumb · from wheel to trackpad + + + + 1999side track wheel850 + 2006trackballPearl 8100 + 2009optical trackpadCurve 8520 + 2013all-touch · removedZ10 / Q10 + 2014Classic brings it backQ20 + + The Classic tool belt — four keys, one optical trackpad in the middle + + + Send + Menu + opticaltrackpad + Back + End + + A tiny optical mouse reporting relative displacement — to native apps, that's SCREEN_EVENT_JOYSTICK DISPLACEMENT. + + +BlackBerry's identity is bound, in large part, to **the navigation key under your thumb**. Its lineage is worth a moment: + +- **Side track wheel**: the earliest BlackBerrys (the 850 in 1999 through the 8700 series around 2005) put a wheel on the side of the body — thumb-scroll, press to confirm. This is the ancestor of the "scroll wheel" the title refers to. +- **Trackball**: the Pearl 8100 in 2006 moved a small rolling ball to the front, four-way plus press; the Curve 8300 and Bold 9000 used it. Nice, but prone to dirt and wear. +- **Optical trackpad**: from the Curve 8520 in 2009, an **optical sensor** replaced the trackball — like a tiny optical mouse, no moving parts, sensing the finger's relative motion directly; the Bold 9700 and 9900 carried it on. +- On BB10's all-touch phones (Z10, Q10, Passport…) the key was removed entirely. +- Then the **Classic (Q20, December 2014)**: it **deliberately put the tool belt back** — Menu / Back / Send / End, with an **optical trackpad** in the middle, a nod to the Bold 9900 era. BB10.3.1 added trackpad support to a system that was never designed for one: **there is no global cursor** (only the browser and Maps get a pointer); everywhere else, a blue focus highlight moves cell by cell through the Cascades UI. + +For us, this trackpad is an interesting engineering problem, because **it is a relative pointing device**: it gives you displacement deltas, not coordinates, and certainly not discrete "up/down/left/right." Our guest, meanwhile, lives in a d-pad / button world (`input.buttons`). So each host has to turn continuous relative motion into discrete focus movement: + +- **The QNX host**: the trackpad arrives in `libscreen` as **`SCREEN_EVENT_JOYSTICK`** events carrying `SCREEN_PROPERTY_DISPLACEMENT` (displacement) and buttons. The displacement is an integer, every event is one "notch," so the host feeds it in with a threshold of 1 — every non-zero event is one d-pad pulse in its direction; a click (`buttons != 0`) becomes `CIRCLE`. +- **The Android host**: the runtime delivers the trackpad as generic-motion scroll axes or trackball deltas — continuous floats — so the host feeds them in with a threshold of 0.35 and a pulse fires only once the running sum crosses it. This mapping is **provisional** — we haven't yet watched, on a device, whether the Classic's Android runtime presents the trackpad as scroll, trackball, or a pointer. + +Both hosts hand their deltas to the same few lines, in the input state machine they share (`hosts/iphone2g/pocket_input.c`); only the threshold differs: + +```c +/* Relative motion → one d-pad pulse per threshold crossing; the axis resets + after a pulse, so the remainder of a big move can never flip the next one. + QNX: integer displacement, threshold 1. Android: float deltas, 0.35. */ +void pocket_input_relative(PocketInputState *state, float delta_x, float delta_y) +{ + const float threshold = state->relative_threshold; + state->relative_x += delta_x; + state->relative_y += delta_y; + if (state->relative_x <= -threshold) { state->pressed |= POCKET_BTN_LEFT; state->relative_x = 0.0f; } + else if (state->relative_x >= threshold) { state->pressed |= POCKET_BTN_RIGHT; state->relative_x = 0.0f; } + if (state->relative_y <= -threshold) { state->pressed |= POCKET_BTN_UP; state->relative_y = 0.0f; } + else if (state->relative_y >= threshold) { state->pressed |= POCKET_BTN_DOWN; state->relative_y = 0.0f; } +} +``` + +(The `POCKET_BTN_*` bits are not typed by hand in either host: `pocket_spec.h` is generated from `contracts/spec/spec.ts`, the same table the Rust core and the JS runtime are generated from, and the contract test refuses to let it drift.) + +This echoes a long-standing PocketJS attitude toward input. The repo already has a **hardware-neutral incremental-input contract** — `RelativeAxis` / `onAxisDelta` (`vapor/host/input.ts`) — a device-agnostic ABI for **incremental controls** like a Playdate crank or a rotary encoder. In that worldview the trackpad is "just another relative axis." The Hero demo only needs buttons, so we collapse the axis down to d-pad pulses rather than exposing `RelativeAxis` to the guest; but the bloodline is the same: **never let a device concept cross the boundary into the guest.** + +So the side wheel of 1999 and the optical trackpad of 2014 are, in PocketJS's eyes, the same thing — **a relative motion sensor hiding under your thumb** — exactly the way it sees a Playdate crank. + +## Input: collapsing two sets of hardware into one contract + +Whether it is QNX's `SCREEN_EVENT_*` or Android's `KeyEvent`/`MotionEvent`, none of it is **allowed across the QuickJS bridge**. The guest sees only one portable button mask and one touch snapshot. Each host translates the physical input into that contract: + +| Physical input | Portable input | +| --- | --- | +| trackpad movement | discrete d-pad focus pulses — one per threshold crossing of the accumulated motion (QNX: integer `SCREEN_PROPERTY_DISPLACEMENT`, threshold 1; Android: scroll-axis/trackball deltas, threshold 0.35 — provisional, see above) | +| trackpad click | the press button (`CIRCLE`), held while the button is down, tracked apart from the keys | +| Enter/Return, d-pad center | the press button | +| arrow keys | d-pad; a key down is one press edge, auto-repeat does not press again | +| Space | `START` | +| Menu | `TRIANGLE` | +| Send (QNX navigator system key) | a one-shot press edge; End and Back stay with the system | +| touchscreen | one tracked contact (a second finger never becomes input), divided into 360×360 logical coordinates, with the host-resolved bounds hit fact | + +Both translations land in the same place: `pocket_input.c`, a small state machine the QNX BPS callbacks and the Android JNI callbacks both feed, and which the frame loop samples exactly once per guest turn. It is plain C with no platform headers, so it is unit-tested with the host compiler — key edges, trackpad pulses, the click, and the touch latch all have a scenario in `tests/fixtures/pocket-input-test.c`. + +For these two hosts, `pocket_runtime.c` also grew two entry points: `pocket_runtime_tick` — **exactly one guest turn followed by one core tick**, taking the button mask, the sampled contact, and its hit fact (the older `pocket_runtime_frame*` calls stay for the original iPhone host, whose 30 Hz presentation advances two core ticks per guest turn) — and `pocket_runtime_gl_reset`, which drops the old GL resources after the platform recreates the GL context so the backend can re-initialize — a must on Android, because `GLSurfaceView` recreates the context on pause/resume. + +One frame's worth of input is fed in like this — sample the state machine into "a button mask + a touch snapshot," run one tick, then let the GPU draw: + +```c +static int render_frame(void) { + PocketInputSample sample; + PocketRuntimeInput frame; + pocket_input_sample(&input, &sample); // held + one-frame edges; latch consumed here + + frame.buttons = sample.buttons; + frame.touch_down = sample.touch_down; + frame.touch_x = (int)(sample.touch_x * POCKET_LOGICAL_WIDTH / surface_width); // 720 → 360 logical + frame.touch_y = (int)(sample.touch_y * POCKET_LOGICAL_HEIGHT / surface_height); + frame.touch_hit = sample.touch_down ? pocket_runtime_hit_test_bounds(frame.touch_x, frame.touch_y) : 0; + + pocket_runtime_tick(&frame); // one guest turn + one core tick + pocket_runtime_gl_render(surface_width, surface_height); // GPU draws the retained tree; the CPU touches no pixel + eglSwapBuffers(egl_display, egl_surface); + return 1; +} +``` + +The press edges and the touch latch inside that state machine are the cure for the following trap. There is a problem here shared with the Meizu M8 post: **an event stream and a sampled state are two different things**. The trackpad hands you a run of displacement events, but the guest samples only once per 60 Hz; a quick press-and-release can happen entirely between two samples. So the host has to **latch** an edge like a press until at least one frame has observed it. Touch is the same: a tap's down and up can fall in the same inter-frame gap — and the latch has to fire **only on the down**, never on the release, or a long press would hand the guest one more "down" frame after the finger had already left. (The first cut of the QNX host did exactly that; a reviewer caught it, and it is now the kind of mistake the unit test refuses.) + +## The translation seam: the host owns the pump, the guest owns the UI + + + Translation seam · host owns pump and presentation, guest owns state and its UI + + + Modern app model (guest) + The two host pumps + Solid signals + TSXdeclare relationships + + PocketJS retained treelayout · focus · hit test + + GLES2 DrawListGPU draws all of 720×720 + + eglSwapBuffers / GLSurfaceView720×720 present · no stretch + + Input returns through the same seam · no OS concept crosses the boundary + QNX SCREEN_* / navigatorAndroid KeyEvent / MotionEvent + + host adaptercoords · edges · one tick + + frame inputbuttons + touch, no HWND/BPS + + app speaks only state + desired UI + host speaks only pump + present + + +Put the two paths side by side and PocketJS's role on BlackBerry is the same as it was on Windows CE: **it does not replace the OS event loop; it sits inside it.** + +- In the QNX host, `bps_get_event` is the pump. It drains the screen/navigator events, normalizes them into one frame input, lets the guest take one tick, and presents 720×720 with `eglSwapBuffers`. +- In the Android host, `GLSurfaceView`'s `onDrawFrame` is the pump. Each frame, the JNI layer folds the accumulated key/touch/relative events into the same contract under one mutex and drives one guest tick. + +On both sides, Solid, the app code, and the Rust core **never know** that `screen_window_t`, BPS, `GLSurfaceView`, or `MotionEvent` exist. And in reverse, the host never learns what "a button" or "a component" means. Each side of the boundary owns half the world: **the host owns the pump and the presentation, the guest owns the state and the UI it wants.** + +This is also why adding a new target costs so little. The M8 turned Windows CE into a whole phone platform; we go the other way, bringing a self-contained modern UI runtime and asking the OS for only the smallest surface that will hold it. QNX and the Android Runtime each provide only that smallest surface. + +## Why bother + +PocketJS's whole bet is that **one guest, one core, dropped onto machines of every shape, changes only a thin layer of host**. The BlackBerry Classic pushes that bet to a new extreme: it runs **the same guest on the same phone through two unrelated native stacks** — once as a QNX-native application asking `screen` for a window, once as an Android 4.3 app hosted by the compatibility layer. The two paths split entirely below the QuickJS bridge and are byte-for-byte identical above it. It is probably the cleanest proof we have of "draw the boundary right and the machine becomes swappable." + +And the machine itself is a specimen about trust. BlackBerry carved security into every layer: even a developer's local debugging needs a time-limited, device-bound, officially blessed pass; files go over challenge-response CGI or password-protected SMB; an app is either signed or holds a token. It was impregnable in its day, and the price was this — when the servers behind it went dark, the whole device closed its door to new code. It was the community, not the vendor, that pushed the door back open. + +So the last word of this post goes to those people: to **bb10.root.sx**'s Oleksandr, and to guizmox, sw7ft, and everyone who re-rooted a platform its maker had already condemned; to **Sachesi**'s Sacha Refshauge, who reversed the official tools' protocol into a program that still compiles and runs today; to everyone still making firmware, writing tools, and keeping documentation for these square-screened phones. What you did is far heavier than this port — you are the reason a 2014 BlackBerry can still light up a freshly written frame in 2026. + +Respect. + +--- + +*Further reading: QNX's [System Architecture](https://www.qnx.com/developers/docs/7.1/com.qnx.doc.neutrino.sys_arch/topic/kernel.html) on the Neutrino microkernel and its [message passing](https://www.qnx.com/developers/docs/6.5.0SP1.update/com.qnx.doc.neutrino_sys_arch/ipc.html); the [Screen Graphics Subsystem](https://www.qnx.com/developers/docs/8.0/com.qnx.doc.screen/topic/manual/cscreen_appDevelopment.html) developer guide, including [composition](https://www.qnx.com/developers/docs/8.0/com.qnx.doc.screen/topic/manual/cscreen_composition.html) and [window groups](http://www.qnx.com/developers/docs/7.0.0/com.qnx.doc.screen/topic/manual/cscreen_windowing-groups.html); the [PPS](https://www.qnx.com/developers/docs/7.1/com.qnx.doc.neutrino.sys_arch/topic/pps.html) service; RIM's own [GoodCitizen](https://github.com/blackberry/NDK-Samples/blob/master/GoodCitizen/main.c) sample for BPS, screen, and the navigator. The BB10 root project lives at [bb10.root.sx](https://bb10.root.sx), and [Sachesi](https://github.com/xsacha/Sachesi) is the community tool whose source made the device protocol legible. BlackBerry's [End of Life FAQ](https://www.blackberry.com/us/en/support/devices/end-of-life) records the January 4, 2022 shutdown. PocketJS's Classic hosts are documented in [`docs/BLACKBERRY_CLASSIC.md`](https://github.com/pocket-stack/pocketjs/blob/main/docs/BLACKBERRY_CLASSIC.md).* diff --git a/site/nav.ts b/site/nav.ts index ae542da5..e4d6a8be 100644 --- a/site/nav.ts +++ b/site/nav.ts @@ -56,6 +56,14 @@ export interface BlogPost { } export const BLOG_POSTS: BlogPost[] = [ + { + slug: "blackberry-classic", + title: "One Square Screen, Two Native Stacks: PocketJS on the BlackBerry Classic", + date: "2026-08-19", + description: + "The same PocketJS guest, two native stacks on one phone: a BlackBerry 10 Core Native BAR over libscreen/EGL/GLES2 on a rooted Classic, and an Android 4.3 (API 18) APK through the BB10 Android runtime on a stock one — diverging only below the QuickJS bridge. Inside: QNX's microkernel and Screen compositor, the dead signing servers a community root project reopened, BlackBerry's two-protocol PC link (HTTPS+CGI+XML to manage, SMB/CIFS to mount files), why MSC only ever exposed the SD card, and the optical trackpad mapped as just another relative axis.", + author: { name: "HalfSweet", url: "https://github.com/HalfSweet" }, + }, { slug: "pocketjs-on-windows-ce", title: "From Message Pump to Multitouch: Windows CE, PocketJS, and the Meizu M8",