Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
1983248
chore(wm6): initialize port branch
Jul 26, 2026
1eebae3
feat(wm6): add iPAQ 212 VS2005 probe
kyokuheishin Jul 26, 2026
7102a21
fix(wm6): support the VS2005 device SDK
kyokuheishin Jul 26, 2026
47223af
fix(wm6): avoid unavailable string helpers
kyokuheishin Jul 26, 2026
0c8c84b
fix(wm6): let the linker infer ARM target
kyokuheishin Jul 26, 2026
8463d21
fix(wm6): align deploy and debug paths
kyokuheishin Jul 26, 2026
5555d4a
fix(wm6): request the native VGA surface
kyokuheishin Jul 26, 2026
df6cf8e
feat(wm6): add Pocket Vapor AOT demo
kyokuheishin Jul 26, 2026
95f8854
fix(wm6): keep VS2005 solution self-contained
kyokuheishin Jul 26, 2026
f102518
fix(wm6): tolerate VC8 SDK C headers
kyokuheishin Jul 26, 2026
b8a21d2
feat(wm6): port QuickJS probe toolchain
kyokuheishin Jul 26, 2026
8988757
fix(wm6): target the ARMV4I instruction baseline
kyokuheishin Jul 26, 2026
e664c90
feat(wm6): boot the PocketJS Cards bundle
kyokuheishin Jul 26, 2026
0cee66f
fix(wm6): initialize the QuickJS host error buffer
kyokuheishin Jul 26, 2026
a2e1963
fix(wm6): preserve the QuickJS deploy entry name
kyokuheishin Jul 26, 2026
e719037
feat(wm6): render the PocketJS Cards screen
kyokuheishin Jul 27, 2026
dd3e27b
fix(wm6): use the Windows CE text API
kyokuheishin Jul 27, 2026
da2b79c
feat(wm6): present Cards through an RGB565 framebuffer
kyokuheishin Jul 27, 2026
75d6359
chore(wm6): report the active display backend
kyokuheishin Jul 27, 2026
12bfb49
fix(wm6): call the Windows CE DirectDraw vtable
kyokuheishin Jul 27, 2026
2fd0ebd
feat(wm6): rasterize PocketJS font atlases
kyokuheishin Jul 27, 2026
910c20a
feat(wm6): switch to the Cursor demo
kyokuheishin Jul 27, 2026
8c94418
feat(wm6): switch to the Hero demo
kyokuheishin Jul 27, 2026
fe2c3aa
fix(wm6): rotate the Hero demo into landscape
kyokuheishin Jul 27, 2026
e71997e
feat(wm6): adapt Hero output to the display
kyokuheishin Jul 27, 2026
943cbff
fix(wm6): preserve the requested display rotation
kyokuheishin Jul 27, 2026
8232aab
fix(wm6): recover rotated surface pixel masks
kyokuheishin Jul 27, 2026
62c248d
feat(wm6): use the rotated runtime viewport
kyokuheishin Jul 27, 2026
2e63f44
feat(wm6): link the native Rust core
kyokuheishin Jul 27, 2026
77d0a71
feat(wm6): forward stylus frames
kyokuheishin Jul 27, 2026
d82ae36
build(wm6): verify Rust core artifacts
kyokuheishin Jul 27, 2026
923ef78
fix(wm6): distinguish the runtime executable
kyokuheishin Jul 27, 2026
90067f1
test(wm6): exercise the native Rust runtime
kyokuheishin Jul 27, 2026
a8db7ec
fix(wm6): avoid stale runtime ABI modules
kyokuheishin Jul 27, 2026
a5f329f
fix(wm6): latch short button presses
kyokuheishin Jul 27, 2026
b287823
fix(wm6): refresh legacy debugger alias
kyokuheishin Jul 27, 2026
1713e1f
fix(wm6): normalize ARM branch relocations
kyokuheishin Jul 27, 2026
8cdfc8e
test(wm6): verify ARM branch addends
kyokuheishin Jul 27, 2026
2d26ebd
fix(wm6): trace stalled first frames
kyokuheishin Jul 27, 2026
acc005e
test(wm6): cover QVGA runtime output
kyokuheishin Jul 27, 2026
ee2793c
fix(wm6): defer DirectDraw until first frame
kyokuheishin Jul 27, 2026
1af681b
fix(wm6): blit through a lockable surface
kyokuheishin Jul 28, 2026
5d0c764
fix(wm6): define legacy offscreen surface flag
kyokuheishin Jul 28, 2026
dac9bdf
fix(wm6): hide the shell chrome
kyokuheishin Jul 28, 2026
481d0c9
fix(wm6): load shell fullscreen dynamically
kyokuheishin Jul 28, 2026
e8099ff
perf(wm6): streamline the VGA fallback
kyokuheishin Jul 28, 2026
6da823f
fix(wm6): define the offscreen pixel format
kyokuheishin Jul 28, 2026
b83738b
fix(wm6): initialize the primary pixel format
kyokuheishin Jul 28, 2026
02293f7
perf(wm6): accelerate framebuffer presentation
kyokuheishin Jul 28, 2026
646cf39
perf(wm6): scale the logical VGA viewport
kyokuheishin Jul 28, 2026
aa6aa9c
perf(wm6): profile the frame stages
kyokuheishin Jul 28, 2026
427eb64
fix(wm6): tolerate sparse DirectDraw locks
kyokuheishin Jul 28, 2026
f071613
feat(wm6): restore the iPAQ 212 port history
kyokuheishin Sep 5, 2026
846e382
fix(wm6): prepare restored VS2005 release builds
kyokuheishin Sep 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions docs/WM6_IPAQ_212.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# HP iPAQ 212 / Windows Mobile 6 port

## Decision

PocketJS should reach the iPAQ 212 through a native Windows Mobile host, built
by Visual C++ 2005 for the Windows Mobile 6 Professional SDK's ARMV4I target.
The repository now contains a first, deployable solution at
`hosts/wm6/vs2005/PocketJS.WM6.sln`.

This is intentionally an experimental port, not a production target in
`POCKET_TARGETS`. Adding a target profile before the complete guest runtime and
its capabilities work on hardware would make build admission promise behavior
that the host does not deliver.

## Why this target

The HP iPAQ 212 belongs to the iPAQ 210 series. It has a 624 MHz Marvell
PXA310, 128 MB SDRAM, and a 4-inch 480×640 touch display, and ships with
Windows Mobile 6 Classic. Despite the device name, Microsoft's SDK mapping is:

| Device class | Required WM6 SDK |
| --- | --- |
| Classic / Pocket PC | Professional SDK |
| Professional / Pocket PC Phone | Professional SDK |
| Standard / Smartphone | Standard SDK |

The Visual Studio platform must therefore read
`Windows Mobile 6 Professional SDK (ARMV4I)`. ARMV4I is the SDK ABI and is
compatible with the PXA310; selecting an unofficial CPU-specific target would
make the binary less portable and would bypass the SDK libraries.

## Honest feasibility boundary

Creating a `.sln` is necessary but not sufficient for the existing guest
runtime:

- the native PocketJS hosts embed QuickJS;
- the retained renderer lives in the Rust `engine/core` crate;
- Visual C++ 2005 predates modern C/C++ language features;
- stable Rust has no Windows CE 5.2 / ARMV4I target; the implemented build
therefore emits ARMv4T ELF and performs a checked relocation/symbol
conversion to WinCE COFF with dual-target CeGCC binutils;
- Windows Mobile 6 has neither a suitable browser runtime nor modern Web APIs,
so wrapping the web host is not a viable shortcut.

The lowest-risk route is to validate each irreversible assumption on the real
device before porting the next layer.

## Port gates

### Gate 0 — native hardware probe (implemented)

Build and run `hosts/wm6/vs2005/PocketJS.WM6.sln`. Accept this gate only when
the physical iPAQ shows:

- `screen: 480 x 640` in portrait (or `640 x 480` after rotation);
- a steadily increasing frame count near the 33 ms timer interval;
- touch coordinates and DOWN/up state following the stylus;
- distinct hexadecimal key codes for the D-pad and centre button;
- stable free RAM after leaving the probe running for ten minutes.

Record the ROM version, orientation, displayed memory values, D-pad key codes,
and whether the Back/Escape key exits. Emulator-only success is insufficient.

Windows Mobile virtualizes a VGA device as `240×320` for legacy applications.
The probe embeds `HI_RES_AWARE CEUX { 1 }` to opt out of that compatibility
scaling. A QVGA result therefore means an older or incorrectly linked resource
was deployed; it is not the iPAQ panel's physical resolution.

### Gate 1 — toolchain-owned QuickJS probe (first executable implemented)

Current QuickJS cannot be built by VC8. The implemented split toolchain keeps
VS2005 as the WM6 window/deployment/debug host and builds QuickJS with CeGCC's
native-API `arm-mingw32ce` compiler. The checked-in ARM/WinCE probe now proves:

1. runtime/context creation;
2. evaluation of an embedded UTF-8 script;
3. a native `print` callback plus Promise pending-job draining;
4. explicit 8 MiB memory and 256 KiB stack limits;
5. 100 repeated create/evaluate/drain/destroy cycles.

Its pinned source, compatibility patch, and reproducible build command live in
`hosts/wm6/quickjs`. `PocketJS.WM6.QuickJS` in the VS2005 solution deploys the
resulting CeGCC executable and starts it on the selected device or emulator.
The CeGCC build targets ARMV4T with interworking rather than the PXA310's
ARMv5TE extensions: the WM6 ARMV4I emulator rejects ARMv5-only instructions
such as `CLZ`, while the physical PXA310 remains backward-compatible.

QuickJS now has a dedicated WinCE compatibility layer for allocation, missing
CRT calls, and the subset used by the Hero guest. Those changes remain a
reviewable patch, as the Symbian toolchain does. Gate 1 remains open until the 100-cycle
probe passes on physical iPAQ hardware with before/after free-memory receipts;
emulator success validates the binary and ABI but cannot close the hardware
gate.

The repository-pinned QuickJS revision cannot be compiled directly by VC8: it
uses C99 syntax, flexible arrays, GCC builtins/attributes, compound literals,
and designated initializers. Compiling it as C++ is also not a shortcut because
the C sources rely on implicit `void *` conversions and C linkage rules. Gate 1
therefore uses a separately owned GNU WinCE build. The future VS2005 host will
load a CeGCC-built DLL through a narrow C ABI; QuickJS values and allocator
ownership must never cross that boundary. That DLL boundary is now
implemented as ABI v3; it also owns the linked Rust core so QuickJS values,
Rust allocations, and framebuffer pointers stay on the CeGCC side.

### AOT milestone — Pocket Vapor Todo (implemented)

`PocketJS.WM6.Vapor` is an independent application project in the same VS2005
solution. It links:

- a checked-in copy of the target-independent `vapor/runtime/vapor_core.c`
(kept inside the solution so a `Y:\vs2005` VM mount is self-contained);
- deterministic C generated from `vapor/examples/todo/todo.tsx`;
- a WM6 GDI cell-grid host with D-pad, centre, Back, soft-key, and stylus
mappings.

This milestone deliberately follows the AOT-first option described below. It
proves that Pocket-authored reactive UI can execute on the SDK ABI without a
heap or JavaScript engine, and it gives the real iPAQ a useful memory/input
test while QuickJS compatibility work continues. It must not be described as
ordinary PocketJS guest compatibility.

### Gate 2 — native Rust renderer (implemented; runtime receipt pending)

The guest-compatible path now reuses the actual Rust core rather than
rewriting it in C:

1. nightly rustc builds the freestanding core for `armv4t-none-eabi`;
2. ARM ELF ld merges per-item sections and discards unwind/LLVM metadata;
3. dual-target CeGCC binutils converts ELF to WinCE COFF;
4. a repository tool reconstructs all COFF relocation symbol indices and
strictly maps `R_ARM_ABS32`, `R_ARM_CALL`, and `R_ARM_JUMP24`;
5. CeGCC links that object into the QuickJS DLL.

The final DLL has 16 PE sections instead of thousands of Rust per-item
sections. The core renders its real incremental ARGB32 framebuffer at the
rotated native viewport; the VC8 host converts it to an RGB565 staging buffer
and presents it through DirectDraw. The next emulator run must provide the
runtime receipt before this gate is considered closed.

### Gate 3 — PocketJS HostOps (implemented; runtime receipt pending)

The ABI v3 DLL installs native lifecycle, node, style/property batch, text,
texture/font, animation, focus/hit-test, debug, tick, and render operations.
It copies the PAK into QuickJS before evaluating the unmodified Hero bundle,
calls `globalThis.frame` at the WM6 timer cadence, ticks the core, and returns
the incremental framebuffer. The former JavaScript tree and hand-authored
draw-list adapter have been removed. D-pad keys and Enter/Space currently feed
the PocketJS directional/Circle button bits. Stylus down/move/up snapshots use
the wide 10-bit PocketJS touch wire format so VGA coordinates are not
truncated.

### Gate 4 — packaging and production admission

Produce a CAB only after direct EXE deployment is stable. The CAB should
install one application under `\Program Files\PocketJS`, add a Start Menu
shortcut, and uninstall without touching shared storage. Promote WM6 to
`POCKET_TARGETS` only when:

- the stock host boots a real `.pocket` guest;
- viewport and input contracts have device tests;
- memory has a measured ceiling;
- suspend/resume and orientation behavior are defined;
- an iPAQ 212 hardware receipt identifies the ROM and binary hash.

## Recommended build machine

Use an isolated 32-bit Windows XP SP3 VM for the shortest path, with Visual
Studio 2005 SP1, the Smart Device C++ feature, and the WM6 Professional SDK
Refresh. ActiveSync 4.5 is the period-correct XP deployment path. A Vista VM
can use Windows Mobile Device Center plus Microsoft's VS2005 Vista updates,
but modern Windows hosts add driver and installer failure modes unrelated to
the port.

Keep the VM offline except while obtaining original toolchain installers.
Never flash the iPAQ as part of this workflow; copying or debugging an
application does not require a ROM change.

## Sources

- [Microsoft: Windows Mobile 6 SDK Refresh download and SDK mapping](https://www.microsoft.com/en-us/download/details.aspx?id=6135)
- [HP: iPAQ 200 series product specifications](https://support.hp.com/tw-zh/document/c01419121)
4 changes: 3 additions & 1 deletion engine/symbian/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ edition = "2021"
crate-type = ["rlib", "staticlib"]

[features]
default = ["standalone-extension-provider"]
default = ["standalone-extension-provider", "freestanding", "gles2"]
standalone-extension-provider = []
freestanding = []
gles2 = []

[dependencies]
pocketjs-core = { path = "../core" }
Expand Down
50 changes: 25 additions & 25 deletions engine/symbian/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,47 +11,47 @@
//! packed, top-left-origin ARGB32 pixels; those pointers remain valid until
//! the next capture, viewport change, init, or shutdown call.

#![cfg_attr(target_os = "none", no_std)]
#![cfg_attr(target_os = "none", feature(alloc_error_handler))]
#![cfg_attr(all(feature = "freestanding", not(test)), no_std)]
#![cfg_attr(all(feature = "freestanding", not(test)), feature(alloc_error_handler))]
#![allow(static_mut_refs)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]

extern crate alloc;

use alloc::vec::Vec;
#[cfg(target_os = "none")]
#[cfg(all(feature = "freestanding", not(test)))]
use core::alloc::{GlobalAlloc, Layout};
#[cfg(target_os = "none")]
#[cfg(all(feature = "freestanding", not(test)))]
use core::ffi::c_void;
use pocketjs_core::damage::{DamagePolicy, DamageTracker, DEFAULT_DAMAGE_REGIONS};
use pocketjs_core::raster;
use pocketjs_core::Ui;

#[cfg(any(target_os = "none", test))]
mod gles2;
pub mod extension;
#[cfg(feature = "gles2")]
mod gles2;

#[cfg(any(target_os = "none", test))]
#[cfg(any(feature = "freestanding", test))]
const C_MALLOC_ALIGNMENT: usize = 8;

#[cfg(any(target_os = "none", test))]
#[cfg(any(feature = "freestanding", test))]
#[inline]
const fn c_allocator_supports_alignment(alignment: usize) -> bool {
alignment <= C_MALLOC_ALIGNMENT
}

#[cfg(target_os = "none")]
#[cfg(all(feature = "freestanding", not(test)))]
unsafe extern "C" {
fn malloc(size: usize) -> *mut c_void;
fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void;
fn free(ptr: *mut c_void);
fn abort() -> !;
}

#[cfg(target_os = "none")]
#[cfg(all(feature = "freestanding", not(test)))]
struct CAllocator;

#[cfg(target_os = "none")]
#[cfg(all(feature = "freestanding", not(test)))]
unsafe impl GlobalAlloc for CAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if !c_allocator_supports_alignment(layout.align()) {
Expand All @@ -72,17 +72,17 @@ unsafe impl GlobalAlloc for CAllocator {
}
}

#[cfg(target_os = "none")]
#[cfg(all(feature = "freestanding", not(test)))]
#[global_allocator]
static ALLOCATOR: CAllocator = CAllocator;

#[cfg(target_os = "none")]
#[cfg(all(feature = "freestanding", not(test)))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo<'_>) -> ! {
unsafe { abort() }
}

#[cfg(target_os = "none")]
#[cfg(all(feature = "freestanding", not(test)))]
#[alloc_error_handler]
fn allocation_error(_layout: Layout) -> ! {
unsafe { abort() }
Expand Down Expand Up @@ -145,7 +145,7 @@ fn clear_framebuffer() {
/// Reset the single UI instance. `raster_density == 0` selects density 1.
#[no_mangle]
pub extern "C" fn ui_init(raster_density: u32) {
#[cfg(target_os = "none")]
#[cfg(feature = "gles2")]
unsafe {
// This call may happen without a current GL context, so the backend
// only marks its caches stale and defers replacement until render.
Expand All @@ -160,7 +160,7 @@ pub extern "C" fn ui_init(raster_density: u32) {
/// Drop all retained UI, texture, font, and framebuffer allocations.
#[no_mangle]
pub extern "C" fn ui_shutdown() {
#[cfg(target_os = "none")]
#[cfg(feature = "gles2")]
unsafe {
gles2::invalidate_resources();
}
Expand Down Expand Up @@ -365,7 +365,7 @@ pub extern "C" fn ui_load_styles(ptr: *const u8, len: usize) -> i32 {
pub extern "C" fn ui_load_font_atlas(ptr: *const u8, len: usize) -> i32 {
let blob = unsafe { bytes(ptr, len) };
let loaded = ui().load_font_atlas(blob);
#[cfg(target_os = "none")]
#[cfg(feature = "gles2")]
if loaded {
if let Some(&slot) = blob.get(12) {
unsafe {
Expand All @@ -392,25 +392,25 @@ pub extern "C" fn ui_tick() {

#[no_mangle]
pub extern "C" fn ui_gl_initialize() -> i32 {
#[cfg(target_os = "none")]
#[cfg(feature = "gles2")]
unsafe {
return gles2::initialize() as i32;
}
#[cfg(not(target_os = "none"))]
#[cfg(not(feature = "gles2"))]
0
}

#[no_mangle]
pub extern "C" fn ui_gl_reset_resources() {
#[cfg(target_os = "none")]
#[cfg(feature = "gles2")]
unsafe {
gles2::reset_resources();
}
}

#[no_mangle]
pub extern "C" fn ui_gl_shutdown() {
#[cfg(target_os = "none")]
#[cfg(feature = "gles2")]
unsafe {
gles2::shutdown();
}
Expand All @@ -425,7 +425,7 @@ pub extern "C" fn ui_gl_render(
window_width: i32,
window_height: i32,
) -> i32 {
#[cfg(target_os = "none")]
#[cfg(feature = "gles2")]
unsafe {
return gles2::render(
ui(),
Expand All @@ -437,7 +437,7 @@ pub extern "C" fn ui_gl_render(
window_height,
) as i32;
}
#[cfg(not(target_os = "none"))]
#[cfg(not(feature = "gles2"))]
{
let _ = (
target_x,
Expand All @@ -463,7 +463,7 @@ pub extern "C" fn ui_gl_render_over(
window_width: i32,
window_height: i32,
) -> i32 {
#[cfg(target_os = "none")]
#[cfg(feature = "gles2")]
unsafe {
return gles2::render_over(
ui(),
Expand All @@ -475,7 +475,7 @@ pub extern "C" fn ui_gl_render_over(
window_height,
) as i32;
}
#[cfg(not(target_os = "none"))]
#[cfg(not(feature = "gles2"))]
{
let _ = (
target_x,
Expand Down
Loading