macOS: update AppIcon encoding

- make `ColorizedGhosttyIcon` codable
- remove deprecated string encoding introduced in tip
This commit is contained in:
Lukas
2026-02-24 09:51:30 +01:00
parent 4b1178e4f6
commit f831f68f1a
3 changed files with 44 additions and 77 deletions

View File

@@ -13,9 +13,9 @@ enum AppIcon: Equatable, Codable {
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)
case custom(_ iconFile: Data)
case customStyle(_ icon: ColorizedGhosttyIcon)
#if !DOCK_TILE_PLUGIN
init?(config: Ghostty.Config) {
switch config.macosIcon {
@@ -39,7 +39,7 @@ enum AppIcon: Equatable, Codable {
self = .xray
case .custom:
if let data = try? Data(contentsOf: URL(filePath: config.macosCustomIcon, relativeTo: nil)) {
self = .custom(fileData: data)
self = .custom(data)
} else {
return nil
}
@@ -47,59 +47,16 @@ enum AppIcon: Equatable, Codable {
// Discard saved icon name
// if no valid colours were found
guard
let ghostColor = config.macosIconGhostColor?.hexString,
let screenColors = config.macosIconScreenColor?.compactMap(\.hexString)
let ghostColor = config.macosIconGhostColor,
let screenColors = config.macosIconScreenColor
else {
return nil
}
self = .customStyle(ghostColorHex: ghostColor, screenColorHexes: screenColors, iconFrame: config.macosIconFrame)
self = .customStyle(ColorizedGhosttyIcon(screenColors: screenColors, ghostColor: ghostColor, frame: 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:
@@ -121,24 +78,9 @@ enum AppIcon: Equatable, Codable {
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
return NSImage(data: file)
case let .customStyle(customIcon):
return customIcon.makeImage(in: bundle)
}
}
}

View File

@@ -1,6 +1,33 @@
import Cocoa
struct ColorizedGhosttyIcon {
struct ColorizedGhosttyIcon: Codable, Equatable {
init(screenColors: [NSColor], ghostColor: NSColor, frame: Ghostty.MacOSIconFrame) {
self.screenColors = screenColors
self.ghostColor = ghostColor
self.frame = frame
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let screenColorHexes = try container.decode([String].self, forKey: .screenColors)
let screenColors = screenColorHexes.compactMap(NSColor.init(hex:))
let ghostColorHex = try container.decode(String.self, forKey: .ghostColor)
guard let ghostColor = NSColor(hex: ghostColorHex) else {
throw NSError(domain: "Custom Icon Error", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Failed to decode ghost color from \(ghostColorHex)"
])
}
let frame = try container.decode(Ghostty.MacOSIconFrame.self, forKey: .frame)
self.init(screenColors: screenColors, ghostColor: ghostColor, frame: frame)
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(screenColors.compactMap(\.hexString), forKey: .screenColors)
try container.encode(ghostColor.hexString, forKey: .ghostColor)
try container.encode(frame, forKey: .frame)
}
/// The colors that make up the gradient of the screen.
let screenColors: [NSColor]
@@ -10,6 +37,12 @@ struct ColorizedGhosttyIcon {
/// The frame type to use
let frame: Ghostty.MacOSIconFrame
private enum CodingKeys: String, CodingKey {
case screenColors
case ghostColor
case frame
}
/// Make a custom colorized ghostty icon.
func makeImage(in bundle: Bundle) -> NSImage? {
// All of our layers (not in order)

View File

@@ -11,14 +11,6 @@ extension UserDefaults {
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