mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
macos: starting to work on new libghostty data models
This commit is contained in:
@@ -53,7 +53,6 @@
|
||||
A54B0CED2D0CFB7700CBEFF8 /* ColorizedGhosttyIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54B0CEC2D0CFB7300CBEFF8 /* ColorizedGhosttyIcon.swift */; };
|
||||
A54B0CEF2D0D2E2800CBEFF8 /* ColorizedGhosttyIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54B0CEE2D0D2E2400CBEFF8 /* ColorizedGhosttyIconImage.swift */; };
|
||||
A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54D786B2CA79788001B19B1 /* BaseTerminalController.swift */; };
|
||||
A55685E029A03A9F004303CE /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55685DF29A03A9F004303CE /* AppError.swift */; };
|
||||
A5593FDF2DF8D57C00B47B10 /* TerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5593FDE2DF8D57100B47B10 /* TerminalWindow.swift */; };
|
||||
A5593FE12DF8D74000B47B10 /* HiddenTitlebarTerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5593FE02DF8D73400B47B10 /* HiddenTitlebarTerminalWindow.swift */; };
|
||||
A5593FE32DF8D78600B47B10 /* TerminalHiddenTitlebar.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5593FE22DF8D78600B47B10 /* TerminalHiddenTitlebar.xib */; };
|
||||
@@ -124,6 +123,9 @@
|
||||
A5E408302E0271320035FEAC /* GhosttyIntentError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E4082F2E0271320035FEAC /* GhosttyIntentError.swift */; };
|
||||
A5E408322E02FEDF0035FEAC /* TerminalEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E408312E02FEDC0035FEAC /* TerminalEntity.swift */; };
|
||||
A5E408342E0320140035FEAC /* GetTerminalDetailsIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E408332E03200F0035FEAC /* GetTerminalDetailsIntent.swift */; };
|
||||
A5E408382E03C7DA0035FEAC /* Ghostty.Surface.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E408372E03C7D80035FEAC /* Ghostty.Surface.swift */; };
|
||||
A5E4083A2E0449BD0035FEAC /* Ghostty.Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E408392E0449BB0035FEAC /* Ghostty.Command.swift */; };
|
||||
A5E4083C2E044DB50035FEAC /* Ghostty.Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E4083B2E044DB40035FEAC /* Ghostty.Error.swift */; };
|
||||
A5FEB3002ABB69450068369E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FEB2FF2ABB69450068369E /* main.swift */; };
|
||||
AEE8B3452B9AA39600260C5E /* NSPasteboard+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */; };
|
||||
C159E81D2B66A06B00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; };
|
||||
@@ -175,7 +177,6 @@
|
||||
A54B0CEC2D0CFB7300CBEFF8 /* ColorizedGhosttyIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorizedGhosttyIcon.swift; sourceTree = "<group>"; };
|
||||
A54B0CEE2D0D2E2400CBEFF8 /* ColorizedGhosttyIconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorizedGhosttyIconImage.swift; sourceTree = "<group>"; };
|
||||
A54D786B2CA79788001B19B1 /* BaseTerminalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTerminalController.swift; sourceTree = "<group>"; };
|
||||
A55685DF29A03A9F004303CE /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
|
||||
A5593FDE2DF8D57100B47B10 /* TerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindow.swift; sourceTree = "<group>"; };
|
||||
A5593FE02DF8D73400B47B10 /* HiddenTitlebarTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenTitlebarTerminalWindow.swift; sourceTree = "<group>"; };
|
||||
A5593FE22DF8D78600B47B10 /* TerminalHiddenTitlebar.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TerminalHiddenTitlebar.xib; sourceTree = "<group>"; };
|
||||
@@ -248,6 +249,9 @@
|
||||
A5E4082F2E0271320035FEAC /* GhosttyIntentError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyIntentError.swift; sourceTree = "<group>"; };
|
||||
A5E408312E02FEDC0035FEAC /* TerminalEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalEntity.swift; sourceTree = "<group>"; };
|
||||
A5E408332E03200F0035FEAC /* GetTerminalDetailsIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetTerminalDetailsIntent.swift; sourceTree = "<group>"; };
|
||||
A5E408372E03C7D80035FEAC /* Ghostty.Surface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Surface.swift; sourceTree = "<group>"; };
|
||||
A5E408392E0449BB0035FEAC /* Ghostty.Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Command.swift; sourceTree = "<group>"; };
|
||||
A5E4083B2E044DB40035FEAC /* Ghostty.Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Error.swift; sourceTree = "<group>"; };
|
||||
A5FEB2FF2ABB69450068369E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPasteboard+Extension.swift"; sourceTree = "<group>"; };
|
||||
C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSColor+Extension.swift"; sourceTree = "<group>"; };
|
||||
@@ -440,12 +444,14 @@
|
||||
A5333E152B59DE8E008AEFF7 /* SurfaceView_UIKit.swift */,
|
||||
A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */,
|
||||
A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */,
|
||||
A5E408392E0449BB0035FEAC /* Ghostty.Command.swift */,
|
||||
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */,
|
||||
A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */,
|
||||
A5E4083B2E044DB40035FEAC /* Ghostty.Error.swift */,
|
||||
A5CF66D62D29DDB100139794 /* Ghostty.Event.swift */,
|
||||
A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */,
|
||||
A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */,
|
||||
A55685DF29A03A9F004303CE /* AppError.swift */,
|
||||
A5E408372E03C7D80035FEAC /* Ghostty.Surface.swift */,
|
||||
A52FFF5A2CAA54A8000C6A5B /* FullscreenMode+Extension.swift */,
|
||||
A5CF66D32D289CEA00139794 /* NSEvent+Extension.swift */,
|
||||
);
|
||||
@@ -766,6 +772,7 @@
|
||||
A5CBD0602CA0C90A0017A1AE /* QuickTerminalWindow.swift in Sources */,
|
||||
A5CBD05E2CA0C5EC0017A1AE /* QuickTerminalController.swift in Sources */,
|
||||
A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */,
|
||||
A5E408382E03C7DA0035FEAC /* Ghostty.Surface.swift in Sources */,
|
||||
A5593FE72DF927D200B47B10 /* TransparentTitlebarTerminalWindow.swift in Sources */,
|
||||
A5A2A3CA2D4445E30033CF96 /* Dock.swift in Sources */,
|
||||
A586365F2DEE6C2300E04A10 /* SplitTree.swift in Sources */,
|
||||
@@ -800,6 +807,7 @@
|
||||
A5985CD72C320C4500C57AD3 /* String+Extension.swift in Sources */,
|
||||
A5A2A3CC2D444ABB0033CF96 /* NSApplication+Extension.swift in Sources */,
|
||||
A5E408302E0271320035FEAC /* GhosttyIntentError.swift in Sources */,
|
||||
A5E4083A2E0449BD0035FEAC /* Ghostty.Command.swift in Sources */,
|
||||
A5FEB3002ABB69450068369E /* main.swift in Sources */,
|
||||
A53A297F2DB4480F00B6E02C /* EventModifiers+Extension.swift in Sources */,
|
||||
A5E4082E2E0237460035FEAC /* NewTerminalIntent.swift in Sources */,
|
||||
@@ -809,13 +817,13 @@
|
||||
A57D79272C9C879B001D522E /* SecureInput.swift in Sources */,
|
||||
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
|
||||
A5593FE12DF8D74000B47B10 /* HiddenTitlebarTerminalWindow.swift in Sources */,
|
||||
A5E4083C2E044DB50035FEAC /* Ghostty.Error.swift in Sources */,
|
||||
A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */,
|
||||
A53A6C032CCC1B7F00943E98 /* Ghostty.Action.swift in Sources */,
|
||||
A54B0CED2D0CFB7700CBEFF8 /* ColorizedGhosttyIcon.swift in Sources */,
|
||||
A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */,
|
||||
A54B0CEF2D0D2E2800CBEFF8 /* ColorizedGhosttyIconImage.swift in Sources */,
|
||||
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */,
|
||||
A55685E029A03A9F004303CE /* AppError.swift in Sources */,
|
||||
A599CDB02CF103F60049FA26 /* NSAppearance+Extension.swift in Sources */,
|
||||
A52FFF572CA90484000C6A5B /* QuickTerminalScreen.swift in Sources */,
|
||||
A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */,
|
||||
|
@@ -17,33 +17,19 @@ struct TerminalCommandPaletteView: View {
|
||||
|
||||
// The commands available to the command palette.
|
||||
private var commandOptions: [CommandOption] {
|
||||
guard let surface = surfaceView.surface else { return [] }
|
||||
|
||||
var ptr: UnsafeMutablePointer<ghostty_command_s>? = nil
|
||||
var count: Int = 0
|
||||
ghostty_surface_commands(surface, &ptr, &count)
|
||||
guard let ptr else { return [] }
|
||||
|
||||
let buffer = UnsafeBufferPointer(start: ptr, count: count)
|
||||
return Array(buffer).filter { c in
|
||||
let key = String(cString: c.action_key)
|
||||
switch (key) {
|
||||
case "toggle_tab_overview",
|
||||
"toggle_window_decorations",
|
||||
"show_gtk_inspector":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}.map { c in
|
||||
let action = String(cString: c.action)
|
||||
return CommandOption(
|
||||
title: String(cString: c.title),
|
||||
description: String(cString: c.description),
|
||||
symbols: ghosttyConfig.keyboardShortcut(for: action)?.keyList
|
||||
) {
|
||||
onAction(action)
|
||||
guard let surface = surfaceView.surfaceModel else { return [] }
|
||||
do {
|
||||
return try surface.commands().map { c in
|
||||
return CommandOption(
|
||||
title: c.title,
|
||||
description: c.description,
|
||||
symbols: ghosttyConfig.keyboardShortcut(for: c.action)?.keyList
|
||||
) {
|
||||
onAction(c.action)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,3 +0,0 @@
|
||||
enum AppError: Error {
|
||||
case surfaceCreateError
|
||||
}
|
46
macos/Sources/Ghostty/Ghostty.Command.swift
Normal file
46
macos/Sources/Ghostty/Ghostty.Command.swift
Normal file
@@ -0,0 +1,46 @@
|
||||
import GhosttyKit
|
||||
|
||||
extension Ghostty {
|
||||
/// `ghostty_command_s`
|
||||
struct Command: Sendable {
|
||||
private let cValue: ghostty_command_s
|
||||
|
||||
/// The title of the command.
|
||||
var title: String {
|
||||
String(cString: cValue.title)
|
||||
}
|
||||
|
||||
/// Human-friendly description of what this command will do.
|
||||
var description: String {
|
||||
String(cString: cValue.description)
|
||||
}
|
||||
|
||||
/// The full action that must be performed to invoke this command.
|
||||
var action: String {
|
||||
String(cString: cValue.action)
|
||||
}
|
||||
|
||||
/// Only the key portion of the action so you can compare action types, e.g. `goto_split`
|
||||
/// instead of `goto_split:left`.
|
||||
var actionKey: String {
|
||||
String(cString: cValue.action_key)
|
||||
}
|
||||
|
||||
/// True if this can be performed on this target.
|
||||
var isSupported: Bool {
|
||||
!Self.unsupportedActionKeys.contains(actionKey)
|
||||
}
|
||||
|
||||
/// Unsupported action keys, because they either don't make sense in the context of our
|
||||
/// target platform or they just aren't implemented yet.
|
||||
static let unsupportedActionKeys: [String] = [
|
||||
"toggle_tab_overview",
|
||||
"toggle_window_decorations",
|
||||
"show_gtk_inspector",
|
||||
]
|
||||
|
||||
init(cValue: ghostty_command_s) {
|
||||
self.cValue = cValue
|
||||
}
|
||||
}
|
||||
}
|
12
macos/Sources/Ghostty/Ghostty.Error.swift
Normal file
12
macos/Sources/Ghostty/Ghostty.Error.swift
Normal file
@@ -0,0 +1,12 @@
|
||||
extension Ghostty {
|
||||
/// Possible errors from internal Ghostty calls.
|
||||
enum Error: Swift.Error, CustomLocalizedStringResourceConvertible {
|
||||
case apiFailed
|
||||
|
||||
var localizedStringResource: LocalizedStringResource {
|
||||
switch self {
|
||||
case .apiFailed: return "libghostty API call failed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
64
macos/Sources/Ghostty/Ghostty.Surface.swift
Normal file
64
macos/Sources/Ghostty/Ghostty.Surface.swift
Normal file
@@ -0,0 +1,64 @@
|
||||
import GhosttyKit
|
||||
|
||||
extension Ghostty {
|
||||
/// Represents a single surface within Ghostty.
|
||||
///
|
||||
/// NOTE(mitchellh): This is a work-in-progress class as part of a general refactor
|
||||
/// of our Ghostty data model. At the time of writing there's still a ton of surface
|
||||
/// functionality that is not encapsulated in this class. It is planned to migrate that
|
||||
/// all over.
|
||||
///
|
||||
/// Wraps a `ghostty_surface_t`
|
||||
final class Surface: Sendable {
|
||||
private let surface: ghostty_surface_t
|
||||
|
||||
/// Read the underlying C value for this surface. This is unsafe because the value will be
|
||||
/// freed when the Surface class is deinitialized.
|
||||
var unsafeCValue: ghostty_surface_t {
|
||||
surface
|
||||
}
|
||||
|
||||
/// Initialize from the C structure.
|
||||
init(cSurface: ghostty_surface_t) {
|
||||
self.surface = cSurface
|
||||
}
|
||||
|
||||
deinit {
|
||||
// deinit is not guaranteed to happen on the main actor and our API
|
||||
// calls into libghostty must happen there so we capture the surface
|
||||
// value so we don't capture `self` and then we detach it in a task.
|
||||
// We can't wait for the task to succeed so this will happen sometime
|
||||
// but that's okay.
|
||||
let surface = self.surface
|
||||
Task.detached { @MainActor in
|
||||
ghostty_surface_free(surface)
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a keybinding action.
|
||||
///
|
||||
/// The action can be any valid keybind parameter. e.g. `keybind = goto_tab:4`
|
||||
/// you can perform `goto_tab:4` with this.
|
||||
///
|
||||
/// Returns true if the action was performed. Invalid actions return false.
|
||||
@MainActor
|
||||
func perform(action: String) -> Bool {
|
||||
let len = action.utf8CString.count
|
||||
if (len == 0) { return false }
|
||||
return action.withCString { cString in
|
||||
ghostty_surface_binding_action(surface, cString, UInt(len - 1))
|
||||
}
|
||||
}
|
||||
|
||||
/// Command options for this surface.
|
||||
@MainActor
|
||||
func commands() throws -> [Command] {
|
||||
var ptr: UnsafeMutablePointer<ghostty_command_s>? = nil
|
||||
var count: Int = 0
|
||||
ghostty_surface_commands(surface, &ptr, &count)
|
||||
guard let ptr else { throw Error.apiFailed }
|
||||
let buffer = UnsafeBufferPointer(start: ptr, count: count)
|
||||
return Array(buffer).map { Command(cValue: $0) }.filter { $0.isSupported }
|
||||
}
|
||||
}
|
||||
}
|
@@ -19,6 +19,15 @@ struct Ghostty {
|
||||
static let userNotificationActionShow = "com.mitchellh.ghostty.userNotification.Show"
|
||||
}
|
||||
|
||||
// MARK: C Extensions
|
||||
|
||||
/// A command is fully self-contained so it is Sendable.
|
||||
extension ghostty_command_s: @unchecked @retroactive Sendable {}
|
||||
|
||||
/// A surface is sendable because it is just a reference type. Using the surface in parameters
|
||||
/// may be unsafe but the value itself is safe to send across threads.
|
||||
extension ghostty_surface_t: @unchecked @retroactive Sendable {}
|
||||
|
||||
// MARK: Build Info
|
||||
|
||||
extension Ghostty {
|
||||
|
@@ -79,7 +79,7 @@ extension Ghostty {
|
||||
let pubResign = center.publisher(for: NSWindow.didResignKeyNotification)
|
||||
#endif
|
||||
|
||||
Surface(view: surfaceView, size: geo.size)
|
||||
SurfaceRepresentable(view: surfaceView, size: geo.size)
|
||||
.focused($surfaceFocus)
|
||||
.focusedValue(\.ghosttySurfacePwd, surfaceView.pwd)
|
||||
.focusedValue(\.ghosttySurfaceView, surfaceView)
|
||||
@@ -381,7 +381,7 @@ extension Ghostty {
|
||||
/// We just wrap an AppKit NSView here at the moment so that we can behave as low level as possible
|
||||
/// since that is what the Metal renderer in Ghostty expects. In the future, it may make more sense to
|
||||
/// wrap an MTKView and use that, but for legacy reasons we didn't do that to begin with.
|
||||
struct Surface: OSViewRepresentable {
|
||||
struct SurfaceRepresentable: OSViewRepresentable {
|
||||
/// The view to render for the terminal surface.
|
||||
let view: SurfaceView
|
||||
|
||||
|
@@ -115,10 +115,20 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the data model for this surface.
|
||||
///
|
||||
/// Note: eventually, all surface access will be through this, but presently its in a transition
|
||||
/// state so we're mixing this with direct surface access.
|
||||
private(set) var surfaceModel: Ghostty.Surface?
|
||||
|
||||
/// Returns the underlying C value for the surface. See "note" on surfaceModel.
|
||||
var surface: ghostty_surface_t? {
|
||||
surfaceModel?.unsafeCValue
|
||||
}
|
||||
|
||||
// Notification identifiers associated with this surface
|
||||
var notificationIdentifiers: Set<String> = []
|
||||
|
||||
private(set) var surface: ghostty_surface_t?
|
||||
private var markedText: NSMutableAttributedString
|
||||
private(set) var focused: Bool = true
|
||||
private var prevPressureStage: Int = 0
|
||||
@@ -282,10 +292,10 @@ extension Ghostty {
|
||||
let surface_cfg = baseConfig ?? SurfaceConfiguration()
|
||||
var surface_cfg_c = surface_cfg.ghosttyConfig(view: self)
|
||||
guard let surface = ghostty_surface_new(app, &surface_cfg_c) else {
|
||||
self.error = AppError.surfaceCreateError
|
||||
self.error = Ghostty.Error.apiFailed
|
||||
return
|
||||
}
|
||||
self.surface = surface;
|
||||
self.surfaceModel = Ghostty.Surface(cSurface: surface)
|
||||
|
||||
// Setup our tracking area so we get mouse moved events
|
||||
updateTrackingAreas()
|
||||
@@ -340,11 +350,6 @@ extension Ghostty {
|
||||
// Remove any notifications associated with this surface
|
||||
let identifiers = Array(self.notificationIdentifiers)
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers)
|
||||
|
||||
// Free our core surface resources
|
||||
if let surface = self.surface {
|
||||
ghostty_surface_free(surface)
|
||||
}
|
||||
}
|
||||
|
||||
func focusDidChange(_ focused: Bool) {
|
||||
|
@@ -1837,12 +1837,10 @@ pub const CAPI = struct {
|
||||
return false;
|
||||
};
|
||||
|
||||
_ = ptr.core_surface.performBindingAction(action) catch |err| {
|
||||
return ptr.core_surface.performBindingAction(action) catch |err| {
|
||||
log.err("error performing binding action action={} err={}", .{ action, err });
|
||||
return false;
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Complete a clipboard read request started via the read callback.
|
||||
|
Reference in New Issue
Block a user