Troubleshooting an AppKit bug on Sonoma

Saturday, March 30, 2024

Updated Thursday, May 23, 2024

swiftappkit

Update! This bug appears to be fixed in macOS Sonoma 14.5!


In the macOS AppKit framework, there’s an NSWorkspace API method for setting the current desktop image, scaling method, and fill color. The method is called setDesktopImageURL, and it accepts a file URL, a screen reference, and an options dictionary, including fillColor.

Wallpaper settings in macOS

Here’s some sample code:

SetDesktopImageURL.swift
import AppKit
import Foundation

let workspace = NSWorkspace.shared
let screens = NSScreen.screens

// transparent Apache logo, part of the base macOS installation
let path = "/Library/WebServer/share/httpd/manual/images/feather.png"
let image = NSURL.fileURL(withPath: path)

// A nice blue green. This will show through any transparent areas of the desktop image
let color = NSColor(calibratedRed: 64/255, green: 116/255, blue: 112/255, alpha: 1.0)

// NSImageScaling options https://developer.apple.com/documentation/appkit/nsimagescaling
let center = NSImageScaling.scaleNone.rawValue

let options: [NSWorkspace.DesktopImageOptionKey: Any] = [
  .allowClipping: false,
  .imageScaling: center,
  .fillColor: color,
]

for screen in screens {
  try workspace.setDesktopImageURL(image, for: screen, options: options)
}

We can read the values we’ve just set using a related API method, desktopImageOptions. This method returns a dictionary of the current desktop image options for the given screen.

More sample code:

DesktopImageOptions.swift
import AppKit
import Foundation

let screen = NSScreen.main!
let options = NSWorkspace.shared.desktopImageOptions(for: screen)!

print("imageScaling:", options[.imageScaling] as Any)
print("allowClipping:", options[.allowClipping] as Any)
print("fillColor:", options[.fillColor] as Any)

This code works as expected in macOS Monterey (version 12.x) and macOS Ventura (version 13.x):

Monterey

macOS version: 12.4.0
imageScaling: Optional(3)
allowClipping: Optional(0)
fillColor: Optional(NSCalibratedRGBColorSpace ...) <-- good

Ventura

macOS version: 13.6.0
imageScaling: Optional(3)
allowClipping: Optional(0)
fillColor: Optional(NSCalibratedRGBColorSpace ...) <-- good

But in Sonoma (version 14.x), the fillColor key is missing from the returned dictionary:

Sonoma

macOS version: 14.4.1
imageScaling: Optional(3)
allowClipping: Optional(0)
fillColor: nil <-- bad

Similarly, when calling setDesktopImageURL, Sonoma seems to ignore the fillColor option, and always sets the fill color to some default blue (even if I have previously set it to some other color manually.)


Keyboard Maestro

I also tested this in the popular macro utility Keyboard Maestro, which has a Set Desktop Image action that allows you to set the desktop image, scaling method, and fill color, likely using the same setDesktopImageURL API method. Keyboard Maestro is able to set the fill color in Monterey, but not in Sonoma.

Keyboard Maestro


So…

…at this point I’m pretty sure this is a regression in macOS Sonoma.

Next steps

  • If you have any experience with this API and know of a workaround, I’d love to hear it!

  • If you have Xcode installed, you can try out a minimal sample project on Github. Clone the repo and type swift run. It will output the current desktop image settings in your console. Or you can copy the code above and save it to a file, and run it with swift filename.swift.

  • I need to file a bug report with Apple, but I haven’t done that before and from what I’ve heard I’m not particularly hopeful that it will get me anywhere. But I’ll try anyway.


A cool thing I found along the way…

I discovered this amazing macOS virtualization tool, Tart.

macOS Ventura running in Tart

It allows you to run different versions of macOS (and Linux) in virtual containers on your Mac. I used it to test the above code on Ventura, as I didn’t have a machine with that version running. The setup is dead-easy (if you already have homebrew installed). It does take a while to download an OS image, but once that’s done the container starts up incredibly quickly, and feels just as responsive as my actual Mac. Definitely worth a look if you do any macOS development.

pascal’s diary · copyright about now · rss