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
var winsys = try WindowingSystem.init(app);
winsys.deinit();
errdefer winsys.deinit();
// Create our window
const window = try glfw.Window.create(640, 480, "ghostty", null, null, Renderer.windowHints());
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);
}
// Initialize our renderer with our initialized windowing system.
try Renderer.windowInit(winsys);
// 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();
const x_dpi = content_scale.x_scale * font.face.default_dpi;
const y_dpi = content_scale.y_scale * font.face.default_dpi;
const content_scale = try winsys.getContentScale();
const x_dpi = content_scale.x * font.face.default_dpi;
const y_dpi = content_scale.y * font.face.default_dpi;
log.debug("xscale={} yscale={} xdpi={} ydpi={}", .{
content_scale.x_scale,
content_scale.y_scale,
content_scale.x,
content_scale.y,
x_dpi,
y_dpi,
});
@ -330,7 +305,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
errdefer renderer_impl.deinit();
// 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 = .{
.width = window_size.width,
.height = window_size.height,
@ -348,7 +323,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
// Create the renderer thread
var render_thread = try renderer.Thread.init(
alloc,
window,
winsys,
&self.renderer,
&self.renderer_state,
);
@ -382,8 +357,8 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
.font_lib = font_lib,
.font_group = font_group,
.font_size = font_size,
.window = window,
.cursor = cursor,
.window = winsys.window,
.cursor = winsys.cursor,
.renderer = renderer_impl,
.renderer_thread = render_thread,
.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
// 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 });
// TODO:
// 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);
window.setCharCallback(charCallback);
window.setKeyCallback(keyCallback);
window.setFocusCallback(focusCallback);
window.setRefreshCallback(refreshCallback);
window.setScrollCallback(scrollCallback);
window.setCursorPosCallback(cursorPosCallback);
window.setMouseButtonCallback(mouseButtonCallback);
winsys.window.setUserPointer(self);
winsys.window.setSizeCallback(sizeCallback);
winsys.window.setCharCallback(charCallback);
winsys.window.setKeyCallback(keyCallback);
winsys.window.setFocusCallback(focusCallback);
winsys.window.setRefreshCallback(refreshCallback);
winsys.window.setScrollCallback(scrollCallback);
winsys.window.setCursorPosCallback(cursorPosCallback);
winsys.window.setMouseButtonCallback(mouseButtonCallback);
// Call our size callback which handles all our retina setup
// 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
// to duplicate.
sizeCallback(
window,
winsys.window,
@intCast(i32, window_size.width),
@intCast(i32, window_size.height),
);
@ -461,12 +437,12 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
DevMode.instance.window = self;
// Let our renderer setup
try renderer_impl.initDevMode(window);
try renderer_impl.initDevMode(winsys);
}
// Give the renderer one more opportunity to finalize any window
// 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
self.renderer_thr = try std.Thread.spawn(
@ -495,7 +471,7 @@ pub fn destroy(self: *Window) void {
self.renderer_thr.join();
// 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();
// If we are devmode-owning, clean that up.
@ -525,51 +501,7 @@ pub fn destroy(self: *Window) void {
self.io.deinit();
}
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 => {},
}
}
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.windowing_system.deinit();
self.font_group.deinit(self.alloc);
self.font_lib.deinit();

View File

@ -18,6 +18,7 @@ const math = @import("../math.zig");
const lru = @import("../lru.zig");
const DevMode = @import("../DevMode.zig");
const Window = @import("../Window.zig");
const window = @import("../window.zig");
const log = std.log.scoped(.grid);
@ -350,7 +351,7 @@ fn resetCellsLRU(self: *OpenGL) void {
}
/// Returns the hints that we want for this
pub fn windowHints() glfw.Window.Hints {
pub fn glfwWindowHints() glfw.Window.Hints {
return .{
.context_version_major = 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
/// window surface as necessary.
pub fn windowInit(window: glfw.Window) !void {
pub fn windowInit(winsys: window.System) !void {
// Treat this like a thread entry
const self: OpenGL = undefined;
try self.threadEnter(window);
try self.threadEnter(winsys);
// Blending for text
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});
// }
// }
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
/// 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;
_ = window;
_ = winsys;
}
/// 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;
if (DevMode.enabled) {
// Initialize for our window
assert(imgui.ImplGlfw.initForOpenGL(
@ptrCast(*imgui.ImplGlfw.GLFWWindow, window.handle),
@ptrCast(*imgui.ImplGlfw.GLFWWindow, winsys.window.handle),
true,
));
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.
pub fn threadEnter(self: *const OpenGL, window: glfw.Window) !void {
pub fn threadEnter(self: *const OpenGL, winsys: window.System) !void {
_ = self;
// 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:
// the prior thread MUST have detached the context prior to calling
// this entrypoint.
try glfw.makeContextCurrent(window);
try glfw.makeContextCurrent(winsys.window);
errdefer glfw.makeContextCurrent(null) catch |err|
log.warn("failed to cleanup OpenGL context err={}", .{err});
try glfw.swapInterval(1);
@ -541,7 +525,7 @@ fn resetFontMetrics(
/// The primary render callback that is completely thread-safe.
pub fn render(
self: *OpenGL,
window: glfw.Window,
winsys: window.System,
state: *renderer.State,
) !void {
// Data we extract out of the critical area.
@ -657,7 +641,7 @@ pub fn render(
}
// Swap our window buffers
try window.swapBuffers();
try winsys.window.swapBuffers();
}
/// 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 builtin = @import("builtin");
const glfw = @import("glfw");
const libuv = @import("libuv");
const renderer = @import("../renderer.zig");
const window = @import("../window.zig");
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
const tracy = @import("tracy");
const trace = tracy.trace;
@ -37,8 +37,8 @@ render_h: libuv.Timer,
/// The timer used for cursor blinking
cursor_h: libuv.Timer,
/// The windo we're rendering to.
window: glfw.Window,
/// The window we're rendering to.
window: window.System,
/// The underlying renderer implementation.
renderer: *renderer.Renderer,
@ -55,7 +55,7 @@ mailbox: *Mailbox,
/// is up to the caller to start the thread with the threadMain entrypoint.
pub fn init(
alloc: Allocator,
window: glfw.Window,
win: window.System,
renderer_impl: *renderer.Renderer,
state: *renderer.State,
) !Thread {
@ -120,7 +120,7 @@ pub fn init(
.stop = stop_h,
.render_h = render_h,
.cursor_h = cursor_timer,
.window = window,
.window = win,
.renderer = renderer_impl,
.state = state,
.mailbox = mailbox,

View File

@ -18,6 +18,8 @@ const glfwNative = glfw.Native(.{
.cocoa = builtin.target.isDarwin(),
});
const log = std.log.scoped(.glfw_window);
/// The glfw window handle
window: glfw.Window,
@ -26,9 +28,25 @@ 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());
const win = try glfw.Window.create(640, 480, "ghostty", null, null, Renderer.glfwWindowHints());
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
if (comptime builtin.target.isDarwin()) {
@ -53,6 +71,7 @@ pub fn init(app: *const App) !Glfw {
try win.setCursor(cursor);
}
// Build our result
return Glfw{
.window = win,
.cursor = cursor,
@ -60,8 +79,50 @@ pub fn init(app: *const App) !Glfw {
}
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.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.