diff --git a/WhatsNew.txt b/WhatsNew.txt index 08f6f323a..880ca0e42 100644 --- a/WhatsNew.txt +++ b/WhatsNew.txt @@ -12,6 +12,9 @@ General: * The preprocessor symbol __IPHONEOS__ has been renamed __IOS__ * SDL_stdinc.h no longer includes stdio.h, stdlib.h, etc., it only provides the SDL C runtime functionality * SDL_intrin.h now includes the intrinsics headers that were in SDL_cpuinfo.h +* Added SDL_GetSystemTheme() to return whether the system is using a dark or light color theme, and SDL_EVENT_SYSTEM_THEME_CHANGED is sent when this changes +* Added SDL_GetDisplays() to return a list of connected displays +* Added SDL_GetPrimaryDisplay() to get the instance ID of the primary display * Added SDL_CreateSurface() and SDL_CreateSurfaceFrom() which replace SDL_CreateRGBSurface*(), and can also be used to create YUV surfaces * Added SDL_GetJoysticks(), SDL_GetJoystickInstanceName(), SDL_GetJoystickInstancePath(), SDL_GetJoystickInstancePlayerIndex(), SDL_GetJoystickInstanceGUID(), SDL_GetJoystickInstanceVendor(), SDL_GetJoystickInstanceProduct(), SDL_GetJoystickInstanceProductVersion(), and SDL_GetJoystickInstanceType() to directly query the list of available joysticks * Added SDL_GetGamepads(), SDL_GetGamepadInstanceName(), SDL_GetGamepadInstancePath(), SDL_GetGamepadInstancePlayerIndex(), SDL_GetGamepadInstanceGUID(), SDL_GetGamepadInstanceVendor(), SDL_GetGamepadInstanceProduct(), SDL_GetGamepadInstanceProductVersion(), and SDL_GetGamepadInstanceType() to directly query the list of available gamepads diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java index a95dc45d6..de03ba790 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -412,6 +412,15 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } catch(Exception ignored) { } + switch (getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) { + case Configuration.UI_MODE_NIGHT_NO: + SDLActivity.onNativeDarkModeChanged(false); + break; + case Configuration.UI_MODE_NIGHT_YES: + SDLActivity.onNativeDarkModeChanged(true); + break; + } + setContentView(mLayout); setWindowStyle(false); @@ -577,6 +586,15 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh mCurrentLocale = newConfig.locale; SDLActivity.onNativeLocaleChanged(); } + + switch (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) { + case Configuration.UI_MODE_NIGHT_NO: + SDLActivity.onNativeDarkModeChanged(false); + break; + case Configuration.UI_MODE_NIGHT_YES: + SDLActivity.onNativeDarkModeChanged(true); + break; + } } @Override @@ -931,6 +949,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh public static native void nativeAddTouch(int touchId, String name); public static native void nativePermissionResult(int requestCode, boolean result); public static native void onNativeLocaleChanged(); + public static native void onNativeDarkModeChanged(boolean enabled); /** * This method is called by SDL using JNI. diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 482fc6d3c..8059da352 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -87,6 +87,8 @@ typedef enum SDL_EVENT_LOCALE_CHANGED, /**< The user's locale preferences have changed. */ + SDL_EVENT_SYSTEM_THEME_CHANGED, /**< The system theme changed */ + /* Display events */ /* 0x150 was SDL_DISPLAYEVENT, reserve the number for sdl2-compat */ SDL_EVENT_DISPLAY_ORIENTATION = 0x151, /**< Display orientation has changed to data1 */ diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index ebdc8a779..ae11aefc2 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -43,6 +43,16 @@ extern "C" { typedef Uint32 SDL_DisplayID; typedef Uint32 SDL_WindowID; +/** + * \brief System theme + */ +typedef enum +{ + SDL_SYSTEM_THEME_UNKNOWN, /**< Unknown system theme */ + SDL_SYSTEM_THEME_LIGHT, /**< Light colored system theme */ + SDL_SYSTEM_THEME_DARK, /**< Dark colored system theme */ +} SDL_SystemTheme; + /** * \brief The structure that defines a display mode * @@ -65,6 +75,18 @@ typedef struct void *driverdata; /**< driver-specific data, initialize to 0 */ } SDL_DisplayMode; +/** + * \brief Display orientation + */ +typedef enum +{ + SDL_ORIENTATION_UNKNOWN, /**< The display orientation can't be determined */ + SDL_ORIENTATION_LANDSCAPE, /**< The display is in landscape mode, with the right side up, relative to portrait mode */ + SDL_ORIENTATION_LANDSCAPE_FLIPPED, /**< The display is in landscape mode, with the left side up, relative to portrait mode */ + SDL_ORIENTATION_PORTRAIT, /**< The display is in portrait mode */ + SDL_ORIENTATION_PORTRAIT_FLIPPED /**< The display is in portrait mode, upside down */ +} SDL_DisplayOrientation; + /** * \brief The type used to identify a window * @@ -151,18 +173,6 @@ typedef enum #define SDL_WINDOWPOS_ISCENTERED(X) \ (((X)&0xFFFF0000) == SDL_WINDOWPOS_CENTERED_MASK) -/** - * \brief Display orientation - */ -typedef enum -{ - SDL_ORIENTATION_UNKNOWN, /**< The display orientation can't be determined */ - SDL_ORIENTATION_LANDSCAPE, /**< The display is in landscape mode, with the right side up, relative to portrait mode */ - SDL_ORIENTATION_LANDSCAPE_FLIPPED, /**< The display is in landscape mode, with the left side up, relative to portrait mode */ - SDL_ORIENTATION_PORTRAIT, /**< The display is in portrait mode */ - SDL_ORIENTATION_PORTRAIT_FLIPPED /**< The display is in portrait mode, upside down */ -} SDL_DisplayOrientation; - /** * \brief Window flash operation */ @@ -297,6 +307,15 @@ extern DECLSPEC const char *SDLCALL SDL_GetVideoDriver(int index); */ extern DECLSPEC const char *SDLCALL SDL_GetCurrentVideoDriver(void); +/** + * Get the current system theme + * + * \returns the current system theme, light, dark, or unknown + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC SDL_SystemTheme SDLCALL SDL_GetSystemTheme(void); + /** * Get a list of currently connected displays. * diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index f01267fae..2508f4331 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -124,6 +124,9 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)( JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)( JNIEnv *env, jclass cls); +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDarkModeChanged)( + JNIEnv *env, jclass cls, jboolean enabled); + JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)( JNIEnv *env, jclass cls); @@ -183,6 +186,7 @@ static JNINativeMethod SDLActivity_tab[] = { { "onNativeClipboardChanged", "()V", SDL_JAVA_INTERFACE(onNativeClipboardChanged) }, { "nativeLowMemory", "()V", SDL_JAVA_INTERFACE(nativeLowMemory) }, { "onNativeLocaleChanged", "()V", SDL_JAVA_INTERFACE(onNativeLocaleChanged) }, + { "onNativeDarkModeChanged", "(Z)V", SDL_JAVA_INTERFACE(onNativeDarkModeChanged) }, { "nativeSendQuit", "()V", SDL_JAVA_INTERFACE(nativeSendQuit) }, { "nativeQuit", "()V", SDL_JAVA_INTERFACE(nativeQuit) }, { "nativePause", "()V", SDL_JAVA_INTERFACE(nativePause) }, @@ -1199,6 +1203,13 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)( SDL_SendAppEvent(SDL_EVENT_LOCALE_CHANGED); } +/* Dark mode */ +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDarkModeChanged)( + JNIEnv *env, jclass cls, jboolean enabled) +{ + Android_SetDarkMode(enabled); +} + /* Send Quit event to "SDLThread" thread */ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)( JNIEnv *env, jclass cls) diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index a9f6680f3..025d992d5 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -838,6 +838,7 @@ SDL3_0.0.0 { SDL_SetRenderScale; SDL_GetRenderScale; SDL_GetRenderWindowSize; + SDL_GetSystemTheme; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index f45dbbfeb..5d86ddf8b 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -865,3 +865,4 @@ #define SDL_SetRenderScale SDL_SetRenderScale_REAL #define SDL_GetRenderScale SDL_GetRenderScale_REAL #define SDL_GetRenderWindowSize SDL_GetRenderWindowSize_REAL +#define SDL_GetSystemTheme SDL_GetSystemTheme_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index fab380c95..94d61004d 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -910,3 +910,4 @@ SDL_DYNAPI_PROC(int,SDL_ConvertEventToRenderCoordinates,(SDL_Renderer *a, SDL_Ev SDL_DYNAPI_PROC(int,SDL_SetRenderScale,(SDL_Renderer *a, float b, float c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_GetRenderScale,(SDL_Renderer *a, float *b, float *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_GetRenderWindowSize,(SDL_Renderer *a, int *b, int *c),(a,b,c),return) +SDL_DYNAPI_PROC(SDL_SystemTheme,SDL_GetSystemTheme,(void),(),return) diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 669c0801c..772173e8e 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -204,6 +204,8 @@ static void SDL_LogEvent(const SDL_Event *event) break; SDL_EVENT_CASE(SDL_EVENT_LOCALE_CHANGED) break; + SDL_EVENT_CASE(SDL_EVENT_SYSTEM_THEME_CHANGED) + break; SDL_EVENT_CASE(SDL_EVENT_KEYMAP_CHANGED) break; SDL_EVENT_CASE(SDL_EVENT_CLIPBOARD_UPDATE) @@ -1346,6 +1348,11 @@ int SDL_SendLocaleChangedEvent(void) return SDL_SendAppEvent(SDL_EVENT_LOCALE_CHANGED); } +int SDL_SendSystemThemeChangedEvent(void) +{ + return SDL_SendAppEvent(SDL_EVENT_SYSTEM_THEME_CHANGED); +} + int SDL_InitEvents(void) { #if !SDL_JOYSTICK_DISABLED diff --git a/src/events/SDL_events_c.h b/src/events/SDL_events_c.h index faf0a626b..44c3eb8d1 100644 --- a/src/events/SDL_events_c.h +++ b/src/events/SDL_events_c.h @@ -44,6 +44,7 @@ extern int SDL_SendAppEvent(SDL_EventType eventType); extern int SDL_SendSysWMEvent(SDL_SysWMmsg *message); extern int SDL_SendKeymapChangedEvent(void); extern int SDL_SendLocaleChangedEvent(void); +extern int SDL_SendSystemThemeChangedEvent(void); extern int SDL_SendQuit(void); diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index 0aecf4d72..7b049b3df 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -1438,6 +1438,21 @@ SDLTest_CommonInit(SDLTest_CommonState *state) return SDL_TRUE; } +static const char *SystemThemeName(void) +{ + switch (SDL_GetSystemTheme()) { +#define CASE(X) \ + case SDL_SYSTEM_THEME_##X: \ + return #X + CASE(UNKNOWN); + CASE(LIGHT); + CASE(DARK); +#undef CASE + default: + return "???"; + } +} + static const char *DisplayOrientationName(int orientation) { switch (orientation) { @@ -1505,6 +1520,9 @@ static const char *GamepadButtonName(const SDL_GamepadButton button) static void SDLTest_PrintEvent(SDL_Event *event) { switch (event->type) { + case SDL_EVENT_SYSTEM_THEME_CHANGED: + SDL_Log("SDL EVENT: System theme changed to %s\n", SystemThemeName()); + break; case SDL_EVENT_DISPLAY_CONNECTED: SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " connected", event->display.displayID); diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 2c690cf81..1942d0978 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -354,6 +354,7 @@ struct SDL_VideoDevice char *primary_selection_text; SDL_bool setting_display_mode; Uint32 quirk_flags; + SDL_SystemTheme system_theme; /* * * */ /* Data used by the GL drivers */ @@ -476,6 +477,7 @@ extern VideoBootStrap NGAGE_bootstrap; extern SDL_bool SDL_OnVideoThread(void); extern SDL_VideoDevice *SDL_GetVideoDevice(void); extern SDL_bool SDL_IsVideoContextExternal(void); +extern void SDL_SetSystemTheme(SDL_SystemTheme theme); extern SDL_DisplayID SDL_AddBasicVideoDisplay(const SDL_DisplayMode *desktop_mode); extern SDL_DisplayID SDL_AddVideoDisplay(const SDL_VideoDisplay *display, SDL_bool send_event); extern void SDL_DelVideoDisplay(SDL_DisplayID display, SDL_bool send_event); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 3bf7a5f80..ccb51f11d 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -576,14 +576,31 @@ SDL_VideoDevice *SDL_GetVideoDevice(void) return _this; } +SDL_bool SDL_OnVideoThread(void) +{ + return (_this && SDL_ThreadID() == _this->thread) ? SDL_TRUE : SDL_FALSE; +} + SDL_bool SDL_IsVideoContextExternal(void) { return SDL_GetHintBoolean(SDL_HINT_VIDEO_EXTERNAL_CONTEXT, SDL_FALSE); } -SDL_bool SDL_OnVideoThread(void) +void SDL_SetSystemTheme(SDL_SystemTheme theme) { - return (_this && SDL_ThreadID() == _this->thread) ? SDL_TRUE : SDL_FALSE; + if (_this && theme != _this->system_theme) { + _this->system_theme = theme; + SDL_SendSystemThemeChangedEvent(); + } +} + +SDL_SystemTheme SDL_GetSystemTheme(void) +{ + if (_this) { + return _this->system_theme; + } else { + return SDL_SYSTEM_THEME_UNKNOWN; + } } static void SDL_FinalizeDisplayMode(SDL_DisplayMode *mode) diff --git a/src/video/android/SDL_androidvideo.c b/src/video/android/SDL_androidvideo.c index 05ef30179..a97ac6105 100644 --- a/src/video/android/SDL_androidvideo.c +++ b/src/video/android/SDL_androidvideo.c @@ -65,6 +65,7 @@ static float Android_ScreenRate = 0.0f; SDL_sem *Android_PauseSem = NULL; SDL_sem *Android_ResumeSem = NULL; SDL_mutex *Android_ActivityMutex = NULL; +static SDL_SystemTheme Android_SystemTheme; static int Android_SuspendScreenSaver(_THIS) { @@ -98,6 +99,7 @@ static SDL_VideoDevice *Android_CreateDevice(void) } device->driverdata = data; + device->system_theme = Android_SystemTheme; /* Set the function pointers */ device->VideoInit = Android_VideoInit; @@ -284,4 +286,19 @@ void Android_SendResize(SDL_Window *window) } } +void Android_SetDarkMode(SDL_bool enabled) +{ + SDL_VideoDevice *device = SDL_GetVideoDevice(); + + if (enabled) { + Android_SystemTheme = SDL_SYSTEM_THEME_DARK; + } else { + Android_SystemTheme = SDL_SYSTEM_THEME_LIGHT; + } + + if (device) { + SDL_SetSystemTheme(Android_SystemTheme); + } +} + #endif /* SDL_VIDEO_DRIVER_ANDROID */ diff --git a/src/video/android/SDL_androidvideo.h b/src/video/android/SDL_androidvideo.h index ae24b1f74..7100b20e0 100644 --- a/src/video/android/SDL_androidvideo.h +++ b/src/video/android/SDL_androidvideo.h @@ -29,6 +29,7 @@ extern void Android_SetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float density, float rate); extern void Android_SetFormat(int format_wanted, int format_got); extern void Android_SendResize(SDL_Window *window); +extern void Android_SetDarkMode(SDL_bool enabled); /* Private display data */ diff --git a/src/video/cocoa/SDL_cocoaevents.m b/src/video/cocoa/SDL_cocoaevents.m index 6cb6da457..a2823343f 100644 --- a/src/video/cocoa/SDL_cocoaevents.m +++ b/src/video/cocoa/SDL_cocoaevents.m @@ -129,6 +129,10 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent) - (id)init; - (void)localeDidChange:(NSNotification *)notification; +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context; @end @implementation SDLAppDelegate : NSObject @@ -154,6 +158,11 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent) selector:@selector(localeDidChange:) name:NSCurrentLocaleDidChangeNotification object:nil]; + + [NSApp addObserver:self + forKeyPath:@"effectiveAppearance" + options:NSKeyValueObservingOptionInitial + context:nil]; } return self; @@ -166,6 +175,7 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent) [center removeObserver:self name:NSWindowWillCloseNotification object:nil]; [center removeObserver:self name:NSApplicationDidBecomeActiveNotification object:nil]; [center removeObserver:self name:NSCurrentLocaleDidChangeNotification object:nil]; + [NSApp removeObserver:self forKeyPath:@"effectiveAppearance"]; /* Remove our URL event handler only if we set it */ if ([NSApp delegate] == self) { @@ -262,11 +272,19 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent) } } -- (void)localeDidChange:(NSNotification *)notification; +- (void)localeDidChange:(NSNotification *)notification { SDL_SendLocaleChangedEvent(); } +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + SDL_SetSystemTheme(Cocoa_GetSystemTheme()); +} + - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { return (BOOL)SDL_SendDropFile(NULL, [filename UTF8String]) && SDL_SendDropComplete(NULL); diff --git a/src/video/cocoa/SDL_cocoavideo.h b/src/video/cocoa/SDL_cocoavideo.h index d88b19a91..2daa40498 100644 --- a/src/video/cocoa/SDL_cocoavideo.h +++ b/src/video/cocoa/SDL_cocoavideo.h @@ -108,6 +108,7 @@ DECLARE_ALERT_STYLE(Critical); @end /* Utility functions */ +extern SDL_SystemTheme Cocoa_GetSystemTheme(void); extern NSImage *Cocoa_CreateImage(SDL_Surface *surface); /* Fix build with the 10.11 SDK */ diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 1d46bc8bb..d849c6332 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -75,6 +75,7 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void) } device->driverdata = (SDL_VideoData *)CFBridgingRetain(data); device->wakeup_lock = SDL_CreateMutex(); + device->system_theme = Cocoa_GetSystemTheme(); /* Set the function pointers */ device->VideoInit = Cocoa_VideoInit; @@ -220,6 +221,18 @@ void Cocoa_VideoQuit(_THIS) } } +/* This function assumes that it's called from within an autorelease pool */ +SDL_SystemTheme Cocoa_GetSystemTheme(void) +{ + NSAppearance* appearance = [[NSApplication sharedApplication] effectiveAppearance]; + + if ([appearance.name containsString: @"Dark"]) { + return SDL_SYSTEM_THEME_DARK; + } else { + return SDL_SYSTEM_THEME_LIGHT; + } +} + /* This function assumes that it's called from within an autorelease pool */ NSImage *Cocoa_CreateImage(SDL_Surface *surface) { diff --git a/src/video/uikit/SDL_uikitvideo.h b/src/video/uikit/SDL_uikitvideo.h index 227aceb61..c3892a077 100644 --- a/src/video/uikit/SDL_uikitvideo.h +++ b/src/video/uikit/SDL_uikitvideo.h @@ -43,4 +43,6 @@ void UIKit_ForceUpdateHomeIndicator(void); SDL_bool UIKit_IsSystemVersionAtLeast(double version); +SDL_SystemTheme UIKit_GetSystemTheme(void); + #endif /* SDL_uikitvideo_h_ */ diff --git a/src/video/uikit/SDL_uikitvideo.m b/src/video/uikit/SDL_uikitvideo.m index f12df8bde..b3dde9c17 100644 --- a/src/video/uikit/SDL_uikitvideo.m +++ b/src/video/uikit/SDL_uikitvideo.m @@ -74,6 +74,7 @@ static SDL_VideoDevice *UIKit_CreateDevice(void) } device->driverdata = (SDL_VideoData *)CFBridgingRetain(data); + device->system_theme = UIKit_GetSystemTheme(); /* Set the function pointers */ device->VideoInit = UIKit_VideoInit; @@ -175,14 +176,27 @@ int UIKit_SuspendScreenSaver(_THIS) return 0; } -SDL_bool -UIKit_IsSystemVersionAtLeast(double version) +SDL_bool UIKit_IsSystemVersionAtLeast(double version) { return [[UIDevice currentDevice].systemVersion doubleValue] >= version; } -CGRect -UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen) +SDL_SystemTheme UIKit_GetSystemTheme(void) +{ + if (@available(iOS 12.0, tvOS 10.0, *)) { + switch ([UIScreen mainScreen].traitCollection.userInterfaceStyle) { + case UIUserInterfaceStyleDark: + return SDL_SYSTEM_THEME_DARK; + case UIUserInterfaceStyleLight: + return SDL_SYSTEM_THEME_LIGHT; + default: + break; + } + } + return SDL_SYSTEM_THEME_UNKNOWN; +} + +CGRect UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen) { SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->driverdata; CGRect frame = screen.bounds; diff --git a/src/video/uikit/SDL_uikitviewcontroller.h b/src/video/uikit/SDL_uikitviewcontroller.h index f3e2f5de9..0e33f6020 100644 --- a/src/video/uikit/SDL_uikitviewcontroller.h +++ b/src/video/uikit/SDL_uikitviewcontroller.h @@ -45,6 +45,8 @@ - (instancetype)initWithSDLWindow:(SDL_Window *)_window; +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection; + - (void)setAnimationCallback:(int)interval callback:(void (*)(void *))callback callbackParam:(void *)callbackParam; diff --git a/src/video/uikit/SDL_uikitviewcontroller.m b/src/video/uikit/SDL_uikitviewcontroller.m index 425e4f92e..758fe4332 100644 --- a/src/video/uikit/SDL_uikitviewcontroller.m +++ b/src/video/uikit/SDL_uikitviewcontroller.m @@ -136,6 +136,11 @@ static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char #endif } +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection +{ + SDL_SetSystemTheme(UIKit_GetSystemTheme()); +} + - (void)setAnimationCallback:(int)interval callback:(void (*)(void *))callback callbackParam:(void *)callbackParam diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index a7444fbae..dc7312f64 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -1723,6 +1723,10 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) break; case WM_SETTINGCHANGE: + if (wParam == 0 && lParam != 0 && SDL_wcscmp((wchar_t *)lParam, L"ImmersiveColorSet") == 0) { + SDL_SetSystemTheme(WIN_GetSystemTheme()); + WIN_UpdateDarkModeForHWND(hwnd); + } if (wParam == SPI_SETMOUSE || wParam == SPI_SETMOUSESPEED) { WIN_UpdateMouseSystemScale(); } diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 7afe97f1d..519c1ef20 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -116,6 +116,7 @@ static SDL_VideoDevice *WIN_CreateDevice(void) } device->driverdata = data; device->wakeup_lock = SDL_CreateMutex(); + device->system_theme = WIN_GetSystemTheme(); #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) data->userDLL = SDL_LoadObject("USER32.DLL"); @@ -675,8 +676,26 @@ SDL_bool SDL_DXGIGetOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int * #endif } -SDL_bool -WIN_IsPerMonitorV2DPIAware(_THIS) +SDL_SystemTheme WIN_GetSystemTheme(void) +{ + DWORD type; + DWORD value; + DWORD count = sizeof(value); + LSTATUS status; + + /* Technically this isn't the system theme, but it's the preference for applications */ + status = RegGetValue(HKEY_CURRENT_USER, + TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"), + TEXT("AppsUseLightTheme"), + RRF_RT_REG_DWORD, &type, &value, &count); + if (status == ERROR_SUCCESS && type == REG_DWORD && value == 0) { + return SDL_SYSTEM_THEME_DARK; + } else { + return SDL_SYSTEM_THEME_LIGHT; + } +} + +SDL_bool WIN_IsPerMonitorV2DPIAware(_THIS) { #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) SDL_VideoData *data = _this->driverdata; diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 27484a9f8..b7cdc7cb8 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -466,6 +466,7 @@ extern SDL_bool g_WindowFrameUsableWhileCursorHidden; typedef struct IDirect3D9 IDirect3D9; extern SDL_bool D3D_LoadDLL(void **pD3DDLL, IDirect3D9 **pDirect3D9Interface); +extern SDL_SystemTheme WIN_GetSystemTheme(void); extern SDL_bool WIN_IsPerMonitorV2DPIAware(_THIS); #endif /* SDL_windowsvideo_h_ */ diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index ba482e47c..62a8ba5a1 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -40,6 +40,12 @@ #include +/* Dark mode support */ +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif +typedef HRESULT (WINAPI *DwmSetWindowAttribute_t)(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute); + /* Windows CE compatibility */ #ifndef SWP_NOCOPYBITS #define SWP_NOCOPYBITS 0 @@ -511,6 +517,8 @@ int WIN_CreateWindow(_THIS, SDL_Window *window) return WIN_SetError("Couldn't create window"); } + WIN_UpdateDarkModeForHWND(hwnd); + WIN_PumpEvents(_this); if (SetupWindowData(_this, window, hwnd, parent, SDL_TRUE) < 0) { @@ -1459,4 +1467,18 @@ int WIN_FlashWindow(_THIS, SDL_Window *window, SDL_FlashOperation operation) } #endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ +void WIN_UpdateDarkModeForHWND(HWND hwnd) +{ + void *handle = SDL_LoadObject("dwmapi.dll"); + if (handle) { + DwmSetWindowAttribute_t DwmSetWindowAttributeFunc = (DwmSetWindowAttribute_t)SDL_LoadFunction(handle, "DwmSetWindowAttribute"); + if (DwmSetWindowAttributeFunc) { + /* FIXME: Do we need to traverse children? */ + BOOL value = (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK) ? TRUE : FALSE; + DwmSetWindowAttributeFunc(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + } + SDL_UnloadObject(handle); + } +} + #endif /* SDL_VIDEO_DRIVER_WINDOWS */ diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h index c27ba0341..9e31e583c 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -110,6 +110,7 @@ extern void WIN_ClientPointFromSDL(const SDL_Window *window, int *x, int *y); extern void WIN_ClientPointFromSDLFloat(const SDL_Window *window, float x, float y, LONG *xOut, LONG *yOut); extern void WIN_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept); extern int WIN_FlashWindow(_THIS, SDL_Window *window, SDL_FlashOperation operation); +extern void WIN_UpdateDarkModeForHWND(HWND hwnd); /* Ends C function definitions when using C++ */ #ifdef __cplusplus