mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-26 12:27:29 +00:00
macOS: Add support for quick terminal sizing configuration
Added C bindings for the already existing quick-terminal-size configuration. Created a new QuickTerminalSize struct to hold these values in Swift. Updated the QuickTerminal implementation to use the user's configuration if supplied. Retains defaults. Also adds support to customize the width of the quick terminal (height if quick terminal is set to right or left).
This commit is contained in:
committed by
Mitchell Hashimoto
parent
5c464e855d
commit
63cd424678
@@ -450,6 +450,14 @@ typedef struct {
|
||||
ghostty_config_color_s colors[256];
|
||||
} ghostty_config_palette_s;
|
||||
|
||||
// config.QuickTerminalSize
|
||||
typedef struct {
|
||||
uint8_t primary_type; // 0 = none, 1 = percentage, 2 = pixels
|
||||
float primary_value;
|
||||
uint8_t secondary_type; // 0 = none, 1 = percentage, 2 = pixels
|
||||
float secondary_value;
|
||||
} ghostty_config_quick_terminal_size_s;
|
||||
|
||||
// apprt.Target.Key
|
||||
typedef enum {
|
||||
GHOSTTY_TARGET_APP,
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */; };
|
||||
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
||||
A5B4EA852DFE691B0022C3A2 /* NSMenuItem+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4EA842DFE69140022C3A2 /* NSMenuItem+Extension.swift */; };
|
||||
A5BB78B92DF9D8CE009AC3FA /* QuickTerminalSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */; };
|
||||
A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */; };
|
||||
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378D2D31D6C100931030 /* Weak.swift */; };
|
||||
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */; };
|
||||
@@ -252,6 +253,7 @@
|
||||
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
|
||||
A5B4EA842DFE69140022C3A2 /* NSMenuItem+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMenuItem+Extension.swift"; sourceTree = "<group>"; };
|
||||
A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalSize.swift; sourceTree = "<group>"; };
|
||||
A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardLayout.swift; sourceTree = "<group>"; };
|
||||
A5CA378D2D31D6C100931030 /* Weak.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weak.swift; sourceTree = "<group>"; };
|
||||
A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableWindowView.swift; sourceTree = "<group>"; };
|
||||
@@ -638,6 +640,7 @@
|
||||
A5CBD0632CA122E70017A1AE /* QuickTerminalPosition.swift */,
|
||||
A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */,
|
||||
A5CBD05F2CA0C9080017A1AE /* QuickTerminalWindow.swift */,
|
||||
A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */,
|
||||
);
|
||||
path = QuickTerminal;
|
||||
sourceTree = "<group>";
|
||||
@@ -939,6 +942,8 @@
|
||||
A53A297B2DB2E49700B6E02C /* CommandPalette.swift in Sources */,
|
||||
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TerminalWindow.swift in Sources */,
|
||||
A5BB78B92DF9D8CE009AC3FA /* QuickTerminalSize.swift in Sources */,
|
||||
A57D79272C9C879B001D522E /* SecureInput.swift in Sources */,
|
||||
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
|
||||
A5593FE12DF8D74000B47B10 /* HiddenTitlebarTerminalWindow.swift in Sources */,
|
||||
|
||||
@@ -109,7 +109,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
syncAppearance()
|
||||
|
||||
// Setup our initial size based on our configured position
|
||||
position.setLoaded(window)
|
||||
position.setLoaded(window, size: derivedConfig.quickTerminalSize)
|
||||
|
||||
// Upon first adding this Window to its host view, older SwiftUI
|
||||
// seems to have a "hiccup" and corrupts the frameRect,
|
||||
@@ -213,7 +213,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
// We use the actual screen the window is on for this, since it should
|
||||
// be on the proper screen.
|
||||
guard let screen = window?.screen ?? NSScreen.main else { return frameSize }
|
||||
return position.restrictFrameSize(frameSize, on: screen)
|
||||
return position.restrictFrameSize(frameSize, on: screen, terminalSize: derivedConfig.quickTerminalSize)
|
||||
}
|
||||
|
||||
// MARK: Base Controller Overrides
|
||||
@@ -341,7 +341,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
}
|
||||
|
||||
// Move our window off screen to the top
|
||||
position.setInitial(in: window, on: screen)
|
||||
position.setInitial(in: window, on: screen, terminalSize: derivedConfig.quickTerminalSize)
|
||||
|
||||
// We need to set our window level to a high value. In testing, only
|
||||
// popUpMenu and above do what we want. This gets it above the menu bar
|
||||
@@ -372,7 +372,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
NSAnimationContext.runAnimationGroup({ context in
|
||||
context.duration = derivedConfig.quickTerminalAnimationDuration
|
||||
context.timingFunction = .init(name: .easeIn)
|
||||
position.setFinal(in: window.animator(), on: screen)
|
||||
position.setFinal(in: window.animator(), on: screen, terminalSize: derivedConfig.quickTerminalSize)
|
||||
}, completionHandler: {
|
||||
// There is a very minor delay here so waiting at least an event loop tick
|
||||
// keeps us safe from the view not being on the window.
|
||||
@@ -496,7 +496,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
NSAnimationContext.runAnimationGroup({ context in
|
||||
context.duration = derivedConfig.quickTerminalAnimationDuration
|
||||
context.timingFunction = .init(name: .easeIn)
|
||||
position.setInitial(in: window.animator(), on: screen)
|
||||
position.setInitial(in: window.animator(), on: screen, terminalSize: derivedConfig.quickTerminalSize)
|
||||
}, completionHandler: {
|
||||
// This causes the window to be removed from the screen list and macOS
|
||||
// handles what should be focused next.
|
||||
@@ -627,6 +627,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
let quickTerminalAnimationDuration: Double
|
||||
let quickTerminalAutoHide: Bool
|
||||
let quickTerminalSpaceBehavior: QuickTerminalSpaceBehavior
|
||||
let quickTerminalSize: QuickTerminalSize
|
||||
let backgroundOpacity: Double
|
||||
|
||||
init() {
|
||||
@@ -634,6 +635,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
self.quickTerminalAnimationDuration = 0.2
|
||||
self.quickTerminalAutoHide = true
|
||||
self.quickTerminalSpaceBehavior = .move
|
||||
self.quickTerminalSize = QuickTerminalSize()
|
||||
self.backgroundOpacity = 1.0
|
||||
}
|
||||
|
||||
@@ -642,6 +644,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
self.quickTerminalAnimationDuration = config.quickTerminalAnimationDuration
|
||||
self.quickTerminalAutoHide = config.quickTerminalAutoHide
|
||||
self.quickTerminalSpaceBehavior = config.quickTerminalSpaceBehavior
|
||||
self.quickTerminalSize = config.quickTerminalSize
|
||||
self.backgroundOpacity = config.backgroundOpacity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,72 +8,58 @@ enum QuickTerminalPosition : String {
|
||||
case center
|
||||
|
||||
/// Set the loaded state for a window.
|
||||
func setLoaded(_ window: NSWindow) {
|
||||
func setLoaded(_ window: NSWindow, size: QuickTerminalSize) {
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
switch (self) {
|
||||
case .top, .bottom:
|
||||
let dimensions = size.calculate(position: self, screenDimensions: screen.frame.size)
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width,
|
||||
height: screen.frame.height / 4)
|
||||
width: dimensions.width,
|
||||
height: dimensions.height)
|
||||
), display: false)
|
||||
|
||||
case .left, .right:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width / 4,
|
||||
height: screen.frame.height)
|
||||
), display: false)
|
||||
|
||||
case .center:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width / 2,
|
||||
height: screen.frame.height / 3)
|
||||
), display: false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the initial state for a window for animating out of this position.
|
||||
func setInitial(in window: NSWindow, on screen: NSScreen) {
|
||||
func setInitial(in window: NSWindow, on screen: NSScreen, terminalSize: QuickTerminalSize) {
|
||||
// We always start invisible
|
||||
window.alphaValue = 0
|
||||
|
||||
// Position depends
|
||||
window.setFrame(.init(
|
||||
origin: initialOrigin(for: window, on: screen),
|
||||
size: restrictFrameSize(window.frame.size, on: screen)
|
||||
size: restrictFrameSize(window.frame.size, on: screen, terminalSize: terminalSize)
|
||||
), display: false)
|
||||
}
|
||||
|
||||
/// Set the final state for a window in this position.
|
||||
func setFinal(in window: NSWindow, on screen: NSScreen) {
|
||||
func setFinal(in window: NSWindow, on screen: NSScreen, terminalSize: QuickTerminalSize) {
|
||||
// We always end visible
|
||||
window.alphaValue = 1
|
||||
|
||||
// Position depends
|
||||
window.setFrame(.init(
|
||||
origin: finalOrigin(for: window, on: screen),
|
||||
size: restrictFrameSize(window.frame.size, on: screen)
|
||||
size: restrictFrameSize(window.frame.size, on: screen, terminalSize: terminalSize)
|
||||
), display: true)
|
||||
}
|
||||
|
||||
/// Restrict the frame size during resizing.
|
||||
func restrictFrameSize(_ size: NSSize, on screen: NSScreen) -> NSSize {
|
||||
func restrictFrameSize(_ size: NSSize, on screen: NSScreen, terminalSize: QuickTerminalSize) -> NSSize {
|
||||
var finalSize = size
|
||||
let dimensions = terminalSize.calculate(position: self, screenDimensions: screen.frame.size)
|
||||
|
||||
switch (self) {
|
||||
case .top, .bottom:
|
||||
finalSize.width = screen.frame.width
|
||||
finalSize.width = dimensions.width
|
||||
finalSize.height = dimensions.height
|
||||
|
||||
case .left, .right:
|
||||
finalSize.height = screen.visibleFrame.height
|
||||
finalSize.width = dimensions.width
|
||||
finalSize.height = dimensions.height
|
||||
|
||||
case .center:
|
||||
finalSize.width = screen.frame.width / 2
|
||||
finalSize.height = screen.frame.height / 3
|
||||
finalSize.width = dimensions.width
|
||||
finalSize.height = dimensions.height
|
||||
}
|
||||
|
||||
return finalSize
|
||||
@@ -83,16 +69,16 @@ enum QuickTerminalPosition : String {
|
||||
func initialOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch (self) {
|
||||
case .top:
|
||||
return .init(x: screen.frame.minX, y: screen.frame.maxY)
|
||||
return .init(x: screen.frame.origin.x + (screen.frame.width - window.frame.width) / 2, y: screen.frame.maxY)
|
||||
|
||||
case .bottom:
|
||||
return .init(x: screen.frame.minX, y: -window.frame.height)
|
||||
return .init(x: screen.frame.origin.x + (screen.frame.width - window.frame.width) / 2, y: -window.frame.height)
|
||||
|
||||
case .left:
|
||||
return .init(x: screen.frame.minX-window.frame.width, y: 0)
|
||||
return .init(x: screen.frame.minX-window.frame.width, y: screen.frame.origin.y + (screen.frame.height - window.frame.height) / 2)
|
||||
|
||||
case .right:
|
||||
return .init(x: screen.frame.maxX, y: 0)
|
||||
return .init(x: screen.frame.maxX, y: screen.frame.origin.y + (screen.frame.height - window.frame.height) / 2)
|
||||
|
||||
case .center:
|
||||
return .init(x: screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2, y: screen.visibleFrame.height - window.frame.width)
|
||||
@@ -103,16 +89,16 @@ enum QuickTerminalPosition : String {
|
||||
func finalOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch (self) {
|
||||
case .top:
|
||||
return .init(x: screen.frame.minX, y: screen.visibleFrame.maxY - window.frame.height)
|
||||
return .init(x: screen.frame.origin.x + (screen.frame.width - window.frame.width) / 2, y: screen.visibleFrame.maxY - window.frame.height)
|
||||
|
||||
case .bottom:
|
||||
return .init(x: screen.frame.minX, y: screen.frame.minY)
|
||||
return .init(x: screen.frame.origin.x + (screen.frame.width - window.frame.width) / 2, y: screen.frame.minY)
|
||||
|
||||
case .left:
|
||||
return .init(x: screen.frame.minX, y: window.frame.origin.y)
|
||||
return .init(x: screen.frame.minX, y: screen.frame.origin.y + (screen.frame.height - window.frame.height) / 2)
|
||||
|
||||
case .right:
|
||||
return .init(x: screen.visibleFrame.maxX - window.frame.width, y: window.frame.origin.y)
|
||||
return .init(x: screen.visibleFrame.maxX - window.frame.width, y: screen.frame.origin.y + (screen.frame.height - window.frame.height) / 2)
|
||||
|
||||
case .center:
|
||||
return .init(x: screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2, y: screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
|
||||
|
||||
80
macos/Sources/Features/QuickTerminal/QuickTerminalSize.swift
Normal file
80
macos/Sources/Features/QuickTerminal/QuickTerminalSize.swift
Normal file
@@ -0,0 +1,80 @@
|
||||
import Cocoa
|
||||
import GhosttyKit
|
||||
|
||||
struct QuickTerminalSize {
|
||||
let primary: Size?
|
||||
let secondary: Size?
|
||||
|
||||
init(primary: Size? = nil, secondary: Size? = nil) {
|
||||
self.primary = primary
|
||||
self.secondary = secondary
|
||||
}
|
||||
|
||||
init(from cStruct: ghostty_config_quick_terminal_size_s) {
|
||||
self.primary = cStruct.primary_type == 0 ? nil : Size(type: cStruct.primary_type, value: cStruct.primary_value)
|
||||
self.secondary = cStruct.secondary_type == 0 ? nil : Size(type: cStruct.secondary_type, value: cStruct.secondary_value)
|
||||
}
|
||||
|
||||
enum Size {
|
||||
case percentage(Float)
|
||||
case pixels(UInt32)
|
||||
|
||||
init?(type: UInt8, value: Float) {
|
||||
switch type {
|
||||
case 1:
|
||||
self = .percentage(value)
|
||||
case 2:
|
||||
self = .pixels(UInt32(value))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func toPixels(parentDimension: CGFloat) -> CGFloat {
|
||||
switch self {
|
||||
case .percentage(let value):
|
||||
return parentDimension * CGFloat(value) / 100.0
|
||||
case .pixels(let value):
|
||||
return CGFloat(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Dimensions {
|
||||
let width: CGFloat
|
||||
let height: CGFloat
|
||||
}
|
||||
|
||||
func calculate(position: QuickTerminalPosition, screenDimensions: CGSize) -> Dimensions {
|
||||
let dims = Dimensions(width: screenDimensions.width, height: screenDimensions.height)
|
||||
|
||||
switch position {
|
||||
case .left, .right:
|
||||
return Dimensions(
|
||||
width: primary?.toPixels(parentDimension: dims.width) ?? 400,
|
||||
height: secondary?.toPixels(parentDimension: dims.height) ?? dims.height
|
||||
)
|
||||
|
||||
case .top, .bottom:
|
||||
return Dimensions(
|
||||
width: secondary?.toPixels(parentDimension: dims.width) ?? dims.width,
|
||||
height: primary?.toPixels(parentDimension: dims.height) ?? 400
|
||||
)
|
||||
|
||||
case .center:
|
||||
if dims.width >= dims.height {
|
||||
// Landscape
|
||||
return Dimensions(
|
||||
width: primary?.toPixels(parentDimension: dims.width) ?? 800,
|
||||
height: secondary?.toPixels(parentDimension: dims.height) ?? 400
|
||||
)
|
||||
} else {
|
||||
// Portrait
|
||||
return Dimensions(
|
||||
width: secondary?.toPixels(parentDimension: dims.width) ?? 400,
|
||||
height: primary?.toPixels(parentDimension: dims.height) ?? 800
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -504,6 +504,14 @@ extension Ghostty {
|
||||
let str = String(cString: ptr)
|
||||
return QuickTerminalSpaceBehavior(fromGhosttyConfig: str) ?? .move
|
||||
}
|
||||
|
||||
var quickTerminalSize: QuickTerminalSize {
|
||||
guard let config = self.config else { return QuickTerminalSize() }
|
||||
var v = ghostty_config_quick_terminal_size_s()
|
||||
let key = "quick-terminal-size"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return QuickTerminalSize() }
|
||||
return QuickTerminalSize(from: v)
|
||||
}
|
||||
#endif
|
||||
|
||||
var resizeOverlay: ResizeOverlay {
|
||||
|
||||
@@ -7198,6 +7198,35 @@ pub const QuickTerminalSize = struct {
|
||||
height: u32,
|
||||
};
|
||||
|
||||
/// C API structure for QuickTerminalSize
|
||||
pub const C = extern struct {
|
||||
primary_type: u8, // 0 = none, 1 = percentage, 2 = pixels
|
||||
primary_value: f32,
|
||||
secondary_type: u8, // 0 = none, 1 = percentage, 2 = pixels
|
||||
secondary_value: f32,
|
||||
};
|
||||
|
||||
pub fn cval(self: QuickTerminalSize) C {
|
||||
return .{
|
||||
.primary_type = if (self.primary) |p| switch (p) {
|
||||
.percentage => 1,
|
||||
.pixels => 2,
|
||||
} else 0,
|
||||
.primary_value = if (self.primary) |p| switch (p) {
|
||||
.percentage => |v| v,
|
||||
.pixels => |v| @floatFromInt(v),
|
||||
} else 0,
|
||||
.secondary_type = if (self.secondary) |s| switch (s) {
|
||||
.percentage => 1,
|
||||
.pixels => 2,
|
||||
} else 0,
|
||||
.secondary_value = if (self.secondary) |s| switch (s) {
|
||||
.percentage => |v| v,
|
||||
.pixels => |v| @floatFromInt(v),
|
||||
} else 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn calculate(
|
||||
self: QuickTerminalSize,
|
||||
position: QuickTerminalPosition,
|
||||
|
||||
Reference in New Issue
Block a user