I've been daily driving my tiling window manager for macOS on macOS Sequoia for a few months now and things have been pretty stable.
I've had a few reports of issues on macOS Tahoe, but it wasn't until setting up a new machine that came with Tahoe preinstalled that I was able to reproduce and debug them myself.
One of the things that a tiling window manager should do is listen for changes to connected displays. The main changes that should be handled gracefully are changes in screen resolution, scaling, and display (dis)connection.
The code below is for a DisplayReconfigurationListener which emits events to
another component when any of the above changes are communicated by the
operating system.
Existing Code in Rust
use crate::core_graphics::error::CoreGraphicsError;
use crate::monitor_reconciliator;
use crate::monitor_reconciliator::MonitorNotification;
use objc2_core_graphics::CGDirectDisplayID;
use objc2_core_graphics::CGDisplayChangeSummaryFlags;
use objc2_core_graphics::CGDisplayRegisterReconfigurationCallback;
use objc2_core_graphics::CGDisplayRemoveReconfigurationCallback;
use std::ffi::c_void;
pub struct DisplayReconfigurationListener {}
unsafe extern "C-unwind" fn callback(
display_id: CGDirectDisplayID,
flags: CGDisplayChangeSummaryFlags,
_user_info: *mut c_void,
) {
if flags.contains(CGDisplayChangeSummaryFlags::DesktopShapeChangedFlag) {
tracing::debug!("display: {display_id} resized");
monitor_reconciliator::send_notification(MonitorNotification::Resize(display_id));
}
if flags.contains(CGDisplayChangeSummaryFlags::AddFlag) {
tracing::debug!("display: {display_id} added");
monitor_reconciliator::send_notification(MonitorNotification::DisplayConnectionChange(
display_id,
));
}
if flags.contains(CGDisplayChangeSummaryFlags::RemoveFlag) {
tracing::debug!("display: {display_id} removed");
monitor_reconciliator::send_notification(MonitorNotification::DisplayConnectionChange(
display_id,
));
}
}
impl DisplayReconfigurationListener {
pub fn init() -> Result<(), CoreGraphicsError> {
tracing::info!("registering display reconfiguration listener callback");
unsafe {
match CoreGraphicsError::from(CGDisplayRegisterReconfigurationCallback(
Some(callback),
std::ptr::null_mut(),
)) {
CoreGraphicsError::Success => Ok(()),
err => Err(err),
}
}
}
}
impl Drop for DisplayReconfigurationListener {
fn drop(&mut self) {
tracing::info!("removing display reconfiguration listener callback");
unsafe {
match CoreGraphicsError::from(CGDisplayRemoveReconfigurationCallback(
Some(callback),
std::ptr::null_mut(),
)) {
CoreGraphicsError::Success => {}
error => {
tracing::error!(
"failed to remove display reconfiguration listener callback {error}"
)
}
}
}
}
}
When initialized, a call is made to
CGDisplayRegisterReconfigurationCallback
to register the callback which listens for specific
CGDisplayChangeSummaryFlags and then emits appropriate events to another
component which handles the state changes required.
After adding some debug logs at the beginning of the callback, it was clear that the callback was not being triggered at all on macOS Tahoe. Strange.
Reproduction in Swift
I wanted to make sure this wasn't a "Rust thing" so I put together something in Swift (which I presume has first-class support on this platform) to validate that registered callbacks were being triggered on Sequoia but not Tahoe.
import Foundation
import CoreGraphics
import AppKit
func displayReconfigurationCallback(
displayID: CGDirectDisplayID,
flags: CGDisplayChangeSummaryFlags,
userInfo: UnsafeMutableRawPointer?
) {
let timestamp = ISO8601DateFormatter().string(from: Date())
print("\n[\(timestamp)] Display reconfiguration event received!")
print(" Display ID: \(displayID)")
print(" Flags raw value: \(flags.rawValue)")
var flagDescriptions: [String] = []
if flags.contains(.beginConfigurationFlag) {
flagDescriptions.append("BeginConfiguration")
}
if flags.contains(.movedFlag) {
flagDescriptions.append("Moved")
}
if flags.contains(.setMainFlag) {
flagDescriptions.append("SetMain")
}
if flags.contains(.setModeFlag) {
flagDescriptions.append("SetMode")
}
if flags.contains(.addFlag) {
flagDescriptions.append("Add")
}
if flags.contains(.removeFlag) {
flagDescriptions.append("Remove")
}
if flags.contains(.enabledFlag) {
flagDescriptions.append("Enabled")
}
if flags.contains(.disabledFlag) {
flagDescriptions.append("Disabled")
}
if flags.contains(.mirrorFlag) {
flagDescriptions.append("Mirror")
}
if flags.contains(.unMirrorFlag) {
flagDescriptions.append("UnMirror")
}
if flags.contains(.desktopShapeChangedFlag) {
flagDescriptions.append("DesktopShapeChanged")
}
if flagDescriptions.isEmpty {
print(" Flags: (none/unknown)")
} else {
print(" Flags: \(flagDescriptions.joined(separator: ", "))")
}
fflush(stdout)
}
print("=== Display Reconfiguration Listener Test ===")
print("macOS version: \(ProcessInfo.processInfo.operatingSystemVersionString)")
print("")
let app = NSApplication.shared
print("NSApplication.shared initialized: \(app)")
let maxDisplays: UInt32 = 16
var displays = [CGDirectDisplayID](repeating: 0, count: Int(maxDisplays))
var displayCount: UInt32 = 0
CGGetActiveDisplayList(maxDisplays, &displays, &displayCount)
print("\nCurrently active displays: \(displayCount)")
for i in 0..<Int(displayCount) {
let id = displays[i]
let bounds = CGDisplayBounds(id)
let isMain = CGDisplayIsMain(id) != 0
print(" Display \(i): ID=\(id), bounds=\(bounds), isMain=\(isMain)")
}
print("\nRegistering CGDisplayReconfigurationCallback...")
let result = CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallback, nil)
if result == .success {
print("✓ Callback registered successfully (CGError.success)")
} else {
print("✗ Failed to register callback: \(result.rawValue)")
exit(1)
}
print("\n>>> Listening for display changes. Try:")
print("\nPress Ctrl+C to exit.\n")
RunLoop.current.run()
Results on macOS Sequoia
=== Display Reconfiguration Listener Test ===
macOS version: Version 15.6.1 (Build 24G90)
NSApplication.shared initialized: <NSApplication: 0x11080cfa0>
Currently active displays: 1
Display 0: ID=3, bounds=(0.0, 0.0, 3840.0, 1620.0), isMain=true
Registering CGDisplayReconfigurationCallback...
✓ Callback registered successfully (CGError.success)
>>> Listening for display changes
Press Ctrl+C to exit.
[2025-11-25T23:57:23Z] Display reconfiguration event received!
Display ID: 3
Flags raw value: 1
Flags: BeginConfiguration
[2025-11-25T23:57:23Z] Display reconfiguration event received!
Display ID: 3
Flags raw value: 4104
Flags: SetMode, DesktopShapeChanged
[2025-11-25T23:57:35Z] Display reconfiguration event received!
Display ID: 3
Flags raw value: 1
Flags: BeginConfiguration
[2025-11-25T23:57:36Z] Display reconfiguration event received!
Display ID: 3
Flags raw value: 4104
Flags: SetMode, DesktopShapeChanged
Results on macOS Tahoe
=== Display Reconfiguration Listener Test ===
macOS version: Version 26.1 (Build 25B78)
NSApplication.shared initialized: <NSApplication: 0x10d47f180>
Currently active displays: 1
Display 0: ID=2, bounds=(0.0, 0.0, 3840.0, 1620.0), isMain=true
Registering CGDisplayReconfigurationCallback...
✓ Callback registered successfully (CGError.success)
>>> Listening for display changes. Try:
Press Ctrl+C to exit.
What next?
The Apple Developer
website
continues to mark CGDisplayRegisterReconfigurationCallback as supported on
macOS 10.3+, and there are no deprecation warnings or notes of additional
permissions required to call this API as of Tahoe.
It looks like it is also possible to register for various NSNotifications such
as
didChangeScreenParametersNotification
which can also be mapped to the behaviors described by
CGDisplayChangeSummaryFlags
with a little trial and error, and this should work for both Sequoia and Tahoe.
The imposter syndrome that never really goes away is telling me that I probably missed something somewhere, which is why I'm sharing these details - hopefully someone can shed some light on where I may have gone wrong.
But if that's not the case, it would be great if the folks at Apple could just... stop breaking things.
If you have any questions or comments you can reach out to me on Bluesky and Mastodon.
If you're interested in what I read to analyze problems like this, you can subscribe to my Software Development RSS feed.
If you'd like to watch me writing code while explaining what I'm doing, you can also subscribe to my YouTube channel.
If you would like early access to komorebi for Mac, you can sponsor me on GitHub.