feat: honor async-backend with epoll/io_uring, fix last RV13 items

Resolves the 4 P3 items in docs/rewrite/notes/review-rv13-codex.md
(final-Z16.md):
- async-backend now takes effect like Ghostty: ghostty_os::xev ports
  libxev's backend detection (io_uring, then epoll); global init detects
  and logs it, the GTK startup applies the config with Zig's messages,
  and +version reports the real backend. The termio IO loop runs on a
  new reactor with epoll, io_uring (io-uring crate) and poll(2) (kept
  for macOS, where libxev uses kqueue). The 21 exec integration tests
  run under every backend; io_uring was exercised here.
- auto-update-channel defaults to the build's release channel.
- Windows error names: sharing violation -> AccessDenied, plus
  ERROR_FILE_EXISTS -> PathAlreadyExists and virus errors ->
  AntivirusInterference, mirroring std.os.windows.OpenFile's NTSTATUS
  mapping in Zig 0.15.2 (verified against its source by the
  orchestrator).
- Exact ports of std.fs.path.basenameWindows/basenamePosix for theme
  name validation (drive, UNC and device roots).

Test Plan:
- cargo test --workspace --no-fail-fast (3555 passed, exit 0)
- just check-macos (both), just check-windows, just check-wasm
- +show-config --default --docs vs /usr/bin/ghostty (only the
  build-specific gtk-opengl-debug line differs)
This commit is contained in:
ddidderr committed 2026-10-12 14:49:00 +02:00
1 parent 27e241d07b
commit f9094c8152
18 files changed
+2012 -742

No files matched your search

@@ -206,6 +206,33 @@ fn set_gtk_env(config: &CoreConfig) {
/// if that fails (usually an invalid `LANG`), retry with `LANG` unset, then fall
/// back to `en_US.UTF-8` (updating `LANG` on success). Idempotent. This must
/// run before GTK initializes and before the child environment is captured.
/// Apply the `async-backend` config (Zig: `startupXev`): `auto` keeps the
/// backend detected at startup, the others request that backend and fall
/// back to the detected one when it is not available.
pub fn apply_async_backend(backend: ghostty_config::types_b::AsyncBackend) -> bool {
use ghostty_config::types_b::AsyncBackend;
use ghostty_os::xev::{self, Backend};
// If our backend is auto then we have no setup to do.
let want = match backend {
AsyncBackend::Auto => return true,
AsyncBackend::Epoll => Backend::Epoll,
AsyncBackend::IoUring => Backend::IoUring,
};
// Setup our event loop backend to the preferred method
let result = xev::is_dynamic() && xev::prefer(want);
if result {
log::info!("libxev manual backend={}", xev::backend());
} else {
log::warn!(
"libxev manual backend failed, using default={}",
xev::backend()
);
}
result
}
pub fn init_locale() {
static ONCE: std::sync::Once = std::sync::Once::new();
ONCE.call_once(|| {
@@ -329,6 +356,9 @@ mod imp {
// Set ourselves as the default application.
gio::Application::set_default(obj.upcast_ref::<gio::Application>());
// Setup our event loop
obj.startup_xev();
// Setup our style manager (light/dark mode)
obj.startup_style_manager();
// Setup some signal handlers
@@ -889,6 +919,14 @@ impl Application {
// -----------------------------------------------------------------
// Startup
/// Configure the event loop (libxev) to use a specific backend.
///
/// This must be called before any event loop is created.
fn startup_xev(&self) {
let config = self.core_config();
apply_async_backend(config.async_backend);
}
/// Setup the style manager on startup: initial light/dark mode based on
/// the configuration, plus change listeners.
fn startup_style_manager(&self) {
@@ -1900,3 +1938,35 @@ mod trim_tests {
assert_eq!(trim_ascii_whitespace(" \t "), "");
}
}
#[cfg(test)]
mod async_backend_tests {
use ghostty_config::types_b::AsyncBackend;
use ghostty_os::xev::{self, Backend};
use super::apply_async_backend;
#[test]
fn async_backend_selects_the_event_loop() {
let _ = xev::detect();
let detected = xev::backend();
// auto: nothing to do.
assert!(apply_async_backend(AsyncBackend::Auto));
assert_eq!(xev::backend(), detected);
// epoll is always available on Linux.
assert!(apply_async_backend(AsyncBackend::Epoll));
assert_eq!(xev::backend(), Backend::Epoll);
// io_uring only when the kernel allows it, else the backend stays.
let ok = apply_async_backend(AsyncBackend::IoUring);
assert_eq!(ok, xev::available(Backend::IoUring));
assert_eq!(
xev::backend(),
if ok { Backend::IoUring } else { Backend::Epoll }
);
let _ = xev::detect();
}
}