mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: initial window save/restore is working for frames only
This commit is contained in:
@ -46,6 +46,7 @@
|
|||||||
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDB29B8009000646FDA /* SplitView.swift */; };
|
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDB29B8009000646FDA /* SplitView.swift */; };
|
||||||
A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */; };
|
A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */; };
|
||||||
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; };
|
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; };
|
||||||
|
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */; };
|
||||||
A5E112932AF73E6E00C6E0C2 /* ClipboardConfirmation.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5E112922AF73E6E00C6E0C2 /* ClipboardConfirmation.xib */; };
|
A5E112932AF73E6E00C6E0C2 /* ClipboardConfirmation.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5E112922AF73E6E00C6E0C2 /* ClipboardConfirmation.xib */; };
|
||||||
A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */; };
|
A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */; };
|
||||||
A5E112972AF7401B00C6E0C2 /* ClipboardConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112962AF7401B00C6E0C2 /* ClipboardConfirmationView.swift */; };
|
A5E112972AF7401B00C6E0C2 /* ClipboardConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112962AF7401B00C6E0C2 /* ClipboardConfirmationView.swift */; };
|
||||||
@ -95,6 +96,7 @@
|
|||||||
A5CEAFDB29B8009000646FDA /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = "<group>"; };
|
A5CEAFDB29B8009000646FDA /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = "<group>"; };
|
||||||
A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.Divider.swift; sourceTree = "<group>"; };
|
A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.Divider.swift; sourceTree = "<group>"; };
|
||||||
A5CEAFFE29C2410700646FDA /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
|
A5CEAFFE29C2410700646FDA /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
|
||||||
|
A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalRestorable.swift; sourceTree = "<group>"; };
|
||||||
A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
|
A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
|
||||||
A5E112922AF73E6E00C6E0C2 /* ClipboardConfirmation.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ClipboardConfirmation.xib; sourceTree = "<group>"; };
|
A5E112922AF73E6E00C6E0C2 /* ClipboardConfirmation.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ClipboardConfirmation.xib; sourceTree = "<group>"; };
|
||||||
A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardConfirmationController.swift; sourceTree = "<group>"; };
|
A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardConfirmationController.swift; sourceTree = "<group>"; };
|
||||||
@ -213,6 +215,7 @@
|
|||||||
A59630992AEE1C6400D64628 /* Terminal.xib */,
|
A59630992AEE1C6400D64628 /* Terminal.xib */,
|
||||||
A596309F2AEF6AEB00D64628 /* TerminalManager.swift */,
|
A596309F2AEF6AEB00D64628 /* TerminalManager.swift */,
|
||||||
A596309B2AEE1C9E00D64628 /* TerminalController.swift */,
|
A596309B2AEE1C9E00D64628 /* TerminalController.swift */,
|
||||||
|
A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */,
|
||||||
A596309D2AEE1D6C00D64628 /* TerminalView.swift */,
|
A596309D2AEE1D6C00D64628 /* TerminalView.swift */,
|
||||||
A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */,
|
A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */,
|
||||||
A535B9D9299C569B0017E2E4 /* ErrorView.swift */,
|
A535B9D9299C569B0017E2E4 /* ErrorView.swift */,
|
||||||
@ -366,6 +369,7 @@
|
|||||||
files = (
|
files = (
|
||||||
A59630A42AF059BB00D64628 /* Ghostty.SplitNode.swift in Sources */,
|
A59630A42AF059BB00D64628 /* Ghostty.SplitNode.swift in Sources */,
|
||||||
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
|
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
|
||||||
|
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
|
||||||
A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */,
|
A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */,
|
||||||
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
||||||
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */,
|
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */,
|
||||||
|
@ -97,6 +97,12 @@ class AppDelegate: NSObject,
|
|||||||
"ApplePressAndHoldEnabled": false,
|
"ApplePressAndHoldEnabled": false,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// TODO: make this configurable via ghostty
|
||||||
|
// reset to system defaults
|
||||||
|
//UserDefaults.standard.removeObject(forKey: "NSQuitAlwaysKeepsWindows")
|
||||||
|
// force state save
|
||||||
|
UserDefaults.standard.setValue(true, forKey: "NSQuitAlwaysKeepsWindows")
|
||||||
|
|
||||||
// Hook up updater menu
|
// Hook up updater menu
|
||||||
menuCheckForUpdates?.target = updaterController
|
menuCheckForUpdates?.target = updaterController
|
||||||
menuCheckForUpdates?.action = #selector(SPUStandardUpdaterController.checkForUpdates(_:))
|
menuCheckForUpdates?.action = #selector(SPUStandardUpdaterController.checkForUpdates(_:))
|
||||||
@ -294,6 +300,21 @@ class AppDelegate: NSObject,
|
|||||||
return terminalManager.focusedSurface?.surface
|
return terminalManager.focusedSurface?.surface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - Restorable State
|
||||||
|
|
||||||
|
/// We support NSSecureCoding for restorable state. Required as of macOS Sonoma (14) but a good idea anyways.
|
||||||
|
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ app: NSApplication, willEncodeRestorableState coder: NSCoder) {
|
||||||
|
Self.logger.debug("application will save window state")
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ app: NSApplication, didDecodeRestorableState coder: NSCoder) {
|
||||||
|
Self.logger.debug("application will restore window state")
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - UNUserNotificationCenterDelegate
|
//MARK: - UNUserNotificationCenterDelegate
|
||||||
|
|
||||||
func userNotificationCenter(
|
func userNotificationCenter(
|
||||||
|
@ -148,6 +148,11 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
|||||||
override func windowDidLoad() {
|
override func windowDidLoad() {
|
||||||
guard let window = window else { return }
|
guard let window = window else { return }
|
||||||
|
|
||||||
|
// Setting all three of these is required for restoration to work.
|
||||||
|
window.isRestorable = true
|
||||||
|
window.restorationClass = TerminalWindowRestoration.self
|
||||||
|
window.identifier = .init(String(describing: TerminalWindowRestoration.self))
|
||||||
|
|
||||||
// If window decorations are disabled, remove our title
|
// If window decorations are disabled, remove our title
|
||||||
if (!ghostty.windowDecorations) { window.styleMask.remove(.titled) }
|
if (!ghostty.windowDecorations) { window.styleMask.remove(.titled) }
|
||||||
|
|
||||||
@ -250,6 +255,16 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
|||||||
self.relabelTabs()
|
self.relabelTabs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func window(_ window: NSWindow, willEncodeRestorableState state: NSCoder) {
|
||||||
|
let data = TerminalRestorableState()
|
||||||
|
data.encode(with: state)
|
||||||
|
AppDelegate.logger.warning("state window del encode")
|
||||||
|
}
|
||||||
|
|
||||||
|
func window(_ window: NSWindow, didDecodeRestorableState state: NSCoder) {
|
||||||
|
AppDelegate.logger.warning("state window del restore")
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - First Responder
|
//MARK: - First Responder
|
||||||
|
|
||||||
@IBAction func newWindow(_ sender: Any?) {
|
@IBAction func newWindow(_ sender: Any?) {
|
||||||
|
@ -139,7 +139,7 @@ class TerminalManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a window controller, adds it to our managed list, and returns it.
|
/// Creates a window controller, adds it to our managed list, and returns it.
|
||||||
private func createWindow(withBaseConfig base: Ghostty.SurfaceConfiguration?) -> TerminalController {
|
func createWindow(withBaseConfig base: Ghostty.SurfaceConfiguration?) -> TerminalController {
|
||||||
// Initialize our controller to load the window
|
// Initialize our controller to load the window
|
||||||
let c = TerminalController(ghostty, withBaseConfig: base)
|
let c = TerminalController(ghostty, withBaseConfig: base)
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ class TerminalManager {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
private func removeWindow(_ controller: TerminalController) {
|
func removeWindow(_ controller: TerminalController) {
|
||||||
// Remove it from our managed set
|
// Remove it from our managed set
|
||||||
guard let idx = self.windows.firstIndex(where: { $0.controller == controller }) else { return }
|
guard let idx = self.windows.firstIndex(where: { $0.controller == controller }) else { return }
|
||||||
let w = self.windows[idx]
|
let w = self.windows[idx]
|
||||||
|
77
macos/Sources/Features/Terminal/TerminalRestorable.swift
Normal file
77
macos/Sources/Features/Terminal/TerminalRestorable.swift
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import Cocoa
|
||||||
|
|
||||||
|
/// The state stored for terminal window restoration.
|
||||||
|
class TerminalRestorableState: NSObject, NSSecureCoding {
|
||||||
|
public static var supportsSecureCoding = true
|
||||||
|
|
||||||
|
static let coderKey = "state"
|
||||||
|
static let versionKey = "version"
|
||||||
|
static let version: Int = 1
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
// If the version doesn't match then we can't decode. In the future we can perform
|
||||||
|
// version upgrading or something but for now we only have one version so we
|
||||||
|
// don't bother.
|
||||||
|
guard aDecoder.decodeInteger(forKey: Self.versionKey) == Self.version else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(with coder: NSCoder) {
|
||||||
|
coder.encode(Self.version, forKey: Self.versionKey)
|
||||||
|
coder.encode(self, forKey: Self.coderKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The NSWindowRestoration implementation that is called when a terminal window needs to be restored.
|
||||||
|
/// The encoding of a terminal window is handled elsewhere (usually NSWindowDelegate).
|
||||||
|
class TerminalWindowRestoration: NSObject, NSWindowRestoration {
|
||||||
|
enum RestoreError: Error {
|
||||||
|
case delegateInvalid
|
||||||
|
case identifierUnknown
|
||||||
|
case stateDecodeFailed
|
||||||
|
case windowDidNotLoad
|
||||||
|
}
|
||||||
|
|
||||||
|
static func restoreWindow(
|
||||||
|
withIdentifier identifier: NSUserInterfaceItemIdentifier,
|
||||||
|
state: NSCoder,
|
||||||
|
completionHandler: @escaping (NSWindow?, Error?) -> Void
|
||||||
|
) {
|
||||||
|
// Verify the identifier is what we expect
|
||||||
|
guard identifier == .init(String(describing: Self.self)) else {
|
||||||
|
completionHandler(nil, RestoreError.identifierUnknown)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the state. If we can't decode the state, then we can't restore.
|
||||||
|
guard let state = TerminalRestorableState(coder: state) else {
|
||||||
|
completionHandler(nil, RestoreError.stateDecodeFailed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The app delegate is definitely setup by now. If it isn't our AppDelegate
|
||||||
|
// then something is royally fucked up but protect against it anyhow.
|
||||||
|
guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else {
|
||||||
|
completionHandler(nil, RestoreError.delegateInvalid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The window creation has to go through our terminalManager so that it
|
||||||
|
// can be found for events from libghostty. This uses the low-level
|
||||||
|
// createWindow so that AppKit can place the window wherever it should
|
||||||
|
// be.
|
||||||
|
let c = appDelegate.terminalManager.createWindow(withBaseConfig: nil)
|
||||||
|
guard let window = c.window else {
|
||||||
|
completionHandler(nil, RestoreError.windowDidNotLoad)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHandler(window, nil)
|
||||||
|
AppDelegate.logger.warning("state RESTORE")
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user