Skip to content

About

OpenHarmony terminal component that embeds libghostty-vt behind an XComponent surface.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

@ohos-rs/terminal

OpenHarmony terminal component that embeds libghostty-vt behind an XComponent surface.

The native addon is built entirely by Zig. This workspace consumes the adjacent ../ohos-zig-binding checkout for XComponent, Native Window, Native VSync, Native Drawing, and IME wrappers. Zig builds wgpu-native and the official libghostty-vt source; the HAR does not vendor prebuilt .a files.

Terminal behavior is sourced from the pinned official Ghostty commit in build.zig.zon; the wrapper includes Ghostty's public headers and does not maintain a second ABI. The OHOS renderer follows Ghostty's generic renderer model—dirty viewport rows, immutable frame data, GPU cell/background buffers, color-aware glyph atlas, and a dedicated render thread—on top of wgpu-native.

Install

ohpm install @ohos-rs/terminal

Stack

example/ (entry HAP demo)
  └─ Terminal / TerminalController  (@ohos-rs/terminal HAR)
       └─ XComponent(libraryname = "terminal")
            └─ libterminal.so   zig build
                 ├─ zig-napi
                 ├─ ohos-zig-binding (XComponent / window / VSync / drawing / IME)
                 ├─ raw-window-handle
                 ├─ wgpu-native-zig  (builds wgpu-native)
                 └─ ghostty          (builds libghostty-vt)

The library does not start a shell. The example echoes keyboard input locally. A real app owns PTY/SSH and wires controller.write() / controller.feed().

Layout

  • src/: Zig addon
  • package/: reusable HAR (Terminal, TerminalController)
  • example/: Stage-model entry HAP demo (pages/Index)

Demo

example/ is the HarmonyOS entry module. After zig build dist, open this repository in DevEco Studio and run the entry HAP.

The demo page hosts Terminal and a cooked local shell (same host split as arkit). Tap the surface to open the native IME. Typed bytes go on_input → demo shell → feed(). Hello, Colors, and Clear inject focused VT samples; Stress injects 1,200 long, colored, Unicode lines for soft-wrap and scrollback verification.

Build

Requires Zig 0.16, an OpenHarmony API 20+ NDK, the adjacent ohos-zig-binding checkout on branch feat/native-vsync-binding, and a network fetch of the remaining git dependencies on the first build.

export OHOS_NDK_HOME=/path/to/ohos-sdk
./scripts/build-ohos.sh

OHOS_NDK_HOME is normalized to the SDK root by the build script. Passing the native/ child is also accepted.

zig build compiles libterminal.so by building the git dependencies. zig build dist writes the addon into package/libs/<abi>/ and example/libs/<abi>/ so the HAR and entry module can package it. Native builds default to ReleaseFast; use -Doptimize=Debug explicitly when debugging. Running the terminal performance demo with Debug-built Ghostty or wgpu-native is not representative and can trip HarmonyOS main-thread watchdogs.

Useful flags:

  • -Dnative=false — host tests only, do not build the OHOS addon

The first zig build fetches git archives and compiles wgpu-native (Rust/cargo) plus libghostty-vt. Later builds reuse the cache.

Package the HAR

Install ohpm-rs, configure OHOS_NDK_HOME, then run:

./scripts/pack-ohpm.sh

The script builds and verifies libterminal.so for arm64-v8a, armeabi-v7a, and x86_64 before invoking ohpm-rs prepublish and ohpm-rs pack. The resulting HAR is written to dist/. package/README.md and package/LICENSE are relative symlinks to their repository-root files so the published package always carries the canonical documentation and license.

Runtime architecture

The terminal follows the same ownership split used by gpui-ghostty and arkit_terminal:

  1. ArkTS owns component lifecycle and the host I/O boundary. Typed input goes to the app through on_input; only host output is fed back to Ghostty. Hardware keys are encoded by Ghostty's own GhosttyKeyEncoder, synchronized from current terminal modes rather than a local escape-sequence table.
  2. The render worker owns hot-path libghostty-vt stream mutation and captures immutable viewport updates. feed() only enqueues bytes, so large PTY bursts never parse on ArkUI. Parsing is chunked and coalesced to display ticks; Ghostty's terminal and per-row dirty state is retained instead of copying the full viewport for each PTY chunk. Surface size and font density determine rows and columns; the grid is reflowed instead of stretching glyphs.
  3. The render worker exclusively owns wgpu, the retained OHNativeWindow, and a window-associated OH_NativeVSync. It keeps one one-shot frame request in flight and presents only the newest snapshot on that surface's display tick. Distinct graphemes are rasterized once into a persistent GPU atlas; backgrounds, cached glyph quads, and the cursor are submitted as GPU instances. There is no CPU-composited fullscreen bitmap. CPU-side GPU cell lists retain one allocation per row, matching Ghostty's renderer.cell.Contents: only dirty rows are rebuilt, while the resulting buffers are batch-uploaded and drawn by wgpu. Frame publication merges unconsumed dirty rows so slow frames cannot drop or replay stale states.
  4. Renderer readiness means that a frame was successfully presented, not just that a device was created. Surface loss and validation errors remain visible through the ArkTS diagnostic overlay.

Native VSync callbacks only enqueue a generation-tagged worker message. If a surface is replaced, late callbacks from its old VSync connection are ignored. If associated VSync creation fails, XComponent frame callbacks retain the previous fallback behavior. A busy terminal lock skips only that display tick, and the latest immutable frame still wins. Touch scrolling accumulates atomically and is applied on the next available frame, preventing engine-lock contention from blocking the ArkUI thread.

Surface and input callbacks only retain native resources or enqueue work. The render worker serializes VT parsing, pending viewport movement, cursor timing, dirty-row capture, GPU cell-list rebuild, and presentation. Configuration and query APIs briefly share the terminal-state lock but never mutate renderer GPU state.

As in official Ghostty, font discovery and a glyph's first rasterization are CPU/font-system operations. Once cached, terminal frame composition and presentation are GPU-only; normal frames do not create or upload a fullscreen CPU bitmap.

Usage

import { Terminal, TerminalController } from '@ohos-rs/terminal';

@Entry
@Component
struct TerminalPage {
  private controller: TerminalController = new TerminalController();

  aboutToAppear(): void {
    this.controller.updateConfig({ fontSize: 16 });
    this.controller.setInputListener((data: string) => {
      this.driver.write(data);
    });
  }

  build() {
    Terminal({
      controller: this.controller,
      surfaceId: 'main-terminal',
      surfaceColor: '#0B0D10'
    })
  }
}

Git dependencies

Declared in build.zig.zon and built by Zig:

  • openharmony-zig/zig-napi
  • openharmony-zig/raw-window-handle
  • openharmony-zig/wgpu_native_zig
  • ghostty-org/ghostty (ghostty-vt-static artifact)

ohos-zig-binding is a local workspace dependency until the new native_vsync module and NativeWindow.surfaceId() are published.

LICENSE

MIT

About

OpenHarmony terminal component that embeds libghostty-vt behind an XComponent surface.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages