diff --git a/build.zig.zon b/build.zig.zon index cc617cf51..053a2f6d8 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,8 +7,8 @@ .libxev = .{ // mitchellh/libxev - .url = "https://deps.files.ghostty.org/libxev-1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c.tar.gz", - .hash = "1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c", + .url = "https://github.com/mitchellh/libxev/archive/8943932a668f338cb2c500f6e1a7396bacd8b55d.tar.gz", + .hash = "1220a67b584c9499154de8c96851ed8e92315452cb2027c06e2d7d07a39c6f900d1a", }, .mach_glfw = .{ // mitchellh/mach-glfw diff --git a/build.zig.zon.nix b/build.zig.zon.nix index e1eecdd3e..ecfab0900 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -84,11 +84,11 @@ with lib; let in linkFarm name [ { - name = "1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c"; + name = "1220a67b584c9499154de8c96851ed8e92315452cb2027c06e2d7d07a39c6f900d1a"; path = fetchZigArtifact { name = "libxev"; - url = "https://deps.files.ghostty.org/libxev-1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c.tar.gz"; - hash = "sha256-VHP90NTytIZ8UZsYRKOOxN490/I6yv6ec40sP8y5MJ8="; + url = "https://github.com/mitchellh/libxev/archive/8943932a668f338cb2c500f6e1a7396bacd8b55d.tar.gz"; + hash = "sha256-TGooUoby2J8PyzbdKHwdEXnu7f2g4T2/TUHj/ktBsN4="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index 5eb530d76..2e5fbca92 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -10,7 +10,6 @@ https://deps.files.ghostty.org/harfbuzz-1220b8588f106c996af10249bfa092c6fb2f35fb https://deps.files.ghostty.org/highway-12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b.tar.gz https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz -https://deps.files.ghostty.org/libxev-1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c.tar.gz https://deps.files.ghostty.org/mach_glfw-12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62.tar.gz https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz @@ -32,6 +31,7 @@ https://github.com/getsentry/breakpad/archive/b99f444ba5f6b98cac261cbb391d8766b3 https://github.com/GNOME/libxml2/archive/refs/tags/v2.11.5.tar.gz https://github.com/mbadolato/iTerm2-Color-Schemes/archive/efb1bb1843500a751eb30afa58fe48a6bec8952c.tar.gz https://github.com/mitchellh/glfw/archive/b552c6ec47326b94015feddb36058ea567b87159.tar.gz +https://github.com/mitchellh/libxev/archive/8943932a668f338cb2c500f6e1a7396bacd8b55d.tar.gz https://github.com/mitchellh/vulkan-headers/archive/04c8a0389d5a0236a96312988017cd4ce27d8041.tar.gz https://github.com/mitchellh/wayland-headers/archive/5f991515a29f994d87b908115a2ab0b899474bd1.tar.gz https://github.com/mitchellh/x11-headers/archive/2ffbd62d82ff73ec929dd8de802bc95effa0ef88.tar.gz diff --git a/build.zig.zon2json-lock b/build.zig.zon2json-lock index 348a69193..03eae0882 100644 --- a/build.zig.zon2json-lock +++ b/build.zig.zon2json-lock @@ -1,8 +1,8 @@ { - "1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c": { + "1220a67b584c9499154de8c96851ed8e92315452cb2027c06e2d7d07a39c6f900d1a": { "name": "libxev", - "url": "https://deps.files.ghostty.org/libxev-1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c.tar.gz", - "hash": "sha256-VHP90NTytIZ8UZsYRKOOxN490/I6yv6ec40sP8y5MJ8=" + "url": "https://github.com/mitchellh/libxev/archive/8943932a668f338cb2c500f6e1a7396bacd8b55d.tar.gz", + "hash": "sha256-TGooUoby2J8PyzbdKHwdEXnu7f2g4T2/TUHj/ktBsN4=" }, "12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62": { "name": "mach_glfw", diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 227c36ec4..d8fcaa74c 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -15,6 +15,7 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const builtin = @import("builtin"); const build_config = @import("../../build_config.zig"); +const xev = @import("../../global.zig").xev; const build_options = @import("build_options"); const apprt = @import("../../apprt.zig"); const configpkg = @import("../../config.zig"); @@ -137,6 +138,27 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { } } + // Setup our event loop backend + if (config.@"async-backend" != .auto) { + const result: bool = switch (config.@"async-backend") { + .auto => unreachable, + .epoll => xev.prefer(.epoll), + .io_uring => xev.prefer(.io_uring), + }; + + if (result) { + log.info( + "libxev manual backend={s}", + .{@tagName(xev.backend)}, + ); + } else { + log.warn( + "libxev manual backend failed, using default={s}", + .{@tagName(xev.backend)}, + ); + } + } + var gdk_debug: struct { /// output OpenGL debug information opengl: bool = false, diff --git a/src/cli/version.zig b/src/cli/version.zig index f6d2ea9df..235cfe40b 100644 --- a/src/cli/version.zig +++ b/src/cli/version.zig @@ -4,7 +4,7 @@ const Allocator = std.mem.Allocator; const builtin = @import("builtin"); const build_config = @import("../build_config.zig"); const internal_os = @import("../os/main.zig"); -const xev = @import("xev"); +const xev = @import("../global.zig").xev; const renderer = @import("../renderer.zig"); const gtk = if (build_config.app_runtime == .gtk) @import("../apprt/gtk/c.zig").c else void; @@ -37,7 +37,7 @@ pub fn run(alloc: Allocator) !u8 { try stdout.print(" - app runtime: {}\n", .{build_config.app_runtime}); try stdout.print(" - font engine: {}\n", .{build_config.font_backend}); try stdout.print(" - renderer : {}\n", .{renderer.Renderer}); - try stdout.print(" - libxev : {}\n", .{xev.backend}); + try stdout.print(" - libxev : {s}\n", .{@tagName(xev.backend)}); if (comptime build_config.app_runtime == .gtk) { try stdout.print(" - desktop env: {s}\n", .{@tagName(internal_os.desktopEnvironment())}); try stdout.print(" - GTK version:\n", .{}); diff --git a/src/config/Config.zig b/src/config/Config.zig index 60a95f33b..a5f5b56b3 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -2235,6 +2235,34 @@ term: []const u8 = "xterm-ghostty", /// running. Defaults to an empty string if not set. @"enquiry-response": []const u8 = "", +/// Configures the low-level API to use for async IO, eventing, etc. +/// +/// Most users should leave this set to `auto`. This will automatically detect +/// scenarios where APIs may not be available (for example `io_uring` on +/// certain hardened kernels) and fall back to a different API. However, if +/// you want to force a specific backend for any reason, you can set this +/// here. +/// +/// Based on various benchmarks, we haven't found a statistically significant +/// difference between the backends with regards to memory, CPU, or latency. +/// The choice of backend is more about compatibility and features. +/// +/// Available options: +/// +/// * `auto` - Automatically choose the best backend for the platform +/// based on available options. +/// * `epoll` - Use the `epoll` API +/// * `io_uring` - Use the `io_uring` API +/// +/// If the selected backend is not available on the platform, Ghostty will +/// fall back to an automatically chosen backend that is available. +/// +/// Changing this value requires a full application restart to take effect. +/// +/// This is only supported on Linux, since this is the only platform +/// where we have multiple options. On macOS, we always use `kqueue`. +@"async-backend": AsyncBackend = .auto, + /// Control the auto-update functionality of Ghostty. This is only supported /// on macOS currently, since Linux builds are distributed via package /// managers that are not centrally controlled by Ghostty. @@ -5912,6 +5940,13 @@ pub const LinuxCgroup = enum { @"single-instance", }; +/// See async-backend +pub const AsyncBackend = enum { + auto, + epoll, + io_uring, +}; + /// See auto-updates pub const AutoUpdate = enum { off, diff --git a/src/global.zig b/src/global.zig index d5a7af630..b9af5983d 100644 --- a/src/global.zig +++ b/src/global.zig @@ -9,7 +9,11 @@ const harfbuzz = @import("harfbuzz"); const oni = @import("oniguruma"); const crash = @import("crash/main.zig"); const renderer = @import("renderer.zig"); -const xev = @import("xev"); + +/// We export the xev backend we want to use so that the rest of +/// Ghostty can import this once and have access to the proper +/// backend. +pub const xev = @import("xev").Dynamic; /// Global process state. This is initialized in main() for exe artifacts /// and by ghostty_init() for lib artifacts. This should ONLY be used by @@ -114,6 +118,12 @@ pub const GlobalState = struct { // Setup our signal handlers before logging initSignals(); + // Setup our Xev backend if we're dynamic + if (comptime xev.dynamic) xev.detect() catch |err| { + std.log.warn("failed to detect xev backend, falling back to " ++ + "most compatible backend err={}", .{err}); + }; + // Output some debug information right away std.log.info("ghostty version={s}", .{build_config.version_string}); std.log.info("ghostty build optimize={s}", .{build_config.mode_string}); @@ -126,7 +136,7 @@ pub const GlobalState = struct { std.log.info("dependency fontconfig={d}", .{fontconfig.version()}); } std.log.info("renderer={}", .{renderer.Renderer}); - std.log.info("libxev backend={}", .{xev.backend}); + std.log.info("libxev default backend={s}", .{@tagName(xev.backend)}); // As early as possible, initialize our resource limits. self.rlimits = ResourceLimits.init(); diff --git a/src/main_ghostty.zig b/src/main_ghostty.zig index 9efe8d9b0..0e57fbc43 100644 --- a/src/main_ghostty.zig +++ b/src/main_ghostty.zig @@ -13,7 +13,6 @@ const macos = @import("macos"); const oni = @import("oniguruma"); const cli = @import("cli.zig"); const internal_os = @import("os/main.zig"); -const xev = @import("xev"); const fontconfig = @import("fontconfig"); const harfbuzz = @import("harfbuzz"); const renderer = @import("renderer.zig"); diff --git a/src/os/cf_release_thread.zig b/src/os/cf_release_thread.zig index 5001441e0..dbf8e6592 100644 --- a/src/os/cf_release_thread.zig +++ b/src/os/cf_release_thread.zig @@ -6,9 +6,9 @@ pub const Thread = @This(); const std = @import("std"); const builtin = @import("builtin"); -const xev = @import("xev"); const macos = @import("macos"); +const xev = @import("../global.zig").xev; const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue; const Allocator = std.mem.Allocator; @@ -106,7 +106,7 @@ pub fn threadMain(self: *Thread) void { // If our loop is not stopped, then we need to keep running so that // messages are drained and we can wait for the surface to send a stop // message. - if (!self.loop.flags.stopped) { + if (!self.loop.stopped()) { log.warn("abrupt cf release thread exit detected, starting xev to drain mailbox", .{}); defer log.debug("cf release thread fully exiting after abnormal failure", .{}); self.flags.drain = true; diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index ca13f87de..e90963c75 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -11,7 +11,7 @@ const objc = @import("objc"); const macos = @import("macos"); const imgui = @import("imgui"); const glslang = @import("glslang"); -const xev = @import("xev"); +const xev = @import("../global.zig").xev; const apprt = @import("../apprt.zig"); const configpkg = @import("../config.zig"); const font = @import("../font/main.zig"); diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index cc63889fa..03b41ab30 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -5,7 +5,7 @@ pub const Thread = @This(); const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; -const xev = @import("xev"); +const xev = @import("../global.zig").xev; const crash = @import("../crash/main.zig"); const internal_os = @import("../os/main.zig"); const renderer = @import("../renderer.zig"); diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 5a2d2a507..4f63076de 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -9,7 +9,7 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; const posix = std.posix; -const xev = @import("xev"); +const xev = @import("../global.zig").xev; const build_config = @import("../build_config.zig"); const configpkg = @import("../config.zig"); const crash = @import("../crash/main.zig"); @@ -589,7 +589,7 @@ fn ttyWrite( _: *xev.Completion, _: xev.Stream, _: xev.WriteBuffer, - r: xev.Stream.WriteError!usize, + r: xev.WriteError!usize, ) xev.CallbackAction { const td = td_.?; td.write_req_pool.put(); @@ -634,13 +634,13 @@ pub const ThreadData = struct { /// This is the pool of available (unused) write requests. If you grab /// one from the pool, you must put it back when you're done! - write_req_pool: SegmentedPool(xev.Stream.WriteRequest, WRITE_REQ_PREALLOC) = .{}, + write_req_pool: SegmentedPool(xev.WriteRequest, WRITE_REQ_PREALLOC) = .{}, /// The pool of available buffers for writing to the pty. write_buf_pool: SegmentedPool([64]u8, WRITE_REQ_PREALLOC) = .{}, /// The write queue for the data stream. - write_queue: xev.Stream.WriteQueue = .{}, + write_queue: xev.WriteQueue = .{}, /// This is used for both waiting for the process to exit and then /// subsequently to wait for the data_stream to close. diff --git a/src/termio/Options.zig b/src/termio/Options.zig index 023423c95..7484fd087 100644 --- a/src/termio/Options.zig +++ b/src/termio/Options.zig @@ -1,7 +1,7 @@ //! The options that are used to configure a terminal IO implementation. const builtin = @import("builtin"); -const xev = @import("xev"); +const xev = @import("../global.zig").xev; const apprt = @import("../apprt.zig"); const renderer = @import("../renderer.zig"); const Command = @import("../Command.zig"); diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index 8a2e6cc7a..1d125f049 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -18,7 +18,7 @@ const Pty = @import("../pty.zig").Pty; const StreamHandler = @import("stream_handler.zig").StreamHandler; const terminal = @import("../terminal/main.zig"); const terminfo = @import("../terminfo/main.zig"); -const xev = @import("xev"); +const xev = @import("../global.zig").xev; const renderer = @import("../renderer.zig"); const apprt = @import("../apprt.zig"); const fastmem = @import("../fastmem.zig"); diff --git a/src/termio/Thread.zig b/src/termio/Thread.zig index d80046737..d8018341d 100644 --- a/src/termio/Thread.zig +++ b/src/termio/Thread.zig @@ -14,7 +14,7 @@ pub const Thread = @This(); const std = @import("std"); const ArenaAllocator = std.heap.ArenaAllocator; const builtin = @import("builtin"); -const xev = @import("xev"); +const xev = @import("../global.zig").xev; const crash = @import("../crash/main.zig"); const termio = @import("../termio.zig"); const renderer = @import("../renderer.zig"); @@ -189,7 +189,7 @@ pub fn threadMain(self: *Thread, io: *termio.Termio) void { // If our loop is not stopped, then we need to keep running so that // messages are drained and we can wait for the surface to send a stop // message. - if (!self.loop.flags.stopped) { + if (!self.loop.stopped()) { log.warn("abrupt io thread exit detected, starting xev to drain mailbox", .{}); defer log.debug("io thread fully exiting after abnormal failure", .{}); self.flags.drain = true; diff --git a/src/termio/backend.zig b/src/termio/backend.zig index 68b283a00..46ed3431c 100644 --- a/src/termio/backend.zig +++ b/src/termio/backend.zig @@ -3,7 +3,7 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const posix = std.posix; -const xev = @import("xev"); +const xev = @import("../global.zig").xev; const build_config = @import("../build_config.zig"); const configpkg = @import("../config.zig"); const internal_os = @import("../os/main.zig"); diff --git a/src/termio/mailbox.zig b/src/termio/mailbox.zig index cac453a1c..b144b512a 100644 --- a/src/termio/mailbox.zig +++ b/src/termio/mailbox.zig @@ -2,7 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const xev = @import("xev"); +const xev = @import("../global.zig").xev; const renderer = @import("../renderer.zig"); const termio = @import("../termio.zig"); const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue; diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index e9bb353fb..43d2888d2 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -2,7 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const xev = @import("xev"); +const xev = @import("../global.zig").xev; const apprt = @import("../apprt.zig"); const build_config = @import("../build_config.zig"); const configpkg = @import("../config.zig");