macOS: React to changes in effective appearance of the NSApplication not the content view for each window

This is nicer now every OS Windows doesnt cause a notification. Also
fixes #9034 which was caused by us setting an explicit appearance on the
window when the titlebar is set to a specific color thereby preventing
the views in the window from getting appearance change notifications.
This commit is contained in:
Kovid Goyal 2025-09-29 17:17:46 +05:30
parent f4867928b4
commit d2cc22e7c6
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
3 changed files with 59 additions and 25 deletions

View file

@ -147,6 +147,10 @@ Detailed list of changes
- macOS: Workaround for bug in macOS Tahoe that caused closed OS Windows to
remain as invisible rectangles that intercept mouse events (:iss:`8952`)
- macOS: Fix a regression in the previous release that broke automatic
switching of dark/light mode when setting :opt:`macos_titlebar_color` to a
arbitrary color (:iss:`9034`)
- goto_session: Add ``--sort-by=alphabetical`` to have the interactive session
picker list the sessions in a fixed order rather than by most recent
(:disc:`9033`)

View file

@ -297,6 +297,7 @@ static NSDictionary<NSString*,NSNumber*> *global_shortcuts = nil;
// Delegate for application related notifications {{{
@interface GLFWApplicationDelegate : NSObject <NSApplicationDelegate>
- (void)handleAppearanceChange;
@end
@implementation GLFWApplicationDelegate
@ -415,20 +416,73 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
}
}
static void *AppearanceObservationContext = &AppearanceObservationContext;
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
if (finish_launching_callback) finish_launching_callback(true);
(void)notification;
[[NSApplication sharedApplication] addObserver:self
forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
context:AppearanceObservationContext];
if (finish_launching_callback) finish_launching_callback(true);
[NSApp stop:nil];
CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL);
_glfwCocoaPostEmptyEvent();
}
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) {
(void)query_if_unintialized;
int theme_type = GLFW_COLOR_SCHEME_NO_PREFERENCE;
NSAppearance *changedAppearance = NSApp.effectiveAppearance;
NSAppearanceName newAppearance = [changedAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
if([newAppearance isEqualToString:NSAppearanceNameDarkAqua]){
theme_type = GLFW_COLOR_SCHEME_DARK;
} else {
theme_type = GLFW_COLOR_SCHEME_LIGHT;
}
return theme_type;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context {
if (context == AppearanceObservationContext) {
if ([keyPath isEqualToString:@"effectiveAppearance"]) {
// The initial call (from NSKeyValueObservingOptionInitial) might happen on a background thread.
// Dispatch to the main thread to be safe, especially if updating UI.
dispatch_async(dispatch_get_main_queue(), ^{
[self handleAppearanceChange];
});
}
} else {
// If the context doesn't match, pass the notification to the superclass.
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)handleAppearanceChange {
static GLFWColorScheme previously_reported_appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE;
GLFWColorScheme new_appearance = glfwGetCurrentSystemColorTheme(true);
if (new_appearance != previously_reported_appearance) {
previously_reported_appearance = new_appearance;
_glfwInputColorScheme(new_appearance, false);
}
}
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
(void)aNotification;
CGDisplayRemoveReconfigurationCallback(display_reconfigured, NULL);
@try {
[[NSApplication sharedApplication] removeObserver:self
forKeyPath:@"effectiveAppearance"
context:AppearanceObservationContext];
} @catch (NSException * __unused exception) {
// Ignore exceptions, which can happen if the observer was never added.
}
}
- (void)applicationDidHide:(NSNotification *)notification

View file

@ -981,16 +981,6 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
updateCursorImage(window);
}
- (void)viewDidChangeEffectiveAppearance
{
static GLFWColorScheme appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE;
GLFWColorScheme new_appearance = glfwGetCurrentSystemColorTheme(true);
if (new_appearance != appearance) {
appearance = new_appearance;
_glfwInputColorScheme(appearance, false);
}
}
- (void)viewDidChangeBackingProperties
{
if (!window) return;
@ -3434,20 +3424,6 @@ GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool us
[window->ns.object makeFirstResponder:window->ns.view];
}}
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) {
(void)query_if_unintialized;
int theme_type = 0;
NSAppearance *changedAppearance = NSApp.effectiveAppearance;
NSAppearanceName newAppearance = [changedAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
if([newAppearance isEqualToString:NSAppearanceNameDarkAqua]){
theme_type = 1;
} else {
theme_type = 2;
}
return theme_type;
}
GLFWAPI uint32_t
glfwGetCocoaKeyEquivalent(uint32_t glfw_key, int glfw_mods, int *cocoa_mods) {
*cocoa_mods = 0;