rename window package to apprt

This commit is contained in:
Mitchell Hashimoto
2022-12-29 15:11:03 -08:00
parent e1cd650245
commit 11a3577ef1
11 changed files with 196 additions and 189 deletions

View File

@ -7,9 +7,9 @@ const Window = @This();
// TODO: eventually, I want to extract Window.zig into the "window" package
// so we can also have alternate implementations (i.e. not glfw).
const message = @import("window/message.zig");
pub const Mailbox = message.Mailbox;
pub const Message = message.Message;
const apprt = @import("apprt.zig");
pub const Mailbox = apprt.Window.Mailbox;
pub const Message = apprt.Window.Message;
const std = @import("std");
const builtin = @import("builtin");
@ -30,7 +30,6 @@ 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(.{
@ -49,7 +48,7 @@ alloc: Allocator,
app: *App,
/// The windowing system state
windowing_system: WindowingSystem,
windowing_system: apprt.runtime.Window,
/// The font structures
font_lib: font.Library,
@ -140,7 +139,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
errdefer alloc.destroy(self);
// Create the windowing system
var winsys = try WindowingSystem.init(app);
var winsys = try apprt.runtime.Window.init(app);
errdefer winsys.deinit();
// Initialize our renderer with our initialized windowing system.

27
src/apprt.zig Normal file
View File

@ -0,0 +1,27 @@
//! "apprt" is the "application runtime" package. This abstracts the
//! application runtime and lifecycle management such as creating windows,
//! 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.
//!
//! 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
//! code when absolutely necessary.
const builtin = @import("builtin");
pub usingnamespace @import("apprt/structs.zig");
pub const glfw = @import("apprt/glfw.zig");
pub const Window = @import("apprt/Window.zig");
/// The implementation to use for the app runtime. This is comptime chosen
/// so that every build has exactly one application runtime implementation.
/// Note: it is very rare to use Runtime directly; most usage will use
/// Window or something.
pub const runtime = switch (builtin.os.tag) {
else => glfw,
};
test {
@import("std").testing.refAllDecls(@This());
}

149
src/apprt/glfw.zig Normal file
View File

@ -0,0 +1,149 @@
//! 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 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 apprt = @import("../apprt.zig");
// Get native API access on certain platforms so we can do more customization.
const glfwNative = glfw.Native(.{
.cocoa = builtin.target.isDarwin(),
});
const log = std.log.scoped(.glfw);
pub const Window = struct {
/// The glfw window handle
window: glfw.Window,
/// The glfw mouse cursor handle.
cursor: glfw.Cursor,
pub fn init(app: *const App) !Window {
// Create our window
const win = try glfw.Window.create(
640,
480,
"ghostty",
null,
null,
Renderer.glfwWindowHints(),
);
errdefer win.destroy();
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()) {
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);
}
// Build our result
return Window{
.window = win,
.cursor = cursor,
};
}
pub fn deinit(self: *Window) 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.
pub fn getContentScale(self: *const Window) !apprt.ContentScale {
const scale = try self.window.getContentScale();
return apprt.ContentScale{ .x = scale.x_scale, .y = scale.y_scale };
}
/// Returns the size of the window in screen coordinates.
pub fn getSize(self: *const Window) !apprt.WindowSize {
const size = try self.window.getSize();
return apprt.WindowSize{ .width = size.width, .height = size.height };
}
};

View File

@ -7,7 +7,7 @@ pub const ContentScale = struct {
};
/// The size of the window in screen coordinates.
pub const Size = struct {
pub const WindowSize = struct {
width: u32,
height: u32,
};

View File

@ -7,6 +7,7 @@ const glfw = @import("glfw");
const assert = std.debug.assert;
const testing = std.testing;
const Allocator = std.mem.Allocator;
const apprt = @import("../apprt.zig");
const font = @import("../font/main.zig");
const imgui = @import("imgui");
const renderer = @import("../renderer.zig");
@ -18,7 +19,6 @@ 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);
@ -364,10 +364,10 @@ pub fn glfwWindowHints() glfw.Window.Hints {
/// This is called early right after window creation to setup our
/// window surface as necessary.
pub fn windowInit(winsys: window.System) !void {
pub fn windowInit(win: apprt.runtime.Window) !void {
// Treat this like a thread entry
const self: OpenGL = undefined;
try self.threadEnter(winsys);
try self.threadEnter(win);
// Blending for text
try gl.enable(gl.c.GL_BLEND);
@ -385,19 +385,19 @@ pub fn windowInit(winsys: window.System) !void {
/// This is called just prior to spinning up the renderer thread for
/// final main thread setup requirements.
pub fn finalizeWindowInit(self: *const OpenGL, winsys: window.System) !void {
pub fn finalizeWindowInit(self: *const OpenGL, win: apprt.runtime.Window) !void {
_ = self;
_ = winsys;
_ = win;
}
/// This is called if this renderer runs DevMode.
pub fn initDevMode(self: *const OpenGL, winsys: window.System) !void {
pub fn initDevMode(self: *const OpenGL, win: apprt.runtime.Window) !void {
_ = self;
if (DevMode.enabled) {
// Initialize for our window
assert(imgui.ImplGlfw.initForOpenGL(
@ptrCast(*imgui.ImplGlfw.GLFWWindow, winsys.window.handle),
@ptrCast(*imgui.ImplGlfw.GLFWWindow, win.window.handle),
true,
));
assert(imgui.ImplOpenGL3.init("#version 330 core"));
@ -415,7 +415,7 @@ pub fn deinitDevMode(self: *const OpenGL) void {
}
/// Callback called by renderer.Thread when it begins.
pub fn threadEnter(self: *const OpenGL, winsys: window.System) !void {
pub fn threadEnter(self: *const OpenGL, win: apprt.runtime.Window) !void {
_ = self;
// We need to make the OpenGL context current. OpenGL requires
@ -423,7 +423,7 @@ pub fn threadEnter(self: *const OpenGL, winsys: window.System) !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(winsys.window);
try glfw.makeContextCurrent(win.window);
errdefer glfw.makeContextCurrent(null) catch |err|
log.warn("failed to cleanup OpenGL context err={}", .{err});
try glfw.swapInterval(1);
@ -525,7 +525,7 @@ fn resetFontMetrics(
/// The primary render callback that is completely thread-safe.
pub fn render(
self: *OpenGL,
winsys: window.System,
win: apprt.runtime.Window,
state: *renderer.State,
) !void {
// Data we extract out of the critical area.
@ -641,7 +641,7 @@ pub fn render(
}
// Swap our window buffers
try winsys.window.swapBuffers();
try win.window.swapBuffers();
}
/// rebuildCells rebuilds all the GPU cells from our CPU state. This is a

View File

@ -6,7 +6,7 @@ const std = @import("std");
const builtin = @import("builtin");
const libuv = @import("libuv");
const renderer = @import("../renderer.zig");
const window = @import("../window.zig");
const apprt = @import("../apprt.zig");
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
const tracy = @import("tracy");
const trace = tracy.trace;
@ -38,7 +38,7 @@ render_h: libuv.Timer,
cursor_h: libuv.Timer,
/// The window we're rendering to.
window: window.System,
window: apprt.runtime.Window,
/// 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,
win: window.System,
win: apprt.runtime.Window,
renderer_impl: *renderer.Renderer,
state: *renderer.State,
) !Thread {

View File

@ -1,18 +0,0 @@
//! 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());
}

View File

@ -1,138 +0,0 @@
//! 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(),
});
const log = std.log.scoped(.glfw_window);
/// 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.glfwWindowHints());
errdefer win.destroy();
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()) {
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);
}
// Build our result
return Glfw{
.window = win,
.cursor = cursor,
};
}
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.
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 };
}

View File

@ -1,2 +0,0 @@
//! Window implementation for the web (browser) via WebAssembly.
pub const Window = @This();

View File

@ -1,10 +0,0 @@
//! 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();