Skip to content

Instantly share code, notes, and snippets.

@Saafo
Created June 29, 2026 12:07
Show Gist options
  • Select an option

  • Save Saafo/285644020b813c92885ee4f07cc2a090 to your computer and use it in GitHub Desktop.

Select an option

Save Saafo/285644020b813c92885ee4f07cc2a090 to your computer and use it in GitHub Desktop.
Animated Dark Mode Switch for macOS 26
#import <AppKit/AppKit.h>
#import <dispatch/dispatch.h>
#import <dlfcn.h>
#import <objc/message.h>
typedef bool (*SLSGetAppearanceThemeLegacyFunc)(void);
typedef void (*SLSSetAppearanceThemeNotifyingFunc)(BOOL, BOOL);
typedef id (*ObjCClassMessageSendFunc)(id, SEL);
typedef void (*ObjCPostChangeMessageSendFunc)(id, SEL, unsigned long long, void (^)(void));
static void ExitWithMessage(int code, NSString *message) {
fprintf(stderr, "%s\n", message.UTF8String);
exit(code);
}
static void *LoadSkyLight(void) {
void *handle = dlopen(
"/System/Library/PrivateFrameworks/SkyLight.framework/SkyLight",
RTLD_LAZY | RTLD_LOCAL
);
if (!handle) {
handle = dlopen(
"/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight",
RTLD_LAZY | RTLD_LOCAL
);
}
if (!handle) {
ExitWithMessage(1, [NSString stringWithFormat:@"dlopen SkyLight failed: %s", dlerror()]);
}
return handle;
}
int main(int argc, const char *argv[]) {
@autoreleasepool {
void *skyLight = LoadSkyLight();
SLSGetAppearanceThemeLegacyFunc getAppearance =
(SLSGetAppearanceThemeLegacyFunc)dlsym(skyLight, "SLSGetAppearanceThemeLegacy");
SLSSetAppearanceThemeNotifyingFunc setAppearance =
(SLSSetAppearanceThemeNotifyingFunc)dlsym(skyLight, "SLSSetAppearanceThemeNotifying");
if (!getAppearance || !setAppearance) {
ExitWithMessage(1, @"Could not resolve SkyLight appearance functions.");
}
Class transitionClass = NSClassFromString(@"NSGlobalPreferenceTransition");
SEL transitionSelector = NSSelectorFromString(@"transition");
SEL postSelector = NSSelectorFromString(@"postChangeNotification:completionHandler:");
if (![transitionClass respondsToSelector:transitionSelector]) {
ExitWithMessage(1, @"NSGlobalPreferenceTransition +transition is unavailable.");
}
BOOL targetDarkMode = !getAppearance();
setAppearance(targetDarkMode, false);
id transition = ((ObjCClassMessageSendFunc)objc_msgSend)(transitionClass, transitionSelector);
if (![transition respondsToSelector:postSelector]) {
ExitWithMessage(
1,
@"NSGlobalPreferenceTransition -postChangeNotification:completionHandler: is unavailable."
);
}
__block BOOL didExit = NO;
void (^finish)(void) = ^{
if (didExit) {
return;
}
didExit = YES;
exit(0);
};
((ObjCPostChangeMessageSendFunc)objc_msgSend)(transition, postSelector, 0, finish);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), finish);
[[NSRunLoop mainRunLoop] run];
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment