diff --git a/src/App.zig b/src/App.zig index 1afb82e31..7986ed8d5 100644 --- a/src/App.zig +++ b/src/App.zig @@ -77,7 +77,7 @@ pub fn destroy(self: *App) void { /// tick. /// /// This returns whether the app should quit or not. -pub fn tick(self: *App, rt_app: *apprt.runtime.App) !bool { +pub fn tick(self: *App, rt_app: *apprt.App) !bool { // If any surfaces are closing, destroy them var i: usize = 0; while (i < self.surfaces.items.len) { @@ -119,7 +119,7 @@ pub fn deleteSurface(self: *App, rt_surface: *apprt.Surface) void { } /// Drain the mailbox. -fn drainMailbox(self: *App, rt_app: *apprt.runtime.App) !void { +fn drainMailbox(self: *App, rt_app: *apprt.App) !void { while (self.mailbox.pop()) |message| { log.debug("mailbox message={s}", .{@tagName(message)}); switch (message) { @@ -138,7 +138,12 @@ fn closeSurface(self: *App, rt_app: *apprt.App, surface: *Surface) !void { } /// Create a new window -fn newWindow(self: *App, rt_app: *apprt.runtime.App, msg: Message.NewWindow) !void { +fn newWindow(self: *App, rt_app: *apprt.App, msg: Message.NewWindow) !void { + if (!@hasDecl(apprt.App, "newWindow")) { + log.warn("newWindow is not supported by this runtime", .{}); + return; + } + const window = try rt_app.newWindow(); if (self.config.@"window-inherit-font-size") { if (msg.parent) |parent| { @@ -150,7 +155,12 @@ fn newWindow(self: *App, rt_app: *apprt.runtime.App, msg: Message.NewWindow) !vo } /// Create a new tab in the parent window -fn newTab(self: *App, rt_app: *apprt.runtime.App, msg: Message.NewTab) !void { +fn newTab(self: *App, rt_app: *apprt.App, msg: Message.NewTab) !void { + if (!@hasDecl(apprt.App, "newTab")) { + log.warn("newTab is not supported by this runtime", .{}); + return; + } + const parent = msg.parent orelse { log.warn("parent must be set in new_tab message", .{}); return; @@ -281,147 +291,3 @@ pub const Wasm = if (!builtin.target.isWasm()) struct {} else struct { // } // } }; - -// C API -pub const CAPI = struct { - const global = &@import("main.zig").state; - - /// Create a new app. - export fn ghostty_app_new( - opts: *const apprt.runtime.App.Options, - config: *const Config, - ) ?*App { - return app_new_(opts, config) catch |err| { - log.err("error initializing app err={}", .{err}); - return null; - }; - } - - fn app_new_( - opts: *const apprt.runtime.App.Options, - config: *const Config, - ) !*App { - const app = try App.create(global.alloc, opts.*, config); - errdefer app.destroy(); - return app; - } - - /// Tick the event loop. This should be called whenever the "wakeup" - /// callback is invoked for the runtime. - export fn ghostty_app_tick(v: *App) void { - v.tick() catch |err| { - log.err("error app tick err={}", .{err}); - }; - } - - /// Return the userdata associated with the app. - export fn ghostty_app_userdata(v: *App) ?*anyopaque { - return v.runtime.opts.userdata; - } - - export fn ghostty_app_free(ptr: ?*App) void { - if (ptr) |v| { - v.destroy(); - v.alloc.destroy(v); - } - } - - /// Create a new surface as part of an app. - export fn ghostty_surface_new( - app: *App, - opts: *const apprt.Surface.Options, - ) ?*Surface { - return surface_new_(app, opts) catch |err| { - log.err("error initializing surface err={}", .{err}); - return null; - }; - } - - fn surface_new_( - app: *App, - opts: *const apprt.Surface.Options, - ) !*Surface { - const w = try app.newWindow(.{ - .runtime = opts.*, - }); - return w; - } - - export fn ghostty_surface_free(ptr: ?*Surface) void { - if (ptr) |v| v.app.closeWindow(v); - } - - /// Returns the app associated with a surface. - export fn ghostty_surface_app(win: *Surface) *App { - return win.app; - } - - /// Tell the surface that it needs to schedule a render - export fn ghostty_surface_refresh(win: *Surface) void { - win.window.refresh(); - } - - /// Update the size of a surface. This will trigger resize notifications - /// to the pty and the renderer. - export fn ghostty_surface_set_size(win: *Surface, w: u32, h: u32) void { - win.window.updateSize(w, h); - } - - /// Update the content scale of the surface. - export fn ghostty_surface_set_content_scale(win: *Surface, x: f64, y: f64) void { - win.window.updateContentScale(x, y); - } - - /// Update the focused state of a surface. - export fn ghostty_surface_set_focus(win: *Surface, focused: bool) void { - win.window.focusCallback(focused); - } - - /// Tell the surface that it needs to schedule a render - export fn ghostty_surface_key( - win: *Surface, - action: input.Action, - key: input.Key, - mods: c_int, - ) void { - win.window.keyCallback( - action, - key, - @bitCast(input.Mods, @truncate(u8, @bitCast(c_uint, mods))), - ); - } - - /// Tell the surface that it needs to schedule a render - export fn ghostty_surface_char(win: *Surface, codepoint: u32) void { - win.window.charCallback(codepoint); - } - - /// Tell the surface that it needs to schedule a render - export fn ghostty_surface_mouse_button( - win: *Surface, - action: input.MouseButtonState, - button: input.MouseButton, - mods: c_int, - ) void { - win.window.mouseButtonCallback( - action, - button, - @bitCast(input.Mods, @truncate(u8, @bitCast(c_uint, mods))), - ); - } - - /// Update the mouse position within the view. - export fn ghostty_surface_mouse_pos(win: *Surface, x: f64, y: f64) void { - win.window.cursorPosCallback(x, y); - } - - export fn ghostty_surface_mouse_scroll(win: *Surface, x: f64, y: f64) void { - win.window.scrollCallback(x, y); - } - - export fn ghostty_surface_ime_point(win: *Surface, x: *f64, y: *f64) void { - const pos = win.imePoint(); - x.* = pos.x; - y.* = pos.y; - } -}; diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index c5b85705e..8c1180a87 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -57,18 +57,38 @@ pub const App = struct { _ = self; } - pub fn wakeup(self: App) !void { + pub fn wakeup(self: App) void { self.opts.wakeup(self.opts.userdata); } pub fn wait(self: App) !void { _ = self; } + + /// Create a new surface for the app. + fn newSurface(self: *App, opts: Surface.Options) !*Surface { + // Grab a surface allocation because we're going to need it. + var surface = try self.core_app.alloc.create(Surface); + errdefer self.core_app.alloc.destroy(surface); + + // Create the surface -- because windows are surfaces for glfw. + try surface.init(self, opts); + errdefer surface.deinit(); + + return surface; + } + + /// Close the given surface. + pub fn closeSurface(self: *App, surface: *Surface) void { + surface.deinit(); + self.core_app.alloc.destroy(surface); + } }; pub const Surface = struct { + app: *App, nsview: objc.Object, - core_surface: *CoreSurface, + core_surface: CoreSurface, content_scale: apprt.ContentScale, size: apprt.SurfaceSize, cursor_pos: apprt.CursorPos, @@ -87,6 +107,7 @@ pub const Surface = struct { pub fn init(self: *Surface, app: *App, opts: Options) !void { self.* = .{ + .app = app, .core_surface = undefined, .nsview = objc.Object.fromId(opts.nsview), .content_scale = .{ @@ -99,18 +120,23 @@ pub const Surface = struct { }; // Add ourselves to the list of surfaces on the app. - try app.app.addSurface(self); - errdefer app.app.deleteSurface(self); + try app.core_app.addSurface(self); + errdefer app.core_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); + try self.core_surface.init( + app.core_app.alloc, + app.core_app.config, + .{ .rt_app = app, .mailbox = &app.core_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.core_app.deleteSurface(self); // Clean up our core surface so that all the rendering and IO stop. self.core_surface.deinit(); @@ -131,19 +157,19 @@ pub const Surface = struct { } pub fn setTitle(self: *Surface, slice: [:0]const u8) !void { - self.core_surface.app.runtime.opts.set_title( + self.app.opts.set_title( self.opts.userdata, slice.ptr, ); } pub fn getClipboardString(self: *const Surface) ![:0]const u8 { - const ptr = self.core_surface.app.runtime.opts.read_clipboard(self.opts.userdata); + const ptr = self.app.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_surface.app.runtime.opts.write_clipboard(self.opts.userdata, val.ptr); + self.app.opts.write_clipboard(self.opts.userdata, val.ptr); } pub fn setShouldClose(self: *Surface) void { @@ -187,7 +213,7 @@ pub const Surface = struct { } pub fn mouseButtonCallback( - self: *const Surface, + self: *Surface, action: input.MouseButtonState, button: input.MouseButton, mods: input.Mods, @@ -198,7 +224,7 @@ pub const Surface = struct { }; } - pub fn scrollCallback(self: *const Surface, xoff: f64, yoff: f64) void { + pub fn scrollCallback(self: *Surface, xoff: f64, yoff: f64) void { self.core_surface.scrollCallback(xoff, yoff) catch |err| { log.err("error in scroll callback err={}", .{err}); return; @@ -207,7 +233,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_surface.window.cursorPosToPixels(.{ + self.cursor_pos = self.cursorPosToPixels(.{ .x = @floatCast(f32, x), .y = @floatCast(f32, y), }) catch |err| { @@ -225,7 +251,7 @@ pub const Surface = struct { } pub fn keyCallback( - self: *const Surface, + self: *Surface, action: input.Action, key: input.Key, mods: input.Mods, @@ -237,7 +263,7 @@ pub const Surface = struct { }; } - pub fn charCallback(self: *const Surface, cp_: u32) void { + pub fn charCallback(self: *Surface, cp_: u32) void { const cp = std.math.cast(u21, cp_) orelse return; self.core_surface.charCallback(cp) catch |err| { log.err("error in char callback err={}", .{err}); @@ -245,7 +271,7 @@ pub const Surface = struct { }; } - pub fn focusCallback(self: *const Surface, focused: bool) void { + pub fn focusCallback(self: *Surface, focused: bool) void { self.core_surface.focusCallback(focused) catch |err| { log.err("error in focus callback err={}", .{err}); return; @@ -259,3 +285,152 @@ pub const Surface = struct { return .{ .x = pos.x * scale.x, .y = pos.y * scale.y }; } }; + +// C API +pub const CAPI = struct { + const global = &@import("../main.zig").state; + const Config = @import("../config.zig").Config; + + /// Create a new app. + export fn ghostty_app_new( + opts: *const apprt.runtime.App.Options, + config: *const Config, + ) ?*App { + return app_new_(opts, config) catch |err| { + log.err("error initializing app err={}", .{err}); + return null; + }; + } + + fn app_new_( + opts: *const apprt.runtime.App.Options, + config: *const Config, + ) !*App { + var core_app = try CoreApp.create(global.alloc, config); + errdefer core_app.destroy(); + + // Create our runtime app + var app = try global.alloc.create(App); + errdefer global.alloc.destroy(app); + app.* = try App.init(core_app, opts.*); + errdefer app.terminate(); + + return app; + } + + /// Tick the event loop. This should be called whenever the "wakeup" + /// callback is invoked for the runtime. + export fn ghostty_app_tick(v: *App) void { + _ = v.core_app.tick(v) catch |err| { + log.err("error app tick err={}", .{err}); + }; + } + + /// Return the userdata associated with the app. + export fn ghostty_app_userdata(v: *App) ?*anyopaque { + return v.opts.userdata; + } + + export fn ghostty_app_free(v: *App) void { + const core_app = v.core_app; + v.terminate(); + global.alloc.destroy(v); + core_app.destroy(); + } + + /// Create a new surface as part of an app. + export fn ghostty_surface_new( + app: *App, + opts: *const apprt.Surface.Options, + ) ?*Surface { + return surface_new_(app, opts) catch |err| { + log.err("error initializing surface err={}", .{err}); + return null; + }; + } + + fn surface_new_( + app: *App, + opts: *const apprt.Surface.Options, + ) !*Surface { + return try app.newSurface(opts.*); + } + + export fn ghostty_surface_free(ptr: *Surface) void { + ptr.app.closeSurface(ptr); + } + + /// Returns the app associated with a surface. + export fn ghostty_surface_app(surface: *Surface) *App { + return surface.app; + } + + /// Tell the surface that it needs to schedule a render + export fn ghostty_surface_refresh(surface: *Surface) void { + surface.refresh(); + } + + /// Update the size of a surface. This will trigger resize notifications + /// to the pty and the renderer. + export fn ghostty_surface_set_size(surface: *Surface, w: u32, h: u32) void { + surface.updateSize(w, h); + } + + /// Update the content scale of the surface. + export fn ghostty_surface_set_content_scale(surface: *Surface, x: f64, y: f64) void { + surface.updateContentScale(x, y); + } + + /// Update the focused state of a surface. + export fn ghostty_surface_set_focus(surface: *Surface, focused: bool) void { + surface.focusCallback(focused); + } + + /// Tell the surface that it needs to schedule a render + export fn ghostty_surface_key( + surface: *Surface, + action: input.Action, + key: input.Key, + mods: c_int, + ) void { + surface.keyCallback( + action, + key, + @bitCast(input.Mods, @truncate(u8, @bitCast(c_uint, mods))), + ); + } + + /// Tell the surface that it needs to schedule a render + export fn ghostty_surface_char(surface: *Surface, codepoint: u32) void { + surface.charCallback(codepoint); + } + + /// Tell the surface that it needs to schedule a render + export fn ghostty_surface_mouse_button( + surface: *Surface, + action: input.MouseButtonState, + button: input.MouseButton, + mods: c_int, + ) void { + surface.mouseButtonCallback( + action, + button, + @bitCast(input.Mods, @truncate(u8, @bitCast(c_uint, mods))), + ); + } + + /// Update the mouse position within the view. + export fn ghostty_surface_mouse_pos(surface: *Surface, x: f64, y: f64) void { + surface.cursorPosCallback(x, y); + } + + export fn ghostty_surface_mouse_scroll(surface: *Surface, x: f64, y: f64) void { + surface.scrollCallback(x, y); + } + + export fn ghostty_surface_ime_point(surface: *Surface, x: *f64, y: *f64) void { + const pos = surface.core_surface.imePoint(); + x.* = pos.x; + y.* = pos.y; + } +}; diff --git a/src/main_c.zig b/src/main_c.zig index 344d477bb..05cf43897 100644 --- a/src/main_c.zig +++ b/src/main_c.zig @@ -10,10 +10,10 @@ const std = @import("std"); const assert = std.debug.assert; const builtin = @import("builtin"); const main = @import("main.zig"); +const apprt = @import("apprt.zig"); // Some comptime assertions that our C API depends on. comptime { - const apprt = @import("apprt.zig"); assert(apprt.runtime == apprt.embedded); } @@ -21,7 +21,7 @@ comptime { pub const std_options = main.std_options; pub usingnamespace @import("config.zig").CAPI; -pub usingnamespace @import("App.zig").CAPI; +pub usingnamespace apprt.runtime.CAPI; /// Initialize ghostty global state. It is possible to have more than /// one global state but it has zero practical benefit.