Skip to content

Instantly share code, notes, and snippets.

@nwjsmith
Last active August 19, 2025 19:09
Show Gist options
  • Select an option

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

Select an option

Save nwjsmith/7db0be1cbd662c3b13103de436b23368 to your computer and use it in GitHub Desktop.
macOS Dynamic Wallpaper Generator - Create appearance-aware wallpapers from hex colors
{
lib,
swift,
swiftPackages,
}:
swiftPackages.stdenv.mkDerivation {
pname = "wallit";
version = "1.0.0";
src = ./Wallit.swift;
dontUnpack = true;
nativeBuildInputs = [ swift ];
buildPhase = ''
runHook preBuild
cp $src Wallit.swift
swiftc Wallit.swift -o wallit -O
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp wallit $out/bin/
runHook postInstall
'';
meta = with lib; {
description = "macOS dynamic wallpaper generator";
platforms = platforms.darwin;
maintainers = [];
mainProgram = "wallit";
};
}
import Foundation
import CoreGraphics
import ImageIO
func parseHexColor(_ hex: String) -> (r: CGFloat, g: CGFloat, b: CGFloat)? {
let clean = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#"))
guard clean.count == 6, let value = Int(clean, radix: 16) else { return nil }
return (
r: CGFloat((value >> 16) & 0xFF) / 255.0,
g: CGFloat((value >> 8) & 0xFF) / 255.0,
b: CGFloat(value & 0xFF) / 255.0
)
}
func createSolidColorImage(_ hex: String) -> CGImage? {
guard let color = parseHexColor(hex) else { return nil }
let width = 5120, height = 2880
let colorSpace = CGColorSpaceCreateDeviceRGB()
guard let context = CGContext(
data: nil,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: width * 4,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue
) else { return nil }
context.setFillColor(red: color.r, green: color.g, blue: color.b, alpha: 1.0)
context.fill(CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height)))
return context.makeImage()
}
func loadImageFromFile(_ path: String) -> CGImage? {
let url = URL(fileURLWithPath: path)
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil) else { return nil }
return CGImageSourceCreateImageAtIndex(source, 0, nil)
}
func getImage(from input: String) -> CGImage? {
// Try as hex color first
if let image = createSolidColorImage(input) {
return image
}
// Otherwise load as file
return loadImageFromFile(input)
}
func createDynamicWallpaper(light: String, dark: String, output: String) throws {
guard let lightImage = getImage(from: light) else {
throw NSError(domain: "", code: 1, userInfo: [NSLocalizedDescriptionKey: "Cannot load light image/color: \(light)"])
}
guard let darkImage = getImage(from: dark) else {
throw NSError(domain: "", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot load dark image/color: \(dark)"])
}
// Create appearance metadata
let metadata = CGImageMetadataCreateMutable()
CGImageMetadataRegisterNamespaceForPrefix(
metadata,
"http://ns.apple.com/namespace/1.0/" as CFString,
"apple_desktop" as CFString,
nil
)
let appearanceInfo = ["d": 1, "l": 0] // dark: image 1, light: image 0
let plistData = try PropertyListSerialization.data(
fromPropertyList: appearanceInfo,
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)
// Write HEIC file
let outputURL = URL(fileURLWithPath: output)
guard let destination = CGImageDestinationCreateWithURL(
outputURL as CFURL,
"public.heic" as CFString,
2,
nil
) else {
throw NSError(domain: "", code: 3, userInfo: [NSLocalizedDescriptionKey: "Cannot create output file"])
}
CGImageDestinationAddImageAndMetadata(destination, lightImage, metadata, nil)
CGImageDestinationAddImage(destination, darkImage, nil)
guard CGImageDestinationFinalize(destination) else {
throw NSError(domain: "", code: 4, userInfo: [NSLocalizedDescriptionKey: "Cannot write output file"])
}
}
// Main
let args = CommandLine.arguments
if args.contains("-h") || args.contains("--help") {
print("""
Usage: \(args[0]) [light] [dark] [output]
Creates a macOS dynamic wallpaper that switches with appearance.
Arguments:
light Hex color or image path for light mode (default: f0f0f0)
dark Hex color or image path for dark mode (default: 1e1e1e)
output Output filename (default: wallpaper.heic)
Examples:
\(args[0]) # Modus theme colors
\(args[0]) ffffff 000000 # Black and white
\(args[0]) day.jpg night.jpg # From images
\(args[0]) f0f0f0 sunset.png custom.heic
""")
exit(0)
}
let light = args.count > 1 ? args[1] : "f0f0f0"
let dark = args.count > 2 ? args[2] : "1e1e1e"
let output = args.count > 3 ? args[3] : "wallpaper.heic"
do {
try createDynamicWallpaper(light: light, dark: dark, output: output)
} 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