Created
June 29, 2026 12:07
-
-
Save Saafo/285644020b813c92885ee4f07cc2a090 to your computer and use it in GitHub Desktop.
Animated Dark Mode Switch for macOS 26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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