diff --git a/.macroscope/check-run-agents/effect-service-conventions.md b/.macroscope/check-run-agents/effect-service-conventions.md
index afbdc55ba60..09f4db07965 100644
--- a/.macroscope/check-run-agents/effect-service-conventions.md
+++ b/.macroscope/check-run-agents/effect-service-conventions.md
@@ -43,6 +43,15 @@ Review changed TypeScript and directly affected call sites for the conventions b
- Keep implementation-specific names when an abstract port module contains one of several possible implementations, for example `makeCloudflaredRelayClient` and `layerCloudflared` in `RelayClient.ts`.
- `infra/relay/src/db.ts` is an intentional exception: an inline `Layer.succeed(RelayDb, db)` is acceptable without generic `make`/`layer` exports.
+## Dependency acquisition and runtime boundaries
+
+- Production service construction must acquire Effect service dependencies from the environment with `yield* Foo.Foo`, and its `make`/`layer` types must expose those requirements. Flag factories or constructors that accept `Foo["Service"]` (or a plain object whose methods return `Effect`) when that value is an implementation dependency owned by the service. Passing service instances explicitly is acceptable in tests and integration harnesses; passing pure configuration, immutable domain values, or deliberate callback strategies is not service injection.
+- Do not hide dependencies in module globals, closures over singleton services, or `Layer.succeed` implementations that call runtime-backed or imperative APIs. Trace helpers used by a supposedly synchronous layer far enough to verify that asynchronous services are represented in the Effect environment.
+- `ManagedRuntime.make`, `runPromise`, and `runPromiseExit` belong at explicit application/framework boundaries such as React, native callback, CLI, or HTTP adapters. Flag their use in domain services, repositories, persistence implementations, and service constructors. A clearly named imperative adapter may bridge an Effect service into a Promise API, but it must not become a dependency of another Effect service.
+- Do not create per-feature managed runtimes or Atom runtimes to smuggle the same owned resource into multiple consumers. Compose the resource once in an application-owned layer/runtime and provide its context to integration runtimes.
+- When acquisition can fail but a caller must retain fallback behavior, keep the failure typed in Effect rather than bypassing the layer through an imperative runtime. Model unavailability in service operations or with an explicit optional-service layer so downstream recovery remains visible and testable.
+- During review, search touched code and affected call sites for service-instance parameters, `Layer.succeed`, `ManagedRuntime.make`, and `.runPromise`/`.runPromiseExit`. Verify that each occurrence is a legitimate test seam, pure value injection, or application boundary—not fake dependency injection or a hidden runtime.
+
## Errors and predicates
- Define service failures with `Schema.TaggedErrorClass` and structured attributes. Derive `message` from those attributes rather than storing an unstructured message as the only data.
diff --git a/app.json b/app.json
new file mode 100644
index 00000000000..306ca48315c
--- /dev/null
+++ b/app.json
@@ -0,0 +1,3 @@
+{
+ "expo": {}
+}
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index c8418e31bac..ceac5792bbb 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -31,7 +31,7 @@
"@effect/vitest": "catalog:",
"@types/node": "catalog:",
"cross-env": "^10.1.0",
- "electron-builder": "26.8.1",
+ "electron-builder": "26.15.6",
"tailwindcss": "^4.0.0",
"vite-plus": "catalog:"
},
diff --git a/apps/marketing/src/pages/index.astro b/apps/marketing/src/pages/index.astro
index dbd9f7c4f23..c20c3728021 100644
--- a/apps/marketing/src/pages/index.astro
+++ b/apps/marketing/src/pages/index.astro
@@ -254,7 +254,7 @@ const mobileEndorsementRows = [
@@ -403,12 +403,14 @@ const mobileEndorsementRows = [
const label = document.getElementById("download-label");
const ctaBtn = document.getElementById("cta-download-btn") as HTMLAnchorElement | null;
const ctaLabel = document.getElementById("cta-download-label");
+ const kbd = document.getElementById("pr-button-kbd");
const platform = detectPlatform();
if (!platform) return;
document.documentElement.dataset.platform = platform.os;
if (label) label.textContent = platform.label;
if (ctaLabel) ctaLabel.textContent = platform.label;
+ if (kbd && platform.os !== "mac") kbd.textContent = "Ctrl ⏎";
try {
const release = await fetchLatestRelease();
diff --git a/apps/mobile/README.md b/apps/mobile/README.md
index ad0242c2714..552216bf353 100644
--- a/apps/mobile/README.md
+++ b/apps/mobile/README.md
@@ -34,6 +34,30 @@ Build and run the local iOS dev client:
vp run ios:dev
```
+If your Xcode account only has a Personal Team, use a bundle identifier you control and opt into the
+reduced-capability local build. Personal Team builds omit the widget extension, push entitlement, and
+native Sign in with Apple entitlement; builds without this opt-in are unchanged.
+
+```bash
+T3CODE_IOS_PERSONAL_TEAM=1 \
+T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID=com.example.t3code.dev \
+vp run ios:dev
+```
+
+Build and install a self-contained Release app that does not need Metro:
+
+```bash
+vp run ios:release
+```
+
+The Personal Team equivalent also needs a unique bundle identifier:
+
+```bash
+T3CODE_IOS_PERSONAL_TEAM=1 \
+T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID=com.example.t3code \
+vp run ios:release
+```
+
Build and run the local iOS preview app:
```bash
diff --git a/apps/mobile/app.config.ts b/apps/mobile/app.config.ts
index 098b830aca3..f9bf88f45ce 100644
--- a/apps/mobile/app.config.ts
+++ b/apps/mobile/app.config.ts
@@ -8,6 +8,20 @@ const repoEnv = loadRepoEnv();
Object.assign(process.env, repoEnv);
const APP_VARIANT = resolveAppVariant(repoEnv.APP_VARIANT);
+const isIosPersonalTeamBuild = repoEnv.T3CODE_IOS_PERSONAL_TEAM === "1";
+
+const personalTeamBundleIdentifier = repoEnv.T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID?.trim();
+const IOS_BUNDLE_IDENTIFIER_PATTERN = /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/;
+
+if (
+ isIosPersonalTeamBuild &&
+ (!personalTeamBundleIdentifier ||
+ !IOS_BUNDLE_IDENTIFIER_PATTERN.test(personalTeamBundleIdentifier))
+) {
+ throw new Error(
+ "T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID must be a reverse-DNS identifier such as com.example.t3code when T3CODE_IOS_PERSONAL_TEAM=1.",
+ );
+}
const VARIANT_CONFIG: Record<
AppVariant,
@@ -63,6 +77,36 @@ function resolveAppVariant(value: string | undefined): AppVariant {
const variant = VARIANT_CONFIG[APP_VARIANT];
+const dmSansFonts = {
+ regular: "@expo-google-fonts/dm-sans/400Regular/DMSans_400Regular.ttf",
+ medium: "@expo-google-fonts/dm-sans/500Medium/DMSans_500Medium.ttf",
+ bold: "@expo-google-fonts/dm-sans/700Bold/DMSans_700Bold.ttf",
+} as const;
+
+const widgetsPlugin: NonNullable[number] = [
+ "expo-widgets",
+ {
+ bundleIdentifier: `${variant.iosBundleIdentifier}.widgets`,
+ groupIdentifier: `group.${variant.iosBundleIdentifier}`,
+ enablePushNotifications: true,
+ // Agent activity can update many times an hour; without the
+ // frequent-updates entitlement iOS throttles the update budget sooner.
+ frequentUpdates: true,
+ widgets: [
+ {
+ name: "AgentActivity",
+ displayName: "Agent Activity",
+ description: "Shows the current state of active T3 Code agents.",
+ supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"],
+ },
+ ],
+ },
+];
+
+// These aliases match the fonts' PostScript names on iOS. Register the same
+// names on Android so React Native and the native composer use one set of
+// family names without waiting for runtime font loading.
+
const config: ExpoConfig = {
name: variant.appName,
slug: "t3-code",
@@ -115,16 +159,59 @@ const config: ExpoConfig = {
backgroundImage: "./assets/android-icon-background.png",
monochromeImage: "./assets/android-icon-monochrome.png",
},
- predictiveBackGestureEnabled: false,
+ // Opts into OnBackInvokedCallback-based back dispatch (Android 13+).
+ // JS back handling survives it via react-native's Android 16 shim plus
+ // withAndroidPredictiveBackCompat on Android 13-15.
+ predictiveBackGestureEnabled: true,
},
web: {
favicon: "./assets/favicon.png",
},
plugins: [
- "expo-font",
+ "expo-asset",
+ [
+ "expo-font",
+ {
+ ios: {
+ fonts: [dmSansFonts.regular, dmSansFonts.medium, dmSansFonts.bold],
+ },
+ android: {
+ fonts: [
+ {
+ fontFamily: "DMSans-Regular",
+ fontDefinitions: [{ path: dmSansFonts.regular, weight: 400 }],
+ },
+ {
+ fontFamily: "DMSans-Medium",
+ fontDefinitions: [{ path: dmSansFonts.medium, weight: 500 }],
+ },
+ {
+ fontFamily: "DMSans-Bold",
+ fontDefinitions: [{ path: dmSansFonts.bold, weight: 700 }],
+ },
+ ],
+ },
+ },
+ ],
"expo-secure-store",
- ["@clerk/expo", { theme: "./clerk-theme.json" }],
+ "expo-sqlite",
+ // appleSignIn must be gated here: withoutIosPersonalTeamCapabilities.cjs runs before
+ // plugins earlier in this array, so it cannot strip the entitlement Clerk would add.
+ ["@clerk/expo", { theme: "./clerk-theme.json", appleSignIn: !isIosPersonalTeamBuild }],
"expo-web-browser",
+ [
+ "expo-quick-actions",
+ {
+ // Adaptive launcher-shortcut icon; referenced by resource name from
+ // the shortcut items set in src/features/shortcuts.
+ androidIcons: {
+ shortcut_icon: {
+ foregroundImage: "./assets/android-icon-foreground.png",
+ backgroundColor: "#E6F4FE",
+ },
+ },
+ },
+ ],
[
"expo-camera",
{
@@ -164,31 +251,19 @@ const config: ExpoConfig = {
// expo-widgets' — its dangerous mod wipes ios/ExpoWidgetsTarget/ (which
// would delete the asset catalog) and its xcodeproj mod creates the widget
// target (which must exist before the compile phase can be attached).
- "./plugins/withWidgetLogoAsset.cjs",
- [
- "expo-widgets",
- {
- bundleIdentifier: `${variant.iosBundleIdentifier}.widgets`,
- groupIdentifier: `group.${variant.iosBundleIdentifier}`,
- enablePushNotifications: true,
- // Agent activity can update many times an hour; without the
- // frequent-updates entitlement iOS throttles the update budget sooner.
- frequentUpdates: true,
- widgets: [
- {
- name: "AgentActivity",
- displayName: "Agent Activity",
- description: "Shows the current state of active T3 Code agents.",
- supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"],
- },
- ],
- },
- ],
+ ...(!isIosPersonalTeamBuild ? ["./plugins/withWidgetLogoAsset.cjs", widgetsPlugin] : []),
"./plugins/withIosSceneLifecycle.cjs",
+ "./plugins/withIosCrashLog.cjs",
"./plugins/withAndroidCleartextTraffic.cjs",
+ "./plugins/withAndroidGradleHeap.cjs",
+ "./plugins/withAndroidModernPopupMenu.cjs",
+ "./plugins/withAndroidModernAlertDialog.cjs",
+ "./plugins/withAndroidPredictiveBackCompat.cjs",
+ ...(isIosPersonalTeamBuild ? ["./plugins/withoutIosPersonalTeamCapabilities.cjs"] : []),
],
extra: {
appVariant: APP_VARIANT,
+ iosPersonalTeamBuild: isIosPersonalTeamBuild,
relay: {
url: repoEnv.T3CODE_RELAY_URL ?? null,
},
diff --git a/apps/mobile/eas.json b/apps/mobile/eas.json
index 82c707eac12..a1d315b7e14 100644
--- a/apps/mobile/eas.json
+++ b/apps/mobile/eas.json
@@ -8,7 +8,8 @@
"development": {
"corepack": true,
"env": {
- "APP_VARIANT": "development"
+ "APP_VARIANT": "development",
+ "NODE_OPTIONS": "--max-old-space-size=4096"
},
"channel": "development",
"developmentClient": true,
@@ -17,7 +18,8 @@
"preview": {
"corepack": true,
"env": {
- "APP_VARIANT": "preview"
+ "APP_VARIANT": "preview",
+ "NODE_OPTIONS": "--max-old-space-size=4096"
},
"channel": "preview",
"environment": "preview",
@@ -27,7 +29,8 @@
"corepack": true,
"env": {
"APP_VARIANT": "preview",
- "MOBILE_VERSION_POLICY": "fingerprint"
+ "MOBILE_VERSION_POLICY": "fingerprint",
+ "NODE_OPTIONS": "--max-old-space-size=4096"
},
"channel": "preview",
"environment": "preview",
@@ -40,7 +43,8 @@
"production": {
"corepack": true,
"env": {
- "APP_VARIANT": "production"
+ "APP_VARIANT": "production",
+ "NODE_OPTIONS": "--max-old-space-size=4096"
},
"channel": "production",
"environment": "production",
diff --git a/apps/mobile/global.css b/apps/mobile/global.css
index f76300ee524..6d36bf77caf 100644
--- a/apps/mobile/global.css
+++ b/apps/mobile/global.css
@@ -12,7 +12,7 @@
/* Card / surface */
--color-card: #ffffff;
--color-card-alt: #f5f5f5;
- --color-card-translucent: rgba(255, 255, 255, 0.94);
+ --color-card-translucent: rgba(255, 255, 255, 0.8);
/* Text */
--color-foreground: #262626;
@@ -51,6 +51,7 @@
/* Inputs */
--color-input: #ffffff;
--color-input-border: rgba(0, 0, 0, 0.1);
+ --color-sidebar-search: rgba(118, 118, 128, 0.12);
--color-placeholder: #a3a3a3;
/* Icons */
@@ -105,7 +106,7 @@
/* Card / surface */
--color-card: #171717;
--color-card-alt: #1c1c1c;
- --color-card-translucent: rgba(17, 17, 17, 0.94);
+ --color-card-translucent: rgba(17, 17, 17, 0.8);
/* Text */
--color-foreground: #f5f5f5;
@@ -144,6 +145,7 @@
/* Inputs */
--color-input: #141414;
--color-input-border: rgba(255, 255, 255, 0.08);
+ --color-sidebar-search: rgba(118, 118, 128, 0.24);
--color-placeholder: #8e8e93;
/* Icons */
@@ -194,9 +196,10 @@
/* ─── Typography ────────────────────────────────────────────────────── */
@theme {
- --font-sans: "DMSans_400Regular";
- --font-medium: "DMSans_500Medium";
- --font-bold: "DMSans_700Bold";
+ /* Keep these native family names aligned with app.config.ts. */
+ --font-sans: "DMSans-Regular";
+ --font-medium: "DMSans-Medium";
+ --font-bold: "DMSans-Bold";
/* Keep this scale aligned with src/lib/typography.ts for native style props. */
--text-3xs: 11px;
@@ -221,14 +224,14 @@
/* ─── Custom utilities ──────────────────────────────────────────────── */
@utility font-t3-medium {
- font-family: "DMSans_500Medium";
+ font-family: var(--font-medium);
}
@utility font-t3-bold {
- font-family: "DMSans_700Bold";
+ font-family: var(--font-bold);
}
@utility font-t3-extrabold {
- font-family: "DMSans_700Bold";
+ font-family: var(--font-bold);
font-weight: 800;
}
diff --git a/apps/mobile/index.ts b/apps/mobile/index.ts
index 979dc75d5f1..8add3abd386 100644
--- a/apps/mobile/index.ts
+++ b/apps/mobile/index.ts
@@ -1,3 +1,7 @@
+// Installed via a side-effect import listed first so the fatal-error handler
+// is in place before the rest of the app module graph evaluates.
+import "./src/lib/installCrashLog";
+
import { registerRootComponent } from "expo";
import "react-native-gesture-handler";
import { featureFlags } from "react-native-screens";
diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js
index d314f6206c6..fe886077697 100644
--- a/apps/mobile/metro.config.js
+++ b/apps/mobile/metro.config.js
@@ -6,6 +6,7 @@ const { withUniwindConfig } = require("uniwind/metro");
/** @type {import("expo/metro-config").MetroConfig} */
const config = getDefaultConfig(__dirname);
const workspaceRoot = path.resolve(__dirname, "../..");
+const escapedWorkspaceRoot = workspaceRoot.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const mobileShikiRoot = path.dirname(require.resolve("shiki/package.json", { paths: [__dirname] }));
const resolveShikiDependencyRoot = (packageName) => {
const entryPath = require.resolve(packageName, { paths: [mobileShikiRoot] });
@@ -25,6 +26,14 @@ const resolveShikiDependencyRoot = (packageName) => {
config.watchFolders = [...new Set([...(config.watchFolders ?? []), workspaceRoot])];
config.resolver = {
...config.resolver,
+ blockList: [
+ ...(Array.isArray(config.resolver?.blockList)
+ ? config.resolver.blockList
+ : config.resolver?.blockList
+ ? [config.resolver.blockList]
+ : []),
+ new RegExp(`${escapedWorkspaceRoot}[/\\\\]\\.t3[/\\\\].*`),
+ ],
extraNodeModules: {
// oxlint-disable-next-line unicorn/no-useless-fallback-in-spread
...(config.resolver?.extraNodeModules ?? {}),
diff --git a/apps/mobile/modules/t3-composer-editor/android/build.gradle b/apps/mobile/modules/t3-composer-editor/android/build.gradle
new file mode 100644
index 00000000000..dfdb4d16a14
--- /dev/null
+++ b/apps/mobile/modules/t3-composer-editor/android/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.library'
+apply plugin: 'org.jetbrains.kotlin.android'
+
+group = 'com.t3tools.composereditor'
+version = '0.0.0'
+
+android {
+ namespace 'expo.modules.t3composereditor'
+ compileSdk rootProject.ext.compileSdkVersion
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ }
+}
+
+dependencies {
+ implementation project(':expo-modules-core')
+}
diff --git a/apps/mobile/modules/t3-composer-editor/android/src/main/AndroidManifest.xml b/apps/mobile/modules/t3-composer-editor/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..94cbbcfc396
--- /dev/null
+++ b/apps/mobile/modules/t3-composer-editor/android/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorModule.kt b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorModule.kt
new file mode 100644
index 00000000000..e11181e81a9
--- /dev/null
+++ b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorModule.kt
@@ -0,0 +1,72 @@
+package expo.modules.t3composereditor
+
+import expo.modules.kotlin.modules.Module
+import expo.modules.kotlin.modules.ModuleDefinition
+
+class T3ComposerEditorModule : Module() {
+ override fun definition() = ModuleDefinition {
+ Name("T3ComposerEditor")
+
+ View(T3ComposerEditorView::class) {
+ Prop("controlledDocumentJson") { view: T3ComposerEditorView, documentJson: String ->
+ view.setControlledDocumentJson(documentJson)
+ }
+ Prop("themeJson") { view: T3ComposerEditorView, themeJson: String ->
+ view.setThemeJson(themeJson)
+ }
+ Prop("placeholder") { view: T3ComposerEditorView, placeholder: String ->
+ view.setPlaceholder(placeholder)
+ }
+ Prop("fontFamily") { view: T3ComposerEditorView, fontFamily: String ->
+ view.setFontFamily(fontFamily)
+ }
+ Prop("fontSize") { view: T3ComposerEditorView, fontSize: Double ->
+ view.setFontSize(fontSize.toFloat())
+ }
+ Prop("lineHeight") { view: T3ComposerEditorView, lineHeight: Double ->
+ view.setLineHeight(lineHeight.toFloat())
+ }
+ Prop("contentInsetVertical") { view: T3ComposerEditorView, contentInsetVertical: Double ->
+ view.setContentInsetVertical(contentInsetVertical.toInt())
+ }
+
+ Prop("singleLineCentered") { view: T3ComposerEditorView, singleLineCentered: Boolean ->
+ view.setSingleLineCentered(singleLineCentered)
+ }
+ Prop("editable") { view: T3ComposerEditorView, editable: Boolean ->
+ view.setEditable(editable)
+ }
+ Prop("scrollEnabled") { view: T3ComposerEditorView, scrollEnabled: Boolean ->
+ view.setScrollEnabled(scrollEnabled)
+ }
+ Prop("autoFocus") { view: T3ComposerEditorView, autoFocus: Boolean ->
+ view.setAutoFocus(autoFocus)
+ }
+ Prop("autoCorrect") { view: T3ComposerEditorView, autoCorrect: Boolean ->
+ view.setAutoCorrect(autoCorrect)
+ }
+ Prop("spellCheck") { view: T3ComposerEditorView, spellCheck: Boolean ->
+ view.setSpellCheck(spellCheck)
+ }
+
+ Events(
+ "onComposerChange",
+ "onComposerSelectionChange",
+ "onComposerFocus",
+ "onComposerBlur",
+ "onComposerPasteImages",
+ "onComposerContentSizeChange",
+ )
+
+ AsyncFunction("focus") { view: T3ComposerEditorView ->
+ view.focusEditor()
+ }
+ AsyncFunction("blur") { view: T3ComposerEditorView ->
+ view.blurEditor()
+ }
+ AsyncFunction("setSelection") { view: T3ComposerEditorView, start: Int, end: Int ->
+ view.setSelection(start, end)
+ }
+ }
+ }
+}
diff --git a/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt
new file mode 100644
index 00000000000..e13c0a52189
--- /dev/null
+++ b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt
@@ -0,0 +1,480 @@
+package expo.modules.t3composereditor
+
+import android.content.Context
+import android.content.ClipboardManager
+import android.graphics.Color
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.RectF
+import android.graphics.Typeface
+import android.text.Editable
+import android.text.InputType
+import android.text.Spanned
+import android.text.TextWatcher
+import android.text.style.ReplacementSpan
+import android.view.Gravity
+import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+import expo.modules.kotlin.AppContext
+import expo.modules.kotlin.viewevent.EventDispatcher
+import expo.modules.kotlin.views.ExpoView
+import org.json.JSONObject
+import kotlin.math.max
+
+class T3ComposerEditorView(context: Context, appContext: AppContext) : ExpoView(
+ context,
+ appContext
+) {
+ private val editor = SelectionAwareEditText(context)
+ private val onComposerChange by EventDispatcher()
+ private val onComposerSelectionChange by EventDispatcher()
+ private val onComposerFocus by EventDispatcher()
+ private val onComposerBlur by EventDispatcher()
+ private val onComposerPasteImages by EventDispatcher()
+ private val onComposerContentSizeChange by EventDispatcher()
+ private var applyingNativeValue = false
+ private var desiredLineHeightPx = 0
+ private var lastContentHeight = 0
+ private var contentInsetVertical = 0
+ private var tokensJson = "[]"
+ private var tokens: List = emptyList()
+ private var chipTheme = ComposerChipTheme.default()
+ private var autoCorrect = true
+ private var spellCheck = true
+ private var nativeEventCount = 0
+
+ init {
+ editor.setBackgroundColor(Color.TRANSPARENT)
+ editor.gravity = Gravity.TOP or Gravity.START
+ editor.includeFontPadding = false
+ editor.isSingleLine = false
+ editor.minLines = 1
+ editor.inputType =
+ InputType.TYPE_CLASS_TEXT or
+ InputType.TYPE_TEXT_FLAG_MULTI_LINE or
+ InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
+ editor.setTextColor(Color.BLACK)
+ editor.setHintTextColor(Color.GRAY)
+ editor.setPadding(0, 0, 0, 0)
+ editor.selectionListener = { start, end ->
+ if (!applyingNativeValue) {
+ emitSelectionChange(start, end)
+ }
+ }
+ editor.pasteImagesListener = { uris ->
+ onComposerPasteImages(mapOf("uris" to uris))
+ }
+ editor.setOnFocusChangeListener { _, hasFocus ->
+ if (hasFocus) {
+ onComposerFocus(emptyMap())
+ } else {
+ onComposerBlur(emptyMap())
+ }
+ }
+ editor.addTextChangedListener(
+ object : TextWatcher {
+ override fun beforeTextChanged(
+ text: CharSequence?,
+ start: Int,
+ count: Int,
+ after: Int
+ ) = Unit
+ override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) = Unit
+
+ override fun afterTextChanged(editable: Editable?) {
+ if (applyingNativeValue) return
+ val nextValue = editable.toString()
+ val selection = currentSelectionPayload()
+ nativeEventCount += 1
+ onComposerChange(
+ mapOf(
+ "value" to nextValue,
+ "selection" to selection,
+ "eventCount" to nativeEventCount,
+ ),
+ )
+ emitContentSizeIfNeeded()
+ }
+ },
+ )
+ editor.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> emitContentSizeIfNeeded() }
+ addView(
+ editor,
+ LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT),
+ )
+ }
+
+ @Suppress("ReturnCount")
+ fun setControlledDocumentJson(documentJson: String) {
+ val document = try {
+ JSONObject(documentJson)
+ } catch (_: Exception) {
+ return
+ }
+ val mostRecentEventCount = document.optInt("mostRecentEventCount", -1)
+ if (mostRecentEventCount < nativeEventCount) return
+
+ val value = document.optString("value")
+ if (document.optBoolean("isNativeEcho") && editor.text.toString() != value) return
+
+ val nextTokensJson = document.optString("tokensJson", "[]")
+ val nextTokens = if (nextTokensJson == tokensJson) tokens else parseTokens(nextTokensJson)
+ val requestedSelection = document.optJSONObject("selection")
+ val previousSelectionStart = editor.selectionStart.coerceAtLeast(0)
+ val previousSelectionEnd = editor.selectionEnd.coerceAtLeast(0)
+ val valueChanged = editor.text.toString() != value
+
+ applyingNativeValue = true
+ try {
+ if (valueChanged) {
+ editor.setText(value)
+ }
+ tokensJson = nextTokensJson
+ tokens = nextTokens
+ applyTokenSpans()
+ if (requestedSelection != null) {
+ applySelection(
+ requestedSelection.optInt("start", previousSelectionStart),
+ requestedSelection.optInt("end", previousSelectionEnd),
+ )
+ } else if (valueChanged) {
+ applySelection(previousSelectionStart, previousSelectionEnd)
+ }
+ } finally {
+ applyingNativeValue = false
+ }
+ emitContentSizeIfNeeded()
+ }
+
+ fun setThemeJson(themeJson: String) {
+ try {
+ val theme = JSONObject(themeJson)
+ editor.setTextColor(parseColor(theme.optString("text"), Color.BLACK))
+ editor.setHintTextColor(parseColor(theme.optString("placeholder"), Color.GRAY))
+ chipTheme = ComposerChipTheme(
+ chipBackground = parseColor(theme.optString("chipBackground"), chipTheme.chipBackground),
+ chipBorder = parseColor(theme.optString("chipBorder"), chipTheme.chipBorder),
+ chipText = parseColor(theme.optString("chipText"), chipTheme.chipText),
+ skillBackground = parseColor(
+ theme.optString("skillBackground"),
+ chipTheme.skillBackground,
+ ),
+ skillBorder = parseColor(theme.optString("skillBorder"), chipTheme.skillBorder),
+ skillText = parseColor(theme.optString("skillText"), chipTheme.skillText),
+ )
+ applyTokenSpans()
+ } catch (_: Exception) {
+ }
+ }
+
+ fun setPlaceholder(placeholder: String) {
+ editor.hint = placeholder
+ }
+
+ fun setFontFamily(fontFamily: String) {
+ editor.typeface = if (fontFamily.contains("Mono", ignoreCase = true)) {
+ Typeface.MONOSPACE
+ } else {
+ Typeface.DEFAULT
+ }
+ }
+
+ fun setFontSize(fontSize: Float) {
+ editor.textSize = fontSize
+ applyLineHeight()
+ }
+
+ fun setLineHeight(lineHeight: Float) {
+ desiredLineHeightPx = (lineHeight * resources.displayMetrics.density).toInt()
+ applyLineHeight()
+ }
+
+ fun setSingleLineCentered(centered: Boolean) {
+ editor.gravity = if (centered) {
+ Gravity.CENTER_VERTICAL or Gravity.START
+ } else {
+ Gravity.TOP or Gravity.START
+ }
+ }
+
+ fun setContentInsetVertical(contentInsetVertical: Int) {
+ this.contentInsetVertical =
+ max(0, (contentInsetVertical * resources.displayMetrics.density).toInt())
+ editor.setPadding(0, this.contentInsetVertical, 0, this.contentInsetVertical)
+ emitContentSizeIfNeeded()
+ }
+
+ fun setEditable(editable: Boolean) {
+ editor.isEnabled = editable
+ editor.isFocusable = editable
+ editor.isFocusableInTouchMode = editable
+ editor.isCursorVisible = editable
+ }
+
+ fun setScrollEnabled(scrollEnabled: Boolean) {
+ editor.isVerticalScrollBarEnabled = scrollEnabled
+ }
+
+ fun setAutoFocus(autoFocus: Boolean) {
+ if (autoFocus) {
+ post { focusEditor() }
+ }
+ }
+
+ fun setAutoCorrect(autoCorrect: Boolean) {
+ this.autoCorrect = autoCorrect
+ updateInputFlags()
+ }
+
+ fun setSpellCheck(spellCheck: Boolean) {
+ this.spellCheck = spellCheck
+ updateInputFlags()
+ }
+
+ fun focusEditor() {
+ editor.requestFocus()
+ val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
+ imm?.showSoftInput(editor, InputMethodManager.SHOW_IMPLICIT)
+ }
+
+ fun blurEditor() {
+ editor.clearFocus()
+ val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
+ imm?.hideSoftInputFromWindow(editor.windowToken, 0)
+ }
+
+ fun setSelection(start: Int, end: Int) {
+ applySelection(start, end)
+ }
+
+ private fun applySelection(start: Int, end: Int) {
+ val textLength = editor.text?.length ?: 0
+ val safeStart = start.coerceIn(0, textLength)
+ val safeEnd = end.coerceIn(0, textLength)
+ editor.setSelection(safeStart, safeEnd)
+ }
+
+ private fun updateInputFlags() {
+ var flags =
+ InputType.TYPE_CLASS_TEXT or
+ InputType.TYPE_TEXT_FLAG_MULTI_LINE or
+ InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
+ flags = if (autoCorrect && spellCheck) {
+ flags or InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
+ } else {
+ flags or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+ }
+ editor.inputType = flags
+ }
+
+ private fun applyLineHeight() {
+ if (desiredLineHeightPx <= 0) return
+ val fontHeight = editor.paint.fontMetricsInt.descent - editor.paint.fontMetricsInt.ascent
+ editor.setLineSpacing(max(0, desiredLineHeightPx - fontHeight).toFloat(), 1f)
+ }
+
+ private fun currentSelectionPayload(): Map =
+ mapOf(
+ "start" to editor.selectionStart.coerceAtLeast(0),
+ "end" to editor.selectionEnd.coerceAtLeast(0),
+ )
+
+ private fun emitSelectionChange(start: Int, end: Int) {
+ onComposerSelectionChange(
+ mapOf(
+ "value" to editor.text.toString(),
+ "selection" to mapOf("start" to start, "end" to end),
+ "eventCount" to nativeEventCount,
+ ),
+ )
+ }
+
+ private fun emitContentSizeIfNeeded() {
+ val height = editor.layout?.height ?: editor.measuredHeight
+ val contentHeight = height + contentInsetVertical * 2
+ if (contentHeight == lastContentHeight) return
+ lastContentHeight = contentHeight
+ onComposerContentSizeChange(
+ mapOf("height" to contentHeight / resources.displayMetrics.density),
+ )
+ }
+
+ private fun applyTokenSpans() {
+ val editable = editor.text ?: return
+ editable.getSpans(
+ 0,
+ editable.length,
+ ComposerChipSpan::class.java
+ ).forEach(editable::removeSpan)
+ tokens.forEach { token ->
+ if (token.start < 0 || token.end <= token.start || token.end > editable.length) return@forEach
+ val expectedSource = editable.substring(token.start, token.end)
+ if (expectedSource != token.source) return@forEach
+ editable.setSpan(
+ ComposerChipSpan(
+ token.label,
+ token.type == "skill",
+ chipTheme,
+ resources.displayMetrics.density
+ ),
+ token.start,
+ token.end,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
+ )
+ }
+ editor.invalidate()
+ }
+
+ private fun parseColor(value: String, fallback: Int): Int =
+ try {
+ Color.parseColor(value)
+ } catch (_: Exception) {
+ fallback
+ }
+}
+
+private data class ComposerToken(
+ val type: String,
+ val source: String,
+ val label: String,
+ val start: Int,
+ val end: Int
+)
+
+private data class ComposerChipTheme(
+ val chipBackground: Int,
+ val chipBorder: Int,
+ val chipText: Int,
+ val skillBackground: Int,
+ val skillBorder: Int,
+ val skillText: Int
+) {
+ companion object {
+ fun default() = ComposerChipTheme(
+ chipBackground = Color.rgb(238, 240, 243),
+ chipBorder = Color.rgb(210, 214, 220),
+ chipText = Color.rgb(35, 39, 45),
+ skillBackground = Color.rgb(233, 239, 255),
+ skillBorder = Color.rgb(185, 200, 245),
+ skillText = Color.rgb(45, 72, 155),
+ )
+ }
+}
+
+private class ComposerChipSpan(
+ private val label: String,
+ private val skill: Boolean,
+ private val theme: ComposerChipTheme,
+ density: Float
+) : ReplacementSpan() {
+ private val horizontalPadding = 7f * density
+ private val verticalPadding = 2f * density
+ private val cornerRadius = 6f * density
+ private val borderWidth = density
+
+ override fun getSize(
+ paint: Paint,
+ text: CharSequence,
+ start: Int,
+ end: Int,
+ fontMetrics: Paint.FontMetricsInt?
+ ): Int {
+ fontMetrics?.let {
+ val extra = verticalPadding.toInt()
+ val base = paint.fontMetricsInt
+ it.top = base.top - extra
+ it.ascent = base.ascent - extra
+ it.descent = base.descent + extra
+ it.bottom = base.bottom + extra
+ }
+ return (paint.measureText(label) + horizontalPadding * 2).toInt()
+ }
+
+ override fun draw(
+ canvas: Canvas,
+ text: CharSequence,
+ start: Int,
+ end: Int,
+ x: Float,
+ top: Int,
+ y: Int,
+ bottom: Int,
+ paint: Paint
+ ) {
+ val width = paint.measureText(label) + horizontalPadding * 2
+ val metrics = paint.fontMetrics
+ val rect = RectF(
+ x,
+ y + metrics.ascent - verticalPadding,
+ x + width,
+ y + metrics.descent + verticalPadding,
+ )
+ val originalColor = paint.color
+ val originalStyle = paint.style
+ val originalStrokeWidth = paint.strokeWidth
+
+ paint.color = if (skill) theme.skillBackground else theme.chipBackground
+ paint.style = Paint.Style.FILL
+ canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
+ paint.color = if (skill) theme.skillBorder else theme.chipBorder
+ paint.style = Paint.Style.STROKE
+ paint.strokeWidth = borderWidth
+ canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
+ paint.color = if (skill) theme.skillText else theme.chipText
+ paint.style = Paint.Style.FILL
+ canvas.drawText(label, x + horizontalPadding, y.toFloat(), paint)
+
+ paint.color = originalColor
+ paint.style = originalStyle
+ paint.strokeWidth = originalStrokeWidth
+ }
+}
+
+private fun parseTokens(value: String): List = try {
+ val array = org.json.JSONArray(value)
+ List(array.length()) { index ->
+ val token = array.getJSONObject(index)
+ ComposerToken(
+ type = token.optString("type"),
+ source = token.optString("source"),
+ label = token.optString("label"),
+ start = token.optInt("start"),
+ end = token.optInt("end"),
+ )
+ }
+} catch (_: Exception) {
+ emptyList()
+}
+
+private class SelectionAwareEditText(context: Context) : EditText(context) {
+ var selectionListener: ((Int, Int) -> Unit)? = null
+ var pasteImagesListener: ((List) -> Unit)? = null
+
+ override fun onSelectionChanged(selStart: Int, selEnd: Int) {
+ super.onSelectionChanged(selStart, selEnd)
+ selectionListener?.invoke(selStart, selEnd)
+ }
+
+ override fun onTextContextMenuItem(id: Int): Boolean {
+ if (id == android.R.id.paste || id == android.R.id.pasteAsPlainText) {
+ val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
+ val clip = clipboard?.primaryClip
+ val imageUris = buildList {
+ if (clip != null) {
+ for (index in 0 until clip.itemCount) {
+ clip.getItemAt(index).uri?.let { uri ->
+ val mimeType = context.contentResolver.getType(uri)
+ if (mimeType?.startsWith("image/") == true) add(uri.toString())
+ }
+ }
+ }
+ }
+ if (imageUris.isNotEmpty()) {
+ pasteImagesListener?.invoke(imageUris)
+ return true
+ }
+ }
+ return super.onTextContextMenuItem(id)
+ }
+}
diff --git a/apps/mobile/modules/t3-composer-editor/expo-module.config.json b/apps/mobile/modules/t3-composer-editor/expo-module.config.json
index 0d6384cd91a..56fd899920c 100644
--- a/apps/mobile/modules/t3-composer-editor/expo-module.config.json
+++ b/apps/mobile/modules/t3-composer-editor/expo-module.config.json
@@ -1,6 +1,9 @@
{
- "platforms": ["apple"],
+ "platforms": ["apple", "android"],
"apple": {
"modules": ["T3ComposerEditorModule"]
+ },
+ "android": {
+ "modules": ["expo.modules.t3composereditor.T3ComposerEditorModule"]
}
}
diff --git a/apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift b/apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift
index 8515abfbae8..180da203008 100644
--- a/apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift
+++ b/apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift
@@ -300,7 +300,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate {
skillText: "#a21caf",
fileTint: "#737373"
)
- private var fontFamily = "DMSans_400Regular"
+ private var fontFamily = "DMSans-Regular"
private var fontSize: CGFloat = 14
private var lineHeight: CGFloat = 20
private var contentInsetVertical: CGFloat = 0
@@ -608,7 +608,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate {
iconImage: UIImage?,
style: ComposerChipStyle
) -> UIImage {
- let font = UIFont(name: "DMSans_500Medium", size: max(12, fontSize - 2))
+ let font = UIFont(name: "DMSans-Medium", size: max(12, fontSize - 2))
?? UIFont.systemFont(ofSize: max(12, fontSize - 2), weight: .medium)
let fallbackIcon = UIImage(
systemName: iconName,
diff --git a/apps/mobile/modules/t3-markdown-text/ios/T3MarkdownTextShadowNode.h b/apps/mobile/modules/t3-markdown-text/ios/T3MarkdownTextShadowNode.h
index 99417490a63..737330db1d4 100644
--- a/apps/mobile/modules/t3-markdown-text/ios/T3MarkdownTextShadowNode.h
+++ b/apps/mobile/modules/t3-markdown-text/ios/T3MarkdownTextShadowNode.h
@@ -20,12 +20,16 @@ struct T3MarkdownTextParagraphStyleRange {
Float firstLineHeadIndent;
Float headIndent;
Float paragraphSpacing;
+
+ bool operator==(const T3MarkdownTextParagraphStyleRange&) const = default;
};
struct T3MarkdownTextAttachmentRange {
size_t location;
size_t length;
std::string imageUri;
+
+ bool operator==(const T3MarkdownTextAttachmentRange&) const = default;
};
inline Float T3MarkdownTextAttachmentSize(const T3MarkdownTextAttachmentRange &) {
@@ -52,11 +56,6 @@ T3MarkdownTextStateReal> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
- T3MarkdownTextShadowNode(
- const ShadowNode& sourceShadowNode,
- const ShadowNodeFragment& fragment
- );
-
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
@@ -71,8 +70,18 @@ T3MarkdownTextStateReal> {
const LayoutConstraints& layoutConstraints) const override;
private:
- mutable AttributedString _attributedString;
- mutable std::vector _paragraphStyleRanges;
- mutable std::vector _attachmentRanges;
+ // Content must be derived from the current children whenever it is needed.
+ // Yoga can invoke layout() on a fresh clone without ever calling
+ // measureContent() on it (for example when both dimensions are already
+ // exact), so caching measure-time content in mutable members and publishing
+ // it from layout() lets state fall behind the children and drop text.
+ struct Content {
+ AttributedString attributedString;
+ std::vector paragraphStyleRanges;
+ std::vector attachmentRanges;
+ };
+
+ Content buildContent(const LayoutContext& layoutContext) const;
+ void updateStateIfNeeded(Content&& content);
};
} // namespace facebook::React
diff --git a/apps/mobile/modules/t3-markdown-text/ios/T3MarkdownTextShadowNode.mm b/apps/mobile/modules/t3-markdown-text/ios/T3MarkdownTextShadowNode.mm
index b9abe452fb9..7a02094a4d3 100644
--- a/apps/mobile/modules/t3-markdown-text/ios/T3MarkdownTextShadowNode.mm
+++ b/apps/mobile/modules/t3-markdown-text/ios/T3MarkdownTextShadowNode.mm
@@ -72,15 +72,8 @@ static void applyAttachments(
}
}
-T3MarkdownTextShadowNode::T3MarkdownTextShadowNode(
- const ShadowNode& sourceShadowNode,
- const ShadowNodeFragment& fragment
-) : ConcreteViewShadowNode(sourceShadowNode, fragment) {
-};
-
-Size T3MarkdownTextShadowNode::measureContent(
- const LayoutContext& layoutContext,
- const LayoutConstraints& layoutConstraints) const {
+T3MarkdownTextShadowNode::Content T3MarkdownTextShadowNode::buildContent(
+ const LayoutContext& layoutContext) const {
const auto &baseProps = getConcreteProps();
auto baseTextAttributes = TextAttributes::defaultTextAttributes();
@@ -207,14 +200,23 @@ static void applyAttachments(
}
}
- _attributedString = baseAttributedString;
- _paragraphStyleRanges = paragraphStyleRanges;
- _attachmentRanges = attachmentRanges;
+ return Content{
+ std::move(baseAttributedString),
+ std::move(paragraphStyleRanges),
+ std::move(attachmentRanges),
+ };
+}
+
+Size T3MarkdownTextShadowNode::measureContent(
+ const LayoutContext& layoutContext,
+ const LayoutConstraints& layoutConstraints) const {
+ const auto &baseProps = getConcreteProps();
+ const auto content = buildContent(layoutContext);
NSMutableAttributedString *convertedAttributedString =
- [RCTNSAttributedStringFromAttributedString(baseAttributedString) mutableCopy];
- applyParagraphStyles(convertedAttributedString, paragraphStyleRanges);
- applyAttachments(convertedAttributedString, attachmentRanges);
+ [RCTNSAttributedStringFromAttributedString(content.attributedString) mutableCopy];
+ applyParagraphStyles(convertedAttributedString, content.paragraphStyleRanges);
+ applyAttachments(convertedAttributedString, content.attachmentRanges);
const CGFloat maximumWidth = std::isfinite(layoutConstraints.maximumSize.width)
? layoutConstraints.maximumSize.width
@@ -255,10 +257,21 @@ static void applyAttachments(
void T3MarkdownTextShadowNode::layout(LayoutContext layoutContext) {
ensureUnsealed();
+ updateStateIfNeeded(buildContent(layoutContext));
+}
+
+void T3MarkdownTextShadowNode::updateStateIfNeeded(Content&& content) {
+ const auto &stateData = getStateData();
+ if (stateData.attributedString == content.attributedString &&
+ stateData.paragraphStyleRanges == content.paragraphStyleRanges &&
+ stateData.attachmentRanges == content.attachmentRanges) {
+ return;
+ }
+
setStateData(T3MarkdownTextStateReal{
- _attributedString,
- _paragraphStyleRanges,
- _attachmentRanges,
+ std::move(content.attributedString),
+ std::move(content.paragraphStyleRanges),
+ std::move(content.attachmentRanges),
});
}
}
diff --git a/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx b/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx
index 56321ba01ad..5c8d5bc97c2 100644
--- a/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx
+++ b/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx
@@ -34,6 +34,7 @@ export function SelectableMarkdownText({
skills = EMPTY_SKILLS,
textStyle,
highlightCode,
+ fillWidth = false,
preserveSoftBreaks = false,
onLinkPress,
marginTop = 0,
@@ -63,7 +64,15 @@ export function SelectableMarkdownText({
// shrink-to-fit containers such as user-message bubbles. Yoga then gives
// the native text node an unbounded second pass and the parent only clips
// the resulting single-line width instead of reflowing it.
-
+
{chunks.map((chunk, index) => {
const content =
chunk.kind === "rich" ? (
diff --git a/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.types.ts b/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.types.ts
index 42cc3cd6fb6..c1287a3565f 100644
--- a/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.types.ts
+++ b/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.types.ts
@@ -40,6 +40,7 @@ export interface SelectableMarkdownTextProps {
readonly markdown: string;
readonly textStyle: NativeMarkdownTextStyle;
readonly highlightCode: MarkdownCodeHighlighter;
+ readonly fillWidth?: boolean;
readonly skills?: ReadonlyArray;
readonly preserveSoftBreaks?: boolean;
readonly onLinkPress?: (href: string) => void;
diff --git a/apps/mobile/modules/t3-native-controls/android/build.gradle b/apps/mobile/modules/t3-native-controls/android/build.gradle
new file mode 100644
index 00000000000..ba0622b7f0e
--- /dev/null
+++ b/apps/mobile/modules/t3-native-controls/android/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.library'
+apply plugin: 'org.jetbrains.kotlin.android'
+
+group = 'com.t3tools.nativecontrols'
+version = '0.0.0'
+
+android {
+ namespace 'expo.modules.t3nativecontrols'
+ compileSdk rootProject.ext.compileSdkVersion
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ }
+}
+
+dependencies {
+ implementation project(':expo-modules-core')
+}
diff --git a/apps/mobile/modules/t3-native-controls/android/src/main/AndroidManifest.xml b/apps/mobile/modules/t3-native-controls/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..94cbbcfc396
--- /dev/null
+++ b/apps/mobile/modules/t3-native-controls/android/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/apps/mobile/modules/t3-native-controls/android/src/main/java/expo/modules/t3nativecontrols/T3HeaderButtonView.kt b/apps/mobile/modules/t3-native-controls/android/src/main/java/expo/modules/t3nativecontrols/T3HeaderButtonView.kt
new file mode 100644
index 00000000000..47db92d92a4
--- /dev/null
+++ b/apps/mobile/modules/t3-native-controls/android/src/main/java/expo/modules/t3nativecontrols/T3HeaderButtonView.kt
@@ -0,0 +1,97 @@
+package expo.modules.t3nativecontrols
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.view.View
+import expo.modules.kotlin.AppContext
+import expo.modules.kotlin.viewevent.EventDispatcher
+import expo.modules.kotlin.views.ExpoView
+
+class T3HeaderButtonView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
+ private val iconView = HeaderIconView(context)
+ private val onTriggered by EventDispatcher()
+
+ init {
+ isClickable = true
+ isFocusable = true
+ setOnClickListener {
+ onTriggered(emptyMap())
+ }
+ addView(iconView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
+ }
+
+ fun setLabel(label: String) {
+ contentDescription = label
+ }
+
+ fun setSystemImage(systemImage: String) {
+ iconView.systemImage = systemImage
+ }
+}
+
+private class HeaderIconView(context: Context) : View(context) {
+ private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = Color.parseColor("#6B7280")
+ strokeCap = Paint.Cap.ROUND
+ strokeJoin = Paint.Join.ROUND
+ strokeWidth = 3f * resources.displayMetrics.density
+ style = Paint.Style.STROKE
+ }
+
+ var systemImage: String = "gearshape"
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ val cx = width / 2f
+ val cy = height / 2f
+ val size = minOf(width, height).toFloat()
+ if (systemImage == "square.and.pencil") {
+ drawNewTask(canvas, cx, cy, size)
+ } else {
+ drawSettings(canvas, cx, cy, size)
+ }
+ }
+
+ private fun drawSettings(canvas: Canvas, cx: Float, cy: Float, size: Float) {
+ val radius = size * 0.12f
+ canvas.drawCircle(cx, cy, radius, paint)
+ for (index in 0 until 8) {
+ val angle = Math.PI * index / 4.0
+ val inner = size * 0.19f
+ val outer = size * 0.27f
+ val sx = cx + kotlin.math.cos(angle).toFloat() * inner
+ val sy = cy + kotlin.math.sin(angle).toFloat() * inner
+ val ex = cx + kotlin.math.cos(angle).toFloat() * outer
+ val ey = cy + kotlin.math.sin(angle).toFloat() * outer
+ canvas.drawLine(sx, sy, ex, ey, paint)
+ }
+ }
+
+ private fun drawNewTask(canvas: Canvas, cx: Float, cy: Float, size: Float) {
+ val left = cx - size * 0.2f
+ val top = cy - size * 0.16f
+ val right = cx + size * 0.14f
+ val bottom = cy + size * 0.2f
+ canvas.drawRoundRect(left, top, right, bottom, size * 0.04f, size * 0.04f, paint)
+ canvas.drawLine(
+ cx - size * 0.02f,
+ cy + size * 0.13f,
+ cx + size * 0.24f,
+ cy - size * 0.13f,
+ paint
+ )
+ canvas.drawLine(
+ cx + size * 0.17f,
+ cy - size * 0.2f,
+ cx + size * 0.24f,
+ cy - size * 0.13f,
+ paint
+ )
+ }
+}
diff --git a/apps/mobile/modules/t3-native-controls/android/src/main/java/expo/modules/t3nativecontrols/T3NativeControlsModule.kt b/apps/mobile/modules/t3-native-controls/android/src/main/java/expo/modules/t3nativecontrols/T3NativeControlsModule.kt
new file mode 100644
index 00000000000..4d5f02aaa9c
--- /dev/null
+++ b/apps/mobile/modules/t3-native-controls/android/src/main/java/expo/modules/t3nativecontrols/T3NativeControlsModule.kt
@@ -0,0 +1,21 @@
+package expo.modules.t3nativecontrols
+
+import expo.modules.kotlin.modules.Module
+import expo.modules.kotlin.modules.ModuleDefinition
+
+class T3NativeControlsModule : Module() {
+ override fun definition() = ModuleDefinition {
+ Name("T3NativeControls")
+
+ View(T3HeaderButtonView::class) {
+ Prop("label") { view: T3HeaderButtonView, label: String ->
+ view.setLabel(label)
+ }
+ Prop("systemImage") { view: T3HeaderButtonView, systemImage: String ->
+ view.setSystemImage(systemImage)
+ }
+
+ Events("onTriggered")
+ }
+ }
+}
diff --git a/apps/mobile/modules/t3-native-controls/expo-module.config.json b/apps/mobile/modules/t3-native-controls/expo-module.config.json
index b53b3c50a03..d9a77f14e25 100644
--- a/apps/mobile/modules/t3-native-controls/expo-module.config.json
+++ b/apps/mobile/modules/t3-native-controls/expo-module.config.json
@@ -1,6 +1,9 @@
{
- "platforms": ["apple"],
+ "platforms": ["apple", "android"],
"apple": {
"modules": ["T3NativeControlsModule", "T3KeyboardCommandsModule"]
+ },
+ "android": {
+ "modules": ["expo.modules.t3nativecontrols.T3NativeControlsModule"]
}
}
diff --git a/apps/mobile/modules/t3-native-controls/ios/T3CrashLog.swift b/apps/mobile/modules/t3-native-controls/ios/T3CrashLog.swift
new file mode 100644
index 00000000000..aa6eca69707
--- /dev/null
+++ b/apps/mobile/modules/t3-native-controls/ios/T3CrashLog.swift
@@ -0,0 +1,163 @@
+import Foundation
+
+/// Durable crash persistence for Documents/crash-logs.
+///
+/// The Expo module uses this for JS `writeSyncText`. AppDelegate installs
+/// `RCTSetFatalHandler` and calls `persistNativeFatal` so reportFatal still
+/// leaves a last-crash.json when the JS ErrorUtils path never flushes.
+public enum T3CrashLog {
+ public static let directoryName = "crash-logs"
+ public static let lastCrashFileName = "last-crash.json"
+
+ private static let isoFormatter: ISO8601DateFormatter = {
+ let formatter = ISO8601DateFormatter()
+ formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
+ return formatter
+ }()
+
+ /// Shared fsync write used by the Expo module and native fatal hooks.
+ @discardableResult
+ public static func writeSyncText(relativePath: String, contents: String) -> Bool {
+ do {
+ let docs = try FileManager.default.url(
+ for: .documentDirectory,
+ in: .userDomainMask,
+ appropriateFor: nil,
+ create: true
+ )
+ let url = docs.appendingPathComponent(relativePath)
+ try FileManager.default.createDirectory(
+ at: url.deletingLastPathComponent(),
+ withIntermediateDirectories: true
+ )
+ let data = Data(contents.utf8)
+ // Non-atomic: under abort we prefer a partial file over losing the rename.
+ try data.write(to: url, options: [])
+ let handle = try FileHandle(forWritingTo: url)
+ defer { try? handle.close() }
+ if #available(iOS 13.0, *) {
+ try handle.synchronize()
+ }
+ return true
+ } catch {
+ return false
+ }
+ }
+
+ /// Persist a native RCTFatal / RCTFatalException payload.
+ public static func persistNativeFatal(
+ message: String,
+ name: String,
+ stack: String?,
+ extra: String?,
+ source: String
+ ) {
+ let truncatedMessage = truncate(message, max: 8_000)
+ let truncatedStack = stack.map { truncate($0, max: 48_000) }
+ let capturedAt = isoFormatter.string(from: Date())
+ let millis = Int(Date().timeIntervalSince1970 * 1000)
+
+ var record: [String: Any] = [
+ "breadcrumbs": [] as [Any],
+ "capturedAt": capturedAt,
+ "handlerInvocation": 0,
+ "isFatal": true,
+ "message": truncatedMessage,
+ "name": name,
+ "source": source,
+ ]
+ if let truncatedStack, !truncatedStack.isEmpty {
+ record["stack"] = truncatedStack
+ }
+ if let extra, !extra.isEmpty {
+ record["extraData"] = truncate(extra, max: 8_000)
+ }
+
+ guard let data = try? JSONSerialization.data(withJSONObject: record, options: []),
+ let contents = String(data: data, encoding: .utf8)
+ else {
+ let escaped = truncatedMessage
+ .replacingOccurrences(of: "\\", with: "\\\\")
+ .replacingOccurrences(of: "\"", with: "\\\"")
+ .replacingOccurrences(of: "\n", with: "\\n")
+ let fallback =
+ "{\"capturedAt\":\"\(capturedAt)\",\"isFatal\":true,\"message\":\"\(escaped)\",\"name\":\"\(name)\",\"source\":\"\(source)\",\"handlerInvocation\":0,\"breadcrumbs\":[]}"
+ _ = writeSyncText(relativePath: "\(directoryName)/\(lastCrashFileName)", contents: fallback)
+ _ = writeSyncText(
+ relativePath: "\(directoryName)/crash-native-\(millis)-0.json",
+ contents: fallback
+ )
+ return
+ }
+
+ _ = writeSyncText(relativePath: "\(directoryName)/\(lastCrashFileName)", contents: contents)
+ _ = writeSyncText(
+ relativePath: "\(directoryName)/crash-native-\(millis)-0.json",
+ contents: contents
+ )
+ }
+
+ public static func formatJSStack(_ value: Any?) -> String? {
+ guard let value else {
+ return nil
+ }
+ if let text = value as? String {
+ return text
+ }
+ if let frames = value as? [[String: Any]] {
+ let lines = frames.prefix(80).map { frame -> String in
+ let method = frame["methodName"] as? String ?? "?"
+ let file = frame["file"] as? String ?? "?"
+ let line = stringifyFrameNumber(frame["lineNumber"])
+ let column = stringifyFrameNumber(frame["column"])
+ return " at \(method) (\(file):\(line):\(column))"
+ }
+ return lines.joined(separator: "\n")
+ }
+ return String(describing: value)
+ }
+
+ public static func formatExceptionStack(_ exception: NSException) -> String {
+ let addresses = exception.callStackSymbols
+ if addresses.isEmpty {
+ return exception.callStackReturnAddresses.map { String(describing: $0) }.joined(separator: "\n")
+ }
+ return addresses.prefix(80).joined(separator: "\n")
+ }
+
+ public static func stringValue(_ value: Any?) -> String? {
+ guard let value, !(value is NSNull) else {
+ return nil
+ }
+ if let text = value as? String {
+ return text
+ }
+ if JSONSerialization.isValidJSONObject(value),
+ let data = try? JSONSerialization.data(withJSONObject: value, options: []),
+ let text = String(data: data, encoding: .utf8) {
+ return text
+ }
+ return String(describing: value)
+ }
+
+ private static func truncate(_ text: String, max: Int) -> String {
+ guard text.count > max else {
+ return text
+ }
+ let end = text.index(text.startIndex, offsetBy: max)
+ return String(text[.. String {
+ if let number = value as? NSNumber {
+ return number.stringValue
+ }
+ if let intValue = value as? Int {
+ return String(intValue)
+ }
+ if let text = value as? String {
+ return text
+ }
+ return "?"
+ }
+}
diff --git a/apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift b/apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift
index 33e1dc9086c..f39a3b82fc5 100644
--- a/apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift
+++ b/apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift
@@ -1,9 +1,21 @@
import ExpoModulesCore
+import Foundation
public final class T3NativeControlsModule: Module {
public func definition() -> ModuleDefinition {
Name("T3NativeControls")
+ // Durable last-chance write for fatal JS records. Expo FileSystem write can
+ // lose the race with RCTFatal abort; this fsyncs to Documents.
+ Function("writeSyncText") { (relativePath: String, contents: String) -> Bool in
+ T3CrashLog.writeSyncText(relativePath: relativePath, contents: contents)
+ }
+
+ // AppDelegate installs the native RCTFatal hooks; this is a no-op ack for JS.
+ Function("installFatalHandler") { () -> Bool in
+ true
+ }
+
View(T3HeaderButtonView.self) {
Prop("label") { (view: T3HeaderButtonView, label: String) in
view.setLabel(label)
diff --git a/apps/mobile/modules/t3-review-diff/android/build.gradle b/apps/mobile/modules/t3-review-diff/android/build.gradle
new file mode 100644
index 00000000000..22bb070b3b8
--- /dev/null
+++ b/apps/mobile/modules/t3-review-diff/android/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.library'
+apply plugin: 'org.jetbrains.kotlin.android'
+
+group = 'com.t3tools.reviewdiff'
+version = '0.0.0'
+
+android {
+ namespace 'expo.modules.t3reviewdiff'
+ compileSdk rootProject.ext.compileSdkVersion
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ }
+}
+
+dependencies {
+ implementation project(':expo-modules-core')
+}
diff --git a/apps/mobile/modules/t3-review-diff/android/src/main/AndroidManifest.xml b/apps/mobile/modules/t3-review-diff/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..94cbbcfc396
--- /dev/null
+++ b/apps/mobile/modules/t3-review-diff/android/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/apps/mobile/modules/t3-review-diff/android/src/main/java/expo/modules/t3reviewdiff/T3ReviewDiffModule.kt b/apps/mobile/modules/t3-review-diff/android/src/main/java/expo/modules/t3reviewdiff/T3ReviewDiffModule.kt
new file mode 100644
index 00000000000..ee40275ede8
--- /dev/null
+++ b/apps/mobile/modules/t3-review-diff/android/src/main/java/expo/modules/t3reviewdiff/T3ReviewDiffModule.kt
@@ -0,0 +1,78 @@
+package expo.modules.t3reviewdiff
+
+import expo.modules.kotlin.modules.Module
+import expo.modules.kotlin.modules.ModuleDefinition
+
+class T3ReviewDiffModule : Module() {
+ override fun definition() = ModuleDefinition {
+ Name("T3ReviewDiffSurface")
+
+ View(T3ReviewDiffView::class) {
+ Prop("tokensResetKey") { view: T3ReviewDiffView, tokensResetKey: String ->
+ view.setTokensResetKey(tokensResetKey)
+ }
+ Prop("contentResetKey") { view: T3ReviewDiffView, contentResetKey: String ->
+ view.setContentResetKey(contentResetKey)
+ }
+ Prop("collapsedFileIdsJson") { view: T3ReviewDiffView, collapsedFileIdsJson: String ->
+ view.setCollapsedFileIdsJson(collapsedFileIdsJson)
+ }
+ Prop("viewedFileIdsJson") { view: T3ReviewDiffView, viewedFileIdsJson: String ->
+ view.setViewedFileIdsJson(viewedFileIdsJson)
+ }
+ Prop("selectedRowIdsJson") { view: T3ReviewDiffView, selectedRowIdsJson: String ->
+ view.setSelectedRowIdsJson(selectedRowIdsJson)
+ }
+ Prop("collapsedCommentIdsJson") { view: T3ReviewDiffView, collapsedCommentIdsJson: String ->
+ view.setCollapsedCommentIdsJson(collapsedCommentIdsJson)
+ }
+ Prop("appearanceScheme") { view: T3ReviewDiffView, appearanceScheme: String ->
+ view.setAppearanceScheme(appearanceScheme)
+ }
+ Prop("themeJson") { view: T3ReviewDiffView, themeJson: String ->
+ view.setThemeJson(themeJson)
+ }
+ Prop("styleJson") { view: T3ReviewDiffView, styleJson: String ->
+ view.setStyleJson(styleJson)
+ }
+ Prop("rowHeight") { view: T3ReviewDiffView, rowHeight: Double ->
+ view.setRowHeight(rowHeight.toFloat())
+ }
+ Prop("contentWidth") { view: T3ReviewDiffView, contentWidth: Double ->
+ view.setContentWidth(contentWidth.toFloat())
+ }
+ Prop("initialRowIndex") { view: T3ReviewDiffView, initialRowIndex: Double ->
+ view.setInitialRowIndex(initialRowIndex)
+ }
+
+ Events(
+ "onDebug",
+ "onVisibleFileChange",
+ "onToggleFile",
+ "onToggleViewedFile",
+ "onPressLine",
+ "onToggleComment",
+ )
+
+ AsyncFunction("scrollToFile") { view: T3ReviewDiffView, fileId: String, animated: Boolean ->
+ view.scrollToFile(fileId, animated)
+ }
+ AsyncFunction("scrollToTop") { view: T3ReviewDiffView, animated: Boolean ->
+ view.scrollToTop(animated)
+ }
+ AsyncFunction("setRowsJson") { view: T3ReviewDiffView, rowsJson: String ->
+ view.setRowsJson(rowsJson)
+ }
+ AsyncFunction("setTokensJson") { view: T3ReviewDiffView, tokensJson: String ->
+ view.setTokensJson(tokensJson)
+ }
+ AsyncFunction("setTokensPatchJson") { view: T3ReviewDiffView, tokensPatchJson: String ->
+ view.setTokensPatchJson(tokensPatchJson)
+ }
+
+ OnViewDestroys { view: T3ReviewDiffView ->
+ view.cleanup()
+ }
+ }
+ }
+}
diff --git a/apps/mobile/modules/t3-review-diff/android/src/main/java/expo/modules/t3reviewdiff/T3ReviewDiffView.kt b/apps/mobile/modules/t3-review-diff/android/src/main/java/expo/modules/t3reviewdiff/T3ReviewDiffView.kt
new file mode 100644
index 00000000000..6f960f818a0
--- /dev/null
+++ b/apps/mobile/modules/t3-review-diff/android/src/main/java/expo/modules/t3reviewdiff/T3ReviewDiffView.kt
@@ -0,0 +1,1056 @@
+package expo.modules.t3reviewdiff
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Typeface
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewConfiguration
+import android.widget.OverScroller
+import expo.modules.kotlin.AppContext
+import expo.modules.kotlin.viewevent.EventDispatcher
+import expo.modules.kotlin.views.ExpoView
+import org.json.JSONArray
+import org.json.JSONObject
+import java.util.concurrent.Executors
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+
+class T3ReviewDiffView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
+ private val canvasView = DiffCanvasView(context)
+ private val onDebug by EventDispatcher()
+ private val onVisibleFileChange by EventDispatcher()
+ private val onToggleFile by EventDispatcher()
+ private val onToggleViewedFile by EventDispatcher()
+ private val onPressLine by EventDispatcher()
+ private val onToggleComment by EventDispatcher()
+ private var rows: List = emptyList()
+ private var visibleRows: List = emptyList()
+ private var collapsedFileIds: Set = emptySet()
+ private var viewedFileIds: Set = emptySet()
+ private var selectedRowIds: Set = emptySet()
+ private var collapsedCommentIds: Set = emptySet()
+ private var initialRowIndex = 0
+ private var pendingInitialScroll = false
+ private var lastVisibleFileId: String? = null
+ private var tokensResetKey = ""
+ private var contentResetKey = ""
+ private var rowsDecodeGeneration = 0
+ private var tokensDecodeGeneration = 0
+ private val payloadDecodeExecutor = Executors.newSingleThreadExecutor()
+ private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
+ private val minimumFlingVelocity = ViewConfiguration.get(context).scaledMinimumFlingVelocity
+ private val verticalScroller = OverScroller(context)
+ private val horizontalScroller = OverScroller(context)
+ private var dragAxis: DragAxis? = null
+ private var lastTouchX = 0f
+ private var lastTouchY = 0f
+ private var velocityTracker: VelocityTracker? = null
+
+ init {
+ canvasView.onRowTap = { row, gesture -> handleRowTap(row, gesture) }
+ canvasView.onVisibleRowsChanged = { first, last ->
+ onDebug(
+ mapOf(
+ "message" to "visible-range",
+ "firstRowIndex" to first,
+ "lastRowIndex" to last,
+ ),
+ )
+ emitVisibleFile(first)
+ }
+
+ addView(
+ canvasView,
+ LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT),
+ )
+ }
+
+ fun setTokensResetKey(value: String) {
+ if (tokensResetKey == value) return
+ tokensResetKey = value
+ canvasView.tokensByRowId = emptyMap()
+ }
+
+ fun setContentResetKey(value: String) {
+ if (contentResetKey == value) return
+ contentResetKey = value
+ tokensDecodeGeneration += 1
+ canvasView.tokensByRowId = emptyMap()
+ lastVisibleFileId = null
+ pendingInitialScroll = true
+ canvasView.setVerticalOffset(0)
+ canvasView.setHorizontalOffset(0)
+ applyPendingInitialScroll()
+ }
+
+ fun setCollapsedFileIdsJson(value: String) {
+ collapsedFileIds = parseStringSet(value)
+ rebuildVisibleRows()
+ }
+
+ fun setViewedFileIdsJson(value: String) {
+ viewedFileIds = parseStringSet(value)
+ canvasView.viewedFileIds = viewedFileIds
+ }
+
+ fun setSelectedRowIdsJson(value: String) {
+ selectedRowIds = parseStringSet(value)
+ canvasView.selectedRowIds = selectedRowIds
+ }
+
+ fun setCollapsedCommentIdsJson(value: String) {
+ collapsedCommentIds = parseStringSet(value)
+ rebuildVisibleRows()
+ }
+
+ fun setAppearanceScheme(value: String) {
+ canvasView.theme = DiffTheme.fallback(value)
+ }
+
+ fun setThemeJson(value: String) {
+ canvasView.theme = DiffTheme.fromJson(value, canvasView.theme)
+ }
+
+ fun setStyleJson(value: String) {
+ canvasView.style = DiffStyle.fromJson(value, canvasView.style, resources.displayMetrics.density)
+ }
+
+ fun setRowHeight(value: Float) {
+ canvasView.style = canvasView.style.copy(rowHeightPx = dp(value))
+ }
+
+ fun setContentWidth(value: Float) {
+ canvasView.contentWidthPx = max(width, dp(value).toInt())
+ }
+
+ fun setInitialRowIndex(value: Double) {
+ initialRowIndex = value.toInt().coerceAtLeast(0)
+ pendingInitialScroll = true
+ applyPendingInitialScroll()
+ }
+
+ fun setRowsJson(value: String) {
+ rowsDecodeGeneration += 1
+ val generation = rowsDecodeGeneration
+ payloadDecodeExecutor.execute {
+ val decodedRows = parseRows(value)
+ post {
+ if (generation != rowsDecodeGeneration) return@post
+ rows = decodedRows
+ lastVisibleFileId = null
+ rebuildVisibleRows()
+ }
+ }
+ }
+
+ fun setTokensJson(value: String) {
+ tokensDecodeGeneration += 1
+ val generation = tokensDecodeGeneration
+ payloadDecodeExecutor.execute {
+ val decodedTokens = parseTokensObject(value)
+ post {
+ if (generation != tokensDecodeGeneration) return@post
+ canvasView.tokensByRowId = decodedTokens
+ }
+ }
+ }
+
+ fun setTokensPatchJson(value: String) {
+ payloadDecodeExecutor.execute {
+ try {
+ val payload = JSONObject(value)
+ val resetKey = payload.optString("resetKey")
+ val decodedTokens = parseTokensObject(
+ payload.optJSONObject("tokensByRowId") ?: JSONObject(),
+ )
+ post {
+ if (resetKey.isNotEmpty() && resetKey != tokensResetKey) return@post
+ if (decodedTokens.isNotEmpty()) {
+ canvasView.tokensByRowId = canvasView.tokensByRowId + decodedTokens
+ }
+ }
+ } catch (_: Exception) {
+ }
+ }
+ }
+
+ fun cleanup() {
+ payloadDecodeExecutor.shutdownNow()
+ }
+
+ fun scrollToFile(fileId: String, animated: Boolean) {
+ val index = visibleRows.indexOfFirst { it.kind == "file" && it.resolvedFileId == fileId }
+ if (index < 0) return
+ scrollToY(canvasView.rowTop(index), animated)
+ }
+
+ fun scrollToTop(animated: Boolean) {
+ scrollToY(0, animated)
+ }
+
+ @Suppress("NestedBlockDepth", "ReturnCount")
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> {
+ verticalScroller.forceFinished(true)
+ horizontalScroller.forceFinished(true)
+ dragAxis = null
+ lastTouchX = event.x
+ lastTouchY = event.y
+ parent?.requestDisallowInterceptTouchEvent(true)
+ return false
+ }
+ MotionEvent.ACTION_MOVE -> {
+ if (dragAxis == null) {
+ val deltaX = event.x - lastTouchX
+ val deltaY = event.y - lastTouchY
+ if (max(abs(deltaX), abs(deltaY)) > touchSlop) {
+ dragAxis = if (abs(deltaY) >= abs(deltaX)) DragAxis.VERTICAL else DragAxis.HORIZONTAL
+ }
+ }
+ return dragAxis != null
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> return dragAxis != null
+ }
+ return false
+ }
+
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ velocityTracker?.recycle()
+ velocityTracker = VelocityTracker.obtain()
+ }
+ velocityTracker?.addMovement(event)
+ val handled = super.dispatchTouchEvent(event)
+ if (
+ (
+ event.actionMasked == MotionEvent.ACTION_UP ||
+ event.actionMasked == MotionEvent.ACTION_CANCEL
+ ) &&
+ dragAxis == null
+ ) {
+ velocityTracker?.recycle()
+ velocityTracker = null
+ parent?.requestDisallowInterceptTouchEvent(false)
+ }
+ return handled
+ }
+
+ @Suppress("NestedBlockDepth")
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ val axis = dragAxis ?: return false
+ when (event.actionMasked) {
+ MotionEvent.ACTION_MOVE -> {
+ val deltaX = (lastTouchX - event.x).toInt()
+ val deltaY = (lastTouchY - event.y).toInt()
+ if (axis == DragAxis.VERTICAL) {
+ canvasView.scrollByVertical(deltaY)
+ } else {
+ canvasView.scrollByHorizontal(deltaX)
+ }
+ lastTouchX = event.x
+ lastTouchY = event.y
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ if (event.actionMasked == MotionEvent.ACTION_UP) {
+ velocityTracker?.computeCurrentVelocity(1000)
+ if (axis == DragAxis.VERTICAL) {
+ val velocity = -(velocityTracker?.yVelocity ?: 0f).toInt()
+ if (abs(velocity) >= minimumFlingVelocity) {
+ verticalScroller.fling(
+ 0,
+ canvasView.verticalOffset(),
+ 0,
+ velocity,
+ 0,
+ 0,
+ 0,
+ canvasView.maxVerticalOffset(),
+ )
+ postInvalidateOnAnimation()
+ }
+ } else {
+ val velocity = -(velocityTracker?.xVelocity ?: 0f).toInt()
+ if (abs(velocity) >= minimumFlingVelocity) {
+ horizontalScroller.fling(
+ canvasView.horizontalOffset(),
+ 0,
+ velocity,
+ 0,
+ 0,
+ canvasView.maxHorizontalOffset(),
+ 0,
+ 0,
+ )
+ postInvalidateOnAnimation()
+ }
+ }
+ }
+ dragAxis = null
+ velocityTracker?.recycle()
+ velocityTracker = null
+ parent?.requestDisallowInterceptTouchEvent(false)
+ }
+ }
+ return true
+ }
+
+ override fun computeScroll() {
+ var animating = false
+ if (verticalScroller.computeScrollOffset()) {
+ canvasView.setVerticalOffset(verticalScroller.currY)
+ animating = true
+ }
+ if (horizontalScroller.computeScrollOffset()) {
+ canvasView.setHorizontalOffset(horizontalScroller.currX)
+ animating = true
+ }
+ if (animating) {
+ postInvalidateOnAnimation()
+ }
+ }
+
+ private fun rebuildVisibleRows() {
+ val filtered = ArrayList(rows.size)
+ var currentFileCollapsed = false
+ rows.forEach { row ->
+ if (row.kind == "file") {
+ currentFileCollapsed = collapsedFileIds.contains(row.resolvedFileId)
+ filtered.add(row)
+ } else if (!currentFileCollapsed) {
+ if (row.kind != "comment" || !collapsedCommentIds.contains(row.id)) {
+ filtered.add(row)
+ } else {
+ filtered.add(row.copy(commentText = "Comment collapsed"))
+ }
+ }
+ }
+ visibleRows = filtered
+ canvasView.rows = filtered
+ canvasView.viewedFileIds = viewedFileIds
+ canvasView.selectedRowIds = selectedRowIds
+ applyPendingInitialScroll()
+ }
+
+ private fun handleRowTap(row: DiffRow, gesture: String) {
+ when (row.kind) {
+ "file" -> {
+ if (gesture == "longPress") {
+ onToggleViewedFile(mapOf("fileId" to row.resolvedFileId))
+ } else {
+ onToggleFile(mapOf("fileId" to row.resolvedFileId))
+ }
+ }
+ "comment" -> onToggleComment(mapOf("commentId" to row.id))
+ "line" -> {
+ val payload = mutableMapOf(
+ "rowId" to row.id,
+ "fileId" to row.resolvedFileId,
+ "gesture" to gesture,
+ "change" to row.change,
+ )
+ row.oldLineNumber?.let { payload["oldLineNumber"] = it }
+ row.newLineNumber?.let { payload["newLineNumber"] = it }
+ onPressLine(payload)
+ }
+ }
+ }
+
+ @Suppress("ReturnCount")
+ private fun emitVisibleFile(firstVisibleIndex: Int) {
+ if (visibleRows.isEmpty()) return
+ val start = firstVisibleIndex.coerceIn(0, visibleRows.lastIndex)
+ val fileId = (start downTo 0)
+ .asSequence()
+ .map { visibleRows[it].resolvedFileId }
+ .firstOrNull { it.isNotEmpty() }
+ ?: return
+ if (fileId == lastVisibleFileId) return
+ lastVisibleFileId = fileId
+ onVisibleFileChange(mapOf("fileId" to fileId))
+ }
+
+ private fun applyPendingInitialScroll() {
+ if (!pendingInitialScroll || visibleRows.isEmpty()) return
+ pendingInitialScroll = false
+ val index = initialRowIndex.coerceIn(0, visibleRows.lastIndex)
+ post { canvasView.setVerticalOffset(canvasView.rowTop(index)) }
+ }
+
+ private fun scrollToY(y: Int, animated: Boolean) {
+ val target = y.coerceIn(0, canvasView.maxVerticalOffset())
+ if (animated) {
+ verticalScroller.startScroll(
+ 0,
+ canvasView.verticalOffset(),
+ 0,
+ target - canvasView.verticalOffset(),
+ 250,
+ )
+ postInvalidateOnAnimation()
+ } else {
+ canvasView.setVerticalOffset(target)
+ }
+ }
+
+ private fun dp(value: Float): Float = value * resources.displayMetrics.density
+
+ private enum class DragAxis {
+ VERTICAL,
+ HORIZONTAL
+ }
+}
+
+private data class DiffRow(
+ val kind: String,
+ val id: String,
+ val fileId: String,
+ val filePath: String,
+ val previousPath: String?,
+ val changeType: String,
+ val additions: Int,
+ val deletions: Int,
+ val text: String,
+ val content: String,
+ val change: String,
+ val oldLineNumber: Int?,
+ val newLineNumber: Int?,
+ val commentText: String,
+ val commentRangeLabel: String,
+ val commentSectionTitle: String
+) {
+ val resolvedFileId: String get() = fileId.ifEmpty { id }
+}
+
+private data class DiffToken(
+ val content: String,
+ val color: Int?,
+ val fontStyle: Int
+)
+
+private data class DiffTheme(
+ val background: Int,
+ val text: Int,
+ val mutedText: Int,
+ val headerBackground: Int,
+ val border: Int,
+ val hunkBackground: Int,
+ val hunkText: Int,
+ val addBackground: Int,
+ val deleteBackground: Int,
+ val addBar: Int,
+ val deleteBar: Int,
+ val addText: Int,
+ val deleteText: Int
+) {
+ companion object {
+ fun fallback(scheme: String): DiffTheme = if (scheme == "dark") {
+ DiffTheme(
+ background = Color.rgb(20, 22, 25),
+ text = Color.rgb(236, 238, 240),
+ mutedText = Color.rgb(153, 160, 170),
+ headerBackground = Color.rgb(26, 29, 33),
+ border = Color.rgb(52, 57, 64),
+ hunkBackground = Color.rgb(7, 31, 40),
+ hunkText = Color.rgb(0, 159, 255),
+ addBackground = Color.rgb(13, 47, 40),
+ deleteBackground = Color.rgb(57, 20, 21),
+ addBar = Color.rgb(0, 202, 177),
+ deleteBar = Color.rgb(255, 46, 63),
+ addText = Color.rgb(94, 204, 113),
+ deleteText = Color.rgb(255, 103, 98),
+ )
+ } else {
+ DiffTheme(
+ background = Color.WHITE,
+ text = Color.rgb(7, 7, 7),
+ mutedText = Color.rgb(102, 106, 115),
+ headerBackground = Color.WHITE,
+ border = Color.rgb(222, 224, 228),
+ hunkBackground = Color.rgb(224, 242, 255),
+ hunkText = Color.rgb(0, 130, 220),
+ addBackground = Color.rgb(229, 248, 245),
+ deleteBackground = Color.rgb(255, 230, 231),
+ addBar = Color.rgb(0, 172, 151),
+ deleteBar = Color.rgb(213, 44, 54),
+ addText = Color.rgb(25, 130, 67),
+ deleteText = Color.rgb(190, 38, 48),
+ )
+ }
+
+ fun fromJson(value: String, fallback: DiffTheme): DiffTheme = try {
+ val json = JSONObject(value)
+ DiffTheme(
+ background = color(json, "background", fallback.background),
+ text = color(json, "text", fallback.text),
+ mutedText = color(json, "mutedText", fallback.mutedText),
+ headerBackground = color(json, "headerBackground", fallback.headerBackground),
+ border = color(json, "border", fallback.border),
+ hunkBackground = color(json, "hunkBackground", fallback.hunkBackground),
+ hunkText = color(json, "hunkText", fallback.hunkText),
+ addBackground = color(json, "addBackground", fallback.addBackground),
+ deleteBackground = color(json, "deleteBackground", fallback.deleteBackground),
+ addBar = color(json, "addBar", fallback.addBar),
+ deleteBar = color(json, "deleteBar", fallback.deleteBar),
+ addText = color(json, "addText", fallback.addText),
+ deleteText = color(json, "deleteText", fallback.deleteText),
+ )
+ } catch (_: Exception) {
+ fallback
+ }
+
+ private fun color(json: JSONObject, key: String, fallback: Int): Int =
+ parseColor(json.optString(key), fallback)
+ }
+}
+
+private data class DiffStyle(
+ val rowHeightPx: Float,
+ val gutterWidthPx: Float,
+ val codePaddingPx: Float,
+ val changeBarWidthPx: Float,
+ val fileHeaderHeightPx: Float,
+ val codeFontSizePx: Float,
+ val lineNumberFontSizePx: Float
+) {
+ companion object {
+ fun defaults(density: Float): DiffStyle = DiffStyle(
+ rowHeightPx = 20f * density,
+ gutterWidthPx = 72f * density,
+ codePaddingPx = 10f * density,
+ changeBarWidthPx = 3f * density,
+ fileHeaderHeightPx = 44f * density,
+ codeFontSizePx = 12f * density,
+ lineNumberFontSizePx = 10f * density,
+ )
+
+ fun fromJson(value: String, fallback: DiffStyle, density: Float): DiffStyle = try {
+ val json = JSONObject(value)
+ DiffStyle(
+ rowHeightPx = json.floatDp("rowHeight", fallback.rowHeightPx, density),
+ gutterWidthPx = json.floatDp("gutterWidth", fallback.gutterWidthPx, density),
+ codePaddingPx = json.floatDp("codePadding", fallback.codePaddingPx, density),
+ changeBarWidthPx = json.floatDp("changeBarWidth", fallback.changeBarWidthPx, density),
+ fileHeaderHeightPx = json.floatDp("fileHeaderHeight", fallback.fileHeaderHeightPx, density),
+ codeFontSizePx = json.floatSp("codeFontSize", fallback.codeFontSizePx, density),
+ lineNumberFontSizePx = json.floatSp(
+ "lineNumberFontSize",
+ fallback.lineNumberFontSizePx,
+ density
+ ),
+ )
+ } catch (_: Exception) {
+ fallback
+ }
+ }
+}
+
+private class DiffCanvasView(context: Context) : View(context) {
+ private val density = resources.displayMetrics.density
+ private val backgroundPaint = Paint()
+ private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { typeface = Typeface.MONOSPACE }
+ private val boldTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
+ }
+ private val gestureDetector = GestureDetector(
+ context,
+ object : GestureDetector.SimpleOnGestureListener() {
+ override fun onDown(event: MotionEvent): Boolean = true
+
+ override fun onSingleTapUp(event: MotionEvent): Boolean {
+ rowAt(event.y)?.let { onRowTap?.invoke(it, "tap") }
+ return true
+ }
+
+ override fun onLongPress(event: MotionEvent) {
+ rowAt(event.y)?.let { onRowTap?.invoke(it, "longPress") }
+ }
+ },
+ )
+ private var rowOffsets = intArrayOf(0)
+ private var verticalOffset = 0
+ private var horizontalOffset = 0
+ private var lastVisibleRange: Pair? = null
+
+ var rows: List = emptyList()
+ set(value) {
+ field = value
+ rebuildOffsets()
+ }
+ var tokensByRowId: Map> = emptyMap()
+ set(value) {
+ field = value
+ invalidate()
+ }
+ var viewedFileIds: Set = emptySet()
+ set(value) {
+ field = value
+ invalidate()
+ }
+ var selectedRowIds: Set = emptySet()
+ set(value) {
+ field = value
+ invalidate()
+ }
+ var theme: DiffTheme = DiffTheme.fallback("light")
+ set(value) {
+ field = value
+ invalidate()
+ }
+ var style: DiffStyle = DiffStyle.defaults(density)
+ set(value) {
+ field = value
+ rebuildOffsets()
+ }
+ var contentWidthPx: Int = (1200 * density).toInt()
+ set(value) {
+ field = max(value, suggestedMinimumWidth)
+ setHorizontalOffset(horizontalOffset)
+ invalidate()
+ }
+ var onRowTap: ((DiffRow, String) -> Unit)? = null
+ var onVisibleRowsChanged: ((Int, Int) -> Unit)? = null
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ setMeasuredDimension(
+ MeasureSpec.getSize(widthMeasureSpec),
+ MeasureSpec.getSize(heightMeasureSpec),
+ )
+ }
+
+ override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
+ super.onSizeChanged(width, height, oldWidth, oldHeight)
+ setVerticalOffset(verticalOffset)
+ setHorizontalOffset(horizontalOffset)
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ canvas.drawColor(theme.background)
+ if (rows.isEmpty()) return
+ val first = rowIndexAt(max(0, verticalOffset + canvas.clipBounds.top))
+ val last = rowIndexAt(
+ min(max(0, rowOffsets.last() - 1), verticalOffset + canvas.clipBounds.bottom),
+ ).coerceAtLeast(first)
+ for (index in first..last.coerceAtMost(rows.lastIndex)) {
+ drawRow(
+ canvas,
+ rows[index],
+ rowOffsets[index] - verticalOffset,
+ rowOffsets[index + 1] - verticalOffset,
+ )
+ }
+ drawStickyFileHeader(canvas, first)
+ drawHorizontalScrollIndicator(canvas)
+ emitVisibleRange()
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean = gestureDetector.onTouchEvent(event)
+
+ fun rowTop(index: Int): Int = rowOffsets[index.coerceIn(0, max(0, rowOffsets.size - 2))]
+
+ fun setVerticalOffset(value: Int) {
+ val maxOffset = max(0, (rowOffsets.lastOrNull() ?: 0) - height)
+ val nextOffset = value.coerceIn(0, maxOffset)
+ if (verticalOffset == nextOffset) return
+ verticalOffset = nextOffset
+ invalidate()
+ emitVisibleRange()
+ }
+
+ fun scrollByVertical(delta: Int) {
+ setVerticalOffset(verticalOffset + delta)
+ }
+
+ fun verticalOffset(): Int = verticalOffset
+
+ fun maxVerticalOffset(): Int = max(0, (rowOffsets.lastOrNull() ?: 0) - height)
+
+ fun setHorizontalOffset(value: Int) {
+ val nextOffset = value.coerceIn(0, maxHorizontalOffset())
+ if (horizontalOffset == nextOffset) return
+ horizontalOffset = nextOffset
+ invalidate()
+ }
+
+ fun scrollByHorizontal(delta: Int) {
+ setHorizontalOffset(horizontalOffset + delta)
+ }
+
+ fun horizontalOffset(): Int = horizontalOffset
+
+ fun maxHorizontalOffset(): Int = max(0, contentWidthPx - width)
+
+ private fun rebuildOffsets() {
+ rowOffsets = IntArray(rows.size + 1)
+ rows.forEachIndexed { index, row ->
+ rowOffsets[index + 1] = rowOffsets[index] + rowHeight(row)
+ }
+ setVerticalOffset(verticalOffset)
+ requestLayout()
+ invalidate()
+ }
+
+ private fun rowHeight(row: DiffRow): Int = when (row.kind) {
+ "file" -> style.fileHeaderHeightPx.toInt()
+ "comment" -> max((style.rowHeightPx * 3.2f).toInt(), (56 * density).toInt())
+ else -> style.rowHeightPx.toInt()
+ }.coerceAtLeast(1)
+
+ @Suppress("ReturnCount")
+ private fun rowIndexAt(y: Int): Int {
+ if (rows.isEmpty()) return 0
+ var low = 0
+ var high = rows.lastIndex
+ while (low <= high) {
+ val middle = (low + high) ushr 1
+ when {
+ y < rowOffsets[middle] -> high = middle - 1
+ y >= rowOffsets[middle + 1] -> low = middle + 1
+ else -> return middle
+ }
+ }
+ return low.coerceIn(0, rows.lastIndex)
+ }
+
+ private fun rowAt(y: Float): DiffRow? {
+ stickyFileHeader(firstVisibleRow())?.let { sticky ->
+ if (y >= max(0, sticky.top).toFloat() && y < sticky.bottom.toFloat()) {
+ return rows.getOrNull(sticky.index)
+ }
+ }
+ return rows.getOrNull(rowIndexAt(verticalOffset + y.toInt()))
+ }
+
+ private fun firstVisibleRow(): Int = rowIndexAt(verticalOffset)
+
+ private fun emitVisibleRange() {
+ if (rows.isEmpty()) return
+ val first = rowIndexAt(verticalOffset)
+ val last = rowIndexAt(verticalOffset + max(1, height))
+ val range = first to last
+ if (range == lastVisibleRange) return
+ lastVisibleRange = range
+ onVisibleRowsChanged?.invoke(first, last)
+ }
+
+ private fun drawRow(canvas: Canvas, row: DiffRow, top: Int, bottom: Int) {
+ when (row.kind) {
+ "file" -> drawFileRow(canvas, row, top, bottom)
+ "hunk" -> drawHunkRow(canvas, row, top, bottom)
+ "notice" -> drawNoticeRow(canvas, row, top, bottom)
+ "comment" -> drawCommentRow(canvas, row, top, bottom)
+ else -> drawLineRow(canvas, row, top, bottom)
+ }
+ }
+
+ private fun drawFileRow(canvas: Canvas, row: DiffRow, top: Int, bottom: Int) {
+ fill(canvas, theme.headerBackground, 0f, top.toFloat(), width.toFloat(), bottom.toFloat())
+ val baseline = centeredBaseline(top, bottom, boldTextPaint.apply { textSize = 13f * density })
+ boldTextPaint.color = theme.text
+ val marker = if (viewedFileIds.contains(row.resolvedFileId)) "[x] " else "[ ] "
+ textPaint.color = theme.mutedText
+ textPaint.textSize = 11f * density
+ val stats = "+${row.additions} -${row.deletions}"
+ val statsX = max(12f * density, width - textPaint.measureText(stats) - 16f * density)
+ val title = ellipsize(marker + row.filePath, boldTextPaint, statsX - 28f * density)
+ canvas.drawText(title, 12f * density, baseline, boldTextPaint)
+ canvas.drawText(stats, statsX, baseline, textPaint)
+ drawBottomBorder(canvas, bottom)
+ }
+
+ private fun drawHunkRow(canvas: Canvas, row: DiffRow, top: Int, bottom: Int) {
+ fill(canvas, theme.hunkBackground, 0f, top.toFloat(), width.toFloat(), bottom.toFloat())
+ textPaint.color = theme.hunkText
+ textPaint.textSize = style.codeFontSizePx
+ drawScrollableCode(canvas, top, bottom) { codeX ->
+ canvas.drawText(
+ row.text.ifEmpty { row.content },
+ codeX,
+ centeredBaseline(top, bottom, textPaint),
+ textPaint,
+ )
+ }
+ }
+
+ private fun drawNoticeRow(canvas: Canvas, row: DiffRow, top: Int, bottom: Int) {
+ textPaint.color = theme.mutedText
+ textPaint.textSize = style.codeFontSizePx
+ drawScrollableCode(canvas, top, bottom) { codeX ->
+ canvas.drawText(row.text, codeX, centeredBaseline(top, bottom, textPaint), textPaint)
+ }
+ }
+
+ private fun drawCommentRow(canvas: Canvas, row: DiffRow, top: Int, bottom: Int) {
+ fill(
+ canvas,
+ theme.headerBackground,
+ style.gutterWidthPx,
+ top.toFloat(),
+ width.toFloat(),
+ bottom.toFloat()
+ )
+ boldTextPaint.color = theme.text
+ boldTextPaint.textSize = 12f * density
+ drawScrollableCode(canvas, top, bottom) { codeX ->
+ canvas.drawText(
+ row.commentSectionTitle.ifEmpty { row.commentRangeLabel.ifEmpty { "Comment" } },
+ codeX,
+ top + 20f * density,
+ boldTextPaint,
+ )
+ textPaint.color = theme.mutedText
+ textPaint.textSize = 12f * density
+ canvas.drawText(row.commentText, codeX, top + 42f * density, textPaint)
+ }
+ drawBottomBorder(canvas, bottom)
+ }
+
+ @Suppress("CyclomaticComplexMethod")
+ private fun drawLineRow(canvas: Canvas, row: DiffRow, top: Int, bottom: Int) {
+ val background = when (row.change) {
+ "add" -> theme.addBackground
+ "delete" -> theme.deleteBackground
+ else -> theme.background
+ }
+ fill(canvas, background, 0f, top.toFloat(), width.toFloat(), bottom.toFloat())
+ val selected = selectedRowIds.contains(row.id)
+ if (selected) {
+ fill(
+ canvas,
+ withAlpha(theme.hunkText, if (theme.background == Color.WHITE) 54 else 76),
+ 0f,
+ top.toFloat(),
+ width.toFloat(),
+ bottom.toFloat(),
+ )
+ }
+ val barColor = when (row.change) {
+ "add" -> theme.addBar
+ "delete" -> theme.deleteBar
+ else -> Color.TRANSPARENT
+ }
+ if (barColor != Color.TRANSPARENT && style.changeBarWidthPx > 0) {
+ fill(canvas, barColor, 0f, top.toFloat(), style.changeBarWidthPx, bottom.toFloat())
+ }
+
+ val tokens = tokensByRowId[row.id]
+ drawScrollableCode(canvas, top, bottom) { codeX ->
+ if (tokens.isNullOrEmpty()) {
+ textPaint.textSize = style.codeFontSizePx
+ textPaint.color = when (row.change) {
+ "add" -> theme.addText
+ "delete" -> theme.deleteText
+ else -> theme.text
+ }
+ canvas.drawText(row.content, codeX, centeredBaseline(top, bottom, textPaint), textPaint)
+ } else {
+ var x = codeX
+ tokens.forEach { token ->
+ textPaint.textSize = style.codeFontSizePx
+ textPaint.color = token.color ?: theme.text
+ textPaint.typeface = when {
+ token.fontStyle and 1 != 0 -> Typeface.create(Typeface.MONOSPACE, Typeface.ITALIC)
+ token.fontStyle and 2 != 0 -> Typeface.create(Typeface.MONOSPACE, Typeface.BOLD)
+ else -> Typeface.MONOSPACE
+ }
+ canvas.drawText(token.content, x, centeredBaseline(top, bottom, textPaint), textPaint)
+ x += textPaint.measureText(token.content)
+ }
+ textPaint.typeface = Typeface.MONOSPACE
+ }
+ }
+
+ textPaint.textSize = style.lineNumberFontSizePx
+ textPaint.color = if (selected) theme.text else theme.mutedText
+ val oldNumber = row.oldLineNumber?.toString().orEmpty()
+ val newNumber = row.newLineNumber?.toString().orEmpty()
+ val baseline = centeredBaseline(top, bottom, textPaint)
+ canvas.drawText(oldNumber, style.changeBarWidthPx + 6f * density, baseline, textPaint)
+ canvas.drawText(
+ newNumber,
+ style.changeBarWidthPx + style.gutterWidthPx / 2f,
+ baseline,
+ textPaint
+ )
+ }
+
+ private fun drawScrollableCode(
+ canvas: Canvas,
+ top: Int,
+ bottom: Int,
+ draw: (Float) -> Unit
+ ) {
+ val gutterEnd = style.changeBarWidthPx + style.gutterWidthPx
+ canvas.save()
+ canvas.clipRect(gutterEnd, top.toFloat(), width.toFloat(), bottom.toFloat())
+ draw(gutterEnd + style.codePaddingPx - horizontalOffset)
+ canvas.restore()
+ }
+
+ private fun drawStickyFileHeader(canvas: Canvas, firstVisibleIndex: Int) {
+ val sticky = stickyFileHeader(firstVisibleIndex) ?: return
+ val naturalTop = rowOffsets[sticky.index] - verticalOffset
+ if (naturalTop == sticky.top) return
+ drawFileRow(canvas, rows[sticky.index], sticky.top, sticky.bottom)
+ }
+
+ @Suppress("ReturnCount")
+ private fun stickyFileHeader(firstVisibleIndex: Int): StickyFileHeader? {
+ if (rows.isEmpty()) return null
+ val fileIndex = (firstVisibleIndex.coerceIn(0, rows.lastIndex) downTo 0)
+ .firstOrNull { rows[it].kind == "file" }
+ ?: return null
+ val headerHeight = rowHeight(rows[fileIndex])
+ val nextFileIndex = ((fileIndex + 1)..rows.lastIndex).firstOrNull { rows[it].kind == "file" }
+ val top = nextFileIndex
+ ?.let { min(0, rowOffsets[it] - verticalOffset - headerHeight) }
+ ?: 0
+ return StickyFileHeader(fileIndex, top, top + headerHeight)
+ }
+
+ private fun drawHorizontalScrollIndicator(canvas: Canvas) {
+ val maxOffset = maxHorizontalOffset()
+ if (maxOffset <= 0 || width <= 0) return
+ val trackWidth = width.toFloat()
+ val thumbWidth = max(24f * density, trackWidth * trackWidth / contentWidthPx)
+ val thumbTravel = trackWidth - thumbWidth
+ val left = thumbTravel * horizontalOffset / maxOffset
+ fill(
+ canvas,
+ withAlpha(theme.mutedText, 110),
+ left,
+ height - 2f * density,
+ left + thumbWidth,
+ height.toFloat(),
+ )
+ }
+
+ @Suppress("LongParameterList")
+ private fun fill(
+ canvas: Canvas,
+ color: Int,
+ left: Float,
+ top: Float,
+ right: Float,
+ bottom: Float
+ ) {
+ backgroundPaint.color = color
+ canvas.drawRect(left, top, right, bottom, backgroundPaint)
+ }
+
+ private fun drawBottomBorder(canvas: Canvas, bottom: Int) {
+ borderPaint.color = theme.border
+ borderPaint.strokeWidth = density
+ canvas.drawLine(0f, bottom - density / 2f, width.toFloat(), bottom - density / 2f, borderPaint)
+ }
+
+ private fun centeredBaseline(top: Int, bottom: Int, paint: Paint): Float {
+ val metrics = paint.fontMetrics
+ return (top + bottom) / 2f - (metrics.ascent + metrics.descent) / 2f
+ }
+
+ private fun ellipsize(value: String, paint: Paint, width: Float): String {
+ if (paint.measureText(value) <= width) return value
+ val suffix = "..."
+ val available = max(0f, width - paint.measureText(suffix))
+ var end = value.length
+ while (end > 0 && paint.measureText(value, 0, end) > available) end -= 1
+ return value.substring(0, end) + suffix
+ }
+
+ private data class StickyFileHeader(
+ val index: Int,
+ val top: Int,
+ val bottom: Int
+ )
+}
+
+private fun withAlpha(color: Int, alpha: Int): Int =
+ Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color))
+
+private fun parseRows(value: String): List = try {
+ val array = JSONArray(value)
+ List(array.length()) { index ->
+ val row = array.getJSONObject(index)
+ DiffRow(
+ kind = row.optString("kind"),
+ id = row.optString("id"),
+ fileId = row.optString("fileId"),
+ filePath = row.optString("filePath"),
+ previousPath = row.optNullableString("previousPath"),
+ changeType = row.optString("changeType"),
+ additions = row.optInt("additions"),
+ deletions = row.optInt("deletions"),
+ text = row.optString("text"),
+ content = row.optString("content"),
+ change = row.optString("change", "context"),
+ oldLineNumber = row.optNullableInt("oldLineNumber"),
+ newLineNumber = row.optNullableInt("newLineNumber"),
+ commentText = row.optString("commentText"),
+ commentRangeLabel = row.optString("commentRangeLabel"),
+ commentSectionTitle = row.optString("commentSectionTitle"),
+ )
+ }
+} catch (_: Exception) {
+ emptyList()
+}
+
+private fun parseTokensObject(value: String): Map> = try {
+ parseTokensObject(JSONObject(value))
+} catch (_: Exception) {
+ emptyMap()
+}
+
+private fun parseTokensObject(value: JSONObject): Map> {
+ val result = LinkedHashMap>()
+ val keys = value.keys()
+ while (keys.hasNext()) {
+ val rowId = keys.next()
+ val array = value.optJSONArray(rowId) ?: continue
+ result[rowId] = List(array.length()) { index ->
+ val token = array.getJSONObject(index)
+ DiffToken(
+ content = token.optString("content"),
+ color = token.optNullableString("color")?.let { parseColor(it, Color.TRANSPARENT) },
+ fontStyle = token.optInt("fontStyle"),
+ )
+ }
+ }
+ return result
+}
+
+private fun parseStringSet(value: String): Set = try {
+ val array = JSONArray(value)
+ buildSet {
+ for (index in 0 until array.length()) add(array.getString(index))
+ }
+} catch (_: Exception) {
+ emptySet()
+}
+
+private fun parseColor(value: String, fallback: Int): Int = try {
+ Color.parseColor(value)
+} catch (_: Exception) {
+ fallback
+}
+
+private fun JSONObject.optNullableString(key: String): String? =
+ if (isNull(key)) null else optString(key).takeIf { it.isNotEmpty() }
+
+private fun JSONObject.optNullableInt(key: String): Int? =
+ if (isNull(key) || !has(key)) null else optInt(key)
+
+private fun JSONObject.floatDp(key: String, fallbackPx: Float, density: Float): Float =
+ if (has(key)) optDouble(key).toFloat() * density else fallbackPx
+
+private fun JSONObject.floatSp(key: String, fallbackPx: Float, density: Float): Float =
+ if (has(key)) optDouble(key).toFloat() * density else fallbackPx
diff --git a/apps/mobile/modules/t3-review-diff/expo-module.config.json b/apps/mobile/modules/t3-review-diff/expo-module.config.json
index fe6b11b649c..bf5baf4fc5c 100644
--- a/apps/mobile/modules/t3-review-diff/expo-module.config.json
+++ b/apps/mobile/modules/t3-review-diff/expo-module.config.json
@@ -1,7 +1,10 @@
{
- "platforms": ["apple"],
+ "platforms": ["apple", "android"],
"apple": {
"modules": ["T3ReviewDiffModule"],
"podspecPath": "T3ReviewDiffNative.podspec"
+ },
+ "android": {
+ "modules": ["expo.modules.t3reviewdiff.T3ReviewDiffModule"]
}
}
diff --git a/apps/mobile/modules/t3-review-diff/package.json b/apps/mobile/modules/t3-review-diff/package.json
index 75ac49e5a99..4b3f91dbd86 100644
--- a/apps/mobile/modules/t3-review-diff/package.json
+++ b/apps/mobile/modules/t3-review-diff/package.json
@@ -7,6 +7,11 @@
"modules": [
"T3ReviewDiffModule"
]
+ },
+ "android": {
+ "modules": [
+ "expo.modules.t3reviewdiff.T3ReviewDiffModule"
+ ]
}
}
}
diff --git a/apps/mobile/modules/t3-terminal/README.md b/apps/mobile/modules/t3-terminal/README.md
index 09d927f4733..768e3c0704c 100644
--- a/apps/mobile/modules/t3-terminal/README.md
+++ b/apps/mobile/modules/t3-terminal/README.md
@@ -18,9 +18,9 @@ uses that callback I/O model:
4. send user input back to JS with the write callback
5. emit Ghostty's measured terminal size through `onResize`
-Android currently implements the same view name (`T3TerminalSurface`) and event payloads so the
-React Native screen and RPC code stay platform-neutral. The renderer backend can be replaced with a
-future Android Ghostty build without changing JS.
+Android implements the same view contract with upstream `libghostty-vt` for terminal state, parsing,
+reflow, and scrollback. An Android Canvas view renders compact snapshots produced by the JNI bridge,
+so the React Native screen and RPC code stay platform-neutral.
Vendored Ghostty revision and license details are in `THIRD_PARTY_NOTICES.md`.
@@ -36,3 +36,15 @@ apps/mobile/modules/t3-terminal/scripts/build-libghostty-ios16.sh
The script builds Ghostty with Zig 0.15.2, strips the iOS archives, and replaces only the
`ios-arm64` and `ios-arm64-simulator` slices. Xcode's Metal toolchain must be installed; if `metal`
fails, run `xcodebuild -downloadComponent MetalToolchain`.
+
+## Rebuilding libghostty-vt for Android
+
+The checked-in Android shared libraries and headers are pinned to the revision recorded in
+`Vendor/libghostty-vt/VERSION`. Set `ANDROID_NDK_HOME` and run:
+
+```bash
+apps/mobile/modules/t3-terminal/scripts/build-libghostty-android.sh
+```
+
+The script downloads Zig 0.15.2 when needed, checks out the pinned upstream Ghostty revision, and
+rebuilds all four Android ABIs with 16 KB page-size support.
diff --git a/apps/mobile/modules/t3-terminal/THIRD_PARTY_NOTICES.md b/apps/mobile/modules/t3-terminal/THIRD_PARTY_NOTICES.md
index 0ed6e1d1487..990dd4bbe9c 100644
--- a/apps/mobile/modules/t3-terminal/THIRD_PARTY_NOTICES.md
+++ b/apps/mobile/modules/t3-terminal/THIRD_PARTY_NOTICES.md
@@ -14,3 +14,21 @@ iOS 16 support fork. That fork was created from VVTerm's custom-I/O Ghostty fork
Ghostty's MIT license applies to the vendored framework. Keep this notice in sync when updating
`Vendor/libghostty`.
+
+## Ghostty / libghostty-vt
+
+The Android terminal renderer vendors upstream `libghostty-vt` shared libraries and C headers.
+
+- Upstream project: https://github.com/ghostty-org/ghostty
+- Vendored revision: `9f62873bf195e4d8a762d768a1405a5f2f7b1697`
+- License: MIT
+
+Ghostty's MIT license applies to the vendored Android libraries. Keep this notice in sync when
+updating `Vendor/libghostty-vt`.
+
+## MesloLGS NF (Android terminal font)
+
+- Files: `android/src/main/assets/fonts/MesloLGS-NF-{Regular,Bold}.ttf`
+- Source: https://github.com/romkatv/powerlevel10k-media (Meslo LG patched with Nerd Fonts glyphs)
+- Upstream: Meslo LG by André Berg (customization of Apple's Menlo), Nerd Fonts patcher
+- License: Apache License 2.0
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/LICENSE b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/LICENSE
new file mode 100644
index 00000000000..0a07a66cd1a
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Mitchell Hashimoto, Ghostty contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/VERSION b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/VERSION
new file mode 100644
index 00000000000..aa5d5e7421e
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/VERSION
@@ -0,0 +1 @@
+9f62873bf195e4d8a762d768a1405a5f2f7b1697
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt.h
new file mode 100644
index 00000000000..94a85033434
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt.h
@@ -0,0 +1,154 @@
+/**
+ * @file vt.h
+ *
+ * libghostty-vt - Virtual terminal emulator library
+ *
+ * This library provides functionality for parsing and handling terminal
+ * escape sequences as well as maintaining terminal state such as styles,
+ * cursor position, screen, scrollback, and more.
+ *
+ * WARNING: This is an incomplete, work-in-progress API. It is not yet
+ * stable and is definitely going to change.
+ */
+
+/**
+ * @mainpage libghostty-vt - Virtual Terminal Emulator Library
+ *
+ * libghostty-vt is a C library which implements a modern terminal emulator,
+ * extracted from the [Ghostty](https://ghostty.org) terminal emulator.
+ *
+ * libghostty-vt contains the logic for handling the core parts of a terminal
+ * emulator: parsing terminal escape sequences, maintaining terminal state,
+ * encoding input events, etc. It can handle scrollback, line wrapping,
+ * reflow on resize, and more.
+ *
+ * @warning This library is currently in development and the API is not yet stable.
+ * Breaking changes are expected in future versions. Use with caution in production code.
+ *
+ * @section groups_sec API Reference
+ *
+ * The API is organized into the following groups:
+ * - @ref terminal "Terminal" - Complete terminal emulator state and rendering
+ * - @ref render "Render State" - Incremental render state updates for custom renderers
+ * - @ref formatter "Formatter" - Format terminal content as plain text, VT sequences, or HTML
+ * - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences
+ * - @ref sgr "SGR Parser" - Parse SGR (Select Graphic Rendition) sequences
+ * - @ref paste "Paste Utilities" - Validate paste data safety
+ * - @ref build_info "Build Info" - Query compile-time build configuration
+ * - @ref allocator "Memory Management" - Memory management and custom allocators
+ * - @ref wasm "WebAssembly Utilities" - WebAssembly convenience functions
+ *
+ * Encoding related APIs:
+ * - @ref focus "Focus Encoding" - Encode focus in/out events into terminal sequences
+ * - @ref key "Key Encoding" - Encode key events into terminal sequences
+ * - @ref mouse "Mouse Encoding" - Encode mouse events into terminal sequences
+ *
+ * @section examples_sec Examples
+ *
+ * Complete working examples:
+ * - @ref c-vt-build-info/src/main.c - Build info query example
+ * - @ref c-vt/src/main.c - OSC parser example
+ * - @ref c-vt-encode-key/src/main.c - Key encoding example
+ * - @ref c-vt-encode-mouse/src/main.c - Mouse encoding example
+ * - @ref c-vt-paste/src/main.c - Paste safety check example
+ * - @ref c-vt-sgr/src/main.c - SGR parser example
+ * - @ref c-vt-formatter/src/main.c - Terminal formatter example
+ * - @ref c-vt-grid-traverse/src/main.c - Grid traversal example using grid refs
+ * - @ref c-vt-grid-ref-tracked/src/main.c - Tracked grid ref example
+ *
+ */
+
+/** @example c-vt-build-info/src/main.c
+ * This example demonstrates how to query compile-time build configuration
+ * such as SIMD support, Kitty graphics, and tmux control mode availability.
+ */
+
+/** @example c-vt/src/main.c
+ * This example demonstrates how to use the OSC parser to parse an OSC sequence,
+ * extract command information, and retrieve command-specific data like window titles.
+ */
+
+/** @example c-vt-encode-key/src/main.c
+ * This example demonstrates how to use the key encoder to convert key events
+ * into terminal escape sequences using the Kitty keyboard protocol.
+ */
+
+/** @example c-vt-encode-mouse/src/main.c
+ * This example demonstrates how to use the mouse encoder to convert mouse events
+ * into terminal escape sequences using the SGR mouse format.
+ */
+
+/** @example c-vt-paste/src/main.c
+ * This example demonstrates how to use the paste utilities to check if
+ * paste data is safe before sending it to the terminal.
+ */
+
+/** @example c-vt-sgr/src/main.c
+ * This example demonstrates how to use the SGR parser to parse terminal
+ * styling sequences and extract text attributes like colors and underline styles.
+ */
+
+/** @example c-vt-formatter/src/main.c
+ * This example demonstrates how to use the terminal and formatter APIs to
+ * create a terminal, write VT-encoded content into it, and format the screen
+ * contents as plain text.
+ */
+
+/** @example c-vt-grid-traverse/src/main.c
+ * This example demonstrates how to traverse the entire terminal grid using
+ * grid refs to inspect cell codepoints, row wrap state, and cell styles.
+ */
+
+/** @example c-vt-grid-ref-tracked/src/main.c
+ * This example demonstrates how to track a grid ref as the terminal scrolls,
+ * detect when it loses its value, and move it to a new point.
+ */
+
+/** @example c-vt-selection-gesture/src/main.c
+ * This example demonstrates how to use synthetic selection gesture events to
+ * derive drag and deep-press selection snapshots.
+ */
+
+/** @example c-vt-kitty-graphics/src/main.c
+ * This example demonstrates how to use the system interface to install a
+ * PNG decoder callback and send a Kitty Graphics Protocol image.
+ */
+
+#ifndef GHOSTTY_VT_H
+#define GHOSTTY_VT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/allocator.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/allocator.h
new file mode 100644
index 00000000000..2e8685e8445
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/allocator.h
@@ -0,0 +1,255 @@
+/**
+ * @file allocator.h
+ *
+ * Memory management interface for libghostty-vt.
+ */
+
+#ifndef GHOSTTY_VT_ALLOCATOR_H
+#define GHOSTTY_VT_ALLOCATOR_H
+
+#include
+#include
+#include
+#include
+
+/** @defgroup allocator Memory Management
+ *
+ * libghostty-vt does require memory allocation for various operations,
+ * but is resilient to allocation failures and will gracefully handle
+ * out-of-memory situations by returning error codes.
+ *
+ * The exact memory management semantics are documented in the relevant
+ * functions and data structures.
+ *
+ * libghostty-vt uses explicit memory allocation via an allocator
+ * interface provided by GhosttyAllocator. The interface is based on the
+ * [Zig](https://ziglang.org) allocator interface, since this has been
+ * shown to be a flexible and powerful interface in practice and enables
+ * a wide variety of allocation strategies.
+ *
+ * **For the common case, you can pass NULL as the allocator for any
+ * function that accepts one,** and libghostty will use a default allocator.
+ * The default allocator will be libc malloc/free if libc is linked.
+ * Otherwise, a custom allocator is used (currently Zig's SMP allocator)
+ * that doesn't require any external dependencies.
+ *
+ * ## Basic Usage
+ *
+ * For simple use cases, you can ignore this interface entirely by passing NULL
+ * as the allocator parameter to functions that accept one. This will use the
+ * default allocator (typically libc malloc/free, if libc is linked, but
+ * we provide our own default allocator if libc isn't linked).
+ *
+ * To use a custom allocator:
+ * 1. Implement the GhosttyAllocatorVtable function pointers
+ * 2. Create a GhosttyAllocator struct with your vtable and context
+ * 3. Pass the allocator to functions that accept one
+ *
+ * ## Alloc/Free Helpers
+ *
+ * ghostty_alloc() and ghostty_free() provide a simple malloc/free-style
+ * interface for allocating and freeing byte buffers through the library's
+ * allocator. These are useful when:
+ *
+ * - You need to allocate a buffer to pass into a libghostty-vt function
+ * (e.g. preparing input data for ghostty_terminal_vt_write()).
+ * - You need to free a buffer returned by a libghostty-vt function
+ * (e.g. the output of ghostty_formatter_format_alloc()).
+ * - You are on a platform where the library's internal allocator differs
+ * from the consumer's C runtime (e.g. Windows, where Zig's libc and
+ * MSVC's CRT maintain separate heaps), so calling the standard C
+ * free() on library-allocated memory would be undefined behavior.
+ *
+ * Always use the same allocator (or NULL) for both the allocation and
+ * the corresponding free.
+ *
+ * @{
+ */
+
+/**
+ * Function table for custom memory allocator operations.
+ *
+ * This vtable defines the interface for a custom memory allocator. All
+ * function pointers must be valid and non-NULL.
+ *
+ * @ingroup allocator
+ *
+ * If you're not going to use a custom allocator, you can ignore all of
+ * this. All functions that take an allocator pointer allow NULL to use a
+ * default allocator.
+ *
+ * The interface is based on the Zig allocator interface. I'll say up front
+ * that it is easy to look at this interface and think "wow, this is really
+ * overcomplicated". The reason for this complexity is well thought out by
+ * the Zig folks, and it enables a diverse set of allocation strategies
+ * as shown by the Zig ecosystem. As a consolation, please note that many
+ * of the arguments are only needed for advanced use cases and can be
+ * safely ignored in simple implementations. For example, if you look at
+ * the Zig implementation of the libc allocator in `lib/std/heap.zig`
+ * (search for CAllocator), you'll see it is very simple.
+ *
+ * We chose to align with the Zig allocator interface because:
+ *
+ * 1. It is a proven interface that serves a wide variety of use cases
+ * in the real world via the Zig ecosystem. It's shown to work.
+ *
+ * 2. Our core implementation itself is Zig, and this lets us very
+ * cheaply and easily convert between C and Zig allocators.
+ *
+ * NOTE(mitchellh): In the future, we can have default implementations of
+ * resize/remap and allow those to be null.
+ */
+typedef struct {
+ /**
+ * Return a pointer to `len` bytes with specified `alignment`, or return
+ * `NULL` indicating the allocation failed.
+ *
+ * @param ctx The allocator context
+ * @param len Number of bytes to allocate
+ * @param alignment Required alignment for the allocation. Guaranteed to
+ * be a power of two between 1 and 16 inclusive.
+ * @param ret_addr First return address of the allocation call stack (0 if not provided)
+ * @return Pointer to allocated memory, or NULL if allocation failed
+ */
+ void* (*alloc)(void *ctx, size_t len, uint8_t alignment, uintptr_t ret_addr);
+
+ /**
+ * Attempt to expand or shrink memory in place.
+ *
+ * `memory_len` must equal the length requested from the most recent
+ * successful call to `alloc`, `resize`, or `remap`. `alignment` must
+ * equal the same value that was passed as the `alignment` parameter to
+ * the original `alloc` call.
+ *
+ * `new_len` must be greater than zero.
+ *
+ * @param ctx The allocator context
+ * @param memory Pointer to the memory block to resize
+ * @param memory_len Current size of the memory block
+ * @param alignment Alignment (must match original allocation)
+ * @param new_len New requested size
+ * @param ret_addr First return address of the allocation call stack (0 if not provided)
+ * @return true if resize was successful in-place, false if relocation would be required
+ */
+ bool (*resize)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, size_t new_len, uintptr_t ret_addr);
+
+ /**
+ * Attempt to expand or shrink memory, allowing relocation.
+ *
+ * `memory_len` must equal the length requested from the most recent
+ * successful call to `alloc`, `resize`, or `remap`. `alignment` must
+ * equal the same value that was passed as the `alignment` parameter to
+ * the original `alloc` call.
+ *
+ * A non-`NULL` return value indicates the resize was successful. The
+ * allocation may have same address, or may have been relocated. In either
+ * case, the allocation now has size of `new_len`. A `NULL` return value
+ * indicates that the resize would be equivalent to allocating new memory,
+ * copying the bytes from the old memory, and then freeing the old memory.
+ * In such case, it is more efficient for the caller to perform the copy.
+ *
+ * `new_len` must be greater than zero.
+ *
+ * @param ctx The allocator context
+ * @param memory Pointer to the memory block to remap
+ * @param memory_len Current size of the memory block
+ * @param alignment Alignment (must match original allocation)
+ * @param new_len New requested size
+ * @param ret_addr First return address of the allocation call stack (0 if not provided)
+ * @return Pointer to resized memory (may be relocated), or NULL if manual copy is needed
+ */
+ void* (*remap)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, size_t new_len, uintptr_t ret_addr);
+
+ /**
+ * Free and invalidate a region of memory.
+ *
+ * `memory_len` must equal the length requested from the most recent
+ * successful call to `alloc`, `resize`, or `remap`. `alignment` must
+ * equal the same value that was passed as the `alignment` parameter to
+ * the original `alloc` call.
+ *
+ * @param ctx The allocator context
+ * @param memory Pointer to the memory block to free
+ * @param memory_len Size of the memory block
+ * @param alignment Alignment (must match original allocation)
+ * @param ret_addr First return address of the allocation call stack (0 if not provided)
+ */
+ void (*free)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, uintptr_t ret_addr);
+} GhosttyAllocatorVtable;
+
+/**
+ * Custom memory allocator.
+ *
+ * For functions that take an allocator pointer, a NULL pointer indicates
+ * that the default allocator should be used. The default allocator will
+ * be libc malloc/free if we're linking to libc. If libc isn't linked,
+ * a custom allocator is used (currently Zig's SMP allocator).
+ *
+ * @ingroup allocator
+ *
+ * Usage example:
+ * @code
+ * GhosttyAllocator allocator = {
+ * .vtable = &my_allocator_vtable,
+ * .ctx = my_allocator_state
+ * };
+ * @endcode
+ */
+typedef struct GhosttyAllocator {
+ /**
+ * Opaque context pointer passed to all vtable functions.
+ * This allows the allocator implementation to maintain state
+ * or reference external resources needed for memory management.
+ */
+ void *ctx;
+
+ /**
+ * Pointer to the allocator's vtable containing function pointers
+ * for memory operations (alloc, resize, remap, free).
+ */
+ const GhosttyAllocatorVtable *vtable;
+} GhosttyAllocator;
+
+/**
+ * Allocate a buffer of `len` bytes.
+ *
+ * Uses the provided allocator, or the default allocator if NULL is passed.
+ * The returned buffer must be freed with ghostty_free() using the same
+ * allocator.
+ *
+ * @param allocator Pointer to the allocator to use, or NULL for the default
+ * @param len Number of bytes to allocate
+ * @return Pointer to the allocated buffer, or NULL if allocation failed
+ *
+ * @ingroup allocator
+ */
+GHOSTTY_API uint8_t* ghostty_alloc(const GhosttyAllocator* allocator, size_t len);
+
+/**
+ * Free memory that was allocated by a libghostty-vt function.
+ *
+ * Use this to free buffers returned by functions such as
+ * ghostty_formatter_format_alloc(). Pass the same allocator that was
+ * used for the allocation, or NULL if the default allocator was used.
+ *
+ * On platforms where the library's internal allocator differs from the
+ * consumer's C runtime (e.g. Windows, where Zig's libc and MSVC's CRT
+ * maintain separate heaps), calling the standard C free() on memory
+ * allocated by the library causes undefined behavior. This function
+ * guarantees the correct allocator is used regardless of platform.
+ *
+ * It is safe to pass a NULL pointer; the call is a no-op in that case.
+ *
+ * @param allocator Pointer to the allocator that was used to allocate the
+ * memory, or NULL if the default allocator was used
+ * @param ptr Pointer to the memory to free (may be NULL)
+ * @param len Length of the allocation in bytes (must match the original
+ * allocation size)
+ *
+ * @ingroup allocator
+ */
+GHOSTTY_API void ghostty_free(const GhosttyAllocator* allocator, uint8_t* ptr, size_t len);
+
+/** @} */
+
+#endif /* GHOSTTY_VT_ALLOCATOR_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/build_info.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/build_info.h
new file mode 100644
index 00000000000..8573556f7f8
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/build_info.h
@@ -0,0 +1,150 @@
+/**
+ * @file build_info.h
+ *
+ * Build info - query compile-time build configuration of libghostty-vt.
+ */
+
+#ifndef GHOSTTY_VT_BUILD_INFO_H
+#define GHOSTTY_VT_BUILD_INFO_H
+
+/** @defgroup build_info Build Info
+ *
+ * Query compile-time build configuration of libghostty-vt.
+ *
+ * These values reflect the options the library was built with and are
+ * constant for the lifetime of the process.
+ *
+ * ## Basic Usage
+ *
+ * Use ghostty_build_info() to query individual build options:
+ *
+ * @snippet c-vt-build-info/src/main.c build-info-query
+ *
+ * @{
+ */
+
+#include
+#include
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Build optimization mode.
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_OPTIMIZE_DEBUG = 0,
+ GHOSTTY_OPTIMIZE_RELEASE_SAFE = 1,
+ GHOSTTY_OPTIMIZE_RELEASE_SMALL = 2,
+ GHOSTTY_OPTIMIZE_RELEASE_FAST = 3,
+ GHOSTTY_OPTIMIZE_MODE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyOptimizeMode;
+
+/**
+ * Build info data types that can be queried.
+ *
+ * Each variant documents the expected output pointer type.
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Invalid data type. Never results in any data extraction. */
+ GHOSTTY_BUILD_INFO_INVALID = 0,
+
+ /**
+ * Whether SIMD-accelerated code paths are enabled.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_BUILD_INFO_SIMD = 1,
+
+ /**
+ * Whether Kitty graphics protocol support is available.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_BUILD_INFO_KITTY_GRAPHICS = 2,
+
+ /**
+ * Whether tmux control mode support is available.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_BUILD_INFO_TMUX_CONTROL_MODE = 3,
+
+ /**
+ * The optimization mode the library was built with.
+ *
+ * Output type: GhosttyOptimizeMode *
+ */
+ GHOSTTY_BUILD_INFO_OPTIMIZE = 4,
+
+ /**
+ * The full version string (e.g. "1.2.3" or "1.2.3-dev+abcdef").
+ *
+ * Output type: GhosttyString *
+ */
+ GHOSTTY_BUILD_INFO_VERSION_STRING = 5,
+
+ /**
+ * The major version number.
+ *
+ * Output type: size_t *
+ */
+ GHOSTTY_BUILD_INFO_VERSION_MAJOR = 6,
+
+ /**
+ * The minor version number.
+ *
+ * Output type: size_t *
+ */
+ GHOSTTY_BUILD_INFO_VERSION_MINOR = 7,
+
+ /**
+ * The patch version number.
+ *
+ * Output type: size_t *
+ */
+ GHOSTTY_BUILD_INFO_VERSION_PATCH = 8,
+
+ /**
+ * The pre metadata string (e.g. "alpha", "beta", "dev"). Has zero length if
+ * no pre metadata is present.
+ *
+ * Output type: GhosttyString *
+ */
+ GHOSTTY_BUILD_INFO_VERSION_PRE = 9,
+
+ /**
+ * The build metadata string (e.g. commit hash). Has zero length if
+ * no build metadata is present.
+ *
+ * Output type: GhosttyString *
+ */
+ GHOSTTY_BUILD_INFO_VERSION_BUILD = 10,
+ GHOSTTY_BUILD_INFO_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyBuildInfo;
+
+/**
+ * Query a compile-time build configuration value.
+ *
+ * The caller must pass a pointer to the correct output type for the
+ * requested data (see GhosttyBuildInfo variants for types).
+ *
+ * @param data The build info field to query
+ * @param out Pointer to store the result (type depends on data parameter)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the
+ * data type is invalid
+ *
+ * @ingroup build_info
+ */
+GHOSTTY_API GhosttyResult ghostty_build_info(GhosttyBuildInfo data, void *out);
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
+
+#endif /* GHOSTTY_VT_BUILD_INFO_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/color.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/color.h
new file mode 100644
index 00000000000..9dc21864eb9
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/color.h
@@ -0,0 +1,97 @@
+/**
+ * @file color.h
+ *
+ * Color types and utilities.
+ */
+
+#ifndef GHOSTTY_VT_COLOR_H
+#define GHOSTTY_VT_COLOR_H
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * RGB color value.
+ *
+ * @ingroup sgr
+ */
+typedef struct {
+ uint8_t r; /**< Red component (0-255) */
+ uint8_t g; /**< Green component (0-255) */
+ uint8_t b; /**< Blue component (0-255) */
+} GhosttyColorRgb;
+
+/**
+ * Palette color index (0-255).
+ *
+ * @ingroup sgr
+ */
+typedef uint8_t GhosttyColorPaletteIndex;
+
+/** @addtogroup sgr
+ * @{
+ */
+
+/** Black color (0) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_BLACK 0
+/** Red color (1) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_RED 1
+/** Green color (2) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_GREEN 2
+/** Yellow color (3) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_YELLOW 3
+/** Blue color (4) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_BLUE 4
+/** Magenta color (5) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_MAGENTA 5
+/** Cyan color (6) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_CYAN 6
+/** White color (7) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_WHITE 7
+/** Bright black color (8) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_BRIGHT_BLACK 8
+/** Bright red color (9) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_BRIGHT_RED 9
+/** Bright green color (10) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_BRIGHT_GREEN 10
+/** Bright yellow color (11) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_BRIGHT_YELLOW 11
+/** Bright blue color (12) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_BRIGHT_BLUE 12
+/** Bright magenta color (13) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_BRIGHT_MAGENTA 13
+/** Bright cyan color (14) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_BRIGHT_CYAN 14
+/** Bright white color (15) @ingroup sgr */
+#define GHOSTTY_COLOR_NAMED_BRIGHT_WHITE 15
+
+/** @} */
+
+/**
+ * Get the RGB color components.
+ *
+ * This function extracts the individual red, green, and blue components
+ * from a GhosttyColorRgb value. Primarily useful in WebAssembly environments
+ * where accessing struct fields directly is difficult.
+ *
+ * @param color The RGB color value
+ * @param r Pointer to store the red component (0-255)
+ * @param g Pointer to store the green component (0-255)
+ * @param b Pointer to store the blue component (0-255)
+ *
+ * @ingroup sgr
+ */
+GHOSTTY_API void ghostty_color_rgb_get(GhosttyColorRgb color,
+ uint8_t* r,
+ uint8_t* g,
+ uint8_t* b);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_COLOR_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/device.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/device.h
new file mode 100644
index 00000000000..0a1567280b8
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/device.h
@@ -0,0 +1,151 @@
+/**
+ * @file device.h
+ *
+ * Device types used by the terminal for device status and device attribute
+ * queries.
+ */
+
+#ifndef GHOSTTY_VT_DEVICE_H
+#define GHOSTTY_VT_DEVICE_H
+
+#include
+#include
+
+/* DA1 conformance levels (Pp parameter). */
+#define GHOSTTY_DA_CONFORMANCE_VT100 1
+#define GHOSTTY_DA_CONFORMANCE_VT101 1
+#define GHOSTTY_DA_CONFORMANCE_VT102 6
+#define GHOSTTY_DA_CONFORMANCE_VT125 12
+#define GHOSTTY_DA_CONFORMANCE_VT131 7
+#define GHOSTTY_DA_CONFORMANCE_VT132 4
+#define GHOSTTY_DA_CONFORMANCE_VT220 62
+#define GHOSTTY_DA_CONFORMANCE_VT240 62
+#define GHOSTTY_DA_CONFORMANCE_VT320 63
+#define GHOSTTY_DA_CONFORMANCE_VT340 63
+#define GHOSTTY_DA_CONFORMANCE_VT420 64
+#define GHOSTTY_DA_CONFORMANCE_VT510 65
+#define GHOSTTY_DA_CONFORMANCE_VT520 65
+#define GHOSTTY_DA_CONFORMANCE_VT525 65
+#define GHOSTTY_DA_CONFORMANCE_LEVEL_2 62
+#define GHOSTTY_DA_CONFORMANCE_LEVEL_3 63
+#define GHOSTTY_DA_CONFORMANCE_LEVEL_4 64
+#define GHOSTTY_DA_CONFORMANCE_LEVEL_5 65
+
+/* DA1 feature codes (Ps parameters). */
+#define GHOSTTY_DA_FEATURE_COLUMNS_132 1
+#define GHOSTTY_DA_FEATURE_PRINTER 2
+#define GHOSTTY_DA_FEATURE_REGIS 3
+#define GHOSTTY_DA_FEATURE_SIXEL 4
+#define GHOSTTY_DA_FEATURE_SELECTIVE_ERASE 6
+#define GHOSTTY_DA_FEATURE_USER_DEFINED_KEYS 8
+#define GHOSTTY_DA_FEATURE_NATIONAL_REPLACEMENT 9
+#define GHOSTTY_DA_FEATURE_TECHNICAL_CHARACTERS 15
+#define GHOSTTY_DA_FEATURE_LOCATOR 16
+#define GHOSTTY_DA_FEATURE_TERMINAL_STATE 17
+#define GHOSTTY_DA_FEATURE_WINDOWING 18
+#define GHOSTTY_DA_FEATURE_HORIZONTAL_SCROLLING 21
+#define GHOSTTY_DA_FEATURE_ANSI_COLOR 22
+#define GHOSTTY_DA_FEATURE_RECTANGULAR_EDITING 28
+#define GHOSTTY_DA_FEATURE_ANSI_TEXT_LOCATOR 29
+#define GHOSTTY_DA_FEATURE_CLIPBOARD 52
+
+/* DA2 device type identifiers (Pp parameter). */
+#define GHOSTTY_DA_DEVICE_TYPE_VT100 0
+#define GHOSTTY_DA_DEVICE_TYPE_VT220 1
+#define GHOSTTY_DA_DEVICE_TYPE_VT240 2
+#define GHOSTTY_DA_DEVICE_TYPE_VT330 18
+#define GHOSTTY_DA_DEVICE_TYPE_VT340 19
+#define GHOSTTY_DA_DEVICE_TYPE_VT320 24
+#define GHOSTTY_DA_DEVICE_TYPE_VT382 32
+#define GHOSTTY_DA_DEVICE_TYPE_VT420 41
+#define GHOSTTY_DA_DEVICE_TYPE_VT510 61
+#define GHOSTTY_DA_DEVICE_TYPE_VT520 64
+#define GHOSTTY_DA_DEVICE_TYPE_VT525 65
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Color scheme reported in response to a CSI ? 996 n query.
+ *
+ * @ingroup terminal
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_COLOR_SCHEME_LIGHT = 0,
+ GHOSTTY_COLOR_SCHEME_DARK = 1,
+ GHOSTTY_COLOR_SCHEME_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyColorScheme;
+
+/**
+ * Primary device attributes (DA1) response data.
+ *
+ * Returned as part of GhosttyDeviceAttributes in response to a CSI c query.
+ * The conformance_level is the Pp parameter and features contains the Ps
+ * feature codes.
+ *
+ * @ingroup terminal
+ */
+typedef struct {
+ /** Conformance level (Pp parameter). E.g. 62 for VT220. */
+ uint16_t conformance_level;
+
+ /** DA1 feature codes. Only the first num_features entries are valid. */
+ uint16_t features[64];
+
+ /** Number of valid entries in the features array. */
+ size_t num_features;
+} GhosttyDeviceAttributesPrimary;
+
+/**
+ * Secondary device attributes (DA2) response data.
+ *
+ * Returned as part of GhosttyDeviceAttributes in response to a CSI > c query.
+ * Response format: CSI > Pp ; Pv ; Pc c
+ *
+ * @ingroup terminal
+ */
+typedef struct {
+ /** Terminal type identifier (Pp). E.g. 1 for VT220. */
+ uint16_t device_type;
+
+ /** Firmware/patch version number (Pv). */
+ uint16_t firmware_version;
+
+ /** ROM cartridge registration number (Pc). Always 0 for emulators. */
+ uint16_t rom_cartridge;
+} GhosttyDeviceAttributesSecondary;
+
+/**
+ * Tertiary device attributes (DA3) response data.
+ *
+ * Returned as part of GhosttyDeviceAttributes in response to a CSI = c query.
+ * Response format: DCS ! | D...D ST (DECRPTUI).
+ *
+ * @ingroup terminal
+ */
+typedef struct {
+ /** Unit ID encoded as 8 uppercase hex digits in the response. */
+ uint32_t unit_id;
+} GhosttyDeviceAttributesTertiary;
+
+/**
+ * Device attributes response data for all three DA levels.
+ *
+ * Filled by the device_attributes callback in response to CSI c,
+ * CSI > c, or CSI = c queries. The terminal uses whichever sub-struct
+ * matches the request type.
+ *
+ * @ingroup terminal
+ */
+typedef struct {
+ GhosttyDeviceAttributesPrimary primary;
+ GhosttyDeviceAttributesSecondary secondary;
+ GhosttyDeviceAttributesTertiary tertiary;
+} GhosttyDeviceAttributes;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_DEVICE_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/focus.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/focus.h
new file mode 100644
index 00000000000..b9940f79247
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/focus.h
@@ -0,0 +1,76 @@
+/**
+ * @file focus.h
+ *
+ * Focus encoding - encode focus in/out events into terminal escape sequences.
+ */
+
+#ifndef GHOSTTY_VT_FOCUS_H
+#define GHOSTTY_VT_FOCUS_H
+
+/** @defgroup focus Focus Encoding
+ *
+ * Utilities for encoding focus gained/lost events into terminal escape
+ * sequences (CSI I / CSI O) for focus reporting mode (mode 1004).
+ *
+ * ## Basic Usage
+ *
+ * Use ghostty_focus_encode() to encode a focus event into a caller-provided
+ * buffer. If the buffer is too small, the function returns
+ * GHOSTTY_OUT_OF_SPACE and sets the required size in the output parameter.
+ *
+ * ## Example
+ *
+ * @snippet c-vt-encode-focus/src/main.c focus-encode
+ *
+ * @{
+ */
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Focus event types for focus reporting mode (mode 1004).
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Terminal window gained focus */
+ GHOSTTY_FOCUS_GAINED = 0,
+ /** Terminal window lost focus */
+ GHOSTTY_FOCUS_LOST = 1,
+ GHOSTTY_FOCUS_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyFocusEvent;
+
+/**
+ * Encode a focus event into a terminal escape sequence.
+ *
+ * Encodes a focus gained (CSI I) or focus lost (CSI O) report into the
+ * provided buffer.
+ *
+ * If the buffer is too small, the function returns GHOSTTY_OUT_OF_SPACE
+ * and writes the required buffer size to @p out_written. The caller can
+ * then retry with a sufficiently sized buffer.
+ *
+ * @param event The focus event to encode
+ * @param buf Output buffer to write the encoded sequence into (may be NULL)
+ * @param buf_len Size of the output buffer in bytes
+ * @param[out] out_written On success, the number of bytes written. On
+ * GHOSTTY_OUT_OF_SPACE, the required buffer size.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_SPACE if the buffer
+ * is too small
+ */
+GHOSTTY_API GhosttyResult ghostty_focus_encode(
+ GhosttyFocusEvent event,
+ char* buf,
+ size_t buf_len,
+ size_t* out_written);
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
+
+#endif /* GHOSTTY_VT_FOCUS_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/formatter.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/formatter.h
new file mode 100644
index 00000000000..5cdcd11a3a7
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/formatter.h
@@ -0,0 +1,207 @@
+/**
+ * @file formatter.h
+ *
+ * Format terminal content as plain text, VT sequences, or HTML.
+ */
+
+#ifndef GHOSTTY_VT_FORMATTER_H
+#define GHOSTTY_VT_FORMATTER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @defgroup formatter Formatter
+ *
+ * Format terminal content as plain text, VT sequences, or HTML.
+ *
+ * A formatter captures a reference to a terminal and formatting options.
+ * It can be used repeatedly to produce output that reflects the current
+ * terminal state at the time of each format call.
+ *
+ * The terminal must outlive the formatter.
+ *
+ * @{
+ */
+
+/**
+ * Extra screen state to include in styled output.
+ *
+ * @ingroup formatter
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttyFormatterScreenExtra). */
+ size_t size;
+
+ /** Emit cursor position using CUP (CSI H). */
+ bool cursor;
+
+ /** Emit current SGR style state based on the cursor's active style_id. */
+ bool style;
+
+ /** Emit current hyperlink state using OSC 8 sequences. */
+ bool hyperlink;
+
+ /** Emit character protection mode using DECSCA. */
+ bool protection;
+
+ /** Emit Kitty keyboard protocol state using CSI > u and CSI = sequences. */
+ bool kitty_keyboard;
+
+ /** Emit character set designations and invocations. */
+ bool charsets;
+} GhosttyFormatterScreenExtra;
+
+/**
+ * Extra terminal state to include in styled output.
+ *
+ * @ingroup formatter
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttyFormatterTerminalExtra). */
+ size_t size;
+
+ /** Emit the palette using OSC 4 sequences. */
+ bool palette;
+
+ /** Emit terminal modes that differ from their defaults using CSI h/l. */
+ bool modes;
+
+ /** Emit scrolling region state using DECSTBM and DECSLRM sequences. */
+ bool scrolling_region;
+
+ /** Emit tabstop positions by clearing all tabs and setting each one. */
+ bool tabstops;
+
+ /** Emit the present working directory using OSC 7. */
+ bool pwd;
+
+ /** Emit keyboard modes such as ModifyOtherKeys. */
+ bool keyboard;
+
+ /** Screen-level extras. */
+ GhosttyFormatterScreenExtra screen;
+} GhosttyFormatterTerminalExtra;
+
+/**
+ * Options for creating a terminal formatter.
+ *
+ * @ingroup formatter
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttyFormatterTerminalOptions). */
+ size_t size;
+
+ /** Output format to emit. */
+ GhosttyFormatterFormat emit;
+
+ /** Whether to unwrap soft-wrapped lines. */
+ bool unwrap;
+
+ /** Whether to trim trailing whitespace on non-blank lines. */
+ bool trim;
+
+ /** Extra terminal state to include in styled output. */
+ GhosttyFormatterTerminalExtra extra;
+
+ /** Optional selection to restrict output to a range.
+ * If NULL, the entire screen is formatted. */
+ const GhosttySelection *selection;
+} GhosttyFormatterTerminalOptions;
+
+/**
+ * Create a formatter for a terminal's active screen.
+ *
+ * The terminal must outlive the formatter. The formatter stores a borrowed
+ * reference to the terminal and reads its current state on each format call.
+ *
+ * @param allocator Pointer to allocator, or NULL to use the default allocator
+ * @param formatter Pointer to store the created formatter handle
+ * @param terminal The terminal to format (must not be NULL)
+ * @param options Formatting options
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup formatter
+ */
+GHOSTTY_API GhosttyResult ghostty_formatter_terminal_new(
+ const GhosttyAllocator* allocator,
+ GhosttyFormatter* formatter,
+ GhosttyTerminal terminal,
+ GhosttyFormatterTerminalOptions options);
+
+/**
+ * Run the formatter and produce output into the caller-provided buffer.
+ *
+ * Each call formats the current terminal state. Pass NULL for buf to
+ * query the required buffer size without writing any output; in that case
+ * out_written receives the required size and the return value is
+ * GHOSTTY_OUT_OF_SPACE.
+ *
+ * If the buffer is too small, returns GHOSTTY_OUT_OF_SPACE and sets
+ * out_written to the required size. The caller can then retry with a
+ * larger buffer.
+ *
+ * @param formatter The formatter handle (must not be NULL)
+ * @param buf Pointer to the output buffer, or NULL to query size
+ * @param buf_len Length of the output buffer in bytes
+ * @param out_written Pointer to receive the number of bytes written,
+ * or the required size on failure
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup formatter
+ */
+GHOSTTY_API GhosttyResult ghostty_formatter_format_buf(GhosttyFormatter formatter,
+ uint8_t* buf,
+ size_t buf_len,
+ size_t* out_written);
+
+/**
+ * Run the formatter and return an allocated buffer with the output.
+ *
+ * Each call formats the current terminal state. The buffer is allocated
+ * using the provided allocator (or the default allocator if NULL).
+ * The caller is responsible for freeing the returned buffer with
+ * ghostty_free(), passing the same allocator (or NULL for the default)
+ * that was used for the allocation.
+ *
+ * @param formatter The formatter handle (must not be NULL)
+ * @param allocator Pointer to allocator, or NULL to use the default allocator
+ * @param out_ptr Pointer to receive the allocated buffer
+ * @param out_len Pointer to receive the length of the output in bytes
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation
+ * failure
+ *
+ * @ingroup formatter
+ */
+GHOSTTY_API GhosttyResult ghostty_formatter_format_alloc(GhosttyFormatter formatter,
+ const GhosttyAllocator* allocator,
+ uint8_t** out_ptr,
+ size_t* out_len);
+
+/**
+ * Free a formatter instance.
+ *
+ * Releases all resources associated with the formatter. After this call,
+ * the formatter handle becomes invalid.
+ *
+ * @param formatter The formatter handle to free (may be NULL)
+ *
+ * @ingroup formatter
+ */
+GHOSTTY_API void ghostty_formatter_free(GhosttyFormatter formatter);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_FORMATTER_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/grid_ref.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/grid_ref.h
new file mode 100644
index 00000000000..c43791dc238
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/grid_ref.h
@@ -0,0 +1,212 @@
+/**
+ * @file grid_ref.h
+ *
+ * Terminal grid reference type for referencing a resolved position in the
+ * terminal grid.
+ */
+
+#ifndef GHOSTTY_VT_GRID_REF_H
+#define GHOSTTY_VT_GRID_REF_H
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @defgroup grid_ref Grid Reference
+ *
+ * A grid reference is a reference to a specific cell position in the
+ * terminal. Obtain a grid reference from `ghostty_terminal_grid_ref`
+ * for untracked or `ghostty_terminal_grid_ref_track` for tracked. Untracked
+ * vs tracked is explained next.
+ *
+ * Important: The grid reference APIs are not meant to be used as the core of a render
+ * loop. They are not built to sustain the framerates needed for rendering large
+ * screens. Use the render state API for that.
+ *
+ * ## Untracked vs Tracked References
+ *
+ * ### Untracked Reference
+ *
+ * An untracked grid reference is a value type that snapshots a specific
+ * cell. It is only valid until the next update to the terminal instance.
+ * There is no guarantee that it will remain valid after any operation,
+ * even if a seemingly unrelated part of the grid is changed. These are meant
+ * to be read and have their values cached immediately after obtaining it.
+ *
+ * An untracked grid reference has a performance cost in its initial lookup,
+ * but doesn't affect the ongoing performance of the terminal in any way,
+ * since it is a one-time snapshot.
+ *
+ * ### Tracked Reference
+ *
+ * A tracked grid reference follows its cell across normal screen operations.
+ * For example scrolling, scrollback pruning, resize/reflow, and other
+ * terminal mutations update the tracked reference automatically.
+ *
+ * A tracked reference can still lose its original semantic location. This can
+ * happen when the underlying grid is reset, pruned, or otherwise discarded in a
+ * way that cannot be mapped to a meaningful new cell. In that state,
+ * ghostty_tracked_grid_ref_has_value() returns false and
+ * ghostty_tracked_grid_ref_snapshot() / ghostty_tracked_grid_ref_point() return
+ * GHOSTTY_NO_VALUE. The handle remains valid, and callers may move it to a new
+ * point with ghostty_tracked_grid_ref_set().
+ *
+ * To read cell data from a tracked reference, first snapshot it with
+ * ghostty_tracked_grid_ref_snapshot(). The returned `GhosttyGridRef` is again
+ * an untracked reference and follows the same short lifetime rules as any other
+ * untracked grid reference.
+ *
+ * A tracked reference belongs to the terminal screen/page-list that was active
+ * when it was created or last set. Converting it to a point uses that owning
+ * screen/page-list, even if the terminal has since switched between primary and
+ * alternate screens. Calling ghostty_tracked_grid_ref_set() resolves the new
+ * point against the terminal's currently active screen/page-list and may move
+ * the tracked reference between screens.
+ *
+ * Tracked references are owned by the caller and must be freed with
+ * ghostty_tracked_grid_ref_free(). If the terminal that created a tracked
+ * reference is freed first, the handle remains valid only for tracked-grid-ref
+ * APIs: it reports no value and can still be freed.
+ *
+ * Each tracked reference adds bookkeeping to terminal mutations. Use them
+ * sparingly for long-lived anchors such as selections, search state, marks,
+ * or application-side bookmarks.
+ *
+ * ## Lifetime
+ *
+ * An untracked reference is a snapshot. It doesn't need to be freed.
+ * The safety of accessing the value is documented explicitly above: it
+ * is only safe to access any data until the next terminal mutating
+ * operation (including free).
+ *
+ * A tracked reference is allocated and must be freed when it is no
+ * longer needed. A tracked reference may outlive the terminal that created it;
+ * after terminal free, it reports no value and can still be freed.
+ *
+ * ## Examples
+ *
+ * @snippet c-vt-grid-traverse/src/main.c grid-ref-traverse
+ * @snippet c-vt-grid-ref-tracked/src/main.c grid-ref-tracked
+ *
+ * @{
+ */
+
+/**
+ * A resolved reference to a terminal cell position.
+ *
+ * This is a sized struct. Use GHOSTTY_INIT_SIZED() to initialize it.
+ *
+ * @ingroup grid_ref
+ */
+typedef struct {
+ size_t size;
+ void *node;
+ uint16_t x;
+ uint16_t y;
+} GhosttyGridRef;
+
+/**
+ * Get the cell from a grid reference.
+ *
+ * @param ref Pointer to the grid reference
+ * @param[out] out_cell On success, set to the cell at the ref's position (may be NULL)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the ref's
+ * node is NULL
+ *
+ * @ingroup grid_ref
+ */
+GHOSTTY_API GhosttyResult ghostty_grid_ref_cell(const GhosttyGridRef *ref,
+ GhosttyCell *out_cell);
+
+/**
+ * Get the row from a grid reference.
+ *
+ * @param ref Pointer to the grid reference
+ * @param[out] out_row On success, set to the row at the ref's position (may be NULL)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the ref's
+ * node is NULL
+ *
+ * @ingroup grid_ref
+ */
+GHOSTTY_API GhosttyResult ghostty_grid_ref_row(const GhosttyGridRef *ref,
+ GhosttyRow *out_row);
+
+/**
+ * Get the grapheme cluster codepoints for the cell at the grid reference's
+ * position.
+ *
+ * Writes the full grapheme cluster (the cell's primary codepoint followed by
+ * any combining codepoints) into the provided buffer. If the cell has no text,
+ * out_len is set to 0 and GHOSTTY_SUCCESS is returned.
+ *
+ * If the buffer is too small (or NULL), the function returns
+ * GHOSTTY_OUT_OF_SPACE and writes the required number of codepoints to
+ * out_len. The caller can then retry with a sufficiently sized buffer.
+ *
+ * @param ref Pointer to the grid reference
+ * @param buf Output buffer of uint32_t codepoints (may be NULL)
+ * @param buf_len Number of uint32_t elements in the buffer
+ * @param[out] out_len On success, the number of codepoints written. On
+ * GHOSTTY_OUT_OF_SPACE, the required buffer size in codepoints.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the ref's
+ * node is NULL, GHOSTTY_OUT_OF_SPACE if the buffer is too small
+ *
+ * @ingroup grid_ref
+ */
+GHOSTTY_API GhosttyResult ghostty_grid_ref_graphemes(const GhosttyGridRef *ref,
+ uint32_t *buf,
+ size_t buf_len,
+ size_t *out_len);
+
+/**
+ * Get the hyperlink URI for the cell at the grid reference's position.
+ *
+ * Writes the URI bytes into the provided buffer. If the cell has no
+ * hyperlink, out_len is set to 0 and GHOSTTY_SUCCESS is returned.
+ *
+ * If the buffer is too small (or NULL), the function returns
+ * GHOSTTY_OUT_OF_SPACE and writes the required number of bytes to
+ * out_len. The caller can then retry with a sufficiently sized buffer.
+ *
+ * @param ref Pointer to the grid reference
+ * @param buf Output buffer for the URI bytes (may be NULL)
+ * @param buf_len Size of the output buffer in bytes
+ * @param[out] out_len On success, the number of bytes written. On
+ * GHOSTTY_OUT_OF_SPACE, the required buffer size in bytes.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the ref's
+ * node is NULL, GHOSTTY_OUT_OF_SPACE if the buffer is too small
+ *
+ * @ingroup grid_ref
+ */
+GHOSTTY_API GhosttyResult ghostty_grid_ref_hyperlink_uri(
+ const GhosttyGridRef *ref,
+ uint8_t *buf,
+ size_t buf_len,
+ size_t *out_len);
+
+/**
+ * Get the style of the cell at the grid reference's position.
+ *
+ * @param ref Pointer to the grid reference
+ * @param[out] out_style On success, set to the cell's style (may be NULL)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the ref's
+ * node is NULL
+ *
+ * @ingroup grid_ref
+ */
+GHOSTTY_API GhosttyResult ghostty_grid_ref_style(const GhosttyGridRef *ref,
+ GhosttyStyle *out_style);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_GRID_REF_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/grid_ref_tracked.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/grid_ref_tracked.h
new file mode 100644
index 00000000000..b56aefacdb6
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/grid_ref_tracked.h
@@ -0,0 +1,139 @@
+/**
+ * @file grid_ref_tracked.h
+ *
+ * Tracked terminal grid references.
+ */
+
+#ifndef GHOSTTY_VT_GRID_REF_TRACKED_H
+#define GHOSTTY_VT_GRID_REF_TRACKED_H
+
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Tracked grid references are owned grid references that move with the
+ * terminal. See @ref grid_ref for the full overview of tracked and untracked
+ * grid reference behavior.
+ *
+ * @ingroup grid_ref
+ */
+
+/**
+ * Free a tracked grid reference.
+ *
+ * Passing NULL is allowed and has no effect. A tracked reference may be freed
+ * after the terminal that created it is freed.
+ *
+ * @param ref Tracked grid reference to free.
+ *
+ * @ingroup grid_ref
+ */
+GHOSTTY_API void ghostty_tracked_grid_ref_free(GhosttyTrackedGridRef ref);
+
+/**
+ * Return whether a tracked grid reference currently has a meaningful value.
+ *
+ * If the terminal that created the tracked reference has been freed, this
+ * returns false.
+ *
+ * @param ref Tracked grid reference.
+ * @return true if the reference currently has a meaningful value.
+ *
+ * @ingroup grid_ref
+ */
+GHOSTTY_API bool ghostty_tracked_grid_ref_has_value(
+ GhosttyTrackedGridRef ref);
+
+/**
+ * Convert a tracked grid reference to a point in the requested coordinate
+ * space.
+ *
+ * This is the tracked equivalent of ghostty_terminal_point_from_grid_ref().
+ * Unlike snapshotting, this does not expose an intermediate untracked
+ * GhosttyGridRef.
+ *
+ * A tracked reference is resolved against the terminal screen/page-list that
+ * currently owns the reference. If the terminal has switched between primary
+ * and alternate screens since the reference was created or last set, this may
+ * be different from the terminal's currently active screen.
+ *
+ * If the tracked reference no longer has a meaningful value, this returns
+ * GHOSTTY_NO_VALUE. GHOSTTY_NO_VALUE is also returned when the reference cannot
+ * be represented in the requested coordinate space, including after the
+ * terminal that created the tracked reference has been freed.
+ *
+ * @param ref Tracked grid reference.
+ * @param tag Coordinate space to convert into.
+ * @param[out] out_point On success, receives the coordinate. May be NULL.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if ref is invalid,
+ * or GHOSTTY_NO_VALUE if there is no representable value.
+ *
+ * @ingroup grid_ref
+ */
+GHOSTTY_API GhosttyResult ghostty_tracked_grid_ref_point(
+ GhosttyTrackedGridRef ref,
+ GhosttyPointTag tag,
+ GhosttyPointCoordinate *out_point);
+
+/**
+ * Move an existing tracked grid reference to a new terminal point.
+ *
+ * On success, the tracked reference begins tracking the new point and any prior
+ * "no value" state is cleared. On GHOSTTY_OUT_OF_MEMORY, the original tracked
+ * reference is left unchanged.
+ *
+ * The terminal must be the same terminal that created the tracked reference.
+ * The point is resolved against the terminal screen/page-list that is active at
+ * the time this function is called. If the terminal has switched between
+ * primary and alternate screens, this may move the tracked reference from one
+ * screen/page-list to the other.
+ *
+ * @param ref Tracked grid reference.
+ * @param terminal Terminal instance that owns the reference.
+ * @param point New point to track.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if ref, terminal,
+ * or point is invalid, or GHOSTTY_OUT_OF_MEMORY if allocation fails.
+ *
+ * @ingroup grid_ref
+ */
+GHOSTTY_API GhosttyResult ghostty_tracked_grid_ref_set(
+ GhosttyTrackedGridRef ref,
+ GhosttyTerminal terminal,
+ GhosttyPoint point);
+
+/**
+ * Snapshot a tracked grid reference into a regular GhosttyGridRef.
+ *
+ * The returned GhosttyGridRef is an untracked snapshot and has the same
+ * lifetime rules as ghostty_terminal_grid_ref(): it is only valid until the
+ * next terminal update. Snapshot immediately before calling
+ * ghostty_grid_ref_cell(), ghostty_grid_ref_row(),
+ * ghostty_grid_ref_graphemes(), ghostty_grid_ref_hyperlink_uri(), or
+ * ghostty_grid_ref_style().
+ *
+ * If the tracked reference no longer has a meaningful value, this returns
+ * GHOSTTY_NO_VALUE. This includes references whose owning terminal has been
+ * freed.
+ *
+ * @param ref Tracked grid reference.
+ * @param[out] out_ref On success, receives an untracked snapshot. May be NULL.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if ref is invalid,
+ * or GHOSTTY_NO_VALUE if the tracked location was discarded.
+ *
+ * @ingroup grid_ref
+ */
+GHOSTTY_API GhosttyResult ghostty_tracked_grid_ref_snapshot(
+ GhosttyTrackedGridRef ref,
+ GhosttyGridRef *out_ref);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_GRID_REF_TRACKED_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/key.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/key.h
new file mode 100644
index 00000000000..61b95475357
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/key.h
@@ -0,0 +1,73 @@
+/**
+ * @file key.h
+ *
+ * Key encoding module - encode key events into terminal escape sequences.
+ */
+
+#ifndef GHOSTTY_VT_KEY_H
+#define GHOSTTY_VT_KEY_H
+
+/** @defgroup key Key Encoding
+ *
+ * Utilities for encoding key events into terminal escape sequences,
+ * supporting both legacy encoding as well as Kitty Keyboard Protocol.
+ *
+ * ## Basic Usage
+ *
+ * 1. Create an encoder instance with ghostty_key_encoder_new()
+ * 2. Configure encoder options with ghostty_key_encoder_setopt()
+ * or ghostty_key_encoder_setopt_from_terminal() if you have a
+ * GhosttyTerminal.
+ * 3. For each key event:
+ * - Create a key event with ghostty_key_event_new()
+ * - Set event properties (action, key, modifiers, etc.)
+ * - Encode with ghostty_key_encoder_encode()
+ * - Free the event with ghostty_key_event_free()
+ * - Note: You can also reuse the same key event multiple times by
+ * changing its properties.
+ * 4. Free the encoder with ghostty_key_encoder_free() when done
+ *
+ * For a complete working example, see example/c-vt-encode-key in the
+ * repository.
+ *
+ * ## Example
+ *
+ * @snippet c-vt-encode-key/src/main.c key-encode
+ *
+ * ## Example: Encoding with Terminal State
+ *
+ * When you have a GhosttyTerminal, you can sync its modes (cursor key
+ * application, Kitty flags, etc.) into the encoder automatically:
+ *
+ * @code{.c}
+ * // Create a terminal and feed it some VT data that changes modes
+ * GhosttyTerminal terminal;
+ * ghostty_terminal_new(NULL, &terminal,
+ * (GhosttyTerminalOptions){.cols = 80, .rows = 24, .max_scrollback = 0});
+ *
+ * // Application might write data that enables Kitty keyboard protocol, etc.
+ * ghostty_terminal_vt_write(terminal, vt_data, vt_len);
+ *
+ * // Create an encoder and sync its options from the terminal
+ * GhosttyKeyEncoder encoder;
+ * ghostty_key_encoder_new(NULL, &encoder);
+ * ghostty_key_encoder_setopt_from_terminal(encoder, terminal);
+ *
+ * // Encode a key event using the terminal-derived options
+ * char buf[128];
+ * size_t written = 0;
+ * ghostty_key_encoder_encode(encoder, event, buf, sizeof(buf), &written);
+ *
+ * ghostty_key_encoder_free(encoder);
+ * ghostty_terminal_free(terminal);
+ * @endcode
+ *
+ * @{
+ */
+
+#include
+#include
+
+/** @} */
+
+#endif /* GHOSTTY_VT_KEY_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/key/encoder.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/key/encoder.h
new file mode 100644
index 00000000000..3aeec6597b1
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/key/encoder.h
@@ -0,0 +1,255 @@
+/**
+ * @file encoder.h
+ *
+ * Key event encoding to terminal escape sequences.
+ */
+
+#ifndef GHOSTTY_VT_KEY_ENCODER_H
+#define GHOSTTY_VT_KEY_ENCODER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+/**
+ * Opaque handle to a key encoder instance.
+ *
+ * This handle represents a key encoder that converts key events into terminal
+ * escape sequences.
+ *
+ * @ingroup key
+ */
+typedef struct GhosttyKeyEncoderImpl *GhosttyKeyEncoder;
+
+/**
+ * Kitty keyboard protocol flags.
+ *
+ * Bitflags representing the various modes of the Kitty keyboard protocol.
+ * These can be combined using bitwise OR operations. Valid values all
+ * start with `GHOSTTY_KITTY_KEY_`.
+ *
+ * @ingroup key
+ */
+typedef uint8_t GhosttyKittyKeyFlags;
+
+/** Kitty keyboard protocol disabled (all flags off) */
+#define GHOSTTY_KITTY_KEY_DISABLED 0
+
+/** Disambiguate escape codes */
+#define GHOSTTY_KITTY_KEY_DISAMBIGUATE (1 << 0)
+
+/** Report key press and release events */
+#define GHOSTTY_KITTY_KEY_REPORT_EVENTS (1 << 1)
+
+/** Report alternate key codes */
+#define GHOSTTY_KITTY_KEY_REPORT_ALTERNATES (1 << 2)
+
+/** Report all key events including those normally handled by the terminal */
+#define GHOSTTY_KITTY_KEY_REPORT_ALL (1 << 3)
+
+/** Report associated text with key events */
+#define GHOSTTY_KITTY_KEY_REPORT_ASSOCIATED (1 << 4)
+
+/** All Kitty keyboard protocol flags enabled */
+#define GHOSTTY_KITTY_KEY_ALL (GHOSTTY_KITTY_KEY_DISAMBIGUATE | GHOSTTY_KITTY_KEY_REPORT_EVENTS | GHOSTTY_KITTY_KEY_REPORT_ALTERNATES | GHOSTTY_KITTY_KEY_REPORT_ALL | GHOSTTY_KITTY_KEY_REPORT_ASSOCIATED)
+
+/**
+ * macOS option key behavior.
+ *
+ * Determines whether the "option" key on macOS is treated as "alt" or not.
+ * See the Ghostty `macos-option-as-alt` configuration option for more details.
+ *
+ * @ingroup key
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Option key is not treated as alt */
+ GHOSTTY_OPTION_AS_ALT_FALSE = 0,
+ /** Option key is treated as alt */
+ GHOSTTY_OPTION_AS_ALT_TRUE = 1,
+ /** Only left option key is treated as alt */
+ GHOSTTY_OPTION_AS_ALT_LEFT = 2,
+ /** Only right option key is treated as alt */
+ GHOSTTY_OPTION_AS_ALT_RIGHT = 3,
+ GHOSTTY_OPTION_AS_ALT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyOptionAsAlt;
+
+/**
+ * Key encoder option identifiers.
+ *
+ * These values are used with ghostty_key_encoder_setopt() to configure
+ * the behavior of the key encoder.
+ *
+ * @ingroup key
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Terminal DEC mode 1: cursor key application mode (value: bool) */
+ GHOSTTY_KEY_ENCODER_OPT_CURSOR_KEY_APPLICATION = 0,
+
+ /** Terminal DEC mode 66: keypad key application mode (value: bool) */
+ GHOSTTY_KEY_ENCODER_OPT_KEYPAD_KEY_APPLICATION = 1,
+
+ /** Terminal DEC mode 1035: ignore keypad with numlock (value: bool) */
+ GHOSTTY_KEY_ENCODER_OPT_IGNORE_KEYPAD_WITH_NUMLOCK = 2,
+
+ /** Terminal DEC mode 1036: alt sends escape prefix (value: bool) */
+ GHOSTTY_KEY_ENCODER_OPT_ALT_ESC_PREFIX = 3,
+
+ /** xterm modifyOtherKeys mode 2 (value: bool) */
+ GHOSTTY_KEY_ENCODER_OPT_MODIFY_OTHER_KEYS_STATE_2 = 4,
+
+ /** Kitty keyboard protocol flags (value: GhosttyKittyKeyFlags bitmask) */
+ GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS = 5,
+
+ /** macOS option-as-alt setting (value: GhosttyOptionAsAlt) */
+ GHOSTTY_KEY_ENCODER_OPT_MACOS_OPTION_AS_ALT = 6,
+
+ /** Backarrow key mode (value: bool)
+ * See https://vt100.net/dec/ek-vt3xx-tp-002.pdf page 170
+ * If `false` (the default), `backspace` emits 0x7f
+ * If `true`, `backspace` emits 0x08
+ */
+ GHOSTTY_KEY_ENCODER_OPT_BACKARROW_KEY_MODE = 7,
+
+ GHOSTTY_KEY_ENCODER_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyKeyEncoderOption;
+
+/**
+ * Create a new key encoder instance.
+ *
+ * Creates a new key encoder with default options. The encoder can be configured
+ * using ghostty_key_encoder_setopt() and must be freed using
+ * ghostty_key_encoder_free() when no longer needed.
+ *
+ * @param allocator Pointer to the allocator to use for memory management, or NULL to use the default allocator
+ * @param encoder Pointer to store the created encoder handle
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup key
+ */
+GHOSTTY_API GhosttyResult ghostty_key_encoder_new(const GhosttyAllocator *allocator, GhosttyKeyEncoder *encoder);
+
+/**
+ * Free a key encoder instance.
+ *
+ * Releases all resources associated with the key encoder. After this call,
+ * the encoder handle becomes invalid and must not be used.
+ *
+ * @param encoder The encoder handle to free (may be NULL)
+ *
+ * @ingroup key
+ */
+GHOSTTY_API void ghostty_key_encoder_free(GhosttyKeyEncoder encoder);
+
+/**
+ * Set an option on the key encoder.
+ *
+ * Configures the behavior of the key encoder. Options control various aspects
+ * of encoding such as terminal modes (cursor key application mode, keypad mode),
+ * protocol selection (Kitty keyboard protocol flags), and platform-specific
+ * behaviors (macOS option-as-alt).
+ *
+ * If you are using a terminal instance, you can set the key encoding
+ * options based on the active terminal state (e.g. legacy vs Kitty mode
+ * and associated flags) with ghostty_key_encoder_setopt_from_terminal().
+ *
+ * A null pointer value does nothing. It does not reset the value to the
+ * default. The setopt call will do nothing.
+ *
+ * @param encoder The encoder handle, must not be NULL
+ * @param option The option to set
+ * @param value Pointer to the value to set (type depends on the option)
+ *
+ * @ingroup key
+ */
+GHOSTTY_API void ghostty_key_encoder_setopt(GhosttyKeyEncoder encoder, GhosttyKeyEncoderOption option, const void *value);
+
+/**
+ * Set encoder options from a terminal's current state.
+ *
+ * Reads the terminal's current modes and flags and applies them to the
+ * encoder's options. This sets cursor key application mode, keypad mode,
+ * alt escape prefix, modifyOtherKeys state, and Kitty keyboard protocol
+ * flags from the terminal state.
+ *
+ * Note that the `macos_option_as_alt` option cannot be determined from
+ * terminal state and is reset to `GHOSTTY_OPTION_AS_ALT_FALSE` by this
+ * call. Use ghostty_key_encoder_setopt() to set it afterward if needed.
+ *
+ * @param encoder The encoder handle, must not be NULL
+ * @param terminal The terminal handle, must not be NULL
+ *
+ * @ingroup key
+ */
+GHOSTTY_API void ghostty_key_encoder_setopt_from_terminal(GhosttyKeyEncoder encoder, GhosttyTerminal terminal);
+
+/**
+ * Encode a key event into a terminal escape sequence.
+ *
+ * Converts a key event into the appropriate terminal escape sequence based on
+ * the encoder's current options. The sequence is written to the provided buffer.
+ *
+ * Not all key events produce output. For example, unmodified modifier keys
+ * typically don't generate escape sequences. Check the out_len parameter to
+ * determine if any data was written.
+ *
+ * If the output buffer is too small, this function returns GHOSTTY_OUT_OF_SPACE
+ * and out_len will contain the required buffer size. The caller can then
+ * allocate a larger buffer and call the function again.
+ *
+ * @param encoder The encoder handle, must not be NULL
+ * @param event The key event to encode, must not be NULL
+ * @param out_buf Buffer to write the encoded sequence to
+ * @param out_buf_size Size of the output buffer in bytes
+ * @param out_len Pointer to store the number of bytes written (may be NULL)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_SPACE if buffer too small, or other error code
+ *
+ * ## Example: Calculate required buffer size
+ *
+ * @code{.c}
+ * // Query the required size with a NULL buffer (always returns OUT_OF_SPACE)
+ * size_t required = 0;
+ * GhosttyResult result = ghostty_key_encoder_encode(encoder, event, NULL, 0, &required);
+ * assert(result == GHOSTTY_OUT_OF_SPACE);
+ *
+ * // Allocate buffer of required size
+ * char *buf = malloc(required);
+ *
+ * // Encode with properly sized buffer
+ * size_t written = 0;
+ * result = ghostty_key_encoder_encode(encoder, event, buf, required, &written);
+ * assert(result == GHOSTTY_SUCCESS);
+ *
+ * // Use the encoded sequence...
+ *
+ * free(buf);
+ * @endcode
+ *
+ * ## Example: Direct encoding with static buffer
+ *
+ * @code{.c}
+ * // Most escape sequences are short, so a static buffer often suffices
+ * char buf[128];
+ * size_t written = 0;
+ * GhosttyResult result = ghostty_key_encoder_encode(encoder, event, buf, sizeof(buf), &written);
+ *
+ * if (result == GHOSTTY_SUCCESS) {
+ * // Write the encoded sequence to the terminal
+ * write(pty_fd, buf, written);
+ * } else if (result == GHOSTTY_OUT_OF_SPACE) {
+ * // Buffer too small, written contains required size
+ * char *dynamic_buf = malloc(written);
+ * result = ghostty_key_encoder_encode(encoder, event, dynamic_buf, written, &written);
+ * assert(result == GHOSTTY_SUCCESS);
+ * write(pty_fd, dynamic_buf, written);
+ * free(dynamic_buf);
+ * }
+ * @endcode
+ *
+ * @ingroup key
+ */
+GHOSTTY_API GhosttyResult ghostty_key_encoder_encode(GhosttyKeyEncoder encoder, GhosttyKeyEvent event, char *out_buf, size_t out_buf_size, size_t *out_len);
+
+#endif /* GHOSTTY_VT_KEY_ENCODER_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/key/event.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/key/event.h
new file mode 100644
index 00000000000..eba433c6a55
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/key/event.h
@@ -0,0 +1,482 @@
+/**
+ * @file event.h
+ *
+ * Key event representation and manipulation.
+ */
+
+#ifndef GHOSTTY_VT_KEY_EVENT_H
+#define GHOSTTY_VT_KEY_EVENT_H
+
+#include
+#include
+#include
+#include
+#include
+
+/**
+ * Opaque handle to a key event.
+ *
+ * This handle represents a keyboard input event containing information about
+ * the physical key pressed, modifiers, and generated text.
+ *
+ * @ingroup key
+ */
+typedef struct GhosttyKeyEventImpl *GhosttyKeyEvent;
+
+/**
+ * Keyboard input event types.
+ *
+ * @ingroup key
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Key was released */
+ GHOSTTY_KEY_ACTION_RELEASE = 0,
+ /** Key was pressed */
+ GHOSTTY_KEY_ACTION_PRESS = 1,
+ /** Key is being repeated (held down) */
+ GHOSTTY_KEY_ACTION_REPEAT = 2,
+ GHOSTTY_KEY_ACTION_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyKeyAction;
+
+/**
+ * Keyboard modifier keys bitmask.
+ *
+ * A bitmask representing all keyboard modifiers. This tracks which modifier keys
+ * are pressed and, where supported by the platform, which side (left or right)
+ * of each modifier is active.
+ *
+ * Use the GHOSTTY_MODS_* constants to test and set individual modifiers.
+ *
+ * Modifier side bits are only meaningful when the corresponding modifier bit is set.
+ * Not all platforms support distinguishing between left and right modifier
+ * keys and Ghostty is built to expect that some platforms may not provide this
+ * information.
+ *
+ * @ingroup key
+ */
+typedef uint16_t GhosttyMods;
+
+/** Shift key is pressed */
+#define GHOSTTY_MODS_SHIFT (1 << 0)
+/** Control key is pressed */
+#define GHOSTTY_MODS_CTRL (1 << 1)
+/** Alt/Option key is pressed */
+#define GHOSTTY_MODS_ALT (1 << 2)
+/** Super/Command/Windows key is pressed */
+#define GHOSTTY_MODS_SUPER (1 << 3)
+/** Caps Lock is active */
+#define GHOSTTY_MODS_CAPS_LOCK (1 << 4)
+/** Num Lock is active */
+#define GHOSTTY_MODS_NUM_LOCK (1 << 5)
+
+/**
+ * Right shift is pressed (0 = left, 1 = right).
+ * Only meaningful when GHOSTTY_MODS_SHIFT is set.
+ */
+#define GHOSTTY_MODS_SHIFT_SIDE (1 << 6)
+/**
+ * Right ctrl is pressed (0 = left, 1 = right).
+ * Only meaningful when GHOSTTY_MODS_CTRL is set.
+ */
+#define GHOSTTY_MODS_CTRL_SIDE (1 << 7)
+/**
+ * Right alt is pressed (0 = left, 1 = right).
+ * Only meaningful when GHOSTTY_MODS_ALT is set.
+ */
+#define GHOSTTY_MODS_ALT_SIDE (1 << 8)
+/**
+ * Right super is pressed (0 = left, 1 = right).
+ * Only meaningful when GHOSTTY_MODS_SUPER is set.
+ */
+#define GHOSTTY_MODS_SUPER_SIDE (1 << 9)
+
+/**
+ * Physical key codes.
+ *
+ * The set of key codes that Ghostty is aware of. These represent physical keys
+ * on the keyboard and are layout-independent. For example, the "a" key on a US
+ * keyboard is the same as the "ф" key on a Russian keyboard, but both will
+ * report the same key_a value.
+ *
+ * Layout-dependent strings are provided separately as UTF-8 text and are produced
+ * by the platform. These values are based on the W3C UI Events KeyboardEvent code
+ * standard. See: https://www.w3.org/TR/uievents-code
+ *
+ * @ingroup key
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_KEY_UNIDENTIFIED = 0,
+
+ // Writing System Keys (W3C § 3.1.1)
+ GHOSTTY_KEY_BACKQUOTE,
+ GHOSTTY_KEY_BACKSLASH,
+ GHOSTTY_KEY_BRACKET_LEFT,
+ GHOSTTY_KEY_BRACKET_RIGHT,
+ GHOSTTY_KEY_COMMA,
+ GHOSTTY_KEY_DIGIT_0,
+ GHOSTTY_KEY_DIGIT_1,
+ GHOSTTY_KEY_DIGIT_2,
+ GHOSTTY_KEY_DIGIT_3,
+ GHOSTTY_KEY_DIGIT_4,
+ GHOSTTY_KEY_DIGIT_5,
+ GHOSTTY_KEY_DIGIT_6,
+ GHOSTTY_KEY_DIGIT_7,
+ GHOSTTY_KEY_DIGIT_8,
+ GHOSTTY_KEY_DIGIT_9,
+ GHOSTTY_KEY_EQUAL,
+ GHOSTTY_KEY_INTL_BACKSLASH,
+ GHOSTTY_KEY_INTL_RO,
+ GHOSTTY_KEY_INTL_YEN,
+ GHOSTTY_KEY_A,
+ GHOSTTY_KEY_B,
+ GHOSTTY_KEY_C,
+ GHOSTTY_KEY_D,
+ GHOSTTY_KEY_E,
+ GHOSTTY_KEY_F,
+ GHOSTTY_KEY_G,
+ GHOSTTY_KEY_H,
+ GHOSTTY_KEY_I,
+ GHOSTTY_KEY_J,
+ GHOSTTY_KEY_K,
+ GHOSTTY_KEY_L,
+ GHOSTTY_KEY_M,
+ GHOSTTY_KEY_N,
+ GHOSTTY_KEY_O,
+ GHOSTTY_KEY_P,
+ GHOSTTY_KEY_Q,
+ GHOSTTY_KEY_R,
+ GHOSTTY_KEY_S,
+ GHOSTTY_KEY_T,
+ GHOSTTY_KEY_U,
+ GHOSTTY_KEY_V,
+ GHOSTTY_KEY_W,
+ GHOSTTY_KEY_X,
+ GHOSTTY_KEY_Y,
+ GHOSTTY_KEY_Z,
+ GHOSTTY_KEY_MINUS,
+ GHOSTTY_KEY_PERIOD,
+ GHOSTTY_KEY_QUOTE,
+ GHOSTTY_KEY_SEMICOLON,
+ GHOSTTY_KEY_SLASH,
+
+ // Functional Keys (W3C § 3.1.2)
+ GHOSTTY_KEY_ALT_LEFT,
+ GHOSTTY_KEY_ALT_RIGHT,
+ GHOSTTY_KEY_BACKSPACE,
+ GHOSTTY_KEY_CAPS_LOCK,
+ GHOSTTY_KEY_CONTEXT_MENU,
+ GHOSTTY_KEY_CONTROL_LEFT,
+ GHOSTTY_KEY_CONTROL_RIGHT,
+ GHOSTTY_KEY_ENTER,
+ GHOSTTY_KEY_META_LEFT,
+ GHOSTTY_KEY_META_RIGHT,
+ GHOSTTY_KEY_SHIFT_LEFT,
+ GHOSTTY_KEY_SHIFT_RIGHT,
+ GHOSTTY_KEY_SPACE,
+ GHOSTTY_KEY_TAB,
+ GHOSTTY_KEY_CONVERT,
+ GHOSTTY_KEY_KANA_MODE,
+ GHOSTTY_KEY_NON_CONVERT,
+
+ // Control Pad Section (W3C § 3.2)
+ GHOSTTY_KEY_DELETE,
+ GHOSTTY_KEY_END,
+ GHOSTTY_KEY_HELP,
+ GHOSTTY_KEY_HOME,
+ GHOSTTY_KEY_INSERT,
+ GHOSTTY_KEY_PAGE_DOWN,
+ GHOSTTY_KEY_PAGE_UP,
+
+ // Arrow Pad Section (W3C § 3.3)
+ GHOSTTY_KEY_ARROW_DOWN,
+ GHOSTTY_KEY_ARROW_LEFT,
+ GHOSTTY_KEY_ARROW_RIGHT,
+ GHOSTTY_KEY_ARROW_UP,
+
+ // Numpad Section (W3C § 3.4)
+ GHOSTTY_KEY_NUM_LOCK,
+ GHOSTTY_KEY_NUMPAD_0,
+ GHOSTTY_KEY_NUMPAD_1,
+ GHOSTTY_KEY_NUMPAD_2,
+ GHOSTTY_KEY_NUMPAD_3,
+ GHOSTTY_KEY_NUMPAD_4,
+ GHOSTTY_KEY_NUMPAD_5,
+ GHOSTTY_KEY_NUMPAD_6,
+ GHOSTTY_KEY_NUMPAD_7,
+ GHOSTTY_KEY_NUMPAD_8,
+ GHOSTTY_KEY_NUMPAD_9,
+ GHOSTTY_KEY_NUMPAD_ADD,
+ GHOSTTY_KEY_NUMPAD_BACKSPACE,
+ GHOSTTY_KEY_NUMPAD_CLEAR,
+ GHOSTTY_KEY_NUMPAD_CLEAR_ENTRY,
+ GHOSTTY_KEY_NUMPAD_COMMA,
+ GHOSTTY_KEY_NUMPAD_DECIMAL,
+ GHOSTTY_KEY_NUMPAD_DIVIDE,
+ GHOSTTY_KEY_NUMPAD_ENTER,
+ GHOSTTY_KEY_NUMPAD_EQUAL,
+ GHOSTTY_KEY_NUMPAD_MEMORY_ADD,
+ GHOSTTY_KEY_NUMPAD_MEMORY_CLEAR,
+ GHOSTTY_KEY_NUMPAD_MEMORY_RECALL,
+ GHOSTTY_KEY_NUMPAD_MEMORY_STORE,
+ GHOSTTY_KEY_NUMPAD_MEMORY_SUBTRACT,
+ GHOSTTY_KEY_NUMPAD_MULTIPLY,
+ GHOSTTY_KEY_NUMPAD_PAREN_LEFT,
+ GHOSTTY_KEY_NUMPAD_PAREN_RIGHT,
+ GHOSTTY_KEY_NUMPAD_SUBTRACT,
+ GHOSTTY_KEY_NUMPAD_SEPARATOR,
+ GHOSTTY_KEY_NUMPAD_UP,
+ GHOSTTY_KEY_NUMPAD_DOWN,
+ GHOSTTY_KEY_NUMPAD_RIGHT,
+ GHOSTTY_KEY_NUMPAD_LEFT,
+ GHOSTTY_KEY_NUMPAD_BEGIN,
+ GHOSTTY_KEY_NUMPAD_HOME,
+ GHOSTTY_KEY_NUMPAD_END,
+ GHOSTTY_KEY_NUMPAD_INSERT,
+ GHOSTTY_KEY_NUMPAD_DELETE,
+ GHOSTTY_KEY_NUMPAD_PAGE_UP,
+ GHOSTTY_KEY_NUMPAD_PAGE_DOWN,
+
+ // Function Section (W3C § 3.5)
+ GHOSTTY_KEY_ESCAPE,
+ GHOSTTY_KEY_F1,
+ GHOSTTY_KEY_F2,
+ GHOSTTY_KEY_F3,
+ GHOSTTY_KEY_F4,
+ GHOSTTY_KEY_F5,
+ GHOSTTY_KEY_F6,
+ GHOSTTY_KEY_F7,
+ GHOSTTY_KEY_F8,
+ GHOSTTY_KEY_F9,
+ GHOSTTY_KEY_F10,
+ GHOSTTY_KEY_F11,
+ GHOSTTY_KEY_F12,
+ GHOSTTY_KEY_F13,
+ GHOSTTY_KEY_F14,
+ GHOSTTY_KEY_F15,
+ GHOSTTY_KEY_F16,
+ GHOSTTY_KEY_F17,
+ GHOSTTY_KEY_F18,
+ GHOSTTY_KEY_F19,
+ GHOSTTY_KEY_F20,
+ GHOSTTY_KEY_F21,
+ GHOSTTY_KEY_F22,
+ GHOSTTY_KEY_F23,
+ GHOSTTY_KEY_F24,
+ GHOSTTY_KEY_F25,
+ GHOSTTY_KEY_FN,
+ GHOSTTY_KEY_FN_LOCK,
+ GHOSTTY_KEY_PRINT_SCREEN,
+ GHOSTTY_KEY_SCROLL_LOCK,
+ GHOSTTY_KEY_PAUSE,
+
+ // Media Keys (W3C § 3.6)
+ GHOSTTY_KEY_BROWSER_BACK,
+ GHOSTTY_KEY_BROWSER_FAVORITES,
+ GHOSTTY_KEY_BROWSER_FORWARD,
+ GHOSTTY_KEY_BROWSER_HOME,
+ GHOSTTY_KEY_BROWSER_REFRESH,
+ GHOSTTY_KEY_BROWSER_SEARCH,
+ GHOSTTY_KEY_BROWSER_STOP,
+ GHOSTTY_KEY_EJECT,
+ GHOSTTY_KEY_LAUNCH_APP_1,
+ GHOSTTY_KEY_LAUNCH_APP_2,
+ GHOSTTY_KEY_LAUNCH_MAIL,
+ GHOSTTY_KEY_MEDIA_PLAY_PAUSE,
+ GHOSTTY_KEY_MEDIA_SELECT,
+ GHOSTTY_KEY_MEDIA_STOP,
+ GHOSTTY_KEY_MEDIA_TRACK_NEXT,
+ GHOSTTY_KEY_MEDIA_TRACK_PREVIOUS,
+ GHOSTTY_KEY_POWER,
+ GHOSTTY_KEY_SLEEP,
+ GHOSTTY_KEY_AUDIO_VOLUME_DOWN,
+ GHOSTTY_KEY_AUDIO_VOLUME_MUTE,
+ GHOSTTY_KEY_AUDIO_VOLUME_UP,
+ GHOSTTY_KEY_WAKE_UP,
+
+ // Legacy, Non-standard, and Special Keys (W3C § 3.7)
+ GHOSTTY_KEY_COPY,
+ GHOSTTY_KEY_CUT,
+ GHOSTTY_KEY_PASTE,
+ GHOSTTY_KEY_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyKey;
+
+/**
+ * Create a new key event instance.
+ *
+ * Creates a new key event with default values. The event must be freed using
+ * ghostty_key_event_free() when no longer needed.
+ *
+ * @param allocator Pointer to the allocator to use for memory management, or NULL to use the default allocator
+ * @param event Pointer to store the created key event handle
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup key
+ */
+GHOSTTY_API GhosttyResult ghostty_key_event_new(const GhosttyAllocator *allocator, GhosttyKeyEvent *event);
+
+/**
+ * Free a key event instance.
+ *
+ * Releases all resources associated with the key event. After this call,
+ * the event handle becomes invalid and must not be used.
+ *
+ * @param event The key event handle to free (may be NULL)
+ *
+ * @ingroup key
+ */
+GHOSTTY_API void ghostty_key_event_free(GhosttyKeyEvent event);
+
+/**
+ * Set the key action (press, release, repeat).
+ *
+ * @param event The key event handle, must not be NULL
+ * @param action The action to set
+ *
+ * @ingroup key
+ */
+GHOSTTY_API void ghostty_key_event_set_action(GhosttyKeyEvent event, GhosttyKeyAction action);
+
+/**
+ * Get the key action (press, release, repeat).
+ *
+ * @param event The key event handle, must not be NULL
+ * @return The key action
+ *
+ * @ingroup key
+ */
+GHOSTTY_API GhosttyKeyAction ghostty_key_event_get_action(GhosttyKeyEvent event);
+
+/**
+ * Set the physical key code.
+ *
+ * @param event The key event handle, must not be NULL
+ * @param key The physical key code to set
+ *
+ * @ingroup key
+ */
+GHOSTTY_API void ghostty_key_event_set_key(GhosttyKeyEvent event, GhosttyKey key);
+
+/**
+ * Get the physical key code.
+ *
+ * @param event The key event handle, must not be NULL
+ * @return The physical key code
+ *
+ * @ingroup key
+ */
+GHOSTTY_API GhosttyKey ghostty_key_event_get_key(GhosttyKeyEvent event);
+
+/**
+ * Set the modifier keys bitmask.
+ *
+ * @param event The key event handle, must not be NULL
+ * @param mods The modifier keys bitmask to set
+ *
+ * @ingroup key
+ */
+GHOSTTY_API void ghostty_key_event_set_mods(GhosttyKeyEvent event, GhosttyMods mods);
+
+/**
+ * Get the modifier keys bitmask.
+ *
+ * @param event The key event handle, must not be NULL
+ * @return The modifier keys bitmask
+ *
+ * @ingroup key
+ */
+GHOSTTY_API GhosttyMods ghostty_key_event_get_mods(GhosttyKeyEvent event);
+
+/**
+ * Set the consumed modifiers bitmask.
+ *
+ * @param event The key event handle, must not be NULL
+ * @param consumed_mods The consumed modifiers bitmask to set
+ *
+ * @ingroup key
+ */
+GHOSTTY_API void ghostty_key_event_set_consumed_mods(GhosttyKeyEvent event, GhosttyMods consumed_mods);
+
+/**
+ * Get the consumed modifiers bitmask.
+ *
+ * @param event The key event handle, must not be NULL
+ * @return The consumed modifiers bitmask
+ *
+ * @ingroup key
+ */
+GHOSTTY_API GhosttyMods ghostty_key_event_get_consumed_mods(GhosttyKeyEvent event);
+
+/**
+ * Set whether the key event is part of a composition sequence.
+ *
+ * @param event The key event handle, must not be NULL
+ * @param composing Whether the key event is part of a composition sequence
+ *
+ * @ingroup key
+ */
+GHOSTTY_API void ghostty_key_event_set_composing(GhosttyKeyEvent event, bool composing);
+
+/**
+ * Get whether the key event is part of a composition sequence.
+ *
+ * @param event The key event handle, must not be NULL
+ * @return Whether the key event is part of a composition sequence
+ *
+ * @ingroup key
+ */
+GHOSTTY_API bool ghostty_key_event_get_composing(GhosttyKeyEvent event);
+
+/**
+ * Set the UTF-8 text generated by the key for the current keyboard layout.
+ *
+ * Must contain the unmodified character before any Ctrl/Meta transformations.
+ * The encoder derives modifier sequences from the logical key and mods
+ * bitmask, not from this text. Do not pass C0 control characters
+ * (U+0000-U+001F, U+007F) or platform function key codes (e.g. macOS PUA
+ * U+F700-U+F8FF); pass NULL instead and let the encoder use the logical key.
+ *
+ * The key event does NOT take ownership of the text pointer. The caller
+ * must ensure the string remains valid for the lifetime needed by the event.
+ *
+ * @param event The key event handle, must not be NULL
+ * @param utf8 The UTF-8 text to set (or NULL for empty)
+ * @param len Length of the UTF-8 text in bytes
+ *
+ * @ingroup key
+ */
+GHOSTTY_API void ghostty_key_event_set_utf8(GhosttyKeyEvent event, const char *utf8, size_t len);
+
+/**
+ * Get the UTF-8 text generated by the key event.
+ *
+ * The returned pointer is valid until the event is freed or the UTF-8 text is modified.
+ *
+ * @param event The key event handle, must not be NULL
+ * @param len Pointer to store the length of the UTF-8 text in bytes (may be NULL)
+ * @return The UTF-8 text (or NULL for empty)
+ *
+ * @ingroup key
+ */
+GHOSTTY_API const char *ghostty_key_event_get_utf8(GhosttyKeyEvent event, size_t *len);
+
+/**
+ * Set the unshifted Unicode codepoint.
+ *
+ * @param event The key event handle, must not be NULL
+ * @param codepoint The unshifted Unicode codepoint to set
+ *
+ * @ingroup key
+ */
+GHOSTTY_API void ghostty_key_event_set_unshifted_codepoint(GhosttyKeyEvent event, uint32_t codepoint);
+
+/**
+ * Get the unshifted Unicode codepoint.
+ *
+ * @param event The key event handle, must not be NULL
+ * @return The unshifted Unicode codepoint
+ *
+ * @ingroup key
+ */
+GHOSTTY_API uint32_t ghostty_key_event_get_unshifted_codepoint(GhosttyKeyEvent event);
+
+#endif /* GHOSTTY_VT_KEY_EVENT_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h
new file mode 100644
index 00000000000..9bace3a3ccf
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h
@@ -0,0 +1,775 @@
+/**
+ * @file kitty_graphics.h
+ *
+ * Kitty graphics protocol
+ *
+ * See @ref kitty_graphics for a full usage guide.
+ */
+
+#ifndef GHOSTTY_VT_KITTY_GRAPHICS_H
+#define GHOSTTY_VT_KITTY_GRAPHICS_H
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @defgroup kitty_graphics Kitty Graphics
+ *
+ * API for inspecting images and placements stored via the
+ * [Kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/).
+ *
+ * The central object is @ref GhosttyKittyGraphics, an opaque handle to
+ * the image storage associated with a terminal's active screen. From it
+ * you can iterate over placements and look up individual images.
+ *
+ * ## Obtaining a KittyGraphics Handle
+ *
+ * A @ref GhosttyKittyGraphics handle is obtained from a terminal via
+ * ghostty_terminal_get() with @ref GHOSTTY_TERMINAL_DATA_KITTY_GRAPHICS.
+ * The handle is borrowed from the terminal and remains valid until the
+ * next mutating terminal call (e.g. ghostty_terminal_vt_write() or
+ * ghostty_terminal_reset()).
+ *
+ * Before images can be stored, Kitty graphics must be enabled on the
+ * terminal by setting a non-zero storage limit with
+ * @ref GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT, and a PNG
+ * decoder callback must be installed via ghostty_sys_set() with
+ * @ref GHOSTTY_SYS_OPT_DECODE_PNG.
+ *
+ * @snippet c-vt-kitty-graphics/src/main.c kitty-graphics-decode-png
+ *
+ * ## Iterating Placements
+ *
+ * Placements are inspected through a @ref GhosttyKittyGraphicsPlacementIterator.
+ * The typical workflow is:
+ *
+ * 1. Create an iterator with ghostty_kitty_graphics_placement_iterator_new().
+ * 2. Populate it from the storage with ghostty_kitty_graphics_get() using
+ * @ref GHOSTTY_KITTY_GRAPHICS_DATA_PLACEMENT_ITERATOR.
+ * 3. Optionally filter by z-layer with
+ * ghostty_kitty_graphics_placement_iterator_set().
+ * 4. Advance with ghostty_kitty_graphics_placement_next() and read
+ * per-placement data with ghostty_kitty_graphics_placement_get().
+ * 5. For each placement, look up its image with
+ * ghostty_kitty_graphics_image() to access pixel data and dimensions.
+ * 6. Free the iterator with ghostty_kitty_graphics_placement_iterator_free().
+ *
+ * ## Looking Up Images
+ *
+ * Given an image ID (obtained from a placement via
+ * @ref GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IMAGE_ID), call
+ * ghostty_kitty_graphics_image() to get a @ref GhosttyKittyGraphicsImage
+ * handle. From this handle, ghostty_kitty_graphics_image_get() provides
+ * the image dimensions, pixel format, compression, and a borrowed pointer
+ * to the raw pixel data.
+ *
+ * ## Rendering Helpers
+ *
+ * Several functions assist with rendering a placement:
+ *
+ * - ghostty_kitty_graphics_placement_pixel_size() — rendered pixel
+ * dimensions accounting for source rect and aspect ratio.
+ * - ghostty_kitty_graphics_placement_grid_size() — number of grid
+ * columns and rows the placement occupies.
+ * - ghostty_kitty_graphics_placement_viewport_pos() — viewport-relative
+ * grid position (may be negative for partially scrolled placements).
+ * - ghostty_kitty_graphics_placement_source_rect() — resolved source
+ * rectangle in pixels, clamped to image bounds.
+ * - ghostty_kitty_graphics_placement_rect() — bounding rectangle as a
+ * @ref GhosttySelection.
+ *
+ * ## Lifetime and Thread Safety
+ *
+ * All handles borrowed from the terminal (GhosttyKittyGraphics,
+ * GhosttyKittyGraphicsImage) are invalidated by any mutating terminal
+ * call. The placement iterator is independently owned and must be freed
+ * by the caller, but the data it yields is only valid while the
+ * underlying terminal is not mutated.
+ *
+ * ## Example
+ *
+ * The following example creates a terminal, sends a Kitty graphics
+ * image, then iterates placements and prints image metadata:
+ *
+ * @snippet c-vt-kitty-graphics/src/main.c kitty-graphics-main
+ *
+ * @{
+ */
+
+/**
+ * Queryable data kinds for ghostty_kitty_graphics_get().
+ *
+ * @ingroup kitty_graphics
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Invalid / sentinel value. */
+ GHOSTTY_KITTY_GRAPHICS_DATA_INVALID = 0,
+
+ /**
+ * Populate a pre-allocated placement iterator with placement data from
+ * the storage. Iterator data is only valid as long as the underlying
+ * terminal is not mutated.
+ *
+ * Output type: GhosttyKittyGraphicsPlacementIterator *
+ */
+ GHOSTTY_KITTY_GRAPHICS_DATA_PLACEMENT_ITERATOR = 1,
+ GHOSTTY_KITTY_GRAPHICS_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyKittyGraphicsData;
+
+/**
+ * Queryable data kinds for ghostty_kitty_graphics_placement_get().
+ *
+ * @ingroup kitty_graphics
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Invalid / sentinel value. */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INVALID = 0,
+
+ /**
+ * The image ID this placement belongs to.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IMAGE_ID = 1,
+
+ /**
+ * The placement ID.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_PLACEMENT_ID = 2,
+
+ /**
+ * Whether this is a virtual placement (unicode placeholder).
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_IS_VIRTUAL = 3,
+
+ /**
+ * Pixel offset from the left edge of the cell.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_X_OFFSET = 4,
+
+ /**
+ * Pixel offset from the top edge of the cell.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Y_OFFSET = 5,
+
+ /**
+ * Source rectangle x origin in pixels.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_X = 6,
+
+ /**
+ * Source rectangle y origin in pixels.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_Y = 7,
+
+ /**
+ * Source rectangle width in pixels (0 = full image width).
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_WIDTH = 8,
+
+ /**
+ * Source rectangle height in pixels (0 = full image height).
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_SOURCE_HEIGHT = 9,
+
+ /**
+ * Number of columns this placement occupies.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_COLUMNS = 10,
+
+ /**
+ * Number of rows this placement occupies.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_ROWS = 11,
+
+ /**
+ * Z-index for this placement.
+ *
+ * Output type: int32_t *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_Z = 12,
+
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyKittyGraphicsPlacementData;
+
+/**
+ * Z-layer classification for kitty graphics placements.
+ *
+ * Based on the kitty protocol z-index conventions:
+ * - BELOW_BG: z < INT32_MIN/2 (drawn below cell background)
+ * - BELOW_TEXT: INT32_MIN/2 <= z < 0 (above background, below text)
+ * - ABOVE_TEXT: z >= 0 (above text)
+ * - ALL: no filtering (current behavior)
+ *
+ * @ingroup kitty_graphics
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_KITTY_PLACEMENT_LAYER_ALL = 0,
+ GHOSTTY_KITTY_PLACEMENT_LAYER_BELOW_BG = 1,
+ GHOSTTY_KITTY_PLACEMENT_LAYER_BELOW_TEXT = 2,
+ GHOSTTY_KITTY_PLACEMENT_LAYER_ABOVE_TEXT = 3,
+ GHOSTTY_KITTY_PLACEMENT_LAYER_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyKittyPlacementLayer;
+
+/**
+ * Settable options for ghostty_kitty_graphics_placement_iterator_set().
+ *
+ * @ingroup kitty_graphics
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /**
+ * Set the z-layer filter for the iterator.
+ *
+ * Input type: GhosttyKittyPlacementLayer *
+ */
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_ITERATOR_OPTION_LAYER = 0,
+ GHOSTTY_KITTY_GRAPHICS_PLACEMENT_ITERATOR_OPTION_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyKittyGraphicsPlacementIteratorOption;
+
+/**
+ * Pixel format of a Kitty graphics image.
+ *
+ * @ingroup kitty_graphics
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_KITTY_IMAGE_FORMAT_RGB = 0,
+ GHOSTTY_KITTY_IMAGE_FORMAT_RGBA = 1,
+ GHOSTTY_KITTY_IMAGE_FORMAT_PNG = 2,
+ GHOSTTY_KITTY_IMAGE_FORMAT_GRAY_ALPHA = 3,
+ GHOSTTY_KITTY_IMAGE_FORMAT_GRAY = 4,
+ GHOSTTY_KITTY_IMAGE_FORMAT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyKittyImageFormat;
+
+/**
+ * Compression of a Kitty graphics image.
+ *
+ * @ingroup kitty_graphics
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_KITTY_IMAGE_COMPRESSION_NONE = 0,
+ GHOSTTY_KITTY_IMAGE_COMPRESSION_ZLIB_DEFLATE = 1,
+ GHOSTTY_KITTY_IMAGE_COMPRESSION_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyKittyImageCompression;
+
+/**
+ * Queryable data kinds for ghostty_kitty_graphics_image_get().
+ *
+ * @ingroup kitty_graphics
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Invalid / sentinel value. */
+ GHOSTTY_KITTY_IMAGE_DATA_INVALID = 0,
+
+ /**
+ * The image ID.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_IMAGE_DATA_ID = 1,
+
+ /**
+ * The image number.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_IMAGE_DATA_NUMBER = 2,
+
+ /**
+ * Image width in pixels.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_IMAGE_DATA_WIDTH = 3,
+
+ /**
+ * Image height in pixels.
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_KITTY_IMAGE_DATA_HEIGHT = 4,
+
+ /**
+ * Pixel format of the image.
+ *
+ * Output type: GhosttyKittyImageFormat *
+ */
+ GHOSTTY_KITTY_IMAGE_DATA_FORMAT = 5,
+
+ /**
+ * Compression of the image.
+ *
+ * Output type: GhosttyKittyImageCompression *
+ */
+ GHOSTTY_KITTY_IMAGE_DATA_COMPRESSION = 6,
+
+ /**
+ * Borrowed pointer to the raw pixel data. Valid as long as the
+ * underlying terminal is not mutated.
+ *
+ * Output type: const uint8_t **
+ */
+ GHOSTTY_KITTY_IMAGE_DATA_DATA_PTR = 7,
+
+ /**
+ * Length of the raw pixel data in bytes.
+ *
+ * Output type: size_t *
+ */
+ GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN = 8,
+
+ GHOSTTY_KITTY_IMAGE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyKittyGraphicsImageData;
+
+/**
+ * Combined rendering geometry for a placement in a single sized struct.
+ *
+ * Combines the results of ghostty_kitty_graphics_placement_pixel_size(),
+ * ghostty_kitty_graphics_placement_grid_size(),
+ * ghostty_kitty_graphics_placement_viewport_pos(), and
+ * ghostty_kitty_graphics_placement_source_rect() into one call. This is
+ * an optimization over calling those four functions individually,
+ * particularly useful in environments with high per-call overhead such
+ * as FFI or Cgo.
+ *
+ * This struct uses the sized-struct ABI pattern. Initialize with
+ * GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementRenderInfo) before calling
+ * ghostty_kitty_graphics_placement_render_info().
+ *
+ * @ingroup kitty_graphics
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttyKittyGraphicsPlacementRenderInfo). */
+ size_t size;
+ /** Rendered width in pixels. */
+ uint32_t pixel_width;
+ /** Rendered height in pixels. */
+ uint32_t pixel_height;
+ /** Number of grid columns the placement occupies. */
+ uint32_t grid_cols;
+ /** Number of grid rows the placement occupies. */
+ uint32_t grid_rows;
+ /** Viewport-relative column (may be negative for partially visible placements). */
+ int32_t viewport_col;
+ /** Viewport-relative row (may be negative for partially visible placements). */
+ int32_t viewport_row;
+ /** False when the placement is fully off-screen or virtual. */
+ bool viewport_visible;
+ /** Resolved source rectangle x origin in pixels. */
+ uint32_t source_x;
+ /** Resolved source rectangle y origin in pixels. */
+ uint32_t source_y;
+ /** Resolved source rectangle width in pixels. */
+ uint32_t source_width;
+ /** Resolved source rectangle height in pixels. */
+ uint32_t source_height;
+} GhosttyKittyGraphicsPlacementRenderInfo;
+
+/**
+ * Get data from a kitty graphics storage instance.
+ *
+ * The output pointer must be of the appropriate type for the requested
+ * data kind.
+ *
+ * Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
+ *
+ * @param graphics The kitty graphics handle
+ * @param data The type of data to extract
+ * @param[out] out Pointer to store the extracted data
+ * @return GHOSTTY_SUCCESS on success
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_get(
+ GhosttyKittyGraphics graphics,
+ GhosttyKittyGraphicsData data,
+ void* out);
+
+/**
+ * Look up a Kitty graphics image by its image ID.
+ *
+ * Returns NULL if no image with the given ID exists or if Kitty graphics
+ * are disabled at build time.
+ *
+ * @param graphics The kitty graphics handle
+ * @param image_id The image ID to look up
+ * @return An opaque image handle, or NULL if not found
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyKittyGraphicsImage ghostty_kitty_graphics_image(
+ GhosttyKittyGraphics graphics,
+ uint32_t image_id);
+
+/**
+ * Get data from a Kitty graphics image.
+ *
+ * The output pointer must be of the appropriate type for the requested
+ * data kind.
+ *
+ * @param image The image handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param data The data kind to query
+ * @param[out] out Pointer to receive the queried value
+ * @return GHOSTTY_SUCCESS on success
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_image_get(
+ GhosttyKittyGraphicsImage image,
+ GhosttyKittyGraphicsImageData data,
+ void* out);
+
+/**
+ * Get multiple data fields from a Kitty graphics image in a single call.
+ *
+ * This is an optimization over calling ghostty_kitty_graphics_image_get()
+ * repeatedly, particularly useful in environments with high per-call
+ * overhead such as FFI or Cgo.
+ *
+ * Each element in the keys array specifies a data kind, and the
+ * corresponding element in the values array receives the result.
+ * The type of each values[i] pointer must match the output type
+ * documented for keys[i].
+ *
+ * Processing stops at the first error; on success out_written
+ * is set to count, on error it is set to the index of the
+ * failing key (i.e. the number of values successfully written).
+ *
+ * @param image The image handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param count Number of key/value pairs
+ * @param keys Array of data kinds to query
+ * @param values Array of output pointers (types must match each key's
+ * documented output type)
+ * @param[out] out_written On return, receives the number of values
+ * successfully written (may be NULL)
+ * @return GHOSTTY_SUCCESS if all queries succeed
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_image_get_multi(
+ GhosttyKittyGraphicsImage image,
+ size_t count,
+ const GhosttyKittyGraphicsImageData* keys,
+ void** values,
+ size_t* out_written);
+
+/**
+ * Create a new placement iterator instance.
+ *
+ * All fields except the allocator are left undefined until populated
+ * via ghostty_kitty_graphics_get() with
+ * GHOSTTY_KITTY_GRAPHICS_DATA_PLACEMENT_ITERATOR.
+ *
+ * @param allocator Pointer to allocator, or NULL to use the default allocator
+ * @param[out] out_iterator On success, receives the created iterator handle
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation
+ * failure
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_iterator_new(
+ const GhosttyAllocator* allocator,
+ GhosttyKittyGraphicsPlacementIterator* out_iterator);
+
+/**
+ * Free a placement iterator.
+ *
+ * @param iterator The iterator handle to free (may be NULL)
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API void ghostty_kitty_graphics_placement_iterator_free(
+ GhosttyKittyGraphicsPlacementIterator iterator);
+
+/**
+ * Set an option on a placement iterator.
+ *
+ * Use GHOSTTY_KITTY_GRAPHICS_PLACEMENT_ITERATOR_OPTION_LAYER with a
+ * GhosttyKittyPlacementLayer value to filter placements by z-layer.
+ * The filter is applied during iteration: ghostty_kitty_graphics_placement_next()
+ * will skip placements that do not match the configured layer.
+ *
+ * The default layer is GHOSTTY_KITTY_PLACEMENT_LAYER_ALL (no filtering).
+ *
+ * @param iterator The iterator handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param option The option to set
+ * @param value Pointer to the value (type depends on option; NULL returns
+ * GHOSTTY_INVALID_VALUE)
+ * @return GHOSTTY_SUCCESS on success
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_iterator_set(
+ GhosttyKittyGraphicsPlacementIterator iterator,
+ GhosttyKittyGraphicsPlacementIteratorOption option,
+ const void* value);
+
+/**
+ * Advance the placement iterator to the next placement.
+ *
+ * If a layer filter has been set via
+ * ghostty_kitty_graphics_placement_iterator_set(), only placements
+ * matching that layer are returned.
+ *
+ * @param iterator The iterator handle (may be NULL)
+ * @return true if advanced to the next placement, false if at the end
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API bool ghostty_kitty_graphics_placement_next(
+ GhosttyKittyGraphicsPlacementIterator iterator);
+
+/**
+ * Get data from the current placement in a placement iterator.
+ *
+ * Call ghostty_kitty_graphics_placement_next() at least once before
+ * calling this function.
+ *
+ * @param iterator The iterator handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param data The data kind to query
+ * @param[out] out Pointer to receive the queried value
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the
+ * iterator is NULL or not positioned on a placement
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_get(
+ GhosttyKittyGraphicsPlacementIterator iterator,
+ GhosttyKittyGraphicsPlacementData data,
+ void* out);
+
+/**
+ * Get multiple data fields from the current placement in a single call.
+ *
+ * This is an optimization over calling ghostty_kitty_graphics_placement_get()
+ * repeatedly, particularly useful in environments with high per-call
+ * overhead such as FFI or Cgo.
+ *
+ * Each element in the keys array specifies a data kind, and the
+ * corresponding element in the values array receives the result.
+ * The type of each values[i] pointer must match the output type
+ * documented for keys[i].
+ *
+ * Processing stops at the first error; on success out_written
+ * is set to count, on error it is set to the index of the
+ * failing key (i.e. the number of values successfully written).
+ *
+ * @param iterator The iterator handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param count Number of key/value pairs
+ * @param keys Array of data kinds to query
+ * @param values Array of output pointers (types must match each key's
+ * documented output type)
+ * @param[out] out_written On return, receives the number of values
+ * successfully written (may be NULL)
+ * @return GHOSTTY_SUCCESS if all queries succeed
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_get_multi(
+ GhosttyKittyGraphicsPlacementIterator iterator,
+ size_t count,
+ const GhosttyKittyGraphicsPlacementData* keys,
+ void** values,
+ size_t* out_written);
+
+/**
+ * Compute the grid rectangle occupied by the current placement.
+ *
+ * Uses the placement's pin, the image dimensions, and the terminal's
+ * cell/pixel geometry to calculate the bounding rectangle. Virtual
+ * placements (unicode placeholders) return GHOSTTY_NO_VALUE.
+ *
+ * @param terminal The terminal handle
+ * @param image The image handle for this placement's image
+ * @param iterator The placement iterator positioned on a placement
+ * @param[out] out_selection On success, receives the bounding rectangle
+ * as a selection with rectangle=true
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if any handle
+ * is NULL or the iterator is not positioned, GHOSTTY_NO_VALUE for
+ * virtual placements or when Kitty graphics are disabled
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_rect(
+ GhosttyKittyGraphicsPlacementIterator iterator,
+ GhosttyKittyGraphicsImage image,
+ GhosttyTerminal terminal,
+ GhosttySelection* out_selection);
+
+/**
+ * Compute the rendered pixel size of the current placement.
+ *
+ * Takes into account the placement's source rectangle, specified
+ * columns/rows, and aspect ratio to calculate the final rendered
+ * pixel dimensions.
+ *
+ * @param iterator The placement iterator positioned on a placement
+ * @param image The image handle for this placement's image
+ * @param terminal The terminal handle
+ * @param[out] out_width On success, receives the width in pixels
+ * @param[out] out_height On success, receives the height in pixels
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if any handle
+ * is NULL or the iterator is not positioned, GHOSTTY_NO_VALUE when
+ * Kitty graphics are disabled
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_pixel_size(
+ GhosttyKittyGraphicsPlacementIterator iterator,
+ GhosttyKittyGraphicsImage image,
+ GhosttyTerminal terminal,
+ uint32_t* out_width,
+ uint32_t* out_height);
+
+/**
+ * Compute the grid cell size of the current placement.
+ *
+ * Returns the number of columns and rows that the placement occupies
+ * in the terminal grid. If the placement specifies explicit columns
+ * and rows, those are returned directly; otherwise they are calculated
+ * from the pixel size and cell dimensions.
+ *
+ * @param iterator The placement iterator positioned on a placement
+ * @param image The image handle for this placement's image
+ * @param terminal The terminal handle
+ * @param[out] out_cols On success, receives the number of columns
+ * @param[out] out_rows On success, receives the number of rows
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if any handle
+ * is NULL or the iterator is not positioned, GHOSTTY_NO_VALUE when
+ * Kitty graphics are disabled
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_grid_size(
+ GhosttyKittyGraphicsPlacementIterator iterator,
+ GhosttyKittyGraphicsImage image,
+ GhosttyTerminal terminal,
+ uint32_t* out_cols,
+ uint32_t* out_rows);
+
+/**
+ * Get the viewport-relative grid position of the current placement.
+ *
+ * Converts the placement's internal pin to viewport-relative column and
+ * row coordinates. The returned coordinates represent the top-left
+ * corner of the placement in the viewport's grid coordinate space.
+ *
+ * The row value can be negative when the placement's origin has
+ * scrolled above the top of the viewport. For example, a 4-row
+ * image that has scrolled up by 2 rows returns row=-2, meaning
+ * its top 2 rows are above the visible area but its bottom 2 rows
+ * are still on screen. Embedders should use these coordinates
+ * directly when computing the destination rectangle for rendering;
+ * the embedder is responsible for clipping the portion of the image
+ * that falls outside the viewport.
+ *
+ * Returns GHOSTTY_SUCCESS for any placement that is at least
+ * partially visible in the viewport. Returns GHOSTTY_NO_VALUE when
+ * the placement is completely outside the viewport (its bottom edge
+ * is above the viewport or its top edge is at or below the last
+ * viewport row), or when the placement is a virtual (unicode
+ * placeholder) placement.
+ *
+ * @param iterator The placement iterator positioned on a placement
+ * @param image The image handle for this placement's image
+ * @param terminal The terminal handle
+ * @param[out] out_col On success, receives the viewport-relative column
+ * @param[out] out_row On success, receives the viewport-relative row
+ * (may be negative for partially visible placements)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if fully
+ * off-screen or virtual, GHOSTTY_INVALID_VALUE if any handle
+ * is NULL or the iterator is not positioned
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_viewport_pos(
+ GhosttyKittyGraphicsPlacementIterator iterator,
+ GhosttyKittyGraphicsImage image,
+ GhosttyTerminal terminal,
+ int32_t* out_col,
+ int32_t* out_row);
+
+/**
+ * Get the resolved source rectangle for the current placement.
+ *
+ * Applies kitty protocol semantics: a width or height of 0 in the
+ * placement means "use the full image dimension", and the resulting
+ * rectangle is clamped to the actual image bounds. The returned
+ * values are in pixels and are ready to use for texture sampling.
+ *
+ * @param iterator The placement iterator positioned on a placement
+ * @param image The image handle for this placement's image
+ * @param[out] out_x Source rect x origin in pixels
+ * @param[out] out_y Source rect y origin in pixels
+ * @param[out] out_width Source rect width in pixels
+ * @param[out] out_height Source rect height in pixels
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if any
+ * handle is NULL or the iterator is not positioned
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_source_rect(
+ GhosttyKittyGraphicsPlacementIterator iterator,
+ GhosttyKittyGraphicsImage image,
+ uint32_t* out_x,
+ uint32_t* out_y,
+ uint32_t* out_width,
+ uint32_t* out_height);
+
+/**
+ * Get all rendering geometry for a placement in a single call.
+ *
+ * Combines pixel size, grid size, viewport position, and source
+ * rectangle into one struct. Initialize with
+ * GHOSTTY_INIT_SIZED(GhosttyKittyGraphicsPlacementRenderInfo).
+ *
+ * When viewport_visible is false, the placement is fully off-screen
+ * or is a virtual placement; viewport_col and viewport_row may
+ * contain meaningless values in that case.
+ *
+ * @param iterator The iterator positioned on a placement
+ * @param image The image handle for this placement's image
+ * @param terminal The terminal handle
+ * @param[out] out_info Pointer to receive the rendering geometry
+ * @return GHOSTTY_SUCCESS on success
+ *
+ * @ingroup kitty_graphics
+ */
+GHOSTTY_API GhosttyResult ghostty_kitty_graphics_placement_render_info(
+ GhosttyKittyGraphicsPlacementIterator iterator,
+ GhosttyKittyGraphicsImage image,
+ GhosttyTerminal terminal,
+ GhosttyKittyGraphicsPlacementRenderInfo* out_info);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_KITTY_GRAPHICS_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/modes.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/modes.h
new file mode 100644
index 00000000000..8e1fd91179e
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/modes.h
@@ -0,0 +1,198 @@
+/**
+ * @file modes.h
+ *
+ * Terminal mode utilities - pack and unpack ANSI/DEC mode identifiers.
+ */
+
+#ifndef GHOSTTY_VT_MODES_H
+#define GHOSTTY_VT_MODES_H
+
+/** @defgroup modes Mode Utilities
+ *
+ * Utilities for working with terminal modes. A mode is a compact
+ * 16-bit representation of a terminal mode identifier that encodes both
+ * the numeric mode value (up to 15 bits) and whether the mode is an ANSI
+ * mode or a DEC private mode (?-prefixed).
+ *
+ * The packed layout (least-significant bit first) is:
+ * - Bits 0–14: mode value (u15)
+ * - Bit 15: ANSI flag (0 = DEC private mode, 1 = ANSI mode)
+ *
+ * ## Example
+ *
+ * @snippet c-vt-modes/src/main.c modes-pack-unpack
+ *
+ * ## DECRPM Report Encoding
+ *
+ * Use ghostty_mode_report_encode() to encode a DECRPM response into a
+ * caller-provided buffer:
+ *
+ * @snippet c-vt-modes/src/main.c modes-decrpm
+ *
+ * @{
+ */
+
+#include
+#include
+#include
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @name ANSI Modes
+ * Modes for standard ANSI modes.
+ * @{
+ */
+#define GHOSTTY_MODE_KAM (ghostty_mode_new(2, true)) /**< Keyboard action (disable keyboard) */
+#define GHOSTTY_MODE_INSERT (ghostty_mode_new(4, true)) /**< Insert mode */
+#define GHOSTTY_MODE_SRM (ghostty_mode_new(12, true)) /**< Send/receive mode */
+#define GHOSTTY_MODE_LINEFEED (ghostty_mode_new(20, true)) /**< Linefeed/new line mode */
+/** @} */
+
+/** @name DEC Private Modes
+ * Modes for DEC private modes (?-prefixed).
+ * @{
+ */
+#define GHOSTTY_MODE_DECCKM (ghostty_mode_new(1, false)) /**< Cursor keys */
+#define GHOSTTY_MODE_132_COLUMN (ghostty_mode_new(3, false)) /**< 132/80 column mode */
+#define GHOSTTY_MODE_SLOW_SCROLL (ghostty_mode_new(4, false)) /**< Slow scroll */
+#define GHOSTTY_MODE_REVERSE_COLORS (ghostty_mode_new(5, false)) /**< Reverse video */
+#define GHOSTTY_MODE_ORIGIN (ghostty_mode_new(6, false)) /**< Origin mode */
+#define GHOSTTY_MODE_WRAPAROUND (ghostty_mode_new(7, false)) /**< Auto-wrap mode */
+#define GHOSTTY_MODE_AUTOREPEAT (ghostty_mode_new(8, false)) /**< Auto-repeat keys */
+#define GHOSTTY_MODE_X10_MOUSE (ghostty_mode_new(9, false)) /**< X10 mouse reporting */
+#define GHOSTTY_MODE_CURSOR_BLINKING (ghostty_mode_new(12, false)) /**< Cursor blink */
+#define GHOSTTY_MODE_CURSOR_VISIBLE (ghostty_mode_new(25, false)) /**< Cursor visible (DECTCEM) */
+#define GHOSTTY_MODE_ENABLE_MODE_3 (ghostty_mode_new(40, false)) /**< Allow 132 column mode */
+#define GHOSTTY_MODE_REVERSE_WRAP (ghostty_mode_new(45, false)) /**< Reverse wrap */
+#define GHOSTTY_MODE_ALT_SCREEN_LEGACY (ghostty_mode_new(47, false)) /**< Alternate screen (legacy) */
+#define GHOSTTY_MODE_KEYPAD_KEYS (ghostty_mode_new(66, false)) /**< Application keypad */
+#define GHOSTTY_MODE_BACKARROW_KEY_MODE (ghostty_mode_new(67, false)) /**< Backarrow key mode (DECBKM) */
+#define GHOSTTY_MODE_LEFT_RIGHT_MARGIN (ghostty_mode_new(69, false)) /**< Left/right margin mode */
+#define GHOSTTY_MODE_NORMAL_MOUSE (ghostty_mode_new(1000, false)) /**< Normal mouse tracking */
+#define GHOSTTY_MODE_BUTTON_MOUSE (ghostty_mode_new(1002, false)) /**< Button-event mouse tracking */
+#define GHOSTTY_MODE_ANY_MOUSE (ghostty_mode_new(1003, false)) /**< Any-event mouse tracking */
+#define GHOSTTY_MODE_FOCUS_EVENT (ghostty_mode_new(1004, false)) /**< Focus in/out events */
+#define GHOSTTY_MODE_UTF8_MOUSE (ghostty_mode_new(1005, false)) /**< UTF-8 mouse format */
+#define GHOSTTY_MODE_SGR_MOUSE (ghostty_mode_new(1006, false)) /**< SGR mouse format */
+#define GHOSTTY_MODE_ALT_SCROLL (ghostty_mode_new(1007, false)) /**< Alternate scroll mode */
+#define GHOSTTY_MODE_URXVT_MOUSE (ghostty_mode_new(1015, false)) /**< URxvt mouse format */
+#define GHOSTTY_MODE_SGR_PIXELS_MOUSE (ghostty_mode_new(1016, false)) /**< SGR-Pixels mouse format */
+#define GHOSTTY_MODE_NUMLOCK_KEYPAD (ghostty_mode_new(1035, false)) /**< Ignore keypad with NumLock */
+#define GHOSTTY_MODE_ALT_ESC_PREFIX (ghostty_mode_new(1036, false)) /**< Alt key sends ESC prefix */
+#define GHOSTTY_MODE_ALT_SENDS_ESC (ghostty_mode_new(1039, false)) /**< Alt sends escape */
+#define GHOSTTY_MODE_REVERSE_WRAP_EXT (ghostty_mode_new(1045, false)) /**< Extended reverse wrap */
+#define GHOSTTY_MODE_ALT_SCREEN (ghostty_mode_new(1047, false)) /**< Alternate screen */
+#define GHOSTTY_MODE_SAVE_CURSOR (ghostty_mode_new(1048, false)) /**< Save cursor (DECSC) */
+#define GHOSTTY_MODE_ALT_SCREEN_SAVE (ghostty_mode_new(1049, false)) /**< Alt screen + save cursor + clear */
+#define GHOSTTY_MODE_BRACKETED_PASTE (ghostty_mode_new(2004, false)) /**< Bracketed paste mode */
+#define GHOSTTY_MODE_SYNC_OUTPUT (ghostty_mode_new(2026, false)) /**< Synchronized output */
+#define GHOSTTY_MODE_GRAPHEME_CLUSTER (ghostty_mode_new(2027, false)) /**< Grapheme cluster mode */
+#define GHOSTTY_MODE_COLOR_SCHEME_REPORT (ghostty_mode_new(2031, false)) /**< Report color scheme */
+#define GHOSTTY_MODE_IN_BAND_RESIZE (ghostty_mode_new(2048, false)) /**< In-band size reports */
+/** @} */
+
+/**
+ * A packed 16-bit terminal mode.
+ *
+ * Encodes a mode value (bits 0–14) and an ANSI flag (bit 15) into a
+ * single 16-bit integer. Use the inline helper functions to construct
+ * and inspect modes rather than manipulating bits directly.
+ */
+typedef uint16_t GhosttyMode;
+
+/**
+ * Create a mode from a mode value and ANSI flag.
+ *
+ * @param value The numeric mode value (0–32767)
+ * @param ansi true for an ANSI mode, false for a DEC private mode
+ * @return The packed mode
+ *
+ * @ingroup modes
+ */
+static inline GhosttyMode ghostty_mode_new(uint16_t value, bool ansi) {
+ return (GhosttyMode)((value & 0x7FFF) | ((uint16_t)ansi << 15));
+}
+
+/**
+ * Extract the numeric mode value from a mode.
+ *
+ * @param mode The mode
+ * @return The mode value (0–32767)
+ *
+ * @ingroup modes
+ */
+static inline uint16_t ghostty_mode_value(GhosttyMode mode) {
+ return mode & 0x7FFF;
+}
+
+/**
+ * Check whether a mode represents an ANSI mode.
+ *
+ * @param mode The mode
+ * @return true if this is an ANSI mode, false if it is a DEC private mode
+ *
+ * @ingroup modes
+ */
+static inline bool ghostty_mode_ansi(GhosttyMode mode) {
+ return (mode >> 15) != 0;
+}
+
+/**
+ * DECRPM report state values.
+ *
+ * These correspond to the Ps2 parameter in a DECRPM response
+ * sequence (CSI ? Ps1 ; Ps2 $ y).
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Mode is not recognized */
+ GHOSTTY_MODE_REPORT_NOT_RECOGNIZED = 0,
+ /** Mode is set (enabled) */
+ GHOSTTY_MODE_REPORT_SET = 1,
+ /** Mode is reset (disabled) */
+ GHOSTTY_MODE_REPORT_RESET = 2,
+ /** Mode is permanently set */
+ GHOSTTY_MODE_REPORT_PERMANENTLY_SET = 3,
+ /** Mode is permanently reset */
+ GHOSTTY_MODE_REPORT_PERMANENTLY_RESET = 4,
+ GHOSTTY_MODE_REPORT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyModeReportState;
+
+/**
+ * Encode a DECRPM (DEC Private Mode Report) response sequence.
+ *
+ * Writes a mode report escape sequence into the provided buffer.
+ * The generated sequence has the form:
+ * - DEC private mode: CSI ? Ps1 ; Ps2 $ y
+ * - ANSI mode: CSI Ps1 ; Ps2 $ y
+ *
+ * If the buffer is too small, the function returns GHOSTTY_OUT_OF_SPACE
+ * and writes the required buffer size to @p out_written. The caller can
+ * then retry with a sufficiently sized buffer.
+ *
+ * @param mode The mode identifying the mode to report on
+ * @param state The report state for this mode
+ * @param buf Output buffer to write the encoded sequence into (may be NULL)
+ * @param buf_len Size of the output buffer in bytes
+ * @param[out] out_written On success, the number of bytes written. On
+ * GHOSTTY_OUT_OF_SPACE, the required buffer size.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_SPACE if the buffer
+ * is too small
+ */
+GHOSTTY_API GhosttyResult ghostty_mode_report_encode(
+ GhosttyMode mode,
+ GhosttyModeReportState state,
+ char* buf,
+ size_t buf_len,
+ size_t* out_written);
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
+
+#endif /* GHOSTTY_VT_MODES_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/mouse.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/mouse.h
new file mode 100644
index 00000000000..4ba5f52e38b
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/mouse.h
@@ -0,0 +1,70 @@
+/**
+ * @file mouse.h
+ *
+ * Mouse encoding module - encode mouse events into terminal escape sequences.
+ */
+
+#ifndef GHOSTTY_VT_MOUSE_H
+#define GHOSTTY_VT_MOUSE_H
+
+/** @defgroup mouse Mouse Encoding
+ *
+ * Utilities for encoding mouse events into terminal escape sequences,
+ * supporting X10, UTF-8, SGR, URxvt, and SGR-Pixels mouse protocols.
+ *
+ * ## Basic Usage
+ *
+ * 1. Create an encoder instance with ghostty_mouse_encoder_new().
+ * 2. Configure encoder options with ghostty_mouse_encoder_setopt() or
+ * ghostty_mouse_encoder_setopt_from_terminal().
+ * 3. For each mouse event:
+ * - Create a mouse event with ghostty_mouse_event_new().
+ * - Set event properties (action, button, modifiers, position).
+ * - Encode with ghostty_mouse_encoder_encode().
+ * - Free the event with ghostty_mouse_event_free() or reuse it.
+ * 4. Free the encoder with ghostty_mouse_encoder_free() when done.
+ *
+ * For a complete working example, see example/c-vt-encode-mouse in the
+ * repository.
+ *
+ * ## Example
+ *
+ * @snippet c-vt-encode-mouse/src/main.c mouse-encode
+ *
+ * ## Example: Encoding with Terminal State
+ *
+ * When you have a GhosttyTerminal, you can sync its tracking mode and
+ * output format into the encoder automatically:
+ *
+ * @code{.c}
+ * // Create a terminal and feed it some VT data that enables mouse tracking
+ * GhosttyTerminal terminal;
+ * ghostty_terminal_new(NULL, &terminal,
+ * (GhosttyTerminalOptions){.cols = 80, .rows = 24, .max_scrollback = 0});
+ *
+ * // Application might write data that enables mouse reporting, etc.
+ * ghostty_terminal_vt_write(terminal, vt_data, vt_len);
+ *
+ * // Create an encoder and sync its options from the terminal
+ * GhosttyMouseEncoder encoder;
+ * ghostty_mouse_encoder_new(NULL, &encoder);
+ * ghostty_mouse_encoder_setopt_from_terminal(encoder, terminal);
+ *
+ * // Encode a mouse event using the terminal-derived options
+ * char buf[128];
+ * size_t written = 0;
+ * ghostty_mouse_encoder_encode(encoder, event, buf, sizeof(buf), &written);
+ *
+ * ghostty_mouse_encoder_free(encoder);
+ * ghostty_terminal_free(terminal);
+ * @endcode
+ *
+ * @{
+ */
+
+#include
+#include
+
+/** @} */
+
+#endif /* GHOSTTY_VT_MOUSE_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/mouse/encoder.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/mouse/encoder.h
new file mode 100644
index 00000000000..d84d863c8d7
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/mouse/encoder.h
@@ -0,0 +1,214 @@
+/**
+ * @file encoder.h
+ *
+ * Mouse event encoding to terminal escape sequences.
+ */
+
+#ifndef GHOSTTY_VT_MOUSE_ENCODER_H
+#define GHOSTTY_VT_MOUSE_ENCODER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/**
+ * Opaque handle to a mouse encoder instance.
+ *
+ * This handle represents a mouse encoder that converts normalized
+ * mouse events into terminal escape sequences.
+ *
+ * @ingroup mouse
+ */
+typedef struct GhosttyMouseEncoderImpl *GhosttyMouseEncoder;
+
+/**
+ * Mouse tracking mode.
+ *
+ * @ingroup mouse
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Mouse reporting disabled. */
+ GHOSTTY_MOUSE_TRACKING_NONE = 0,
+
+ /** X10 mouse mode. */
+ GHOSTTY_MOUSE_TRACKING_X10 = 1,
+
+ /** Normal mouse mode (button press/release only). */
+ GHOSTTY_MOUSE_TRACKING_NORMAL = 2,
+
+ /** Button-event tracking mode. */
+ GHOSTTY_MOUSE_TRACKING_BUTTON = 3,
+
+ /** Any-event tracking mode. */
+ GHOSTTY_MOUSE_TRACKING_ANY = 4,
+ GHOSTTY_MOUSE_TRACKING_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyMouseTrackingMode;
+
+/**
+ * Mouse output format.
+ *
+ * @ingroup mouse
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_MOUSE_FORMAT_X10 = 0,
+ GHOSTTY_MOUSE_FORMAT_UTF8 = 1,
+ GHOSTTY_MOUSE_FORMAT_SGR = 2,
+ GHOSTTY_MOUSE_FORMAT_URXVT = 3,
+ GHOSTTY_MOUSE_FORMAT_SGR_PIXELS = 4,
+ GHOSTTY_MOUSE_FORMAT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyMouseFormat;
+
+/**
+ * Mouse encoder size and geometry context.
+ *
+ * This describes the rendered terminal geometry used to convert
+ * surface-space positions into encoded coordinates.
+ *
+ * @ingroup mouse
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttyMouseEncoderSize). */
+ size_t size;
+
+ /** Full screen width in pixels. */
+ uint32_t screen_width;
+
+ /** Full screen height in pixels. */
+ uint32_t screen_height;
+
+ /** Cell width in pixels. Must be non-zero. */
+ uint32_t cell_width;
+
+ /** Cell height in pixels. Must be non-zero. */
+ uint32_t cell_height;
+
+ /** Top padding in pixels. */
+ uint32_t padding_top;
+
+ /** Bottom padding in pixels. */
+ uint32_t padding_bottom;
+
+ /** Right padding in pixels. */
+ uint32_t padding_right;
+
+ /** Left padding in pixels. */
+ uint32_t padding_left;
+} GhosttyMouseEncoderSize;
+
+/**
+ * Mouse encoder option identifiers.
+ *
+ * These values are used with ghostty_mouse_encoder_setopt() to configure
+ * the behavior of the mouse encoder.
+ *
+ * @ingroup mouse
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Mouse tracking mode (value: GhosttyMouseTrackingMode). */
+ GHOSTTY_MOUSE_ENCODER_OPT_EVENT = 0,
+
+ /** Mouse output format (value: GhosttyMouseFormat). */
+ GHOSTTY_MOUSE_ENCODER_OPT_FORMAT = 1,
+
+ /** Renderer size context (value: GhosttyMouseEncoderSize). */
+ GHOSTTY_MOUSE_ENCODER_OPT_SIZE = 2,
+
+ /** Whether any mouse button is currently pressed (value: bool). */
+ GHOSTTY_MOUSE_ENCODER_OPT_ANY_BUTTON_PRESSED = 3,
+
+ /** Whether to enable motion deduplication by last cell (value: bool). */
+ GHOSTTY_MOUSE_ENCODER_OPT_TRACK_LAST_CELL = 4,
+ GHOSTTY_MOUSE_ENCODER_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyMouseEncoderOption;
+
+/**
+ * Create a new mouse encoder instance.
+ *
+ * @param allocator Pointer to allocator, or NULL to use the default allocator
+ * @param encoder Pointer to store the created encoder handle
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API GhosttyResult ghostty_mouse_encoder_new(const GhosttyAllocator *allocator,
+ GhosttyMouseEncoder *encoder);
+
+/**
+ * Free a mouse encoder instance.
+ *
+ * @param encoder The encoder handle to free (may be NULL)
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API void ghostty_mouse_encoder_free(GhosttyMouseEncoder encoder);
+
+/**
+ * Set an option on the mouse encoder.
+ *
+ * A null pointer value does nothing. It does not reset to defaults.
+ *
+ * @param encoder The encoder handle, must not be NULL
+ * @param option The option to set
+ * @param value Pointer to option value (type depends on option)
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API void ghostty_mouse_encoder_setopt(GhosttyMouseEncoder encoder,
+ GhosttyMouseEncoderOption option,
+ const void *value);
+
+/**
+ * Set encoder options from a terminal's current state.
+ *
+ * This sets tracking mode and output format from terminal state.
+ * It does not modify size or any-button state.
+ *
+ * @param encoder The encoder handle, must not be NULL
+ * @param terminal The terminal handle, must not be NULL
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API void ghostty_mouse_encoder_setopt_from_terminal(GhosttyMouseEncoder encoder,
+ GhosttyTerminal terminal);
+
+/**
+ * Reset internal encoder state.
+ *
+ * This clears motion deduplication state (last tracked cell).
+ *
+ * @param encoder The encoder handle (may be NULL)
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API void ghostty_mouse_encoder_reset(GhosttyMouseEncoder encoder);
+
+/**
+ * Encode a mouse event into a terminal escape sequence.
+ *
+ * Not all mouse events produce output. In such cases this returns
+ * GHOSTTY_SUCCESS with out_len set to 0.
+ *
+ * If the output buffer is too small, this returns GHOSTTY_OUT_OF_SPACE
+ * and out_len contains the required size.
+ *
+ * @param encoder The encoder handle, must not be NULL
+ * @param event The mouse event to encode, must not be NULL
+ * @param out_buf Buffer to write encoded bytes to, or NULL to query required size
+ * @param out_buf_size Size of out_buf in bytes
+ * @param out_len Pointer to store bytes written (or required bytes on failure)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_SPACE if buffer is too small,
+ * or another error code
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API GhosttyResult ghostty_mouse_encoder_encode(GhosttyMouseEncoder encoder,
+ GhosttyMouseEvent event,
+ char *out_buf,
+ size_t out_buf_size,
+ size_t *out_len);
+
+#endif /* GHOSTTY_VT_MOUSE_ENCODER_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/mouse/event.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/mouse/event.h
new file mode 100644
index 00000000000..a24b0c079bb
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/mouse/event.h
@@ -0,0 +1,195 @@
+/**
+ * @file event.h
+ *
+ * Mouse event representation and manipulation.
+ */
+
+#ifndef GHOSTTY_VT_MOUSE_EVENT_H
+#define GHOSTTY_VT_MOUSE_EVENT_H
+
+#include
+#include
+#include
+#include
+
+/**
+ * Opaque handle to a mouse event.
+ *
+ * This handle represents a normalized mouse input event containing
+ * action, button, modifiers, and surface-space position.
+ *
+ * @ingroup mouse
+ */
+typedef struct GhosttyMouseEventImpl *GhosttyMouseEvent;
+
+/**
+ * Mouse event action type.
+ *
+ * @ingroup mouse
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Mouse button was pressed. */
+ GHOSTTY_MOUSE_ACTION_PRESS = 0,
+
+ /** Mouse button was released. */
+ GHOSTTY_MOUSE_ACTION_RELEASE = 1,
+
+ /** Mouse moved. */
+ GHOSTTY_MOUSE_ACTION_MOTION = 2,
+ GHOSTTY_MOUSE_ACTION_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyMouseAction;
+
+/**
+ * Mouse button identity.
+ *
+ * @ingroup mouse
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_MOUSE_BUTTON_UNKNOWN = 0,
+ GHOSTTY_MOUSE_BUTTON_LEFT = 1,
+ GHOSTTY_MOUSE_BUTTON_RIGHT = 2,
+ GHOSTTY_MOUSE_BUTTON_MIDDLE = 3,
+ GHOSTTY_MOUSE_BUTTON_FOUR = 4,
+ GHOSTTY_MOUSE_BUTTON_FIVE = 5,
+ GHOSTTY_MOUSE_BUTTON_SIX = 6,
+ GHOSTTY_MOUSE_BUTTON_SEVEN = 7,
+ GHOSTTY_MOUSE_BUTTON_EIGHT = 8,
+ GHOSTTY_MOUSE_BUTTON_NINE = 9,
+ GHOSTTY_MOUSE_BUTTON_TEN = 10,
+ GHOSTTY_MOUSE_BUTTON_ELEVEN = 11,
+ GHOSTTY_MOUSE_BUTTON_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyMouseButton;
+
+/**
+ * Mouse position in surface-space pixels.
+ *
+ * @ingroup mouse
+ */
+typedef struct {
+ float x;
+ float y;
+} GhosttyMousePosition;
+
+/**
+ * Create a new mouse event instance.
+ *
+ * @param allocator Pointer to allocator, or NULL to use the default allocator
+ * @param event Pointer to store the created event handle
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API GhosttyResult ghostty_mouse_event_new(const GhosttyAllocator *allocator,
+ GhosttyMouseEvent *event);
+
+/**
+ * Free a mouse event instance.
+ *
+ * @param event The mouse event handle to free (may be NULL)
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API void ghostty_mouse_event_free(GhosttyMouseEvent event);
+
+/**
+ * Set the event action.
+ *
+ * @param event The event handle, must not be NULL
+ * @param action The action to set
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API void ghostty_mouse_event_set_action(GhosttyMouseEvent event,
+ GhosttyMouseAction action);
+
+/**
+ * Get the event action.
+ *
+ * @param event The event handle, must not be NULL
+ * @return The event action
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API GhosttyMouseAction ghostty_mouse_event_get_action(GhosttyMouseEvent event);
+
+/**
+ * Set the event button.
+ *
+ * This sets a concrete button identity for the event.
+ * To represent "no button" (for motion events), use
+ * ghostty_mouse_event_clear_button().
+ *
+ * @param event The event handle, must not be NULL
+ * @param button The button to set
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API void ghostty_mouse_event_set_button(GhosttyMouseEvent event,
+ GhosttyMouseButton button);
+
+/**
+ * Clear the event button.
+ *
+ * This sets the event button to "none".
+ *
+ * @param event The event handle, must not be NULL
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API void ghostty_mouse_event_clear_button(GhosttyMouseEvent event);
+
+/**
+ * Get the event button.
+ *
+ * @param event The event handle, must not be NULL
+ * @param out_button Output pointer for the button value (may be NULL)
+ * @return true if a button is set, false if no button is set
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API bool ghostty_mouse_event_get_button(GhosttyMouseEvent event,
+ GhosttyMouseButton *out_button);
+
+/**
+ * Set keyboard modifiers held during the event.
+ *
+ * @param event The event handle, must not be NULL
+ * @param mods Modifier bitmask
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API void ghostty_mouse_event_set_mods(GhosttyMouseEvent event,
+ GhosttyMods mods);
+
+/**
+ * Get keyboard modifiers held during the event.
+ *
+ * @param event The event handle, must not be NULL
+ * @return Modifier bitmask
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API GhosttyMods ghostty_mouse_event_get_mods(GhosttyMouseEvent event);
+
+/**
+ * Set the event position in surface-space pixels.
+ *
+ * @param event The event handle, must not be NULL
+ * @param position The position to set
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API void ghostty_mouse_event_set_position(GhosttyMouseEvent event,
+ GhosttyMousePosition position);
+
+/**
+ * Get the event position in surface-space pixels.
+ *
+ * @param event The event handle, must not be NULL
+ * @return The current event position
+ *
+ * @ingroup mouse
+ */
+GHOSTTY_API GhosttyMousePosition ghostty_mouse_event_get_position(GhosttyMouseEvent event);
+
+#endif /* GHOSTTY_VT_MOUSE_EVENT_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/osc.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/osc.h
new file mode 100644
index 00000000000..9409ebc738f
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/osc.h
@@ -0,0 +1,215 @@
+/**
+ * @file osc.h
+ *
+ * OSC (Operating System Command) sequence parser and command handling.
+ */
+
+#ifndef GHOSTTY_VT_OSC_H
+#define GHOSTTY_VT_OSC_H
+
+#include
+#include
+#include
+#include
+#include
+
+/** @defgroup osc OSC Parser
+ *
+ * OSC (Operating System Command) sequence parser and command handling.
+ *
+ * The parser operates in a streaming fashion, processing input byte-by-byte
+ * to handle OSC sequences that may arrive in fragments across multiple reads.
+ * This interface makes it easy to integrate into most environments and avoids
+ * over-allocating buffers.
+ *
+ * ## Basic Usage
+ *
+ * 1. Create a parser instance with ghostty_osc_new()
+ * 2. Feed bytes to the parser using ghostty_osc_next()
+ * 3. Finalize parsing with ghostty_osc_end() to get the command
+ * 4. Query command type and extract data using ghostty_osc_command_type()
+ * and ghostty_osc_command_data()
+ * 5. Free the parser with ghostty_osc_free() when done
+ *
+ * @{
+ */
+
+/**
+ * OSC command types.
+ *
+ * @ingroup osc
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_OSC_COMMAND_INVALID = 0,
+ GHOSTTY_OSC_COMMAND_CHANGE_WINDOW_TITLE = 1,
+ GHOSTTY_OSC_COMMAND_CHANGE_WINDOW_ICON = 2,
+ GHOSTTY_OSC_COMMAND_SEMANTIC_PROMPT = 3,
+ GHOSTTY_OSC_COMMAND_CLIPBOARD_CONTENTS = 4,
+ GHOSTTY_OSC_COMMAND_REPORT_PWD = 5,
+ GHOSTTY_OSC_COMMAND_MOUSE_SHAPE = 6,
+ GHOSTTY_OSC_COMMAND_COLOR_OPERATION = 7,
+ GHOSTTY_OSC_COMMAND_KITTY_COLOR_PROTOCOL = 8,
+ GHOSTTY_OSC_COMMAND_SHOW_DESKTOP_NOTIFICATION = 9,
+ GHOSTTY_OSC_COMMAND_HYPERLINK_START = 10,
+ GHOSTTY_OSC_COMMAND_HYPERLINK_END = 11,
+ GHOSTTY_OSC_COMMAND_CONEMU_SLEEP = 12,
+ GHOSTTY_OSC_COMMAND_CONEMU_SHOW_MESSAGE_BOX = 13,
+ GHOSTTY_OSC_COMMAND_CONEMU_CHANGE_TAB_TITLE = 14,
+ GHOSTTY_OSC_COMMAND_CONEMU_PROGRESS_REPORT = 15,
+ GHOSTTY_OSC_COMMAND_CONEMU_WAIT_INPUT = 16,
+ GHOSTTY_OSC_COMMAND_CONEMU_GUIMACRO = 17,
+ GHOSTTY_OSC_COMMAND_CONEMU_RUN_PROCESS = 18,
+ GHOSTTY_OSC_COMMAND_CONEMU_OUTPUT_ENVIRONMENT_VARIABLE = 19,
+ GHOSTTY_OSC_COMMAND_CONEMU_XTERM_EMULATION = 20,
+ GHOSTTY_OSC_COMMAND_CONEMU_COMMENT = 21,
+ GHOSTTY_OSC_COMMAND_KITTY_TEXT_SIZING = 22,
+ GHOSTTY_OSC_COMMAND_TYPE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyOscCommandType;
+
+/**
+ * OSC command data types.
+ *
+ * These values specify what type of data to extract from an OSC command
+ * using `ghostty_osc_command_data`.
+ *
+ * @ingroup osc
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Invalid data type. Never results in any data extraction. */
+ GHOSTTY_OSC_DATA_INVALID = 0,
+
+ /**
+ * Window title string data.
+ *
+ * Valid for: GHOSTTY_OSC_COMMAND_CHANGE_WINDOW_TITLE
+ *
+ * Output type: const char ** (pointer to null-terminated string)
+ *
+ * Lifetime: Valid until the next call to any ghostty_osc_* function with
+ * the same parser instance. Memory is owned by the parser.
+ */
+ GHOSTTY_OSC_DATA_CHANGE_WINDOW_TITLE_STR = 1,
+ GHOSTTY_OSC_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyOscCommandData;
+
+/**
+ * Create a new OSC parser instance.
+ *
+ * Creates a new OSC (Operating System Command) parser using the provided
+ * allocator. The parser must be freed using ghostty_vt_osc_free() when
+ * no longer needed.
+ *
+ * @param allocator Pointer to the allocator to use for memory management, or NULL to use the default allocator
+ * @param parser Pointer to store the created parser handle
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup osc
+ */
+GHOSTTY_API GhosttyResult ghostty_osc_new(const GhosttyAllocator *allocator, GhosttyOscParser *parser);
+
+/**
+ * Free an OSC parser instance.
+ *
+ * Releases all resources associated with the OSC parser. After this call,
+ * the parser handle becomes invalid and must not be used.
+ *
+ * @param parser The parser handle to free (may be NULL)
+ *
+ * @ingroup osc
+ */
+GHOSTTY_API void ghostty_osc_free(GhosttyOscParser parser);
+
+/**
+ * Reset an OSC parser instance to its initial state.
+ *
+ * Resets the parser state, clearing any partially parsed OSC sequences
+ * and returning the parser to its initial state. This is useful for
+ * reusing a parser instance or recovering from parse errors.
+ *
+ * @param parser The parser handle to reset, must not be null.
+ *
+ * @ingroup osc
+ */
+GHOSTTY_API void ghostty_osc_reset(GhosttyOscParser parser);
+
+/**
+ * Parse the next byte in an OSC sequence.
+ *
+ * Processes a single byte as part of an OSC sequence. The parser maintains
+ * internal state to track the progress through the sequence. Call this
+ * function for each byte in the sequence data.
+ *
+ * When finished pumping the parser with bytes, call ghostty_osc_end
+ * to get the final result.
+ *
+ * @param parser The parser handle, must not be null.
+ * @param byte The next byte to parse
+ *
+ * @ingroup osc
+ */
+GHOSTTY_API void ghostty_osc_next(GhosttyOscParser parser, uint8_t byte);
+
+/**
+ * Finalize OSC parsing and retrieve the parsed command.
+ *
+ * Call this function after feeding all bytes of an OSC sequence to the parser
+ * using ghostty_osc_next() with the exception of the terminating character
+ * (ESC or ST). This function finalizes the parsing process and returns the
+ * parsed OSC command.
+ *
+ * The return value is never NULL. Invalid commands will return a command
+ * with type GHOSTTY_OSC_COMMAND_INVALID.
+ *
+ * The terminator parameter specifies the byte that terminated the OSC sequence
+ * (typically 0x07 for BEL or 0x5C for ST after ESC). This information is
+ * preserved in the parsed command so that responses can use the same terminator
+ * format for better compatibility with the calling program. For commands that
+ * do not require a response, this parameter is ignored and the resulting
+ * command will not retain the terminator information.
+ *
+ * The returned command handle is valid until the next call to any
+ * `ghostty_osc_*` function with the same parser instance with the exception
+ * of command introspection functions such as `ghostty_osc_command_type`.
+ *
+ * @param parser The parser handle, must not be null.
+ * @param terminator The terminating byte of the OSC sequence (0x07 for BEL, 0x5C for ST)
+ * @return Handle to the parsed OSC command
+ *
+ * @ingroup osc
+ */
+GHOSTTY_API GhosttyOscCommand ghostty_osc_end(GhosttyOscParser parser, uint8_t terminator);
+
+/**
+ * Get the type of an OSC command.
+ *
+ * Returns the type identifier for the given OSC command. This can be used
+ * to determine what kind of command was parsed and what data might be
+ * available from it.
+ *
+ * @param command The OSC command handle to query (may be NULL)
+ * @return The command type, or GHOSTTY_OSC_COMMAND_INVALID if command is NULL
+ *
+ * @ingroup osc
+ */
+GHOSTTY_API GhosttyOscCommandType ghostty_osc_command_type(GhosttyOscCommand command);
+
+/**
+ * Extract data from an OSC command.
+ *
+ * Extracts typed data from the given OSC command based on the specified
+ * data type. The output pointer must be of the appropriate type for the
+ * requested data kind. Valid command types, output types, and memory
+ * safety information are documented in the `GhosttyOscCommandData` enum.
+ *
+ * @param command The OSC command handle to query (may be NULL)
+ * @param data The type of data to extract
+ * @param out Pointer to store the extracted data (type depends on data parameter)
+ * @return true if data extraction was successful, false otherwise
+ *
+ * @ingroup osc
+ */
+GHOSTTY_API bool ghostty_osc_command_data(GhosttyOscCommand command, GhosttyOscCommandData data, void *out);
+
+/** @} */
+
+#endif /* GHOSTTY_VT_OSC_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/paste.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/paste.h
new file mode 100644
index 00000000000..b3df5be4e0d
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/paste.h
@@ -0,0 +1,101 @@
+/**
+ * @file paste.h
+ *
+ * Paste utilities - validate and encode paste data for terminal input.
+ */
+
+#ifndef GHOSTTY_VT_PASTE_H
+#define GHOSTTY_VT_PASTE_H
+
+/** @defgroup paste Paste Utilities
+ *
+ * Utilities for validating and encoding paste data for terminal input.
+ *
+ * ## Basic Usage
+ *
+ * Use ghostty_paste_is_safe() to check if paste data contains potentially
+ * dangerous sequences before sending it to the terminal.
+ *
+ * Use ghostty_paste_encode() to encode paste data for writing to the pty,
+ * including bracketed paste wrapping and unsafe byte stripping.
+ *
+ * ## Examples
+ *
+ * ### Safety Check
+ *
+ * @snippet c-vt-paste/src/main.c paste-safety
+ *
+ * ### Encoding
+ *
+ * @snippet c-vt-paste/src/main.c paste-encode
+ *
+ * @{
+ */
+
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Check if paste data is safe to paste into the terminal.
+ *
+ * Data is considered unsafe if it contains:
+ * - Newlines (`\n`) which can inject commands
+ * - The bracketed paste end sequence (`\x1b[201~`) which can be used
+ * to exit bracketed paste mode and inject commands
+ *
+ * This check is conservative and considers data unsafe regardless of
+ * current terminal state.
+ *
+ * @param data The paste data to check (must not be NULL)
+ * @param len The length of the data in bytes
+ * @return true if the data is safe to paste, false otherwise
+ */
+GHOSTTY_API bool ghostty_paste_is_safe(const char* data, size_t len);
+
+/**
+ * Encode paste data for writing to the terminal pty.
+ *
+ * This function prepares paste data for terminal input by:
+ * - Stripping unsafe control bytes (NUL, ESC, DEL, etc.) by replacing
+ * them with spaces
+ * - Wrapping the data in bracketed paste sequences if @p bracketed is true
+ * - Replacing newlines with carriage returns if @p bracketed is false
+ *
+ * The input @p data buffer is modified in place during encoding. The
+ * encoded result (potentially with bracketed paste prefix/suffix) is
+ * written to the output buffer.
+ *
+ * If the output buffer is too small, the function returns
+ * GHOSTTY_OUT_OF_SPACE and sets the required size in @p out_written.
+ * The caller can then retry with a sufficiently sized buffer.
+ *
+ * @param data The paste data to encode (modified in place, may be NULL)
+ * @param data_len The length of the input data in bytes
+ * @param bracketed Whether bracketed paste mode is active
+ * @param buf Output buffer to write the encoded result into (may be NULL)
+ * @param buf_len Size of the output buffer in bytes
+ * @param[out] out_written On success, the number of bytes written. On
+ * GHOSTTY_OUT_OF_SPACE, the required buffer size.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_SPACE if the buffer
+ * is too small
+ */
+GHOSTTY_API GhosttyResult ghostty_paste_encode(
+ char* data,
+ size_t data_len,
+ bool bracketed,
+ char* buf,
+ size_t buf_len,
+ size_t* out_written);
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
+
+#endif /* GHOSTTY_VT_PASTE_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/point.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/point.h
new file mode 100644
index 00000000000..8b717f4940c
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/point.h
@@ -0,0 +1,89 @@
+/**
+ * @file point.h
+ *
+ * Terminal point types for referencing locations in the terminal grid.
+ */
+
+#ifndef GHOSTTY_VT_POINT_H
+#define GHOSTTY_VT_POINT_H
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @defgroup point Point
+ *
+ * Types for referencing x/y positions in the terminal grid under
+ * different coordinate systems (active area, viewport, full screen,
+ * scrollback history).
+ *
+ * @{
+ */
+
+/**
+ * A coordinate in the terminal grid.
+ *
+ * @ingroup point
+ */
+typedef struct {
+ /** Column (0-indexed). */
+ uint16_t x;
+
+ /** Row (0-indexed). May exceed page size for screen/history tags. */
+ uint32_t y;
+} GhosttyPointCoordinate;
+
+/**
+ * Point reference tag.
+ *
+ * Determines which coordinate system a point uses.
+ *
+ * @ingroup point
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Active area where the cursor can move. */
+ GHOSTTY_POINT_TAG_ACTIVE = 0,
+
+ /** Visible viewport (changes when scrolled). */
+ GHOSTTY_POINT_TAG_VIEWPORT = 1,
+
+ /** Full screen including scrollback. */
+ GHOSTTY_POINT_TAG_SCREEN = 2,
+
+ /** Scrollback history only (before active area). */
+ GHOSTTY_POINT_TAG_HISTORY = 3,
+ GHOSTTY_POINT_TAG_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+ } GhosttyPointTag;
+
+/**
+ * Point value union.
+ *
+ * @ingroup point
+ */
+typedef union {
+ /** Coordinate (used for all tag variants). */
+ GhosttyPointCoordinate coordinate;
+
+ /** Padding for ABI compatibility. Do not use. */
+ uint64_t _padding[2];
+} GhosttyPointValue;
+
+/**
+ * Tagged union for a point in the terminal grid.
+ *
+ * @ingroup point
+ */
+typedef struct {
+ GhosttyPointTag tag;
+ GhosttyPointValue value;
+} GhosttyPoint;
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_POINT_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/render.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/render.h
new file mode 100644
index 00000000000..c5b1d0d4fc2
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/render.h
@@ -0,0 +1,729 @@
+/**
+ * @file render.h
+ *
+ * Render state for creating high performance renderers.
+ */
+
+#ifndef GHOSTTY_VT_RENDER_H
+#define GHOSTTY_VT_RENDER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @defgroup render Render State
+ *
+ * Represents the state required to render a visible screen (a viewport)
+ * of a terminal instance. This is stateful and optimized for repeated
+ * updates from a single terminal instance and only updating dirty regions
+ * of the screen.
+ *
+ * The key design principle of this API is that it only needs read/write
+ * access to the terminal instance during the update call. This allows
+ * the render state to minimally impact terminal IO performance and also
+ * allows the renderer to be safely multi-threaded (as long as a lock is
+ * held during the update call to ensure exclusive access to the terminal
+ * instance).
+ *
+ * The basic usage of this API is:
+ *
+ * 1. Create an empty render state
+ * 2. Update it from a terminal instance whenever you need.
+ * 3. Read from the render state to get the data needed to draw your frame.
+ *
+ * ## Dirty Tracking
+ *
+ * Dirty tracking is a key feature of the render state that allows renderers
+ * to efficiently determine what parts of the screen have changed and only
+ * redraw changed regions.
+ *
+ * The render state API keeps track of dirty state at two independent layers:
+ * a global dirty state that indicates whether the entire frame is clean,
+ * partially dirty, or fully dirty, and a per-row dirty state that allows
+ * tracking which rows in a partially dirty frame have changed.
+ *
+ * The user of the render state API is expected to unset both of these.
+ * The `update` call does not unset dirty state, it only updates it.
+ *
+ * An extremely important detail: setting one dirty state doesn't unset
+ * the other. For example, setting the global dirty state to false does not
+ * reset the row-level dirty flags. So, the caller of the render state API must
+ * be careful to manage both layers of dirty state correctly.
+ *
+ * ## Examples
+ *
+ * ### Creating and updating render state
+ * @snippet c-vt-render/src/main.c render-state-update
+ *
+ * ### Checking dirty state
+ * @snippet c-vt-render/src/main.c render-dirty-check
+ *
+ * ### Reading colors
+ * @snippet c-vt-render/src/main.c render-colors
+ *
+ * ### Reading cursor state
+ * @snippet c-vt-render/src/main.c render-cursor
+ *
+ * ### Iterating rows and cells
+ * @snippet c-vt-render/src/main.c render-row-iterate
+ *
+ * ### Resetting dirty state after rendering
+ * @snippet c-vt-render/src/main.c render-dirty-reset
+ *
+ * @{
+ */
+
+/**
+ * Dirty state of a render state after update.
+ *
+ * @ingroup render
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Not dirty at all; rendering can be skipped. */
+ GHOSTTY_RENDER_STATE_DIRTY_FALSE = 0,
+
+ /** Some rows changed; renderer can redraw incrementally. */
+ GHOSTTY_RENDER_STATE_DIRTY_PARTIAL = 1,
+
+ /** Global state changed; renderer should redraw everything. */
+ GHOSTTY_RENDER_STATE_DIRTY_FULL = 2,
+ GHOSTTY_RENDER_STATE_DIRTY_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyRenderStateDirty;
+
+/**
+ * Visual style of the cursor.
+ *
+ * @ingroup render
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Bar cursor (DECSCUSR 5, 6). */
+ GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BAR = 0,
+
+ /** Block cursor (DECSCUSR 1, 2). */
+ GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BLOCK = 1,
+
+ /** Underline cursor (DECSCUSR 3, 4). */
+ GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_UNDERLINE = 2,
+
+ /** Hollow block cursor. */
+ GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BLOCK_HOLLOW = 3,
+ GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyRenderStateCursorVisualStyle;
+
+/**
+ * Queryable data kinds for ghostty_render_state_get().
+ *
+ * @ingroup render
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Invalid / sentinel value. */
+ GHOSTTY_RENDER_STATE_DATA_INVALID = 0,
+
+ /** Viewport width in cells (uint16_t). */
+ GHOSTTY_RENDER_STATE_DATA_COLS = 1,
+
+ /** Viewport height in cells (uint16_t). */
+ GHOSTTY_RENDER_STATE_DATA_ROWS = 2,
+
+ /** Current dirty state (GhosttyRenderStateDirty). */
+ GHOSTTY_RENDER_STATE_DATA_DIRTY = 3,
+
+ /** Populate a pre-allocated GhosttyRenderStateRowIterator with row data
+ * from the render state (GhosttyRenderStateRowIterator). Row data is
+ * only valid as long as the underlying render state is not updated.
+ * It is unsafe to use row data after updating the render state.
+ * */
+ GHOSTTY_RENDER_STATE_DATA_ROW_ITERATOR = 4,
+
+ /** Default/current background color (GhosttyColorRgb). */
+ GHOSTTY_RENDER_STATE_DATA_COLOR_BACKGROUND = 5,
+
+ /** Default/current foreground color (GhosttyColorRgb). */
+ GHOSTTY_RENDER_STATE_DATA_COLOR_FOREGROUND = 6,
+
+ /** Cursor color when explicitly set by terminal state (GhosttyColorRgb).
+ * Returns GHOSTTY_INVALID_VALUE if no explicit cursor color is set;
+ * use COLOR_CURSOR_HAS_VALUE to check first. */
+ GHOSTTY_RENDER_STATE_DATA_COLOR_CURSOR = 7,
+
+ /** Whether an explicit cursor color is set (bool). */
+ GHOSTTY_RENDER_STATE_DATA_COLOR_CURSOR_HAS_VALUE = 8,
+
+ /** The active 256-color palette (GhosttyColorRgb[256]). */
+ GHOSTTY_RENDER_STATE_DATA_COLOR_PALETTE = 9,
+
+ /** The visual style of the cursor (GhosttyRenderStateCursorVisualStyle). */
+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VISUAL_STYLE = 10,
+
+ /** Whether the cursor is visible based on terminal modes (bool). */
+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VISIBLE = 11,
+
+ /** Whether the cursor should blink based on terminal modes (bool). */
+ GHOSTTY_RENDER_STATE_DATA_CURSOR_BLINKING = 12,
+
+ /** Whether the cursor is at a password input field (bool). */
+ GHOSTTY_RENDER_STATE_DATA_CURSOR_PASSWORD_INPUT = 13,
+
+ /** Whether the cursor is visible within the viewport (bool).
+ * If false, the cursor viewport position values are undefined. */
+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE = 14,
+
+ /** Cursor viewport x position in cells (uint16_t).
+ * Only valid when CURSOR_VIEWPORT_HAS_VALUE is true. */
+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_X = 15,
+
+ /** Cursor viewport y position in cells (uint16_t).
+ * Only valid when CURSOR_VIEWPORT_HAS_VALUE is true. */
+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_Y = 16,
+
+ /** Whether the cursor is on the tail of a wide character (bool).
+ * Only valid when CURSOR_VIEWPORT_HAS_VALUE is true. */
+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_WIDE_TAIL = 17,
+ GHOSTTY_RENDER_STATE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyRenderStateData;
+
+/**
+ * Settable options for ghostty_render_state_set().
+ *
+ * @ingroup render
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Set dirty state (GhosttyRenderStateDirty). */
+ GHOSTTY_RENDER_STATE_OPTION_DIRTY = 0,
+ GHOSTTY_RENDER_STATE_OPTION_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyRenderStateOption;
+
+/**
+ * Queryable data kinds for ghostty_render_state_row_get().
+ *
+ * @ingroup render
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Invalid / sentinel value. */
+ GHOSTTY_RENDER_STATE_ROW_DATA_INVALID = 0,
+
+ /** Whether the current row is dirty (bool). */
+ GHOSTTY_RENDER_STATE_ROW_DATA_DIRTY = 1,
+
+ /** The raw row value (GhosttyRow). */
+ GHOSTTY_RENDER_STATE_ROW_DATA_RAW = 2,
+
+ /** Populate a pre-allocated GhosttyRenderStateRowCells with cell data for
+ * the current row (GhosttyRenderStateRowCells). Cell data is only
+ * valid as long as the underlying render state is not updated.
+ * It is unsafe to use cell data after updating the render state. */
+ GHOSTTY_RENDER_STATE_ROW_DATA_CELLS = 3,
+
+ /** Row-local selected cell range (GhosttyRenderStateRowSelection). */
+ GHOSTTY_RENDER_STATE_ROW_DATA_SELECTION = 4,
+ GHOSTTY_RENDER_STATE_ROW_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyRenderStateRowData;
+
+/**
+ * Settable options for ghostty_render_state_row_set().
+ *
+ * @ingroup render
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Set dirty state for the current row (bool). */
+ GHOSTTY_RENDER_STATE_ROW_OPTION_DIRTY = 0,
+ GHOSTTY_RENDER_STATE_ROW_OPTION_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyRenderStateRowOption;
+
+/**
+ * Row-local selection range.
+ *
+ * This struct uses the sized-struct ABI pattern. Initialize with
+ * GHOSTTY_INIT_SIZED(GhosttyRenderStateRowSelection) before querying
+ * GHOSTTY_RENDER_STATE_ROW_DATA_SELECTION.
+ *
+ * Querying GHOSTTY_RENDER_STATE_ROW_DATA_SELECTION returns GHOSTTY_NO_VALUE
+ * if the current row does not intersect the current selection.
+ *
+ * @ingroup render
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttyRenderStateRowSelection). */
+ size_t size;
+
+ /** Start column of the row-local selection range, inclusive. */
+ uint16_t start_x;
+
+ /** End column of the row-local selection range, inclusive. */
+ uint16_t end_x;
+} GhosttyRenderStateRowSelection;
+
+/**
+ * Render-state color information.
+ *
+ * This struct uses the sized-struct ABI pattern. Initialize with
+ * GHOSTTY_INIT_SIZED(GhosttyRenderStateColors) before calling
+ * ghostty_render_state_colors_get().
+ *
+ * Example:
+ * @code
+ * GhosttyRenderStateColors colors = GHOSTTY_INIT_SIZED(GhosttyRenderStateColors);
+ * GhosttyResult result = ghostty_render_state_colors_get(state, &colors);
+ * @endcode
+ *
+ * @ingroup render
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttyRenderStateColors). */
+ size_t size;
+
+ /** The default/current background color for the render state. */
+ GhosttyColorRgb background;
+
+ /** The default/current foreground color for the render state. */
+ GhosttyColorRgb foreground;
+
+ /** The cursor color when explicitly set by terminal state. */
+ GhosttyColorRgb cursor;
+
+ /**
+ * True when cursor contains a valid explicit cursor color value.
+ * If this is false, the cursor color should be ignored; it will
+ * contain undefined data.
+ * */
+ bool cursor_has_value;
+
+ /** The active 256-color palette for this render state. */
+ GhosttyColorRgb palette[256];
+} GhosttyRenderStateColors;
+
+/**
+ * Create a new render state instance.
+ *
+ * @param allocator Pointer to allocator, or NULL to use the default allocator
+ * @param state Pointer to store the created render state handle
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation
+ * failure
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_new(const GhosttyAllocator* allocator,
+ GhosttyRenderState* state);
+
+/**
+ * Free a render state instance.
+ *
+ * Releases all resources associated with the render state. After this call,
+ * the render state handle becomes invalid.
+ *
+ * @param state The render state handle to free (may be NULL)
+ *
+ * @ingroup render
+ */
+GHOSTTY_API void ghostty_render_state_free(GhosttyRenderState state);
+
+/**
+ * Update a render state instance from a terminal.
+ *
+ * This consumes terminal/screen dirty state in the same way as the internal
+ * render state update path.
+ *
+ * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param terminal The terminal handle to read from (NULL returns GHOSTTY_INVALID_VALUE)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` or
+ * `terminal` is NULL, GHOSTTY_OUT_OF_MEMORY if updating the state requires
+ * allocation and that allocation fails
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_update(GhosttyRenderState state,
+ GhosttyTerminal terminal);
+
+/**
+ * Get a value from a render state.
+ *
+ * The `out` pointer must point to a value of the type corresponding to the
+ * requested data kind (see GhosttyRenderStateData).
+ *
+ * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param data The data kind to query
+ * @param[out] out Pointer to receive the queried value
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` is
+ * NULL or `data` is not a recognized enum value
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_get(GhosttyRenderState state,
+ GhosttyRenderStateData data,
+ void* out);
+
+/**
+ * Get multiple data fields from a render state in a single call.
+ *
+ * Each element in the keys array specifies a data kind, and the
+ * corresponding element in the values array receives the result.
+ *
+ * Processing stops at the first error; on success out_written
+ * is set to count, on error it is set to the index of the
+ * failing key (i.e. the number of values successfully written).
+ *
+ * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param count Number of key/value pairs
+ * @param keys Array of data kinds to query
+ * @param values Array of output pointers (types must match each key's
+ * documented output type)
+ * @param[out] out_written On return, receives the number of values
+ * successfully written (may be NULL)
+ * @return GHOSTTY_SUCCESS if all queries succeed
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_get_multi(
+ GhosttyRenderState state,
+ size_t count,
+ const GhosttyRenderStateData* keys,
+ void** values,
+ size_t* out_written);
+
+/**
+ * Set an option on a render state.
+ *
+ * The `value` pointer must point to a value of the type corresponding to the
+ * requested option kind (see GhosttyRenderStateOption).
+ *
+ * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param option The option to set
+ * @param[in] value Pointer to the value to set (NULL returns
+ * GHOSTTY_INVALID_VALUE)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` or
+ * `value` is NULL
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_set(GhosttyRenderState state,
+ GhosttyRenderStateOption option,
+ const void* value);
+
+/**
+ * Get the current color information from a render state.
+ *
+ * This writes as many fields as fit in the caller-provided sized struct.
+ * `out_colors->size` must be set by the caller (typically via
+ * GHOSTTY_INIT_SIZED(GhosttyRenderStateColors)).
+ *
+ * @param state The render state handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param[out] out_colors Sized output struct to receive render-state colors
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` or
+ * `out_colors` is NULL, or if `out_colors->size` is smaller than
+ * `sizeof(size_t)`
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_colors_get(GhosttyRenderState state,
+ GhosttyRenderStateColors* out_colors);
+
+/**
+ * Create a new row iterator instance.
+ *
+ * All fields except the allocator are left undefined until populated
+ * via ghostty_render_state_get() with
+ * GHOSTTY_RENDER_STATE_DATA_ROW_ITERATOR.
+ *
+ * @param allocator Pointer to allocator, or NULL to use the default allocator
+ * @param[out] out_iterator On success, receives the created iterator handle
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation
+ * failure
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_row_iterator_new(
+ const GhosttyAllocator* allocator,
+ GhosttyRenderStateRowIterator* out_iterator);
+
+/**
+ * Free a render-state row iterator.
+ *
+ * @param iterator The iterator handle to free (may be NULL)
+ *
+ * @ingroup render
+ */
+GHOSTTY_API void ghostty_render_state_row_iterator_free(GhosttyRenderStateRowIterator iterator);
+
+/**
+ * Move a render-state row iterator to the next row.
+ *
+ * Returns true if the iterator moved successfully and row data is
+ * available to read at the new position.
+ *
+ * @param iterator The iterator handle to advance (may be NULL)
+ * @return true if advanced to the next row, false if `iterator` is
+ * NULL or if the iterator has reached the end
+ *
+ * @ingroup render
+ */
+GHOSTTY_API bool ghostty_render_state_row_iterator_next(GhosttyRenderStateRowIterator iterator);
+
+/**
+ * Get a value from the current row in a render-state row iterator.
+ *
+ * The `out` pointer must point to a value of the type corresponding to the
+ * requested data kind (see GhosttyRenderStateRowData).
+ * Call ghostty_render_state_row_iterator_next() at least once before
+ * calling this function.
+ *
+ * @param iterator The iterator handle to query (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param data The data kind to query
+ * @param[out] out Pointer to receive the queried value
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if
+ * `iterator` is NULL or the iterator is not positioned on a row
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_row_get(
+ GhosttyRenderStateRowIterator iterator,
+ GhosttyRenderStateRowData data,
+ void* out);
+
+/**
+ * Get multiple data fields from the current row in a single call.
+ *
+ * Each element in the keys array specifies a data kind, and the
+ * corresponding element in the values array receives the result.
+ *
+ * Processing stops at the first error; on success out_written
+ * is set to count, on error it is set to the index of the
+ * failing key (i.e. the number of values successfully written).
+ *
+ * @param iterator The iterator handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param count Number of key/value pairs
+ * @param keys Array of data kinds to query
+ * @param values Array of output pointers (types must match each key's
+ * documented output type)
+ * @param[out] out_written On return, receives the number of values
+ * successfully written (may be NULL)
+ * @return GHOSTTY_SUCCESS if all queries succeed
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_row_get_multi(
+ GhosttyRenderStateRowIterator iterator,
+ size_t count,
+ const GhosttyRenderStateRowData* keys,
+ void** values,
+ size_t* out_written);
+
+/**
+ * Set an option on the current row in a render-state row iterator.
+ *
+ * The `value` pointer must point to a value of the type corresponding to the
+ * requested option kind (see GhosttyRenderStateRowOption).
+ * Call ghostty_render_state_row_iterator_next() at least once before
+ * calling this function.
+ *
+ * @param iterator The iterator handle to update (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param option The option to set
+ * @param[in] value Pointer to the value to set (NULL returns
+ * GHOSTTY_INVALID_VALUE)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if
+ * `iterator` is NULL or the iterator is not positioned on a row
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_row_set(
+ GhosttyRenderStateRowIterator iterator,
+ GhosttyRenderStateRowOption option,
+ const void* value);
+
+/**
+ * Create a new row cells instance.
+ *
+ * All fields except the allocator are left undefined until populated
+ * via ghostty_render_state_row_get() with
+ * GHOSTTY_RENDER_STATE_ROW_DATA_CELLS.
+ *
+ * You can reuse this value repeatedly with ghostty_render_state_row_get() to
+ * avoid allocating a new cells container for every row.
+ *
+ * @param allocator Pointer to allocator, or NULL to use the default allocator
+ * @param[out] out_cells On success, receives the created row cells handle
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation
+ * failure
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_row_cells_new(
+ const GhosttyAllocator* allocator,
+ GhosttyRenderStateRowCells* out_cells);
+
+/**
+ * Queryable data kinds for ghostty_render_state_row_cells_get().
+ *
+ * @ingroup render
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Invalid / sentinel value. */
+ GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_INVALID = 0,
+
+ /** The raw cell value (GhosttyCell). */
+ GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_RAW = 1,
+
+ /** The style for the current cell (GhosttyStyle). */
+ GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_STYLE = 2,
+
+ /** The total number of grapheme codepoints including the base codepoint
+ * (uint32_t). Returns 0 if the cell has no text. */
+ GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_LEN = 3,
+
+ /** Write grapheme codepoints into a caller-provided buffer (uint32_t*).
+ * The buffer must be at least graphemes_len elements. The base codepoint
+ * is written first, followed by any extra codepoints. */
+ GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF = 4,
+
+ /** The resolved background color of the cell (GhosttyColorRgb).
+ * Flattens the three possible sources: content-tag bg_color_rgb,
+ * content-tag bg_color_palette (looked up in the palette), or the
+ * style's bg_color. Returns GHOSTTY_INVALID_VALUE if the cell has
+ * no background color, in which case the caller should use whatever
+ * default background color it wants (e.g. the terminal background). */
+ GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_BG_COLOR = 5,
+
+ /** The resolved foreground color of the cell (GhosttyColorRgb).
+ * Resolves palette indices through the palette. Bold color handling
+ * is not applied; the caller should handle bold styling separately.
+ * Returns GHOSTTY_INVALID_VALUE if the cell has no explicit foreground
+ * color, in which case the caller should use whatever default foreground
+ * color it wants (e.g. the terminal foreground). */
+ GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_FG_COLOR = 6,
+
+ /** Whether the cell is contained within the current selection (bool).
+ * This returns true when the cell's column is within the current row's
+ * row-local selection range, and false otherwise. Rendering policy for
+ * selected cells (colors, inversion, etc.) is left to the caller.
+ *
+ * Renderers that can draw cells in spans may be more efficient querying
+ * GHOSTTY_RENDER_STATE_ROW_DATA_SELECTION once per row and applying that
+ * range directly, avoiding one C API call per cell for selection state. */
+ GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_SELECTED = 7,
+
+ /** Whether the cell has any explicit styling (bool).
+ * This is equivalent to querying the raw cell's
+ * GHOSTTY_CELL_DATA_HAS_STYLING value, but avoids materializing the raw
+ * GhosttyCell for renderers that only need to know whether fetching the
+ * full style is necessary. */
+ GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_HAS_STYLING = 8,
+
+ /**
+ * Encode the current cell's full grapheme cluster as UTF-8 into a
+ * caller-provided buffer (GhosttyBuffer).
+ *
+ * The base codepoint is encoded first, followed by any extra grapheme
+ * codepoints. Returns GHOSTTY_SUCCESS with len=0 when the cell has no text.
+ *
+ * If ptr is NULL or cap is too small for a non-empty cell, returns
+ * GHOSTTY_OUT_OF_SPACE without writing any bytes and sets len to the required
+ * buffer size in bytes.
+ */
+ GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_UTF8 = 9,
+ GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyRenderStateRowCellsData;
+
+/**
+ * Move a render-state row cells iterator to the next cell.
+ *
+ * Returns true if the iterator moved successfully and cell data is
+ * available to read at the new position.
+ *
+ * @param cells The row cells handle to advance (may be NULL)
+ * @return true if advanced to the next cell, false if `cells` is
+ * NULL or if the iterator has reached the end
+ *
+ * @ingroup render
+ */
+GHOSTTY_API bool ghostty_render_state_row_cells_next(GhosttyRenderStateRowCells cells);
+
+/**
+ * Move a render-state row cells iterator to a specific column.
+ *
+ * Positions the iterator at the given x (column) index so that
+ * subsequent reads return data for that cell.
+ *
+ * @param cells The row cells handle to reposition (NULL returns
+ * GHOSTTY_INVALID_VALUE)
+ * @param x The zero-based column index to select
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `cells`
+ * is NULL or `x` is out of range
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_row_cells_select(
+ GhosttyRenderStateRowCells cells, uint16_t x);
+
+/**
+ * Get a value from the current cell in a render-state row cells iterator.
+ *
+ * The `out` pointer must point to a value of the type corresponding to the
+ * requested data kind (see GhosttyRenderStateRowCellsData).
+ * Call ghostty_render_state_row_cells_next() or
+ * ghostty_render_state_row_cells_select() at least once before
+ * calling this function.
+ *
+ * @param cells The row cells handle to query (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param data The data kind to query
+ * @param[out] out Pointer to receive the queried value
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if
+ * `cells` is NULL or the iterator is not positioned on a cell
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_row_cells_get(
+ GhosttyRenderStateRowCells cells,
+ GhosttyRenderStateRowCellsData data,
+ void* out);
+
+/**
+ * Get multiple data fields from the current cell in a single call.
+ *
+ * Each element in the keys array specifies a data kind, and the
+ * corresponding element in the values array receives the result.
+ *
+ * Processing stops at the first error; on success out_written
+ * is set to count, on error it is set to the index of the
+ * failing key (i.e. the number of values successfully written).
+ *
+ * @param cells The row cells handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param count Number of key/value pairs
+ * @param keys Array of data kinds to query
+ * @param values Array of output pointers (types must match each key's
+ * documented output type)
+ * @param[out] out_written On return, receives the number of values
+ * successfully written (may be NULL)
+ * @return GHOSTTY_SUCCESS if all queries succeed
+ *
+ * @ingroup render
+ */
+GHOSTTY_API GhosttyResult ghostty_render_state_row_cells_get_multi(
+ GhosttyRenderStateRowCells cells,
+ size_t count,
+ const GhosttyRenderStateRowCellsData* keys,
+ void** values,
+ size_t* out_written);
+
+/**
+ * Free a row cells instance.
+ *
+ * @param cells The row cells handle to free (may be NULL)
+ *
+ * @ingroup render
+ */
+GHOSTTY_API void ghostty_render_state_row_cells_free(GhosttyRenderStateRowCells cells);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_RENDER_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/screen.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/screen.h
new file mode 100644
index 00000000000..9f639b58313
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/screen.h
@@ -0,0 +1,400 @@
+/**
+ * @file screen.h
+ *
+ * Terminal screen cell and row types.
+ */
+
+#ifndef GHOSTTY_VT_SCREEN_H
+#define GHOSTTY_VT_SCREEN_H
+
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @defgroup screen Screen
+ *
+ * Terminal screen cell and row types.
+ *
+ * These types represent the contents of a terminal screen. A GhosttyCell
+ * is a single grid cell and a GhosttyRow is a single row. Both are opaque
+ * values whose fields are accessed via ghostty_cell_get() and
+ * ghostty_row_get() respectively.
+ *
+ * @{
+ */
+
+/**
+ * Opaque cell value.
+ *
+ * Represents a single terminal cell. The internal layout is opaque and
+ * must be queried via ghostty_cell_get(). Obtain cell values from
+ * terminal query APIs.
+ *
+ * @ingroup screen
+ */
+typedef uint64_t GhosttyCell;
+
+/**
+ * Opaque row value.
+ *
+ * Represents a single terminal row. The internal layout is opaque and
+ * must be queried via ghostty_row_get(). Obtain row values from
+ * terminal query APIs.
+ *
+ * @ingroup screen
+ */
+typedef uint64_t GhosttyRow;
+
+/**
+ * Cell content tag.
+ *
+ * Describes what kind of content a cell holds.
+ *
+ * @ingroup screen
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** A single codepoint (may be zero for empty). */
+ GHOSTTY_CELL_CONTENT_CODEPOINT = 0,
+
+ /** A codepoint that is part of a multi-codepoint grapheme cluster. */
+ GHOSTTY_CELL_CONTENT_CODEPOINT_GRAPHEME = 1,
+
+ /** No text; background color from palette. */
+ GHOSTTY_CELL_CONTENT_BG_COLOR_PALETTE = 2,
+
+ /** No text; background color as RGB. */
+ GHOSTTY_CELL_CONTENT_BG_COLOR_RGB = 3,
+ GHOSTTY_CELL_CONTENT_TAG_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyCellContentTag;
+
+/**
+ * Cell wide property.
+ *
+ * Describes the width behavior of a cell.
+ *
+ * @ingroup screen
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Not a wide character, cell width 1. */
+ GHOSTTY_CELL_WIDE_NARROW = 0,
+
+ /** Wide character, cell width 2. */
+ GHOSTTY_CELL_WIDE_WIDE = 1,
+
+ /** Spacer after wide character. Do not render. */
+ GHOSTTY_CELL_WIDE_SPACER_TAIL = 2,
+
+ /** Spacer at end of soft-wrapped line for a wide character. */
+ GHOSTTY_CELL_WIDE_SPACER_HEAD = 3,
+ GHOSTTY_CELL_WIDE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyCellWide;
+
+/**
+ * Semantic content type of a cell.
+ *
+ * Set by semantic prompt sequences (OSC 133) to distinguish between
+ * command output, user input, and shell prompt text.
+ *
+ * @ingroup screen
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Regular output content, such as command output. */
+ GHOSTTY_CELL_SEMANTIC_OUTPUT = 0,
+
+ /** Content that is part of user input. */
+ GHOSTTY_CELL_SEMANTIC_INPUT = 1,
+
+ /** Content that is part of a shell prompt. */
+ GHOSTTY_CELL_SEMANTIC_PROMPT = 2,
+ GHOSTTY_CELL_SEMANTIC_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyCellSemanticContent;
+
+/**
+ * Cell data types.
+ *
+ * These values specify what type of data to extract from a cell
+ * using `ghostty_cell_get`.
+ *
+ * @ingroup screen
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Invalid data type. Never results in any data extraction. */
+ GHOSTTY_CELL_DATA_INVALID = 0,
+
+ /**
+ * The codepoint of the cell (0 if empty or bg-color-only).
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_CELL_DATA_CODEPOINT = 1,
+
+ /**
+ * The content tag describing what kind of content is in the cell.
+ *
+ * Output type: GhosttyCellContentTag *
+ */
+ GHOSTTY_CELL_DATA_CONTENT_TAG = 2,
+
+ /**
+ * The wide property of the cell.
+ *
+ * Output type: GhosttyCellWide *
+ */
+ GHOSTTY_CELL_DATA_WIDE = 3,
+
+ /**
+ * Whether the cell has text to render.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_CELL_DATA_HAS_TEXT = 4,
+
+ /**
+ * Whether the cell has non-default styling.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_CELL_DATA_HAS_STYLING = 5,
+
+ /**
+ * The style ID for the cell (for use with style lookups).
+ *
+ * Output type: uint16_t *
+ */
+ GHOSTTY_CELL_DATA_STYLE_ID = 6,
+
+ /**
+ * Whether the cell has a hyperlink.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_CELL_DATA_HAS_HYPERLINK = 7,
+
+ /**
+ * Whether the cell is protected.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_CELL_DATA_PROTECTED = 8,
+
+ /**
+ * The semantic content type of the cell (from OSC 133).
+ *
+ * Output type: GhosttyCellSemanticContent *
+ */
+ GHOSTTY_CELL_DATA_SEMANTIC_CONTENT = 9,
+
+ /**
+ * The palette index for the cell's background color.
+ * Only valid when content_tag is GHOSTTY_CELL_CONTENT_BG_COLOR_PALETTE.
+ *
+ * Output type: GhosttyColorPaletteIndex *
+ */
+ GHOSTTY_CELL_DATA_COLOR_PALETTE = 10,
+
+ /**
+ * The RGB value for the cell's background color.
+ * Only valid when content_tag is GHOSTTY_CELL_CONTENT_BG_COLOR_RGB.
+ *
+ * Output type: GhosttyColorRgb *
+ */
+ GHOSTTY_CELL_DATA_COLOR_RGB = 11,
+ GHOSTTY_CELL_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyCellData;
+
+/**
+ * Row semantic prompt state.
+ *
+ * Indicates whether any cells in a row are part of a shell prompt,
+ * as reported by OSC 133 sequences.
+ *
+ * @ingroup screen
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** No prompt cells in this row. */
+ GHOSTTY_ROW_SEMANTIC_NONE = 0,
+
+ /** Prompt cells exist and this is a primary prompt line. */
+ GHOSTTY_ROW_SEMANTIC_PROMPT = 1,
+
+ /** Prompt cells exist and this is a continuation line. */
+ GHOSTTY_ROW_SEMANTIC_PROMPT_CONTINUATION = 2,
+ GHOSTTY_ROW_SEMANTIC_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyRowSemanticPrompt;
+
+/**
+ * Row data types.
+ *
+ * These values specify what type of data to extract from a row
+ * using `ghostty_row_get`.
+ *
+ * @ingroup screen
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Invalid data type. Never results in any data extraction. */
+ GHOSTTY_ROW_DATA_INVALID = 0,
+
+ /**
+ * Whether this row is soft-wrapped.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_ROW_DATA_WRAP = 1,
+
+ /**
+ * Whether this row is a continuation of a soft-wrapped row.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_ROW_DATA_WRAP_CONTINUATION = 2,
+
+ /**
+ * Whether any cells in this row have grapheme clusters.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_ROW_DATA_GRAPHEME = 3,
+
+ /**
+ * Whether any cells in this row have styling (may have false positives).
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_ROW_DATA_STYLED = 4,
+
+ /**
+ * Whether any cells in this row have hyperlinks (may have false positives).
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_ROW_DATA_HYPERLINK = 5,
+
+ /**
+ * The semantic prompt state of this row.
+ *
+ * Output type: GhosttyRowSemanticPrompt *
+ */
+ GHOSTTY_ROW_DATA_SEMANTIC_PROMPT = 6,
+
+ /**
+ * Whether this row contains a Kitty virtual placeholder.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_ROW_DATA_KITTY_VIRTUAL_PLACEHOLDER = 7,
+
+ /**
+ * Whether this row is dirty and requires a redraw.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_ROW_DATA_DIRTY = 8,
+ GHOSTTY_ROW_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyRowData;
+
+/**
+ * Get data from a cell.
+ *
+ * Extracts typed data from the given cell based on the specified
+ * data type. The output pointer must be of the appropriate type for the
+ * requested data kind. Valid data types and output types are documented
+ * in the `GhosttyCellData` enum.
+ *
+ * @param cell The cell value
+ * @param data The type of data to extract
+ * @param out Pointer to store the extracted data (type depends on data parameter)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the
+ * data type is invalid
+ *
+ * @ingroup screen
+ */
+GHOSTTY_API GhosttyResult ghostty_cell_get(GhosttyCell cell,
+ GhosttyCellData data,
+ void *out);
+
+/**
+ * Get multiple data fields from a cell in a single call.
+ *
+ * Each element in the keys array specifies a data kind, and the
+ * corresponding element in the values array receives the result.
+ *
+ * Processing stops at the first error; on success out_written
+ * is set to count, on error it is set to the index of the
+ * failing key (i.e. the number of values successfully written).
+ *
+ * @param cell The cell value
+ * @param count Number of key/value pairs
+ * @param keys Array of data kinds to query
+ * @param values Array of output pointers (types must match each key's
+ * documented output type)
+ * @param[out] out_written On return, receives the number of values
+ * successfully written (may be NULL)
+ * @return GHOSTTY_SUCCESS if all queries succeed
+ *
+ * @ingroup screen
+ */
+GHOSTTY_API GhosttyResult ghostty_cell_get_multi(GhosttyCell cell,
+ size_t count,
+ const GhosttyCellData* keys,
+ void** values,
+ size_t* out_written);
+
+/**
+ * Get data from a row.
+ *
+ * Extracts typed data from the given row based on the specified
+ * data type. The output pointer must be of the appropriate type for the
+ * requested data kind. Valid data types and output types are documented
+ * in the `GhosttyRowData` enum.
+ *
+ * @param row The row value
+ * @param data The type of data to extract
+ * @param out Pointer to store the extracted data (type depends on data parameter)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the
+ * data type is invalid
+ *
+ * @ingroup screen
+ */
+GHOSTTY_API GhosttyResult ghostty_row_get(GhosttyRow row,
+ GhosttyRowData data,
+ void *out);
+
+/**
+ * Get multiple data fields from a row in a single call.
+ *
+ * Each element in the keys array specifies a data kind, and the
+ * corresponding element in the values array receives the result.
+ *
+ * Processing stops at the first error; on success out_written
+ * is set to count, on error it is set to the index of the
+ * failing key (i.e. the number of values successfully written).
+ *
+ * @param row The row value
+ * @param count Number of key/value pairs
+ * @param keys Array of data kinds to query
+ * @param values Array of output pointers (types must match each key's
+ * documented output type)
+ * @param[out] out_written On return, receives the number of values
+ * successfully written (may be NULL)
+ * @return GHOSTTY_SUCCESS if all queries succeed
+ *
+ * @ingroup screen
+ */
+GHOSTTY_API GhosttyResult ghostty_row_get_multi(GhosttyRow row,
+ size_t count,
+ const GhosttyRowData* keys,
+ void** values,
+ size_t* out_written);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_SCREEN_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/selection.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/selection.h
new file mode 100644
index 00000000000..3b926aab628
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/selection.h
@@ -0,0 +1,1061 @@
+/**
+ * @file selection.h
+ *
+ * Selection range type for specifying a region of terminal content.
+ */
+
+#ifndef GHOSTTY_VT_SELECTION_H
+#define GHOSTTY_VT_SELECTION_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @defgroup selection Selection
+ *
+ * A snapshot selection range defined by two grid references that identifies
+ * a contiguous or rectangular region of terminal content.
+ *
+ * The start and end values are GhosttyGridRef values. They are therefore
+ * untracked grid references and inherit the same lifetime rules: they are
+ * only safe to use until the next mutating operation on the terminal that
+ * produced them, including freeing the terminal. To keep a selection valid
+ * across terminal mutations, callers must maintain tracked grid references
+ * for the endpoints and reconstruct a GhosttySelection from fresh snapshots
+ * when needed.
+ *
+ * Selection gestures provide a reusable state machine for turning UI pointer
+ * interactions into selection snapshots. A caller creates one
+ * GhosttySelectionGesture per active gesture stream, reuses typed
+ * GhosttySelectionGestureEvent objects for synthetic press, drag, release,
+ * autoscroll tick, and deep-press events, and applies each event with
+ * ghostty_selection_gesture_event(). The returned GhosttySelection is a
+ * snapshot; the embedder decides whether to render it, format/copy it, or
+ * install it as the terminal's active selection.
+ *
+ * ## Examples
+ *
+ * @snippet c-vt-selection/src/main.c selection-main
+ * @snippet c-vt-selection-gesture/src/main.c selection-gesture-main
+ *
+ * @{
+ */
+
+/**
+ * Opaque handle to state for interpreting terminal selection gestures.
+ *
+ * The gesture owns only the state required to interpret pointer events. Calls
+ * that use a gesture are not concurrency-safe and must be serialized with
+ * terminal mutations.
+ *
+ * @ingroup selection
+ */
+typedef struct GhosttySelectionGestureImpl* GhosttySelectionGesture;
+
+/**
+ * Opaque handle to reusable input data for selection gesture operations.
+ *
+ * Event options are set with ghostty_selection_gesture_event_set(). Individual
+ * gesture operations document which options are required or optional.
+ *
+ * @ingroup selection
+ */
+typedef struct GhosttySelectionGestureEventImpl* GhosttySelectionGestureEvent;
+
+/**
+ * A snapshot selection range defined by two grid references.
+ *
+ * Both endpoints are inclusive. The endpoints preserve selection direction
+ * and may be reversed; callers must not assume that start is the top-left
+ * endpoint or that end is the bottom-right endpoint.
+ *
+ * When rectangle is false, the endpoints describe a linear selection. When
+ * rectangle is true, the same endpoints are interpreted as opposite corners
+ * of a rectangular/block selection.
+ *
+ * The start and end values are untracked GhosttyGridRef snapshots and are
+ * only valid until the next mutating operation on the terminal that produced
+ * them unless the selection is reconstructed from tracked references.
+ *
+ * This is a sized struct. Use GHOSTTY_INIT_SIZED() to initialize it.
+ *
+ * @ingroup selection
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttySelection). */
+ size_t size;
+
+ /**
+ * Start of the selection range (inclusive).
+ *
+ * This may be after end in terminal order. It is an untracked
+ * GhosttyGridRef snapshot and follows untracked grid-ref lifetime rules.
+ */
+ GhosttyGridRef start;
+
+ /**
+ * End of the selection range (inclusive).
+ *
+ * This may be before start in terminal order. It is an untracked
+ * GhosttyGridRef snapshot and follows untracked grid-ref lifetime rules.
+ */
+ GhosttyGridRef end;
+
+ /**
+ * Whether the endpoints are interpreted as a rectangular/block selection
+ * rather than a linear selection.
+ */
+ bool rectangle;
+} GhosttySelection;
+
+/**
+ * Options for deriving a word selection from a terminal grid reference.
+ *
+ * This is a sized struct. Use GHOSTTY_INIT_SIZED() to initialize it.
+ * If boundary_codepoints is NULL and boundary_codepoints_len is 0, Ghostty's
+ * default word-boundary codepoints are used. If boundary_codepoints_len is
+ * non-zero, boundary_codepoints must not be NULL.
+ *
+ * @ingroup selection
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttyTerminalSelectWordOptions). */
+ size_t size;
+
+ /** Grid reference under which to derive the word selection. */
+ GhosttyGridRef ref;
+
+ /** Optional word-boundary codepoints as uint32_t scalar values. */
+ const uint32_t* boundary_codepoints;
+
+ /** Number of entries in boundary_codepoints. */
+ size_t boundary_codepoints_len;
+} GhosttyTerminalSelectWordOptions;
+
+/**
+ * Options for deriving the nearest word selection between two grid references.
+ *
+ * This is a sized struct. Use GHOSTTY_INIT_SIZED() to initialize it.
+ * If boundary_codepoints is NULL and boundary_codepoints_len is 0, Ghostty's
+ * default word-boundary codepoints are used. If boundary_codepoints_len is
+ * non-zero, boundary_codepoints must not be NULL.
+ *
+ * @ingroup selection
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttyTerminalSelectWordBetweenOptions). */
+ size_t size;
+
+ /** Starting grid reference for the inclusive search range. */
+ GhosttyGridRef start;
+
+ /** Ending grid reference for the inclusive search range. */
+ GhosttyGridRef end;
+
+ /** Optional word-boundary codepoints as uint32_t scalar values. */
+ const uint32_t* boundary_codepoints;
+
+ /** Number of entries in boundary_codepoints. */
+ size_t boundary_codepoints_len;
+} GhosttyTerminalSelectWordBetweenOptions;
+
+/**
+ * Options for deriving a line selection from a terminal grid reference.
+ *
+ * This is a sized struct. Use GHOSTTY_INIT_SIZED() to initialize it.
+ * If whitespace is NULL and whitespace_len is 0, Ghostty's default line-trim
+ * whitespace codepoints are used. If whitespace_len is non-zero, whitespace
+ * must not be NULL.
+ *
+ * @ingroup selection
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttyTerminalSelectLineOptions). */
+ size_t size;
+
+ /** Grid reference under which to derive the line selection. */
+ GhosttyGridRef ref;
+
+ /** Optional codepoints to trim from the start and end of the line. */
+ const uint32_t* whitespace;
+
+ /** Number of entries in whitespace. */
+ size_t whitespace_len;
+
+ /** Whether semantic prompt state changes should bound the line selection. */
+ bool semantic_prompt_boundary;
+} GhosttyTerminalSelectLineOptions;
+
+/**
+ * Options for one-shot formatting of a terminal selection.
+ *
+ * This is a sized struct. Use GHOSTTY_INIT_SIZED() to initialize it.
+ *
+ * If selection is NULL, the terminal's current active selection is used.
+ * If selection is non-NULL, that caller-provided snapshot selection is used.
+ *
+ * The selection is formatted from the terminal's active screen using the same
+ * formatting semantics as GhosttyFormatter. For copy/clipboard behavior
+ * matching Ghostty's Screen.selectionString(), use plain output with unwrap
+ * and trim both set to true.
+ *
+ * @ingroup selection
+ */
+typedef struct {
+ /** Size of this struct in bytes. Must be set to sizeof(GhosttyTerminalSelectionFormatOptions). */
+ size_t size;
+
+ /** Output format to emit. */
+ GhosttyFormatterFormat emit;
+
+ /** Whether to unwrap soft-wrapped lines. */
+ bool unwrap;
+
+ /** Whether to trim trailing whitespace on non-blank lines. */
+ bool trim;
+
+ /**
+ * Optional selection to format.
+ *
+ * If NULL, the terminal's current active selection is used. If the terminal
+ * has no active selection, formatting returns GHOSTTY_NO_VALUE.
+ *
+ * If non-NULL, the pointed-to selection must be a valid snapshot selection
+ * for this terminal and must obey GhosttySelection lifetime rules.
+ */
+ const GhosttySelection *selection;
+} GhosttyTerminalSelectionFormatOptions;
+
+/**
+ * Ordering of a selection's endpoints in terminal coordinates.
+ *
+ * Mirrored orders are only produced by rectangular selections whose start
+ * and end endpoints are on opposite diagonal corners that are not simple
+ * top-left-to-bottom-right or bottom-right-to-top-left orderings.
+ *
+ * @ingroup selection
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Start is before end in top-left to bottom-right order. */
+ GHOSTTY_SELECTION_ORDER_FORWARD = 0,
+
+ /** End is before start in top-left to bottom-right order. */
+ GHOSTTY_SELECTION_ORDER_REVERSE = 1,
+
+ /** Rectangular selection from top-right to bottom-left. */
+ GHOSTTY_SELECTION_ORDER_MIRRORED_FORWARD = 2,
+
+ /** Rectangular selection from bottom-left to top-right. */
+ GHOSTTY_SELECTION_ORDER_MIRRORED_REVERSE = 3,
+
+ GHOSTTY_SELECTION_ORDER_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySelectionOrder;
+
+/**
+ * Operation used to adjust a selection endpoint.
+ *
+ * Adjustment mutates the selection's logical end endpoint, not whichever
+ * endpoint is visually bottom/right. This preserves keyboard and drag
+ * behavior for both forward and reversed selections.
+ *
+ * @ingroup selection
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Move left to the previous non-empty cell, wrapping upward. */
+ GHOSTTY_SELECTION_ADJUST_LEFT = 0,
+
+ /** Move right to the next non-empty cell, wrapping downward. */
+ GHOSTTY_SELECTION_ADJUST_RIGHT = 1,
+
+ /**
+ * Move up one row at the current column, or to the beginning of the
+ * line if already at the top.
+ */
+ GHOSTTY_SELECTION_ADJUST_UP = 2,
+
+ /**
+ * Move down to the next non-blank row at the current column, or to the
+ * end of the line if none exists.
+ */
+ GHOSTTY_SELECTION_ADJUST_DOWN = 3,
+
+ /** Move to the top-left cell of the screen. */
+ GHOSTTY_SELECTION_ADJUST_HOME = 4,
+
+ /** Move to the right edge of the last non-blank row on the screen. */
+ GHOSTTY_SELECTION_ADJUST_END = 5,
+
+ /**
+ * Move up by one terminal page height, or to home if that would move
+ * past the top.
+ */
+ GHOSTTY_SELECTION_ADJUST_PAGE_UP = 6,
+
+ /**
+ * Move down by one terminal page height, or to end if that would move
+ * past the bottom.
+ */
+ GHOSTTY_SELECTION_ADJUST_PAGE_DOWN = 7,
+
+ /** Move to the left edge of the current line. */
+ GHOSTTY_SELECTION_ADJUST_BEGINNING_OF_LINE = 8,
+
+ /** Move to the right edge of the current line. */
+ GHOSTTY_SELECTION_ADJUST_END_OF_LINE = 9,
+
+ GHOSTTY_SELECTION_ADJUST_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySelectionAdjust;
+
+/**
+ * Selection behavior chosen for a gesture's click sequence.
+ *
+ * @ingroup selection
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Cell-granular drag selection. */
+ GHOSTTY_SELECTION_GESTURE_BEHAVIOR_CELL = 0,
+
+ /** Word selection on press and word-granular drag selection. */
+ GHOSTTY_SELECTION_GESTURE_BEHAVIOR_WORD = 1,
+
+ /** Line selection on press and line-granular drag selection. */
+ GHOSTTY_SELECTION_GESTURE_BEHAVIOR_LINE = 2,
+
+ /** Semantic command output selection on press and drag. */
+ GHOSTTY_SELECTION_GESTURE_BEHAVIOR_OUTPUT = 3,
+
+ GHOSTTY_SELECTION_GESTURE_BEHAVIOR_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySelectionGestureBehavior;
+
+/**
+ * Selection behaviors for single-, double-, and triple-click gestures.
+ *
+ * @ingroup selection
+ */
+typedef struct {
+ /** Behavior for single-click selection gestures. */
+ GhosttySelectionGestureBehavior single_click;
+
+ /** Behavior for double-click selection gestures. */
+ GhosttySelectionGestureBehavior double_click;
+
+ /** Behavior for triple-click selection gestures. */
+ GhosttySelectionGestureBehavior triple_click;
+} GhosttySelectionGestureBehaviors;
+
+/**
+ * Display geometry used to interpret selection gesture drag events.
+ *
+ * @ingroup selection
+ */
+typedef struct {
+ /** Number of columns in the rendered terminal grid. Must be non-zero. */
+ uint32_t columns;
+
+ /** Width of one terminal cell in surface pixels. Must be non-zero. */
+ uint32_t cell_width;
+
+ /** Left padding before the terminal grid begins in surface pixels. */
+ uint32_t padding_left;
+
+ /** Height of the rendered terminal surface in surface pixels. Must be non-zero. */
+ uint32_t screen_height;
+} GhosttySelectionGestureGeometry;
+
+/**
+ * Current autoscroll direction for an active selection drag gesture.
+ *
+ * @ingroup selection
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** No selection autoscroll is requested. */
+ GHOSTTY_SELECTION_GESTURE_AUTOSCROLL_NONE = 0,
+
+ /** Selection dragging should autoscroll the viewport upward. */
+ GHOSTTY_SELECTION_GESTURE_AUTOSCROLL_UP = 1,
+
+ /** Selection dragging should autoscroll the viewport downward. */
+ GHOSTTY_SELECTION_GESTURE_AUTOSCROLL_DOWN = 2,
+
+ GHOSTTY_SELECTION_GESTURE_AUTOSCROLL_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySelectionGestureAutoscroll;
+
+/**
+ * Data fields readable from a selection gesture with
+ * ghostty_selection_gesture_get().
+ *
+ * @ingroup selection
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Current click count: uint8_t*. 0 means inactive. */
+ GHOSTTY_SELECTION_GESTURE_DATA_CLICK_COUNT = 0,
+
+ /** Whether the current/last left-click gesture has dragged: bool*. */
+ GHOSTTY_SELECTION_GESTURE_DATA_DRAGGED = 1,
+
+ /** Current autoscroll request: GhosttySelectionGestureAutoscroll*. */
+ GHOSTTY_SELECTION_GESTURE_DATA_AUTOSCROLL = 2,
+
+ /** Current gesture behavior: GhosttySelectionGestureBehavior*. */
+ GHOSTTY_SELECTION_GESTURE_DATA_BEHAVIOR = 3,
+
+ /**
+ * Current left-click anchor: GhosttyGridRef*.
+ *
+ * Returns GHOSTTY_NO_VALUE if there is no valid active anchor. On success,
+ * writes an untracked GhosttyGridRef snapshot with normal GhosttyGridRef
+ * lifetime rules.
+ */
+ GHOSTTY_SELECTION_GESTURE_DATA_ANCHOR = 4,
+
+ GHOSTTY_SELECTION_GESTURE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySelectionGestureData;
+
+/**
+ * Selection gesture event type.
+ *
+ * The event type is fixed when the event is created. Each event type documents
+ * which options are valid and which options are required by gesture operations.
+ *
+ * @ingroup selection
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Press event for ghostty_selection_gesture_event(). */
+ GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_PRESS = 0,
+
+ /** Release event for ghostty_selection_gesture_event(). */
+ GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_RELEASE = 1,
+
+ /** Drag event for ghostty_selection_gesture_event(). */
+ GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_DRAG = 2,
+
+ /** Autoscroll tick event for ghostty_selection_gesture_event(). */
+ GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_AUTOSCROLL_TICK = 3,
+
+ /** Deep press event for ghostty_selection_gesture_event(). */
+ GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_DEEP_PRESS = 4,
+
+ GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySelectionGestureEventType;
+
+/**
+ * Options stored on a reusable selection gesture event.
+ *
+ * Passing NULL as the value to ghostty_selection_gesture_event_set() clears the
+ * corresponding option.
+ *
+ * @ingroup selection
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /**
+ * Grid reference under the pointer: GhosttyGridRef*.
+ *
+ * Required for PRESS and DRAG events. Optional for RELEASE events; when unset
+ * or cleared, release records that the pointer did not map to a valid cell.
+ */
+ GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REF = 0,
+
+ /**
+ * Surface-space pointer position: GhosttySurfacePosition*.
+ *
+ * Valid for PRESS, DRAG, and AUTOSCROLL_TICK.
+ */
+ GHOSTTY_SELECTION_GESTURE_EVENT_OPT_POSITION = 1,
+
+ /** Maximum repeat-click distance in pixels: double*. */
+ GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REPEAT_DISTANCE = 2,
+
+ /**
+ * Optional monotonic event time in nanoseconds: uint64_t*.
+ *
+ * If unset, press treats the event as untimed and only single-click behavior
+ * is available.
+ */
+ GHOSTTY_SELECTION_GESTURE_EVENT_OPT_TIME_NS = 3,
+
+ /** Maximum interval between repeat clicks in nanoseconds: uint64_t*. */
+ GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REPEAT_INTERVAL_NS = 4,
+
+ /**
+ * Word-boundary codepoints: GhosttyCodepoints*.
+ *
+ * The codepoints are copied into event-owned storage when set. If unset,
+ * operations that need word boundaries use Ghostty's defaults.
+ *
+ * Valid for PRESS, DRAG, AUTOSCROLL_TICK, and DEEP_PRESS.
+ */
+ GHOSTTY_SELECTION_GESTURE_EVENT_OPT_WORD_BOUNDARY_CODEPOINTS = 5,
+
+ /**
+ * Selection behavior table: GhosttySelectionGestureBehaviors*.
+ *
+ * If unset, press uses the default behavior table: cell, word, line.
+ */
+ GHOSTTY_SELECTION_GESTURE_EVENT_OPT_BEHAVIORS = 6,
+
+ /** Whether a drag or autoscroll tick should produce a rectangular selection: bool*. */
+ GHOSTTY_SELECTION_GESTURE_EVENT_OPT_RECTANGLE = 7,
+
+ /** Drag display geometry: GhosttySelectionGestureGeometry*. Required for DRAG and AUTOSCROLL_TICK. */
+ GHOSTTY_SELECTION_GESTURE_EVENT_OPT_GEOMETRY = 8,
+
+ /** Viewport coordinate for an autoscroll tick: GhosttyPointCoordinate*. Required for AUTOSCROLL_TICK. */
+ GHOSTTY_SELECTION_GESTURE_EVENT_OPT_VIEWPORT = 9,
+
+ GHOSTTY_SELECTION_GESTURE_EVENT_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySelectionGestureEventOption;
+
+/**
+ * Create a reusable selection gesture event object.
+ *
+ * @param allocator Allocator, or NULL for the default allocator
+ * @param out_event Receives the created event handle
+ * @param type Event type. This is fixed for the lifetime of the event.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if out_event is
+ * NULL or type is invalid, or GHOSTTY_OUT_OF_MEMORY if allocation fails
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_selection_gesture_event_new(
+ const GhosttyAllocator* allocator,
+ GhosttySelectionGestureEvent* out_event,
+ GhosttySelectionGestureEventType type);
+
+/**
+ * Free a selection gesture event object.
+ *
+ * Passing NULL is allowed and is a no-op.
+ *
+ * @param event Selection gesture event handle to free
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API void ghostty_selection_gesture_event_free(
+ GhosttySelectionGestureEvent event);
+
+/**
+ * Set or clear an option on a selection gesture event.
+ *
+ * The value type depends on option and is documented by
+ * GhosttySelectionGestureEventOption. Passing NULL for value clears the option.
+ *
+ * @param event Selection gesture event handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param option Event option to set or clear
+ * @param value Pointer to the input value for option, or NULL to clear
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY if copying
+ * event-owned data fails, or GHOSTTY_INVALID_VALUE if event, option, or
+ * value is invalid
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_selection_gesture_event_set(
+ GhosttySelectionGestureEvent event,
+ GhosttySelectionGestureEventOption option,
+ const void* value);
+
+/**
+ * Apply a selection gesture event and return the resulting selection snapshot.
+ *
+ * This dispatches to the gesture operation matching the event's fixed type.
+ * For GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_PRESS, the event must have
+ * GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REF set before calling this function.
+ * All other press options use their initialized defaults when unset or cleared.
+ *
+ * For GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_RELEASE, only
+ * GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REF is valid. It is optional; if unset or
+ * cleared, release records that the pointer did not map to a valid cell. Release
+ * events update gesture state but do not produce a selection, so this function
+ * returns GHOSTTY_NO_VALUE after applying them.
+ *
+ * For GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_DRAG,
+ * GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REF and
+ * GHOSTTY_SELECTION_GESTURE_EVENT_OPT_GEOMETRY are required. Position,
+ * rectangle, and word-boundary codepoints are optional and use initialized
+ * defaults when unset or cleared.
+ *
+ * For GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_AUTOSCROLL_TICK,
+ * GHOSTTY_SELECTION_GESTURE_EVENT_OPT_VIEWPORT and
+ * GHOSTTY_SELECTION_GESTURE_EVENT_OPT_GEOMETRY are required. Position,
+ * rectangle, and word-boundary codepoints are optional and use initialized
+ * defaults when unset or cleared.
+ *
+ * For GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_DEEP_PRESS, only
+ * GHOSTTY_SELECTION_GESTURE_EVENT_OPT_WORD_BOUNDARY_CODEPOINTS is valid. It is
+ * optional and uses initialized defaults when unset or cleared.
+ *
+ * The returned selection is not installed as the terminal's current selection.
+ * It is a snapshot with the same lifetime rules as GhosttySelection.
+ *
+ * @param gesture Selection gesture handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param terminal Terminal used to interpret and update gesture state
+ * @param event Selection gesture event handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param[out] out_selection On success, receives the resulting selection. May
+ * be NULL to apply the event and discard the selection result.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if the event does not
+ * currently produce a selection, GHOSTTY_OUT_OF_MEMORY if tracking
+ * gesture state fails, or GHOSTTY_INVALID_VALUE if gesture, terminal,
+ * event, or required event data is invalid
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_selection_gesture_event(
+ GhosttySelectionGesture gesture,
+ GhosttyTerminal terminal,
+ GhosttySelectionGestureEvent event,
+ GhosttySelection* out_selection);
+
+/**
+ * Create a selection gesture object.
+ *
+ * The gesture stores mutable state for terminal text selection gestures. The
+ * gesture is not bound to a terminal at creation time; terminal-dependent APIs
+ * take the terminal explicitly.
+ *
+ * @param allocator Allocator, or NULL for the default allocator
+ * @param out_gesture Receives the created gesture handle
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if out_gesture is
+ * NULL, or GHOSTTY_OUT_OF_MEMORY if allocation fails
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_selection_gesture_new(
+ const GhosttyAllocator* allocator,
+ GhosttySelectionGesture* out_gesture);
+
+/**
+ * Free a selection gesture object.
+ *
+ * This releases any tracked terminal references owned by the gesture using the
+ * provided terminal, then frees the gesture object. Passing NULL for gesture is
+ * allowed and is a no-op.
+ *
+ * If the terminal is still alive, pass the terminal most recently used with the
+ * gesture so any tracked terminal references can be released correctly. If the
+ * terminal has already been freed, pass NULL for terminal; the terminal's page
+ * storage has already released the underlying tracked references, so the
+ * gesture wrapper can be safely discarded without touching the stale terminal
+ * state.
+ *
+ * @param gesture Selection gesture handle to free
+ * @param terminal Terminal used to release tracked gesture state, or NULL if
+ * the terminal has already been freed
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API void ghostty_selection_gesture_free(
+ GhosttySelectionGesture gesture,
+ GhosttyTerminal terminal);
+
+/**
+ * Reset any active selection gesture state.
+ *
+ * This cancels the active click sequence and releases any tracked terminal
+ * references owned by the gesture without freeing the gesture object.
+ * Passing NULL is allowed and is a no-op.
+ *
+ * @param gesture Selection gesture handle to reset
+ * @param terminal Terminal used to release tracked gesture state
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API void ghostty_selection_gesture_reset(
+ GhosttySelectionGesture gesture,
+ GhosttyTerminal terminal);
+
+/**
+ * Read data from a selection gesture.
+ *
+ * The type of value depends on data and is documented by
+ * GhosttySelectionGestureData. For GHOSTTY_SELECTION_GESTURE_DATA_ANCHOR,
+ * the returned GhosttyGridRef is an untracked snapshot with normal grid-ref
+ * lifetime rules.
+ *
+ * @param gesture Selection gesture handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param terminal Terminal used to validate terminal-backed gesture state
+ * @param data Data field to read
+ * @param value Output pointer whose type depends on data
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if the requested data
+ * has no value, or GHOSTTY_INVALID_VALUE if gesture, terminal, data, or
+ * value is invalid
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_selection_gesture_get(
+ GhosttySelectionGesture gesture,
+ GhosttyTerminal terminal,
+ GhosttySelectionGestureData data,
+ void* value);
+
+/**
+ * Read multiple data fields from a selection gesture in a single call.
+ *
+ * This is an optimization over calling ghostty_selection_gesture_get() multiple
+ * times. Each entry in values must point to storage of the type documented by
+ * the corresponding GhosttySelectionGestureData key.
+ *
+ * If any individual read fails, the function returns that error and writes the
+ * index of the failing key to out_written when out_written is non-NULL. On
+ * success, out_written receives count when non-NULL.
+ *
+ * @param gesture Selection gesture handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param terminal Terminal used to validate terminal-backed gesture state
+ * @param count Number of data fields to read
+ * @param keys Data fields to read (must not be NULL)
+ * @param values Output pointers corresponding to keys (must not be NULL)
+ * @param out_written Optional number of fields read, or failing index on error
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if a requested data
+ * field has no value, or GHOSTTY_INVALID_VALUE if gesture, terminal,
+ * keys, values, or a value pointer is invalid
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_selection_gesture_get_multi(
+ GhosttySelectionGesture gesture,
+ GhosttyTerminal terminal,
+ size_t count,
+ const GhosttySelectionGestureData* keys,
+ void** values,
+ size_t* out_written);
+
+/**
+ * Derive a word selection snapshot from a terminal grid reference.
+ *
+ * The returned selection is not installed as the terminal's current
+ * selection. It is a snapshot with the same lifetime rules as GhosttySelection.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param options Word-selection options
+ * @param[out] out_selection On success, receives the derived selection
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if the valid ref has
+ * no selectable word content, or GHOSTTY_INVALID_VALUE if the
+ * terminal, options, ref, codepoint pointer, or output pointer are
+ * invalid.
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_select_word(
+ GhosttyTerminal terminal,
+ const GhosttyTerminalSelectWordOptions* options,
+ GhosttySelection* out_selection);
+
+/**
+ * Derive the nearest word selection snapshot between two terminal grid refs.
+ *
+ * Starting at options->start, this searches toward options->end (inclusive)
+ * and returns the first selectable word found using Ghostty's word-selection
+ * rules.
+ *
+ * This is useful for implementing double-click-and-drag selection in a UI. If
+ * a user double-clicks one word and drags across spaces or punctuation toward
+ * another word, selecting only the word directly under the current pointer can
+ * flicker or collapse when the pointer is between words. Instead, ask for the
+ * nearest word between the original click and the drag point, ask again in the
+ * reverse direction, and combine the two word bounds into the drag selection.
+ *
+ * @snippet c-vt-selection/src/main.c selection-word-between
+ *
+ * The returned selection is not installed as the terminal's current
+ * selection. It is a snapshot with the same lifetime rules as GhosttySelection.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param options Word-between-selection options
+ * @param[out] out_selection On success, receives the derived selection
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if there is no
+ * selectable word content between the valid refs, or
+ * GHOSTTY_INVALID_VALUE if the terminal, options, refs, codepoint
+ * pointer, or output pointer are invalid.
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_select_word_between(
+ GhosttyTerminal terminal,
+ const GhosttyTerminalSelectWordBetweenOptions* options,
+ GhosttySelection* out_selection);
+
+/**
+ * Derive a line selection snapshot from a terminal grid reference.
+ *
+ * The returned selection is not installed as the terminal's current
+ * selection. It is a snapshot with the same lifetime rules as GhosttySelection.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param options Line-selection options
+ * @param[out] out_selection On success, receives the derived selection
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if the valid ref has
+ * no selectable line content, or GHOSTTY_INVALID_VALUE if the
+ * terminal, options, ref, codepoint pointer, or output pointer are
+ * invalid.
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_select_line(
+ GhosttyTerminal terminal,
+ const GhosttyTerminalSelectLineOptions* options,
+ GhosttySelection* out_selection);
+
+/**
+ * Derive a selection snapshot covering all selectable terminal content.
+ *
+ * The returned selection is not installed as the terminal's current
+ * selection. It is a snapshot with the same lifetime rules as GhosttySelection.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param[out] out_selection On success, receives the derived selection
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if there is no
+ * selectable content, or GHOSTTY_INVALID_VALUE if the terminal or
+ * output pointer is invalid.
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_select_all(
+ GhosttyTerminal terminal,
+ GhosttySelection* out_selection);
+
+/**
+ * Derive a command-output selection snapshot from a terminal grid reference.
+ *
+ * The returned selection is not installed as the terminal's current
+ * selection. It is a snapshot with the same lifetime rules as GhosttySelection.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param ref Grid reference within command output to select
+ * @param[out] out_selection On success, receives the derived selection
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if the valid ref is
+ * not selectable command output, or GHOSTTY_INVALID_VALUE if the
+ * terminal, ref, or output pointer is invalid.
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_select_output(
+ GhosttyTerminal terminal,
+ GhosttyGridRef ref,
+ GhosttySelection* out_selection);
+
+/**
+ * Format a terminal selection into a caller-provided buffer.
+ *
+ * This is a one-shot convenience API for formatting either the terminal's
+ * active selection or a caller-provided GhosttySelection without explicitly
+ * creating a GhosttyFormatter.
+ *
+ * Pass NULL for buf to query the required output size. In that case,
+ * out_written receives the required size and the function returns
+ * GHOSTTY_OUT_OF_SPACE.
+ *
+ * If buf is too small, the function returns GHOSTTY_OUT_OF_SPACE and writes
+ * the required size to out_written. The caller can then retry with a larger
+ * buffer.
+ *
+ * If options.selection is NULL and the terminal has no active selection, the
+ * function returns GHOSTTY_NO_VALUE.
+ *
+ * @param terminal The terminal to read from (must not be NULL)
+ * @param options Selection formatting options
+ * @param buf Output buffer, or NULL to query required size
+ * @param buf_len Length of buf in bytes
+ * @param out_written Number of bytes written, or required size on
+ * GHOSTTY_OUT_OF_SPACE (must not be NULL)
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_selection_format_buf(
+ GhosttyTerminal terminal,
+ GhosttyTerminalSelectionFormatOptions options,
+ uint8_t* buf,
+ size_t buf_len,
+ size_t* out_written);
+
+/**
+ * Format a terminal selection into an allocated buffer.
+ *
+ * This is a one-shot convenience API for formatting either the terminal's
+ * active selection or a caller-provided GhosttySelection without explicitly
+ * creating a GhosttyFormatter.
+ *
+ * The returned buffer is allocated using allocator, or the default allocator
+ * if NULL is passed. The caller owns the returned buffer and must free it with
+ * ghostty_free(), passing the same allocator and returned length.
+ *
+ * The returned bytes are not NUL-terminated. This supports plain text, VT, and
+ * HTML uniformly as byte output.
+ *
+ * If options.selection is NULL and the terminal has no active selection, the
+ * function returns GHOSTTY_NO_VALUE and leaves out_ptr as NULL and out_len as 0.
+ *
+ * @param terminal The terminal to read from (must not be NULL)
+ * @param allocator Allocator used for the returned buffer, or NULL for the default allocator
+ * @param options Selection formatting options
+ * @param out_ptr Receives the allocated output buffer (must not be NULL)
+ * @param out_len Receives the output length in bytes (must not be NULL)
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_selection_format_alloc(
+ GhosttyTerminal terminal,
+ const GhosttyAllocator* allocator,
+ GhosttyTerminalSelectionFormatOptions options,
+ uint8_t** out_ptr,
+ size_t* out_len);
+
+/**
+ * Adjust a selection snapshot using terminal selection semantics.
+ *
+ * This mutates the caller-provided GhosttySelection in place. The logical end
+ * endpoint is always moved, regardless of whether the selection is forward or
+ * reversed visually. The input selection remains a snapshot: after adjustment,
+ * call ghostty_terminal_set() with GHOSTTY_TERMINAL_OPT_SELECTION to install it
+ * as the terminal-owned selection if desired.
+ *
+ * The selection's start and end grid refs must both be valid untracked
+ * snapshots for the given terminal's currently active screen. In practice,
+ * they must come from that terminal and screen, and no mutating terminal call
+ * may have occurred since the refs were produced or reconstructed from
+ * tracked refs. Passing refs from another terminal, another screen, or stale
+ * refs violates this precondition.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param selection Selection snapshot to adjust in place
+ * @param adjustment The adjustment operation to apply
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal,
+ * selection, or adjustment are invalid. Selection reference validity
+ * is a precondition and is not checked.
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_selection_adjust(
+ GhosttyTerminal terminal,
+ GhosttySelection* selection,
+ GhosttySelectionAdjust adjustment);
+
+/**
+ * Get the current endpoint ordering of a selection snapshot.
+ *
+ * The selection's start and end grid refs must both be valid untracked
+ * snapshots for the given terminal's currently active screen. In practice,
+ * they must come from that terminal and screen, and no mutating terminal call
+ * may have occurred since the refs were produced or reconstructed from
+ * tracked refs. Passing refs from another terminal, another screen, or stale
+ * refs violates this precondition.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param selection Selection snapshot to inspect
+ * @param[out] out_order On success, receives the selection order
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal,
+ * selection, or output pointer are invalid. Selection reference
+ * validity is a precondition and is not checked.
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_selection_order(
+ GhosttyTerminal terminal,
+ const GhosttySelection* selection,
+ GhosttySelectionOrder* out_order);
+
+/**
+ * Return a selection snapshot with endpoints ordered as requested.
+ *
+ * Use GHOSTTY_SELECTION_ORDER_FORWARD to get top-left to bottom-right bounds,
+ * and GHOSTTY_SELECTION_ORDER_REVERSE to get bottom-right to top-left bounds.
+ * Mirrored desired orders are accepted but normalized the same as forward.
+ * The output selection is a fresh untracked snapshot and is not installed as
+ * the terminal's current selection.
+ *
+ * The selection's start and end grid refs must both be valid untracked
+ * snapshots for the given terminal's currently active screen. In practice,
+ * they must come from that terminal and screen, and no mutating terminal call
+ * may have occurred since the refs were produced or reconstructed from
+ * tracked refs. Passing refs from another terminal, another screen, or stale
+ * refs violates this precondition.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param selection Selection snapshot to order
+ * @param desired Desired endpoint order
+ * @param[out] out_selection On success, receives the ordered selection
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal,
+ * selection, desired order, or output pointer are invalid. Selection
+ * reference validity is a precondition and is not checked.
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_selection_ordered(
+ GhosttyTerminal terminal,
+ const GhosttySelection* selection,
+ GhosttySelectionOrder desired,
+ GhosttySelection* out_selection);
+
+/**
+ * Test whether a terminal point is inside a selection snapshot.
+ *
+ * This uses the same selection semantics as the terminal, including
+ * rectangular/block selections and linear selections spanning multiple rows.
+ *
+ * The selection's start and end grid refs must both be valid untracked
+ * snapshots for the given terminal's currently active screen. In practice,
+ * they must come from that terminal and screen, and no mutating terminal call
+ * may have occurred since the refs were produced or reconstructed from
+ * tracked refs. Passing refs from another terminal, another screen, or stale
+ * refs violates this precondition.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param selection Selection snapshot to inspect
+ * @param point Point to test for containment
+ * @param[out] out_contains On success, receives whether point is inside selection
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal,
+ * selection, point, or output pointer are invalid. Selection reference
+ * validity is a precondition and is not checked.
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_selection_contains(
+ GhosttyTerminal terminal,
+ const GhosttySelection* selection,
+ GhosttyPoint point,
+ bool* out_contains);
+
+/**
+ * Test whether two selection snapshots are equal.
+ *
+ * Equality uses the terminal's internal selection semantics: both endpoint
+ * pins must match and both selections must have the same rectangular/block
+ * state. This avoids requiring callers to compare raw GhosttyGridRef internals.
+ *
+ * Both selections' start and end grid refs must be valid untracked snapshots
+ * for the given terminal's currently active screen. In practice, they must
+ * come from that terminal and screen, and no mutating terminal call may have
+ * occurred since the refs were produced or reconstructed from tracked refs.
+ * Passing refs from another terminal, another screen, or stale refs violates
+ * this precondition.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param a First selection snapshot to compare
+ * @param b Second selection snapshot to compare
+ * @param[out] out_equal On success, receives whether the selections are equal
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal,
+ * selections, or output pointer are invalid. Selection reference
+ * validity is a precondition and is not checked.
+ *
+ * @ingroup selection
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_selection_equal(
+ GhosttyTerminal terminal,
+ const GhosttySelection* a,
+ const GhosttySelection* b,
+ bool* out_equal);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_SELECTION_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/sgr.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/sgr.h
new file mode 100644
index 00000000000..8eec11dc970
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/sgr.h
@@ -0,0 +1,350 @@
+/**
+ * @file sgr.h
+ *
+ * SGR (Select Graphic Rendition) attribute parsing and handling.
+ */
+
+#ifndef GHOSTTY_VT_SGR_H
+#define GHOSTTY_VT_SGR_H
+
+/** @defgroup sgr SGR Parser
+ *
+ * SGR (Select Graphic Rendition) attribute parser.
+ *
+ * SGR sequences are the syntax used to set styling attributes such as
+ * bold, italic, underline, and colors for text in terminal emulators.
+ * For example, you may be familiar with sequences like `ESC[1;31m`. The
+ * `1;31` is the SGR attribute list.
+ *
+ * The parser processes SGR parameters from CSI sequences (e.g., `ESC[1;31m`)
+ * and returns individual text attributes like bold, italic, colors, etc.
+ * It supports both semicolon (`;`) and colon (`:`) separators, possibly mixed,
+ * and handles various color formats including 8-color, 16-color, 256-color,
+ * X11 named colors, and RGB in multiple formats.
+ *
+ * ## Basic Usage
+ *
+ * 1. Create a parser instance with ghostty_sgr_new()
+ * 2. Set SGR parameters with ghostty_sgr_set_params()
+ * 3. Iterate through attributes using ghostty_sgr_next()
+ * 4. Free the parser with ghostty_sgr_free() when done
+ *
+ * ## Example
+ *
+ * @snippet c-vt-sgr/src/main.c sgr-basic
+ *
+ * @{
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * SGR attribute tags.
+ *
+ * These values identify the type of an SGR attribute in a tagged union.
+ * Use the tag to determine which field in the attribute value union to access.
+ *
+ * @ingroup sgr
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_SGR_ATTR_UNSET = 0,
+ GHOSTTY_SGR_ATTR_UNKNOWN = 1,
+ GHOSTTY_SGR_ATTR_BOLD = 2,
+ GHOSTTY_SGR_ATTR_RESET_BOLD = 3,
+ GHOSTTY_SGR_ATTR_ITALIC = 4,
+ GHOSTTY_SGR_ATTR_RESET_ITALIC = 5,
+ GHOSTTY_SGR_ATTR_FAINT = 6,
+ GHOSTTY_SGR_ATTR_UNDERLINE = 7,
+ GHOSTTY_SGR_ATTR_UNDERLINE_COLOR = 8,
+ GHOSTTY_SGR_ATTR_UNDERLINE_COLOR_256 = 9,
+ GHOSTTY_SGR_ATTR_RESET_UNDERLINE_COLOR = 10,
+ GHOSTTY_SGR_ATTR_OVERLINE = 11,
+ GHOSTTY_SGR_ATTR_RESET_OVERLINE = 12,
+ GHOSTTY_SGR_ATTR_BLINK = 13,
+ GHOSTTY_SGR_ATTR_RESET_BLINK = 14,
+ GHOSTTY_SGR_ATTR_INVERSE = 15,
+ GHOSTTY_SGR_ATTR_RESET_INVERSE = 16,
+ GHOSTTY_SGR_ATTR_INVISIBLE = 17,
+ GHOSTTY_SGR_ATTR_RESET_INVISIBLE = 18,
+ GHOSTTY_SGR_ATTR_STRIKETHROUGH = 19,
+ GHOSTTY_SGR_ATTR_RESET_STRIKETHROUGH = 20,
+ GHOSTTY_SGR_ATTR_DIRECT_COLOR_FG = 21,
+ GHOSTTY_SGR_ATTR_DIRECT_COLOR_BG = 22,
+ GHOSTTY_SGR_ATTR_BG_8 = 23,
+ GHOSTTY_SGR_ATTR_FG_8 = 24,
+ GHOSTTY_SGR_ATTR_RESET_FG = 25,
+ GHOSTTY_SGR_ATTR_RESET_BG = 26,
+ GHOSTTY_SGR_ATTR_BRIGHT_BG_8 = 27,
+ GHOSTTY_SGR_ATTR_BRIGHT_FG_8 = 28,
+ GHOSTTY_SGR_ATTR_BG_256 = 29,
+ GHOSTTY_SGR_ATTR_FG_256 = 30,
+ GHOSTTY_SGR_ATTR_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySgrAttributeTag;
+
+/**
+ * Underline style types.
+ *
+ * @ingroup sgr
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_SGR_UNDERLINE_NONE = 0,
+ GHOSTTY_SGR_UNDERLINE_SINGLE = 1,
+ GHOSTTY_SGR_UNDERLINE_DOUBLE = 2,
+ GHOSTTY_SGR_UNDERLINE_CURLY = 3,
+ GHOSTTY_SGR_UNDERLINE_DOTTED = 4,
+ GHOSTTY_SGR_UNDERLINE_DASHED = 5,
+ GHOSTTY_SGR_UNDERLINE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySgrUnderline;
+
+/**
+ * Unknown SGR attribute data.
+ *
+ * Contains the full parameter list and the partial list where parsing
+ * encountered an unknown or invalid sequence.
+ *
+ * @ingroup sgr
+ */
+typedef struct {
+ const uint16_t* full_ptr;
+ size_t full_len;
+ const uint16_t* partial_ptr;
+ size_t partial_len;
+} GhosttySgrUnknown;
+
+/**
+ * SGR attribute value union.
+ *
+ * This union contains all possible attribute values. Use the tag field
+ * to determine which union member is active. Attributes without associated
+ * data (like bold, italic) don't use the union value.
+ *
+ * @ingroup sgr
+ */
+typedef union {
+ GhosttySgrUnknown unknown;
+ GhosttySgrUnderline underline;
+ GhosttyColorRgb underline_color;
+ GhosttyColorPaletteIndex underline_color_256;
+ GhosttyColorRgb direct_color_fg;
+ GhosttyColorRgb direct_color_bg;
+ GhosttyColorPaletteIndex bg_8;
+ GhosttyColorPaletteIndex fg_8;
+ GhosttyColorPaletteIndex bright_bg_8;
+ GhosttyColorPaletteIndex bright_fg_8;
+ GhosttyColorPaletteIndex bg_256;
+ GhosttyColorPaletteIndex fg_256;
+ uint64_t _padding[8];
+} GhosttySgrAttributeValue;
+
+/**
+ * SGR attribute (tagged union).
+ *
+ * A complete SGR attribute with both its type tag and associated value.
+ * Always check the tag field to determine which value union member is valid.
+ *
+ * Attributes without associated data (e.g., GHOSTTY_SGR_ATTR_BOLD) can be
+ * identified by tag alone; the value union is not used for these and
+ * the memory in the value field is undefined.
+ *
+ * @ingroup sgr
+ */
+typedef struct {
+ GhosttySgrAttributeTag tag;
+ GhosttySgrAttributeValue value;
+} GhosttySgrAttribute;
+
+/**
+ * Create a new SGR parser instance.
+ *
+ * Creates a new SGR (Select Graphic Rendition) parser using the provided
+ * allocator. The parser must be freed using ghostty_sgr_free() when
+ * no longer needed.
+ *
+ * @param allocator Pointer to the allocator to use for memory management, or
+ * NULL to use the default allocator
+ * @param parser Pointer to store the created parser handle
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup sgr
+ */
+GHOSTTY_API GhosttyResult ghostty_sgr_new(const GhosttyAllocator* allocator,
+ GhosttySgrParser* parser);
+
+/**
+ * Free an SGR parser instance.
+ *
+ * Releases all resources associated with the SGR parser. After this call,
+ * the parser handle becomes invalid and must not be used. This includes
+ * any attributes previously returned by ghostty_sgr_next().
+ *
+ * @param parser The parser handle to free (may be NULL)
+ *
+ * @ingroup sgr
+ */
+GHOSTTY_API void ghostty_sgr_free(GhosttySgrParser parser);
+
+/**
+ * Reset an SGR parser instance to the beginning of the parameter list.
+ *
+ * Resets the parser's iteration state without clearing the parameters.
+ * After calling this, ghostty_sgr_next() will start from the beginning
+ * of the parameter list again.
+ *
+ * @param parser The parser handle to reset, must not be NULL
+ *
+ * @ingroup sgr
+ */
+GHOSTTY_API void ghostty_sgr_reset(GhosttySgrParser parser);
+
+/**
+ * Set SGR parameters for parsing.
+ *
+ * Sets the SGR parameter list to parse. Parameters are the numeric values
+ * from a CSI SGR sequence (e.g., for `ESC[1;31m`, params would be {1, 31}).
+ *
+ * The separators array optionally specifies the separator type for each
+ * parameter position. Each byte should be either ';' for semicolon or ':'
+ * for colon. This is needed for certain color formats that use colon
+ * separators (e.g., `ESC[4:3m` for curly underline). Any invalid separator
+ * values are treated as semicolons. The separators array must have the same
+ * length as the params array, if it is not NULL.
+ *
+ * If separators is NULL, all parameters are assumed to be semicolon-separated.
+ *
+ * This function makes an internal copy of the parameter and separator data,
+ * so the caller can safely free or modify the input arrays after this call.
+ *
+ * After calling this function, the parser is automatically reset and ready
+ * to iterate from the beginning.
+ *
+ * @param parser The parser handle, must not be NULL
+ * @param params Array of SGR parameter values
+ * @param separators Optional array of separator characters (';' or ':'), or
+ * NULL
+ * @param len Number of parameters (and separators if provided)
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup sgr
+ */
+GHOSTTY_API GhosttyResult ghostty_sgr_set_params(GhosttySgrParser parser,
+ const uint16_t* params,
+ const char* separators,
+ size_t len);
+
+/**
+ * Get the next SGR attribute.
+ *
+ * Parses and returns the next attribute from the parameter list.
+ * Call this function repeatedly until it returns false to process
+ * all attributes in the sequence.
+ *
+ * @param parser The parser handle, must not be NULL
+ * @param attr Pointer to store the next attribute
+ * @return true if an attribute was returned, false if no more attributes
+ *
+ * @ingroup sgr
+ */
+GHOSTTY_API bool ghostty_sgr_next(GhosttySgrParser parser, GhosttySgrAttribute* attr);
+
+/**
+ * Get the full parameter list from an unknown SGR attribute.
+ *
+ * This function retrieves the full parameter list that was provided to the
+ * parser when an unknown attribute was encountered. Primarily useful in
+ * WebAssembly environments where accessing struct fields directly is difficult.
+ *
+ * @param unknown The unknown attribute data
+ * @param ptr Pointer to store the pointer to the parameter array (may be NULL)
+ * @return The length of the full parameter array
+ *
+ * @ingroup sgr
+ */
+GHOSTTY_API size_t ghostty_sgr_unknown_full(GhosttySgrUnknown unknown,
+ const uint16_t** ptr);
+
+/**
+ * Get the partial parameter list from an unknown SGR attribute.
+ *
+ * This function retrieves the partial parameter list where parsing stopped
+ * when an unknown attribute was encountered. Primarily useful in WebAssembly
+ * environments where accessing struct fields directly is difficult.
+ *
+ * @param unknown The unknown attribute data
+ * @param ptr Pointer to store the pointer to the parameter array (may be NULL)
+ * @return The length of the partial parameter array
+ *
+ * @ingroup sgr
+ */
+GHOSTTY_API size_t ghostty_sgr_unknown_partial(GhosttySgrUnknown unknown,
+ const uint16_t** ptr);
+
+/**
+ * Get the tag from an SGR attribute.
+ *
+ * This function extracts the tag that identifies which type of attribute
+ * this is. Primarily useful in WebAssembly environments where accessing
+ * struct fields directly is difficult.
+ *
+ * @param attr The SGR attribute
+ * @return The attribute tag
+ *
+ * @ingroup sgr
+ */
+GHOSTTY_API GhosttySgrAttributeTag ghostty_sgr_attribute_tag(GhosttySgrAttribute attr);
+
+/**
+ * Get the value from an SGR attribute.
+ *
+ * This function returns a pointer to the value union from an SGR attribute. Use
+ * the tag to determine which field of the union is valid. Primarily useful in
+ * WebAssembly environments where accessing struct fields directly is difficult.
+ *
+ * @param attr Pointer to the SGR attribute
+ * @return Pointer to the attribute value union
+ *
+ * @ingroup sgr
+ */
+GHOSTTY_API GhosttySgrAttributeValue* ghostty_sgr_attribute_value(
+ GhosttySgrAttribute* attr);
+
+#ifdef __wasm__
+/**
+ * Allocate memory for an SGR attribute (WebAssembly only).
+ *
+ * This is a convenience function for WebAssembly environments to allocate
+ * memory for an SGR attribute structure that can be passed to ghostty_sgr_next.
+ *
+ * @return Pointer to the allocated attribute structure
+ *
+ * @ingroup wasm
+ */
+GHOSTTY_API GhosttySgrAttribute* ghostty_wasm_alloc_sgr_attribute(void);
+
+/**
+ * Free memory for an SGR attribute (WebAssembly only).
+ *
+ * Frees memory allocated by ghostty_wasm_alloc_sgr_attribute.
+ *
+ * @param attr Pointer to the attribute structure to free
+ *
+ * @ingroup wasm
+ */
+GHOSTTY_API void ghostty_wasm_free_sgr_attribute(GhosttySgrAttribute* attr);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
+
+#endif /* GHOSTTY_VT_SGR_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/size_report.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/size_report.h
new file mode 100644
index 00000000000..da33e5e5593
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/size_report.h
@@ -0,0 +1,101 @@
+/**
+ * @file size_report.h
+ *
+ * Size report encoding - encode terminal size reports into escape sequences.
+ */
+
+#ifndef GHOSTTY_VT_SIZE_REPORT_H
+#define GHOSTTY_VT_SIZE_REPORT_H
+
+/** @defgroup size_report Size Report Encoding
+ *
+ * Utilities for encoding terminal size reports into escape sequences,
+ * supporting in-band size reports (mode 2048) and XTWINOPS responses
+ * (CSI 14 t, CSI 16 t, CSI 18 t).
+ *
+ * ## Basic Usage
+ *
+ * Use ghostty_size_report_encode() to encode a size report into a
+ * caller-provided buffer. If the buffer is too small, the function
+ * returns GHOSTTY_OUT_OF_SPACE and sets the required size in the
+ * output parameter.
+ *
+ * ## Example
+ *
+ * @snippet c-vt-size-report/src/main.c size-report-encode
+ *
+ * @{
+ */
+
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Size report style.
+ *
+ * Determines the output format for the terminal size report.
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** In-band size report (mode 2048): ESC [ 48 ; rows ; cols ; height ; width t */
+ GHOSTTY_SIZE_REPORT_MODE_2048 = 0,
+ /** XTWINOPS text area size in pixels: ESC [ 4 ; height ; width t */
+ GHOSTTY_SIZE_REPORT_CSI_14_T = 1,
+ /** XTWINOPS cell size in pixels: ESC [ 6 ; height ; width t */
+ GHOSTTY_SIZE_REPORT_CSI_16_T = 2,
+ /** XTWINOPS text area size in characters: ESC [ 8 ; rows ; cols t */
+ GHOSTTY_SIZE_REPORT_CSI_18_T = 3,
+ GHOSTTY_SIZE_REPORT_STYLE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySizeReportStyle;
+
+/**
+ * Terminal size information for encoding size reports.
+ */
+typedef struct {
+ /** Terminal row count in cells. */
+ uint16_t rows;
+ /** Terminal column count in cells. */
+ uint16_t columns;
+ /** Width of a single terminal cell in pixels. */
+ uint32_t cell_width;
+ /** Height of a single terminal cell in pixels. */
+ uint32_t cell_height;
+} GhosttySizeReportSize;
+
+/**
+ * Encode a terminal size report into an escape sequence.
+ *
+ * Encodes a size report in the format specified by @p style into the
+ * provided buffer.
+ *
+ * If the buffer is too small, the function returns GHOSTTY_OUT_OF_SPACE
+ * and writes the required buffer size to @p out_written. The caller can
+ * then retry with a sufficiently sized buffer.
+ *
+ * @param style The size report format to encode
+ * @param size Terminal size information
+ * @param buf Output buffer to write the encoded sequence into (may be NULL)
+ * @param buf_len Size of the output buffer in bytes
+ * @param[out] out_written On success, the number of bytes written. On
+ * GHOSTTY_OUT_OF_SPACE, the required buffer size.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_SPACE if the buffer
+ * is too small
+ */
+GHOSTTY_API GhosttyResult ghostty_size_report_encode(
+ GhosttySizeReportStyle style,
+ GhosttySizeReportSize size,
+ char* buf,
+ size_t buf_len,
+ size_t* out_written);
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
+
+#endif /* GHOSTTY_VT_SIZE_REPORT_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/style.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/style.h
new file mode 100644
index 00000000000..b6bf860ebe7
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/style.h
@@ -0,0 +1,139 @@
+/**
+ * @file style.h
+ *
+ * Terminal cell style types.
+ */
+
+#ifndef GHOSTTY_VT_STYLE_H
+#define GHOSTTY_VT_STYLE_H
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @defgroup style Style
+ *
+ * Terminal cell style attributes.
+ *
+ * A style describes the visual attributes of a terminal cell, including
+ * foreground, background, and underline colors, as well as flags for
+ * bold, italic, underline, and other text decorations.
+ *
+ * @{
+ */
+
+/**
+ * Style identifier type.
+ *
+ * Used to look up the full style from a grid reference.
+ * Obtain this from a cell via GHOSTTY_CELL_DATA_STYLE_ID.
+ *
+ * @ingroup style
+ */
+typedef uint16_t GhosttyStyleId;
+
+/**
+ * Style color tags.
+ *
+ * These values identify the type of color in a style color.
+ * Use the tag to determine which field in the color value union to access.
+ *
+ * @ingroup style
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_STYLE_COLOR_NONE = 0,
+ GHOSTTY_STYLE_COLOR_PALETTE = 1,
+ GHOSTTY_STYLE_COLOR_RGB = 2,
+ GHOSTTY_STYLE_COLOR_TAG_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+ } GhosttyStyleColorTag;
+
+/**
+ * Style color value union.
+ *
+ * Use the tag to determine which field is active.
+ *
+ * @ingroup style
+ */
+typedef union {
+ GhosttyColorPaletteIndex palette;
+ GhosttyColorRgb rgb;
+ uint64_t _padding;
+} GhosttyStyleColorValue;
+
+/**
+ * Style color (tagged union).
+ *
+ * A color used in a style attribute. Can be unset (none), a palette
+ * index, or a direct RGB value.
+ *
+ * @ingroup style
+ */
+typedef struct {
+ GhosttyStyleColorTag tag;
+ GhosttyStyleColorValue value;
+} GhosttyStyleColor;
+
+/**
+ * Terminal cell style.
+ *
+ * Describes the complete visual style for a terminal cell, including
+ * foreground, background, and underline colors, as well as text
+ * decoration flags. The underline field uses the same values as
+ * GhosttySgrUnderline.
+ *
+ * This is a sized struct. Use GHOSTTY_INIT_SIZED() to initialize it.
+ *
+ * @ingroup style
+ */
+typedef struct {
+ size_t size;
+ GhosttyStyleColor fg_color;
+ GhosttyStyleColor bg_color;
+ GhosttyStyleColor underline_color;
+ bool bold;
+ bool italic;
+ bool faint;
+ bool blink;
+ bool inverse;
+ bool invisible;
+ bool strikethrough;
+ bool overline;
+ int underline; /**< One of GHOSTTY_SGR_UNDERLINE_* values */
+} GhosttyStyle;
+
+/**
+ * Get the default style.
+ *
+ * Initializes the style to the default values (no colors, no flags).
+ *
+ * @param style Pointer to the style to initialize
+ *
+ * @ingroup style
+ */
+GHOSTTY_API void ghostty_style_default(GhosttyStyle* style);
+
+/**
+ * Check if a style is the default style.
+ *
+ * Returns true if all colors are unset and all flags are off.
+ *
+ * @param style Pointer to the style to check
+ * @return true if the style is the default style
+ *
+ * @ingroup style
+ */
+GHOSTTY_API bool ghostty_style_is_default(const GhosttyStyle* style);
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
+
+#endif /* GHOSTTY_VT_STYLE_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/sys.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/sys.h
new file mode 100644
index 00000000000..ae90596927d
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/sys.h
@@ -0,0 +1,210 @@
+/**
+ * @file sys.h
+ *
+ * System interface - runtime-swappable implementations for external dependencies.
+ */
+
+#ifndef GHOSTTY_VT_SYS_H
+#define GHOSTTY_VT_SYS_H
+
+#include
+#include
+#include
+#include
+#include
+
+/** @defgroup sys System Interface
+ *
+ * Runtime-swappable function pointers for operations that depend on
+ * external implementations (e.g. image decoding).
+ *
+ * These are process-global settings that must be configured at startup
+ * before any terminal functionality that depends on them is used.
+ * Setting these enables various optional features of the terminal. For
+ * example, setting a PNG decoder enables PNG image support in the Kitty
+ * Graphics Protocol.
+ *
+ * Use ghostty_sys_set() with a `GhosttySysOption` to install or clear
+ * an implementation. Passing NULL as the value clears the implementation
+ * and disables the corresponding feature.
+ *
+ * ## Example
+ *
+ * ### Defining a PNG decode callback
+ * @snippet c-vt-kitty-graphics/src/main.c kitty-graphics-decode-png
+ *
+ * ### Installing the callback and sending a PNG image
+ * @snippet c-vt-kitty-graphics/src/main.c kitty-graphics-main
+ *
+ * @{
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Result of decoding an image.
+ *
+ * The `data` buffer must be allocated through the allocator provided to
+ * the decode callback. The library takes ownership and will free it
+ * with the same allocator.
+ */
+typedef struct {
+ /** Image width in pixels. */
+ uint32_t width;
+
+ /** Image height in pixels. */
+ uint32_t height;
+
+ /** Pointer to the decoded RGBA pixel data. */
+ uint8_t* data;
+
+ /** Length of the pixel data in bytes. */
+ size_t data_len;
+} GhosttySysImage;
+
+/**
+ * Log severity levels for the log callback.
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ GHOSTTY_SYS_LOG_LEVEL_ERROR = 0,
+ GHOSTTY_SYS_LOG_LEVEL_WARNING = 1,
+ GHOSTTY_SYS_LOG_LEVEL_INFO = 2,
+ GHOSTTY_SYS_LOG_LEVEL_DEBUG = 3,
+ GHOSTTY_SYS_LOG_LEVEL_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySysLogLevel;
+
+/**
+ * Callback type for logging.
+ *
+ * When installed, internal library log messages are delivered through
+ * this callback instead of being discarded. The embedder is responsible
+ * for formatting and routing log output.
+ *
+ * @p scope is the log scope name as UTF-8 bytes (e.g. "osc", "kitty").
+ * When the log is unscoped (default scope), @p scope_len is 0.
+ *
+ * All pointer arguments are only valid for the duration of the callback.
+ * The callback must be safe to call from any thread.
+ *
+ * @param userdata The userdata pointer set via GHOSTTY_SYS_OPT_USERDATA
+ * @param level The severity level of the log message
+ * @param scope Pointer to the scope name bytes
+ * @param scope_len Length of the scope name in bytes
+ * @param message Pointer to the log message bytes
+ * @param message_len Length of the log message in bytes
+ */
+typedef void (*GhosttySysLogFn)(
+ void* userdata,
+ GhosttySysLogLevel level,
+ const uint8_t* scope,
+ size_t scope_len,
+ const uint8_t* message,
+ size_t message_len);
+
+/**
+ * Callback type for PNG decoding.
+ *
+ * Decodes raw PNG data into RGBA pixels. The output pixel data must be
+ * allocated through the provided allocator. The library takes ownership
+ * of the buffer and will free it with the same allocator.
+ *
+ * @param userdata The userdata pointer set via GHOSTTY_SYS_OPT_USERDATA
+ * @param allocator The allocator to use for the output pixel buffer
+ * @param data Pointer to the raw PNG data
+ * @param data_len Length of the raw PNG data in bytes
+ * @param[out] out On success, filled with the decoded image
+ * @return true on success, false on failure
+ */
+typedef bool (*GhosttySysDecodePngFn)(
+ void* userdata,
+ const GhosttyAllocator* allocator,
+ const uint8_t* data,
+ size_t data_len,
+ GhosttySysImage* out);
+
+/**
+ * System option identifiers for ghostty_sys_set().
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /**
+ * Set the userdata pointer passed to all sys callbacks.
+ *
+ * Input type: void* (or NULL)
+ */
+ GHOSTTY_SYS_OPT_USERDATA = 0,
+
+ /**
+ * Set the PNG decode function.
+ *
+ * When set, the terminal can accept PNG images via the Kitty
+ * Graphics Protocol. When cleared (NULL value), PNG decoding is
+ * unsupported and PNG image data will be rejected.
+ *
+ * Input type: GhosttySysDecodePngFn (function pointer, or NULL)
+ */
+ GHOSTTY_SYS_OPT_DECODE_PNG = 1,
+
+ /**
+ * Set the log callback.
+ *
+ * When set, internal library log messages are delivered to this
+ * callback. When cleared (NULL value), log messages are silently
+ * discarded.
+ *
+ * Use ghostty_sys_log_stderr as a convenience callback that
+ * writes formatted messages to stderr.
+ *
+ * Which log levels are emitted depends on the build mode of the
+ * library and is not configurable at runtime. Debug builds emit
+ * all levels (debug and above). Release builds emit info and
+ * above; debug-level messages are compiled out entirely and will
+ * never reach the callback.
+ *
+ * Input type: GhosttySysLogFn (function pointer, or NULL)
+ */
+ GHOSTTY_SYS_OPT_LOG = 2,
+ GHOSTTY_SYS_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttySysOption;
+
+/**
+ * Set a system-level option.
+ *
+ * Configures a process-global implementation function. These should be
+ * set once at startup before using any terminal functionality that
+ * depends on them.
+ *
+ * @param option The option to set
+ * @param value Pointer to the value (type depends on the option),
+ * or NULL to clear it
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the
+ * option is not recognized
+ */
+GHOSTTY_API GhosttyResult ghostty_sys_set(GhosttySysOption option,
+ const void* value);
+
+/**
+ * Built-in log callback that writes to stderr.
+ *
+ * Formats each message as "[level](scope): message\n".
+ * Can be passed directly to ghostty_sys_set():
+ *
+ * @code
+ * ghostty_sys_set(GHOSTTY_SYS_OPT_LOG, &ghostty_sys_log_stderr);
+ * @endcode
+ */
+GHOSTTY_API void ghostty_sys_log_stderr(void* userdata,
+ GhosttySysLogLevel level,
+ const uint8_t* scope,
+ size_t scope_len,
+ const uint8_t* message,
+ size_t message_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
+
+#endif /* GHOSTTY_VT_SYS_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/terminal.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/terminal.h
new file mode 100644
index 00000000000..b22e8aedc89
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/terminal.h
@@ -0,0 +1,1322 @@
+/**
+ * @file terminal.h
+ *
+ * Complete terminal emulator state and rendering.
+ */
+
+#ifndef GHOSTTY_VT_TERMINAL_H
+#define GHOSTTY_VT_TERMINAL_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @defgroup terminal Terminal
+ *
+ * Complete terminal emulator state and rendering.
+ *
+ * A terminal instance manages the full emulator state including the screen,
+ * scrollback, cursor, styles, modes, and VT stream processing.
+ *
+ * Once a terminal session is up and running, you can configure a key encoder
+ * to write keyboard input via ghostty_key_encoder_setopt_from_terminal().
+ *
+ * ### Example: VT stream processing
+ * @snippet c-vt-stream/src/main.c vt-stream-init
+ * @snippet c-vt-stream/src/main.c vt-stream-write
+ *
+ * ## Effects
+ *
+ * By default, the terminal sequence processing with ghostty_terminal_vt_write()
+ * only process sequences that directly affect terminal state and
+ * ignores sequences that have side effect behavior or require responses.
+ * These sequences include things like bell characters, title changes, device
+ * attributes queries, and more. To handle these sequences, the embedder
+ * must configure "effects."
+ *
+ * Effects are callbacks that the terminal invokes in response to VT
+ * sequences processed during ghostty_terminal_vt_write(). They let the
+ * embedding application react to terminal-initiated events such as bell
+ * characters, title changes, device status report responses, and more.
+ *
+ * Each effect is registered with ghostty_terminal_set() using the
+ * corresponding `GhosttyTerminalOption` identifier. A `NULL` value
+ * pointer clears the callback and disables the effect.
+ *
+ * A userdata pointer can be attached via `GHOSTTY_TERMINAL_OPT_USERDATA`
+ * and is passed to every callback, allowing callers to route events
+ * back to their own application state without global variables.
+ * You cannot specify different userdata for different callbacks.
+ *
+ * All callbacks are invoked synchronously during
+ * ghostty_terminal_vt_write(). Callbacks **must not** call
+ * ghostty_terminal_vt_write() on the same terminal (no reentrancy).
+ * And callbacks must be very careful to not block for too long or perform
+ * expensive operations, since they are blocking further IO processing.
+ *
+ * The available effects are:
+ *
+ * | Option | Callback Type | Trigger |
+ * |-----------------------------------------|-----------------------------------|-------------------------------------------|
+ * | `GHOSTTY_TERMINAL_OPT_WRITE_PTY` | `GhosttyTerminalWritePtyFn` | Query responses written back to the pty |
+ * | `GHOSTTY_TERMINAL_OPT_BELL` | `GhosttyTerminalBellFn` | BEL character (0x07) |
+ * | `GHOSTTY_TERMINAL_OPT_TITLE_CHANGED` | `GhosttyTerminalTitleChangedFn` | Title change via OSC 0 / OSC 2 |
+ * | `GHOSTTY_TERMINAL_OPT_PWD_CHANGED` | `GhosttyTerminalPwdChangedFn` | Pwd change via OSC 7 / OSC 9 / OSC 1337 |
+ * | `GHOSTTY_TERMINAL_OPT_ENQUIRY` | `GhosttyTerminalEnquiryFn` | ENQ character (0x05) |
+ * | `GHOSTTY_TERMINAL_OPT_XTVERSION` | `GhosttyTerminalXtversionFn` | XTVERSION query (CSI > q) |
+ * | `GHOSTTY_TERMINAL_OPT_SIZE` | `GhosttyTerminalSizeFn` | XTWINOPS size query (CSI 14/16/18 t) |
+ * | `GHOSTTY_TERMINAL_OPT_COLOR_SCHEME` | `GhosttyTerminalColorSchemeFn` | Color scheme query (CSI ? 996 n) |
+ * | `GHOSTTY_TERMINAL_OPT_DEVICE_ATTRIBUTES`| `GhosttyTerminalDeviceAttributesFn`| Device attributes query (CSI c / > c / = c)|
+ *
+ * ### Defining a write_pty callback
+ * @snippet c-vt-effects/src/main.c effects-write-pty
+ *
+ * ### Defining a bell callback
+ * @snippet c-vt-effects/src/main.c effects-bell
+ *
+ * ### Defining a title_changed callback
+ * @snippet c-vt-effects/src/main.c effects-title-changed
+ *
+ * ### Registering effects and processing VT data
+ * @snippet c-vt-effects/src/main.c effects-register
+ *
+ * ## Color Theme
+ *
+ * The terminal maintains a set of colors used for rendering: a foreground
+ * color, a background color, a cursor color, and a 256-color palette. Each
+ * of these has two layers: a **default** value set by the embedder, and an
+ * **override** value that programs running in the terminal can set via OSC
+ * escape sequences (e.g. OSC 10/11/12 for foreground/background/cursor,
+ * OSC 4 for individual palette entries).
+ *
+ * ### Default Colors
+ *
+ * Use ghostty_terminal_set() with the color options to configure the
+ * default colors. These represent the theme or configuration chosen by
+ * the embedder. Passing `NULL` clears the default, leaving the color
+ * unset.
+ *
+ * | Option | Input Type | Description |
+ * |-----------------------------------------|-------------------------|--------------------------------------|
+ * | `GHOSTTY_TERMINAL_OPT_COLOR_FOREGROUND` | `GhosttyColorRgb*` | Default foreground color |
+ * | `GHOSTTY_TERMINAL_OPT_COLOR_BACKGROUND` | `GhosttyColorRgb*` | Default background color |
+ * | `GHOSTTY_TERMINAL_OPT_COLOR_CURSOR` | `GhosttyColorRgb*` | Default cursor color |
+ * | `GHOSTTY_TERMINAL_OPT_COLOR_PALETTE` | `GhosttyColorRgb[256]*` | Default 256-color palette |
+ *
+ * For the palette, passing `NULL` resets to the built-in default palette.
+ * The palette set operation preserves any per-index OSC overrides that
+ * programs have applied; only unmodified indices are updated.
+ *
+ * ### Reading colors
+ *
+ * Use ghostty_terminal_get() to read colors. There are two variants for
+ * each color: the **effective** value (which returns the OSC override if
+ * one is active, otherwise the default) and the **default** value (which
+ * ignores any OSC overrides).
+ *
+ * | Data | Output Type | Description |
+ * |---------------------------------------------------|-------------------------|------------------------------------------------|
+ * | `GHOSTTY_TERMINAL_DATA_COLOR_FOREGROUND` | `GhosttyColorRgb*` | Effective foreground (override or default) |
+ * | `GHOSTTY_TERMINAL_DATA_COLOR_BACKGROUND` | `GhosttyColorRgb*` | Effective background (override or default) |
+ * | `GHOSTTY_TERMINAL_DATA_COLOR_CURSOR` | `GhosttyColorRgb*` | Effective cursor (override or default) |
+ * | `GHOSTTY_TERMINAL_DATA_COLOR_PALETTE` | `GhosttyColorRgb[256]*` | Current palette (with any OSC overrides) |
+ * | `GHOSTTY_TERMINAL_DATA_COLOR_FOREGROUND_DEFAULT` | `GhosttyColorRgb*` | Default foreground only (ignores OSC override) |
+ * | `GHOSTTY_TERMINAL_DATA_COLOR_BACKGROUND_DEFAULT` | `GhosttyColorRgb*` | Default background only (ignores OSC override) |
+ * | `GHOSTTY_TERMINAL_DATA_COLOR_CURSOR_DEFAULT` | `GhosttyColorRgb*` | Default cursor only (ignores OSC override) |
+ * | `GHOSTTY_TERMINAL_DATA_COLOR_PALETTE_DEFAULT` | `GhosttyColorRgb[256]*` | Default palette only (ignores OSC overrides) |
+ *
+ * For foreground, background, and cursor colors, the getters return
+ * `GHOSTTY_NO_VALUE` if no color is configured (neither a default nor an
+ * OSC override). The palette getters always succeed since the palette
+ * always has a value (the built-in default if nothing else is set).
+ *
+ * ### Setting a color theme
+ * @snippet c-vt-colors/src/main.c colors-set-defaults
+ *
+ * ### Reading effective and default colors
+ * @snippet c-vt-colors/src/main.c colors-read
+ *
+ * ### Full example with OSC overrides
+ * @snippet c-vt-colors/src/main.c colors-main
+ *
+ * @{
+ */
+
+/**
+ * Terminal initialization options.
+ *
+ * @ingroup terminal
+ */
+typedef struct {
+ /** Terminal width in cells. Must be greater than zero. */
+ uint16_t cols;
+
+ /** Terminal height in cells. Must be greater than zero. */
+ uint16_t rows;
+
+ /** Maximum number of lines to keep in scrollback history. */
+ size_t max_scrollback;
+
+ // TODO: Consider ABI compatibility implications of this struct.
+ // We may want to artificially pad it significantly to support
+ // future options.
+} GhosttyTerminalOptions;
+
+/**
+ * Scroll viewport behavior tag.
+ *
+ * @ingroup terminal
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Scroll to the top of the scrollback. */
+ GHOSTTY_SCROLL_VIEWPORT_TOP,
+
+ /** Scroll to the bottom (active area). */
+ GHOSTTY_SCROLL_VIEWPORT_BOTTOM,
+
+ /** Scroll by a delta amount (up is negative). */
+ GHOSTTY_SCROLL_VIEWPORT_DELTA,
+ GHOSTTY_SCROLL_VIEWPORT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyTerminalScrollViewportTag;
+
+/**
+ * Scroll viewport value.
+ *
+ * @ingroup terminal
+ */
+typedef union {
+ /** Scroll delta (only used with GHOSTTY_SCROLL_VIEWPORT_DELTA). Up is negative. */
+ intptr_t delta;
+
+ /** Padding for ABI compatibility. Do not use. */
+ uint64_t _padding[2];
+} GhosttyTerminalScrollViewportValue;
+
+/**
+ * Tagged union for scroll viewport behavior.
+ *
+ * @ingroup terminal
+ */
+typedef struct {
+ GhosttyTerminalScrollViewportTag tag;
+ GhosttyTerminalScrollViewportValue value;
+} GhosttyTerminalScrollViewport;
+
+/**
+ * Terminal screen identifier.
+ *
+ * Identifies which screen buffer is active in the terminal.
+ *
+ * @ingroup terminal
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** The primary (normal) screen. */
+ GHOSTTY_TERMINAL_SCREEN_PRIMARY = 0,
+
+ /** The alternate screen. */
+ GHOSTTY_TERMINAL_SCREEN_ALTERNATE = 1,
+ GHOSTTY_TERMINAL_SCREEN_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyTerminalScreen;
+
+/**
+ * Visual style of the terminal cursor.
+ *
+ * @ingroup terminal
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Bar cursor (DECSCUSR 5, 6). */
+ GHOSTTY_TERMINAL_CURSOR_STYLE_BAR = 0,
+
+ /** Block cursor (DECSCUSR 1, 2). */
+ GHOSTTY_TERMINAL_CURSOR_STYLE_BLOCK = 1,
+
+ /** Underline cursor (DECSCUSR 3, 4). */
+ GHOSTTY_TERMINAL_CURSOR_STYLE_UNDERLINE = 2,
+
+ /** Hollow block cursor. */
+ GHOSTTY_TERMINAL_CURSOR_STYLE_BLOCK_HOLLOW = 3,
+ GHOSTTY_TERMINAL_CURSOR_STYLE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyTerminalCursorStyle;
+
+/**
+ * Scrollbar state for the terminal viewport.
+ *
+ * Represents the scrollable area dimensions needed to render a scrollbar.
+ *
+ * @ingroup terminal
+ */
+typedef struct {
+ /** Total size of the scrollable area in rows. */
+ uint64_t total;
+
+ /** Offset into the total area that the viewport is at. */
+ uint64_t offset;
+
+ /** Length of the visible area in rows. */
+ uint64_t len;
+} GhosttyTerminalScrollbar;
+
+/**
+ * Callback function type for bell.
+ *
+ * Called when the terminal receives a BEL character (0x07).
+ *
+ * @param terminal The terminal handle
+ * @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
+ *
+ * @ingroup terminal
+ */
+typedef void (*GhosttyTerminalBellFn)(GhosttyTerminal terminal,
+ void* userdata);
+
+/**
+ * Callback function type for color scheme queries (CSI ? 996 n).
+ *
+ * Called when the terminal receives a color scheme device status report
+ * query. Return true and fill *out_scheme with the current color scheme,
+ * or return false to silently ignore the query.
+ *
+ * @param terminal The terminal handle
+ * @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
+ * @param[out] out_scheme Pointer to store the current color scheme
+ * @return true if the color scheme was filled, false to ignore the query
+ *
+ * @ingroup terminal
+ */
+typedef bool (*GhosttyTerminalColorSchemeFn)(GhosttyTerminal terminal,
+ void* userdata,
+ GhosttyColorScheme* out_scheme);
+
+/**
+ * Callback function type for device attributes queries (DA1/DA2/DA3).
+ *
+ * Called when the terminal receives a device attributes query (CSI c,
+ * CSI > c, or CSI = c). Return true and fill *out_attrs with the
+ * response data, or return false to silently ignore the query.
+ *
+ * The terminal uses whichever sub-struct (primary, secondary, tertiary)
+ * matches the request type, but all three should be filled for simplicity.
+ *
+ * @param terminal The terminal handle
+ * @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
+ * @param[out] out_attrs Pointer to store the device attributes response
+ * @return true if attributes were filled, false to ignore the query
+ *
+ * @ingroup terminal
+ */
+typedef bool (*GhosttyTerminalDeviceAttributesFn)(GhosttyTerminal terminal,
+ void* userdata,
+ GhosttyDeviceAttributes* out_attrs);
+
+/**
+ * Callback function type for enquiry (ENQ, 0x05).
+ *
+ * Called when the terminal receives an ENQ character. Return the
+ * response bytes as a GhosttyString. The memory must remain valid
+ * until the callback returns. Return a zero-length string to send
+ * no response.
+ *
+ * @param terminal The terminal handle
+ * @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
+ * @return The response bytes to write back to the pty
+ *
+ * @ingroup terminal
+ */
+typedef GhosttyString (*GhosttyTerminalEnquiryFn)(GhosttyTerminal terminal,
+ void* userdata);
+
+/**
+ * Callback function type for size queries (XTWINOPS).
+ *
+ * Called in response to XTWINOPS size queries (CSI 14/16/18 t).
+ * Return true and fill *out_size with the current terminal geometry,
+ * or return false to silently ignore the query.
+ *
+ * @param terminal The terminal handle
+ * @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
+ * @param[out] out_size Pointer to store the terminal size information
+ * @return true if size was filled, false to ignore the query
+ *
+ * @ingroup terminal
+ */
+typedef bool (*GhosttyTerminalSizeFn)(GhosttyTerminal terminal,
+ void* userdata,
+ GhosttySizeReportSize* out_size);
+
+/**
+ * Callback function type for title_changed.
+ *
+ * Called when the terminal title changes via escape sequences
+ * (e.g. OSC 0 or OSC 2). The new title can be queried from the
+ * terminal after the callback returns.
+ *
+ * @param terminal The terminal handle
+ * @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
+ *
+ * @ingroup terminal
+ */
+typedef void (*GhosttyTerminalTitleChangedFn)(GhosttyTerminal terminal,
+ void* userdata);
+
+/**
+ * Callback function type for pwd_changed.
+ *
+ * Called when the terminal pwd (current working directory) changes via
+ * escape sequences: OSC 7 (file:// URI), OSC 9 (ConEmu CurrentDir), or
+ * OSC 1337 CurrentDir (iTerm2). Use ghostty_terminal_get() with
+ * GHOSTTY_TERMINAL_DATA_PWD inside the callback to read the new value.
+ *
+ * The terminal stores whatever bytes the shell emitted, without parsing.
+ * That means for OSC 7 the value is the raw URI (typically file://...);
+ * for OSC 9/OSC 1337 it is typically a bare path. The embedder is
+ * responsible for decoding any URI scheme or host if it cares about them.
+ *
+ * The callback also fires when the shell clears the pwd (e.g. an empty
+ * OSC 7). In that case GHOSTTY_TERMINAL_DATA_PWD returns a zero-length
+ * string.
+ *
+ * @param terminal The terminal handle
+ * @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
+ *
+ * @ingroup terminal
+ */
+typedef void (*GhosttyTerminalPwdChangedFn)(GhosttyTerminal terminal,
+ void* userdata);
+
+/**
+ * Callback function type for write_pty.
+ *
+ * Called when the terminal needs to write data back to the pty, for
+ * example in response to a device status report or mode query. The
+ * data is only valid for the duration of the call; callers must copy
+ * it if it needs to persist.
+ *
+ * @param terminal The terminal handle
+ * @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
+ * @param data Pointer to the response bytes
+ * @param len Length of the response in bytes
+ *
+ * @ingroup terminal
+ */
+typedef void (*GhosttyTerminalWritePtyFn)(GhosttyTerminal terminal,
+ void* userdata,
+ const uint8_t* data,
+ size_t len);
+
+/**
+ * Callback function type for XTVERSION.
+ *
+ * Called when the terminal receives an XTVERSION query (CSI > q).
+ * Return the version string (e.g. "myterm 1.0") as a GhosttyString.
+ * The memory must remain valid until the callback returns. Return a
+ * zero-length string to report the default "libghostty" version.
+ *
+ * @param terminal The terminal handle
+ * @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
+ * @return The version string to report
+ *
+ * @ingroup terminal
+ */
+typedef GhosttyString (*GhosttyTerminalXtversionFn)(GhosttyTerminal terminal,
+ void* userdata);
+
+/**
+ * Terminal option identifiers.
+ *
+ * These values are used with ghostty_terminal_set() to configure
+ * terminal callbacks and associated state.
+ *
+ * @ingroup terminal
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /**
+ * Opaque userdata pointer passed to all callbacks.
+ *
+ * Input type: void*
+ */
+ GHOSTTY_TERMINAL_OPT_USERDATA = 0,
+
+ /**
+ * Callback invoked when the terminal needs to write data back
+ * to the pty (e.g. in response to a DECRQM query or device
+ * status report). Set to NULL to ignore such sequences.
+ *
+ * Input type: GhosttyTerminalWritePtyFn
+ */
+ GHOSTTY_TERMINAL_OPT_WRITE_PTY = 1,
+
+ /**
+ * Callback invoked when the terminal receives a BEL character
+ * (0x07). Set to NULL to ignore bell events.
+ *
+ * Input type: GhosttyTerminalBellFn
+ */
+ GHOSTTY_TERMINAL_OPT_BELL = 2,
+
+ /**
+ * Callback invoked when the terminal receives an ENQ character
+ * (0x05). Set to NULL to send no response.
+ *
+ * Input type: GhosttyTerminalEnquiryFn
+ */
+ GHOSTTY_TERMINAL_OPT_ENQUIRY = 3,
+
+ /**
+ * Callback invoked when the terminal receives an XTVERSION query
+ * (CSI > q). Set to NULL to report the default "libghostty" string.
+ *
+ * Input type: GhosttyTerminalXtversionFn
+ */
+ GHOSTTY_TERMINAL_OPT_XTVERSION = 4,
+
+ /**
+ * Callback invoked when the terminal title changes via escape
+ * sequences (e.g. OSC 0 or OSC 2). Set to NULL to ignore title
+ * change events.
+ *
+ * Input type: GhosttyTerminalTitleChangedFn
+ */
+ GHOSTTY_TERMINAL_OPT_TITLE_CHANGED = 5,
+
+ /**
+ * Callback invoked in response to XTWINOPS size queries
+ * (CSI 14/16/18 t). Set to NULL to silently ignore size queries.
+ *
+ * Input type: GhosttyTerminalSizeFn
+ */
+ GHOSTTY_TERMINAL_OPT_SIZE = 6,
+
+ /**
+ * Callback invoked in response to a color scheme device status
+ * report query (CSI ? 996 n). Return true and fill the out pointer
+ * to report the current scheme, or return false to silently ignore.
+ * Set to NULL to ignore color scheme queries.
+ *
+ * Input type: GhosttyTerminalColorSchemeFn
+ */
+ GHOSTTY_TERMINAL_OPT_COLOR_SCHEME = 7,
+
+ /**
+ * Callback invoked in response to a device attributes query
+ * (CSI c, CSI > c, or CSI = c). Return true and fill the out
+ * pointer with response data, or return false to silently ignore.
+ * Set to NULL to ignore device attributes queries.
+ *
+ * Input type: GhosttyTerminalDeviceAttributesFn
+ */
+ GHOSTTY_TERMINAL_OPT_DEVICE_ATTRIBUTES = 8,
+
+ /**
+ * Set the terminal title manually.
+ *
+ * The string data is copied into the terminal. A NULL value pointer
+ * clears the title (equivalent to setting an empty string).
+ *
+ * Input type: GhosttyString*
+ */
+ GHOSTTY_TERMINAL_OPT_TITLE = 9,
+
+ /**
+ * Set the terminal working directory manually.
+ *
+ * The string data is copied into the terminal. A NULL value pointer
+ * clears the pwd (equivalent to setting an empty string).
+ *
+ * Input type: GhosttyString*
+ */
+ GHOSTTY_TERMINAL_OPT_PWD = 10,
+
+ /**
+ * Set the default foreground color.
+ *
+ * A NULL value pointer clears the default (unset).
+ *
+ * Input type: GhosttyColorRgb*
+ */
+ GHOSTTY_TERMINAL_OPT_COLOR_FOREGROUND = 11,
+
+ /**
+ * Set the default background color.
+ *
+ * A NULL value pointer clears the default (unset).
+ *
+ * Input type: GhosttyColorRgb*
+ */
+ GHOSTTY_TERMINAL_OPT_COLOR_BACKGROUND = 12,
+
+ /**
+ * Set the default cursor color.
+ *
+ * A NULL value pointer clears the default (unset).
+ *
+ * Input type: GhosttyColorRgb*
+ */
+ GHOSTTY_TERMINAL_OPT_COLOR_CURSOR = 13,
+
+ /**
+ * Set the default 256-color palette.
+ *
+ * The value must point to an array of exactly 256 GhosttyColorRgb values.
+ * A NULL value pointer resets to the built-in default palette.
+ *
+ * Input type: GhosttyColorRgb[256]*
+ */
+ GHOSTTY_TERMINAL_OPT_COLOR_PALETTE = 14,
+
+ /**
+ * Set the Kitty image storage limit in bytes.
+ *
+ * Applied to all initialized screens (primary and alternate).
+ * A value of zero disables the Kitty graphics protocol entirely,
+ * deleting all stored images and placements. A NULL value pointer
+ * is equivalent to zero (disables). Has no effect when Kitty graphics
+ * are disabled at build time.
+ *
+ * Input type: uint64_t*
+ */
+ GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_STORAGE_LIMIT = 15,
+
+ /**
+ * Enable or disable Kitty image loading via the file medium.
+ *
+ * A NULL value pointer is a no-op. Has no effect when Kitty graphics
+ * are disabled at build time.
+ *
+ * Input type: bool*
+ */
+ GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_MEDIUM_FILE = 16,
+
+ /**
+ * Enable or disable Kitty image loading via the temporary file medium.
+ *
+ * A NULL value pointer is a no-op. Has no effect when Kitty graphics
+ * are disabled at build time.
+ *
+ * Input type: bool*
+ */
+ GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_MEDIUM_TEMP_FILE = 17,
+
+ /**
+ * Enable or disable Kitty image loading via the shared memory medium.
+ *
+ * A NULL value pointer is a no-op. Has no effect when Kitty graphics
+ * are disabled at build time.
+ *
+ * Input type: bool*
+ */
+ GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_MEDIUM_SHARED_MEM = 18,
+
+ /**
+ * Set the maximum bytes the APC handler will buffer for all protocols.
+ * This prevents malicious input from causing unbounded memory allocation.
+ * A NULL value pointer removes all overrides, reverting to the built-in
+ * defaults.
+ *
+ * Input type: size_t*
+ */
+ GHOSTTY_TERMINAL_OPT_APC_MAX_BYTES = 19,
+
+ /**
+ * Set the maximum bytes the APC handler will buffer for Kitty graphics
+ * protocol data. A NULL value pointer removes the override, reverting
+ * to the built-in default.
+ *
+ * Input type: size_t*
+ */
+ GHOSTTY_TERMINAL_OPT_APC_MAX_BYTES_KITTY = 20,
+
+ /**
+ * Set the active screen selection.
+ *
+ * The value must point to a GhosttySelection whose grid references are
+ * valid for this terminal's active screen at the time of the call. The
+ * terminal copies the selection immediately and converts it to
+ * terminal-owned tracked state, so the GhosttySelection struct and its
+ * untracked grid references do not need to outlive this call.
+ *
+ * Passing NULL clears the active screen selection.
+ *
+ * Input type: GhosttySelection*
+ */
+ GHOSTTY_TERMINAL_OPT_SELECTION = 21,
+
+ /**
+ * Set the default cursor style used by DECSCUSR reset (CSI 0 q).
+ *
+ * A NULL value pointer resets to the built-in default block cursor.
+ *
+ * Input type: GhosttyTerminalCursorStyle*
+ */
+ GHOSTTY_TERMINAL_OPT_DEFAULT_CURSOR_STYLE = 22,
+
+ /**
+ * Set whether the default cursor should blink when reset by DECSCUSR
+ * (CSI 0 q).
+ *
+ * A NULL value pointer resets to the built-in default of not blinking.
+ *
+ * Input type: bool*
+ */
+ GHOSTTY_TERMINAL_OPT_DEFAULT_CURSOR_BLINK = 23,
+
+ /**
+ * Enable or disable Glyph Protocol APC handling.
+ *
+ * When disabled, Glyph Protocol APC sequences are ignored and no
+ * support/query/register/clear responses are emitted. Disabling also clears
+ * the terminal session's glyph glossary. A NULL value pointer is a no-op.
+ *
+ * Input type: bool*
+ */
+ GHOSTTY_TERMINAL_OPT_GLYPH_PROTOCOL = 24,
+
+ /**
+ * Callback invoked when the terminal pwd changes via escape
+ * sequences (OSC 7, OSC 9, or OSC 1337 CurrentDir). Set to NULL
+ * to ignore pwd change events.
+ *
+ * Input type: GhosttyTerminalPwdChangedFn
+ */
+ GHOSTTY_TERMINAL_OPT_PWD_CHANGED = 25,
+ GHOSTTY_TERMINAL_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyTerminalOption;
+
+/**
+ * Terminal data types.
+ *
+ * These values specify what type of data to extract from a terminal
+ * using `ghostty_terminal_get`.
+ *
+ * @ingroup terminal
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Invalid data type. Never results in any data extraction. */
+ GHOSTTY_TERMINAL_DATA_INVALID = 0,
+
+ /**
+ * Terminal width in cells.
+ *
+ * Output type: uint16_t *
+ */
+ GHOSTTY_TERMINAL_DATA_COLS = 1,
+
+ /**
+ * Terminal height in cells.
+ *
+ * Output type: uint16_t *
+ */
+ GHOSTTY_TERMINAL_DATA_ROWS = 2,
+
+ /**
+ * Cursor column position (0-indexed).
+ *
+ * Output type: uint16_t *
+ */
+ GHOSTTY_TERMINAL_DATA_CURSOR_X = 3,
+
+ /**
+ * Cursor row position within the active area (0-indexed).
+ *
+ * Output type: uint16_t *
+ */
+ GHOSTTY_TERMINAL_DATA_CURSOR_Y = 4,
+
+ /**
+ * Whether the cursor has a pending wrap (next print will soft-wrap).
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_TERMINAL_DATA_CURSOR_PENDING_WRAP = 5,
+
+ /**
+ * The currently active screen.
+ *
+ * Output type: GhosttyTerminalScreen *
+ */
+ GHOSTTY_TERMINAL_DATA_ACTIVE_SCREEN = 6,
+
+ /**
+ * Whether the cursor is visible (DEC mode 25).
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_TERMINAL_DATA_CURSOR_VISIBLE = 7,
+
+ /**
+ * Current Kitty keyboard protocol flags.
+ *
+ * Output type: GhosttyKittyKeyFlags * (uint8_t *)
+ */
+ GHOSTTY_TERMINAL_DATA_KITTY_KEYBOARD_FLAGS = 8,
+
+ /**
+ * Scrollbar state for the terminal viewport.
+ *
+ * This may be expensive to calculate depending on where the viewport
+ * is (arbitrary pins are expensive). The caller should take care to only
+ * call this as needed and not too frequently.
+ *
+ * Output type: GhosttyTerminalScrollbar *
+ */
+ GHOSTTY_TERMINAL_DATA_SCROLLBAR = 9,
+
+ /**
+ * The current SGR style of the cursor.
+ *
+ * This is the style that will be applied to newly printed characters.
+ *
+ * Output type: GhosttyStyle *
+ */
+ GHOSTTY_TERMINAL_DATA_CURSOR_STYLE = 10,
+
+ /**
+ * Whether any mouse tracking mode is active.
+ *
+ * Returns true if any of the mouse tracking modes (X10, normal, button,
+ * or any-event) are enabled.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_TERMINAL_DATA_MOUSE_TRACKING = 11,
+
+ /**
+ * The terminal title as set by escape sequences (e.g. OSC 0/2).
+ *
+ * Returns a borrowed string. The pointer is valid until the next call
+ * to ghostty_terminal_vt_write() or ghostty_terminal_reset(). An empty
+ * string (len=0) is returned when no title has been set.
+ *
+ * Output type: GhosttyString *
+ */
+ GHOSTTY_TERMINAL_DATA_TITLE = 12,
+
+ /**
+ * The terminal's current working directory as set by escape sequences
+ * (e.g. OSC 7).
+ *
+ * Returns a borrowed string. The pointer is valid until the next call
+ * to ghostty_terminal_vt_write() or ghostty_terminal_reset(). An empty
+ * string (len=0) is returned when no pwd has been set.
+ *
+ * Output type: GhosttyString *
+ */
+ GHOSTTY_TERMINAL_DATA_PWD = 13,
+
+ /**
+ * The total number of rows in the active screen including scrollback.
+ *
+ * Output type: size_t *
+ */
+ GHOSTTY_TERMINAL_DATA_TOTAL_ROWS = 14,
+
+ /**
+ * The number of scrollback rows (total rows minus viewport rows).
+ *
+ * Output type: size_t *
+ */
+ GHOSTTY_TERMINAL_DATA_SCROLLBACK_ROWS = 15,
+
+ /**
+ * The total width of the terminal in pixels.
+ *
+ * This is cols * cell_width_px as set by ghostty_terminal_resize().
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_TERMINAL_DATA_WIDTH_PX = 16,
+
+ /**
+ * The total height of the terminal in pixels.
+ *
+ * This is rows * cell_height_px as set by ghostty_terminal_resize().
+ *
+ * Output type: uint32_t *
+ */
+ GHOSTTY_TERMINAL_DATA_HEIGHT_PX = 17,
+
+ /**
+ * The effective foreground color (override or default).
+ *
+ * Returns GHOSTTY_NO_VALUE if no foreground color is set.
+ *
+ * Output type: GhosttyColorRgb *
+ */
+ GHOSTTY_TERMINAL_DATA_COLOR_FOREGROUND = 18,
+
+ /**
+ * The effective background color (override or default).
+ *
+ * Returns GHOSTTY_NO_VALUE if no background color is set.
+ *
+ * Output type: GhosttyColorRgb *
+ */
+ GHOSTTY_TERMINAL_DATA_COLOR_BACKGROUND = 19,
+
+ /**
+ * The effective cursor color (override or default).
+ *
+ * Returns GHOSTTY_NO_VALUE if no cursor color is set.
+ *
+ * Output type: GhosttyColorRgb *
+ */
+ GHOSTTY_TERMINAL_DATA_COLOR_CURSOR = 20,
+
+ /**
+ * The current 256-color palette.
+ *
+ * Output type: GhosttyColorRgb[256] *
+ */
+ GHOSTTY_TERMINAL_DATA_COLOR_PALETTE = 21,
+
+ /**
+ * The default foreground color (ignoring any OSC override).
+ *
+ * Returns GHOSTTY_NO_VALUE if no default foreground color is set.
+ *
+ * Output type: GhosttyColorRgb *
+ */
+ GHOSTTY_TERMINAL_DATA_COLOR_FOREGROUND_DEFAULT = 22,
+
+ /**
+ * The default background color (ignoring any OSC override).
+ *
+ * Returns GHOSTTY_NO_VALUE if no default background color is set.
+ *
+ * Output type: GhosttyColorRgb *
+ */
+ GHOSTTY_TERMINAL_DATA_COLOR_BACKGROUND_DEFAULT = 23,
+
+ /**
+ * The default cursor color (ignoring any OSC override).
+ *
+ * Returns GHOSTTY_NO_VALUE if no default cursor color is set.
+ *
+ * Output type: GhosttyColorRgb *
+ */
+ GHOSTTY_TERMINAL_DATA_COLOR_CURSOR_DEFAULT = 24,
+
+ /**
+ * The default 256-color palette (ignoring any OSC overrides).
+ *
+ * Output type: GhosttyColorRgb[256] *
+ */
+ GHOSTTY_TERMINAL_DATA_COLOR_PALETTE_DEFAULT = 25,
+
+ /**
+ * The Kitty image storage limit in bytes for the active screen.
+ *
+ * A value of zero means the Kitty graphics protocol is disabled.
+ * Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
+ *
+ * Output type: uint64_t *
+ */
+ GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_STORAGE_LIMIT = 26,
+
+ /**
+ * Whether the file medium is enabled for Kitty image loading on the
+ * active screen.
+ *
+ * Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_MEDIUM_FILE = 27,
+
+ /**
+ * Whether the temporary file medium is enabled for Kitty image loading
+ * on the active screen.
+ *
+ * Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_MEDIUM_TEMP_FILE = 28,
+
+ /**
+ * Whether the shared memory medium is enabled for Kitty image loading
+ * on the active screen.
+ *
+ * Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_MEDIUM_SHARED_MEM = 29,
+
+ /**
+ * The Kitty graphics image storage for the active screen.
+ *
+ * Returns a borrowed pointer to the image storage. The pointer is valid
+ * until the next mutating terminal call (e.g. ghostty_terminal_vt_write()
+ * or ghostty_terminal_reset()).
+ *
+ * Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time.
+ *
+ * Output type: GhosttyKittyGraphics *
+ */
+ GHOSTTY_TERMINAL_DATA_KITTY_GRAPHICS = 30,
+
+ /**
+ * The active screen's current selection.
+ *
+ * On success, writes an untracked snapshot of the terminal-owned selection
+ * to the caller-provided GhosttySelection. The GhosttySelection struct is
+ * caller-owned and may be kept, but the grid references inside it are
+ * untracked borrowed references into the active screen. They are only valid
+ * until the next mutating terminal call, such as ghostty_terminal_set(),
+ * ghostty_terminal_vt_write(), ghostty_terminal_resize(), or
+ * ghostty_terminal_reset().
+ *
+ * Returns GHOSTTY_NO_VALUE when there is no active selection.
+ *
+ * Output type: GhosttySelection *
+ */
+ GHOSTTY_TERMINAL_DATA_SELECTION = 31,
+
+ /**
+ * Whether the viewport is currently pinned to the active area.
+ *
+ * This is true when the viewport is following the active terminal area,
+ * and false when the user has scrolled into history.
+ *
+ * Output type: bool *
+ */
+ GHOSTTY_TERMINAL_DATA_VIEWPORT_ACTIVE = 32,
+ GHOSTTY_TERMINAL_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyTerminalData;
+
+/**
+ * Create a new terminal instance.
+ *
+ * @param allocator Pointer to allocator, or NULL to use the default allocator
+ * @param terminal Pointer to store the created terminal handle
+ * @param options Terminal initialization options
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_new(const GhosttyAllocator* allocator,
+ GhosttyTerminal* terminal,
+ GhosttyTerminalOptions options);
+
+/**
+ * Free a terminal instance.
+ *
+ * Releases all resources associated with the terminal. After this call,
+ * the terminal handle becomes invalid and must not be used.
+ *
+ * @param terminal The terminal handle to free (may be NULL)
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API void ghostty_terminal_free(GhosttyTerminal terminal);
+
+/**
+ * Perform a full reset of the terminal (RIS).
+ *
+ * Resets all terminal state back to its initial configuration, including
+ * modes, scrollback, scrolling region, and screen contents. The terminal
+ * dimensions are preserved.
+ *
+ * @param terminal The terminal handle (may be NULL, in which case this is a no-op)
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API void ghostty_terminal_reset(GhosttyTerminal terminal);
+
+/**
+ * Resize the terminal to the given dimensions.
+ *
+ * Changes the number of columns and rows in the terminal. The primary
+ * screen will reflow content if wraparound mode is enabled; the alternate
+ * screen does not reflow. If the dimensions are unchanged, this is a no-op.
+ *
+ * This also updates the terminal's pixel dimensions (used for image
+ * protocols and size reports), disables synchronized output mode (allowed
+ * by the spec so that resize results are shown immediately), and sends an
+ * in-band size report if mode 2048 is enabled.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param cols New width in cells (must be greater than zero)
+ * @param rows New height in cells (must be greater than zero)
+ * @param cell_width_px Width of a single cell in pixels
+ * @param cell_height_px Height of a single cell in pixels
+ * @return GHOSTTY_SUCCESS on success, or an error code on failure
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_resize(GhosttyTerminal terminal,
+ uint16_t cols,
+ uint16_t rows,
+ uint32_t cell_width_px,
+ uint32_t cell_height_px);
+
+/**
+ * Set an option on the terminal.
+ *
+ * Configures terminal callbacks and associated state such as the
+ * write_pty callback and userdata pointer. The value is passed
+ * directly for pointer types (callbacks, userdata) or as a pointer
+ * to the value for non-pointer types (e.g. GhosttyString*).
+ * NULL clears the option to its default.
+ *
+ * Callbacks are invoked synchronously during ghostty_terminal_vt_write().
+ * Callbacks must not call ghostty_terminal_vt_write() on the same
+ * terminal (no reentrancy).
+ *
+ * @param terminal The terminal handle (may be NULL, in which case this is a no-op)
+ * @param option The option to set
+ * @param value Pointer to the value to set (type depends on the option),
+ * or NULL to clear the option
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_set(GhosttyTerminal terminal,
+ GhosttyTerminalOption option,
+ const void* value);
+
+/**
+ * Write VT-encoded data to the terminal for processing.
+ *
+ * Feeds raw bytes through the terminal's VT stream parser, updating
+ * terminal state accordingly. By default, sequences that require output
+ * (queries, device status reports) are silently ignored. Use
+ * ghostty_terminal_set() with GHOSTTY_TERMINAL_OPT_WRITE_PTY to install
+ * a callback that receives response data.
+ *
+ * This never fails. Any erroneous input or errors in processing the
+ * input are logged internally but do not cause this function to fail
+ * because this input is assumed to be untrusted and from an external
+ * source; so the primary goal is to keep the terminal state consistent and
+ * not allow malformed input to corrupt or crash.
+ *
+ * @param terminal The terminal handle
+ * @param data Pointer to the data to write
+ * @param len Length of the data in bytes
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API void ghostty_terminal_vt_write(GhosttyTerminal terminal,
+ const uint8_t* data,
+ size_t len);
+
+/**
+ * Scroll the terminal viewport.
+ *
+ * Scrolls the terminal's viewport according to the given behavior.
+ * When using GHOSTTY_SCROLL_VIEWPORT_DELTA, set the delta field in
+ * the value union to specify the number of rows to scroll (negative
+ * for up, positive for down). For other behaviors, the value is ignored.
+ *
+ * @param terminal The terminal handle (may be NULL, in which case this is a no-op)
+ * @param behavior The scroll behavior as a tagged union
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API void ghostty_terminal_scroll_viewport(GhosttyTerminal terminal,
+ GhosttyTerminalScrollViewport behavior);
+
+/**
+ * Get the current value of a terminal mode.
+ *
+ * Returns the value of the mode identified by the given mode.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param mode The mode identifying the mode to query
+ * @param[out] out_value On success, set to true if the mode is set, false
+ * if it is reset
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
+ * is NULL or the mode does not correspond to a known mode
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_mode_get(GhosttyTerminal terminal,
+ GhosttyMode mode,
+ bool* out_value);
+
+/**
+ * Set the value of a terminal mode.
+ *
+ * Sets the mode identified by the given mode to the specified value.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param mode The mode identifying the mode to set
+ * @param value true to set the mode, false to reset it
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
+ * is NULL or the mode does not correspond to a known mode
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_mode_set(GhosttyTerminal terminal,
+ GhosttyMode mode,
+ bool value);
+
+/**
+ * Get data from a terminal instance.
+ *
+ * Extracts typed data from the given terminal based on the specified
+ * data type. The output pointer must be of the appropriate type for the
+ * requested data kind. Valid data types and output types are documented
+ * in the `GhosttyTerminalData` enum.
+ *
+ * @param terminal The terminal handle (may be NULL)
+ * @param data The type of data to extract
+ * @param out Pointer to store the extracted data (type depends on data parameter)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
+ * is NULL or the data type is invalid
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_get(GhosttyTerminal terminal,
+ GhosttyTerminalData data,
+ void *out);
+
+/**
+ * Get multiple data fields from a terminal in a single call.
+ *
+ * This is an optimization over calling ghostty_terminal_get()
+ * repeatedly, particularly useful in environments with high per-call
+ * overhead such as FFI or Cgo.
+ *
+ * Each element in the keys array specifies a data kind, and the
+ * corresponding element in the values array receives the result.
+ * The type of each values[i] pointer must match the output type
+ * documented for keys[i].
+ *
+ * Processing stops at the first error; on success out_written
+ * is set to count, on error it is set to the index of the
+ * failing key (i.e. the number of values successfully written).
+ *
+ * @param terminal The terminal handle (may be NULL)
+ * @param count Number of key/value pairs
+ * @param keys Array of data kinds to query
+ * @param values Array of output pointers (types must match each key's
+ * documented output type)
+ * @param[out] out_written On return, receives the number of values
+ * successfully written (may be NULL)
+ * @return GHOSTTY_SUCCESS if all queries succeed
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_get_multi(GhosttyTerminal terminal,
+ size_t count,
+ const GhosttyTerminalData* keys,
+ void** values,
+ size_t* out_written);
+
+/**
+ * Resolve a point in the terminal grid to a grid reference.
+ *
+ * Resolves the given point (which can be in active, viewport, screen,
+ * or history coordinates) to a grid reference for that location. Use
+ * ghostty_grid_ref_cell() and ghostty_grid_ref_row() to extract the cell
+ * and row.
+ *
+ * Lookups using the `active` and `viewport` tags are fast. The `screen`
+ * and `history` tags may require traversing the full scrollback page list
+ * to resolve the y coordinate, so they can be expensive for large
+ * scrollback buffers.
+ *
+ * This function isn't meant to be used as the core of render loop. It
+ * isn't built to sustain the framerates needed for rendering large screens.
+ * Use the render state API for that. This API is instead meant for less
+ * strictly performance-sensitive use cases.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param point The point specifying which cell to look up
+ * @param[out] out_ref On success, set to the grid reference at the given point (may be NULL)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
+ * is NULL or the point is out of bounds
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_grid_ref(GhosttyTerminal terminal,
+ GhosttyPoint point,
+ GhosttyGridRef *out_ref);
+
+/**
+ * Create an owned tracked grid reference for a terminal point.
+ *
+ * This is the tracked variant of ghostty_terminal_grid_ref(). The returned
+ * handle follows the referenced cell as the terminal's page list is modified:
+ * scrolling, pruning, resize/reflow, and other page-list operations update the
+ * tracked reference automatically.
+ *
+ * The reference is attached to the terminal screen/page-list that is active at
+ * creation time.
+ *
+ * If the point is outside the requested coordinate space, this returns
+ * GHOSTTY_INVALID_VALUE and writes NULL to out_ref.
+ *
+ * The returned handle must be freed with ghostty_tracked_grid_ref_free(). If
+ * the terminal is freed first, the handle remains valid only for
+ * tracked-grid-ref APIs: it reports no value and can still be freed.
+ *
+ * @param terminal Terminal instance.
+ * @param point Point to track.
+ * @param[out] out_ref On success, receives the tracked reference handle.
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if terminal,
+ * point, or out_ref is invalid, or GHOSTTY_OUT_OF_MEMORY if allocation
+ * fails.
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_grid_ref_track(
+ GhosttyTerminal terminal,
+ GhosttyPoint point,
+ GhosttyTrackedGridRef *out_ref);
+
+/**
+ * Convert a grid reference back to a point in the given coordinate system.
+ *
+ * This is the inverse of ghostty_terminal_grid_ref(): given a grid reference,
+ * it returns the x/y coordinates in the requested coordinate system (active,
+ * viewport, screen, or history).
+ *
+ * The grid reference must have been obtained from the same terminal instance.
+ * Like all grid references, it is only valid until the next mutating terminal
+ * call.
+ *
+ * Not every grid reference is representable in every coordinate system. For
+ * example, a cell in scrollback history cannot be expressed in active
+ * coordinates, and a cell that has scrolled off the visible area cannot be
+ * expressed in viewport coordinates. In these cases, the function returns
+ * GHOSTTY_NO_VALUE.
+ *
+ * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
+ * @param ref Pointer to the grid reference to convert
+ * @param tag The target coordinate system
+ * @param[out] out On success, set to the coordinate in the requested system (may be NULL)
+ * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
+ * or ref is NULL/invalid, GHOSTTY_NO_VALUE if the ref falls outside
+ * the requested coordinate system
+ *
+ * @ingroup terminal
+ */
+GHOSTTY_API GhosttyResult ghostty_terminal_point_from_grid_ref(
+ GhosttyTerminal terminal,
+ const GhosttyGridRef *ref,
+ GhosttyPointTag tag,
+ GhosttyPointCoordinate *out);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GHOSTTY_VT_TERMINAL_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/types.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/types.h
new file mode 100644
index 00000000000..214d282296c
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/types.h
@@ -0,0 +1,335 @@
+/**
+ * @file types.h
+ *
+ * Common types, macros, and utilities for libghostty-vt.
+ */
+
+#ifndef GHOSTTY_VT_TYPES_H
+#define GHOSTTY_VT_TYPES_H
+
+#include
+#include
+#include
+
+// Symbol visibility for shared library builds. On Windows, functions
+// are exported from the DLL when building and imported when consuming.
+// On other platforms with GCC/Clang, functions are marked with default
+// visibility so they remain accessible when the library is built with
+// -fvisibility=hidden. For static library builds, define GHOSTTY_STATIC
+// before including this header to make this a no-op.
+#ifndef GHOSTTY_API
+#if defined(GHOSTTY_STATIC)
+ #define GHOSTTY_API
+#elif defined(_WIN32) || defined(_WIN64)
+ #ifdef GHOSTTY_BUILD_SHARED
+ #define GHOSTTY_API __declspec(dllexport)
+ #else
+ #define GHOSTTY_API __declspec(dllimport)
+ #endif
+#elif defined(__GNUC__) && __GNUC__ >= 4
+ #define GHOSTTY_API __attribute__((visibility("default")))
+#else
+ #define GHOSTTY_API
+#endif
+#endif
+
+/**
+ * Enum int-sizing helpers.
+ *
+ * The Zig side backs all C enums with c_int, so the C declarations
+ * must use int as their underlying type to maintain ABI compatibility.
+ *
+ * C23 (detected via __STDC_VERSION__ >= 202311L) supports explicit
+ * enum underlying types with `enum : int { ... }`. For pre-C23
+ * compilers, which are free to choose any type that can represent
+ * all values (C11 §6.7.2.2), we add an INT_MAX sentinel as the last
+ * entry to force the compiler to use int.
+ *
+ * INT_MAX is used rather than a fixed constant like 0xFFFFFFFF
+ * because enum constants must have type int (which is signed).
+ * Values above INT_MAX overflow signed int and are a constraint
+ * violation in standard C; compilers that accept them interpret them
+ * as negative values via two's complement, which can collide with
+ * legitimate negative enum values.
+ *
+ * Usage:
+ * @code
+ * typedef enum GHOSTTY_ENUM_TYPED {
+ * FOO_A = 0,
+ * FOO_B = 1,
+ * FOO_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+ * } Foo;
+ * @endcode
+ */
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
+#define GHOSTTY_ENUM_TYPED : int
+#else
+#define GHOSTTY_ENUM_TYPED
+#endif
+#define GHOSTTY_ENUM_MAX_VALUE INT_MAX
+
+/**
+ * Result codes for libghostty-vt operations.
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Operation completed successfully */
+ GHOSTTY_SUCCESS = 0,
+ /** Operation failed due to failed allocation */
+ GHOSTTY_OUT_OF_MEMORY = -1,
+ /** Operation failed due to invalid value */
+ GHOSTTY_INVALID_VALUE = -2,
+ /** Operation failed because the provided buffer was too small */
+ GHOSTTY_OUT_OF_SPACE = -3,
+ /** The requested value has no value */
+ GHOSTTY_NO_VALUE = -4,
+ GHOSTTY_RESULT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyResult;
+
+/* ---- Opaque handles ---- */
+
+/**
+ * Opaque handle to a terminal instance.
+ *
+ * @ingroup terminal
+ */
+typedef struct GhosttyTerminalImpl* GhosttyTerminal;
+
+/**
+ * Opaque handle to a tracked grid reference.
+ *
+ * A tracked grid reference is owned by the caller and must be freed with
+ * ghostty_tracked_grid_ref_free(). If the terminal that created it is freed
+ * first, the handle remains valid only for tracked-grid-ref APIs: it reports no
+ * value and can still be freed.
+ *
+ * @ingroup grid_ref
+ */
+typedef struct GhosttyTrackedGridRefImpl* GhosttyTrackedGridRef;
+
+/**
+ * Opaque handle to a Kitty graphics image storage.
+ *
+ * Obtained via ghostty_terminal_get() with
+ * GHOSTTY_TERMINAL_DATA_KITTY_GRAPHICS. The pointer is borrowed from
+ * the terminal and remains valid until the next mutating terminal call
+ * (e.g. ghostty_terminal_vt_write() or ghostty_terminal_reset()).
+ *
+ * @ingroup kitty_graphics
+ */
+typedef struct GhosttyKittyGraphicsImpl* GhosttyKittyGraphics;
+
+/**
+ * Opaque handle to a Kitty graphics image.
+ *
+ * Obtained via ghostty_kitty_graphics_image() with an image ID. The
+ * pointer is borrowed from the storage and remains valid until the next
+ * mutating terminal call.
+ *
+ * @ingroup kitty_graphics
+ */
+typedef const struct GhosttyKittyGraphicsImageImpl* GhosttyKittyGraphicsImage;
+
+/**
+ * Opaque handle to a Kitty graphics placement iterator.
+ *
+ * @ingroup kitty_graphics
+ */
+typedef struct GhosttyKittyGraphicsPlacementIteratorImpl* GhosttyKittyGraphicsPlacementIterator;
+
+/**
+ * Opaque handle to a render state instance.
+ *
+ * @ingroup render
+ */
+typedef struct GhosttyRenderStateImpl* GhosttyRenderState;
+
+/**
+ * Opaque handle to a render-state row iterator.
+ *
+ * @ingroup render
+ */
+typedef struct GhosttyRenderStateRowIteratorImpl* GhosttyRenderStateRowIterator;
+
+/**
+ * Opaque handle to render-state row cells.
+ *
+ * @ingroup render
+ */
+typedef struct GhosttyRenderStateRowCellsImpl* GhosttyRenderStateRowCells;
+
+/**
+ * Opaque handle to an SGR parser instance.
+ *
+ * This handle represents an SGR (Select Graphic Rendition) parser that can
+ * be used to parse SGR sequences and extract individual text attributes.
+ *
+ * @ingroup sgr
+ */
+typedef struct GhosttySgrParserImpl* GhosttySgrParser;
+
+/**
+ * Opaque handle to a formatter instance.
+ *
+ * @ingroup formatter
+ */
+typedef struct GhosttyFormatterImpl* GhosttyFormatter;
+
+/**
+ * Opaque handle to an OSC parser instance.
+ *
+ * This handle represents an OSC (Operating System Command) parser that can
+ * be used to parse the contents of OSC sequences.
+ *
+ * @ingroup osc
+ */
+typedef struct GhosttyOscParserImpl* GhosttyOscParser;
+
+/**
+ * Opaque handle to a single OSC command.
+ *
+ * This handle represents a parsed OSC (Operating System Command) command.
+ * The command can be queried for its type and associated data.
+ *
+ * @ingroup osc
+ */
+typedef struct GhosttyOscCommandImpl* GhosttyOscCommand;
+
+/* ---- Common value types ---- */
+
+/**
+ * Terminal content output format.
+ *
+ * @ingroup formatter
+ */
+typedef enum GHOSTTY_ENUM_TYPED {
+ /** Plain text (no escape sequences). */
+ GHOSTTY_FORMATTER_FORMAT_PLAIN,
+
+ /** VT sequences preserving colors, styles, URLs, etc. */
+ GHOSTTY_FORMATTER_FORMAT_VT,
+
+ /** HTML with inline styles. */
+ GHOSTTY_FORMATTER_FORMAT_HTML,
+ GHOSTTY_FORMATTER_FORMAT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+} GhosttyFormatterFormat;
+
+/**
+ * A borrowed byte string (pointer + length).
+ *
+ * The memory is not owned by this struct. The pointer is only valid
+ * for the lifetime documented by the API that produces or consumes it.
+ */
+typedef struct {
+ /** Pointer to the string bytes. */
+ const uint8_t* ptr;
+
+ /** Length of the string in bytes. */
+ size_t len;
+} GhosttyString;
+
+/**
+ * A caller-provided byte buffer.
+ *
+ * APIs that write to this type use `len` for the number of bytes written on
+ * GHOSTTY_SUCCESS and the required byte capacity on GHOSTTY_OUT_OF_SPACE.
+ */
+typedef struct {
+ /** Destination buffer for bytes. May be NULL when cap is 0 to query required size. */
+ uint8_t* ptr;
+
+ /** Capacity of ptr in bytes. */
+ size_t cap;
+
+ /** Bytes written on success, or required byte capacity on GHOSTTY_OUT_OF_SPACE. */
+ size_t len;
+} GhosttyBuffer;
+
+/**
+ * A surface-space position in pixels.
+ *
+ * This is not a terminal grid coordinate. It represents an x/y position in the
+ * rendered surface coordinate space, with (0, 0) at the top-left of the
+ * surface.
+ */
+typedef struct {
+ /** X position in surface pixels. */
+ double x;
+
+ /** Y position in surface pixels. */
+ double y;
+} GhosttySurfacePosition;
+
+/**
+ * A borrowed list of Unicode scalar values.
+ *
+ * Values are encoded as uint32_t scalar values. The memory is not owned by this
+ * struct. The pointer is only valid for the lifetime documented by the API that
+ * consumes or produces it.
+ *
+ * APIs may document special handling for NULL + len 0, such as “use defaults”.
+ */
+typedef struct {
+ /** Pointer to Unicode scalar values. */
+ const uint32_t* ptr;
+
+ /** Number of entries in ptr. */
+ size_t len;
+} GhosttyCodepoints;
+
+/**
+ * Initialize a sized struct to zero and set its size field.
+ *
+ * Sized structs use a `size` field as the first member for ABI
+ * compatibility. This macro zero-initializes the struct and sets the
+ * size field to `sizeof(type)`, which allows the library to detect
+ * which version of the struct the caller was compiled against.
+ *
+ * @param type The struct type to initialize
+ * @return A zero-initialized struct with the size field set
+ *
+ * Example:
+ * @code
+ * GhosttyFormatterTerminalOptions opts = GHOSTTY_INIT_SIZED(GhosttyFormatterTerminalOptions);
+ * opts.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN;
+ * opts.trim = true;
+ * @endcode
+ */
+#define GHOSTTY_INIT_SIZED(type) \
+ ((type){ .size = sizeof(type) })
+
+/**
+ * Return a pointer to a null-terminated JSON string describing the
+ * layout of every C API struct for the current target.
+ *
+ * This is primarily useful for language bindings that can't easily
+ * set C struct fields and need to do so via byte offsets. For example,
+ * WebAssembly modules can't share struct definitions with the host.
+ *
+ * Example (abbreviated):
+ * @code{.json}
+ * {
+ * "GhosttyMouseEncoderSize": {
+ * "size": 40,
+ * "align": 8,
+ * "fields": {
+ * "size": { "offset": 0, "size": 8, "type": "u64" },
+ * "screen_width": { "offset": 8, "size": 4, "type": "u32" },
+ * "screen_height": { "offset": 12, "size": 4, "type": "u32" },
+ * "cell_width": { "offset": 16, "size": 4, "type": "u32" },
+ * "cell_height": { "offset": 20, "size": 4, "type": "u32" },
+ * "padding_top": { "offset": 24, "size": 4, "type": "u32" },
+ * "padding_bottom": { "offset": 28, "size": 4, "type": "u32" },
+ * "padding_right": { "offset": 32, "size": 4, "type": "u32" },
+ * "padding_left": { "offset": 36, "size": 4, "type": "u32" }
+ * }
+ * }
+ * }
+ * @endcode
+ *
+ * The returned pointer is valid for the lifetime of the process.
+ *
+ * @return Pointer to the null-terminated JSON string.
+ */
+GHOSTTY_API const char *ghostty_type_json(void);
+
+#endif /* GHOSTTY_VT_TYPES_H */
diff --git a/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/wasm.h b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/wasm.h
new file mode 100644
index 00000000000..e2b63e2c601
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/Vendor/libghostty-vt/include/ghostty/vt/wasm.h
@@ -0,0 +1,160 @@
+/**
+ * @file wasm.h
+ *
+ * WebAssembly utility functions for libghostty-vt.
+ */
+
+#ifndef GHOSTTY_VT_WASM_H
+#define GHOSTTY_VT_WASM_H
+
+#ifdef __wasm__
+
+#include
+#include
+#include
+
+/** @defgroup wasm WebAssembly Utilities
+ *
+ * Convenience functions for allocating various types in WebAssembly builds.
+ * **These are only available the libghostty-vt wasm module.**
+ *
+ * Ghostty relies on pointers to various types for ABI compatibility, and
+ * creating those pointers in Wasm can be tedious. These functions provide
+ * a purely additive set of utilities that simplify memory management in
+ * Wasm environments without changing the core C library API.
+ *
+ * @note These functions always use the default allocator. If you need
+ * custom allocation strategies, you should allocate types manually using
+ * your custom allocator. This is a very rare use case in the WebAssembly
+ * world so these are optimized for simplicity.
+ *
+ * ## Example Usage
+ *
+ * Here's a simple example of using the Wasm utilities with the key encoder:
+ *
+ * @code
+ * const { exports } = wasmInstance;
+ * const view = new DataView(wasmMemory.buffer);
+ *
+ * // Create key encoder
+ * const encoderPtr = exports.ghostty_wasm_alloc_opaque();
+ * exports.ghostty_key_encoder_new(null, encoderPtr);
+ * const encoder = view.getUint32(encoder, true);
+ *
+ * // Configure encoder with Kitty protocol flags
+ * const flagsPtr = exports.ghostty_wasm_alloc_u8();
+ * view.setUint8(flagsPtr, 0x1F);
+ * exports.ghostty_key_encoder_setopt(encoder, 5, flagsPtr);
+ *
+ * // Allocate output buffer and size pointer
+ * const bufferSize = 32;
+ * const bufPtr = exports.ghostty_wasm_alloc_u8_array(bufferSize);
+ * const writtenPtr = exports.ghostty_wasm_alloc_usize();
+ *
+ * // Encode the key event
+ * exports.ghostty_key_encoder_encode(
+ * encoder, eventPtr, bufPtr, bufferSize, writtenPtr
+ * );
+ *
+ * // Read encoded output
+ * const bytesWritten = view.getUint32(writtenPtr, true);
+ * const encoded = new Uint8Array(wasmMemory.buffer, bufPtr, bytesWritten);
+ * @endcode
+ *
+ * @remark The code above is pretty ugly! This is the lowest level interface
+ * to the libghostty-vt Wasm module. In practice, this should be wrapped
+ * in a higher-level API that abstracts away all this.
+ *
+ * @{
+ */
+
+/**
+ * Allocate an opaque pointer. This can be used for any opaque pointer
+ * types such as GhosttyKeyEncoder, GhosttyKeyEvent, etc.
+ *
+ * @return Pointer to allocated opaque pointer, or NULL if allocation failed
+ * @ingroup wasm
+ */
+GHOSTTY_API void** ghostty_wasm_alloc_opaque(void);
+
+/**
+ * Free an opaque pointer allocated by ghostty_wasm_alloc_opaque().
+ *
+ * @param ptr Pointer to free, or NULL (NULL is safely ignored)
+ * @ingroup wasm
+ */
+GHOSTTY_API void ghostty_wasm_free_opaque(void **ptr);
+
+/**
+ * Allocate an array of uint8_t values.
+ *
+ * @param len Number of uint8_t elements to allocate
+ * @return Pointer to allocated array, or NULL if allocation failed
+ * @ingroup wasm
+ */
+GHOSTTY_API uint8_t* ghostty_wasm_alloc_u8_array(size_t len);
+
+/**
+ * Free an array allocated by ghostty_wasm_alloc_u8_array().
+ *
+ * @param ptr Pointer to the array to free, or NULL (NULL is safely ignored)
+ * @param len Length of the array (must match the length passed to alloc)
+ * @ingroup wasm
+ */
+GHOSTTY_API void ghostty_wasm_free_u8_array(uint8_t *ptr, size_t len);
+
+/**
+ * Allocate an array of uint16_t values.
+ *
+ * @param len Number of uint16_t elements to allocate
+ * @return Pointer to allocated array, or NULL if allocation failed
+ * @ingroup wasm
+ */
+GHOSTTY_API uint16_t* ghostty_wasm_alloc_u16_array(size_t len);
+
+/**
+ * Free an array allocated by ghostty_wasm_alloc_u16_array().
+ *
+ * @param ptr Pointer to the array to free, or NULL (NULL is safely ignored)
+ * @param len Length of the array (must match the length passed to alloc)
+ * @ingroup wasm
+ */
+GHOSTTY_API void ghostty_wasm_free_u16_array(uint16_t *ptr, size_t len);
+
+/**
+ * Allocate a single uint8_t value.
+ *
+ * @return Pointer to allocated uint8_t, or NULL if allocation failed
+ * @ingroup wasm
+ */
+GHOSTTY_API uint8_t* ghostty_wasm_alloc_u8(void);
+
+/**
+ * Free a uint8_t allocated by ghostty_wasm_alloc_u8().
+ *
+ * @param ptr Pointer to free, or NULL (NULL is safely ignored)
+ * @ingroup wasm
+ */
+GHOSTTY_API void ghostty_wasm_free_u8(uint8_t *ptr);
+
+/**
+ * Allocate a single size_t value.
+ *
+ * @return Pointer to allocated size_t, or NULL if allocation failed
+ * @ingroup wasm
+ */
+GHOSTTY_API size_t* ghostty_wasm_alloc_usize(void);
+
+/**
+ * Free a size_t allocated by ghostty_wasm_alloc_usize().
+ *
+ * @param ptr Pointer to free, or NULL (NULL is safely ignored)
+ * @ingroup wasm
+ */
+GHOSTTY_API void ghostty_wasm_free_usize(size_t *ptr);
+
+/** @} */
+
+#endif /* __wasm__ */
+
+#endif /* GHOSTTY_VT_WASM_H */
diff --git a/apps/mobile/modules/t3-terminal/android/.gitignore b/apps/mobile/modules/t3-terminal/android/.gitignore
new file mode 100644
index 00000000000..3166b90b042
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/android/.gitignore
@@ -0,0 +1 @@
+.cxx/
diff --git a/apps/mobile/modules/t3-terminal/android/build.gradle b/apps/mobile/modules/t3-terminal/android/build.gradle
index 90c0d4fc21e..0da0777cf4f 100644
--- a/apps/mobile/modules/t3-terminal/android/build.gradle
+++ b/apps/mobile/modules/t3-terminal/android/build.gradle
@@ -6,11 +6,25 @@ version = '0.0.0'
android {
namespace 'expo.modules.t3terminal'
+ ndkVersion rootProject.ext.ndkVersion
compileSdk rootProject.ext.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
+
+ externalNativeBuild {
+ cmake {
+ cppFlags '-std=c++17 -Wall -Wextra -Werror'
+ }
+ }
+ }
+
+ externalNativeBuild {
+ cmake {
+ path 'src/main/cpp/CMakeLists.txt'
+ version '3.22.1'
+ }
}
}
diff --git a/apps/mobile/modules/t3-terminal/android/src/main/assets/fonts/MesloLGS-NF-Bold.ttf b/apps/mobile/modules/t3-terminal/android/src/main/assets/fonts/MesloLGS-NF-Bold.ttf
new file mode 100644
index 00000000000..e0e39544b02
Binary files /dev/null and b/apps/mobile/modules/t3-terminal/android/src/main/assets/fonts/MesloLGS-NF-Bold.ttf differ
diff --git a/apps/mobile/modules/t3-terminal/android/src/main/assets/fonts/MesloLGS-NF-Regular.ttf b/apps/mobile/modules/t3-terminal/android/src/main/assets/fonts/MesloLGS-NF-Regular.ttf
new file mode 100644
index 00000000000..88b81490cd7
Binary files /dev/null and b/apps/mobile/modules/t3-terminal/android/src/main/assets/fonts/MesloLGS-NF-Regular.ttf differ
diff --git a/apps/mobile/modules/t3-terminal/android/src/main/cpp/CMakeLists.txt b/apps/mobile/modules/t3-terminal/android/src/main/cpp/CMakeLists.txt
new file mode 100644
index 00000000000..0273ff6c864
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/android/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 3.22.1)
+
+project(t3terminal LANGUAGES CXX)
+
+add_library(ghostty-vt SHARED IMPORTED)
+set_target_properties(
+ ghostty-vt
+ PROPERTIES IMPORTED_LOCATION
+ "${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libghostty-vt.so"
+)
+
+add_library(t3terminal SHARED t3_terminal_jni.cpp)
+
+target_include_directories(
+ t3terminal
+ PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../../../Vendor/libghostty-vt/include"
+)
+
+target_link_options(
+ t3terminal PRIVATE "-Wl,-z,common-page-size=16384" "-Wl,-z,max-page-size=16384"
+)
+
+target_link_libraries(t3terminal PRIVATE ghostty-vt log)
diff --git a/apps/mobile/modules/t3-terminal/android/src/main/cpp/t3_terminal_jni.cpp b/apps/mobile/modules/t3-terminal/android/src/main/cpp/t3_terminal_jni.cpp
new file mode 100644
index 00000000000..95760e8ead9
--- /dev/null
+++ b/apps/mobile/modules/t3-terminal/android/src/main/cpp/t3_terminal_jni.cpp
@@ -0,0 +1,516 @@
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace {
+
+constexpr uint32_t kSnapshotMagic = 0x54563354; // "T3VT" in little endian.
+constexpr uint16_t kSnapshotVersion = 1;
+constexpr size_t kMaxScrollbackRows = 10000;
+
+enum CellFlag : uint16_t {
+ kBold = 1 << 0,
+ kItalic = 1 << 1,
+ kFaint = 1 << 2,
+ kInverse = 1 << 3,
+ kInvisible = 1 << 4,
+ kStrikethrough = 1 << 5,
+ kOverline = 1 << 6,
+ kUnderline = 1 << 7,
+ kSelected = 1 << 8,
+};
+
+struct Session {
+ GhosttyTerminal terminal = nullptr;
+ GhosttyRenderState render_state = nullptr;
+ GhosttyRenderStateRowIterator row_iterator = nullptr;
+ GhosttyRenderStateRowCells row_cells = nullptr;
+ std::vector responses;
+ std::mutex mutex;
+};
+
+class ByteWriter {
+ public:
+ explicit ByteWriter(size_t capacity) { bytes_.reserve(capacity); }
+
+ void U8(uint8_t value) { bytes_.push_back(value); }
+
+ void U16(uint16_t value) {
+ U8(static_cast(value));
+ U8(static_cast(value >> 8));
+ }
+
+ void U32(uint32_t value) {
+ U16(static_cast(value));
+ U16(static_cast(value >> 16));
+ }
+
+ void Bytes(const std::vector& value) {
+ bytes_.insert(bytes_.end(), value.begin(), value.end());
+ }
+
+ std::vector Take() { return std::move(bytes_); }
+
+ private:
+ std::vector bytes_;
+};
+
+Session* FromHandle(jlong handle) {
+ return reinterpret_cast(static_cast(handle));
+}
+
+jbyteArray ToJavaBytes(JNIEnv* env, const std::vector& bytes) {
+ auto result = env->NewByteArray(static_cast(bytes.size()));
+ if (result != nullptr && !bytes.empty()) {
+ env->SetByteArrayRegion(result, 0, static_cast(bytes.size()),
+ reinterpret_cast(bytes.data()));
+ }
+ return result;
+}
+
+GhosttyColorRgb RgbFromArgb(jint color) {
+ const auto value = static_cast(color);
+ return {
+ .r = static_cast(value >> 16),
+ .g = static_cast(value >> 8),
+ .b = static_cast(value),
+ };
+}
+
+uint32_t ArgbFromRgb(GhosttyColorRgb color) {
+ return 0xFF000000U | (static_cast(color.r) << 16U) |
+ (static_cast(color.g) << 8U) | color.b;
+}
+
+GhosttyColorRgb Blend(GhosttyColorRgb foreground, GhosttyColorRgb background,
+ uint8_t foreground_weight) {
+ const auto blend = [foreground_weight](uint8_t front, uint8_t back) {
+ const uint16_t back_weight = 255 - foreground_weight;
+ return static_cast((front * foreground_weight + back * back_weight) / 255);
+ };
+ return {
+ .r = blend(foreground.r, background.r),
+ .g = blend(foreground.g, background.g),
+ .b = blend(foreground.b, background.b),
+ };
+}
+
+void AppendUtf8(std::vector* output, uint32_t codepoint) {
+ if (codepoint <= 0x7F) {
+ output->push_back(static_cast(codepoint));
+ } else if (codepoint <= 0x7FF) {
+ output->push_back(static_cast(0xC0 | (codepoint >> 6)));
+ output->push_back(static_cast(0x80 | (codepoint & 0x3F)));
+ } else if (codepoint <= 0xFFFF && !(codepoint >= 0xD800 && codepoint <= 0xDFFF)) {
+ output->push_back(static_cast(0xE0 | (codepoint >> 12)));
+ output->push_back(static_cast(0x80 | ((codepoint >> 6) & 0x3F)));
+ output->push_back(static_cast(0x80 | (codepoint & 0x3F)));
+ } else if (codepoint <= 0x10FFFF) {
+ output->push_back(static_cast(0xF0 | (codepoint >> 18)));
+ output->push_back(static_cast(0x80 | ((codepoint >> 12) & 0x3F)));
+ output->push_back(static_cast(0x80 | ((codepoint >> 6) & 0x3F)));
+ output->push_back(static_cast(0x80 | (codepoint & 0x3F)));
+ }
+}
+
+void OnWritePty(GhosttyTerminal, void* userdata, const uint8_t* data, size_t len) {
+ auto* session = static_cast(userdata);
+ if (session == nullptr || data == nullptr || len == 0) return;
+ session->responses.insert(session->responses.end(), data, data + len);
+}
+
+void ApplyTheme(Session* session, jint foreground, jint background, jint cursor,
+ JNIEnv* env, jintArray palette_array) {
+ auto foreground_rgb = RgbFromArgb(foreground);
+ auto background_rgb = RgbFromArgb(background);
+ auto cursor_rgb = RgbFromArgb(cursor);
+ ghostty_terminal_set(session->terminal, GHOSTTY_TERMINAL_OPT_COLOR_FOREGROUND,
+ &foreground_rgb);
+ ghostty_terminal_set(session->terminal, GHOSTTY_TERMINAL_OPT_COLOR_BACKGROUND,
+ &background_rgb);
+ ghostty_terminal_set(session->terminal, GHOSTTY_TERMINAL_OPT_COLOR_CURSOR, &cursor_rgb);
+
+ if (palette_array == nullptr) return;
+ const auto palette_length = env->GetArrayLength(palette_array);
+ if (palette_length <= 0) return;
+
+ GhosttyColorRgb palette[256];
+ if (ghostty_terminal_get(session->terminal,
+ GHOSTTY_TERMINAL_DATA_COLOR_PALETTE_DEFAULT,
+ palette) != GHOSTTY_SUCCESS) {
+ return;
+ }
+
+ const auto copied_length = std::min(palette_length, 256);
+ std::vector colors(static_cast(copied_length));
+ env->GetIntArrayRegion(palette_array, 0, copied_length, colors.data());
+ for (jsize index = 0; index < copied_length; ++index) {
+ palette[index] = RgbFromArgb(colors[index]);
+ }
+ ghostty_terminal_set(session->terminal, GHOSTTY_TERMINAL_OPT_COLOR_PALETTE, palette);
+}
+
+void FreeSession(Session* session) {
+ if (session == nullptr) return;
+ ghostty_render_state_row_cells_free(session->row_cells);
+ ghostty_render_state_row_iterator_free(session->row_iterator);
+ ghostty_render_state_free(session->render_state);
+ ghostty_terminal_free(session->terminal);
+ delete session;
+}
+
+std::vector DrainResponses(Session* session) {
+ std::vector responses;
+ responses.swap(session->responses);
+ return responses;
+}
+
+bool ViewportGridRef(Session* session, jint x, jint y, GhosttyGridRef* out) {
+ *out = GhosttyGridRef{};
+ out->size = sizeof(*out);
+ GhosttyPoint point{};
+ point.tag = GHOSTTY_POINT_TAG_VIEWPORT;
+ point.value.coordinate.x = static_cast(std::max(x, 0));
+ point.value.coordinate.y = static_cast(std::max(y, 0));
+ return ghostty_terminal_grid_ref(session->terminal, point, out) == GHOSTTY_SUCCESS;
+}
+
+uint16_t StyleFlags(const GhosttyStyle& style, bool selected) {
+ uint16_t flags = 0;
+ if (style.bold) flags |= kBold;
+ if (style.italic) flags |= kItalic;
+ if (style.faint) flags |= kFaint;
+ if (style.inverse) flags |= kInverse;
+ if (style.invisible) flags |= kInvisible;
+ if (style.strikethrough) flags |= kStrikethrough;
+ if (style.overline) flags |= kOverline;
+ if (style.underline != 0) flags |= kUnderline;
+ if (selected) flags |= kSelected;
+ return flags;
+}
+
+} // namespace
+
+extern "C" JNIEXPORT jlong JNICALL
+Java_expo_modules_t3terminal_GhosttyBridge_nativeCreate(
+ JNIEnv* env, jclass, jint cols, jint rows, jint cell_width, jint cell_height,
+ jint foreground, jint background, jint cursor, jintArray palette) {
+ auto* session = new Session();
+ GhosttyTerminalOptions options = {
+ .cols = static_cast(std::clamp(cols, 1, 65535)),
+ .rows = static_cast(std::clamp(rows, 1, 65535)),
+ .max_scrollback = kMaxScrollbackRows,
+ };
+ if (ghostty_terminal_new(nullptr, &session->terminal, options) != GHOSTTY_SUCCESS ||
+ ghostty_render_state_new(nullptr, &session->render_state) != GHOSTTY_SUCCESS ||
+ ghostty_render_state_row_iterator_new(nullptr, &session->row_iterator) != GHOSTTY_SUCCESS ||
+ ghostty_render_state_row_cells_new(nullptr, &session->row_cells) != GHOSTTY_SUCCESS) {
+ FreeSession(session);
+ return 0;
+ }
+
+ ghostty_terminal_set(session->terminal, GHOSTTY_TERMINAL_OPT_USERDATA, session);
+ ghostty_terminal_set(session->terminal, GHOSTTY_TERMINAL_OPT_WRITE_PTY,
+ reinterpret_cast(OnWritePty));
+ ApplyTheme(session, foreground, background, cursor, env, palette);
+ ghostty_terminal_resize(session->terminal, options.cols, options.rows,
+ static_cast(std::max(cell_width, 1)),
+ static_cast(std::max(cell_height, 1)));
+ return static_cast(reinterpret_cast(session));
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_expo_modules_t3terminal_GhosttyBridge_nativeDestroy(JNIEnv*, jclass, jlong handle) {
+ FreeSession(FromHandle(handle));
+}
+
+extern "C" JNIEXPORT jbyteArray JNICALL
+Java_expo_modules_t3terminal_GhosttyBridge_nativeFeed(JNIEnv* env, jclass, jlong handle,
+ jbyteArray data) {
+ auto* session = FromHandle(handle);
+ if (session == nullptr || data == nullptr) return env->NewByteArray(0);
+ std::lock_guard lock(session->mutex);
+ const auto length = env->GetArrayLength(data);
+ std::vector bytes(static_cast(length));
+ if (length > 0) {
+ env->GetByteArrayRegion(data, 0, length, reinterpret_cast(bytes.data()));
+ ghostty_terminal_vt_write(session->terminal, bytes.data(), bytes.size());
+ }
+ return ToJavaBytes(env, DrainResponses(session));
+}
+
+extern "C" JNIEXPORT jbyteArray JNICALL
+Java_expo_modules_t3terminal_GhosttyBridge_nativeResize(
+ JNIEnv* env, jclass, jlong handle, jint cols, jint rows, jint cell_width, jint cell_height) {
+ auto* session = FromHandle(handle);
+ if (session == nullptr) return env->NewByteArray(0);
+ std::lock_guard lock(session->mutex);
+ ghostty_terminal_resize(session->terminal,
+ static_cast(std::clamp(cols, 1, 65535)),
+ static_cast(std::clamp(rows, 1, 65535)),
+ static_cast(std::max(cell_width, 1)),
+ static_cast(std::max(cell_height, 1)));
+ return ToJavaBytes(env, DrainResponses(session));
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_expo_modules_t3terminal_GhosttyBridge_nativeScroll(JNIEnv*, jclass, jlong handle,
+ jint rows) {
+ auto* session = FromHandle(handle);
+ if (session == nullptr || rows == 0) return;
+ std::lock_guard