diff --git a/src/Window.zig b/src/Window.zig index 074d04f26..e4b7422af 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -30,6 +30,7 @@ const input = @import("input.zig"); const DevMode = @import("DevMode.zig"); const App = @import("App.zig"); const internal_os = @import("os/main.zig"); +const WindowingSystem = @import("window.zig").System; // Get native API access on certain platforms so we can do more customization. const glfwNative = glfw.Native(.{ @@ -47,6 +48,9 @@ alloc: Allocator, /// The app that this window is a part of. app: *App, +/// The windowing system state +windowing_system: WindowingSystem, + /// The font structures font_lib: font.Library, font_group: *font.GroupCache, @@ -135,6 +139,10 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { var self = try alloc.create(Window); errdefer alloc.destroy(self); + // Create the windowing system + var winsys = try WindowingSystem.init(app); + winsys.deinit(); + // Create our window const window = try glfw.Window.create(640, 480, "ghostty", null, null, Renderer.windowHints()); errdefer window.destroy(); @@ -153,6 +161,16 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { nswindow.setProperty("tabbingIdentifier", app.darwin.tabbing_id); } + // Create the cursor + const cursor = try glfw.Cursor.createStandard(.ibeam); + errdefer cursor.destroy(); + if ((comptime !builtin.target.isDarwin()) or internal_os.macosVersionAtLeast(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. + try window.setCursor(cursor); + } + // Determine our DPI configurations so we can properly configure // font points to pixels and handle other high-DPI scaling factors. const content_scale = try window.getContentScale(); @@ -322,23 +340,6 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { cell_size, ); - // Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app - // but is otherwise somewhat arbitrary. - try window.setSizeLimits(.{ - .width = @floatToInt(u32, cell_size.width * 10), - .height = @floatToInt(u32, cell_size.height * 4), - }, .{ .width = null, .height = null }); - - // Create the cursor - const cursor = try glfw.Cursor.createStandard(.ibeam); - errdefer cursor.destroy(); - if ((comptime !builtin.target.isDarwin()) or internal_os.macosVersionAtLeast(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. - try window.setCursor(cursor); - } - // The mutex used to protect our renderer state. var mutex = try alloc.create(std.Thread.Mutex); mutex.* = .{}; @@ -377,6 +378,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { self.* = .{ .alloc = alloc, .app = app, + .windowing_system = winsys, .font_lib = font_lib, .font_group = font_group, .font_size = font_size, @@ -409,6 +411,13 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { }; errdefer if (DevMode.enabled) self.imgui_ctx.destroy(); + // Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app + // but is otherwise somewhat arbitrary. + try window.setSizeLimits(.{ + .width = @floatToInt(u32, cell_size.width * 10), + .height = @floatToInt(u32, cell_size.height * 4), + }, .{ .width = null, .height = null }); + // Setup our callbacks and user data window.setUserPointer(self); window.setSizeCallback(sizeCallback); diff --git a/src/window.zig b/src/window.zig new file mode 100644 index 000000000..6132b90a9 --- /dev/null +++ b/src/window.zig @@ -0,0 +1,18 @@ +//! Window implementation and utilities. The window subsystem is responsible +//! for maintaining a "window" or "surface" abstraction around a terminal, +//! effectively being the primary interface to the terminal. + +const builtin = @import("builtin"); + +pub usingnamespace @import("window/structs.zig"); +pub const Glfw = @import("window/Glfw.zig"); + +/// The implementation to use for the windowing system. This is comptime chosen +/// so that every build has exactly one windowing implementation. +pub const System = switch (builtin.os.tag) { + else => Glfw, +}; + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/src/window/Glfw.zig b/src/window/Glfw.zig new file mode 100644 index 000000000..ad84b1adf --- /dev/null +++ b/src/window/Glfw.zig @@ -0,0 +1,77 @@ +//! Window implementation that uses GLFW (https://www.glfw.org/). +pub const Glfw = @This(); + +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const glfw = @import("glfw"); +const objc = @import("objc"); +const App = @import("../App.zig"); +const internal_os = @import("../os/main.zig"); +const renderer = @import("../renderer.zig"); +const Renderer = renderer.Renderer; +const window = @import("../window.zig"); + +// Get native API access on certain platforms so we can do more customization. +const glfwNative = glfw.Native(.{ + .cocoa = builtin.target.isDarwin(), +}); + +/// The glfw window handle +window: glfw.Window, + +/// The glfw mouse cursor handle. +cursor: glfw.Cursor, + +pub fn init(app: *const App) !Glfw { + // Create our window + const win = try glfw.Window.create(640, 480, "ghostty", null, null, Renderer.windowHints()); + errdefer win.destroy(); + try Renderer.windowInit(win); + + // On Mac, enable tabbing + if (comptime builtin.target.isDarwin()) { + 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); + } + + // Create the cursor + const cursor = try glfw.Cursor.createStandard(.ibeam); + errdefer cursor.destroy(); + if ((comptime !builtin.target.isDarwin()) or internal_os.macosVersionAtLeast(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. + try win.setCursor(cursor); + } + + return Glfw{ + .window = win, + .cursor = cursor, + }; +} + +pub fn deinit(self: *Glfw) void { + self.window.destroy(); + self.cursor.destroy(); +} + +/// Returns the content scale for the created window. +pub fn getContentScale(self: *const Glfw) !window.ContentScale { + const scale = try self.window.getContentScale(); + return window.ContentScale{ .x = scale.x_scale, .y = scale.y_scale }; +} + +/// Returns the size of the window in screen coordinates. +pub fn getSize(self: *const Glfw) !window.Size { + const size = try self.window.getSize(); + return window.Size{ .width = size.width, .height = size.height }; +} diff --git a/src/window/Web.zig b/src/window/Web.zig new file mode 100644 index 000000000..091ffc216 --- /dev/null +++ b/src/window/Web.zig @@ -0,0 +1,2 @@ +//! Window implementation for the web (browser) via WebAssembly. +pub const Window = @This(); diff --git a/src/window/Window.zig b/src/window/Window.zig new file mode 100644 index 000000000..6fa563adc --- /dev/null +++ b/src/window/Window.zig @@ -0,0 +1,10 @@ +//! Window represents a single terminal window. A terminal window is +//! a single drawable terminal surface. +//! +//! This Window is the abstract window logic that applies to all platforms. +//! Platforms are expected to implement a compile-time "interface" to +//! implement platform-specific logic. +//! +//! Note(mitchellh): We current conflate a "window" and a "surface". If +//! we implement splits, we probably will need to separate these concepts. +pub const Window = @This(); diff --git a/src/window/structs.zig b/src/window/structs.zig new file mode 100644 index 000000000..bb5590267 --- /dev/null +++ b/src/window/structs.zig @@ -0,0 +1,13 @@ +/// ContentScale is the ratio between the current DPI and the platform's +/// default DPI. This is used to determine how much certain rendered elements +/// need to be scaled up or down. +pub const ContentScale = struct { + x: f32, + y: f32, +}; + +/// The size of the window in screen coordinates. +pub const Size = struct { + width: u32, + height: u32, +};