diff --git a/include/ghostty.h b/include/ghostty.h index a256b7364..8ed455aea 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -44,6 +44,7 @@ void ghostty_config_finalize(ghostty_config_t); ghostty_app_t ghostty_app_new(ghostty_runtime_config_s *, ghostty_config_t); void ghostty_app_free(ghostty_app_t); +int ghostty_app_tick(ghostty_app_t); #ifdef __cplusplus } diff --git a/src/App.zig b/src/App.zig index 731a6ee6b..462847d14 100644 --- a/src/App.zig +++ b/src/App.zig @@ -147,25 +147,31 @@ pub fn wakeup(self: App) void { /// application quits or every window is closed. pub fn run(self: *App) !void { while (!self.quit and self.windows.items.len > 0) { - // Block for any events. - try self.runtime.wait(); + try self.tick(); + } +} - // If any windows are closing, destroy them - var i: usize = 0; - while (i < self.windows.items.len) { - const window = self.windows.items[i]; - if (window.shouldClose()) { - window.destroy(); - _ = self.windows.swapRemove(i); - continue; - } +/// Tick ticks the app loop. This will drain our mailbox and process those +/// events. +pub fn tick(self: *App) !void { + // Block for any events. + try self.runtime.wait(); - i += 1; + // If any windows are closing, destroy them + var i: usize = 0; + while (i < self.windows.items.len) { + const window = self.windows.items[i]; + if (window.shouldClose()) { + window.destroy(); + _ = self.windows.swapRemove(i); + continue; } - // Drain our mailbox only if we're not quitting. - if (!self.quit) try self.drainMailbox(); + i += 1; } + + // Drain our mailbox only if we're not quitting. + if (!self.quit) try self.drainMailbox(); } /// Drain the mailbox. @@ -201,6 +207,11 @@ fn newTab(self: *App, msg: Message.NewWindow) !void { return; } + if (comptime build_config.artifact != .exe) { + log.warn("tabbing is not supported in embedded mode", .{}); + return; + } + const parent = msg.parent orelse { log.warn("parent must be set in new_tab message", .{}); return; @@ -332,6 +343,14 @@ pub const CAPI = struct { 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}); + }; + } + export fn ghostty_app_free(ptr: ?*App) void { if (ptr) |v| { v.destroy(); diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index fa61e85e5..596b36f5d 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -4,6 +4,14 @@ //! example for the macOS build of Ghostty so that we can use a native //! Swift+XCode-based application. +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const apprt = @import("../apprt.zig"); +const CoreApp = @import("../App.zig"); +const CoreWindow = @import("../Window.zig"); + pub const App = struct { /// Because we only expect the embedding API to be used in embedded /// environments, the options are extern so that we can expose it @@ -40,4 +48,50 @@ pub const Window = struct { pub fn deinit(self: *Window) void { _ = self; } + + pub fn init(app: *const CoreApp, core_win: *CoreWindow) !Window { + _ = app; + _ = core_win; + return .{}; + } + + pub fn getContentScale(self: *const Window) !apprt.ContentScale { + _ = self; + return apprt.ContentScale{ .x = 1, .y = 1 }; + } + + pub fn getSize(self: *const Window) !apprt.WindowSize { + _ = self; + return apprt.WindowSize{ .width = 1, .height = 1 }; + } + + pub fn setSizeLimits(self: *Window, min: apprt.WindowSize, max_: ?apprt.WindowSize) !void { + _ = self; + _ = min; + _ = max_; + } + + pub fn setTitle(self: *Window, slice: [:0]const u8) !void { + _ = self; + _ = slice; + } + + pub fn getClipboardString(self: *const Window) ![:0]const u8 { + _ = self; + return ""; + } + + pub fn setClipboardString(self: *const Window, val: [:0]const u8) !void { + _ = self; + _ = val; + } + + pub fn setShouldClose(self: *Window) void { + _ = self; + } + + pub fn shouldClose(self: *const Window) bool { + _ = self; + return false; + } }; diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 6ee656e6f..e15233d44 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -308,6 +308,7 @@ pub fn finalizeWindowInit(self: *const Metal, win: apprt.runtime.Window) !void { // Set our window backing layer to be our swapchain const nswindow = switch (apprt.runtime) { apprt.glfw => objc.Object.fromId(glfwNative.getCocoaWindow(win.window).?), + apprt.embedded => @panic("TODO"), else => @compileError("unsupported apprt for metal"), }; const contentView = objc.Object.fromId(nswindow.getProperty(?*anyopaque, "contentView").?); @@ -613,16 +614,18 @@ pub fn render( state.mutex.lock(); defer state.mutex.unlock(); - if (state.devmode) |dm| { - if (dm.visible) { - imgui.ImplMetal.newFrame(desc.value); - imgui.ImplGlfw.newFrame(); - try dm.update(); - imgui.ImplMetal.renderDrawData( - try dm.render(), - buffer.value, - encoder.value, - ); + if (DevMode.enabled) { + if (state.devmode) |dm| { + if (dm.visible) { + imgui.ImplMetal.newFrame(desc.value); + imgui.ImplGlfw.newFrame(); + try dm.update(); + imgui.ImplMetal.renderDrawData( + try dm.render(), + buffer.value, + encoder.value, + ); + } } } } @@ -690,7 +693,8 @@ pub fn setScreenSize(self: *Metal, _: renderer.ScreenSize) !void { // and we split them equal across all boundaries. const padding = self.padding.explicit.add(if (self.padding.balance) renderer.Padding.balanced(dim, grid_size, self.cell_size) - else .{}); + else + .{}); const padded_dim = dim.subPadding(padding); // Update our shaper