macos: address quick terminal basic functionality with new API

This commit is contained in:
Mitchell Hashimoto
2025-06-07 13:07:05 -07:00
parent 20744f0482
commit 6e77a5a6ca
5 changed files with 62 additions and 12 deletions

View File

@@ -786,6 +786,7 @@ ghostty_app_t ghostty_surface_app(ghostty_surface_t);
ghostty_surface_config_s ghostty_surface_inherited_config(ghostty_surface_t); ghostty_surface_config_s ghostty_surface_inherited_config(ghostty_surface_t);
void ghostty_surface_update_config(ghostty_surface_t, ghostty_config_t); void ghostty_surface_update_config(ghostty_surface_t, ghostty_config_t);
bool ghostty_surface_needs_confirm_quit(ghostty_surface_t); bool ghostty_surface_needs_confirm_quit(ghostty_surface_t);
bool ghostty_surface_process_exited(ghostty_surface_t);
void ghostty_surface_refresh(ghostty_surface_t); void ghostty_surface_refresh(ghostty_surface_t);
void ghostty_surface_draw(ghostty_surface_t); void ghostty_surface_draw(ghostty_surface_t);
void ghostty_surface_set_content_scale(ghostty_surface_t, double, double); void ghostty_surface_set_content_scale(ghostty_surface_t, double, double);

View File

@@ -612,6 +612,10 @@ class AppDelegate: NSObject,
guard let surfaceView = notification.object as? Ghostty.SurfaceView else { return } guard let surfaceView = notification.object as? Ghostty.SurfaceView else { return }
guard let window = surfaceView.window else { return } guard let window = surfaceView.window else { return }
// We only want to listen to new tabs if the focused parent is
// a regular terminal controller.
guard window.windowController is TerminalController else { return }
let configAny = notification.userInfo?[Ghostty.Notification.NewSurfaceConfigKey] let configAny = notification.userInfo?[Ghostty.Notification.NewSurfaceConfigKey]
let config = configAny as? Ghostty.SurfaceConfiguration let config = configAny as? Ghostty.SurfaceConfiguration

View File

@@ -61,6 +61,12 @@ class QuickTerminalController: BaseTerminalController {
selector: #selector(ghosttyConfigDidChange(_:)), selector: #selector(ghosttyConfigDidChange(_:)),
name: .ghosttyConfigDidChange, name: .ghosttyConfigDidChange,
object: nil) object: nil)
center.addObserver(
self,
selector: #selector(closeWindow(_:)),
name: .ghosttyCloseWindow,
object: nil
)
center.addObserver( center.addObserver(
self, self,
selector: #selector(onNewTab), selector: #selector(onNewTab),
@@ -198,16 +204,38 @@ class QuickTerminalController: BaseTerminalController {
// If our surface tree is nil then we animate the window out. // If our surface tree is nil then we animate the window out.
if (to.isEmpty) { if (to.isEmpty) {
// Save the current window frame before animating out. This preserves
// the user's preferred window size and position for when the quick
// terminal is reactivated with a new surface. Without this, SwiftUI
// would reset the window to its minimum content size.
lastClosedFrame = window?.frame
animateOut() animateOut()
} }
} }
override func closeSurfaceNode(
_ node: SplitTree<Ghostty.SurfaceView>.Node,
withConfirmation: Bool = true
) {
// If this isn't the root then we're dealing with a split closure.
if surfaceTree.root != node {
super.closeSurfaceNode(node, withConfirmation: withConfirmation)
return
}
// If this isn't a final leaf then we're dealing with a split closure
guard case .leaf(let surface) = node else {
super.closeSurfaceNode(node, withConfirmation: withConfirmation)
return
}
// If its the root, we check if the process exited. If it did,
// then we do empty the tree.
if surface.processExited {
surfaceTree = .init()
return
}
// If its the root then we just animate out. We never actually allow
// the surface to fully close.
animateOut()
}
// MARK: Methods // MARK: Methods
func toggle() { func toggle() {
@@ -252,12 +280,6 @@ class QuickTerminalController: BaseTerminalController {
let view = Ghostty.SurfaceView(ghostty_app, baseConfig: nil) let view = Ghostty.SurfaceView(ghostty_app, baseConfig: nil)
surfaceTree = SplitTree(view: view) surfaceTree = SplitTree(view: view)
focusedSurface = view focusedSurface = view
// Restore our previous frame if we have one
if let lastClosedFrame {
window.setFrame(lastClosedFrame, display: false)
self.lastClosedFrame = nil
}
} }
// Animate the window in // Animate the window in
@@ -283,6 +305,12 @@ class QuickTerminalController: BaseTerminalController {
private func animateWindowIn(window: NSWindow, from position: QuickTerminalPosition) { private func animateWindowIn(window: NSWindow, from position: QuickTerminalPosition) {
guard let screen = derivedConfig.quickTerminalScreen.screen else { return } guard let screen = derivedConfig.quickTerminalScreen.screen else { return }
// Restore our previous frame if we have one
if let lastClosedFrame {
window.setFrame(lastClosedFrame, display: false)
self.lastClosedFrame = nil
}
// Move our window off screen to the top // Move our window off screen to the top
position.setInitial(in: window, on: screen) position.setInitial(in: window, on: screen)
@@ -393,6 +421,12 @@ class QuickTerminalController: BaseTerminalController {
} }
private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) { private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) {
// Save the current window frame before animating out. This preserves
// the user's preferred window size and position for when the quick
// terminal is reactivated with a new surface. Without this, SwiftUI
// would reset the window to its minimum content size.
lastClosedFrame = window.frame
// If we hid the dock then we unhide it. // If we hid the dock then we unhide it.
hiddenDock = nil hiddenDock = nil

View File

@@ -92,6 +92,12 @@ extension Ghostty {
return ghostty_surface_needs_confirm_quit(surface) return ghostty_surface_needs_confirm_quit(surface)
} }
// Retruns true if the process in this surface has exited.
var processExited: Bool {
guard let surface = self.surface else { return true }
return ghostty_surface_process_exited(surface)
}
// Returns the inspector instance for this surface, or nil if the // Returns the inspector instance for this surface, or nil if the
// surface has been closed. // surface has been closed.
var inspector: ghostty_inspector_t? { var inspector: ghostty_inspector_t? {

View File

@@ -1359,6 +1359,11 @@ pub const CAPI = struct {
return surface.core_surface.needsConfirmQuit(); return surface.core_surface.needsConfirmQuit();
} }
/// Returns true if the surface process has exited.
export fn ghostty_surface_process_exited(surface: *Surface) bool {
return surface.core_surface.child_exited;
}
/// Returns true if the surface has a selection. /// Returns true if the surface has a selection.
export fn ghostty_surface_has_selection(surface: *Surface) bool { export fn ghostty_surface_has_selection(surface: *Surface) bool {
return surface.core_surface.hasSelection(); return surface.core_surface.hasSelection();