window: abstract more, it starts

This commit is contained in:
Mitchell Hashimoto
2022-12-29 14:51:56 -08:00
parent 6eb5a0238a
commit e1cd650245
4 changed files with 111 additions and 134 deletions

View File

@ -141,44 +141,19 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
// Create the windowing system // Create the windowing system
var winsys = try WindowingSystem.init(app); var winsys = try WindowingSystem.init(app);
winsys.deinit(); errdefer winsys.deinit();
// Create our window // Initialize our renderer with our initialized windowing system.
const window = try glfw.Window.create(640, 480, "ghostty", null, null, Renderer.windowHints()); try Renderer.windowInit(winsys);
errdefer window.destroy();
try Renderer.windowInit(window);
// 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(window).?);
// 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 window.setCursor(cursor);
}
// Determine our DPI configurations so we can properly configure // Determine our DPI configurations so we can properly configure
// font points to pixels and handle other high-DPI scaling factors. // font points to pixels and handle other high-DPI scaling factors.
const content_scale = try window.getContentScale(); const content_scale = try winsys.getContentScale();
const x_dpi = content_scale.x_scale * font.face.default_dpi; const x_dpi = content_scale.x * font.face.default_dpi;
const y_dpi = content_scale.y_scale * font.face.default_dpi; const y_dpi = content_scale.y * font.face.default_dpi;
log.debug("xscale={} yscale={} xdpi={} ydpi={}", .{ log.debug("xscale={} yscale={} xdpi={} ydpi={}", .{
content_scale.x_scale, content_scale.x,
content_scale.y_scale, content_scale.y,
x_dpi, x_dpi,
y_dpi, y_dpi,
}); });
@ -330,7 +305,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
errdefer renderer_impl.deinit(); errdefer renderer_impl.deinit();
// Calculate our grid size based on known dimensions. // Calculate our grid size based on known dimensions.
const window_size = try window.getSize(); const window_size = try winsys.getSize();
const screen_size: renderer.ScreenSize = .{ const screen_size: renderer.ScreenSize = .{
.width = window_size.width, .width = window_size.width,
.height = window_size.height, .height = window_size.height,
@ -348,7 +323,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
// Create the renderer thread // Create the renderer thread
var render_thread = try renderer.Thread.init( var render_thread = try renderer.Thread.init(
alloc, alloc,
window, winsys,
&self.renderer, &self.renderer,
&self.renderer_state, &self.renderer_state,
); );
@ -382,8 +357,8 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
.font_lib = font_lib, .font_lib = font_lib,
.font_group = font_group, .font_group = font_group,
.font_size = font_size, .font_size = font_size,
.window = window, .window = winsys.window,
.cursor = cursor, .cursor = winsys.cursor,
.renderer = renderer_impl, .renderer = renderer_impl,
.renderer_thread = render_thread, .renderer_thread = render_thread,
.renderer_state = .{ .renderer_state = .{
@ -413,21 +388,22 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
// Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app // Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app
// but is otherwise somewhat arbitrary. // but is otherwise somewhat arbitrary.
try window.setSizeLimits(.{ // TODO:
.width = @floatToInt(u32, cell_size.width * 10), // try window.setSizeLimits(.{
.height = @floatToInt(u32, cell_size.height * 4), // .width = @floatToInt(u32, cell_size.width * 10),
}, .{ .width = null, .height = null }); // .height = @floatToInt(u32, cell_size.height * 4),
// }, .{ .width = null, .height = null });
// Setup our callbacks and user data // Setup our callbacks and user data
window.setUserPointer(self); winsys.window.setUserPointer(self);
window.setSizeCallback(sizeCallback); winsys.window.setSizeCallback(sizeCallback);
window.setCharCallback(charCallback); winsys.window.setCharCallback(charCallback);
window.setKeyCallback(keyCallback); winsys.window.setKeyCallback(keyCallback);
window.setFocusCallback(focusCallback); winsys.window.setFocusCallback(focusCallback);
window.setRefreshCallback(refreshCallback); winsys.window.setRefreshCallback(refreshCallback);
window.setScrollCallback(scrollCallback); winsys.window.setScrollCallback(scrollCallback);
window.setCursorPosCallback(cursorPosCallback); winsys.window.setCursorPosCallback(cursorPosCallback);
window.setMouseButtonCallback(mouseButtonCallback); winsys.window.setMouseButtonCallback(mouseButtonCallback);
// Call our size callback which handles all our retina setup // Call our size callback which handles all our retina setup
// Note: this shouldn't be necessary and when we clean up the window // Note: this shouldn't be necessary and when we clean up the window
@ -435,7 +411,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
// sizeCallback does retina-aware stuff we don't do here and don't want // sizeCallback does retina-aware stuff we don't do here and don't want
// to duplicate. // to duplicate.
sizeCallback( sizeCallback(
window, winsys.window,
@intCast(i32, window_size.width), @intCast(i32, window_size.width),
@intCast(i32, window_size.height), @intCast(i32, window_size.height),
); );
@ -461,12 +437,12 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
DevMode.instance.window = self; DevMode.instance.window = self;
// Let our renderer setup // Let our renderer setup
try renderer_impl.initDevMode(window); try renderer_impl.initDevMode(winsys);
} }
// Give the renderer one more opportunity to finalize any window // Give the renderer one more opportunity to finalize any window
// setup on the main thread prior to spinning up the rendering thread. // setup on the main thread prior to spinning up the rendering thread.
try renderer_impl.finalizeWindowInit(window); try renderer_impl.finalizeWindowInit(winsys);
// Start our renderer thread // Start our renderer thread
self.renderer_thr = try std.Thread.spawn( self.renderer_thr = try std.Thread.spawn(
@ -495,7 +471,7 @@ pub fn destroy(self: *Window) void {
self.renderer_thr.join(); self.renderer_thr.join();
// We need to become the active rendering thread again // We need to become the active rendering thread again
self.renderer.threadEnter(self.window) catch unreachable; self.renderer.threadEnter(self.windowing_system) catch unreachable;
self.renderer_thread.deinit(); self.renderer_thread.deinit();
// If we are devmode-owning, clean that up. // If we are devmode-owning, clean that up.
@ -525,51 +501,7 @@ pub fn destroy(self: *Window) void {
self.io.deinit(); self.io.deinit();
} }
var tabgroup_opt: if (builtin.target.isDarwin()) ?objc.Object else void = undefined; self.windowing_system.deinit();
if (comptime builtin.target.isDarwin()) {
const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(self.window).?);
const tabgroup = nswindow.getProperty(objc.Object, "tabGroup");
// On macOS versions prior to Ventura, we lose window focus on tab close
// for some reason. We manually fix this by keeping track of the tab
// group and just selecting the next window.
if (internal_os.macosVersionAtLeast(13, 0, 0))
tabgroup_opt = null
else
tabgroup_opt = 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 => tabgroup_opt = null,
// 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 => {},
}
}
self.window.destroy();
// If we have a tabgroup set, we want to manually focus the next window.
// We should NOT have to do this usually, see the comments above.
if (comptime builtin.target.isDarwin()) {
if (tabgroup_opt) |tabgroup| {
const selected = tabgroup.getProperty(objc.Object, "selectedWindow");
selected.msgSend(void, objc.sel("makeKeyWindow"), .{});
}
}
// We can destroy the cursor right away. glfw will just revert any
// windows using it to the default.
self.cursor.destroy();
self.font_group.deinit(self.alloc); self.font_group.deinit(self.alloc);
self.font_lib.deinit(); self.font_lib.deinit();

View File

@ -18,6 +18,7 @@ const math = @import("../math.zig");
const lru = @import("../lru.zig"); const lru = @import("../lru.zig");
const DevMode = @import("../DevMode.zig"); const DevMode = @import("../DevMode.zig");
const Window = @import("../Window.zig"); const Window = @import("../Window.zig");
const window = @import("../window.zig");
const log = std.log.scoped(.grid); const log = std.log.scoped(.grid);
@ -350,7 +351,7 @@ fn resetCellsLRU(self: *OpenGL) void {
} }
/// Returns the hints that we want for this /// Returns the hints that we want for this
pub fn windowHints() glfw.Window.Hints { pub fn glfwWindowHints() glfw.Window.Hints {
return .{ return .{
.context_version_major = 3, .context_version_major = 3,
.context_version_minor = 3, .context_version_minor = 3,
@ -363,10 +364,10 @@ pub fn windowHints() glfw.Window.Hints {
/// This is called early right after window creation to setup our /// This is called early right after window creation to setup our
/// window surface as necessary. /// window surface as necessary.
pub fn windowInit(window: glfw.Window) !void { pub fn windowInit(winsys: window.System) !void {
// Treat this like a thread entry // Treat this like a thread entry
const self: OpenGL = undefined; const self: OpenGL = undefined;
try self.threadEnter(window); try self.threadEnter(winsys);
// Blending for text // Blending for text
try gl.enable(gl.c.GL_BLEND); try gl.enable(gl.c.GL_BLEND);
@ -380,40 +381,23 @@ pub fn windowInit(window: glfw.Window) !void {
// log.debug("OpenGL extension available name={s}", .{ext}); // log.debug("OpenGL extension available name={s}", .{ext});
// } // }
// } // }
if (builtin.mode == .Debug) {
// Get our physical DPI - debug only because we don't have a use for
// this but the logging of it may be useful
const monitor = window.getMonitor() orelse monitor: {
log.warn("window had null monitor, getting primary monitor", .{});
break :monitor glfw.Monitor.getPrimary().?;
};
const physical_size = monitor.getPhysicalSize();
const video_mode = try monitor.getVideoMode();
const physical_x_dpi = @intToFloat(f32, video_mode.getWidth()) / (@intToFloat(f32, physical_size.width_mm) / 25.4);
const physical_y_dpi = @intToFloat(f32, video_mode.getHeight()) / (@intToFloat(f32, physical_size.height_mm) / 25.4);
log.debug("physical dpi x={} y={}", .{
physical_x_dpi,
physical_y_dpi,
});
}
} }
/// This is called just prior to spinning up the renderer thread for /// This is called just prior to spinning up the renderer thread for
/// final main thread setup requirements. /// final main thread setup requirements.
pub fn finalizeWindowInit(self: *const OpenGL, window: glfw.Window) !void { pub fn finalizeWindowInit(self: *const OpenGL, winsys: window.System) !void {
_ = self; _ = self;
_ = window; _ = winsys;
} }
/// This is called if this renderer runs DevMode. /// This is called if this renderer runs DevMode.
pub fn initDevMode(self: *const OpenGL, window: glfw.Window) !void { pub fn initDevMode(self: *const OpenGL, winsys: window.System) !void {
_ = self; _ = self;
if (DevMode.enabled) { if (DevMode.enabled) {
// Initialize for our window // Initialize for our window
assert(imgui.ImplGlfw.initForOpenGL( assert(imgui.ImplGlfw.initForOpenGL(
@ptrCast(*imgui.ImplGlfw.GLFWWindow, window.handle), @ptrCast(*imgui.ImplGlfw.GLFWWindow, winsys.window.handle),
true, true,
)); ));
assert(imgui.ImplOpenGL3.init("#version 330 core")); assert(imgui.ImplOpenGL3.init("#version 330 core"));
@ -431,7 +415,7 @@ pub fn deinitDevMode(self: *const OpenGL) void {
} }
/// Callback called by renderer.Thread when it begins. /// Callback called by renderer.Thread when it begins.
pub fn threadEnter(self: *const OpenGL, window: glfw.Window) !void { pub fn threadEnter(self: *const OpenGL, winsys: window.System) !void {
_ = self; _ = self;
// We need to make the OpenGL context current. OpenGL requires // We need to make the OpenGL context current. OpenGL requires
@ -439,7 +423,7 @@ pub fn threadEnter(self: *const OpenGL, window: glfw.Window) !void {
// ensures that the context switches over to our thread. Important: // ensures that the context switches over to our thread. Important:
// the prior thread MUST have detached the context prior to calling // the prior thread MUST have detached the context prior to calling
// this entrypoint. // this entrypoint.
try glfw.makeContextCurrent(window); try glfw.makeContextCurrent(winsys.window);
errdefer glfw.makeContextCurrent(null) catch |err| errdefer glfw.makeContextCurrent(null) catch |err|
log.warn("failed to cleanup OpenGL context err={}", .{err}); log.warn("failed to cleanup OpenGL context err={}", .{err});
try glfw.swapInterval(1); try glfw.swapInterval(1);
@ -541,7 +525,7 @@ fn resetFontMetrics(
/// The primary render callback that is completely thread-safe. /// The primary render callback that is completely thread-safe.
pub fn render( pub fn render(
self: *OpenGL, self: *OpenGL,
window: glfw.Window, winsys: window.System,
state: *renderer.State, state: *renderer.State,
) !void { ) !void {
// Data we extract out of the critical area. // Data we extract out of the critical area.
@ -657,7 +641,7 @@ pub fn render(
} }
// Swap our window buffers // Swap our window buffers
try window.swapBuffers(); try winsys.window.swapBuffers();
} }
/// rebuildCells rebuilds all the GPU cells from our CPU state. This is a /// rebuildCells rebuilds all the GPU cells from our CPU state. This is a

View File

@ -4,9 +4,9 @@ pub const Thread = @This();
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const glfw = @import("glfw");
const libuv = @import("libuv"); const libuv = @import("libuv");
const renderer = @import("../renderer.zig"); const renderer = @import("../renderer.zig");
const window = @import("../window.zig");
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue; const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
const tracy = @import("tracy"); const tracy = @import("tracy");
const trace = tracy.trace; const trace = tracy.trace;
@ -37,8 +37,8 @@ render_h: libuv.Timer,
/// The timer used for cursor blinking /// The timer used for cursor blinking
cursor_h: libuv.Timer, cursor_h: libuv.Timer,
/// The windo we're rendering to. /// The window we're rendering to.
window: glfw.Window, window: window.System,
/// The underlying renderer implementation. /// The underlying renderer implementation.
renderer: *renderer.Renderer, renderer: *renderer.Renderer,
@ -55,7 +55,7 @@ mailbox: *Mailbox,
/// is up to the caller to start the thread with the threadMain entrypoint. /// is up to the caller to start the thread with the threadMain entrypoint.
pub fn init( pub fn init(
alloc: Allocator, alloc: Allocator,
window: glfw.Window, win: window.System,
renderer_impl: *renderer.Renderer, renderer_impl: *renderer.Renderer,
state: *renderer.State, state: *renderer.State,
) !Thread { ) !Thread {
@ -120,7 +120,7 @@ pub fn init(
.stop = stop_h, .stop = stop_h,
.render_h = render_h, .render_h = render_h,
.cursor_h = cursor_timer, .cursor_h = cursor_timer,
.window = window, .window = win,
.renderer = renderer_impl, .renderer = renderer_impl,
.state = state, .state = state,
.mailbox = mailbox, .mailbox = mailbox,

View File

@ -18,6 +18,8 @@ const glfwNative = glfw.Native(.{
.cocoa = builtin.target.isDarwin(), .cocoa = builtin.target.isDarwin(),
}); });
const log = std.log.scoped(.glfw_window);
/// The glfw window handle /// The glfw window handle
window: glfw.Window, window: glfw.Window,
@ -26,9 +28,25 @@ cursor: glfw.Cursor,
pub fn init(app: *const App) !Glfw { pub fn init(app: *const App) !Glfw {
// Create our window // Create our window
const win = try glfw.Window.create(640, 480, "ghostty", null, null, Renderer.windowHints()); const win = try glfw.Window.create(640, 480, "ghostty", null, null, Renderer.glfwWindowHints());
errdefer win.destroy(); errdefer win.destroy();
try Renderer.windowInit(win);
if (builtin.mode == .Debug) {
// Get our physical DPI - debug only because we don't have a use for
// this but the logging of it may be useful
const monitor = win.getMonitor() orelse monitor: {
log.warn("window had null monitor, getting primary monitor", .{});
break :monitor glfw.Monitor.getPrimary().?;
};
const physical_size = monitor.getPhysicalSize();
const video_mode = try monitor.getVideoMode();
const physical_x_dpi = @intToFloat(f32, video_mode.getWidth()) / (@intToFloat(f32, physical_size.width_mm) / 25.4);
const physical_y_dpi = @intToFloat(f32, video_mode.getHeight()) / (@intToFloat(f32, physical_size.height_mm) / 25.4);
log.debug("physical dpi x={} y={}", .{
physical_x_dpi,
physical_y_dpi,
});
}
// On Mac, enable tabbing // On Mac, enable tabbing
if (comptime builtin.target.isDarwin()) { if (comptime builtin.target.isDarwin()) {
@ -53,6 +71,7 @@ pub fn init(app: *const App) !Glfw {
try win.setCursor(cursor); try win.setCursor(cursor);
} }
// Build our result
return Glfw{ return Glfw{
.window = win, .window = win,
.cursor = cursor, .cursor = cursor,
@ -60,8 +79,50 @@ pub fn init(app: *const App) !Glfw {
} }
pub fn deinit(self: *Glfw) void { pub fn deinit(self: *Glfw) void {
var tabgroup_opt: if (builtin.target.isDarwin()) ?objc.Object else void = undefined;
if (comptime builtin.target.isDarwin()) {
const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(self.window).?);
const tabgroup = nswindow.getProperty(objc.Object, "tabGroup");
// On macOS versions prior to Ventura, we lose window focus on tab close
// for some reason. We manually fix this by keeping track of the tab
// group and just selecting the next window.
if (internal_os.macosVersionAtLeast(13, 0, 0))
tabgroup_opt = null
else
tabgroup_opt = 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 => tabgroup_opt = null,
// 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(); self.window.destroy();
self.cursor.destroy(); self.cursor.destroy();
// If we have a tabgroup set, we want to manually focus the next window.
// We should NOT have to do this usually, see the comments above.
if (comptime builtin.target.isDarwin()) {
if (tabgroup_opt) |tabgroup| {
const selected = tabgroup.getProperty(objc.Object, "selectedWindow");
selected.msgSend(void, objc.sel("makeKeyWindow"), .{});
}
}
} }
/// Returns the content scale for the created window. /// Returns the content scale for the created window.