diff --git a/src/App.zig b/src/App.zig index bf89a6847..1afb82e31 100644 --- a/src/App.zig +++ b/src/App.zig @@ -24,9 +24,6 @@ const log = std.log.scoped(.app); const SurfaceList = std.ArrayListUnmanaged(*apprt.Surface); -/// The type used for sending messages to the app thread. -pub const Mailbox = BlockingQueue(Message, 64); - /// General purpose allocator alloc: Allocator, @@ -38,14 +35,11 @@ config: *const Config, /// The mailbox that can be used to send this thread messages. Note /// this is a blocking queue so if it is full you will get errors (or block). -mailbox: *Mailbox, +mailbox: Mailbox.Queue, /// Set to true once we're quitting. This never goes false again. quit: bool, -/// App will call this when tick should be called. -wakeup_cb: ?*const fn () void = null, - /// Initialize the main app instance. This creates the main window, sets /// up the renderer state, compiles the shaders, etc. This is the primary /// "startup" logic. @@ -53,10 +47,6 @@ pub fn create( alloc: Allocator, config: *const Config, ) !*App { - // The mailbox for messaging this thread - var mailbox = try Mailbox.create(alloc); - errdefer mailbox.destroy(alloc); - // If we have DevMode on, store the config so we can show it if (DevMode.enabled) DevMode.instance.config = config; @@ -66,7 +56,7 @@ pub fn create( .alloc = alloc, .surfaces = .{}, .config = config, - .mailbox = mailbox, + .mailbox = .{}, .quit = false, }; errdefer app.surfaces.deinit(alloc); @@ -78,16 +68,10 @@ pub fn destroy(self: *App) void { // Clean up all our surfaces for (self.surfaces.items) |surface| surface.deinit(); self.surfaces.deinit(self.alloc); - self.mailbox.destroy(self.alloc); self.alloc.destroy(self); } -/// Request the app runtime to process app events via tick. -pub fn wakeup(self: App) void { - if (self.wakeup_cb) |cb| cb(); -} - /// Tick ticks the app loop. This will drain our mailbox and process those /// events. This should be called by the application runtime on every loop /// tick. @@ -127,26 +111,13 @@ pub fn deleteSurface(self: *App, rt_surface: *apprt.Surface) void { while (i < self.surfaces.items.len) { if (self.surfaces.items[i] == rt_surface) { _ = self.surfaces.swapRemove(i); + continue; } + + i += 1; } } -/// Close a window and free all resources associated with it. This can -/// only be called from the main thread. -// pub fn closeWindow(self: *App, window: *Window) void { -// var i: usize = 0; -// while (i < self.surfaces.items.len) { -// const current = self.surfaces.items[i]; -// if (window == current) { -// window.destroy(); -// _ = self.surfaces.swapRemove(i); -// return; -// } -// -// i += 1; -// } -// } - /// Drain the mailbox. fn drainMailbox(self: *App, rt_app: *apprt.runtime.App) !void { while (self.mailbox.pop()) |message| { @@ -154,12 +125,18 @@ fn drainMailbox(self: *App, rt_app: *apprt.runtime.App) !void { switch (message) { .new_window => |msg| try self.newWindow(rt_app, msg), .new_tab => |msg| try self.newTab(rt_app, msg), + .close => |surface| try self.closeSurface(rt_app, surface), .quit => try self.setQuit(), .surface_message => |msg| try self.surfaceMessage(msg.surface, msg.message), } } } +fn closeSurface(self: *App, rt_app: *apprt.App, surface: *Surface) !void { + if (!self.hasSurface(surface)) return; + rt_app.closeSurface(surface.rt_surface); +} + /// Create a new window fn newWindow(self: *App, rt_app: *apprt.runtime.App, msg: Message.NewWindow) !void { const window = try rt_app.newWindow(); @@ -231,6 +208,10 @@ pub const Message = union(enum) { /// environment that doesn't support tabs. new_tab: NewTab, + /// Close a surface. This notifies the runtime that a surface + /// should close. + close: *Surface, + /// Quit quit: void, @@ -254,6 +235,25 @@ pub const Message = union(enum) { }; }; +/// Mailbox is the way that other threads send the app thread messages. +pub const Mailbox = struct { + /// The type used for sending messages to the app thread. + pub const Queue = BlockingQueue(Message, 64); + + rt_app: *apprt.App, + mailbox: *Queue, + + /// Send a message to the surface. + pub fn push(self: Mailbox, msg: Message, timeout: Queue.Timeout) Queue.Size { + const result = self.mailbox.push(msg, timeout); + + // Wake up our app loop + self.rt_app.wakeup(); + + return result; + } +}; + // Wasm API. pub const Wasm = if (!builtin.target.isWasm()) struct {} else struct { const wasm = @import("os/wasm.zig"); @@ -329,7 +329,7 @@ pub const CAPI = struct { /// Create a new surface as part of an app. export fn ghostty_surface_new( app: *App, - opts: *const apprt.runtime.Window.Options, + opts: *const apprt.Surface.Options, ) ?*Surface { return surface_new_(app, opts) catch |err| { log.err("error initializing surface err={}", .{err}); @@ -339,7 +339,7 @@ pub const CAPI = struct { fn surface_new_( app: *App, - opts: *const apprt.runtime.Window.Options, + opts: *const apprt.Surface.Options, ) !*Surface { const w = try app.newWindow(.{ .runtime = opts.*, diff --git a/src/Surface.zig b/src/Surface.zig index a09b34609..65740340e 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -42,8 +42,8 @@ const Renderer = renderer.Renderer; /// Allocator alloc: Allocator, -/// The app that this window is a part of. -app: *App, +/// The mailbox for sending messages to the main app thread. +app_mailbox: App.Mailbox, /// The windowing system surface rt_surface: *apprt.runtime.Surface, @@ -128,12 +128,11 @@ const Mouse = struct { /// stable due to interfacing with various callbacks. pub fn init( self: *Surface, - app: *App, + alloc: Allocator, config: *const Config, + app_mailbox: App.Mailbox, rt_surface: *apprt.runtime.Surface, ) !void { - const alloc = app.alloc; - // Initialize our renderer with our initialized surface. try Renderer.surfaceInit(rt_surface); @@ -291,7 +290,7 @@ pub fn init( .explicit = padding, .balance = config.@"window-padding-balance", }, - .surface_mailbox = .{ .surface = self, .app = app.mailbox }, + .surface_mailbox = .{ .surface = self, .app = app_mailbox }, }); errdefer renderer_impl.deinit(); @@ -328,7 +327,7 @@ pub fn init( .renderer_state = &self.renderer_state, .renderer_wakeup = render_thread.wakeup, .renderer_mailbox = render_thread.mailbox, - .surface_mailbox = .{ .surface = self, .app = app.mailbox }, + .surface_mailbox = .{ .surface = self, .app = app_mailbox }, }); errdefer io.deinit(); @@ -343,7 +342,7 @@ pub fn init( self.* = .{ .alloc = alloc, - .app = app, + .app_mailbox = app_mailbox, .rt_surface = rt_surface, .font_lib = font_lib, .font_group = font_group, @@ -902,30 +901,29 @@ pub fn keyCallback( } else log.warn("dev mode was not compiled into this binary", .{}), .new_window => { - _ = self.app.mailbox.push(.{ + _ = self.app_mailbox.push(.{ .new_window = .{ .parent = self, }, }, .{ .instant = {} }); - self.app.wakeup(); }, .new_tab => { - _ = self.app.mailbox.push(.{ + _ = self.app_mailbox.push(.{ .new_tab = .{ .parent = self, }, }, .{ .instant = {} }); - self.app.wakeup(); }, - .close_window => self.rt_surface.setShouldClose(), + .close_window => { + _ = self.app_mailbox.push(.{ .close = self }, .{ .instant = {} }); + }, .quit => { - _ = self.app.mailbox.push(.{ + _ = self.app_mailbox.push(.{ .quit = {}, }, .{ .instant = {} }); - self.app.wakeup(); }, } diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 617850149..c5b85705e 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -46,10 +46,11 @@ pub const App = struct { write_clipboard: *const fn (SurfaceUD, [*:0]const u8) callconv(.C) void, }; + core_app: *CoreApp, opts: Options, - pub fn init(opts: Options) !App { - return .{ .opts = opts }; + pub fn init(core_app: *CoreApp, opts: Options) !App { + return .{ .core_app = core_app, .opts = opts }; } pub fn terminate(self: App) void { @@ -67,7 +68,7 @@ pub const App = struct { pub const Surface = struct { nsview: objc.Object, - core_win: *CoreSurface, + core_surface: *CoreSurface, content_scale: apprt.ContentScale, size: apprt.SurfaceSize, cursor_pos: apprt.CursorPos, @@ -84,11 +85,9 @@ pub const Surface = struct { scale_factor: f64 = 1, }; - pub fn init(app: *const CoreApp, core_win: *CoreSurface, opts: Options) !Surface { - _ = app; - - return .{ - .core_win = core_win, + pub fn init(self: *Surface, app: *App, opts: Options) !void { + self.* = .{ + .core_surface = undefined, .nsview = objc.Object.fromId(opts.nsview), .content_scale = .{ .x = @floatCast(f32, opts.scale_factor), @@ -98,10 +97,23 @@ pub const Surface = struct { .cursor_pos = .{ .x = 0, .y = 0 }, .opts = opts, }; + + // Add ourselves to the list of surfaces on the app. + try app.app.addSurface(self); + errdefer app.app.deleteSurface(self); + + // Initialize our surface right away. We're given a view that is + // ready to use. + try self.core_surface.init(app.app, app.app.config, self); + errdefer self.core_surface.deinit(); } pub fn deinit(self: *Surface) void { - _ = self; + // Remove ourselves from the list of known surfaces in the app. + self.core_surface.app.deleteSurface(self); + + // Clean up our core surface so that all the rendering and IO stop. + self.core_surface.deinit(); } pub fn getContentScale(self: *const Surface) !apprt.ContentScale { @@ -119,19 +131,19 @@ pub const Surface = struct { } pub fn setTitle(self: *Surface, slice: [:0]const u8) !void { - self.core_win.app.runtime.opts.set_title( + self.core_surface.app.runtime.opts.set_title( self.opts.userdata, slice.ptr, ); } pub fn getClipboardString(self: *const Surface) ![:0]const u8 { - const ptr = self.core_win.app.runtime.opts.read_clipboard(self.opts.userdata); + const ptr = self.core_surface.app.runtime.opts.read_clipboard(self.opts.userdata); return std.mem.sliceTo(ptr, 0); } pub fn setClipboardString(self: *const Surface, val: [:0]const u8) !void { - self.core_win.app.runtime.opts.write_clipboard(self.opts.userdata, val.ptr); + self.core_surface.app.runtime.opts.write_clipboard(self.opts.userdata, val.ptr); } pub fn setShouldClose(self: *Surface) void { @@ -148,7 +160,7 @@ pub const Surface = struct { } pub fn refresh(self: *Surface) void { - self.core_win.refreshCallback() catch |err| { + self.core_surface.refreshCallback() catch |err| { log.err("error in refresh callback err={}", .{err}); return; }; @@ -168,7 +180,7 @@ pub const Surface = struct { }; // Call the primary callback. - self.core_win.sizeCallback(self.size) catch |err| { + self.core_surface.sizeCallback(self.size) catch |err| { log.err("error in size callback err={}", .{err}); return; }; @@ -180,14 +192,14 @@ pub const Surface = struct { button: input.MouseButton, mods: input.Mods, ) void { - self.core_win.mouseButtonCallback(action, button, mods) catch |err| { + self.core_surface.mouseButtonCallback(action, button, mods) catch |err| { log.err("error in mouse button callback err={}", .{err}); return; }; } pub fn scrollCallback(self: *const Surface, xoff: f64, yoff: f64) void { - self.core_win.scrollCallback(xoff, yoff) catch |err| { + self.core_surface.scrollCallback(xoff, yoff) catch |err| { log.err("error in scroll callback err={}", .{err}); return; }; @@ -195,7 +207,7 @@ pub const Surface = struct { pub fn cursorPosCallback(self: *Surface, x: f64, y: f64) void { // Convert our unscaled x/y to scaled. - self.cursor_pos = self.core_win.window.cursorPosToPixels(.{ + self.cursor_pos = self.core_surface.window.cursorPosToPixels(.{ .x = @floatCast(f32, x), .y = @floatCast(f32, y), }) catch |err| { @@ -206,7 +218,7 @@ pub const Surface = struct { return; }; - self.core_win.cursorPosCallback(self.cursor_pos) catch |err| { + self.core_surface.cursorPosCallback(self.cursor_pos) catch |err| { log.err("error in cursor pos callback err={}", .{err}); return; }; @@ -219,7 +231,7 @@ pub const Surface = struct { mods: input.Mods, ) void { // log.warn("key action={} key={} mods={}", .{ action, key, mods }); - self.core_win.keyCallback(action, key, mods) catch |err| { + self.core_surface.keyCallback(action, key, mods) catch |err| { log.err("error in key callback err={}", .{err}); return; }; @@ -227,14 +239,14 @@ pub const Surface = struct { pub fn charCallback(self: *const Surface, cp_: u32) void { const cp = std.math.cast(u21, cp_) orelse return; - self.core_win.charCallback(cp) catch |err| { + self.core_surface.charCallback(cp) catch |err| { log.err("error in char callback err={}", .{err}); return; }; } pub fn focusCallback(self: *const Surface, focused: bool) void { - self.core_win.focusCallback(focused) catch |err| { + self.core_surface.focusCallback(focused) catch |err| { log.err("error in focus callback err={}", .{err}); return; }; diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 8fa6a6b0d..59c79dfa5 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -43,9 +43,6 @@ pub const App = struct { var darwin = if (Darwin.enabled) try Darwin.init() else {}; errdefer if (Darwin.enabled) darwin.deinit(); - // Set our callback for being woken up - core_app.wakeup_cb = wakeup; - return .{ .app = core_app, .darwin = darwin, @@ -71,7 +68,8 @@ pub const App = struct { } /// Wakeup the event loop. This should be able to be called from any thread. - pub fn wakeup() void { + pub fn wakeup(self: *const App) void { + _ = self; glfw.postEmptyEvent(); } @@ -114,11 +112,11 @@ pub const App = struct { // point in the grid. const size = parent.rt_surface.getSize() catch |err| { log.err("error querying window size for size callback on new tab err={}", .{err}); - return; + return window; }; parent.sizeCallback(size) catch |err| { log.err("error in size callback from new tab err={}", .{err}); - return; + return window; }; return window; @@ -193,6 +191,9 @@ pub const Surface = struct { /// The glfw mouse cursor handle. cursor: glfw.Cursor, + /// The app we're part of + app: *App, + /// A core surface core_surface: CoreSurface, @@ -265,6 +266,7 @@ pub const Surface = struct { // Build our result self.* = .{ + .app = app, .window = win, .cursor = cursor, .core_surface = undefined, @@ -276,13 +278,18 @@ pub const Surface = struct { errdefer app.app.deleteSurface(self); // Initialize our surface now that we have the stable pointer. - try self.core_surface.init(app.app, app.app.config, self); + try self.core_surface.init( + app.app.alloc, + app.app.config, + .{ .rt_app = app, .mailbox = &app.app.mailbox }, + self, + ); errdefer self.core_surface.deinit(); } pub fn deinit(self: *Surface) void { // Remove ourselves from the list of known surfaces in the app. - self.core_surface.app.deleteSurface(self); + self.app.app.deleteSurface(self); // Clean up our core surface so that all the rendering and IO stop. self.core_surface.deinit(); diff --git a/src/apprt/surface.zig b/src/apprt/surface.zig index f76a4ea06..0e6439a41 100644 --- a/src/apprt/surface.zig +++ b/src/apprt/surface.zig @@ -28,23 +28,22 @@ pub const Message = union(enum) { /// A surface mailbox. pub const Mailbox = struct { surface: *Surface, - app: *App.Mailbox, + app: App.Mailbox, /// Send a message to the surface. - pub fn push(self: Mailbox, msg: Message, timeout: App.Mailbox.Timeout) App.Mailbox.Size { + pub fn push( + self: Mailbox, + msg: Message, + timeout: App.Mailbox.Queue.Timeout, + ) App.Mailbox.Queue.Size { // Surface message sending is actually implemented on the app // thread, so we have to rewrap the message with our surface // pointer and send it to the app thread. - const result = self.app.push(.{ + return self.app.push(.{ .surface_message = .{ .surface = self.surface, .message = msg, }, }, timeout); - - // Wake up our app loop - self.surface.app.wakeup(); - - return result; } };