feat(core): port App/Surface and wire up the runnable ghostty binary
This is the integration step that turns the ported crates into a working
terminal: `cargo run` now opens a GTK window running the user's shell.
ghostty-core ports App.zig, Surface.zig (split into keyboard, mouse,
binding, clipboard and config submodules), surface_mouse.zig and
global.zig, and implements the runtime traits. perform_binding_action
handles every binding action.
Cross-crate integration:
- The core<->runtime traits (CoreApp, CoreSurface, RtApp, RtSurface,
Waker) move from apprt-gtk into ghostty_apprt::runtime_api so the core
stays runtime-agnostic; Scrollbar/RendererHealth stand-ins are replaced
by the real terminal/renderer types.
- termio now uses ghostty_renderer::state::{State, SharedState}; the
renderer calls kitty_update; image-storage-limit is applied at init
and on config change.
- The search thread locks the terminal through a TerminalLock trait so
it can share the render state mutex; termio's Wakeup gained a hook to
wake the renderer thread.
- apprt-gtk: GL proc addresses are resolved through libepoxy's
epoxy_glFoo pointer variables (plain glFoo symbols resolved to null),
default DPI fixed to 96, libadwaita version read without GTK init so
--version works headless.
The root binary dispatches +actions to ghostty-cli (list-fonts,
show-face and new-window are now wired) and otherwise runs the GTK app
with the real core.
Verified at runtime on Wayland and X11: shell prompt and colored output
render, child exit closes the window cleanly. Not yet verified: keyboard
and mouse input, clipboard, IME, tabs and splits (no key injection was
possible in this environment). GHOSTTY_DEBUG_SCREENSHOT=<png> dumps the
renderer's last frame for headless checks.
Test Plan:
- cargo test --workspace (2890 passed)
- cargo clippy --workspace --all-targets -- -D warnings
- GSETTINGS_BACKEND=memory GHOSTTY_RESOURCES_DIR=/usr/share/ghostty \
cargo run
This commit is contained in:
1 parent
e256c42e53
commit
ad76ae2301
42 files changed
+9301
-179
No files matched your search
@@ -19,14 +19,14 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use ghostty_apprt::action::{self, Action, Target};
|
||||
use ghostty_apprt::runtime_api::{
|
||||
AppMessage, CellSize, ClipboardError, CoreApp, CoreError, CoreSurface, DesiredFontSize,
|
||||
InputEffect, KeyEvent, RtApp, RtSurface, SurfaceId,
|
||||
};
|
||||
use ghostty_apprt::structs::{
|
||||
ClipboardRequest, ColorScheme, ContentScale, CursorPos, ImePos, SurfaceSize,
|
||||
};
|
||||
use ghostty_apprt_gtk::app::App;
|
||||
use ghostty_apprt_gtk::core::{
|
||||
AppMessage, CellSize, ClipboardError, CoreApp, CoreError, CoreSurface, DesiredFontSize,
|
||||
InputEffect, KeyEvent, RtApp, RtSurface, SurfaceId,
|
||||
};
|
||||
use ghostty_config::config::Config;
|
||||
use ghostty_input::binding::set::Value as BindingValue;
|
||||
use ghostty_input::binding::{self, Action as Binding};
|
||||
@@ -204,9 +204,8 @@ impl CoreSurface for DemoSurface {
|
||||
fn renderer_realized(&self) -> Result<(), CoreError> {
|
||||
// SAFETY: the GLArea context is current while realizing; the loader
|
||||
// resolves symbols through libepoxy which dispatches to it.
|
||||
let gl = unsafe {
|
||||
glow::Context::from_loader_function(ghostty_apprt_gtk::gl::proc_address)
|
||||
};
|
||||
let gl =
|
||||
unsafe { glow::Context::from_loader_function(ghostty_apprt_gtk::gl::proc_address) };
|
||||
*self.gl.borrow_mut() = Some(gl);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,12 +6,20 @@ use crate::gtk_version::Version;
|
||||
pub const COMPTIME_VERSION: Version = (1, 5, 0);
|
||||
|
||||
/// Returns the runtime libadwaita version.
|
||||
#[allow(unsafe_code)]
|
||||
pub fn get_runtime_version() -> Version {
|
||||
(
|
||||
adw::major_version(),
|
||||
adw::minor_version(),
|
||||
adw::micro_version(),
|
||||
)
|
||||
// The safe `adw::major_version()` etc. assert that libadwaita was
|
||||
// initialized, but the C functions don't need it (and `+version` has no
|
||||
// display to initialize GTK with).
|
||||
// SAFETY: these functions only return compile-time constants of the
|
||||
// loaded library and have no preconditions.
|
||||
unsafe {
|
||||
(
|
||||
adw::ffi::adw_get_major_version(),
|
||||
adw::ffi::adw_get_minor_version(),
|
||||
adw::ffi::adw_get_micro_version(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs the build and runtime libadwaita version.
|
||||
|
||||
@@ -14,7 +14,7 @@ use ghostty_apprt::action::{Action, Target};
|
||||
use ghostty_apprt::ipc;
|
||||
|
||||
use crate::class::application::{Application, RunError};
|
||||
use crate::core::{CoreApp, SurfaceId, Waker};
|
||||
use ghostty_apprt::runtime_api::{CoreApp, SurfaceId, Waker};
|
||||
|
||||
/// This is detected by the Renderer, in which case it sends a
|
||||
/// `redraw_surface` message so that we can call `drawFrame` ourselves from
|
||||
|
||||
@@ -41,9 +41,9 @@ use crate::class::split_tree::SplitTree;
|
||||
use crate::class::surface::{Size, Surface};
|
||||
use crate::class::tab::Tab;
|
||||
use crate::class::window::{SelectTab, TabOverrides, Window};
|
||||
use crate::core::{AppMessage, CoreApp, RtApp, SurfaceId};
|
||||
use crate::css::CssProviders;
|
||||
use crate::winproto;
|
||||
use ghostty_apprt::runtime_api::{AppMessage, CoreApp, RtApp, SurfaceId};
|
||||
|
||||
/// Errors from [`Application::run_loop`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! scrolled window), embedding a `GtkGLArea` that the core renders into.
|
||||
//!
|
||||
//! The "apprt Surface" of Zig (`Surface.zig`, the object the core calls back
|
||||
//! into) is [`RtSurfaceHandle`], which implements [`crate::core::RtSurface`]
|
||||
//! into) is [`RtSurfaceHandle`], which implements [`ghostty_apprt::runtime_api::RtSurface`]
|
||||
//! and holds only a weak reference to the widget.
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
@@ -43,11 +43,11 @@ use crate::class::search_overlay::SearchOverlay;
|
||||
use crate::class::surface_child_exited::SurfaceChildExited;
|
||||
use crate::class::title_dialog::{self, TitleDialog};
|
||||
use crate::class::window::Window;
|
||||
use crate::core::{
|
||||
use crate::key as gtk_key;
|
||||
use ghostty_apprt::runtime_api::{
|
||||
ClipboardError, CoreSurface, DesiredFontSize, InputEffect, MIN_WINDOW_HEIGHT_CELLS,
|
||||
MIN_WINDOW_WIDTH_CELLS, RtSurface, SurfaceId,
|
||||
};
|
||||
use crate::key as gtk_key;
|
||||
|
||||
/// Simple dimensions struct for the surface used by various properties
|
||||
/// (Zig: `Surface.Size`, GType `GhosttySurfaceSize`).
|
||||
@@ -1834,6 +1834,29 @@ impl RtSurface for RtSurfaceHandle {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn gl_proc_address(&self, name: &str) -> *const std::ffi::c_void {
|
||||
crate::gl::proc_address(name)
|
||||
}
|
||||
|
||||
fn create_scope_fn(&self) -> Option<ghostty_apprt::runtime_api::CreateScopeFn> {
|
||||
// GDBusConnection is thread-safe; the closure runs on the IO thread
|
||||
// right after fork.
|
||||
let dbus = gtk::gio::Application::default().and_then(|a| a.dbus_connection())?;
|
||||
Some(std::sync::Arc::new(move |pid, memory_high, tasks_max| {
|
||||
Some(
|
||||
crate::cgroup::create_scope(
|
||||
&dbus,
|
||||
pid,
|
||||
crate::cgroup::Options {
|
||||
memory_high,
|
||||
tasks_max,
|
||||
},
|
||||
)
|
||||
.map_err(|e| e.to_string()),
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
fn redraw_inspector(&self) {
|
||||
if let Some(s) = self.gobj() {
|
||||
s.redraw_inspector();
|
||||
|
||||
@@ -1,282 +0,0 @@
|
||||
//! The trait boundary between this app runtime and `ghostty-core`.
|
||||
//!
|
||||
//! In Zig, `apprt/gtk` calls directly into `CoreApp` (App.zig) and
|
||||
//! `CoreSurface` (Surface.zig). `ghostty-core` isn't ported yet (and it can't
|
||||
//! be a dependency of this crate without a cycle once core implements these
|
||||
//! traits), so the exact surface of what GTK needs is expressed here:
|
||||
//!
|
||||
//! * [`CoreApp`] / [`CoreSurface`]: implemented by ghostty-core, called by GTK.
|
||||
//! * [`RtApp`] / [`RtSurface`]: implemented by this crate, handed to core so it
|
||||
//! can call back (Zig: `rt_app.performAction`, `rt_surface.getSize()`, ...).
|
||||
//!
|
||||
//! Threading: everything here is **main-thread only** (`!Send`), like the GTK
|
||||
//! objects behind it. The only cross-thread entry point is [`Waker`]
|
||||
//! (`App.wakeup`), used by core threads to request an app `tick()`.
|
||||
//!
|
||||
//! See `docs/rewrite/notes/ghostty-apprt-gtk.md` for the full spec.
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ghostty_apprt::action::{Action, Target};
|
||||
use ghostty_apprt::structs::{
|
||||
Clipboard, ClipboardRequest, ColorScheme, ContentScale, CursorPos, ImePos, SurfaceSize,
|
||||
};
|
||||
use ghostty_config::config::Config;
|
||||
use ghostty_input::{Mods, MouseButton, MouseButtonState, ScrollMods};
|
||||
|
||||
/// Identifier for a core surface (Zig: `*CoreSurface` pointer). Allocated by
|
||||
/// this crate for each GTK surface; core stores it in its `Target::Surface`.
|
||||
pub type SurfaceId = u64;
|
||||
|
||||
/// Minimum window size in cells (Zig: `CoreSurface.min_window_{width,height}_cells`).
|
||||
pub const MIN_WINDOW_WIDTH_CELLS: u32 = 10;
|
||||
/// See [`MIN_WINDOW_WIDTH_CELLS`].
|
||||
pub const MIN_WINDOW_HEIGHT_CELLS: u32 = 4;
|
||||
|
||||
/// Result of `CoreSurface::key_callback` (Zig: `Surface.InputEffect`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum InputEffect {
|
||||
/// The key was consumed by a binding that closed the surface; the
|
||||
/// surface may no longer be valid.
|
||||
Closed,
|
||||
/// The key wasn't used.
|
||||
Ignored,
|
||||
/// The key was used (binding performed or sent to the pty).
|
||||
Consumed,
|
||||
}
|
||||
|
||||
/// Errors from `CoreSurface::complete_clipboard_request` that the GTK side
|
||||
/// reacts to (Zig error set: `UnsafePaste`, `UnauthorizedPaste`).
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ClipboardError {
|
||||
#[error("unsafe paste")]
|
||||
UnsafePaste,
|
||||
#[error("unauthorized paste")]
|
||||
UnauthorizedPaste,
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
/// Generic core error (Zig: `anyerror`).
|
||||
pub type CoreError = Box<dyn std::error::Error + Send + Sync>;
|
||||
|
||||
/// A message sent by the GTK side to the core app mailbox
|
||||
/// (Zig: `core_app.mailbox.push`).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AppMessage {
|
||||
/// Open the config file in the default editor (`App.Message.open_config`).
|
||||
OpenConfig,
|
||||
/// `App.Message.new_window` (queued by `activate`; core responds with
|
||||
/// `rt.perform_action(Target::App, Action::NewWindow)`).
|
||||
NewWindow,
|
||||
/// `surface_message{ surface, .present_surface }`; core validates the id.
|
||||
PresentSurface(SurfaceId),
|
||||
}
|
||||
|
||||
/// Desired font size request used when initializing a surface (Zig:
|
||||
/// `font.face.DesiredSize` points).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct DesiredFontSize {
|
||||
pub points: f32,
|
||||
pub xdpi: u16,
|
||||
pub ydpi: u16,
|
||||
}
|
||||
|
||||
/// Input to `CoreSurface::key_callback`: `ghostty_input::KeyEvent` is used
|
||||
/// directly.
|
||||
pub use ghostty_input::KeyEvent;
|
||||
|
||||
/// Cell metrics for a config+DPI combo (Zig: `font_grid_set.ref(..)` ->
|
||||
/// `grid.cellSize()`), used to compute the default window size.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct CellSize {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
/// Implemented by ghostty-core's `App`.
|
||||
pub trait CoreApp {
|
||||
/// `App.tick`: drain the app mailbox and perform pending work. Core calls
|
||||
/// back into `rt` (e.g. `rt.perform_action`). Called on the main thread
|
||||
/// whenever a [`Waker`] wake was requested, and after config updates.
|
||||
fn tick(&self, rt: &dyn RtApp) -> Result<(), CoreError>;
|
||||
|
||||
/// `App.updateConfig`: apply a new config to the app and all surfaces
|
||||
/// (surfaces get `Message::ChangeConfig`). Returns Ok on success.
|
||||
fn update_config(&self, rt: &dyn RtApp, config: &Arc<Config>) -> Result<(), CoreError>;
|
||||
|
||||
/// `App.needsConfirmQuit`: any surface with a running process?
|
||||
fn needs_confirm_quit(&self) -> bool;
|
||||
|
||||
/// `App.colorSchemeEvent`: the system color scheme changed.
|
||||
fn color_scheme_event(&self, rt: &dyn RtApp, scheme: ColorScheme);
|
||||
|
||||
/// `App.performAllAction`: perform `action` on every surface
|
||||
/// (`rt.perform_action(Target::Surface(id), ..)` for each).
|
||||
fn perform_all_action(
|
||||
&self,
|
||||
rt: &dyn RtApp,
|
||||
action: &ghostty_input::binding::Action,
|
||||
) -> Result<(), CoreError>;
|
||||
|
||||
/// `App.mailbox.push(.., .forever)`.
|
||||
fn push_message(&self, msg: AppMessage);
|
||||
|
||||
/// `App.addSurface` + `Surface.init`: create a core surface for a GTK
|
||||
/// surface. `config` is the (possibly modified) surface config. The
|
||||
/// returned surface is owned by the GTK surface. `rt_surface` is the
|
||||
/// back-reference core uses (Zig: `&priv.rt_surface`).
|
||||
fn new_surface(
|
||||
&self,
|
||||
id: SurfaceId,
|
||||
config: Config,
|
||||
rt_app: Rc<dyn RtApp>,
|
||||
rt_surface: Rc<dyn RtSurface>,
|
||||
) -> Result<Rc<dyn CoreSurface>, CoreError>;
|
||||
|
||||
/// `App.deleteSurface`: called when the GTK surface is finalized, after
|
||||
/// the `CoreSurface` was dropped.
|
||||
fn delete_surface(&self, id: SurfaceId);
|
||||
|
||||
/// Ids of all live surfaces (`core_app.surfaces.items`).
|
||||
fn surface_ids(&self) -> Vec<SurfaceId>;
|
||||
|
||||
/// Cell size for the given config and DPI, used for the default window
|
||||
/// size (`font_grid_set.ref(..).cellSize()`); None if fonts fail.
|
||||
fn cell_size(&self, config: &Config, font_size: DesiredFontSize) -> Option<CellSize>;
|
||||
}
|
||||
|
||||
/// Implemented by ghostty-core's `Surface`. All methods take `&self`
|
||||
/// (interior mutability inside core), main thread only.
|
||||
pub trait CoreSurface {
|
||||
/// The config the surface was created with / last updated to.
|
||||
fn config(&self) -> Arc<Config>;
|
||||
|
||||
// ---- input -------------------------------------------------------
|
||||
fn key_callback(&self, event: KeyEvent<'_>) -> Result<InputEffect, CoreError>;
|
||||
/// `preeditCallback(null)` clears.
|
||||
fn preedit_callback(&self, text: Option<&str>) -> Result<(), CoreError>;
|
||||
fn focus_callback(&self, focused: bool) -> Result<(), CoreError>;
|
||||
/// Returns true if the click was consumed.
|
||||
fn mouse_button_callback(
|
||||
&self,
|
||||
state: MouseButtonState,
|
||||
button: MouseButton,
|
||||
mods: Mods,
|
||||
) -> Result<bool, CoreError>;
|
||||
/// `pos` is in surface (logical*scale) pixels; `(-1,-1)` = left window.
|
||||
fn cursor_pos_callback(&self, pos: CursorPos, mods: Option<Mods>) -> Result<(), CoreError>;
|
||||
fn scroll_callback(&self, xoff: f64, yoff: f64, mods: ScrollMods) -> Result<(), CoreError>;
|
||||
fn mouse_pressure_callback(&self, _stage: ghostty_input::MousePressureStage, _pressure: f64) {}
|
||||
fn size_callback(&self, size: SurfaceSize) -> Result<(), CoreError>;
|
||||
fn content_scale_callback(&self, scale: ContentScale) -> Result<(), CoreError>;
|
||||
/// IME candidate window position in surface pixels (`imePoint`).
|
||||
fn ime_point(&self) -> ImePos;
|
||||
fn has_selection(&self) -> bool;
|
||||
/// `colorSchemeCallback`: the system color scheme changed.
|
||||
fn color_scheme_callback(&self, scheme: ColorScheme) -> Result<(), CoreError>;
|
||||
/// `Surface.updateConfig`: apply a new config to just this surface.
|
||||
fn update_config(&self, config: &Arc<Config>) -> Result<(), CoreError>;
|
||||
/// Perform a keybinding action on this surface; returns whether it was
|
||||
/// performed.
|
||||
fn perform_binding_action(
|
||||
&self,
|
||||
action: &ghostty_input::binding::Action,
|
||||
) -> Result<bool, CoreError>;
|
||||
|
||||
// ---- clipboard ---------------------------------------------------
|
||||
/// `completeClipboardRequest(req, data, confirmed)`.
|
||||
fn complete_clipboard_request(
|
||||
&self,
|
||||
req: ClipboardRequest,
|
||||
data: &str,
|
||||
confirmed: bool,
|
||||
) -> Result<(), ClipboardError>;
|
||||
/// After the user confirms (`surface.config.clipboard_read = .allow`).
|
||||
fn set_clipboard_read_allowed(&self, allow: bool);
|
||||
/// See [`CoreSurface::set_clipboard_read_allowed`] (`clipboard_write`).
|
||||
fn set_clipboard_write_allowed(&self, allow: bool);
|
||||
|
||||
// ---- lifecycle / rendering ----------------------------------------
|
||||
/// Whether closing needs confirmation (`needsConfirmQuit`).
|
||||
fn needs_confirm_quit(&self) -> bool;
|
||||
/// Whether the child exited (`child_exited`) - used by close logic.
|
||||
fn child_exited(&self) -> bool;
|
||||
/// `renderer.displayRealized()`: GLArea realized, GL context current.
|
||||
fn renderer_realized(&self) -> Result<(), CoreError>;
|
||||
/// `renderer.displayUnrealized()`: GL context current (if it could be).
|
||||
fn renderer_unrealized(&self);
|
||||
/// `renderer.drawFrame(true)`: draw into the currently bound GLArea
|
||||
/// framebuffer (GL context is current). `default_framebuffer` is the
|
||||
/// GLArea's FBO id (`GL_FRAMEBUFFER_BINDING`).
|
||||
fn draw_frame(&self, default_framebuffer: u32) -> Result<(), CoreError>;
|
||||
|
||||
// ---- misc state read by the surface widget (added by F5) -------------
|
||||
/// The `SurfaceId` this core surface was created with (`new_surface`'s
|
||||
/// `id`); lets the GTK side map a core surface back to its widget.
|
||||
fn id(&self) -> Option<SurfaceId> {
|
||||
None
|
||||
}
|
||||
/// `surface.readonly` (read-only mode state).
|
||||
fn readonly(&self) -> bool {
|
||||
false
|
||||
}
|
||||
/// `surface.size.grid()` -> (columns, rows); used by the resize overlay.
|
||||
fn grid_size(&self) -> (u32, u32) {
|
||||
(0, 0)
|
||||
}
|
||||
/// `surface.font_size` (inherited by child surfaces in `setParent`).
|
||||
fn font_size(&self) -> DesiredFontSize {
|
||||
DesiredFontSize {
|
||||
points: 12.0,
|
||||
xdpi: 96,
|
||||
ydpi: 96,
|
||||
}
|
||||
}
|
||||
|
||||
// ---- inspector (deferred) -------------------------------------------
|
||||
fn activate_inspector(&self) -> Result<(), CoreError> {
|
||||
Ok(())
|
||||
}
|
||||
fn deactivate_inspector(&self) {}
|
||||
}
|
||||
|
||||
/// Implemented by this crate (GTK `Application`); handed to core.
|
||||
pub trait RtApp {
|
||||
/// `App.performAction`. Returns whether the action was performed.
|
||||
/// Runs on the main thread.
|
||||
fn perform_action(&self, target: Target<SurfaceId>, action: Action) -> bool;
|
||||
}
|
||||
|
||||
/// Implemented by this crate (GTK `Surface`); handed to core. Mirrors
|
||||
/// `apprt/gtk/Surface.zig`.
|
||||
pub trait RtSurface {
|
||||
/// `close(process_active)`.
|
||||
fn close(&self, process_active: bool);
|
||||
/// Cgroup path of the surface (Linux, `gtk-single-instance` scopes).
|
||||
fn cgroup(&self) -> Option<String>;
|
||||
fn get_title(&self) -> Option<String>;
|
||||
fn get_content_scale(&self) -> ContentScale;
|
||||
fn get_size(&self) -> SurfaceSize;
|
||||
fn get_cursor_pos(&self) -> CursorPos;
|
||||
fn supports_clipboard(&self, clipboard: Clipboard) -> bool {
|
||||
let _ = clipboard;
|
||||
true
|
||||
}
|
||||
/// `clipboardRequest`; the answer arrives later through
|
||||
/// `CoreSurface::complete_clipboard_request`. Returns true if started.
|
||||
fn clipboard_request(&self, clipboard: Clipboard, state: ClipboardRequest) -> bool;
|
||||
/// `setClipboard(type, contents, confirm)`; contents are (mime, data).
|
||||
fn set_clipboard(&self, clipboard: Clipboard, contents: &[(String, String)], confirm: bool);
|
||||
/// `defaultTermioEnv`: env vars to set for the child (GDK/GTK related
|
||||
/// cleanups).
|
||||
fn default_termio_env(&self) -> Vec<(String, String)>;
|
||||
fn redraw_inspector(&self) {}
|
||||
}
|
||||
|
||||
/// Thread-safe handle to wake the app thread (`App.wakeup`). Core's threads
|
||||
/// call [`Waker::wake`]; the GTK main loop then runs `CoreApp::tick`.
|
||||
pub trait Waker: Send + Sync {
|
||||
fn wake(&self);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
//! OpenGL function loader for the `GLArea`.
|
||||
//!
|
||||
//! GTK uses libepoxy, which exports dispatching stubs for every GL entry
|
||||
//! point and resolves them against whichever context (EGL or GLX) is current.
|
||||
//! So `dlsym` on libepoxy gives us a loader that works on both Wayland and
|
||||
//! GTK uses libepoxy, which exports a lazily-resolving function pointer for
|
||||
//! every GL entry point (resolved against whichever context, EGL or GLX, is
|
||||
//! current). So reading those pointers gives us a loader that works on both Wayland and
|
||||
//! X11, which is what `ghostty-renderer`'s `prepare_context(|name| ..)` and
|
||||
//! `glow::Context::from_loader_function` need. Call it only while the GLArea
|
||||
//! context is current.
|
||||
@@ -28,17 +28,25 @@ fn epoxy() -> Option<&'static libloading::Library> {
|
||||
}
|
||||
|
||||
/// Resolve an OpenGL function by name (null if unavailable).
|
||||
///
|
||||
/// libepoxy does not export `glFoo` symbols; it exports a function pointer
|
||||
/// *variable* `epoxy_glFoo` per entry point. The variable initially points
|
||||
/// at a resolver stub which, on first call, resolves the real function for
|
||||
/// the current context (EGL/GLX) and rewrites the variable. Reading the
|
||||
/// variable therefore yields a callable function pointer.
|
||||
#[must_use]
|
||||
pub fn proc_address(name: &str) -> *const c_void {
|
||||
let Some(lib) = epoxy() else {
|
||||
return std::ptr::null();
|
||||
};
|
||||
// SAFETY: we only read the symbol's address; the library is kept alive
|
||||
// for the whole process lifetime by the `static` above, so the returned
|
||||
// pointer stays valid. Callers cast it to the correct GL signature.
|
||||
let symbol = format!("epoxy_{name}");
|
||||
// SAFETY: we read the pointer value stored in the exported variable; the
|
||||
// library is kept alive for the whole process lifetime by the `static`
|
||||
// above, so the returned pointer stays valid. Callers cast it to the
|
||||
// correct GL signature.
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
lib.get::<*const c_void>(name.as_bytes())
|
||||
.map_or(std::ptr::null(), |sym| *sym)
|
||||
lib.get::<*const *const c_void>(symbol.as_bytes())
|
||||
.map_or(std::ptr::null(), |sym| **sym)
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ pub mod adw_version;
|
||||
pub mod app;
|
||||
pub mod cgroup;
|
||||
pub mod class;
|
||||
pub mod core;
|
||||
pub mod css;
|
||||
pub mod ext;
|
||||
pub mod flatpak;
|
||||
|
||||
Reference in new issue
Block a user