moving lots of files, removing unused stuff

This commit is contained in:
Mitchell Hashimoto
2026-02-23 13:14:49 -08:00
committed by Lukas
parent a79557f521
commit 2c28c27ca5
10 changed files with 248 additions and 244 deletions

View File

@@ -97,9 +97,13 @@
8193245D2F24E80800A9ED8F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
App/macOS/AppIcon.swift,
"Features/Colorized Ghostty Icon/ColorizedGhosttyIcon.swift",
Ghostty/SharedPackage.swift,
"Features/Dock Tile Plugin/AppIcon.swift",
"Features/Dock Tile Plugin/DockTilePlugin.swift",
"Features/Dock Tile Plugin/Notification+AppIcon.swift",
"Features/Dock Tile Plugin/UserDefaults+AppIcon.swift",
Ghostty/Ghostty.ConfigTypes.swift,
Ghostty/GhosttyPackageMeta.swift,
Helpers/CrossKit.swift,
"Helpers/Extensions/NSImage+Extension.swift",
"Helpers/Extensions/OSColor+Extension.swift",
@@ -111,7 +115,6 @@
membershipExceptions = (
App/macOS/AppDelegate.swift,
"App/macOS/AppDelegate+Ghostty.swift",
App/macOS/AppIcon.swift,
App/macOS/main.swift,
App/macOS/MainMenu.xib,
Features/About/About.xib,
@@ -139,6 +142,10 @@
"Features/Colorized Ghostty Icon/ColorizedGhosttyIconView.swift",
"Features/Command Palette/CommandPalette.swift",
"Features/Command Palette/TerminalCommandPalette.swift",
"Features/Dock Tile Plugin/AppIcon.swift",
"Features/Dock Tile Plugin/DockTilePlugin.swift",
"Features/Dock Tile Plugin/Notification+AppIcon.swift",
"Features/Dock Tile Plugin/UserDefaults+AppIcon.swift",
"Features/Global Keybinds/GlobalEventTap.swift",
Features/QuickTerminal/QuickTerminal.xib,
Features/QuickTerminal/QuickTerminalController.swift,
@@ -238,6 +245,7 @@
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
App/iOS/iOSApp.swift,
"Features/Dock Tile Plugin/DockTilePlugin.swift",
"Ghostty/Surface View/SurfaceView_UIKit.swift",
);
target = A5B30530299BEAAA0047F10C /* Ghostty */;
@@ -246,7 +254,6 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */
810ACCA02E9D3302004F8F92 /* GhosttyUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = GhosttyUITests; sourceTree = "<group>"; };
8193245A2F24E7D000A9ED8F /* DockTilePlugIn */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = DockTilePlugIn; sourceTree = "<group>"; };
81F82BC72E82815D001EDFA7 /* Sources */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (81F82CB12E8281F9001EDFA7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 81F82CB02E8281F5001EDFA7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 8193245D2F24E80800A9ED8F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Sources; sourceTree = "<group>"; };
A54F45F42E1F047A0046BD5C /* Tests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Tests; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
@@ -321,7 +328,6 @@
A51BFC282B30F26D00E92F16 /* GhosttyDebug.entitlements */,
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */,
81F82BC72E82815D001EDFA7 /* Sources */,
8193245A2F24E7D000A9ED8F /* DockTilePlugIn */,
A54F45F42E1F047A0046BD5C /* Tests */,
810ACCA02E9D3302004F8F92 /* GhosttyUITests */,
A5D495A3299BECBA00DD1313 /* Frameworks */,
@@ -389,9 +395,6 @@
);
dependencies = (
);
fileSystemSynchronizedGroups = (
8193245A2F24E7D000A9ED8F /* DockTilePlugIn */,
);
name = DockTilePlugin;
packageProductDependencies = (
);
@@ -845,13 +848,14 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Ghostty Dock Tile Plugin";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSPrincipalClass = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.DockTilePlugin;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = "com.mitchellh.ghostty-dock-tile";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
@@ -873,13 +877,14 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Ghostty Dock Tile Plugin";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSPrincipalClass = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.DockTilePlugin;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = "com.mitchellh.ghostty-dock-tile";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
@@ -901,13 +906,14 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Ghostty Dock Tile Plugin";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSPrincipalClass = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.DockTilePlugin;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = "com.mitchellh.ghostty-dock-tile";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;

View File

@@ -928,6 +928,7 @@ class AppDelegate: NSObject,
} else {
GlobalEventTap.shared.disable()
}
updateAppIcon(from: config)
}
@@ -941,9 +942,9 @@ class AppDelegate: NSObject,
// clean it up here to trigger a correct update of the current config.
UserDefaults.standard.removeObject(forKey: "CustomGhosttyIcon")
DispatchQueue.global().async {
UserDefaults.standard.appIcon = Ghostty.CustomAppIcon(config: config)
UserDefaults.standard.appIcon = AppIcon(config: config)
DistributedNotificationCenter.default()
.postNotificationName(Ghostty.Notification.ghosttyIconDidChange, object: nil, userInfo: nil, deliverImmediately: true)
.postNotificationName(.ghosttyIconDidChange, object: nil, userInfo: nil, deliverImmediately: true)
}
}

View File

@@ -1,187 +0,0 @@
import AppKit
import System
#if !DOCK_TILE_PLUGIN
import GhosttyKit
#endif
extension Ghostty {
/// For DockTilePlugin to generate icon
/// without relying on ``Ghostty/Ghostty/Config``
enum CustomAppIcon: Equatable, Codable {
case official
case blueprint
case chalkboard
case glass
case holographic
case microchip
case paper
case retro
case xray
/// Save image data to avoid sandboxing issues
case custom(fileData: Data)
case customStyle(ghostColorHex: String, screenColorHexes: [String], iconFrame: Ghostty.MacOSIconFrame)
/// Restore the icon from previously saved values
init?(string: String) {
switch string {
case MacOSIcon.official.rawValue:
self = .official
case MacOSIcon.blueprint.rawValue:
self = .blueprint
case MacOSIcon.chalkboard.rawValue:
self = .chalkboard
case MacOSIcon.glass.rawValue:
self = .glass
case MacOSIcon.holographic.rawValue:
self = .holographic
case MacOSIcon.microchip.rawValue:
self = .microchip
case MacOSIcon.paper.rawValue:
self = .paper
case MacOSIcon.retro.rawValue:
self = .retro
case MacOSIcon.xray.rawValue:
self = .xray
default:
/*
let colorStrings = ([ghostColor] + screenColors).compactMap(\.hexString)
appIconName = (colorStrings + [config.macosIconFrame.rawValue])
.joined(separator: "_")
*/
var parts = string.split(separator: "_").map(String.init)
if
let _ = parts.first.flatMap(NSColor.init(hex:)),
let frame = parts.last.flatMap(Ghostty.MacOSIconFrame.init(rawValue:))
{
let ghostC = parts.removeFirst()
_ = parts.removeLast()
self = .customStyle(
ghostColorHex: ghostC,
screenColorHexes: parts,
iconFrame: frame
)
} else {
// Due to sandboxing with `com.apple.dock.external.extra.arm64`,
// we cant restore custom icon file automatically.
// The user must open the app to update it.
return nil
}
}
}
func image(in bundle: Bundle) -> NSImage? {
switch self {
case .official:
return nil
case .blueprint:
return bundle.image(forResource: "BlueprintImage")!
case .chalkboard:
return bundle.image(forResource: "ChalkboardImage")!
case .glass:
return bundle.image(forResource: "GlassImage")!
case .holographic:
return bundle.image(forResource: "HolographicImage")!
case .microchip:
return bundle.image(forResource: "MicrochipImage")!
case .paper:
return bundle.image(forResource: "PaperImage")!
case .retro:
return bundle.image(forResource: "RetroImage")!
case .xray:
return bundle.image(forResource: "XrayImage")!
case let .custom(file):
if let userIcon = NSImage(data: file) {
return userIcon
} else {
return nil
}
case let .customStyle(ghostColorHex, screenColorHexes, macosIconFrame):
let screenColors = screenColorHexes.compactMap(NSColor.init(hex:))
guard
let ghostColor = NSColor(hex: ghostColorHex),
let icon = ColorizedGhosttyIcon(
screenColors: screenColors,
ghostColor: ghostColor,
frame: macosIconFrame
).makeImage(in: bundle)
else {
return nil
}
return icon
}
}
}
}
#if !DOCK_TILE_PLUGIN
extension Ghostty.CustomAppIcon {
init?(config: Ghostty.Config) {
switch config.macosIcon {
case .official:
return nil
case .blueprint:
self = .blueprint
case .chalkboard:
self = .chalkboard
case .glass:
self = .glass
case .holographic:
self = .holographic
case .microchip:
self = .microchip
case .paper:
self = .paper
case .retro:
self = .retro
case .xray:
self = .xray
case .custom:
if let data = try? Data(contentsOf: URL(filePath: config.macosCustomIcon, relativeTo: nil)) {
self = .custom(fileData: data)
} else {
return nil
}
case .customStyle:
// Discard saved icon name
// if no valid colours were found
guard
let ghostColor = config.macosIconGhostColor?.hexString,
let screenColors = config.macosIconScreenColor?.compactMap(\.hexString)
else {
return nil
}
self = .customStyle(ghostColorHex: ghostColor, screenColorHexes: screenColors, iconFrame: config.macosIconFrame)
}
}
}
#endif
extension UserDefaults {
var appIcon: Ghostty.CustomAppIcon? {
get {
defer {
removeObject(forKey: "CustomGhosttyIcon")
}
if let previous = string(forKey: "CustomGhosttyIcon"), let newIcon = Ghostty.CustomAppIcon(string: previous) {
// update new storage once
self.appIcon = newIcon
return newIcon
}
guard let data = data(forKey: "NewCustomGhosttyIcon") else {
return nil
}
return try? JSONDecoder().decode(Ghostty.CustomAppIcon.self, from: data)
}
set {
guard let newData = try? JSONEncoder().encode(newValue) else {
return
}
set(newData, forKey: "NewCustomGhosttyIcon")
}
}
}
extension Ghostty.Notification {
static let ghosttyIconDidChange = Notification.Name("com.mitchellh.ghostty.iconDidChange")
}

View File

@@ -0,0 +1,144 @@
import AppKit
import System
/// The icon style for the Ghostty App.
enum AppIcon: Equatable, Codable {
case official
case blueprint
case chalkboard
case glass
case holographic
case microchip
case paper
case retro
case xray
/// Save full image data to avoid sandboxing issues
case custom(fileData: Data)
case customStyle(ghostColorHex: String, screenColorHexes: [String], iconFrame: Ghostty.MacOSIconFrame)
#if !DOCK_TILE_PLUGIN
init?(config: Ghostty.Config) {
switch config.macosIcon {
case .official:
return nil
case .blueprint:
self = .blueprint
case .chalkboard:
self = .chalkboard
case .glass:
self = .glass
case .holographic:
self = .holographic
case .microchip:
self = .microchip
case .paper:
self = .paper
case .retro:
self = .retro
case .xray:
self = .xray
case .custom:
if let data = try? Data(contentsOf: URL(filePath: config.macosCustomIcon, relativeTo: nil)) {
self = .custom(fileData: data)
} else {
return nil
}
case .customStyle:
// Discard saved icon name
// if no valid colours were found
guard
let ghostColor = config.macosIconGhostColor?.hexString,
let screenColors = config.macosIconScreenColor?.compactMap(\.hexString)
else {
return nil
}
self = .customStyle(ghostColorHex: ghostColor, screenColorHexes: screenColors, iconFrame: config.macosIconFrame)
}
}
#endif
/// Restore the icon from previously saved values
init?(string: String) {
switch string {
case Ghostty.MacOSIcon.official.rawValue:
self = .official
case Ghostty.MacOSIcon.blueprint.rawValue:
self = .blueprint
case Ghostty.MacOSIcon.chalkboard.rawValue:
self = .chalkboard
case Ghostty.MacOSIcon.glass.rawValue:
self = .glass
case Ghostty.MacOSIcon.holographic.rawValue:
self = .holographic
case Ghostty.MacOSIcon.microchip.rawValue:
self = .microchip
case Ghostty.MacOSIcon.paper.rawValue:
self = .paper
case Ghostty.MacOSIcon.retro.rawValue:
self = .retro
case Ghostty.MacOSIcon.xray.rawValue:
self = .xray
default:
var parts = string.split(separator: "_").map(String.init)
if
let _ = parts.first.flatMap(NSColor.init(hex:)),
let frame = parts.last.flatMap(Ghostty.MacOSIconFrame.init(rawValue:))
{
let ghostC = parts.removeFirst()
_ = parts.removeLast()
self = .customStyle(
ghostColorHex: ghostC,
screenColorHexes: parts,
iconFrame: frame
)
} else {
// Due to sandboxing with `com.apple.dock.external.extra.arm64`,
// we cant restore custom icon file automatically.
// The user must open the app to update it.
return nil
}
}
}
func image(in bundle: Bundle) -> NSImage? {
switch self {
case .official:
return nil
case .blueprint:
return bundle.image(forResource: "BlueprintImage")!
case .chalkboard:
return bundle.image(forResource: "ChalkboardImage")!
case .glass:
return bundle.image(forResource: "GlassImage")!
case .holographic:
return bundle.image(forResource: "HolographicImage")!
case .microchip:
return bundle.image(forResource: "MicrochipImage")!
case .paper:
return bundle.image(forResource: "PaperImage")!
case .retro:
return bundle.image(forResource: "RetroImage")!
case .xray:
return bundle.image(forResource: "XrayImage")!
case let .custom(file):
if let userIcon = NSImage(data: file) {
return userIcon
} else {
return nil
}
case let .customStyle(ghostColorHex, screenColorHexes, macosIconFrame):
let screenColors = screenColorHexes.compactMap(NSColor.init(hex:))
guard
let ghostColor = NSColor(hex: ghostColorHex),
let icon = ColorizedGhosttyIcon(
screenColors: screenColors,
ghostColor: ghostColor,
frame: macosIconFrame
).makeImage(in: bundle)
else {
return nil
}
return icon
}
}
}

View File

@@ -1,10 +1,14 @@
import AppKit
/// This class lives as long as the app is in the Dock.
/// If the user pins the app to the Dock, it will not be deallocated.
/// Be careful when storing state in this class.
class DockTilePlugin: NSObject, NSDockTilePlugIn {
// WARNING: An instance of this class is alive as long as Ghostty's icon is
// in the doc (running or not!), so keep any state and processing to a
// minimum to respect resource usage.
private let pluginBundle = Bundle(for: DockTilePlugin.self)
// Separate defaults based on debug vs release builds so we can test icons
// without messing up releases.
#if DEBUG
private let ghosttyUserDefaults = UserDefaults(suiteName: "com.mitchellh.ghostty.debug")
#else
@@ -21,7 +25,7 @@ class DockTilePlugin: NSObject, NSDockTilePlugIn {
// Try to restore the previous icon on launch.
iconDidChange(ghosttyUserDefaults.appIcon, dockTile: dockTile)
iconChangeObserver = DistributedNotificationCenter.default().publisher(for: Ghostty.Notification.ghosttyIconDidChange)
iconChangeObserver = DistributedNotificationCenter.default().publisher(for: .ghosttyIconDidChange)
.map { [weak self] _ in
self?.ghosttyUserDefaults?.appIcon
}
@@ -41,7 +45,7 @@ class DockTilePlugin: NSObject, NSDockTilePlugIn {
return url.path
}
func iconDidChange(_ newIcon: Ghostty.CustomAppIcon?, dockTile: NSDockTile) {
func iconDidChange(_ newIcon: AppIcon?, dockTile: NSDockTile) {
guard let appIcon = newIcon?.image(in: pluginBundle) else {
resetIcon(dockTile: dockTile)
return
@@ -80,6 +84,7 @@ class DockTilePlugin: NSObject, NSDockTilePlugIn {
appIcon = pluginBundle.image(forResource: "AppIconImage")!
NSWorkspace.shared.setIcon(appIcon, forFile: appBundlePath)
}
NSWorkspace.shared.noteFileSystemChanged(appBundlePath)
dockTile.setIcon(appIcon)
}
@@ -99,17 +104,3 @@ private extension NSDockTile {
}
extension NSDockTile: @unchecked @retroactive Sendable {}
#if DEBUG
private extension NSAlert {
static func notify(_ message: String, image: NSImage?) {
DispatchQueue.main.async {
let alert = NSAlert()
alert.messageText = message
alert.icon = image
_ = alert.runModal()
}
}
}
#endif

View File

@@ -0,0 +1,5 @@
import AppKit
extension Notification.Name {
static let ghosttyIconDidChange = Notification.Name("com.mitchellh.ghostty.iconDidChange")
}

View File

@@ -0,0 +1,37 @@
import AppKit
extension UserDefaults {
private static let customIconKeyOld = "CustomGhosttyIcon"
private static let customIconKeyNew = "CustomGhosttyIcon2"
var appIcon: AppIcon? {
get {
// Always remove our old pre-docktileplugin values.
defer {
removeObject(forKey: Self.customIconKeyOld)
}
// If we have an old, pre-docktileplugin value, then we parse the
// the old value (try) and set it.
if let previous = string(forKey: Self.customIconKeyOld), let newIcon = AppIcon(string: previous) {
// update new storage once
self.appIcon = newIcon
return newIcon
}
// Check if we have the new key for our dock tile plugin format.
guard let data = data(forKey: Self.customIconKeyNew) else {
return nil
}
return try? JSONDecoder().decode(AppIcon.self, from: data)
}
set {
guard let newData = try? JSONEncoder().encode(newValue) else {
return
}
set(newData, forKey: Self.customIconKeyNew)
}
}
}

View File

@@ -1,22 +1,5 @@
import Foundation
import os
enum Ghostty {
// The primary logger used by the GhosttyKit libraries.
static let logger = Logger(
subsystem: Bundle.main.bundleIdentifier!,
category: "ghostty"
)
// All the notifications that will be emitted will be put here.
struct Notification {}
// The user notification category identifier
static let userNotificationCategory = "com.mitchellh.ghostty.userNotification"
// The user notification "Show" action
static let userNotificationActionShow = "com.mitchellh.ghostty.userNotification.Show"
}
// This file contains the configuration types for Ghostty so that alternate targets
// can get typed information without depending on all the dependencies of GhosttyKit.
extension Ghostty {
/// macos-icon

View File

@@ -11,6 +11,14 @@ extension ghostty_command_s: @unchecked @retroactive Sendable {}
/// may be unsafe but the value itself is safe to send across threads.
extension ghostty_surface_t: @unchecked @retroactive Sendable {}
extension Ghostty {
// The user notification category identifier
static let userNotificationCategory = "com.mitchellh.ghostty.userNotification"
// The user notification "Show" action
static let userNotificationActionShow = "com.mitchellh.ghostty.userNotification.Show"
}
// MARK: Build Info
extension Ghostty {

View File

@@ -0,0 +1,16 @@
import Foundation
import os
// This defines the minimal information required so all other files can do
// `extension Ghostty` to add more to it. This purposely has minimal
// dependencies so things like our dock tile plugin can use it.
enum Ghostty {
// The primary logger used by the GhosttyKit libraries.
static let logger = Logger(
subsystem: Bundle.main.bundleIdentifier!,
category: "ghostty"
)
// All the notifications that will be emitted will be put here.
struct Notification {}
}