Skip to content

embedded: add Linux host support#9

Open
shuhei0866 wants to merge 5 commits intomanaflow-ai:mainfrom
shuhei0866:draft/linux-embedded-host-support
Open

embedded: add Linux host support#9
shuhei0866 wants to merge 5 commits intomanaflow-ai:mainfrom
shuhei0866:draft/linux-embedded-host-support

Conversation

@shuhei0866
Copy link

@shuhei0866 shuhei0866 commented Mar 8, 2026

Summary

Add the Linux-specific embedded host support needed for a GtkGLArea-based libghostty host.

Problem

The embedded runtime already works for the macOS host, and #8 addresses the renderer-side stale-frame behavior during resize.

That still leaves a separate gap for the Ubuntu/cmux host:

  • the public C ABI does not expose a Linux embedded platform payload
  • apprt.embedded does not recognize a Linux host surface
  • the OpenGL backend still treats embedded rendering as effectively unimplemented

Without this, the Ubuntu host is relying on local-only Ghostty changes for basic surface creation and draw-thread behavior.

Change

  • add GHOSTTY_PLATFORM_LINUX and ghostty_platform_linux_s to ghostty.h
  • teach src/apprt/embedded.zig to accept a Linux gl_area platform payload
  • mark Linux embedded as must_draw_from_app_thread
  • make src/renderer/OpenGL.zig initialize from the current embedded GL context and treat displayRealized like GTK

Why this layer

This is platform bring-up for the embedded runtime, not renderer resize policy.

  • #8 keeps resize presentation policy correct for apprt.embedded
  • this PR makes the Linux embedded host itself a first-class runtime path

Keeping them separate makes review clearer:

  • #8: renderer policy
  • this PR: Linux embedded platform support

Validation

Validated through the cmux Ubuntu host integration path:

  • the host passes a Linux GtkGLArea payload into libghostty
  • resize/draw behavior depends on the embedded runtime treating Linux like an app-thread-owned GL host
  • zig build -Dapp-runtime=none passes in this branch

Note: full zig build in this environment still stops earlier on missing local build tools (msgfmt, blueprint-compiler), which is unrelated to this diff.

Notes

  • This is a draft because it is part of the Ubuntu MVP dependency chain for cmux.
  • The full usable Ubuntu MVP currently depends on both this PR and #8.

Related draft PRs

This PR is one part of the Ghostty-side dependency chain for the Ubuntu/cmux MVP.

Scope split:

  • #8: renderer policy for apprt.embedded resize presentation
  • #9: Linux embedded host support for GtkGLArea / OpenGL bring-up

Summary by cubic

Add Linux embedded host support for GtkGLArea so Linux hosts can create a surface and render via the embedded runtime. OpenGL now initializes from the host-owned context, draws on the app thread, and the resize stale-frame guard override is limited to Linux to avoid stale visuals during interactive resize.

  • New Features

    • C ABI: adds GHOSTTY_PLATFORM_LINUX and ghostty_platform_linux_s { gl_area } to ghostty.h.
    • Embedded runtime: accepts a Linux gl_area payload; sets must_draw_from_app_thread on Linux.
    • OpenGL: initializes from the current embedded GL context; treats displayRealized like GTK; no thread setup for embedded.
  • Bug Fixes

    • Renderer: disable the sync stale-frame guard only for Linux embedded; keep it for other embedded hosts (e.g., Darwin) to prevent stale frames during interactive resize.

Written for commit 9b0febb. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added Linux platform support to the embedding API so apps can run embedded GUIs on Linux.
    • Exposed Linux-specific platform configuration to allow proper graphics/GL area handling on Linux.
    • Improved embedded runtime initialization to correctly prepare OpenGL contexts across Linux, macOS, and iOS.

@coderabbitai
Copy link

coderabbitai bot commented Mar 8, 2026

📝 Walkthrough

Walkthrough

Linux platform support added: public header types for Linux, embedded runtime wiring to carry a Linux GL area through the C ABI, and renderer updates to initialize and gate OpenGL behavior for embedded/Linux contexts.

Changes

Cohort / File(s) Summary
Public API (header)
include/ghostty.h
Added GHOSTTY_PLATFORM_LINUX enum; new ghostty_platform_linux_s { void* gl_area; }; added linux member to ghostty_platform_u.
Embedded runtime
src/apprt/embedded.zig
Added .linux Platform variant and PlatformTag; introduced Linux struct exposing gl_area: *anyopaque; added must_draw_from_app_thread = true for Linux and init logic to load linux.gl_area from C ABI, returning UnsupportedPlatform when absent.
OpenGL renderer
src/renderer/OpenGL.zig
For embedded runtimes, call prepareContext(null) in surfaceInit/displayRealized to load GL function pointers; treat embedded same as GTK for prepareContext and update error messaging.
Generic renderer
src/renderer/generic.zig
Added platform-aware guard (use_sync_stale_frame_guard) around stale-frame re-present logic: disabled for embedded Linux, enabled for other embedded and non-embedded runtimes.

Sequence Diagram(s)

sequenceDiagram
    participant App as App Runtime
    participant CABI as C ABI (ghostty_platform_u)
    participant Renderer as OpenGL Renderer
    participant GLArea as GtkGLArea

    App->>CABI: read PlatformTag (.linux) and linux.gl_area
    CABI-->>App: return linux.gl_area pointer
    App->>Renderer: init with platform.linux (gl_area)
    Renderer->>GLArea: prepareContext(null) to load GL functions
    Renderer-->>App: surfaceInit / displayRealized complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 In a Linux glade the GL area sleeps,
I wake it gently where the GTK keeps,
Threads steady, contexts tidy and neat,
Embedded hops forward on tiny feet,
I chew a carrot and ship the feats. 🌿

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'embedded: add Linux host support' clearly and concisely describes the main change—adding Linux support to the embedded runtime. The title accurately summarizes the primary objective of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@shuhei0866
Copy link
Author

The current failing check is not caused by the diff itself. The Vouch - Check PR workflow was cancelled after waiting 24h for a runner, so I am re-running it and moving this PR to review-ready in parallel.

@shuhei0866 shuhei0866 marked this pull request as ready for review March 9, 2026 15:51
@greptile-apps
Copy link

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR adds Linux embedded host support for GtkGLArea-based libghostty consumers, completing the platform bring-up needed by the Ubuntu/cmux MVP. It wires up the Linux platform payload through the C ABI (ghostty.h), the embedded apprt (embedded.zig), and the OpenGL renderer (OpenGL.zig), making the embedded runtime treat a Linux host the same way it treats GTK.

Key changes:

  • ghostty.h: adds GHOSTTY_PLATFORM_LINUX enum value, ghostty_platform_linux_s { gl_area } struct, and a linux member in ghostty_platform_u
  • embedded.zig: adds must_draw_from_app_thread = true on Linux, a Linux platform variant holding the GtkGLArea pointer, and the corresponding Platform.init dispatch branch
  • OpenGL.zig: activates prepareContext for embedded in surfaceInit, removes the "unimplemented" placeholder from threadEnter, and extends displayRealized to include apprt.embedded alongside apprt.gtk

Issue found:

  • The linux field name added to ghostty_platform_u in ghostty.h conflicts with the linux predefined preprocessor macro present in GCC/Clang on Linux when using GNU extensions (the default mode for GTK application builds). This will cause a compile error in any C consumer that includes the header on Linux. The field should be renamed (e.g. linux_platform) to avoid the collision.

Confidence Score: 2/5

  • Not safe to merge without fixing the linux macro conflict in ghostty.h, which will break compilation for the primary consumers of this feature.
  • The Zig-side changes in embedded.zig and OpenGL.zig are logically sound and consistent with existing GTK patterns. However, ghostty.h uses linux as a C struct field name, which collides with the linux predefined macro on Linux/GCC in GNU-extensions mode — the exact build environment used by GtkGLArea/GTK hosts. This is a blocking compile error for the feature's primary consumers.
  • include/ghostty.h — the linux member name in ghostty_platform_u must be renamed before this header can be safely included in Linux C/C++ code built with GNU extensions.

Important Files Changed

Filename Overview
include/ghostty.h Adds GHOSTTY_PLATFORM_LINUX, ghostty_platform_linux_s, and a linux member to ghostty_platform_u. The linux field name conflicts with the predefined linux preprocessor macro on Linux/GCC, causing a compile error in any C consumer built with GNU extensions (the common default for GTK apps).
src/apprt/embedded.zig Adds must_draw_from_app_thread for Linux, a Linux platform variant, and the corresponding Platform.init branch for Linux. Logic and structure are consistent with the existing macOS/iOS patterns, with a minor style inconsistency in error naming for null gl_area.
src/renderer/OpenGL.zig Activates prepareContext for embedded in surfaceInit, clears the "unimplemented" placeholder in threadEnter, and extends displayRealized to cover apprt.embedded alongside apprt.gtk. All changes follow existing GTK patterns correctly.

Sequence Diagram

sequenceDiagram
    participant Host as GtkGLArea Host (C)
    participant CABI as ghostty.h (C ABI)
    participant Embedded as apprt/embedded.zig
    participant OGL as renderer/OpenGL.zig
    participant Thread as renderer/Thread.zig

    Host->>CABI: ghostty_surface_new(platform_tag=LINUX, platform.linux.gl_area)
    CABI->>Embedded: Platform.init(.linux, C{gl_area})
    Embedded->>Embedded: validate gl_area != null
    Embedded-->>CABI: Platform{ .linux = { gl_area } }

    Host->>OGL: surfaceInit(surface)
    Note over OGL: Host has already made GL context current
    OGL->>OGL: prepareContext(null) — load GL fn ptrs

    OGL->>OGL: threadEnter() — no-op (host owns context)

    Note over Thread: must_draw_from_app_thread = true on Linux
    Thread->>Host: app_mailbox.push(redraw_surface) instead of direct drawFrame

    Host->>OGL: displayRealized()
    OGL->>OGL: prepareContext(null) — re-load GL fn ptrs
Loading

Last reviewed commit: 4d5e3bf

typedef union {
ghostty_platform_macos_s macos;
ghostty_platform_ios_s ios;
ghostty_platform_linux_s linux;
Copy link

Choose a reason for hiding this comment

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

linux field name conflicts with predefined GCC/Clang macro

On Linux, GCC and Clang in GNU-extensions mode (e.g. -std=gnu11, which is the default and common when building GTK apps) predefine linux as a macro that expands to 1. Any C translation unit that #includes this header on Linux will fail to compile because the field declaration expands to:

ghostty_platform_linux_s 1;  // preprocessor expansion of `linux` → compile error

The macOS and iOS fields (macos, ios) do not have this problem because macos and ios are not predefined macros on those platforms.

The fix is to use a name that doesn't collide with the predefined macro. Options include:

typedef union {
  ghostty_platform_macos_s macos;
  ghostty_platform_ios_s ios;
  ghostty_platform_linux_s linux_platform;  // or linux_host, gl_area_platform, etc.
} ghostty_platform_u;

Or, alternatively, #undef linux at the top of the header (before the union) and restore it afterward with #ifdef __linux__ / #define linux 1. However, renaming the field is cleaner and avoids unexpected side-effects for downstream consumers. The Zig-side Platform.C.linux and Platform.linux variant names would need to be updated to match.

Comment on lines +402 to +407
.linux => if (Linux != void) linux: {
const config = c_platform.linux;
const gl_area = config.gl_area orelse
break :linux error.UnsupportedPlatform;
break :linux .{ .linux = .{ .gl_area = gl_area } };
} else error.UnsupportedPlatform,
Copy link

Choose a reason for hiding this comment

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

Null gl_area returns misleading error

When gl_area is null, the code returns error.UnsupportedPlatform, but that error is also used for the cross-platform case (building for non-Linux and passing GHOSTTY_PLATFORM_LINUX). A caller on Linux who simply forgets to set gl_area will receive an error that suggests the entire platform is unsupported, which will be confusing to debug.

The iOS branch uses the more descriptive error.UIViewMustBeSet for the analogous case. Consider using a similarly specific error here:

Suggested change
.linux => if (Linux != void) linux: {
const config = c_platform.linux;
const gl_area = config.gl_area orelse
break :linux error.UnsupportedPlatform;
break :linux .{ .linux = .{ .gl_area = gl_area } };
} else error.UnsupportedPlatform,
.linux => if (Linux != void) linux: {
const config = c_platform.linux;
const gl_area = config.gl_area orelse
break :linux error.GlAreaMustBeSet;
break :linux .{ .linux = .{ .gl_area = gl_area } };
} else error.UnsupportedPlatform,

Copy link

@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/apprt/embedded.zig (1)

401-407: Consider using a more specific error for null gl_area.

When gl_area is null, returning error.UnsupportedPlatform is inconsistent with the macOS/iOS paths which return dedicated errors (error.NSViewMustBeSet, error.UIViewMustBeSet). A specific error like error.GLAreaMustBeSet would provide better diagnostics for host developers.

Proposed change
             .linux => if (Linux != void) linux: {
                 const config = c_platform.linux;
                 const gl_area = config.gl_area orelse
-                    break :linux error.UnsupportedPlatform;
+                    break :linux error.GLAreaMustBeSet;
                 break :linux .{ .linux = .{ .gl_area = gl_area } };
             } else error.UnsupportedPlatform,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/apprt/embedded.zig` around lines 401 - 407, The linux path currently maps
a null gl_area to error.UnsupportedPlatform; change this to a specific error
(e.g., error.GLAreaMustBeSet) for clarity: in the .linux branch that reads const
gl_area = config.gl_area orelse break :linux error.UnsupportedPlatform, replace
the error with break :linux error.GLAreaMustBeSet and add the corresponding
error declaration (error.GLAreaMustBeSet) to the error set used by the
surrounding function/enum so callers see a distinct, descriptive failure instead
of UnsupportedPlatform.
🤖 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/apprt/embedded.zig`:
- Around line 401-407: The linux path currently maps a null gl_area to
error.UnsupportedPlatform; change this to a specific error (e.g.,
error.GLAreaMustBeSet) for clarity: in the .linux branch that reads const
gl_area = config.gl_area orelse break :linux error.UnsupportedPlatform, replace
the error with break :linux error.GLAreaMustBeSet and add the corresponding
error declaration (error.GLAreaMustBeSet) to the error set used by the
surrounding function/enum so callers see a distinct, descriptive failure instead
of UnsupportedPlatform.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92680cbf-43f4-45c0-8747-a54a10a135ae

📥 Commits

Reviewing files that changed from the base of the PR and between 1b008f5 and 4d5e3bf.

📒 Files selected for processing (3)
  • include/ghostty.h
  • src/apprt/embedded.zig
  • src/renderer/OpenGL.zig

Copy link

@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 3 files

Copy link

@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/renderer/generic.zig (1)

1503-1506: Prefer a capability flag here instead of builtin.target.os.tag.

This makes shared renderer policy depend on a specific OS rather than the surface/runtime contract that actually drives the resize behavior. If another embedded host needs different sync-resize handling later, generic.zig will need more platform branching again. Pushing this behind an apprt/GraphicsAPI capability would keep the shared renderer generic.

As per coding guidelines, "Maintain shared Zig core in the src/ directory".

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

In `@src/renderer/generic.zig` around lines 1503 - 1506, The current logic for
use_sync_stale_frame_guard uses builtin.target.os.tag which ties shared renderer
behavior to a specific OS; instead add a capability flag on the apprt (or the
GraphicsAPI/runtime capability struct) such as supports_sync_stale_frame_guard
and use that in the switch that computes use_sync_stale_frame_guard (replace
builtin.target.os.tag check with apprt.supports_sync_stale_frame_guard); update
the embedded host initializations that set apprt.runtime to also set this
capability appropriately for Linux vs non‑Linux embedded hosts so the
platform-specific decision is owned by the host capability rather than by
generic.zig.
🤖 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/renderer/generic.zig`:
- Around line 1503-1506: The current logic for use_sync_stale_frame_guard uses
builtin.target.os.tag which ties shared renderer behavior to a specific OS;
instead add a capability flag on the apprt (or the GraphicsAPI/runtime
capability struct) such as supports_sync_stale_frame_guard and use that in the
switch that computes use_sync_stale_frame_guard (replace builtin.target.os.tag
check with apprt.supports_sync_stale_frame_guard); update the embedded host
initializations that set apprt.runtime to also set this capability appropriately
for Linux vs non‑Linux embedded hosts so the platform-specific decision is owned
by the host capability rather than by generic.zig.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 18ba3431-0516-47e3-be3a-b2ed2dd2e34e

📥 Commits

Reviewing files that changed from the base of the PR and between 4d5e3bf and 10cb29c.

📒 Files selected for processing (1)
  • src/renderer/generic.zig

@shuhei0866 shuhei0866 force-pushed the draft/linux-embedded-host-support branch from 10cb29c to 9b0febb Compare March 11, 2026 04:28
Copy link

@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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/apprt/embedded.zig`:
- Around line 402-406: The Linux branch currently returns
error.UnsupportedPlatform when config.gl_area is missing; change that to a
distinct error (e.g., error.MissingLinuxGLArea) so missing caller configuration
is distinguishable from an unsupported build. Update the break at the gl_area
check (the line using "const gl_area = config.gl_area orelse break :linux
error.UnsupportedPlatform;") to return the new error variant instead, and add
the new error symbol to the module's error union/enum where other errors are
declared so the code compiles and callers can handle this specific
missing-configuration case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1d9c933e-2e7b-492b-b300-86ed7677b6ff

📥 Commits

Reviewing files that changed from the base of the PR and between 10cb29c and 9b0febb.

📒 Files selected for processing (4)
  • include/ghostty.h
  • src/apprt/embedded.zig
  • src/renderer/OpenGL.zig
  • src/renderer/generic.zig
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/OpenGL.zig

Comment on lines +402 to +406
.linux => if (Linux != void) linux: {
const config = c_platform.linux;
const gl_area = config.gl_area orelse
break :linux error.UnsupportedPlatform;
break :linux .{ .linux = .{ .gl_area = gl_area } };
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a distinct error when gl_area is missing.

Returning error.UnsupportedPlatform here makes “Linux embedding isn't built in” indistinguishable from “the caller forgot to populate platform.linux.gl_area”, which makes host-side init failures harder to diagnose.

🩹 Proposed fix
-                const gl_area = config.gl_area orelse
-                    break :linux error.UnsupportedPlatform;
+                const gl_area = config.gl_area orelse
+                    break :linux error.GtkGLAreaMustBeSet;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/apprt/embedded.zig` around lines 402 - 406, The Linux branch currently
returns error.UnsupportedPlatform when config.gl_area is missing; change that to
a distinct error (e.g., error.MissingLinuxGLArea) so missing caller
configuration is distinguishable from an unsupported build. Update the break at
the gl_area check (the line using "const gl_area = config.gl_area orelse break
:linux error.UnsupportedPlatform;") to return the new error variant instead, and
add the new error symbol to the module's error union/enum where other errors are
declared so the code compiles and callers can handle this specific
missing-configuration case.

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.

1 participant