swiftxcodemacoscocoaappkit

How to get macOS wallpaper image URL which respects the current color scheme?


I am looking to get the URL of the current wallpaper image on macOS, which also takes the current color scheme into consideration when using wallpapers with a different appearance based on the active color scheme.

To get the URL of the current desktop wallpaper image, I am using the following method:

NSWorkspace.shared.desktopImageURL(for: screen)

This returns the URL correctly, however, it only returns the URL of the light mode variant of the current wallpaper. I would like to get the URL of the variant which matches the currently active color scheme.

Is that possible?


Solution

  • Both variants are stored in the same URL. The HEIC file can store many different images. Try opening the image with Preview.app :)

    To get the light and dark images, we need to read the metadata of the HEIC file, which will tell us the respective indices of the light mode image and the dark mode image. Note that there could be other variants of the image, depending on sunrise/sunset times and other factors. I will disregard these in this answer.

    Thanks to the reverse engineer here, I have written this function:

    func lightDarkImages(url: URL) throws -> (light: CGImage, dark: CGImage) {
        guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) else {
            throw Errors.notFound
        }
        guard let metadata = CGImageSourceCopyMetadataAtIndex(imageSource, 0, nil) else {
            throw Errors.noMetadata
        }
        let plistDecoder = PropertyListDecoder()
        if let solarTag = CGImageMetadataCopyTagWithPath(metadata, nil, "apple_desktop:solar" as CFString),
            let base64String = CGImageMetadataTagCopyValue(solarTag) as? String,
            let data = Data(base64Encoded: base64String) {
            let indices = try plistDecoder.decode(SolarMetadata.self, from: data)
            return (
                light: CGImageSourceCreateImageAtIndex(imageSource, indices.ap.l, nil)!,
                dark: CGImageSourceCreateImageAtIndex(imageSource, indices.ap.d, nil)!
            )
        } else if let aprTag = CGImageMetadataCopyTagWithPath(metadata, nil, "apple_desktop:apr" as CFString),
                  let base64String = CGImageMetadataTagCopyValue(aprTag) as? String,
                  let data = Data(base64Encoded: base64String) {
            let indices = try plistDecoder.decode(LightDarkIndex.self, from: data)
            return (
                light: CGImageSourceCreateImageAtIndex(imageSource, indices.l, nil)!,
                dark: CGImageSourceCreateImageAtIndex(imageSource, indices.d, nil)!
            )
        } else {
            throw Errors.invalidMetadata
        }
    }
    
    enum Errors: Error {
        case notFound
        case noMetadata
        case invalidMetadata
    }
    
    struct LightDarkIndex: Decodable {
        let l: Int
        let d: Int
    }
    
    struct SolarMetadata: Decodable {
        let ap: LightDarkIndex
    }
    

    Here is a simple SwiftUI view to display them. Set imageFileURL to a HEIC file and see it working.

    struct ContentView: View {
        @State var light: Image?
        @State var dark: Image?
        
        var body: some View {
            HStack {
                if let light {
                    light
                }
                if let dark {
                    dark
                }
            }
            .frame(width: 300)
                .onAppear {
                    do {
                        let imageFileURL = ...
                        let images = try lightDarkImages(url: imageFileURL)
                        light = Image(nsImage: NSImage(cgImage: images.light, size: .init(width: 100, height: 100)))
                        dark = Image(nsImage: NSImage(cgImage: images.dark, size: .init(width: 100, height: 100)))
                    } catch {
                        print(error)
                    }
                }
        }
    }
    

    Here is the result for the "hello Green" wall paper.

    enter image description here