diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 59389c5c0..183dca544 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -1335,7 +1335,7 @@ extension Ghostty { mode: ghostty_action_inspector_e) { switch (target.tag) { case GHOSTTY_TARGET_APP: - Ghostty.logger.warning("toggle split zoom does nothing with an app target") + Ghostty.logger.warning("toggle inspector does nothing with an app target") return case GHOSTTY_TARGET_SURFACE: diff --git a/macos/Sources/Ghostty/Ghostty.Inspector.swift b/macos/Sources/Ghostty/Ghostty.Inspector.swift new file mode 100644 index 000000000..79567bc4a --- /dev/null +++ b/macos/Sources/Ghostty/Ghostty.Inspector.swift @@ -0,0 +1,100 @@ +import GhosttyKit +import Metal + +extension Ghostty { + /// Represents the inspector for a surface within Ghostty. + /// + /// Wraps a `ghostty_inspector_t` + final class Inspector: Sendable { + private let inspector: ghostty_inspector_t + + /// Read the underlying C value for this inspector. This is unsafe because the value will be + /// freed when the Inspector class is deinitialized. + var unsafeCValue: ghostty_inspector_t { + inspector + } + + /// Initialize from the C structure. + init(cInspector: ghostty_inspector_t) { + self.inspector = cInspector + } + + /// Set the focus state of the inspector. + @MainActor + func setFocus(_ focused: Bool) { + ghostty_inspector_set_focus(inspector, focused) + } + + /// Set the content scale of the inspector. + @MainActor + func setContentScale(x: Double, y: Double) { + ghostty_inspector_set_content_scale(inspector, x, y) + } + + /// Set the size of the inspector. + @MainActor + func setSize(width: UInt32, height: UInt32) { + ghostty_inspector_set_size(inspector, width, height) + } + + /// Send a mouse button event to the inspector. + @MainActor + func mouseButton( + _ state: ghostty_input_mouse_state_e, + button: ghostty_input_mouse_button_e, + mods: ghostty_input_mods_e + ) { + ghostty_inspector_mouse_button(inspector, state, button, mods) + } + + /// Send a mouse position event to the inspector. + @MainActor + func mousePos(x: Double, y: Double) { + ghostty_inspector_mouse_pos(inspector, x, y) + } + + /// Send a mouse scroll event to the inspector. + @MainActor + func mouseScroll(x: Double, y: Double, mods: ghostty_input_scroll_mods_t) { + ghostty_inspector_mouse_scroll(inspector, x, y, mods) + } + + /// Send a key event to the inspector. + @MainActor + func key( + _ action: ghostty_input_action_e, + key: ghostty_input_key_e, + mods: ghostty_input_mods_e + ) { + ghostty_inspector_key(inspector, action, key, mods) + } + + /// Send text to the inspector. + @MainActor + func text(_ text: String) { + text.withCString { ptr in + ghostty_inspector_text(inspector, ptr) + } + } + + /// Initialize Metal rendering for the inspector. + @MainActor + func metalInit(device: MTLDevice) -> Bool { + let devicePtr = Unmanaged.passRetained(device).toOpaque() + return ghostty_inspector_metal_init(inspector, devicePtr) + } + + /// Render the inspector using Metal. + @MainActor + func metalRender( + commandBuffer: MTLCommandBuffer, + descriptor: MTLRenderPassDescriptor + ) { + ghostty_inspector_metal_render( + inspector, + Unmanaged.passRetained(commandBuffer).toOpaque(), + Unmanaged.passRetained(descriptor).toOpaque() + ) + } + } +} diff --git a/macos/Sources/Ghostty/Surface View/InspectorView.swift b/macos/Sources/Ghostty/Surface View/InspectorView.swift index e8eaf3a80..0ca48371e 100644 --- a/macos/Sources/Ghostty/Surface View/InspectorView.swift +++ b/macos/Sources/Ghostty/Surface View/InspectorView.swift @@ -98,7 +98,7 @@ extension Ghostty { didSet { surfaceViewDidChange() } } - private var inspector: ghostty_inspector_t? { + private var inspector: Ghostty.Inspector? { guard let surfaceView = self.surfaceView else { return nil } return surfaceView.inspector } @@ -150,8 +150,7 @@ extension Ghostty { 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) + _ = inspector.metalInit(device: device) // Register an observer for render requests center.addObserver( @@ -172,10 +171,10 @@ extension Ghostty { let fbFrame = self.convertToBacking(self.frame) let xScale = fbFrame.size.width / self.frame.size.width let yScale = fbFrame.size.height / self.frame.size.height - ghostty_inspector_set_content_scale(inspector, xScale, yScale) + inspector.setContentScale(x: xScale, y: yScale) // When our scale factor changes, so does our fb size so we send that too - ghostty_inspector_set_size(inspector, UInt32(fbFrame.size.width), UInt32(fbFrame.size.height)) + inspector.setSize(width: UInt32(fbFrame.size.width), height: UInt32(fbFrame.size.height)) } // MARK: NSView @@ -184,7 +183,7 @@ extension Ghostty { let result = super.becomeFirstResponder() if (result) { if let inspector = self.inspector { - ghostty_inspector_set_focus(inspector, true) + inspector.setFocus(true) } } return result @@ -194,7 +193,7 @@ extension Ghostty { let result = super.resignFirstResponder() if (result) { if let inspector = self.inspector { - ghostty_inspector_set_focus(inspector, false) + inspector.setFocus(false) } } return result @@ -229,25 +228,25 @@ extension Ghostty { override func mouseDown(with event: NSEvent) { guard let inspector = self.inspector else { return } let mods = Ghostty.ghosttyMods(event.modifierFlags) - ghostty_inspector_mouse_button(inspector, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_LEFT, mods) + inspector.mouseButton(GHOSTTY_MOUSE_PRESS, button: GHOSTTY_MOUSE_LEFT, mods: mods) } override func mouseUp(with event: NSEvent) { guard let inspector = self.inspector else { return } let mods = Ghostty.ghosttyMods(event.modifierFlags) - ghostty_inspector_mouse_button(inspector, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_LEFT, mods) + inspector.mouseButton(GHOSTTY_MOUSE_RELEASE, button: GHOSTTY_MOUSE_LEFT, mods: mods) } override func rightMouseDown(with event: NSEvent) { guard let inspector = self.inspector else { return } let mods = Ghostty.ghosttyMods(event.modifierFlags) - ghostty_inspector_mouse_button(inspector, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_RIGHT, mods) + inspector.mouseButton(GHOSTTY_MOUSE_PRESS, button: GHOSTTY_MOUSE_RIGHT, mods: mods) } override func rightMouseUp(with event: NSEvent) { guard let inspector = self.inspector else { return } let mods = Ghostty.ghosttyMods(event.modifierFlags) - ghostty_inspector_mouse_button(inspector, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_RIGHT, mods) + inspector.mouseButton(GHOSTTY_MOUSE_RELEASE, button: GHOSTTY_MOUSE_RIGHT, mods: mods) } override func mouseMoved(with event: NSEvent) { @@ -255,7 +254,7 @@ extension Ghostty { // Convert window position to view position. Note (0, 0) is bottom left. let pos = self.convert(event.locationInWindow, from: nil) - ghostty_inspector_mouse_pos(inspector, pos.x, frame.height - pos.y) + inspector.mousePos(x: pos.x, y: frame.height - pos.y) } @@ -297,7 +296,7 @@ extension Ghostty { // Pack our momentum value into the mods bitmask mods |= Int32(momentum.rawValue) << 1 - ghostty_inspector_mouse_scroll(inspector, x, y, mods) + inspector.mouseScroll(x: x, y: y, mods: mods) } override func keyDown(with event: NSEvent) { @@ -336,7 +335,7 @@ extension Ghostty { guard let inspector = self.inspector else { return } guard let key = Ghostty.Input.Key(keyCode: event.keyCode) else { return } let mods = Ghostty.ghosttyMods(event.modifierFlags) - ghostty_inspector_key(inspector, action, key.cKey, mods) + inspector.key(action, key: key.cKey, mods: mods) } // MARK: NSTextInputClient @@ -406,9 +405,7 @@ extension Ghostty { let len = chars.utf8CString.count if (len == 0) { return } - chars.withCString { ptr in - ghostty_inspector_text(inspector, ptr) - } + inspector.text(chars) } override func doCommand(by selector: Selector) { @@ -435,11 +432,7 @@ extension Ghostty { updateSize() // Render - ghostty_inspector_metal_render( - inspector, - Unmanaged.passRetained(commandBuffer).toOpaque(), - Unmanaged.passRetained(descriptor).toOpaque() - ) + inspector.metalRender(commandBuffer: commandBuffer, descriptor: descriptor) guard let drawable = self.currentDrawable else { return } commandBuffer.present(drawable) diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift index 0ddfe57b8..c856b0163 100644 --- a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift @@ -173,10 +173,11 @@ extension Ghostty { } // Returns the inspector instance for this surface, or nil if the - // surface has been closed. - var inspector: ghostty_inspector_t? { + // surface has been closed or no inspector is active. + var inspector: Ghostty.Inspector? { guard let surface = self.surface else { return nil } - return ghostty_surface_inspector(surface) + guard let cInspector = ghostty_surface_inspector(surface) else { return nil } + return Ghostty.Inspector(cInspector: cInspector) } // True if the inspector should be visible