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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 29 additions & 18 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apple/GhostteaApp/Resources/Ghosttea-iOS.cdx.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
},
{
"name": "ghosttea:cargo-lock:sha256",
"value": "6da56d16f37b315fb22ebe023528c7f4ff8b00b6674613e894838a658c2c83d3"
"value": "7cdc22165195a7f98749dadbf1de0801225a9a3fcac38cfc0a18b931767268e3"
},
{
"name": "ghosttea:rust-target",
Expand Down
2 changes: 1 addition & 1 deletion apple/GhostteaApp/Resources/THIRD-PARTY-NOTICES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ GHOSTTEA iOS THIRD-PARTY NOTICES
================================

This file is generated from the reviewed iOS release dependency graph.
CycloneDX SHA-256: be5713a39ecb1300ec9c5f41db8010e63fe0de4697e9c64f6e3966ac357d9b43
CycloneDX SHA-256: 9733b5e2f5f8662b650181292deb5e5d130de982ada840cb9e53b5ffaa4f2065
Components: 108
Unique license documents: 97

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
"schemaVersion": 1,
"sourceBom": {
"path": "apple/GhostteaKit/Compatibility/ios-release.cdx.json",
"sha256": "be5713a39ecb1300ec9c5f41db8010e63fe0de4697e9c64f6e3966ac357d9b43"
"sha256": "9733b5e2f5f8662b650181292deb5e5d130de982ada840cb9e53b5ffaa4f2065"
},
"bundledBom": {
"path": "apple/GhostteaApp/Resources/Ghosttea-iOS.cdx.json",
"sha256": "be5713a39ecb1300ec9c5f41db8010e63fe0de4697e9c64f6e3966ac357d9b43"
"sha256": "9733b5e2f5f8662b650181292deb5e5d130de982ada840cb9e53b5ffaa4f2065"
},
"notices": {
"path": "apple/GhostteaApp/Resources/THIRD-PARTY-NOTICES.txt",
"sha256": "ba2189bb8022dd15234bbb6438fea7905df0ae1a8f226121fd89fa501bc06aec",
"sha256": "584197ad95026d1abed13290bf951db8c74db0aaa46afd8f2e64db8820cb143f",
"componentCount": 108,
"licenseDocumentCount": 97
}
Expand Down
2 changes: 1 addition & 1 deletion apple/GhostteaKit/Compatibility/ios-release.cdx.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
},
{
"name": "ghosttea:cargo-lock:sha256",
"value": "6da56d16f37b315fb22ebe023528c7f4ff8b00b6674613e894838a658c2c83d3"
"value": "7cdc22165195a7f98749dadbf1de0801225a9a3fcac38cfc0a18b931767268e3"
},
{
"name": "ghosttea:rust-target",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"pkg:cargo/ghosttea-font-fixture-ffi@0.12.0"
]
},
"cargoLockSha256": "6da56d16f37b315fb22ebe023528c7f4ff8b00b6674613e894838a658c2c83d3",
"cargoLockSha256": "7cdc22165195a7f98749dadbf1de0801225a9a3fcac38cfc0a18b931767268e3",
"components": [
{
"ref": "pkg:cargo/anyhow@1.0.103",
Expand Down
24 changes: 24 additions & 0 deletions docs/webgpu-appearance-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,30 @@ A solid block cursor is inserted after selection backgrounds and before glyphs;
the covered glyph uses `cursor-text`. Bar, underline, and hollow cursors remain
late overlay geometry.

## Light adaptation

Some applications paint every cell with their own truecolor background, so a
light theme never shows through (Grok's default theme paints `#141414`
everywhere). With `lightAdaptation: "auto"`, the default from configuration,
a pane is remapped when the theme background is light and at least 60% of its
cells carry an explicit dark background. It releases below 35%, so partially
painted scrolls do not flicker. Applications that follow the theme (Codex,
OpenCode, Claude Code's Auto theme) never cross that threshold.

The remap runs in `resolveStyle`, per style and before blending, so glyph
coverage, color glyphs, and the shader stack are untouched. Surfaces mirror
their OKLab-with-toe lightness around the theme background, anchored at the
application's dominant surface. Neutral ink keeps its distance from its own
cell background, and accents land in an APCA mid-tone band with their hue
intact. Default foreground and background stay theme-owned. Engaging,
re-keying, or releasing invalidates the persistent scene and geometry cache.

Applications that switch themes themselves need the terminal to say which
scheme it is. ghosttead answers `CSI ? 996 n` from the default background's
luminance, sends `CSI ? 997 ; 1|2 n` when a theme change flips it and mode
2031 is set, and sets `COLORFGBG` for new sessions unless the caller provides
one.

## Catalogs and persistence

The linked `ghostty.style` gallery seeds its built-in collection from every
Expand Down
31 changes: 31 additions & 0 deletions native/ghosttea/crates/ghosttea-core/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,37 @@ mod tests {
}
}

#[test]
fn theme_change_reports_the_new_color_scheme_to_the_program() {
let runtime = Arc::new(TerminalRuntime::discover().unwrap());
let mut model = TerminalModel::new(
runtime,
TerminalModelOptions {
session_handle: 4,
session_epoch: 1,
layout_epoch: 1,
cols: 20,
rows: 4,
scrollback_bytes: 4096,
},
)
.unwrap();
model.feed(b"\x1b[?2031h", RenderRequest::None).unwrap();
let update = model
.set_appearance(
[0x4c, 0x4f, 0x69],
[0xef, 0xf1, 0xf5],
[0xdc, 0x8a, 0x78],
&[],
RenderRequest::Full,
)
.unwrap();
assert!(update.as_slice().iter().any(|effect| matches!(
effect,
TerminalEffect::WriteToTransport(bytes) if bytes == b"\x1b[?997;2n"
)));
}

#[test]
fn terminal_reply_precedes_semantic_and_render_effects() {
let runtime = Arc::new(TerminalRuntime::discover().unwrap());
Expand Down
47 changes: 46 additions & 1 deletion native/ghosttea/crates/ghosttea-vt-sys/src/ghostty_shim.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "ghostty_shim_internal.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>

Expand All @@ -9,6 +10,30 @@ struct EgTrackedSelection {
bool rectangle;
};

static double eg_srgb_to_linear(uint8_t value) {
double v = value / 255.0;
return v <= 0.04045 ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4);
}

// A background is light when dark text out-contrasts light text on it, which
// is WCAG relative luminance above ~0.179.
static GhosttyColorScheme eg_scheme_for_background(GhosttyColorRgb background) {
double luminance = 0.2126 * eg_srgb_to_linear(background.r) +
0.7152 * eg_srgb_to_linear(background.g) +
0.0722 * eg_srgb_to_linear(background.b);
return (luminance + 0.05) / 0.05 > 1.05 / (luminance + 0.05)
? GHOSTTY_COLOR_SCHEME_LIGHT
: GHOSTTY_COLOR_SCHEME_DARK;
}

static bool eg_color_scheme(GhosttyTerminal terminal,
void* userdata,
GhosttyColorScheme* out_scheme) {
(void)terminal;
*out_scheme = ((EgTerminal*)userdata)->color_scheme;
return true;
}

int eg_terminal_set_colors(EgTerminal* state,
uint8_t fg_r, uint8_t fg_g, uint8_t fg_b,
uint8_t bg_r, uint8_t bg_g, uint8_t bg_b,
Expand All @@ -23,8 +48,27 @@ int eg_terminal_set_colors(EgTerminal* state,
result = ghostty_terminal_set(
state->terminal, GHOSTTY_TERMINAL_OPT_COLOR_BACKGROUND, &background);
if (result != GHOSTTY_SUCCESS) return result;
return ghostty_terminal_set(
result = ghostty_terminal_set(
state->terminal, GHOSTTY_TERMINAL_OPT_COLOR_CURSOR, &cursor);
if (result != GHOSTTY_SUCCESS) return result;

// Programs that enabled mode 2031 (Claude Code's Auto theme, Neovim) learn
// about a light/dark flip without re-querying. The report rides the normal
// PTY response buffer, so it reaches the program with the next snapshot.
GhosttyColorScheme scheme = eg_scheme_for_background(background);
if (scheme == state->color_scheme) return GHOSTTY_SUCCESS;
state->color_scheme = scheme;
bool reporting = false;
if (ghostty_terminal_mode_get(
state->terminal, GHOSTTY_MODE_COLOR_SCHEME_REPORT, &reporting) != GHOSTTY_SUCCESS ||
!reporting)
return GHOSTTY_SUCCESS;
char report[32];
size_t written = 0;
if (ghostty_color_scheme_report_encode(scheme, report, sizeof report, &written) ==
GHOSTTY_SUCCESS)
(void)eg_buffer_append(&state->response, (const uint8_t*)report, written);
return GHOSTTY_SUCCESS;
}

int eg_terminal_set_palette(EgTerminal* state,
Expand Down Expand Up @@ -158,6 +202,7 @@ EgTerminal* eg_terminal_new(uint16_t cols, uint16_t rows, size_t max_scrollback)
ghostty_terminal_set(state->terminal, GHOSTTY_TERMINAL_OPT_TITLE_CHANGED, (const void*)eg_title_changed);
ghostty_terminal_set(state->terminal, GHOSTTY_TERMINAL_OPT_PWD_CHANGED, (const void*)eg_pwd_changed);
ghostty_terminal_set(state->terminal, GHOSTTY_TERMINAL_OPT_CLIPBOARD_WRITE, (const void*)eg_clipboard_write);
ghostty_terminal_set(state->terminal, GHOSTTY_TERMINAL_OPT_COLOR_SCHEME, (const void*)eg_color_scheme);
if (eg_terminal_set_colors(
state,
255, 255, 255,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ struct EgTerminal {
EgBuffer grapheme;
bool mouse_pressed;
uint32_t effects;
// Derived from the embedder's default background; answers CSI ? 996 n and
// drives mode-2031 reports when a theme change flips it.
GhosttyColorScheme color_scheme;
};

bool eg_buffer_reserve(EgBuffer* buffer, size_t required);
Expand Down
Loading
Loading