Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Makefile text eol=lf
*.txt text eol=lf

# Windows resource files - preserve as-is (native Windows tooling)
*.cmd text eol=crlf
*.rc -text
*.manifest -text

Expand Down
4 changes: 3 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,12 @@ pub fn build(b: *std.Build) !void {
lib_shared.install("ghostty-internal.dll");
lib_static.install("ghostty-internal-static.lib");
} else {
lib_shared.install("ghostty-internal.so");
lib_shared.install("libghostty-internal.so");
lib_static.install("ghostty-internal.a");
}
}
resources.install();
if (i18n) |v| v.install();
}

// macOS only artifacts. These will error if they're initialized for
Expand Down
5 changes: 5 additions & 0 deletions example/electron-embed-windows/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
artifacts/*.json
artifacts/*.log
artifacts/computer-use/
build/
node_modules/
78 changes: 78 additions & 0 deletions example/electron-embed-windows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Electron + native libghostty on Windows

This demo embeds a real libghostty surface in Electron 43.1.0. The Node-API
addon creates a child `HWND` under `BrowserWindow.getNativeWindowHandle()`,
owns a WGL OpenGL 4.3 context, and supplies that context through
`GHOSTTY_PLATFORM_OPENGL`. Chromium renders the panel beside it.

There is no xterm.js dependency. `ghostty_surface_new` starts the Windows
ConPTY-backed shell, and Ghostty's OpenGL renderer presents every terminal
frame through the addon's `SwapBuffers` callback.

## Build

Install Zig 0.15.2, Visual Studio 2022 Build Tools with the Desktop C++
workload, Node.js, and npm. From this directory:

```powershell
npm install
npm run build:ghostty
npm run build
npm start
```

The Ghostty build now installs `ghostty-internal.lib` with
`ghostty-internal.dll`, so MSVC embedders link against stable files under
`zig-out\lib`.

Ghostty requires desktop OpenGL 4.3. Many Windows cloud VMs expose only the
Microsoft OpenGL 1.1 RDP driver. Install a trusted OpenGL 4.3-capable GPU
driver for production. For software-rendered cloud validation, extract a
trusted Mesa Windows x64 build, then run:

```powershell
$env:GHOSTTY_MESA_DIR = "C:\path\to\mesa"
npm run deploy:mesa
.\scripts\start-software-renderer.cmd
```

The deploy script renames Mesa's `opengl32.dll` and places it beside the addon
with `libgallium_wgl.dll`. The addon loads that private WGL table through
`GHOSTTY_MESA_OPENGL_PATH`; it does not replace Electron's `opengl32.dll` or
alter Chromium's graphics stack. The addon rejects contexts older than 4.3 and
reports the exact GL version instead of showing a blank pseudo-terminal.

## Input and lifecycle

The child window routes physical scan codes, committed UTF-16 text, focus,
selection drags, wheel scrolling, and terminal mouse reporting directly to
libghostty. Right-click first checks `ghostty_surface_mouse_captured`: captured
applications receive the button, while an uncaptured shell gets a native
Copy/Paste menu backed by Ghostty's clipboard callbacks.

`destroy()` is idempotent. It first joins and frees the Ghostty surface while
WGL callbacks remain alive, then frees the app and config, deletes the GL
context, and destroys the child window. N-API finalization repeats the same
safe path, so closing during active output does not depend on garbage
collection order. Embedded surface creation also waits until renderer and IO
stop watchers are armed, so an immediate destroy cannot lose a startup stop
notification and deadlock teardown.

## Stress

`npm run stress` immediately creates and destroys 25 surfaces to exercise the
thread-startup race, then recreates 50 rendered surfaces, performs 2,500 native
resizes, closes each shell during active output, and injects five Chromium
renderer deaths while retaining the native terminal host. It writes a JSON
report under `artifacts` and fails on unexpected renderer loss, unresponsive
windows, native renderer health failures, a surface that never swaps a real
WGL frame, or retained memory growth above the limit.

`npm run stress:cycles` repeats the test in five fresh Electron processes for
cold-start coverage. The aggregate report is
`artifacts\stress-cycles.json`.

`npm run stress:teardown` is the focused ConPTY reader teardown regression
harness. It creates and immediately frees 1,000 native surfaces in one
Electron process and fails if any `ghostty_surface_free` call blocks long
enough to hit the process watchdog.
34 changes: 34 additions & 0 deletions example/electron-embed-windows/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"variables": {
"ghostty_root%": "<!(node -p \"process.env.GHOSTTY_ROOT || require('node:path').resolve('../..')\")",
"ghostty_lib_dir%": "<!(node -p \"process.env.GHOSTTY_LIB_DIR || require('node:path').resolve('../../zig-out/lib')\")"
},
"targets": [
{
"target_name": "ghostty_embed",
"sources": ["src/libghostty_embed_win.cc"],
"include_dirs": ["<(ghostty_root)/include"],
"libraries": [
"<(ghostty_lib_dir)/ghostty-internal.lib",
"user32.lib",
"gdi32.lib",
"vcruntime.lib",
"ucrt.lib",
"msvcrt.lib"
],
"defines": ["NAPI_VERSION=9"],
"msvs_settings": {
"VCCLCompilerTool": {
"AdditionalOptions": ["/std:c++20", "/EHsc"],
"RuntimeLibrary": 2
}
},
"copies": [
{
"destination": "<(PRODUCT_DIR)",
"files": ["<(ghostty_lib_dir)/ghostty-internal.dll"]
}
]
}
]
}
Loading