Skip to content

Instantly share code, notes, and snippets.

@nwjsmith
Created August 19, 2025 16:21
Show Gist options
  • Select an option

  • Save nwjsmith/f2ab354250eeed5f869f21317ee42fac to your computer and use it in GitHub Desktop.

Select an option

Save nwjsmith/f2ab354250eeed5f869f21317ee42fac to your computer and use it in GitHub Desktop.
macOS Dynamic Wallpaper Generator - Creates HEIC wallpapers that switch between light/dark modes
#!/usr/bin/swift
import Foundation
import AppKit
import AVFoundation
import ImageIO
struct Color {
let r: CGFloat
let g: CGFloat
let b: CGFloat
init?(hex: String) {
let clean = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#"))
guard clean.count == 6, let value = Int(clean, radix: 16) else { return nil }
self.r = CGFloat((value >> 16) & 0xFF) / 255.0
self.g = CGFloat((value >> 8) & 0xFF) / 255.0
self.b = CGFloat(value & 0xFF) / 255.0
}
}
func createImage(color: Color, size: CGSize = CGSize(width: 5120, height: 2880)) -> CGImage? {
let colorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(
data: nil,
width: Int(size.width),
height: Int(size.height),
bitsPerComponent: 8,
bytesPerRow: Int(size.width) * 4,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue
)
context?.setFillColor(red: color.r, green: color.g, blue: color.b, alpha: 1.0)
context?.fill(CGRect(origin: .zero, size: size))
return context?.makeImage()
}
func createWallpaper(lightHex: String, darkHex: String, outputFile: String) throws {
guard let lightColor = Color(hex: lightHex) else {
throw NSError(domain: "", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid light color: \(lightHex)"])
}
guard let darkColor = Color(hex: darkHex) else {
throw NSError(domain: "", code: 2, userInfo: [NSLocalizedDescriptionKey: "Invalid dark color: \(darkHex)"])
}
guard let lightImage = createImage(color: lightColor) else {
throw NSError(domain: "", code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to create light image"])
}
guard let darkImage = createImage(color: darkColor) else {
throw NSError(domain: "", code: 4, userInfo: [NSLocalizedDescriptionKey: "Failed to create dark image"])
}
// Create metadata for appearance switching
let metadata = CGImageMetadataCreateMutable()
CGImageMetadataRegisterNamespaceForPrefix(
metadata,
"http://ns.apple.com/namespace/1.0/" as CFString,
"apple_desktop" as CFString,
nil
)
let appearanceData = try JSONEncoder().encode([
"ap": ["l": 0, "d": 1] // light index: 0, dark index: 1
])
let plistData = try PropertyListSerialization.data(
fromPropertyList: try JSONSerialization.jsonObject(with: appearanceData),
format: .binary,
options: 0
)
let tag = CGImageMetadataTagCreate(
"http://ns.apple.com/namespace/1.0/" as CFString,
"apple_desktop" as CFString,
"apr" as CFString,
.string,
plistData.base64EncodedString() as CFString
)
CGImageMetadataSetTagWithPath(metadata, nil, "apple_desktop:apr" as CFString, tag!)
// Create HEIC file
let outputData = NSMutableData()
let destination = CGImageDestinationCreateWithData(
outputData,
AVFileType.heic as CFString,
2,
nil
)!
CGImageDestinationAddImageAndMetadata(
destination,
lightImage,
metadata,
[kCGImageDestinationLossyCompressionQuality: 1.0] as CFDictionary
)
CGImageDestinationAddImage(
destination,
darkImage,
[kCGImageDestinationLossyCompressionQuality: 1.0] as CFDictionary
)
guard CGImageDestinationFinalize(destination) else {
throw NSError(domain: "", code: 5, userInfo: [NSLocalizedDescriptionKey: "Failed to create HEIC"])
}
try outputData.write(to: URL(fileURLWithPath: outputFile))
}
func printUsage() {
print("""
Usage: \(CommandLine.arguments[0]) [light_color] [dark_color] [output_file]
Creates a macOS dynamic wallpaper that switches between light and dark colors.
Arguments:
light_color Hex color for light mode (default: f0f0f0)
dark_color Hex color for dark mode (default: 1e1e1e)
output_file Output filename (default: wallpaper.heic)
Examples:
\(CommandLine.arguments[0]) # Modus colors
\(CommandLine.arguments[0]) ffffff 000000 # Black and white
\(CommandLine.arguments[0]) fdf6e3 002b36 solarized.heic # Solarized theme
Colors can be specified with or without '#' prefix.
""")
}
// Main
let args = CommandLine.arguments
if args.count > 1 && (args[1] == "-h" || args[1] == "--help") {
printUsage()
exit(0)
}
let lightHex = args.count > 1 ? args[1] : "f0f0f0" // Modus Operandi bg-dim
let darkHex = args.count > 2 ? args[2] : "1e1e1e" // Modus Vivendi bg-dim
let outputFile = args.count > 3 ? args[3] : "wallpaper.heic"
do {
try createWallpaper(lightHex: lightHex, darkHex: darkHex, outputFile: outputFile)
} catch {
print("Error: \(error.localizedDescription)")
exit(1)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment