Skip to content

Add ghostty_config_set_color_scheme C API#37

Open
rodchristiansen wants to merge 1 commit intomanaflow-ai:mainfrom
rodchristiansen:feat/config-set-color-scheme-api
Open

Add ghostty_config_set_color_scheme C API#37
rodchristiansen wants to merge 1 commit intomanaflow-ai:mainfrom
rodchristiansen:feat/config-set-color-scheme-api

Conversation

@rodchristiansen
Copy link
Copy Markdown

@rodchristiansen rodchristiansen commented Apr 9, 2026

Summary

Adds a new C API function ghostty_config_set_color_scheme that allows setting the config's conditional theme state before finalization.

Why

Without this, ghostty_config_finalize always uses ConditionalState.theme = .light (the Zig struct default) to resolve conditional theme configs like theme = light:Foo,dark:Bar. Apps end up loading the light theme colors even when the system is in dark mode, and the background/foreground state can only be corrected after a surface color scheme change triggers a cascade re-derivation — which has race conditions and doesn't fully update the app-level default background color used by embedding apprts.

With this API, embedders can set the scheme before finalization so the config resolves the correct theme from the start.

Test plan

  • Verify ghostty_config_set_color_scheme(config, GHOSTTY_COLOR_SCHEME_DARK) before ghostty_config_finalize(config) results in the config loading dark theme conditional values
  • Verify ghostty_config_set_color_scheme(config, GHOSTTY_COLOR_SCHEME_LIGHT) loads light theme conditional values
  • Invalid scheme values emit a warning and don't modify state

Summary by cubic

Adds a C API to set the config’s color scheme before finalization so conditional themes resolve correctly from the start. This prevents apps from defaulting to light colors when the system is in dark mode.

  • New Features

    • Added ghostty_config_set_color_scheme(ghostty_config_t, ghostty_color_scheme_e) to set the conditional theme used during ghostty_config_finalize.
    • Invalid scheme values log a warning and do not change state.
  • Migration

    • Call ghostty_config_set_color_scheme(config, GHOSTTY_COLOR_SCHEME_DARK or GHOSTTY_COLOR_SCHEME_LIGHT) before ghostty_config_finalize(config).

Written for commit e0f6eba. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added a public API function to set color schemes on application configurations, allowing users to directly specify light or dark color scheme preferences.

Allows setting the config's conditional theme state before finalization
so that conditional theme resolution (e.g. `theme = light:Foo,dark:Bar`)
uses the correct scheme during ghostty_config_finalize. Without this,
finalization always uses the ConditionalState default (.light), causing
apps to load the wrong theme colors until a surface color scheme change
triggers re-derivation.
Copilot AI review requested due to automatic review settings April 9, 2026 17:54
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

A new public C API function ghostty_config_set_color_scheme() was added to enable direct color scheme configuration on a ghostty_config_t object. The implementation maps integer values to theme states with validation and logging for invalid inputs.

Changes

Cohort / File(s) Summary
Public API Declaration
include/ghostty.h
Added function declaration ghostty_config_set_color_scheme(ghostty_config_t config, ghostty_color_scheme_e scheme) to expose color scheme configuration capability.
C-API Implementation
src/config/CApi.zig
Implemented the color scheme setter that maps scheme values (0→light, 1→dark) to conditional theme state, logging warnings for invalid values.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • PR #20: Modifies propagation of conditional_state.theme from surface config into Termio DerivedConfig, directly complementing this API for setting color scheme on the config object.

Poem

🐰 A scheme setter hops through the code,
Colors bright on the C API road,
Light and dark with validation in place,
Ghostty's config finds its grace! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add ghostty_config_set_color_scheme C API' directly and concisely describes the main change: adding a new C API function for setting the color scheme in the Ghostty configuration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 9, 2026

Greptile Summary

This PR adds ghostty_config_set_color_scheme, a new C API function that lets embedders set the conditional theme state on a Config before calling ghostty_config_finalize. This ensures conditional theme resolution (e.g. theme = light:Foo,dark:Bar) picks the correct variant from the start, avoiding the race-prone post-finalization cascade the PR description outlines.

Confidence Score: 5/5

Safe to merge; all findings are P2 style suggestions that do not affect correctness.

The implementation is functionally correct — the switch values (0=light, 1=dark) match the C header constants, the warning path on invalid input is handled, and the approach is consistent with how the rest of Config.zig directly manipulates _conditional_state. Both remaining comments are purely stylistic (enum conversion pattern and missing test) and do not introduce any runtime defect.

src/config/CApi.zig — enum conversion style and missing test coverage.

Vulnerabilities

No security concerns identified. The new function only writes to a plain enum field (_conditional_state.theme) on an already-allocated Config struct; there is no heap allocation, pointer arithmetic, or external input passed through unvalidated.

Important Files Changed

Filename Overview
src/config/CApi.zig Adds ghostty_config_set_color_scheme export; uses hardcoded switch on raw integers instead of std.meta.intToEnum, inconsistent with the pattern in embedded.zig; missing unit test.
include/ghostty.h Adds ghostty_config_set_color_scheme declaration using the pre-existing ghostty_color_scheme_e typedef; placed correctly among other config API declarations.

Sequence Diagram

sequenceDiagram
    participant Embedder as Embedder (C)
    participant CApi as CApi.zig
    participant Config as Config._conditional_state

    Embedder->>CApi: ghostty_config_new()
    CApi-->>Embedder: config*

    Embedder->>CApi: ghostty_config_load_default_files(config)
    Embedder->>CApi: ghostty_config_set_color_scheme(config, DARK)
    CApi->>Config: theme = .dark

    Embedder->>CApi: ghostty_config_finalize(config)
    Note over Config: Conditional theme resolution uses .dark → loads dark theme values

    Embedder->>CApi: ghostty_config_get(config, "background", ...)
    CApi-->>Embedder: dark background color
Loading

Reviews (1): Last reviewed commit: "Add ghostty_config_set_color_scheme C AP..." | Re-trigger Greptile

Comment on lines +97 to +109
export fn ghostty_config_set_color_scheme(self: *Config, scheme_raw: c_int) void {
self._conditional_state.theme = switch (scheme_raw) {
0 => .light,
1 => .dark,
else => {
log.warn(
"invalid color scheme to ghostty_config_set_color_scheme value={}",
.{scheme_raw},
);
return;
},
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Inconsistent enum conversion pattern

The analogous functions ghostty_app_set_color_scheme and ghostty_surface_set_color_scheme in src/apprt/embedded.zig both use std.meta.intToEnum to validate and convert the raw integer, keeping a single conversion point that will produce a compile error if the enum grows. The new function hard-codes the integer literals instead, which diverges from that convention and silently becomes stale if conditional.State.Theme ever adds a variant with a non-sequential value.

Suggested change
export fn ghostty_config_set_color_scheme(self: *Config, scheme_raw: c_int) void {
self._conditional_state.theme = switch (scheme_raw) {
0 => .light,
1 => .dark,
else => {
log.warn(
"invalid color scheme to ghostty_config_set_color_scheme value={}",
.{scheme_raw},
);
return;
},
};
}
export fn ghostty_config_set_color_scheme(self: *Config, scheme_raw: c_int) void {
self._conditional_state.theme = std.meta.intToEnum(
conditional.State.Theme,
scheme_raw,
) catch {
log.warn(
"invalid color scheme to ghostty_config_set_color_scheme value={}",
.{scheme_raw},
);
return;
};
}

Comment on lines +97 to +109
export fn ghostty_config_set_color_scheme(self: *Config, scheme_raw: c_int) void {
self._conditional_state.theme = switch (scheme_raw) {
0 => .light,
1 => .dark,
else => {
log.warn(
"invalid color scheme to ghostty_config_set_color_scheme value={}",
.{scheme_raw},
);
return;
},
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing unit test

Every other exported function in this file has at least one Zig test (e.g. ghostty_config_get has six). A test that sets the scheme to dark before finalization and verifies _conditional_state.theme == .dark — and a second confirming the warning path leaves the state unchanged — would match the existing test pattern here.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new C API entry point to allow embedders to set the config’s conditional theme (light/dark) prior to config finalization, so conditional theme values resolve correctly from the start.

Changes:

  • Add ghostty_config_set_color_scheme Zig export to set Config._conditional_state.theme based on a C enum value.
  • Expose the new function in the public C header (include/ghostty.h).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/config/CApi.zig Adds the new exported C API function that sets the conditional theme before finalize.
include/ghostty.h Declares the new C API function for external consumers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const state = &@import("../global.zig").state;
const c = @import("../main_c.zig");

const conditional = @import("conditional.zig");
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const conditional = @import("conditional.zig"); is currently unused in this file, which will fail Zig compilation (unused top-level declaration). Either remove the import or use it (e.g., parse scheme_raw via std.meta.intToEnum(conditional.State.Theme, …) and assign the result).

Suggested change
const conditional = @import("conditional.zig");

Copilot uses AI. Check for mistakes.
Comment on lines +93 to +97
/// Set the color scheme on a configuration's conditional state before
/// finalization. This ensures that conditional theme resolution (e.g.
/// `theme = light:Foo,dark:Bar`) uses the correct scheme during
/// `ghostty_config_finalize`. Must be called before finalize.
export fn ghostty_config_set_color_scheme(self: *Config, scheme_raw: c_int) void {
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new C API entry point isn’t covered by tests in this file. Since src/config/CApi.zig already has unit tests, add tests for ghostty_config_set_color_scheme verifying: (1) 0/1 set _conditional_state.theme to light/dark respectively, and (2) invalid values log a warning and do not modify the existing theme value.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/config/CApi.zig (1)

93-109: Add focused tests for the new color-scheme setter behavior.

Please add coverage for DARK, LIGHT, and invalid inputs (including “state unchanged on invalid”) to prevent regressions in pre-finalize theme resolution.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/CApi.zig` around lines 93 - 109, Add scoped unit tests exercising
ghostty_config_set_color_scheme: verify that passing 0 sets
Config._conditional_state.theme to .light, passing 1 sets it to .dark, and
passing an invalid integer leaves _conditional_state.theme unchanged and logs a
warning; specifically, create tests that initialize a Config with a known
starting theme, call ghostty_config_set_color_scheme for LIGHT (0) and DARK (1)
and assert the theme value changes accordingly, then call it with an invalid
value and assert the theme remains the pre-call value while a warning was
emitted. Ensure tests invoke ghostty_config_set_color_scheme directly and assert
on Config._conditional_state.theme and the logging side-effect to cover all
three behaviors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/config/CApi.zig`:
- Around line 93-109: Add scoped unit tests exercising
ghostty_config_set_color_scheme: verify that passing 0 sets
Config._conditional_state.theme to .light, passing 1 sets it to .dark, and
passing an invalid integer leaves _conditional_state.theme unchanged and logs a
warning; specifically, create tests that initialize a Config with a known
starting theme, call ghostty_config_set_color_scheme for LIGHT (0) and DARK (1)
and assert the theme value changes accordingly, then call it with an invalid
value and assert the theme remains the pre-call value while a warning was
emitted. Ensure tests invoke ghostty_config_set_color_scheme directly and assert
on Config._conditional_state.theme and the logging side-effect to cover all
three behaviors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b5a5606f-fe26-4ad8-b525-d9c8229c47ba

📥 Commits

Reviewing files that changed from the base of the PR and between 3b684a0 and e0f6eba.

📒 Files selected for processing (2)
  • include/ghostty.h
  • src/config/CApi.zig

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

rodchristiansen added a commit to rodchristiansen/cmux that referenced this pull request Apr 9, 2026
The previous fix only handled the config reload path. If macOS auto-
switched dark/light while cmux was in the background (e.g. overnight
via the auto appearance schedule), per-view viewDidChangeEffectiveAppearance
callbacks did not always fire reliably for hidden views, leaving the
surfaces stuck in the wrong theme.

The root cause runs deeper: Ghostty's ConditionalState.theme defaults
to .light, so ghostty_config_finalize always resolved conditional
themes (e.g. `theme = light:Foo,dark:Bar`) with the light variant even
on systems running dark mode. This made defaultBackgroundColor and
the surface-level config wrong from the start.

Fix:

1. New ghostty C API: ghostty_config_set_color_scheme, called before
   ghostty_config_finalize in loadDefaultConfigFilesWithLegacyFallback
   so the config is finalized with the correct theme variant from the
   beginning. (Requires ghostty submodule bump with the new export.)

2. Deferred ghostty_app_set_color_scheme after ghostty_app_new so
   app.config_conditional_state matches the current appearance. New
   surfaces then inherit the right scheme instead of .light. Deferred
   via DispatchQueue.main.async to avoid re-entrant reloads during
   GhosttyApp initialization.

3. ghostty_app_set_color_scheme in synchronizeThemeWithAppearance to
   keep the app-level state in sync with the new appearance before the
   config reload runs.

4. KVO observer on NSApp.effectiveAppearance so appearance changes are
   detected even when no terminal view receives
   viewDidChangeEffectiveAppearance.

5. synchronizeThemeWithAppearance call in applicationDidBecomeActive
   as a backstop for when cmux regains focus after macOS switched
   appearance modes while it was inactive.

.gitmodules temporarily points ghostty at the rodchristiansen fork
while the upstream ghostty C API PR (manaflow-ai/ghostty#37) is in
review. Revert to manaflow-ai/ghostty once that merges.
rodchristiansen added a commit to rodchristiansen/cmux that referenced this pull request Apr 9, 2026
The previous fix only handled the config reload path. If macOS auto-
switched dark/light while cmux was in the background (e.g. overnight
via the auto appearance schedule), per-view viewDidChangeEffectiveAppearance
callbacks did not always fire reliably for hidden views, leaving the
surfaces stuck in the wrong theme.

The root cause runs deeper: Ghostty's ConditionalState.theme defaults
to .light, so ghostty_config_finalize always resolved conditional
themes (e.g. `theme = light:Foo,dark:Bar`) with the light variant even
on systems running dark mode. This made defaultBackgroundColor and
the surface-level config wrong from the start.

Fix:

1. New ghostty C API: ghostty_config_set_color_scheme, called before
   ghostty_config_finalize in loadDefaultConfigFilesWithLegacyFallback
   so the config is finalized with the correct theme variant from the
   beginning. (Requires ghostty submodule bump with the new export.)

2. Deferred ghostty_app_set_color_scheme after ghostty_app_new so
   app.config_conditional_state matches the current appearance. New
   surfaces then inherit the right scheme instead of .light. Deferred
   via DispatchQueue.main.async to avoid re-entrant reloads during
   GhosttyApp initialization.

3. ghostty_app_set_color_scheme in synchronizeThemeWithAppearance to
   keep the app-level state in sync with the new appearance before the
   config reload runs.

4. KVO observer on NSApp.effectiveAppearance so appearance changes are
   detected even when no terminal view receives
   viewDidChangeEffectiveAppearance.

5. synchronizeThemeWithAppearance call in applicationDidBecomeActive
   as a backstop for when cmux regains focus after macOS switched
   appearance modes while it was inactive.

.gitmodules temporarily points ghostty at the rodchristiansen fork
while the upstream ghostty C API PR (manaflow-ai/ghostty#37) is in
review. Revert to manaflow-ai/ghostty once that merges.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants