mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-23 20:26:09 +03:00
rename window package to apprt
This commit is contained in:
@ -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
27
src/apprt.zig
Normal 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
149
src/apprt/glfw.zig
Normal 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 };
|
||||
}
|
||||
};
|
@ -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,
|
||||
};
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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());
|
||||
}
|
@ -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 };
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
//! Window implementation for the web (browser) via WebAssembly.
|
||||
pub const Window = @This();
|
@ -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();
|
Reference in New Issue
Block a user