summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMitchell Hashimoto <m@mitchellh.com>2025-07-04 13:43:30 -0700
committerMitchell Hashimoto <m@mitchellh.com>2025-07-04 14:12:18 -0700
commitfb9c52ecf44ee1004de16a4b02a67d4258e66c14 (patch)
tree89e88b3d2a7c240479a3297295b8a7f15e75ba83 /src
parent7bd90e6ec4354e0a25f95225f1508293912612d4 (diff)
Nuke GLFW from Orbit
This deletes the GLFW apprt from the Ghostty codebase. The GLFW apprt was the original apprt used by Ghostty (well, before Ghostty even had the concept of an "apprt" -- it was all just a single application then). It let me iterate on the core terminal features, rendering, etc. without bothering about the UI. It was a good way to get started. But it has long since outlived its usefulness. We've had a stable GTK apprt for Linux (and Windows via WSL) and a native macOS app via libghostty for awhile now. The GLFW apprt only remained within the tree for a few reasons: 1. Primarily, it provided a faster feedback loop on macOS because building the macOS app historically required us to hop out of the zig build system and into Xcode, which is slow and cumbersome. 2. It was a convenient way to narrow whether a bug was in the core Ghostty codebase or in the apprt itself. If a bug was in both the glfw and macOS app then it was likely in the core. 3. It provided us a way on macOS to test OpenGL. All of these reasons are no longer valid. Respectively: 1. Our Zig build scripts now execute the `xcodebuild` CLI directly and can open the resulting app, stream logs, etc. This is the same experience we have on Linux. (Xcode has always been a dependency of building on macOS in general, so this is not cumbersome.) 2. We have a healthy group of maintainers, many of which have access to both macOS and Linux, so we can quickly narrow down bugs regardless of the apprt. 3. Our OpenGL renderer hasn't been compatible with macOS for some time now, so this is no longer a useful feature. At this point, the GLFW apprt is just a burden. It adds complexity across the board, and some people try to run Ghostty with it in the real world and get confused when it doesn't work (it's always been lacking in features and buggy compared to the other apprts). So, it's time to say goodbye. Its bittersweet because it is a big part of Ghostty's history, but we've grown up now and it's time to move on. Thank you, goodbye. (NOTE: If you are a user of the GLFW apprt, then please fork the project prior to this commit or start a new project based on it. We've warned against using it for a very, very long time now.)
Diffstat (limited to 'src')
-rw-r--r--src/apprt.zig20
-rw-r--r--src/apprt/embedded.zig2
-rw-r--r--src/apprt/glfw.zig1266
-rw-r--r--src/build/Config.zig5
-rw-r--r--src/build/SharedDeps.zig11
-rw-r--r--src/config/Config.zig8
-rw-r--r--src/main_ghostty.zig1
-rw-r--r--src/renderer/Metal.zig27
-rw-r--r--src/renderer/OpenGL.zig45
-rw-r--r--src/renderer/generic.zig15
-rw-r--r--src/termio/stream_handler.zig13
11 files changed, 10 insertions, 1403 deletions
diff --git a/src/apprt.zig b/src/apprt.zig
index dd726b3f2..cb542875e 100644
--- a/src/apprt.zig
+++ b/src/apprt.zig
@@ -3,7 +3,7 @@
//! getting user input (mouse/keyboard), etc.
//!
//! This enables compile-time interfaces to be built to swap out the underlying
-//! application runtime. For example: glfw, pure macOS Cocoa, GTK+, browser, etc.
+//! application runtime. For example: pure macOS Cocoa, GTK+, browser, etc.
//!
//! The goal is to have different implementations share as much of the core
//! logic as possible, and to only reach out to platform-specific implementation
@@ -15,7 +15,6 @@ const build_config = @import("build_config.zig");
const structs = @import("apprt/structs.zig");
pub const action = @import("apprt/action.zig");
-pub const glfw = @import("apprt/glfw.zig");
pub const gtk = @import("apprt/gtk.zig");
pub const none = @import("apprt/none.zig");
pub const browser = @import("apprt/browser.zig");
@@ -42,7 +41,6 @@ pub const SurfaceSize = structs.SurfaceSize;
pub const runtime = switch (build_config.artifact) {
.exe => switch (build_config.app_runtime) {
.none => none,
- .glfw => glfw,
.gtk => gtk,
},
.lib => embedded,
@@ -53,18 +51,12 @@ pub const App = runtime.App;
pub const Surface = runtime.Surface;
/// Runtime is the runtime to use for Ghostty. All runtimes do not provide
-/// equivalent feature sets. For example, GTK offers tabbing and more features
-/// that glfw does not provide. However, glfw may require many less
-/// dependencies.
+/// equivalent feature sets.
pub const Runtime = enum {
/// Will not produce an executable at all when `zig build` is called.
/// This is only useful if you're only interested in the lib only (macOS).
none,
- /// Glfw-backed. Very simple. Glfw is statically linked. Tabbing and
- /// other rich windowing features are not supported.
- glfw,
-
/// GTK-backed. Rich windowed application. GTK is dynamically linked.
gtk,
@@ -72,12 +64,8 @@ pub const Runtime = enum {
// The Linux default is GTK because it is full featured.
if (target.os.tag == .linux) return .gtk;
- // Windows we currently only support glfw
- if (target.os.tag == .windows) return .glfw;
-
- // Otherwise, we do NONE so we don't create an exe. The GLFW
- // build is opt-in because it is missing so many features compared
- // to the other builds that are impossible due to the GLFW interface.
+ // Otherwise, we do NONE so we don't create an exe and we
+ // create libghostty.
return .none;
}
};
diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig
index dec1e4135..0121494b7 100644
--- a/src/apprt/embedded.zig
+++ b/src/apprt/embedded.zig
@@ -236,7 +236,7 @@ pub const App = struct {
var surface = try self.core_app.alloc.create(Surface);
errdefer self.core_app.alloc.destroy(surface);
- // Create the surface -- because windows are surfaces for glfw.
+ // Create the surface
try surface.init(self, opts);
errdefer surface.deinit();
diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig
deleted file mode 100644
index b82771d75..000000000
--- a/src/apprt/glfw.zig
+++ /dev/null
@@ -1,1266 +0,0 @@
-//! Application runtime implementation that uses GLFW (https://www.glfw.org/).
-//!
-//! This works on macOS and Linux with OpenGL and Metal.
-//! (The above sentence may be out of date).
-
-const std = @import("std");
-const builtin = @import("builtin");
-const build_config = @import("../build_config.zig");
-const assert = std.debug.assert;
-const Allocator = std.mem.Allocator;
-const glfw = @import("glfw");
-const macos = @import("macos");
-const objc = @import("objc");
-const cli = @import("../cli.zig");
-const input = @import("../input.zig");
-const internal_os = @import("../os/main.zig");
-const renderer = @import("../renderer.zig");
-const terminal = @import("../terminal/main.zig");
-const Renderer = renderer.Renderer;
-const apprt = @import("../apprt.zig");
-const CoreApp = @import("../App.zig");
-const CoreSurface = @import("../Surface.zig");
-const configpkg = @import("../config.zig");
-const Config = @import("../config.zig").Config;
-
-// Get native API access on certain platforms so we can do more customization.
-const glfwNative = glfw.Native(.{
- .cocoa = builtin.target.os.tag.isDarwin(),
- .x11 = builtin.os.tag == .linux,
-});
-
-/// True if darwin-specific logic is enabled
-const darwin_enabled = builtin.target.os.tag.isDarwin() and
- build_config.artifact == .exe;
-
-const log = std.log.scoped(.glfw);
-
-pub const resourcesDir = internal_os.resourcesDir;
-
-pub const App = struct {
- app: *CoreApp,
- config: Config,
-
- /// Flips to true to quit on the next event loop tick. This
- /// never goes false and forces the event loop to exit.
- quit: bool = false,
-
- /// Mac-specific state.
- darwin: if (darwin_enabled) Darwin else void,
-
- pub const Options = struct {};
-
- pub fn init(self: *App, core_app: *CoreApp, _: Options) !void {
- if (comptime builtin.target.os.tag.isDarwin()) {
- log.warn("WARNING WARNING WARNING: GLFW ON MAC HAS BUGS.", .{});
- log.warn("You should use the AppKit-based app instead. The official download", .{});
- log.warn("is properly built and available from GitHub. If you're building from", .{});
- log.warn("source, see the README for details on how to build the AppKit app.", .{});
- }
-
- if (!glfw.init(.{})) {
- if (glfw.getError()) |err| {
- log.err("error initializing GLFW err={} msg={s}", .{
- err.error_code,
- err.description,
- });
- return err.error_code;
- }
-
- return error.GlfwInitFailedUnknownReason;
- }
- glfw.setErrorCallback(glfwErrorCallback);
-
- // Mac-specific state. For example, on Mac we enable window tabbing.
- var darwin = if (darwin_enabled) try Darwin.init() else {};
- errdefer if (darwin_enabled) darwin.deinit();
-
- // Load our configuration
- var config = try Config.load(core_app.alloc);
- errdefer config.deinit();
-
- // If we had configuration errors, then log them.
- if (!config._diagnostics.empty()) {
- var buf = std.ArrayList(u8).init(core_app.alloc);
- defer buf.deinit();
- for (config._diagnostics.items()) |diag| {
- try diag.write(buf.writer());
- log.warn("configuration error: {s}", .{buf.items});
- buf.clearRetainingCapacity();
- }
-
- // If we have any CLI errors, exit.
- if (config._diagnostics.containsLocation(.cli)) {
- log.warn("CLI errors detected, exiting", .{});
- _ = core_app.mailbox.push(.{
- .quit = {},
- }, .{ .forever = {} });
- }
- }
-
- // Queue a single new window that starts on launch
- // Note: above we may send a quit so this may never happen
- _ = core_app.mailbox.push(.{
- .new_window = .{},
- }, .{ .forever = {} });
-
- // We want the event loop to wake up instantly so we can process our tick.
- glfw.postEmptyEvent();
-
- self.* = .{
- .app = core_app,
- .config = config,
- .darwin = darwin,
- };
- }
-
- pub fn terminate(self: *App) void {
- self.config.deinit();
- glfw.terminate();
- }
-
- /// Run the event loop. This doesn't return until the app exits.
- pub fn run(self: *App) !void {
- while (true) {
- // Wait for any events from the app event loop. wakeup will post
- // an empty event so that this will return.
- //
- // Warning: a known issue on macOS is that this will block while
- // a resize event is actively happening, which will prevent the
- // app tick from happening. I don't know know a way around this
- // but its not a big deal since we don't use glfw for the official
- // mac app, but noting it in case anyone builds for macos using
- // glfw.
- glfw.waitEvents();
-
- // Tick the terminal app
- try self.app.tick(self);
-
- // If the tick caused us to quit, then we're done.
- if (self.quit or self.app.surfaces.items.len == 0) {
- for (self.app.surfaces.items) |surface| {
- surface.close(false);
- }
-
- return;
- }
- }
- }
-
- /// Wakeup the event loop. This should be able to be called from any thread.
- pub fn wakeup(self: *const App) void {
- _ = self;
- glfw.postEmptyEvent();
- }
-
- /// Perform a given action. Returns `true` if the action was able to be
- /// performed, `false` otherwise.
- pub fn performAction(
- self: *App,
- target: apprt.Target,
- comptime action: apprt.Action.Key,
- value: apprt.Action.Value(action),
- ) !bool {
- switch (action) {
- .quit => self.quit = true,
-
- .new_window => _ = try self.newSurface(switch (target) {
- .app => null,
- .surface => |v| v,
- }),
-
- .new_tab => try self.newTab(switch (target) {
- .app => null,
- .surface => |v| v,
- }),
-
- .size_limit => switch (target) {
- .app => {},
- .surface => |surface| try surface.rt_surface.setSizeLimits(.{
- .width = value.min_width,
- .height = value.min_height,
- }, if (value.max_width > 0) .{
- .width = value.max_width,
- .height = value.max_height,
- } else null),
- },
-
- .initial_size => switch (target) {
- .app => {},
- .surface => |surface| try surface.rt_surface.setInitialWindowSize(
- value.width,
- value.height,
- ),
- },
-
- .toggle_fullscreen => self.toggleFullscreen(target),
-
- .open_config => try configpkg.edit.open(self.app.alloc),
-
- .set_title => switch (target) {
- .app => {},
- .surface => |surface| try surface.rt_surface.setTitle(value.title),
- },
-
- .mouse_shape => switch (target) {
- .app => {},
- .surface => |surface| try surface.rt_surface.setMouseShape(value),
- },
-
- .mouse_visibility => switch (target) {
- .app => {},
- .surface => |surface| surface.rt_surface.setMouseVisibility(switch (value) {
- .visible => true,
- .hidden => false,
- }),
- },
-
- .reload_config => try self.reloadConfig(target, value),
-
- // Unimplemented
- .new_split,
- .goto_split,
- .resize_split,
- .equalize_splits,
- .toggle_split_zoom,
- .present_terminal,
- .close_all_windows,
- .close_window,
- .close_tab,
- .toggle_tab_overview,
- .toggle_window_decorations,
- .toggle_quick_terminal,
- .toggle_command_palette,
- .toggle_visibility,
- .goto_tab,
- .move_tab,
- .inspector,
- .render_inspector,
- .quit_timer,
- .float_window,
- .secure_input,
- .key_sequence,
- .desktop_notification,
- .mouse_over_link,
- .cell_size,
- .renderer_health,
- .color_change,
- .pwd,
- .config_change,
- .toggle_maximize,
- .prompt_title,
- .reset_window_size,
- .ring_bell,
- .check_for_updates,
- .undo,
- .redo,
- .show_gtk_inspector,
- => {
- log.info("unimplemented action={}", .{action});
- return false;
- },
- }
-
- return true;
- }
-
- /// Reload the configuration. This should return the new configuration.
- /// The old value can be freed immediately at this point assuming a
- /// successful return.
- ///
- /// The returned pointer value is only valid for a stable self pointer.
- fn reloadConfig(
- self: *App,
- target: apprt.action.Target,
- opts: apprt.action.ReloadConfig,
- ) !void {
- if (opts.soft) {
- switch (target) {
- .app => try self.app.updateConfig(self, &self.config),
- .surface => |core_surface| try core_surface.updateConfig(
- &self.config,
- ),
- }
- return;
- }
-
- // Load our configuration
- var config = try Config.load(self.app.alloc);
- errdefer config.deinit();
-
- // Call into our app to update
- switch (target) {
- .app => try self.app.updateConfig(self, &config),
- .surface => |core_surface| try core_surface.updateConfig(&config),
- }
-
- // Update the existing config, be sure to clean up the old one.
- self.config.deinit();
- self.config = config;
- }
-
- /// Toggle the window to fullscreen mode.
- fn toggleFullscreen(self: *App, target: apprt.Target) void {
- _ = self;
- const surface: *Surface = switch (target) {
- .app => return,
- .surface => |v| v.rt_surface,
- };
- const win = surface.window;
-
- if (surface.isFullscreen()) {
- win.setMonitor(
- null,
- @intCast(surface.monitor_dims.position_x),
- @intCast(surface.monitor_dims.position_y),
- surface.monitor_dims.width,
- surface.monitor_dims.height,
- 0,
- );
- return;
- }
-
- const monitor = win.getMonitor() orelse monitor: {
- log.warn("window had null monitor, getting primary monitor", .{});
- break :monitor glfw.Monitor.getPrimary() orelse {
- log.warn("window could not get any monitor. will not perform action", .{});
- return;
- };
- };
-
- const video_mode = monitor.getVideoMode() orelse {
- log.warn("failed to get video mode. will not perform action", .{});
- return;
- };
-
- const position = win.getPos();
- const size = surface.getSize() catch {
- log.warn("failed to get window size. will not perform fullscreen action", .{});
- return;
- };
-
- surface.monitor_dims = .{
- .width = size.width,
- .height = size.height,
- .position_x = position.x,
- .position_y = position.y,
- };
-
- win.setMonitor(monitor, 0, 0, video_mode.getWidth(), video_mode.getHeight(), 0);
- }
-
- /// Create a new tab in the parent surface.
- fn newTab(self: *App, parent_: ?*CoreSurface) !void {
- if (comptime !darwin_enabled) {
- log.warn("tabbing is not supported on this platform", .{});
- return;
- }
-
- const parent = parent_ orelse {
- _ = try self.newSurface(null);
- return;
- };
-
- // Create the new window
- const window = try self.newSurface(parent);
-
- // Add the new window the parent window
- const parent_win = glfwNative.getCocoaWindow(parent.rt_surface.window).?;
- const other_win = glfwNative.getCocoaWindow(window.window).?;
- const NSWindowOrderingMode = enum(isize) { below = -1, out = 0, above = 1 };
- const nswindow = objc.Object.fromId(parent_win);
- nswindow.msgSend(void, objc.sel("addTabbedWindow:ordered:"), .{
- objc.Object.fromId(other_win),
- NSWindowOrderingMode.above,
- });
-
- // Adding a new tab can cause the tab bar to appear which changes
- // our viewport size. We need to call the size callback in order to
- // update values. For example, we need this to set the proper mouse selection
- // point in the grid.
- const size = parent.rt_surface.getSize() catch |err| {
- log.err("error querying window size for size callback on new tab err={}", .{err});
- return;
- };
- parent.sizeCallback(size) catch |err| {
- log.err("error in size callback from new tab err={}", .{err});
- return;
- };
- }
-
- fn newSurface(self: *App, parent_: ?*CoreSurface) !*Surface {
- // Grab a surface allocation because we're going to need it.
- var surface = try self.app.alloc.create(Surface);
- errdefer self.app.alloc.destroy(surface);
-
- // Create the surface -- because windows are surfaces for glfw.
- try surface.init(self);
- errdefer surface.deinit();
-
- // If we have a parent, inherit some properties
- if (self.config.@"window-inherit-font-size") {
- if (parent_) |parent| {
- try surface.core_surface.setFontSize(parent.font_size);
- }
- }
-
- return surface;
- }
-
- /// Close the given surface.
- pub fn closeSurface(self: *App, surface: *Surface) void {
- surface.deinit();
- self.app.alloc.destroy(surface);
- }
-
- pub fn redrawSurface(self: *App, surface: *Surface) void {
- _ = self;
- _ = surface;
-
- @panic("This should never be called for GLFW.");
- }
-
- pub fn redrawInspector(self: *App, surface: *Surface) void {
- _ = self;
- _ = surface;
-
- // GLFW doesn't support the inspector
- }
-
- fn glfwErrorCallback(code: glfw.ErrorCode, desc: [:0]const u8) void {
- std.log.warn("glfw error={} message={s}", .{ code, desc });
-
- // Workaround for: https://github.com/ocornut/imgui/issues/5908
- // If we get an invalid value with "scancode" in the message we assume
- // it is from the glfw key callback that imgui sets and we clear the
- // error so that our future code doesn't crash.
- if (code == glfw.ErrorCode.InvalidValue and
- std.mem.indexOf(u8, desc, "scancode") != null)
- {
- _ = glfw.getError();
- }
- }
-
- pub fn keyboardLayout(self: *const App) input.KeyboardLayout {
- _ = self;
-
- // Not supported by glfw
- return .unknown;
- }
-
- /// Mac-specific settings. This is only enabled when the target is
- /// Mac and the artifact is a standalone exe. We don't target libs because
- /// the embedded API doesn't do windowing.
- const Darwin = struct {
- tabbing_id: *macos.foundation.String,
-
- pub fn init() !Darwin {
- const NSWindow = objc.getClass("NSWindow").?;
- NSWindow.msgSend(void, objc.sel("setAllowsAutomaticWindowTabbing:"), .{true});
-
- // Our tabbing ID allows all of our windows to group together
- const tabbing_id = try macos.foundation.String.createWithBytes(
- "com.mitchellh.ghostty.window",
- .utf8,
- false,
- );
- errdefer tabbing_id.release();
-
- // Setup our Mac settings
- return .{ .tabbing_id = tabbing_id };
- }
-
- pub fn deinit(self: *Darwin) void {
- self.tabbing_id.release();
- self.* = undefined;
- }
- };
-};
-
-/// These are used to keep track of the original monitor values so that we can
-/// safely toggle on and off of fullscreen.
-const MonitorDimensions = struct {
- width: u32,
- height: u32,
- position_x: i64,
- position_y: i64,
-};
-
-/// Surface represents the drawable surface for glfw. In glfw, a surface
-/// is always a window because that is the only abstraction that glfw exposes.
-///
-/// This means that there is no way for the glfw runtime to support tabs,
-/// splits, etc. without considerable effort. In fact, on Darwin, we do
-/// support tabs because the minimal tabbing interface is a window abstraction,
-/// but this is a bit of a hack. The native Swift runtime should be used instead
-/// which uses real native tabbing.
-///
-/// Other runtimes a surface usually represents the equivalent of a "view"
-/// or "widget" level granularity.
-pub const Surface = struct {
- /// The glfw window handle
- window: glfw.Window,
-
- /// The glfw mouse cursor handle.
- cursor: ?glfw.Cursor,
-
- /// The app we're part of
- app: *App,
-
- /// A core surface
- core_surface: CoreSurface,
-
- /// This is the key event that was processed in keyCallback. This is only
- /// non-null if the event was NOT consumed in keyCallback. This lets us
- /// know in charCallback whether we should populate it and call it again.
- /// (GLFW guarantees that charCallback is called after keyCallback).
- key_event: ?input.KeyEvent = null,
-
- /// The monitor dimensions so we can toggle fullscreen on and off.
- monitor_dims: MonitorDimensions,
-
- /// Save the title text so that we can return it later when requested.
- /// This is allocated from the heap so it must be freed when we deinit the
- /// surface.
- title_text: ?[:0]const u8 = null,
-
- pub const Options = struct {};
-
- /// Initialize the surface into the given self pointer. This gives a
- /// stable pointer to the destination that can be used for callbacks.
- pub fn init(self: *Surface, app: *App) !void {
- // Create our window
- const win = glfw.Window.create(
- 640,
- 480,
- "ghostty",
- if (app.config.fullscreen) glfw.Monitor.getPrimary() else null,
- null,
- Renderer.glfwWindowHints(&app.config),
- ) orelse return glfw.mustGetErrorCode();
- errdefer win.destroy();
-
- // Setup our
- setInitialWindowPosition(
- win,
- app.config.@"window-position-x",
- app.config.@"window-position-y",
- );
-
- // Get our physical DPI - debug only because we don't have a use for
- // this but the logging of it may be useful
- if (builtin.mode == .Debug) {
- const monitor = win.getMonitor() orelse monitor: {
- log.warn("window had null monitor, getting primary monitor", .{});
- break :monitor glfw.Monitor.getPrimary().?;
- };
- const video_mode = monitor.getVideoMode() orelse return glfw.mustGetErrorCode();
- const physical_size = monitor.getPhysicalSize();
- const physical_x_dpi = @as(f32, @floatFromInt(video_mode.getWidth())) / (@as(f32, @floatFromInt(physical_size.width_mm)) / 25.4);
- const physical_y_dpi = @as(f32, @floatFromInt(video_mode.getHeight())) / (@as(f32, @floatFromInt(physical_size.height_mm)) / 25.4);
- log.debug("physical dpi x={} y={}", .{
- physical_x_dpi,
- physical_y_dpi,
- });
- }
-
- // On Mac, enable window tabbing
- if (comptime darwin_enabled) {
- const NSWindowTabbingMode = enum(usize) { automatic = 0, preferred = 1, disallowed = 2 };
- const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(win).?);
-
- // Tabbing mode enables tabbing at all
- nswindow.setProperty("tabbingMode", NSWindowTabbingMode.automatic);
-
- // All windows within a tab bar must have a matching tabbing ID.
- // The app sets this up for us.
- nswindow.setProperty("tabbingIdentifier", app.darwin.tabbing_id);
- }
-
- // Set our callbacks
- win.setUserPointer(&self.core_surface);
- win.setSizeCallback(sizeCallback);
- win.setCharCallback(charCallback);
- win.setKeyCallback(keyCallback);
- win.setFocusCallback(focusCallback);
- win.setRefreshCallback(refreshCallback);
- win.setScrollCallback(scrollCallback);
- win.setCursorPosCallback(cursorPosCallback);
- win.setMouseButtonCallback(mouseButtonCallback);
- win.setDropCallback(dropCallback);
-
- const dimensions: MonitorDimensions = dimensions: {
- const pos = win.getPos();
- const size = win.getFramebufferSize();
- break :dimensions .{
- .width = size.width,
- .height = size.height,
- .position_x = pos.x,
- .position_y = pos.y,
- };
- };
-
- // Build our result
- self.* = .{
- .app = app,
- .window = win,
- .cursor = null,
- .core_surface = undefined,
- .monitor_dims = dimensions,
- };
- errdefer self.* = undefined;
-
- // Initialize our cursor
- try self.setMouseShape(.text);
-
- // Add ourselves to the list of surfaces on the app.
- try app.app.addSurface(self);
- errdefer app.app.deleteSurface(self);
-
- // Get our new surface config
- var config = try apprt.surface.newConfig(app.app, &app.config);
- defer config.deinit();
-
- // Initialize our surface now that we have the stable pointer.
- try self.core_surface.init(
- app.app.alloc,
- &config,
- app.app,
- app,
- self,
- );
- errdefer self.core_surface.deinit();
- }
-
- pub fn deinit(self: *Surface) void {
- if (self.title_text) |t| self.core_surface.alloc.free(t);
-
- // Remove ourselves from the list of known surfaces in the app.
- self.app.app.deleteSurface(self);
-
- // Clean up our core surface so that all the rendering and IO stop.
- self.core_surface.deinit();
-
- if (comptime darwin_enabled) {
- const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(self.window).?);
- const tabgroup = nswindow.getProperty(objc.Object, "tabGroup");
- const windows = tabgroup.getProperty(objc.Object, "windows");
- switch (windows.getProperty(usize, "count")) {
- // If we're going down to one window our tab bar is going to be
- // destroyed so unset it so that the later logic doesn't try to
- // use it.
- 1 => {},
-
- // If our tab bar is visible and we are going down to 1 window,
- // hide the tab bar. The check is "2" because our current window
- // is still present.
- 2 => if (tabgroup.getProperty(bool, "tabBarVisible")) {
- nswindow.msgSend(void, objc.sel("toggleTabBar:"), .{nswindow.value});
- },
-
- else => {},
- }
- }
-
- // We can now safely destroy our windows. We have to do this BEFORE
- // setting up the new focused window below.
- self.window.destroy();
- if (self.cursor) |c| {
- c.destroy();
- self.cursor = null;
- }
- }
-
- /// Checks if the glfw window is in fullscreen.
- pub fn isFullscreen(self: *Surface) bool {
- return self.window.getMonitor() != null;
- }
-
- /// Close this surface.
- pub fn close(self: *Surface, processActive: bool) void {
- _ = processActive;
- self.setShouldClose();
- self.deinit();
- self.app.app.alloc.destroy(self);
- }
-
- /// Set the initial window size. This is called exactly once at
- /// surface initialization time. This may be called before "self"
- /// is fully initialized.
- fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void {
- const monitor = self.window.getMonitor() orelse glfw.Monitor.getPrimary() orelse {
- log.warn("window is not on a monitor, not setting initial size", .{});
- return;
- };
-
- const workarea = monitor.getWorkarea();
- self.window.setSize(.{
- .width = @min(width, workarea.width),
- .height = @min(height, workarea.height),
- });
- }
-
- /// Set the initial window position. This is called exactly once at
- /// surface initialization time. This may be called before "self"
- /// is fully initialized.
- fn setInitialWindowPosition(win: glfw.Window, x: ?i16, y: ?i16) void {
- const start_position_x = x orelse return;
- const start_position_y = y orelse return;
-
- log.debug("setting initial window position ({},{})", .{ start_position_x, start_position_y });
- win.setPos(.{ .x = start_position_x, .y = start_position_y });
- }
-
- /// Set the size limits of the window.
- /// Note: this interface is not good, we should redo it if we plan
- /// to use this more. i.e. you can't set max width but no max height,
- /// or no mins.
- fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void {
- self.window.setSizeLimits(.{
- .width = min.width,
- .height = min.height,
- }, if (max_) |max| .{
- .width = max.width,
- .height = max.height,
- } else .{
- .width = null,
- .height = null,
- });
- }
-
- /// Returns the content scale for the created window.
- pub fn getContentScale(self: *const Surface) !apprt.ContentScale {
- const scale = self.window.getContentScale();
- return apprt.ContentScale{ .x = scale.x_scale, .y = scale.y_scale };
- }
-
- /// Returns the size of the window in pixels. The pixel size may
- /// not match screen coordinate size but we should be able to convert
- /// back and forth using getContentScale.
- pub fn getSize(self: *const Surface) !apprt.SurfaceSize {
- const size = self.window.getFramebufferSize();
- return apprt.SurfaceSize{ .width = size.width, .height = size.height };
- }
-
- /// Returns the cursor position in scaled pixels relative to the
- /// upper-left of the window.
- pub fn getCursorPos(self: *const Surface) !apprt.CursorPos {
- const unscaled_pos = self.window.getCursorPos();
- const pos = try self.cursorPosToPixels(unscaled_pos);
- return apprt.CursorPos{
- .x = @floatCast(pos.xpos),
- .y = @floatCast(pos.ypos),
- };
- }
-
- /// Set the flag that notes this window should be closed for the next
- /// iteration of the event loop.
- pub fn setShouldClose(self: *Surface) void {
- self.window.setShouldClose(true);
- }
-
- /// Returns true if the window is flagged to close.
- pub fn shouldClose(self: *const Surface) bool {
- return self.window.shouldClose();
- }
-
- /// Set the title of the window.
- fn setTitle(self: *Surface, slice: [:0]const u8) !void {
- if (self.title_text) |t| self.core_surface.alloc.free(t);
- self.title_text = try self.core_surface.alloc.dupeZ(u8, slice);
- self.window.setTitle(self.title_text.?.ptr);
- }
-
- /// Return the title of the window.
- pub fn getTitle(self: *Surface) ?[:0]const u8 {
- return self.title_text;
- }
-
- /// Set the shape of the cursor.
- fn setMouseShape(self: *Surface, shape: terminal.MouseShape) !void {
- if ((comptime builtin.target.os.tag.isDarwin()) and
- !internal_os.macos.isAtLeastVersion(13, 0, 0))
- {
- // We only set our cursor if we're NOT on Mac, or if we are then the
- // macOS version is >= 13 (Ventura). On prior versions, glfw crashes
- // since we use a tab group.
- return;
- }
-
- const new = glfw.Cursor.createStandard(switch (shape) {
- .default => .arrow,
- .text => .ibeam,
- .crosshair => .crosshair,
- .pointer => .pointing_hand,
- .ew_resize => .resize_ew,
- .ns_resize => .resize_ns,
- .nwse_resize => .resize_nwse,
- .nesw_resize => .resize_nesw,
- .all_scroll => .resize_all,
- .not_allowed => .not_allowed,
- else => return, // unsupported, ignore
- }) orelse {
- const err = glfw.mustGetErrorCode();
- log.warn("error creating cursor: {}", .{err});
- return;
- };
- errdefer new.destroy();
-
- // Set our cursor before we destroy the old one
- self.window.setCursor(new);
-
- if (self.cursor) |c| c.destroy();
- self.cursor = new;
- }
-
- /// Set the visibility of the mouse cursor.
- fn setMouseVisibility(self: *Surface, visible: bool) void {
- self.window.setInputModeCursor(if (visible) .normal else .hidden);
- }
-
- pub fn supportsClipboard(
- self: *const Surface,
- clipboard_type: apprt.Clipboard,
- ) bool {
- _ = self;
- return switch (clipboard_type) {
- .standard => true,
- .selection, .primary => comptime builtin.os.tag == .linux,
- };
- }
-
- /// Start an async clipboard request.
- pub fn clipboardRequest(
- self: *Surface,
- clipboard_type: apprt.Clipboard,
- state: apprt.ClipboardRequest,
- ) !void {
- // GLFW can read clipboards immediately so just do that.
- const str: [:0]const u8 = switch (clipboard_type) {
- .standard => glfw.getClipboardString() orelse return glfw.mustGetErrorCode(),
- .selection, .primary => selection: {
- // Not supported except on Linux
- if (comptime builtin.os.tag != .linux) break :selection "";
-
- const raw = glfwNative.getX11SelectionString() orelse
- return glfw.mustGetErrorCode();
- break :selection std.mem.span(raw);
- },
- };
-
- // Complete our request. We always allow unsafe because we don't
- // want to deal with user confirmation in this runtime.
- try self.core_surface.completeClipboardRequest(state, str, true);
- }
-
- /// Set the clipboard.
- pub fn setClipboardString(
- self: *const Surface,
- val: [:0]const u8,
- clipboard_type: apprt.Clipboard,
- confirm: bool,
- ) !void {
- _ = confirm;
- _ = self;
- switch (clipboard_type) {
- .standard => glfw.setClipboardString(val),
- .selection, .primary => {
- // Not supported except on Linux
- if (comptime builtin.os.tag != .linux) return;
- glfwNative.setX11SelectionString(val.ptr);
- },
- }
- }
-
- /// The cursor position from glfw directly is in screen coordinates but
- /// all our interface works in pixels.
- fn cursorPosToPixels(self: *const Surface, pos: glfw.Window.CursorPos) !glfw.Window.CursorPos {
- // The cursor position is in screen coordinates but we
- // want it in pixels. we need to get both the size of the
- // window in both to get the ratio to make the conversion.
- const size = self.window.getSize();
- const fb_size = self.window.getFramebufferSize();
-
- // If our framebuffer and screen are the same, then there is no scaling
- // happening and we can short-circuit by returning the pos as-is.
- if (fb_size.width == size.width and fb_size.height == size.height)
- return pos;
-
- const x_scale = @as(f64, @floatFromInt(fb_size.width)) / @as(f64, @floatFromInt(size.width));
- const y_scale = @as(f64, @floatFromInt(fb_size.height)) / @as(f64, @floatFromInt(size.height));
- return .{
- .xpos = pos.xpos * x_scale,
- .ypos = pos.ypos * y_scale,
- };
- }
-
- pub fn defaultTermioEnv(self: *Surface) !std.process.EnvMap {
- return try internal_os.getEnvMap(self.app.app.alloc);
- }
-
- fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
- _ = width;
- _ = height;
-
- // Get the size. We are given a width/height but this is in screen
- // coordinates and we want raw pixels. The core window uses the content
- // scale to scale appropriately.
- const core_win = window.getUserPointer(CoreSurface) orelse return;
- const size = core_win.rt_surface.getSize() catch |err| {
- log.err("error querying window size for size callback err={}", .{err});
- return;
- };
-
- // Call the primary callback.
- core_win.sizeCallback(size) catch |err| {
- log.err("error in size callback err={}", .{err});
- return;
- };
- }
-
- fn charCallback(window: glfw.Window, codepoint: u21) void {
- const core_win = window.getUserPointer(CoreSurface) orelse return;
-
- // We need a key event in order to process the charcallback. If it
- // isn't set then the key event was consumed.
- var key_event = core_win.rt_surface.key_event orelse return;
- core_win.rt_surface.key_event = null;
-
- // Populate the utf8 value for the event
- var buf: [4]u8 = undefined;
- const len = std.unicode.utf8Encode(codepoint, &buf) catch |err| {
- log.err("error encoding codepoint={} err={}", .{ codepoint, err });
- return;
- };
- key_event.utf8 = buf[0..len];
-
- // On macOS we need to also disable some modifiers because
- // alt+key consumes the alt.
- if (comptime builtin.target.os.tag.isDarwin()) {
- // For GLFW, we say we always consume alt because
- // GLFW doesn't have a way to disable the alt key.
- key_event.consumed_mods.alt = true;
- }
-
- _ = core_win.keyCallback(key_event) catch |err| {
- log.err("error in key callback err={}", .{err});
- return;
- };
- }
-
- fn keyCallback(
- window: glfw.Window,
- glfw_key: glfw.Key,
- scancode: i32,
- glfw_action: glfw.Action,
- glfw_mods: glfw.Mods,
- ) void {
- _ = scancode;
-
- const core_win = window.getUserPointer(CoreSurface) orelse return;
-
- // Convert our glfw types into our input types
- const mods: input.Mods = .{
- .shift = glfw_mods.shift,
- .ctrl = glfw_mods.control,
- .alt = glfw_mods.alt,
- .super = glfw_mods.super,
- };
- const action: input.Action = switch (glfw_action) {
- .release => .release,
- .press => .press,
- .repeat => .repeat,
- };
- const key: input.Key = switch (glfw_key) {
- .a => .key_a,
- .b => .key_b,
- .c => .key_c,
- .d => .key_d,
- .e => .key_e,
- .f => .key_f,
- .g => .key_g,
- .h => .key_h,
- .i => .key_i,
- .j => .key_j,
- .k => .key_k,
- .l => .key_l,
- .m => .key_m,
- .n => .key_n,
- .o => .key_o,
- .p => .key_p,
- .q => .key_q,
- .r => .key_r,
- .s => .key_s,
- .t => .key_t,
- .u => .key_u,
- .v => .key_v,
- .w => .key_w,
- .x => .key_x,
- .y => .key_y,
- .z => .key_z,
- .zero => .digit_0,
- .one => .digit_1,
- .two => .digit_2,
- .three => .digit_3,
- .four => .digit_4,
- .five => .digit_5,
- .six => .digit_6,
- .seven => .digit_7,
- .eight => .digit_8,
- .nine => .digit_9,
- .up => .arrow_up,
- .down => .arrow_down,
- .right => .arrow_right,
- .left => .arrow_left,
- .home => .home,
- .end => .end,
- .page_up => .page_up,
- .page_down => .page_down,
- .escape => .escape,
- .F1 => .f1,
- .F2 => .f2,
- .F3 => .f3,
- .F4 => .f4,
- .F5 => .f5,
- .F6 => .f6,
- .F7 => .f7,
- .F8 => .f8,
- .F9 => .f9,
- .F10 => .f10,
- .F11 => .f11,
- .F12 => .f12,
- .F13 => .f13,
- .F14 => .f14,
- .F15 => .f15,
- .F16 => .f16,
- .F17 => .f17,
- .F18 => .f18,
- .F19 => .f19,
- .F20 => .f20,
- .F21 => .f21,
- .F22 => .f22,
- .F23 => .f23,
- .F24 => .f24,
- .F25 => .f25,
- .kp_0 => .numpad_0,
- .kp_1 => .numpad_1,
- .kp_2 => .numpad_2,
- .kp_3 => .numpad_3,
- .kp_4 => .numpad_4,
- .kp_5 => .numpad_5,
- .kp_6 => .numpad_6,
- .kp_7 => .numpad_7,
- .kp_8 => .numpad_8,
- .kp_9 => .numpad_9,
- .kp_decimal => .numpad_decimal,
- .kp_divide => .numpad_divide,
- .kp_multiply => .numpad_multiply,
- .kp_subtract => .numpad_subtract,
- .kp_add => .numpad_add,
- .kp_enter => .numpad_enter,
- .kp_equal => .numpad_equal,
- .grave_accent => .backquote,
- .minus => .minus,
- .equal => .equal,
- .space => .space,
- .semicolon => .semicolon,
- .apostrophe => .quote,
- .comma => .comma,
- .period => .period,
- .slash => .slash,
- .left_bracket => .bracket_left,
- .right_bracket => .bracket_right,
- .backslash => .backslash,
- .enter => .enter,
- .tab => .tab,
- .backspace => .backspace,
- .insert => .insert,
- .delete => .delete,
- .caps_lock => .caps_lock,
- .scroll_lock => .scroll_lock,
- .num_lock => .num_lock,
- .print_screen => .print_screen,
- .pause => .pause,
- .left_shift => .shift_left,
- .left_control => .control_left,
- .left_alt => .alt_left,
- .left_super => .meta_left,
- .right_shift => .shift_right,
- .right_control => .control_right,
- .right_alt => .alt_right,
- .right_super => .meta_right,
- .menu => .context_menu,
-
- .world_1,
- .world_2,
- .unknown,
- => .unidentified,
- };
-
- // This is a hack for GLFW. We require our apprts to send both
- // the UTF8 encoding AND the keypress at the same time. Its critical
- // for things like ctrl sequences to work. However, GLFW doesn't
- // provide this information all at once. So we just infer based on
- // the key press. This isn't portable but GLFW is only for testing.
- const utf8 = switch (key) {
- inline else => |k| utf8: {
- if (mods.shift) break :utf8 "";
- const cp = k.codepoint() orelse break :utf8 "";
- const byte = std.math.cast(u8, cp) orelse break :utf8 "";
- break :utf8 &.{byte};
- },
- };
-
- const key_event: input.KeyEvent = .{
- .action = action,
- .key = key,
- .mods = mods,
- .consumed_mods = .{},
- .composing = false,
- .utf8 = utf8,
- .unshifted_codepoint = if (utf8.len > 0) @intCast(utf8[0]) else 0,
- };
-
- const effect = core_win.keyCallback(key_event) catch |err| {
- log.err("error in key callback err={}", .{err});
- return;
- };
-
- // Surface closed.
- if (effect == .closed) return;
-
- // If it wasn't consumed, we set it on our self so that charcallback
- // can make another attempt. Otherwise, we set null so the charcallback
- // is ignored.
- core_win.rt_surface.key_event = null;
- if (effect == .ignored and
- (action == .press or action == .repeat))
- {
- core_win.rt_surface.key_event = key_event;
- }
- }
-
- fn focusCallback(window: glfw.Window, focused: bool) void {
- const core_win = window.getUserPointer(CoreSurface) orelse return;
- core_win.focusCallback(focused) catch |err| {
- log.err("error in focus callback err={}", .{err});
- return;
- };
- }
-
- fn refreshCallback(window: glfw.Window) void {
- const core_win = window.getUserPointer(CoreSurface) orelse return;
- core_win.refreshCallback() catch |err| {
- log.err("error in refresh callback err={}", .{err});
- return;
- };
- }
-
- fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void {
- // Glfw doesn't support any of the scroll mods.
- const scroll_mods: input.ScrollMods = .{};
-
- const core_win = window.getUserPointer(CoreSurface) orelse return;
- core_win.scrollCallback(xoff, yoff, scroll_mods) catch |err| {
- log.err("error in scroll callback err={}", .{err});
- return;
- };
- }
-
- fn cursorPosCallback(
- window: glfw.Window,
- unscaled_xpos: f64,
- unscaled_ypos: f64,
- ) void {
- const core_win = window.getUserPointer(CoreSurface) orelse return;
-
- // Convert our unscaled x/y to scaled.
- const pos = core_win.rt_surface.cursorPosToPixels(.{
- .xpos = unscaled_xpos,
- .ypos = unscaled_ypos,
- }) catch |err| {
- log.err(
- "error converting cursor pos to scaled pixels in cursor pos callback err={}",
- .{err},
- );
- return;
- };
-
- core_win.cursorPosCallback(.{
- .x = @floatCast(pos.xpos),
- .y = @floatCast(pos.ypos),
- }, null) catch |err| {
- log.err("error in cursor pos callback err={}", .{err});
- return;
- };
- }
-
- fn mouseButtonCallback(
- window: glfw.Window,
- glfw_button: glfw.MouseButton,
- glfw_action: glfw.Action,
- glfw_mods: glfw.Mods,
- ) void {
- const core_win = window.getUserPointer(CoreSurface) orelse return;
-
- // Convert glfw button to input button
- const mods: input.Mods = .{
- .shift = glfw_mods.shift,
- .ctrl = glfw_mods.control,
- .alt = glfw_mods.alt,
- .super = glfw_mods.super,
- };
- const button: input.MouseButton = switch (glfw_button) {
- .left => .left,
- .right => .right,
- .middle => .middle,
- .four => .four,
- .five => .five,
- .six => .six,
- .seven => .seven,
- .eight => .eight,
- };
- const action: input.MouseButtonState = switch (glfw_action) {
- .press => .press,
- .release => .release,
- else => unreachable,
- };
-
- _ = core_win.mouseButtonCallback(action, button, mods) catch |err| {
- log.err("error in scroll callback err={}", .{err});
- return;
- };
- }
-
- fn dropCallback(window: glfw.Window, paths: [][*:0]const u8) void {
- const surface = window.getUserPointer(CoreSurface) orelse return;
-
- var list = std.ArrayList(u8).init(surface.alloc);
- defer list.deinit();
-
- for (paths) |path| {
- const path_slice = std.mem.span(path);
-
- // preallocate worst case of escaping every char + space
- list.ensureTotalCapacity(path_slice.len * 2 + 1) catch |err| {
- log.err("error in drop callback err={}", .{err});
- return;
- };
-
- const writer = list.writer();
- for (path_slice) |c| {
- if (std.mem.indexOfScalar(u8, "\\ ()[]{}<>\"'`!#$&;|*?\t", c)) |_| {
- writer.print("\\{c}", .{c}) catch unreachable; // memory preallocated
- } else writer.writeByte(c) catch unreachable; // same here
- }
- writer.writeByte(' ') catch unreachable; // separate paths
-
- surface.textCallback(list.items) catch |err| {
- log.err("error in drop callback err={}", .{err});
- return;
- };
-
- list.clearRetainingCapacity(); // avoid unnecessary reallocations
- }
- }
-};
diff --git a/src/build/Config.zig b/src/build/Config.zig
index 5f8780af9..3449a9ce3 100644
--- a/src/build/Config.zig
+++ b/src/build/Config.zig
@@ -378,11 +378,6 @@ pub fn init(b: *std.Build) !Config {
"glslang",
"spirv-cross",
"simdutf",
-
- // This is default false because it is used for testing
- // primarily and not official packaging. The packaging
- // guide advises against building the GLFW backend.
- "glfw3",
}) |dep| {
_ = b.systemIntegrationOption(dep, .{ .default = false });
}
diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig
index f173e4856..5d620ac75 100644
--- a/src/build/SharedDeps.zig
+++ b/src/build/SharedDeps.zig
@@ -515,17 +515,6 @@ pub fn add(
switch (self.config.app_runtime) {
.none => {},
-
- .glfw => if (b.lazyDependency("glfw", .{
- .target = target,
- .optimize = optimize,
- })) |glfw_dep| {
- step.root_module.addImport(
- "glfw",
- glfw_dep.module("glfw"),
- );
- },
-
.gtk => try self.addGTK(step),
}
}
diff --git a/src/config/Config.zig b/src/config/Config.zig
index fec5b41fc..7a08b5583 100644
--- a/src/config/Config.zig
+++ b/src/config/Config.zig
@@ -1582,9 +1582,9 @@ keybind: Keybinds = .{},
/// the visible screen area. This means that if the menu bar is visible, the
/// window will be placed below the menu bar.
///
-/// Note: this is only supported on macOS and Linux GLFW builds. The GTK
-/// runtime does not support setting the window position, as windows are
-/// only allowed position themselves in X11 and not Wayland.
+/// Note: this is only supported on macOS. The GTK runtime does not support
+/// setting the window position, as windows are only allowed position
+/// themselves in X11 and not Wayland.
@"window-position-x": ?i16 = null,
@"window-position-y": ?i16 = null,
@@ -2504,8 +2504,6 @@ keybind: Keybinds = .{},
///
/// The values `left` or `right` enable this for the left or right *Option*
/// key, respectively.
-///
-/// This does not work with GLFW builds.
@"macos-option-as-alt": ?OptionAsAlt = null,
/// Whether to enable the macOS window shadow. The default value is true.
diff --git a/src/main_ghostty.zig b/src/main_ghostty.zig
index 567eec5f9..b747fe6f0 100644
--- a/src/main_ghostty.zig
+++ b/src/main_ghostty.zig
@@ -7,7 +7,6 @@ const Allocator = std.mem.Allocator;
const posix = std.posix;
const build_config = @import("build_config.zig");
const options = @import("build_options");
-const glfw = @import("glfw");
const glslang = @import("glslang");
const macos = @import("macos");
const oni = @import("oniguruma");
diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig
index 3899bb8c5..70be1a96b 100644
--- a/src/renderer/Metal.zig
+++ b/src/renderer/Metal.zig
@@ -5,7 +5,6 @@ const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const builtin = @import("builtin");
-const glfw = @import("glfw");
const objc = @import("objc");
const macos = @import("macos");
const graphics = macos.graphics;
@@ -38,11 +37,6 @@ pub const swap_chain_count = 3;
const log = std.log.scoped(.metal);
-// Get native API access on certain platforms so we can do more customization.
-const glfwNative = glfw.Native(.{
- .cocoa = builtin.os.tag == .macos,
-});
-
layer: IOSurfaceLayer,
/// MTLDevice
@@ -87,27 +81,6 @@ pub fn init(alloc: Allocator, opts: rendererpkg.Options) !Metal {
// Get the metadata about our underlying view that we'll be rendering to.
const info: ViewInfo = switch (apprt.runtime) {
- apprt.glfw => info: {
- // Everything in glfw is window-oriented so we grab the backing
- // window, then derive everything from that.
- const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(
- opts.rt_surface.window,
- ).?);
-
- const contentView = objc.Object.fromId(
- nswindow.getProperty(?*anyopaque, "contentView").?,
- );
- const scaleFactor = nswindow.getProperty(
- graphics.c.CGFloat,
- "backingScaleFactor",
- );
-
- break :info .{
- .view = contentView,
- .scaleFactor = scaleFactor,
- };
- },
-
apprt.embedded => .{
.scaleFactor = @floatCast(opts.rt_surface.content_scale.x),
.view = switch (opts.rt_surface.platform) {
diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig
index cf195361e..00df8e273 100644
--- a/src/renderer/OpenGL.zig
+++ b/src/renderer/OpenGL.zig
@@ -5,7 +5,6 @@ const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const builtin = @import("builtin");
-const glfw = @import("glfw");
const gl = @import("opengl");
const shadertoy = @import("shadertoy.zig");
const apprt = @import("../apprt.zig");
@@ -60,18 +59,6 @@ pub fn deinit(self: *OpenGL) void {
self.* = undefined;
}
-/// Returns the hints that we want for this
-pub fn glfwWindowHints(config: *const configpkg.Config) glfw.Window.Hints {
- _ = config;
- return .{
- .context_version_major = MIN_VERSION_MAJOR,
- .context_version_minor = MIN_VERSION_MINOR,
- .opengl_profile = .opengl_core_profile,
- .opengl_forward_compat = true,
- .transparent_framebuffer = true,
- };
-}
-
/// 32-bit windows cross-compilation breaks with `.c` for some reason, so...
const gl_debug_proc_callconv =
@typeInfo(
@@ -172,8 +159,7 @@ fn prepareContext(getProcAddress: anytype) !void {
/// This is called early right after surface creation.
pub fn surfaceInit(surface: *apprt.Surface) !void {
- // Treat this like a thread entry
- const self: OpenGL = undefined;
+ _ = surface;
switch (apprt.runtime) {
else => @compileError("unsupported app runtime for OpenGL"),
@@ -181,8 +167,6 @@ pub fn surfaceInit(surface: *apprt.Surface) !void {
// GTK uses global OpenGL context so we load from null.
apprt.gtk => try prepareContext(null),
- apprt.glfw => try self.threadEnter(surface),
-
apprt.embedded => {
// TODO(mitchellh): this does nothing today to allow libghostty
// to compile for OpenGL targets but libghostty is strictly
@@ -205,17 +189,12 @@ pub fn surfaceInit(surface: *apprt.Surface) !void {
pub fn finalizeSurfaceInit(self: *const OpenGL, surface: *apprt.Surface) !void {
_ = self;
_ = surface;
-
- // For GLFW, we grabbed the OpenGL context in surfaceInit and
- // we need to release it before we start the renderer thread.
- if (apprt.runtime == apprt.glfw) {
- glfw.makeContextCurrent(null);
- }
}
/// Callback called by renderer.Thread when it begins.
pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void {
_ = self;
+ _ = surface;
switch (apprt.runtime) {
else => @compileError("unsupported app runtime for OpenGL"),
@@ -227,21 +206,6 @@ pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void {
// on the main thread. As such, we don't do anything here.
},
- apprt.glfw => {
- // We need to make the OpenGL context current. OpenGL requires
- // that a single thread own the a single OpenGL context (if any).
- // This ensures that the context switches over to our thread.
- // Important: the prior thread MUST have detached the context
- // prior to calling this entrypoint.
- glfw.makeContextCurrent(surface.window);
- errdefer glfw.makeContextCurrent(null);
- glfw.swapInterval(1);
-
- // Load OpenGL bindings. This API is context-aware so this sets
- // a threadlocal context for these pointers.
- try prepareContext(&glfw.getProcAddress);
- },
-
apprt.embedded => {
// TODO(mitchellh): this does nothing today to allow libghostty
// to compile for OpenGL targets but libghostty is strictly
@@ -262,11 +226,6 @@ pub fn threadExit(self: *const OpenGL) void {
// be sharing the global bindings with other windows.
},
- apprt.glfw => {
- gl.glad.unload();
- glfw.makeContextCurrent(null);
- },
-
apprt.embedded => {
// TODO: see threadEnter
},
diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig
index e7faf633f..1263364d3 100644
--- a/src/renderer/generic.zig
+++ b/src/renderer/generic.zig
@@ -1,6 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
-const glfw = @import("glfw");
const xev = @import("xev");
const wuffs = @import("wuffs");
const apprt = @import("../apprt.zig");
@@ -606,20 +605,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
}
};
- /// Returns the hints that we want for this window.
- pub fn glfwWindowHints(config: *const configpkg.Config) glfw.Window.Hints {
- // If our graphics API provides hints, use them,
- // otherwise fall back to generic hints.
- if (@hasDecl(GraphicsAPI, "glfwWindowHints")) {
- return GraphicsAPI.glfwWindowHints(config);
- }
-
- return .{
- .client_api = .no_api,
- .transparent_framebuffer = config.@"background-opacity" < 1,
- };
- }
-
pub fn init(alloc: Allocator, options: renderer.Options) !Self {
// Initialize our graphics API wrapper, this will prepare the
// surface provided by the apprt and set up any API-specific
diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig
index 040132f03..039b11c03 100644
--- a/src/termio/stream_handler.zig
+++ b/src/termio/stream_handler.zig
@@ -15,11 +15,6 @@ const posix = std.posix;
const log = std.log.scoped(.io_handler);
-/// True if we should disable the kitty keyboard protocol. We have to
-/// disable this on GLFW because GLFW input events don't support the
-/// correct granularity of events.
-const disable_kitty_keyboard_protocol = apprt.runtime == apprt.glfw;
-
/// This is used as the handler for the terminal.Stream type. This is
/// stateful and is expected to live for the entire lifetime of the terminal.
/// It is NOT VALID to stop a stream handler, create a new one, and use that
@@ -913,8 +908,6 @@ pub const StreamHandler = struct {
}
pub fn queryKittyKeyboard(self: *StreamHandler) !void {
- if (comptime disable_kitty_keyboard_protocol) return;
-
log.debug("querying kitty keyboard mode", .{});
var data: termio.Message.WriteReq.Small.Array = undefined;
const resp = try std.fmt.bufPrint(&data, "\x1b[?{}u", .{
@@ -933,15 +926,11 @@ pub const StreamHandler = struct {
self: *StreamHandler,
flags: terminal.kitty.KeyFlags,
) !void {
- if (comptime disable_kitty_keyboard_protocol) return;
-
log.debug("pushing kitty keyboard mode: {}", .{flags});
self.terminal.screen.kitty_keyboard.push(flags);
}
pub fn popKittyKeyboard(self: *StreamHandler, n: u16) !void {
- if (comptime disable_kitty_keyboard_protocol) return;
-
log.debug("popping kitty keyboard mode n={}", .{n});
self.terminal.screen.kitty_keyboard.pop(@intCast(n));
}
@@ -951,8 +940,6 @@ pub const StreamHandler = struct {
mode: terminal.kitty.KeySetMode,
flags: terminal.kitty.KeyFlags,
) !void {
- if (comptime disable_kitty_keyboard_protocol) return;
-
log.debug("setting kitty keyboard mode: {} {}", .{ mode, flags });
self.terminal.screen.kitty_keyboard.set(mode, flags);
}