@geastack/apple 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geastack/apple",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Generated Apple SDK bindings plus the macOS and iOS native build targets for Gea apps.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -102,14 +102,14 @@
102
102
  "access": "public"
103
103
  },
104
104
  "dependencies": {
105
- "@geastack/compiler": "^1.0.12",
106
- "@geastack/core": "^0.1.21",
107
- "@geastack/elements": "^0.1.0",
108
- "@geastack/engine": "^0.1.2",
109
- "@geastack/geaos": "^0.1.1",
110
- "@geastack/geatsc-plugin-apple-native": "^0.1.0",
111
- "@geastack/geatsc-plugin-gea": "^0.1.3",
112
- "@geastack/host": "^0.1.6"
105
+ "@geastack/compiler": "^1.0.18",
106
+ "@geastack/core": "^0.1.23",
107
+ "@geastack/elements": "^0.1.2",
108
+ "@geastack/engine": "^0.1.5",
109
+ "@geastack/geaos": "^0.1.3",
110
+ "@geastack/geatsc-plugin-apple-native": "^0.1.3",
111
+ "@geastack/geatsc-plugin-gea": "^0.1.6",
112
+ "@geastack/host": "^0.1.8"
113
113
  },
114
114
  "scripts": {
115
115
  "prepublishOnly": "npm run build",
@@ -2,12 +2,27 @@
2
2
 
3
3
  Build a gea-embedded JSX app as a native UIKit app.
4
4
 
5
- First milestone:
5
+ From an app that depends on `@geastack/apple` (every iOS example does, and
6
+ `gea create --targets ios` adds it), the CLI drives this target:
6
7
 
7
8
  ```bash
8
- targets/ios/build-ios.sh bouncing-balls-jsx simulator
9
+ npx gea build --target ios # simulator .app, no launch
10
+ npx gea build --target ios --mode device # unsigned iphoneos .app
11
+ npx gea run --target ios # build, install and launch on a simulator
12
+ npx gea run --target ios --mode device # build, sign, install and launch on an iPhone
9
13
  ```
10
14
 
15
+ The same script can be run directly from the app folder; the CLI passes the
16
+ app id and destination through unchanged:
17
+
18
+ ```bash
19
+ node_modules/@geastack/apple/targets/ios/build-ios.sh <app-id> simulator
20
+ ```
21
+
22
+ The script resolves `@geastack/core`, `@geastack/compiler`, the apple-native
23
+ geatsc plugin and `@geastack/cli` by walking up `node_modules` from the app
24
+ folder, so it works on a hoisted npm install and on a linked checkout alike.
25
+
11
26
  The simulator path auto-picks an available iPhone simulator. Set
12
27
  `GEA_IOS_SIMULATOR_UDID` to force a specific device. By default, the script
13
28
  also opens Simulator.app for the selected device; set
@@ -18,7 +33,7 @@ team when it can find one. You can also set your Apple development team
18
33
  explicitly:
19
34
 
20
35
  ```bash
21
- GEA_IOS_DEVELOPMENT_TEAM=ABCDE12345 targets/ios/build-ios.sh bouncing-balls-jsx device
36
+ GEA_IOS_DEVELOPMENT_TEAM=ABCDE12345 npx gea run --target ios --mode device
22
37
  ```
23
38
 
24
39
  To see the automatically detected team id:
@@ -37,7 +52,7 @@ To only verify that the app compiles for the iPhone device SDK, skip launch
37
52
  without a development team:
38
53
 
39
54
  ```bash
40
- GEA_IOS_SKIP_LAUNCH=1 targets/ios/build-ios.sh bouncing-balls-jsx device
55
+ npx gea build --target ios --mode device
41
56
  ```
42
57
 
43
58
  The Xcode project is generated under
@@ -4,32 +4,84 @@ set -euo pipefail
4
4
  ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
5
5
  IOS_DIR="$ROOT_DIR/targets/ios"
6
6
 
7
- # Resolve the gea framework through npm.
8
- GEA_CORE="$(node -e "process.stdout.write(require('fs').realpathSync('$ROOT_DIR/node_modules/@geastack/core'))" 2>/dev/null || true)"
9
- [ -n "$GEA_CORE" ] && [ -f "$GEA_CORE/package.json" ] || { echo "Cannot resolve @geastack/core via node_modules — run 'npm install' in $ROOT_DIR" >&2; exit 1; }
10
- GEA_COMPILER="$(node -e "process.stdout.write(require('fs').realpathSync('$ROOT_DIR/node_modules/@geastack/compiler'))" 2>/dev/null || true)"
11
- [ -n "$GEA_COMPILER" ] && [ -f "$GEA_COMPILER/package.json" ] || { echo "Cannot resolve @geastack/compiler via node_modules — run 'npm install' in $ROOT_DIR" >&2; exit 1; }
7
+ # The project is the directory this script was started in -- `gea build
8
+ # --target ios` spawns it with the app's own folder as cwd, and a direct run is
9
+ # made from the app folder. Captured once, before anything can move. Every
10
+ # nested `gea` call inherits the same cwd and resolves the same project, so
11
+ # none carry --project.
12
+ BUILD_INVOCATION_CWD="$(pwd -P)"
13
+
14
+ # Resolve the gea framework through npm plus the shared manifest, the same way
15
+ # build-macos.sh does. `apple` is a native npm package whose @geastack/* deps
16
+ # npm HOISTS to the app's node_modules, one or more levels above this package,
17
+ # while a linked checkout has them under its own. Looking one level below this
18
+ # package -- what this script did before -- finds nothing in either installed
19
+ # layout, so walk the node_modules chain from the app first, then from here.
20
+ resolve_gea_package_from() {
21
+ node -e '
22
+ const fs = require("node:fs")
23
+ const path = require("node:path")
24
+ let dir = process.argv[1]
25
+ while (true) {
26
+ const candidate = path.join(dir, "node_modules", process.argv[2])
27
+ if (fs.existsSync(path.join(candidate, "package.json"))) {
28
+ process.stdout.write(fs.realpathSync(candidate))
29
+ break
30
+ }
31
+ const parent = path.dirname(dir)
32
+ if (parent === dir) break
33
+ dir = parent
34
+ }
35
+ ' "$1" "$2" 2>/dev/null || true
36
+ }
37
+ resolve_gea_package() {
38
+ local start found
39
+ for start in "${2:-$BUILD_INVOCATION_CWD}" "${2:-$ROOT_DIR}"; do
40
+ found="$(resolve_gea_package_from "$start" "$1")"
41
+ if [[ -n "$found" ]]; then
42
+ printf '%s' "$found"
43
+ return 0
44
+ fi
45
+ done
46
+ }
47
+ GEA_CORE="$(resolve_gea_package @geastack/core)"
48
+ [ -n "$GEA_CORE" ] && [ -f "$GEA_CORE/package.json" ] || { echo "Cannot resolve @geastack/core from $BUILD_INVOCATION_CWD — add @geastack/apple to the app's dependencies and run 'npm install' there" >&2; exit 1; }
49
+ GEA_COMPILER="$(resolve_gea_package @geastack/compiler)"
50
+ [ -n "$GEA_COMPILER" ] && [ -f "$GEA_COMPILER/package.json" ] || { echo "Cannot resolve @geastack/compiler from $BUILD_INVOCATION_CWD — add @geastack/apple to the app's dependencies and run 'npm install' there" >&2; exit 1; }
51
+ # An app that installs @geastack/apple gets the plugin from node_modules; inside
52
+ # this repository it is a sibling package that nothing links, so name it there.
53
+ GEA_APPLE_NATIVE_PLUGIN="$(resolve_gea_package @geastack/geatsc-plugin-apple-native)"
54
+ if [[ -z "$GEA_APPLE_NATIVE_PLUGIN" && -d "$ROOT_DIR/../geatsc-plugin-apple-native" ]]; then
55
+ GEA_APPLE_NATIVE_PLUGIN="$(cd "$ROOT_DIR/../geatsc-plugin-apple-native" && pwd)"
56
+ fi
57
+ [ -n "$GEA_APPLE_NATIVE_PLUGIN" ] && [ -f "$GEA_APPLE_NATIVE_PLUGIN/dist/index.js" ] || { echo "Cannot resolve @geastack/geatsc-plugin-apple-native from $BUILD_INVOCATION_CWD" >&2; exit 1; }
12
58
  GEA_HOST_DIR="${GEA_HOST_DIR:-$GEA_CORE/../host}"
13
59
  GEA_ENGINE_DIR="${GEA_ENGINE_DIR:-$GEA_CORE/../engine}"
14
60
  GEA_ELEMENTS_DIR="${GEA_ELEMENTS_DIR:-$GEA_CORE/../elements}"
15
61
  GEA_GEAOS_PACKAGE_DIR="${GEA_GEAOS_PACKAGE_DIR:-$GEA_CORE/../geaos}"
16
- # The project is the directory this script was started in -- `gea build` spawns
17
- # it with the app's own folder as cwd, and a direct run is made from the app
18
- # folder. Captured once, before anything can move. Every nested `gea` call
19
- # inherits the same cwd and resolves the same project, so none carry --project.
20
- BUILD_INVOCATION_CWD="$(pwd -P)"
62
+ # The source manifest reads these from the ENVIRONMENT: gea_sources.sh is a
63
+ # front end for gea_sources.mjs, which runs as a child process. A plain shell
64
+ # variable never reaches it and it reports "<name> unset" while the include
65
+ # roots come back empty.
66
+ export GEA_CORE GEA_HOST_DIR GEA_ENGINE_DIR GEA_ELEMENTS_DIR GEA_GEAOS_PACKAGE_DIR
21
67
  # This script IS @geastack/apple, so the generator must bind against this
22
68
  # working copy rather than whatever installed copy it would resolve from the app.
23
69
  export GEA_APPLE_ROOT="$ROOT_DIR"
24
- GEA_CLI="${GEA_CLI_BIN:-$(node -e "process.stdout.write(require('path').join(require('fs').realpathSync('$ROOT_DIR/node_modules/@geastack/cli'), 'bin', 'gea.mjs'))" 2>/dev/null || true)}"
25
- [ -n "$GEA_CLI" ] && [ -f "$GEA_CLI" ] || { echo "Cannot resolve @geastack/cli via node_modules — run 'npm install' in $ROOT_DIR" >&2; exit 1; }
70
+ # Prefer an explicit override, then the package the app installed, then gea on PATH.
71
+ GEA_CLI="${GEA_CLI_BIN:-}"
72
+ if [[ -z "$GEA_CLI" ]]; then
73
+ GEA_CLI="$(node -e 'process.stdout.write(require("path").join(process.argv[1], "bin", "gea.mjs"))' "$(resolve_gea_package @geastack/cli)" 2>/dev/null || true)"
74
+ if [[ ! -f "$GEA_CLI" ]]; then
75
+ GEA_CLI="$(command -v gea || true)"
76
+ fi
77
+ fi
78
+ [ -n "$GEA_CLI" ] && [ -f "$GEA_CLI" ] || { echo "Cannot locate GeaStack CLI — set GEA_CLI_BIN to gea.mjs, install @geastack/cli locally, or add gea to PATH" >&2; exit 1; }
26
79
 
27
80
  # Framework sources/includes from the shared manifest (iOS keeps camera — ios_camera.mm
28
- # provides it — but has no resident-app/OTA/diagnostics services or single-app
29
- # runtime shell, so exclude those). generate-xcode-project.mjs consumes these.
81
+ # provides it — but has no OTA/diagnostics services or single-app runtime
82
+ # shell, so exclude those). generate-xcode-project.mjs consumes these.
30
83
  # shellcheck source=/dev/null
31
84
  source "$GEA_CORE/gea_sources.sh"
32
- export GEA_CORE
33
85
  export GEA_FW_CXX_SOURCES="$(gea_fw_cxx_sources | grep -vE "/(runtime|services/[a-z_]+)\.cpp\$")"
34
86
  export GEA_FW_C_SOURCES="$(gea_fw_c_sources)"
35
87
  export GEA_FW_INCLUDE_DIRS="$(gea_fw_include_flags | sed "s/^-I//")"
@@ -83,7 +135,6 @@ build_font_args=(
83
135
  generate_app() {
84
136
  local app_id="$1"
85
137
  local out_dir="$2"
86
- local prefix="${3:-}"
87
138
  local info
88
139
  local root
89
140
  local entry
@@ -91,7 +142,7 @@ generate_app() {
91
142
  info="$(node "$GEA_CLI" apps inspect "$app_id" --format shell)" || return 1
92
143
  IFS=$'\t' read -r root entry runtime _name <<< "$info"
93
144
  if [[ "$runtime" != "gea" ]]; then
94
- echo "Skipping resident $app_id: runtime=$runtime is not supported by iOS resident build" >&2
145
+ echo "iOS target only supports runtime=gea apps: $app_id is runtime=$runtime" >&2
95
146
  return 1
96
147
  fi
97
148
  local args=(
@@ -103,10 +154,9 @@ generate_app() {
103
154
  # function no iOS target defines. `build-macos.sh` passes `macos` for the
104
155
  # same reason; iOS was simply never given its half.
105
156
  --apple-platform ios
106
- # The plugin resolves itself from `@geastack/geatsc-plugin-apple-native`,
107
- # which is not installed in this repo's node_modules -- so, as
108
- # `build-macos.sh` already does, name the in-repo build directly.
109
- --geatsc-apple-native-plugin "$ROOT_DIR/packages/geatsc-plugin-apple-native/dist/index.js"
157
+ # The plugin is resolved above the way build-macos.sh resolves it: from the
158
+ # app's node_modules, or the sibling package inside this repository.
159
+ --geatsc-apple-native-plugin "$GEA_APPLE_NATIVE_PLUGIN/dist/index.js"
110
160
  --app-dir "$root"
111
161
  --entry "$entry"
112
162
  --out-dir "$out_dir"
@@ -116,14 +166,6 @@ generate_app() {
116
166
  if [[ -f "$native_plugin" ]]; then
117
167
  args+=(--extra-geatsc-plugin "$native_plugin")
118
168
  fi
119
- if [[ -n "$prefix" ]]; then
120
- args+=(
121
- --entry-symbol "${prefix}_top_level"
122
- --cpp-prelude-symbol "${prefix}_register_styles"
123
- --font-symbol-prefix "$prefix"
124
- --isolate-symbols
125
- )
126
- fi
127
169
  node "$GEA_CORE/scripts/build-gea-vite-geatsc.mjs" "${args[@]}"
128
170
  }
129
171
 
@@ -214,33 +256,7 @@ pick_ios_device() {
214
256
  rm -f "$output_json"
215
257
  }
216
258
 
217
- RESIDENT_IDS=()
218
- if [[ "$APP_ID" == "app-launcher" && "${GEA_IOS_RESIDENT_APPS:-auto}" != "none" ]]; then
219
- if [[ -n "${GEA_IOS_RESIDENT_APPS:-}" && "${GEA_IOS_RESIDENT_APPS:-}" != "auto" ]]; then
220
- read -r -a RESIDENT_IDS <<< "${GEA_IOS_RESIDENT_APPS//,/ }"
221
- else
222
- mapfile -t RESIDENT_IDS < <(
223
- node "$GEA_CLI" apps list --target esp32 | grep -vx 'direct-psram-balls' || true
224
- )
225
- fi
226
- fi
227
-
228
- if [[ ${#RESIDENT_IDS[@]} -eq 0 ]]; then
229
- generate_app "$APP_ID" "$GENERATED_DIR" ""
230
- else
231
- ACTUAL_RESIDENTS=()
232
- for id in "${RESIDENT_IDS[@]}"; do
233
- safe="${id//[^A-Za-z0-9_]/_}"
234
- prefix="gea_resident_${safe}"
235
- resident_dir="$GENERATED_DIR/residents/$id"
236
- mkdir -p "$resident_dir"
237
- generate_app "$id" "$resident_dir" "$prefix" || continue
238
- node "$IOS_DIR/generate-resident-entry.mjs" "$prefix" "$resident_dir/entry.cpp"
239
- ACTUAL_RESIDENTS+=("$id")
240
- done
241
- RESIDENT_IDS=("${ACTUAL_RESIDENTS[@]}")
242
- node "$IOS_DIR/generate-resident-registry.mjs" "$GENERATED_DIR/resident_registry.cpp" "$APP_ID" "${RESIDENT_IDS[@]}"
243
- fi
259
+ generate_app "$APP_ID" "$GENERATED_DIR"
244
260
 
245
261
  if [[ ! -f "$GENERATED_DIR/gea_runtime.cpp" ]]; then
246
262
  echo "#include \"$GEA_COMPILER/dist/targets/cpp/runtime/runtime.cpp\"" > "$GENERATED_DIR/gea_runtime.cpp"
@@ -40,6 +40,10 @@ if (!appId || !appName || !bundleId || !generatedDir || !projectPath) {
40
40
  }
41
41
 
42
42
  const iosDir = scriptDir // apple/targets/ios — this script's own directory, not the apps repo
43
+ // The framework packages are wherever build-ios.sh resolved them (the app's
44
+ // hoisted node_modules, or a linked checkout); it exports the roots it found.
45
+ const geaCore = process.env.GEA_CORE || ''
46
+ if (!geaCore) fail('GEA_CORE is not set: run this generator through build-ios.sh, which resolves @geastack/core for it.')
43
47
  const plistOut = path.join(projectPath, 'Info.plist')
44
48
  let appMeta
45
49
  try {
@@ -140,25 +144,6 @@ function findFontResources() {
140
144
  .sort()
141
145
  }
142
146
 
143
- function findResidentSources() {
144
- const residentsDir = path.join(generatedDir, 'residents')
145
- if (!fs.existsSync(residentsDir)) return []
146
- return fs.readdirSync(residentsDir, { withFileTypes: true })
147
- .filter((entry) => entry.isDirectory())
148
- .flatMap((entry) => {
149
- const dir = path.join(residentsDir, entry.name)
150
- return [
151
- ...readGeatscSources(dir),
152
- source(path.join(dir, 'entry.cpp')),
153
- source(path.join(dir, 'gea_embedded_font_generated.cpp')),
154
- source(path.join(dir, 'gea_embedded_assets_generated.cpp')),
155
- ].filter(existing)
156
- })
157
- }
158
-
159
- const residentRegistry = source(path.join(generatedDir, 'resident_registry.cpp'))
160
- const hasResidentRegistry = existing(residentRegistry)
161
- const residentSources = hasResidentRegistry ? [residentRegistry, ...findResidentSources()] : []
162
147
  const appleNativeBridgeSource = source(path.join(generatedDir, 'gea/apple/native_bridge.mm'), 'sourcecode.cpp.objcpp')
163
148
  const generatedProgramSources = readGeatscSources(
164
149
  generatedDir,
@@ -194,25 +179,21 @@ const sources = [
194
179
  ...appNativeSources,
195
180
  // Framework C/C++ sources come from @geastack/core/gea_sources.sh,
196
181
  // passed by build-ios.sh via env (iOS-curated: camera kept since ios_camera.mm
197
- // provides it; resident-app/OTA/diagnostics services + the single-app runtime
182
+ // provides it; OTA/diagnostics services + the single-app runtime
198
183
  // shell excluded — iOS uses its own UIKit run loop). Single source of truth.
199
184
  ...(process.env.GEA_FW_C_SOURCES || '').split('\n').filter(Boolean).map((f) => source(f, 'sourcecode.c.c')),
200
185
  ...(process.env.GEA_FW_CXX_SOURCES || '').split('\n').filter(Boolean).map((f) => source(f)),
201
- // gea_app_entry / resident_apps are mode-specific (deliberately not in the manifest).
202
- ...(hasResidentRegistry
203
- ? []
204
- : [
205
- source(path.join(geaCore, 'gea_app_entry.cpp')),
206
- source(path.join(geaCore, '../geaos/resident_apps.cpp')),
207
- ]),
208
- ...(hasResidentRegistry ? residentSources : generatedProgramSources),
186
+ // gea_app_entry.cpp provides Application::init/frame, which calls the one
187
+ // __gea_top_level geatsc emitted (deliberately not in the manifest).
188
+ source(path.join(geaCore, 'gea_app_entry.cpp')),
189
+ ...generatedProgramSources,
209
190
  appleNativeBridgeSource,
210
191
  ].filter(existing)
211
192
 
212
193
  const fontGenerated = source(path.join(generatedDir, 'gea_embedded_font_generated.cpp'))
213
- if (!hasResidentRegistry && existing(fontGenerated)) sources.push(fontGenerated)
194
+ if (existing(fontGenerated)) sources.push(fontGenerated)
214
195
  const assetsGenerated = source(path.join(generatedDir, 'gea_embedded_assets_generated.cpp'))
215
- if (!hasResidentRegistry && existing(assetsGenerated)) sources.push(assetsGenerated)
196
+ if (existing(assetsGenerated)) sources.push(assetsGenerated)
216
197
  const hasGeneratedFonts = sources.some((entry) => path.basename(entry.file) === 'gea_embedded_font_generated.cpp')
217
198
 
218
199
  const fontResources = findFontResources().map((file) => resource(file))
@@ -10,7 +10,6 @@
10
10
  #include "font_registry.h"
11
11
  #include "image.h"
12
12
  #include "ios_renderer.h"
13
- #include "resident_apps.h"
14
13
  #include "ios_root_background.h"
15
14
  #include "pixel.h"
16
15
  #include "ui/tree_internal.h"
@@ -60,19 +59,6 @@ NSString *NSStringFromAttr(const char *value)
60
59
  return [NSString stringWithUTF8String:value ? value : ""] ?: @"";
61
60
  }
62
61
 
63
- void queueInitialResidentAppFromProcessArguments()
64
- {
65
- if (!gea::framework::apps::ResidentApps::isEnabled()) return;
66
- NSArray<NSString *> *arguments = NSProcessInfo.processInfo.arguments;
67
- for (NSUInteger i = 0; i + 1 < arguments.count; ++i) {
68
- if (![arguments[i] isEqualToString:@"--gea-initial-app"]) continue;
69
- NSString *appId = arguments[i + 1];
70
- if (appId.length > 0) gea::framework::apps::ResidentApps::requestLaunch(appId.UTF8String);
71
- }
72
- const char *envAppId = std::getenv("GEA_IOS_INITIAL_APP");
73
- if (envAppId && envAppId[0]) gea::framework::apps::ResidentApps::requestLaunch(envAppId);
74
- }
75
-
76
62
  NSTextAlignment textAlignmentForStyle(int align)
77
63
  {
78
64
  switch (align) {
@@ -567,49 +553,12 @@ bool attributedStringHasTextDecoration(NSAttributedString *value)
567
553
  @property(nonatomic, strong) UIView *rootView;
568
554
  @property(nonatomic, strong) GeaDisplayView *displayView;
569
555
  @property(nonatomic, strong) UIView *nativeRootView;
570
- @property(nonatomic, strong) UIButton *launcherButton;
571
556
  @property(nonatomic, strong) CADisplayLink *displayLink;
572
557
  - (void)installNativeRootView:(UIView *)view;
573
558
  @end
574
559
 
575
560
  @implementation GeaAppDelegate
576
561
 
577
- - (BOOL)shouldShowLauncherButton
578
- {
579
- if (!gea::framework::apps::ResidentApps::isEnabled()) return NO;
580
- const char *activeId = gea::framework::apps::ResidentApps::activeId();
581
- return activeId && std::strcmp(activeId, gea::framework::apps::AppManager::launcherAppId()) != 0;
582
- }
583
-
584
- - (void)syncLauncherButton
585
- {
586
- if (!self.launcherButton || !self.rootView) return;
587
- const BOOL show = [self shouldShowLauncherButton];
588
- UIEdgeInsets safeInsets = self.rootView.safeAreaInsets;
589
- const CGRect bounds = self.rootView.bounds;
590
- const CGFloat size = 52.0;
591
- const CGFloat bottomMargin = 12.0;
592
- const CGFloat x = (bounds.size.width - size) * 0.5;
593
- const CGFloat y = bounds.size.height - safeInsets.bottom - bottomMargin - size;
594
- self.launcherButton.frame = CGRectMake(std::max<CGFloat>(safeInsets.left, x),
595
- std::max<CGFloat>(safeInsets.top, y),
596
- size,
597
- size);
598
- self.launcherButton.layer.cornerRadius = size * 0.5;
599
- self.launcherButton.hidden = !show;
600
- self.launcherButton.userInteractionEnabled = show;
601
- if (show) [self.rootView bringSubviewToFront:self.launcherButton];
602
- }
603
-
604
- - (void)returnToLauncher:(id)sender
605
- {
606
- (void)sender;
607
- if (gea::framework::apps::AppManager::returnRunningAppToLauncher("iOS launcher button")) {
608
- self.launcherButton.hidden = YES;
609
- self.launcherButton.userInteractionEnabled = NO;
610
- }
611
- }
612
-
613
562
  - (void)layoutDisplayViewInSafeArea
614
563
  {
615
564
  if (!self.rootView || !self.displayView) return;
@@ -620,7 +569,6 @@ bool attributedStringHasTextDecoration(NSAttributedString *value)
620
569
  if (frame.size.width <= 0 || frame.size.height <= 0) frame = self.rootView.bounds;
621
570
  self.displayView.frame = frame;
622
571
  if (self.nativeRootView) self.nativeRootView.frame = self.rootView.bounds;
623
- [self syncLauncherButton];
624
572
  }
625
573
 
626
574
  - (void)installNativeRootView:(UIView *)view
@@ -636,13 +584,8 @@ bool attributedStringHasTextDecoration(NSAttributedString *value)
636
584
  view.userInteractionEnabled = YES;
637
585
  self.displayView.hidden = YES;
638
586
  self.displayView.userInteractionEnabled = NO;
639
- if (self.launcherButton.superview == self.rootView) {
640
- [self.rootView insertSubview:view belowSubview:self.launcherButton];
641
- } else {
642
- [self.rootView addSubview:view];
643
- }
587
+ [self.rootView addSubview:view];
644
588
  [view setNeedsLayout];
645
- [self syncLauncherButton];
646
589
  }
647
590
 
648
591
  - (CGSize)viewportSize
@@ -686,23 +629,6 @@ bool attributedStringHasTextDecoration(NSAttributedString *value)
686
629
  self.displayView = [[GeaDisplayView alloc] initWithFrame:CGRectZero];
687
630
  self.displayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
688
631
  [self.rootView addSubview:self.displayView];
689
- self.launcherButton = [UIButton buttonWithType:UIButtonTypeSystem];
690
- self.launcherButton.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.62];
691
- self.launcherButton.tintColor = UIColor.whiteColor;
692
- self.launcherButton.layer.borderWidth = 1.0;
693
- self.launcherButton.layer.borderColor = [UIColor colorWithWhite:1.0 alpha:0.22].CGColor;
694
- self.launcherButton.clipsToBounds = YES;
695
- self.launcherButton.hidden = YES;
696
- self.launcherButton.accessibilityLabel = @"App Launcher";
697
- if (@available(iOS 13.0, *)) {
698
- UIImage *image = [UIImage systemImageNamed:@"square.grid.2x2"];
699
- [self.launcherButton setImage:image forState:UIControlStateNormal];
700
- } else {
701
- [self.launcherButton setTitle:@"Apps" forState:UIControlStateNormal];
702
- self.launcherButton.titleLabel.font = [UIFont boldSystemFontOfSize:13.0];
703
- }
704
- [self.launcherButton addTarget:self action:@selector(returnToLauncher:) forControlEvents:UIControlEventTouchUpInside];
705
- [self.rootView addSubview:self.launcherButton];
706
632
  controller.view = self.rootView;
707
633
  self.window.rootViewController = controller;
708
634
  [self.window makeKeyAndVisible];
@@ -731,7 +657,6 @@ bool attributedStringHasTextDecoration(NSAttributedString *value)
731
657
  // ImageStore decodes straight to full-colour RGBA8888 native pixels (no 565
732
658
  // quantization, no separate retained buffer) for native UIImageView.
733
659
  gea::framework::app::Application::init(viewportWidth, viewportHeight, devicePixelRatio);
734
- queueInitialResidentAppFromProcessArguments();
735
660
  [self syncAppBackgroundColor];
736
661
  [self tick:nil];
737
662
 
@@ -748,27 +673,8 @@ bool attributedStringHasTextDecoration(NSAttributedString *value)
748
673
  - (void)tick:(CADisplayLink *)link
749
674
  {
750
675
  (void)link;
751
- {
752
- char pendingId[64] = "";
753
- if (gea::framework::apps::ResidentApps::consumeLaunch(pendingId, sizeof(pendingId)) &&
754
- gea::framework::apps::ResidentApps::select(pendingId)) {
755
- CGSize viewport = [self viewportSize];
756
- const CGFloat viewportScale = [self viewportScale];
757
- self.displayView.contentScaleFactor = viewportScale;
758
- const int viewportWidth = std::max(1, static_cast<int>(std::ceil(viewport.width)));
759
- const int viewportHeight = std::max(1, static_cast<int>(std::ceil(viewport.height)));
760
- // gea's internal CSS device-pixel-ratio stays 1 (see init above): the
761
- // layout is logical points; UIKit handles retina via contentScaleFactor.
762
- const int devicePixelRatio = 1;
763
- gea::ios::IosRenderer::instance().teardown();
764
- gea_ios_display_set_viewport_size(viewportWidth, viewportHeight);
765
- gea::platform::display::Display::init();
766
- gea::framework::app::Application::init(viewportWidth, viewportHeight, devicePixelRatio);
767
- }
768
- }
769
676
  gea::framework::app::Application::frame(gea_embedded_now_ms());
770
677
  [self syncAppBackgroundColor];
771
- [self syncLauncherButton];
772
678
  auto &tree = gea::embedded::ui::Tree::instance();
773
679
  const int root = tree.mountedRoot();
774
680
  if (!self.nativeRootView && root >= 0) {
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env node
2
- // Per-resident-app entry.cpp generator for the iOS in-process app launcher.
3
-
4
- import { writeFileSync } from 'node:fs'
5
-
6
- const [, , prefix, outPath] = process.argv
7
- if (!prefix || !outPath) {
8
- console.error('Usage: generate-resident-entry.mjs <prefix> <out-cpp>')
9
- process.exit(1)
10
- }
11
-
12
- const code = `// Auto-generated by targets/ios/generate-resident-entry.mjs. Do not edit.
13
- #include "app.h"
14
- #include "gea/embedded.h"
15
- #include "graphics/font.h"
16
- #include "host/rtc.h"
17
- #include "host/timers.h"
18
- #include "host/websocket.h"
19
- #include "ui/document.h"
20
- #include "ui/style.h"
21
-
22
- extern void ${prefix}_top_level();
23
- extern void ${prefix}_register_styles();
24
- extern void gea_cpp_clear_microtasks();
25
-
26
- namespace gea::framework::app::generated::${prefix} {
27
- void drainMicrotasks();
28
- } // namespace gea::framework::app::generated::${prefix}
29
-
30
- void __attribute__((weak)) ${prefix}_register_styles() {}
31
-
32
- void ${prefix}_init(int width, int height, double devicePixelRatio)
33
- {
34
- \tgea::embedded::ui::Document::setPreferredMountSize(width, height);
35
- \tgea::embedded::ui::setViewportMetrics(width, height, devicePixelRatio);
36
- \tgea::host::resetAnimationFrameCallbacks();
37
- \tgea::host::resetScheduledTimers();
38
- \tgea_cpp_clear_microtasks();
39
- \tgea::embedded::ui::StyleSheet::instance().clear();
40
- \t${prefix}_register_styles();
41
- \tgea::embedded::ui::Document::instance().clear();
42
- \t${prefix}_top_level();
43
- \tgea::embedded::ui::Document::instance().refreshMountedIfDirty();
44
- }
45
-
46
- void ${prefix}_frame(int timestampMs)
47
- {
48
- \tgea::framework::app::generated::${prefix}::drainMicrotasks();
49
- \tgea::host::runAnimationFrameCallbacks(static_cast<double>(timestampMs));
50
- \tgea::framework::app::generated::${prefix}::drainMicrotasks();
51
- \tgea::host::websocket::runCallbacks();
52
- \tgea::host::rtc::runCallbacks();
53
- \tgea::embedded::ui::Document::instance().frame(timestampMs);
54
- \tgea::embedded::ui::Document::instance().refreshMountedIfDirty();
55
- }
56
-
57
- void ${prefix}_settings_toggle() {}
58
- int ${prefix}_mirror_begin_snapshot() { return 0; }
59
- int ${prefix}_mirror_begin_diff() { return 0; }
60
- int ${prefix}_mirror_next_record(unsigned char *, int) { return 0; }
61
- void ${prefix}_mirror_clear_dirty() {}
62
- void ${prefix}_ble_connected() {}
63
- void ${prefix}_ble_disconnected() {}
64
- void ${prefix}_ble_bound() {}
65
- `
66
-
67
- writeFileSync(outPath, code)
@@ -1,162 +0,0 @@
1
- #!/usr/bin/env node
2
- // Generates the iOS resident-app registry used by app-launcher simulator builds.
3
-
4
- import { writeFileSync } from 'node:fs'
5
-
6
- const [, , outPath, launcherId, ...appIds] = process.argv
7
- if (!outPath || !launcherId || appIds.length === 0) {
8
- console.error('Usage: generate-resident-registry.mjs <out-cpp> <launcher-id> <app-id...>')
9
- process.exit(1)
10
- }
11
-
12
- const safe = (id) => id.replace(/[^A-Za-z0-9_]/g, '_')
13
- const prefix = (id) => `gea_resident_${safe(id)}`
14
- const quote = (s) => s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
15
-
16
- const lines = []
17
- lines.push('// Auto-generated by targets/ios/generate-resident-registry.mjs.')
18
- lines.push('// Do not edit.')
19
- lines.push('#include "app.h"')
20
- lines.push('#include "resident_apps.h"')
21
- lines.push('#include "graphics/font.h"')
22
- lines.push('')
23
- lines.push('#include <cstdio>')
24
- lines.push('#include <cstring>')
25
- lines.push('#include <mutex>')
26
- lines.push('')
27
-
28
- for (const id of appIds) {
29
- const p = prefix(id)
30
- lines.push(`extern void ${p}_init(int width, int height, double devicePixelRatio);`)
31
- lines.push(`extern void ${p}_frame(int timestampMs);`)
32
- lines.push(`extern void ${p}_settings_toggle();`)
33
- lines.push(`extern const gea::framework::graphics::RasterizedFontData *${p}_lookup_font(int fontId);`)
34
- lines.push(`extern const gea::framework::graphics::RasterizedFontData *${p}_lookup_font_for_family(int familyId, int sizePx);`)
35
- lines.push(`extern int ${p}_lookup_font_family(const char *family);`)
36
- lines.push(`extern const char *${p}_lookup_font_family_name(int familyId);`)
37
- }
38
- lines.push('')
39
-
40
- lines.push('namespace gea::framework::apps {')
41
- lines.push('namespace {')
42
- lines.push(`constexpr const char *kLauncherAppId = "${quote(launcherId)}";`)
43
- lines.push('std::mutex &pendingLock() { static std::mutex lock; return lock; }')
44
- lines.push('char pendingLaunchAppId[64] = "";')
45
- lines.push('bool pendingLaunchValid = false;')
46
- lines.push('')
47
- lines.push('const ResidentApp residentApps[] = {')
48
- for (const id of appIds) {
49
- const p = prefix(id)
50
- lines.push('\t{')
51
- lines.push(`\t\t.id = "${quote(id)}",`)
52
- lines.push('\t\t.displayFlushChunkRows = 0,')
53
- lines.push('\t\t.displayFlushQueueDepth = 0,')
54
- lines.push(`\t\t.init = ${p}_init,`)
55
- lines.push(`\t\t.frame = ${p}_frame,`)
56
- lines.push(`\t\t.settingsToggle = ${p}_settings_toggle,`)
57
- lines.push(`\t\t.lookupFont = ${p}_lookup_font,`)
58
- lines.push(`\t\t.lookupFontForFamily = ${p}_lookup_font_for_family,`)
59
- lines.push(`\t\t.lookupFontFamily = ${p}_lookup_font_family,`)
60
- lines.push(`\t\t.lookupFontFamilyName = ${p}_lookup_font_family_name,`)
61
- lines.push('\t},')
62
- }
63
- lines.push('};')
64
- lines.push(`constexpr int kResidentAppCount = ${appIds.length};`)
65
- lines.push('const ResidentApp *activeApp = &residentApps[0];')
66
- lines.push('')
67
- lines.push('const ResidentApp *findApp(const char *appId)')
68
- lines.push('{')
69
- lines.push("\tif (!appId || appId[0] == '\\0') return nullptr;")
70
- lines.push('\tfor (int i = 0; i < kResidentAppCount; i++) {')
71
- lines.push('\t\tif (std::strcmp(residentApps[i].id, appId) == 0) return &residentApps[i];')
72
- lines.push('\t}')
73
- lines.push('\treturn nullptr;')
74
- lines.push('}')
75
- lines.push('} // namespace')
76
- lines.push('')
77
- lines.push('namespace generated {')
78
- lines.push('const ResidentApp *activeResidentApp() { return activeApp; }')
79
- lines.push('} // namespace generated')
80
- lines.push('')
81
- lines.push('bool ResidentApps::isEnabled() { return kResidentAppCount > 0; }')
82
- lines.push('const char *ResidentApps::activeId() { return activeApp ? activeApp->id : nullptr; }')
83
- lines.push('')
84
- lines.push('bool ResidentApps::select(const char *appId)')
85
- lines.push('{')
86
- lines.push('\tconst ResidentApp *next = findApp(appId);')
87
- lines.push('\tif (!next) return false;')
88
- lines.push('\tactiveApp = next;')
89
- lines.push('\treturn true;')
90
- lines.push('}')
91
- lines.push('')
92
- lines.push('bool ResidentApps::requestLaunch(const char *appId)')
93
- lines.push('{')
94
- lines.push('\tif (!findApp(appId)) return false;')
95
- lines.push('\tstd::lock_guard guard(pendingLock());')
96
- lines.push('\tstd::snprintf(pendingLaunchAppId, sizeof(pendingLaunchAppId), "%s", appId);')
97
- lines.push('\tpendingLaunchValid = true;')
98
- lines.push('\treturn true;')
99
- lines.push('}')
100
- lines.push('')
101
- lines.push('bool ResidentApps::consumeLaunch(char *dst, std::size_t cap)')
102
- lines.push('{')
103
- lines.push('\tstd::lock_guard guard(pendingLock());')
104
- lines.push('\tif (!pendingLaunchValid) return false;')
105
- lines.push('\tif (dst && cap > 0) std::snprintf(dst, cap, "%s", pendingLaunchAppId);')
106
- lines.push('\tpendingLaunchValid = false;')
107
- lines.push("\tpendingLaunchAppId[0] = '\\0';")
108
- lines.push('\treturn true;')
109
- lines.push('}')
110
- lines.push('')
111
- lines.push('bool ResidentApps::returnToLauncher(const char *trigger)')
112
- lines.push('{')
113
- lines.push('\t(void)trigger;')
114
- lines.push('\tif (!activeApp || std::strcmp(activeApp->id, kLauncherAppId) == 0) return false;')
115
- lines.push('\treturn ResidentApps::requestLaunch(kLauncherAppId);')
116
- lines.push('}')
117
- lines.push('')
118
- lines.push('} // namespace gea::framework::apps')
119
- lines.push('')
120
- lines.push('namespace gea::framework::app {')
121
- lines.push('void Application::init(int width, int height, double devicePixelRatio)')
122
- lines.push('{')
123
- lines.push('\tconst auto *active = gea::framework::apps::generated::activeResidentApp();')
124
- lines.push('\tif (active && active->init) active->init(width, height, devicePixelRatio);')
125
- lines.push('}')
126
- lines.push('void Application::frame(int timestampMs)')
127
- lines.push('{')
128
- lines.push('\tconst auto *active = gea::framework::apps::generated::activeResidentApp();')
129
- lines.push('\tif (active && active->frame) active->frame(timestampMs);')
130
- lines.push('}')
131
- lines.push('void Application::toggleSettings()')
132
- lines.push('{')
133
- lines.push('\tconst auto *active = gea::framework::apps::generated::activeResidentApp();')
134
- lines.push('\tif (active && active->settingsToggle) active->settingsToggle();')
135
- lines.push('}')
136
- lines.push('} // namespace gea::framework::app')
137
- lines.push('')
138
- lines.push('namespace gea::framework::graphics::generated {')
139
- lines.push('void ensureLinked() {}')
140
- lines.push('const RasterizedFontData *lookupFont(int fontId)')
141
- lines.push('{')
142
- lines.push('\tconst auto *active = gea::framework::apps::generated::activeResidentApp();')
143
- lines.push('\treturn active && active->lookupFont ? active->lookupFont(fontId) : nullptr;')
144
- lines.push('}')
145
- lines.push('const RasterizedFontData *lookupFontForFamily(int familyId, int sizePx)')
146
- lines.push('{')
147
- lines.push('\tconst auto *active = gea::framework::apps::generated::activeResidentApp();')
148
- lines.push('\treturn active && active->lookupFontForFamily ? active->lookupFontForFamily(familyId, sizePx) : nullptr;')
149
- lines.push('}')
150
- lines.push('int lookupFontFamily(const char *family)')
151
- lines.push('{')
152
- lines.push('\tconst auto *active = gea::framework::apps::generated::activeResidentApp();')
153
- lines.push('\treturn active && active->lookupFontFamily ? active->lookupFontFamily(family) : -1;')
154
- lines.push('}')
155
- lines.push('const char *lookupFontFamilyName(int familyId)')
156
- lines.push('{')
157
- lines.push('\tconst auto *active = gea::framework::apps::generated::activeResidentApp();')
158
- lines.push('\treturn active && active->lookupFontFamilyName ? active->lookupFontFamilyName(familyId) : nullptr;')
159
- lines.push('}')
160
- lines.push('} // namespace gea::framework::graphics::generated')
161
-
162
- writeFileSync(outPath, lines.join('\n') + '\n')