Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions apps/blackberry-classic-demo/app.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Hero
actionLabel="CLICK OR TAP"
deviceLabel="running on a BlackBerry Classic."
headline="JSX on Classic."
onAction={(count) => reportAppAction("hero_press", count)}
presentationHz={60}
runtimeLabel="RUST + QUICKJS + GLES2"
spinnerFrameStep={6}
/>
);
}
5 changes: 5 additions & 0 deletions apps/blackberry-classic-demo/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// @title PocketJS: BlackBerry Classic Hero
import { mount } from "@pocketjs/framework/solid";
import BlackBerryClassicHero from "./app.tsx";

mount(() => <BlackBerryClassicHero />);
25 changes: 25 additions & 0 deletions apps/blackberry-classic-demo/pocket.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
46 changes: 46 additions & 0 deletions contracts/spec/gen-c.ts
Original file line number Diff line number Diff line change
@@ -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}`);
}
306 changes: 306 additions & 0 deletions docs/BLACKBERRY_CLASSIC.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
18 changes: 17 additions & 1 deletion framework/src/manifest/host-build-inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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: {
Expand All @@ -109,6 +122,9 @@ export function hostBuildEnvironment(
): Readonly<Record<string, string>> {
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,
Expand Down
4 changes: 3 additions & 1 deletion framework/src/manifest/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PocketManifestV2, "id" | "title"> &
/** 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<PocketManifestV2, "id" | "title" | "version"> &
Pick<PocketManifestV2["app"], "entry" | "framework"> & {
readonly output: string;
};
Expand Down
1 change: 1 addition & 0 deletions framework/src/manifest/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
30 changes: 30 additions & 0 deletions hosts/blackberry-android/app/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Template: package identity is rendered from the resolved build plan by
tools/blackberry-android.ts (packageIdentity); this file is not the source
of the app id or version. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="@POCKET_PACKAGE@"
android:versionCode="@POCKET_VERSION_CODE@"
android:versionName="@POCKET_VERSION_NAME@">

<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="18" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

<application
android:allowBackup="false"
android:debuggable="false"
android:hardwareAccelerated="true"
android:icon="@drawable/icon"
android:label="@string/app_name"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
<activity
android:name="dev.pocketstack.blackberry.PocketActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Loading