diff --git a/include/ghostty.h b/include/ghostty.h index 7e248df06..d1ce87b7d 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -329,6 +329,7 @@ typedef void (*ghostty_runtime_toggle_split_zoom_cb)(void *); typedef void (*ghostty_runtime_goto_tab_cb)(void *, int32_t); typedef void (*ghostty_runtime_toggle_fullscreen_cb)(void *, ghostty_non_native_fullscreen_e); typedef void (*ghostty_runtime_set_initial_window_size_cb)(void *, uint32_t, uint32_t); +typedef void (*ghostty_runtime_render_inspector_cb)(void *); typedef struct { void *userdata; @@ -349,6 +350,7 @@ typedef struct { ghostty_runtime_goto_tab_cb goto_tab_cb; ghostty_runtime_toggle_fullscreen_cb toggle_fullscreen_cb; ghostty_runtime_set_initial_window_size_cb set_initial_window_size_cb; + ghostty_runtime_render_inspector_cb render_inspector_cb; } ghostty_runtime_config_s; //------------------------------------------------------------------- diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 90df697a7..fb03c41e2 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -141,7 +141,8 @@ extension Ghostty { toggle_split_zoom_cb: { userdata in AppState.toggleSplitZoom(userdata) }, goto_tab_cb: { userdata, n in AppState.gotoTab(userdata, n: n) }, toggle_fullscreen_cb: { userdata, nonNativeFullscreen in AppState.toggleFullscreen(userdata, nonNativeFullscreen: nonNativeFullscreen) }, - set_initial_window_size_cb: { userdata, width, height in AppState.setInitialWindowSize(userdata, width: width, height: height) } + set_initial_window_size_cb: { userdata, width, height in AppState.setInitialWindowSize(userdata, width: width, height: height) }, + render_inspector_cb: { userdata in AppState.renderInspector(userdata) } ) // Create the ghostty app. @@ -415,6 +416,14 @@ extension Ghostty { // standpoint since we don't do this much. DispatchQueue.main.async { state.appTick() } } + + static func renderInspector(_ userdata: UnsafeMutableRawPointer?) { + guard let surface = self.surfaceUserdata(from: userdata) else { return } + NotificationCenter.default.post( + name: Notification.inspectorNeedsDisplay, + object: surface + ) + } static func setTitle(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer?) { let surfaceView = Unmanaged.fromOpaque(userdata!).takeUnretainedValue() diff --git a/macos/Sources/Ghostty/InspectorView.swift b/macos/Sources/Ghostty/InspectorView.swift index 2b5aca393..108a353a1 100644 --- a/macos/Sources/Ghostty/InspectorView.swift +++ b/macos/Sources/Ghostty/InspectorView.swift @@ -63,6 +63,10 @@ extension Ghostty { self.commandQueue = commandQueue super.init(frame: frame, device: device) + // This makes it so renders only happen when we request + self.enableSetNeedsDisplay = true + self.isPaused = true + // After initializing the parent we can set our own properties self.device = MTLCreateSystemDefaultDevice() self.clearColor = MTLClearColor(red: 0x28 / 0xFF, green: 0x2C / 0xFF, blue: 0x34 / 0xFF, alpha: 1.0) @@ -77,15 +81,31 @@ extension Ghostty { deinit { trackingAreas.forEach { removeTrackingArea($0) } + NotificationCenter.default.removeObserver(self) } // MARK: Internal Inspector Funcs private func surfaceViewDidChange() { + let center = NotificationCenter.default + center.removeObserver(self) + + guard let surfaceView = self.surfaceView else { return } guard let inspector = self.inspector else { return } guard let device = self.device else { return } let devicePtr = Unmanaged.passRetained(device).toOpaque() ghostty_inspector_metal_init(inspector, devicePtr) + + // Register an observer for render requests + center.addObserver( + self, + selector: #selector(didRequestRender), + name: Ghostty.Notification.inspectorNeedsDisplay, + object: surfaceView) + } + + @objc private func didRequestRender(notification: SwiftUI.Notification) { + self.needsDisplay = true } private func updateSize() { diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index 8f97a090d..051ff4656 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -102,6 +102,9 @@ extension Ghostty.Notification { /// Notification static let didReceiveInitialWindowFrame = Notification.Name("com.mitchellh.ghostty.didReceiveInitialWindowFrame") static let FrameKey = "com.mitchellh.ghostty.frame" + + /// Notification to render the inspector for a surface + static let inspectorNeedsDisplay = Notification.Name("com.mitchellh.ghostty.inspectorNeedsDisplay") } // Make the input enum hashable. diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index cb9f1b77e..87336f6ee 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -91,6 +91,9 @@ pub const App = struct { /// Set the initial window size. It is up to the user of libghostty to /// determine if it is the initial window and set this appropriately. set_initial_window_size: ?*const fn (SurfaceUD, u32, u32) callconv(.C) void = null, + + /// Render the inspector for the given surface. + render_inspector: ?*const fn (SurfaceUD) callconv(.C) void = null, }; /// Special values for the goto_tab callback. @@ -788,6 +791,15 @@ pub const Surface = struct { func(self.opts.userdata, width, height); } + fn queueInspectorRender(self: *const Surface) void { + const func = self.app.opts.render_inspector orelse { + log.info("runtime embedder does not render_inspector", .{}); + return; + }; + + func(self.opts.userdata); + } + fn newSurfaceOptions(self: *const Surface) apprt.Surface.Options { const font_size: u16 = font_size: { if (!self.app.config.@"window-inherit-font-size") break :font_size 0; @@ -851,8 +863,7 @@ pub const Inspector = struct { /// Queue a render for the next frame. pub fn queueRender(self: *Inspector) void { - // TODO - _ = self; + self.surface.queueInspectorRender(); } /// Initialize the inspector for a metal backend.