mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-27 09:43:55 +00:00
moving lots of files, removing unused stuff
This commit is contained in:
committed by
Lukas
parent
a79557f521
commit
2c28c27ca5
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 can’t 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")
|
||||
}
|
||||
144
macos/Sources/Features/Dock Tile Plugin/AppIcon.swift
Normal file
144
macos/Sources/Features/Dock Tile Plugin/AppIcon.swift
Normal 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 can’t 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
|
||||
}
|
||||
}
|
||||
}
|
||||
106
macos/Sources/Features/Dock Tile Plugin/DockTilePlugin.swift
Normal file
106
macos/Sources/Features/Dock Tile Plugin/DockTilePlugin.swift
Normal file
@@ -0,0 +1,106 @@
|
||||
import AppKit
|
||||
|
||||
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
|
||||
private let ghosttyUserDefaults = UserDefaults(suiteName: "com.mitchellh.ghostty")
|
||||
#endif
|
||||
|
||||
private var iconChangeObserver: Any?
|
||||
|
||||
func setDockTile(_ dockTile: NSDockTile?) {
|
||||
guard let dockTile, let ghosttyUserDefaults else {
|
||||
iconChangeObserver = nil
|
||||
return
|
||||
}
|
||||
// Try to restore the previous icon on launch.
|
||||
iconDidChange(ghosttyUserDefaults.appIcon, dockTile: dockTile)
|
||||
|
||||
iconChangeObserver = DistributedNotificationCenter.default().publisher(for: .ghosttyIconDidChange)
|
||||
.map { [weak self] _ in
|
||||
self?.ghosttyUserDefaults?.appIcon
|
||||
}
|
||||
.receive(on: DispatchQueue.global())
|
||||
.sink { [weak self] newIcon in
|
||||
guard let self else { return }
|
||||
iconDidChange(newIcon, dockTile: dockTile)
|
||||
}
|
||||
}
|
||||
|
||||
func getGhosttyAppPath() -> String {
|
||||
var url = pluginBundle.bundleURL
|
||||
// Remove "/Contents/PlugIns/DockTilePlugIn.bundle" from the bundle URL to reach Ghostty.app.
|
||||
while url.lastPathComponent != "Ghostty.app", !url.lastPathComponent.isEmpty {
|
||||
url.deleteLastPathComponent()
|
||||
}
|
||||
return url.path
|
||||
}
|
||||
|
||||
func iconDidChange(_ newIcon: AppIcon?, dockTile: NSDockTile) {
|
||||
guard let appIcon = newIcon?.image(in: pluginBundle) else {
|
||||
resetIcon(dockTile: dockTile)
|
||||
return
|
||||
}
|
||||
let appBundlePath = getGhosttyAppPath()
|
||||
NSWorkspace.shared.setIcon(appIcon, forFile: appBundlePath)
|
||||
NSWorkspace.shared.noteFileSystemChanged(appBundlePath)
|
||||
|
||||
dockTile.setIcon(appIcon)
|
||||
}
|
||||
|
||||
func resetIcon(dockTile: NSDockTile) {
|
||||
let appBundlePath = getGhosttyAppPath()
|
||||
let appIcon: NSImage
|
||||
if #available(macOS 26.0, *) {
|
||||
// Reset to the default (glassy) icon.
|
||||
NSWorkspace.shared.setIcon(nil, forFile: appBundlePath)
|
||||
#if DEBUG
|
||||
// Use the `Blueprint` icon to
|
||||
// distinguish Debug from Release builds.
|
||||
appIcon = pluginBundle.image(forResource: "BlueprintImage")!
|
||||
#else
|
||||
// Get the composed icon from the app bundle.
|
||||
if let iconRep = NSWorkspace.shared.icon(forFile: appBundlePath).bestRepresentation(for: CGRect(origin: .zero, size: dockTile.size), context: nil, hints: nil) {
|
||||
appIcon = NSImage(size: dockTile.size)
|
||||
appIcon.addRepresentation(iconRep)
|
||||
} else {
|
||||
// If something unexpected happens on macOS 26,
|
||||
// fall back to a bundled icon.
|
||||
appIcon = pluginBundle.image(forResource: "AppIconImage")!
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// Use the bundled icon to keep the corner radius
|
||||
// consistent with other apps.
|
||||
appIcon = pluginBundle.image(forResource: "AppIconImage")!
|
||||
NSWorkspace.shared.setIcon(appIcon, forFile: appBundlePath)
|
||||
}
|
||||
|
||||
NSWorkspace.shared.noteFileSystemChanged(appBundlePath)
|
||||
dockTile.setIcon(appIcon)
|
||||
}
|
||||
}
|
||||
|
||||
private extension NSDockTile {
|
||||
func setIcon(_ newIcon: NSImage) {
|
||||
// Update the Dock tile on the main thread.
|
||||
DispatchQueue.main.async {
|
||||
let iconView = NSImageView(frame: CGRect(origin: .zero, size: self.size))
|
||||
iconView.wantsLayer = true
|
||||
iconView.image = newIcon
|
||||
self.contentView = iconView
|
||||
self.display()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSDockTile: @unchecked @retroactive Sendable {}
|
||||
@@ -0,0 +1,5 @@
|
||||
import AppKit
|
||||
|
||||
extension Notification.Name {
|
||||
static let ghosttyIconDidChange = Notification.Name("com.mitchellh.ghostty.iconDidChange")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
16
macos/Sources/Ghostty/GhosttyPackageMeta.swift
Normal file
16
macos/Sources/Ghostty/GhosttyPackageMeta.swift
Normal 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 {}
|
||||
}
|
||||
Reference in New Issue
Block a user