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

Fixes https://github.com/libsdl-org/SDL/issues/5334
Fixes https://github.com/libsdl-org/SDL/issues/6958
Closes https://github.com/libsdl-org/SDL/pull/6440
main
Sam Lantinga 2023-03-07 00:01:34 -08:00
parent fb0c3197e0
commit 8994878767
27 changed files with 243 additions and 21 deletions

View File

@ -12,6 +12,9 @@ General:
* The preprocessor symbol __IPHONEOS__ has been renamed __IOS__ * 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_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 * 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_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_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 * 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

View File

@ -412,6 +412,15 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
} catch(Exception ignored) { } 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); setContentView(mLayout);
setWindowStyle(false); setWindowStyle(false);
@ -577,6 +586,15 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
mCurrentLocale = newConfig.locale; mCurrentLocale = newConfig.locale;
SDLActivity.onNativeLocaleChanged(); 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 @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 nativeAddTouch(int touchId, String name);
public static native void nativePermissionResult(int requestCode, boolean result); public static native void nativePermissionResult(int requestCode, boolean result);
public static native void onNativeLocaleChanged(); public static native void onNativeLocaleChanged();
public static native void onNativeDarkModeChanged(boolean enabled);
/** /**
* This method is called by SDL using JNI. * This method is called by SDL using JNI.

View File

@ -87,6 +87,8 @@ typedef enum
SDL_EVENT_LOCALE_CHANGED, /**< The user's locale preferences have changed. */ SDL_EVENT_LOCALE_CHANGED, /**< The user's locale preferences have changed. */
SDL_EVENT_SYSTEM_THEME_CHANGED, /**< The system theme changed */
/* Display events */ /* Display events */
/* 0x150 was SDL_DISPLAYEVENT, reserve the number for sdl2-compat */ /* 0x150 was SDL_DISPLAYEVENT, reserve the number for sdl2-compat */
SDL_EVENT_DISPLAY_ORIENTATION = 0x151, /**< Display orientation has changed to data1 */ SDL_EVENT_DISPLAY_ORIENTATION = 0x151, /**< Display orientation has changed to data1 */

View File

@ -43,6 +43,16 @@ extern "C" {
typedef Uint32 SDL_DisplayID; typedef Uint32 SDL_DisplayID;
typedef Uint32 SDL_WindowID; 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 * \brief The structure that defines a display mode
* *
@ -65,6 +75,18 @@ typedef struct
void *driverdata; /**< driver-specific data, initialize to 0 */ void *driverdata; /**< driver-specific data, initialize to 0 */
} SDL_DisplayMode; } 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 * \brief The type used to identify a window
* *
@ -151,18 +173,6 @@ typedef enum
#define SDL_WINDOWPOS_ISCENTERED(X) \ #define SDL_WINDOWPOS_ISCENTERED(X) \
(((X)&0xFFFF0000) == SDL_WINDOWPOS_CENTERED_MASK) (((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 * \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); 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. * Get a list of currently connected displays.
* *

View File

@ -124,6 +124,9 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)( JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)(
JNIEnv *env, jclass cls); JNIEnv *env, jclass cls);
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDarkModeChanged)(
JNIEnv *env, jclass cls, jboolean enabled);
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)( JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)(
JNIEnv *env, jclass cls); JNIEnv *env, jclass cls);
@ -183,6 +186,7 @@ static JNINativeMethod SDLActivity_tab[] = {
{ "onNativeClipboardChanged", "()V", SDL_JAVA_INTERFACE(onNativeClipboardChanged) }, { "onNativeClipboardChanged", "()V", SDL_JAVA_INTERFACE(onNativeClipboardChanged) },
{ "nativeLowMemory", "()V", SDL_JAVA_INTERFACE(nativeLowMemory) }, { "nativeLowMemory", "()V", SDL_JAVA_INTERFACE(nativeLowMemory) },
{ "onNativeLocaleChanged", "()V", SDL_JAVA_INTERFACE(onNativeLocaleChanged) }, { "onNativeLocaleChanged", "()V", SDL_JAVA_INTERFACE(onNativeLocaleChanged) },
{ "onNativeDarkModeChanged", "(Z)V", SDL_JAVA_INTERFACE(onNativeDarkModeChanged) },
{ "nativeSendQuit", "()V", SDL_JAVA_INTERFACE(nativeSendQuit) }, { "nativeSendQuit", "()V", SDL_JAVA_INTERFACE(nativeSendQuit) },
{ "nativeQuit", "()V", SDL_JAVA_INTERFACE(nativeQuit) }, { "nativeQuit", "()V", SDL_JAVA_INTERFACE(nativeQuit) },
{ "nativePause", "()V", SDL_JAVA_INTERFACE(nativePause) }, { "nativePause", "()V", SDL_JAVA_INTERFACE(nativePause) },
@ -1199,6 +1203,13 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)(
SDL_SendAppEvent(SDL_EVENT_LOCALE_CHANGED); 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 */ /* Send Quit event to "SDLThread" thread */
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)( JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)(
JNIEnv *env, jclass cls) JNIEnv *env, jclass cls)

View File

@ -838,6 +838,7 @@ SDL3_0.0.0 {
SDL_SetRenderScale; SDL_SetRenderScale;
SDL_GetRenderScale; SDL_GetRenderScale;
SDL_GetRenderWindowSize; SDL_GetRenderWindowSize;
SDL_GetSystemTheme;
# extra symbols go here (don't modify this line) # extra symbols go here (don't modify this line)
local: *; local: *;
}; };

View File

@ -865,3 +865,4 @@
#define SDL_SetRenderScale SDL_SetRenderScale_REAL #define SDL_SetRenderScale SDL_SetRenderScale_REAL
#define SDL_GetRenderScale SDL_GetRenderScale_REAL #define SDL_GetRenderScale SDL_GetRenderScale_REAL
#define SDL_GetRenderWindowSize SDL_GetRenderWindowSize_REAL #define SDL_GetRenderWindowSize SDL_GetRenderWindowSize_REAL
#define SDL_GetSystemTheme SDL_GetSystemTheme_REAL

View File

@ -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_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_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(int,SDL_GetRenderWindowSize,(SDL_Renderer *a, int *b, int *c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_SystemTheme,SDL_GetSystemTheme,(void),(),return)

View File

@ -204,6 +204,8 @@ static void SDL_LogEvent(const SDL_Event *event)
break; break;
SDL_EVENT_CASE(SDL_EVENT_LOCALE_CHANGED) SDL_EVENT_CASE(SDL_EVENT_LOCALE_CHANGED)
break; break;
SDL_EVENT_CASE(SDL_EVENT_SYSTEM_THEME_CHANGED)
break;
SDL_EVENT_CASE(SDL_EVENT_KEYMAP_CHANGED) SDL_EVENT_CASE(SDL_EVENT_KEYMAP_CHANGED)
break; break;
SDL_EVENT_CASE(SDL_EVENT_CLIPBOARD_UPDATE) SDL_EVENT_CASE(SDL_EVENT_CLIPBOARD_UPDATE)
@ -1346,6 +1348,11 @@ int SDL_SendLocaleChangedEvent(void)
return SDL_SendAppEvent(SDL_EVENT_LOCALE_CHANGED); return SDL_SendAppEvent(SDL_EVENT_LOCALE_CHANGED);
} }
int SDL_SendSystemThemeChangedEvent(void)
{
return SDL_SendAppEvent(SDL_EVENT_SYSTEM_THEME_CHANGED);
}
int SDL_InitEvents(void) int SDL_InitEvents(void)
{ {
#if !SDL_JOYSTICK_DISABLED #if !SDL_JOYSTICK_DISABLED

View File

@ -44,6 +44,7 @@ extern int SDL_SendAppEvent(SDL_EventType eventType);
extern int SDL_SendSysWMEvent(SDL_SysWMmsg *message); extern int SDL_SendSysWMEvent(SDL_SysWMmsg *message);
extern int SDL_SendKeymapChangedEvent(void); extern int SDL_SendKeymapChangedEvent(void);
extern int SDL_SendLocaleChangedEvent(void); extern int SDL_SendLocaleChangedEvent(void);
extern int SDL_SendSystemThemeChangedEvent(void);
extern int SDL_SendQuit(void); extern int SDL_SendQuit(void);

View File

@ -1438,6 +1438,21 @@ SDLTest_CommonInit(SDLTest_CommonState *state)
return SDL_TRUE; 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) static const char *DisplayOrientationName(int orientation)
{ {
switch (orientation) { switch (orientation) {
@ -1505,6 +1520,9 @@ static const char *GamepadButtonName(const SDL_GamepadButton button)
static void SDLTest_PrintEvent(SDL_Event *event) static void SDLTest_PrintEvent(SDL_Event *event)
{ {
switch (event->type) { 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: case SDL_EVENT_DISPLAY_CONNECTED:
SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " connected", SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " connected",
event->display.displayID); event->display.displayID);

View File

@ -354,6 +354,7 @@ struct SDL_VideoDevice
char *primary_selection_text; char *primary_selection_text;
SDL_bool setting_display_mode; SDL_bool setting_display_mode;
Uint32 quirk_flags; Uint32 quirk_flags;
SDL_SystemTheme system_theme;
/* * * */ /* * * */
/* Data used by the GL drivers */ /* Data used by the GL drivers */
@ -476,6 +477,7 @@ extern VideoBootStrap NGAGE_bootstrap;
extern SDL_bool SDL_OnVideoThread(void); extern SDL_bool SDL_OnVideoThread(void);
extern SDL_VideoDevice *SDL_GetVideoDevice(void); extern SDL_VideoDevice *SDL_GetVideoDevice(void);
extern SDL_bool SDL_IsVideoContextExternal(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_AddBasicVideoDisplay(const SDL_DisplayMode *desktop_mode);
extern SDL_DisplayID SDL_AddVideoDisplay(const SDL_VideoDisplay *display, SDL_bool send_event); extern SDL_DisplayID SDL_AddVideoDisplay(const SDL_VideoDisplay *display, SDL_bool send_event);
extern void SDL_DelVideoDisplay(SDL_DisplayID display, SDL_bool send_event); extern void SDL_DelVideoDisplay(SDL_DisplayID display, SDL_bool send_event);

View File

@ -576,14 +576,31 @@ SDL_VideoDevice *SDL_GetVideoDevice(void)
return _this; return _this;
} }
SDL_bool SDL_OnVideoThread(void)
{
return (_this && SDL_ThreadID() == _this->thread) ? SDL_TRUE : SDL_FALSE;
}
SDL_bool SDL_IsVideoContextExternal(void) SDL_bool SDL_IsVideoContextExternal(void)
{ {
return SDL_GetHintBoolean(SDL_HINT_VIDEO_EXTERNAL_CONTEXT, SDL_FALSE); 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) static void SDL_FinalizeDisplayMode(SDL_DisplayMode *mode)

View File

@ -65,6 +65,7 @@ static float Android_ScreenRate = 0.0f;
SDL_sem *Android_PauseSem = NULL; SDL_sem *Android_PauseSem = NULL;
SDL_sem *Android_ResumeSem = NULL; SDL_sem *Android_ResumeSem = NULL;
SDL_mutex *Android_ActivityMutex = NULL; SDL_mutex *Android_ActivityMutex = NULL;
static SDL_SystemTheme Android_SystemTheme;
static int Android_SuspendScreenSaver(_THIS) static int Android_SuspendScreenSaver(_THIS)
{ {
@ -98,6 +99,7 @@ static SDL_VideoDevice *Android_CreateDevice(void)
} }
device->driverdata = data; device->driverdata = data;
device->system_theme = Android_SystemTheme;
/* Set the function pointers */ /* Set the function pointers */
device->VideoInit = Android_VideoInit; 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 */ #endif /* SDL_VIDEO_DRIVER_ANDROID */

View File

@ -29,6 +29,7 @@
extern void Android_SetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float density, float rate); 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_SetFormat(int format_wanted, int format_got);
extern void Android_SendResize(SDL_Window *window); extern void Android_SendResize(SDL_Window *window);
extern void Android_SetDarkMode(SDL_bool enabled);
/* Private display data */ /* Private display data */

View File

@ -129,6 +129,10 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent)
- (id)init; - (id)init;
- (void)localeDidChange:(NSNotification *)notification; - (void)localeDidChange:(NSNotification *)notification;
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context;
@end @end
@implementation SDLAppDelegate : NSObject @implementation SDLAppDelegate : NSObject
@ -154,6 +158,11 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent)
selector:@selector(localeDidChange:) selector:@selector(localeDidChange:)
name:NSCurrentLocaleDidChangeNotification name:NSCurrentLocaleDidChangeNotification
object:nil]; object:nil];
[NSApp addObserver:self
forKeyPath:@"effectiveAppearance"
options:NSKeyValueObservingOptionInitial
context:nil];
} }
return self; return self;
@ -166,6 +175,7 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent)
[center removeObserver:self name:NSWindowWillCloseNotification object:nil]; [center removeObserver:self name:NSWindowWillCloseNotification object:nil];
[center removeObserver:self name:NSApplicationDidBecomeActiveNotification object:nil]; [center removeObserver:self name:NSApplicationDidBecomeActiveNotification object:nil];
[center removeObserver:self name:NSCurrentLocaleDidChangeNotification object:nil]; [center removeObserver:self name:NSCurrentLocaleDidChangeNotification object:nil];
[NSApp removeObserver:self forKeyPath:@"effectiveAppearance"];
/* Remove our URL event handler only if we set it */ /* Remove our URL event handler only if we set it */
if ([NSApp delegate] == self) { if ([NSApp delegate] == self) {
@ -262,11 +272,19 @@ static void Cocoa_DispatchEvent(NSEvent *theEvent)
} }
} }
- (void)localeDidChange:(NSNotification *)notification; - (void)localeDidChange:(NSNotification *)notification
{ {
SDL_SendLocaleChangedEvent(); SDL_SendLocaleChangedEvent();
} }
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context
{
SDL_SetSystemTheme(Cocoa_GetSystemTheme());
}
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{ {
return (BOOL)SDL_SendDropFile(NULL, [filename UTF8String]) && SDL_SendDropComplete(NULL); return (BOOL)SDL_SendDropFile(NULL, [filename UTF8String]) && SDL_SendDropComplete(NULL);

View File

@ -108,6 +108,7 @@ DECLARE_ALERT_STYLE(Critical);
@end @end
/* Utility functions */ /* Utility functions */
extern SDL_SystemTheme Cocoa_GetSystemTheme(void);
extern NSImage *Cocoa_CreateImage(SDL_Surface *surface); extern NSImage *Cocoa_CreateImage(SDL_Surface *surface);
/* Fix build with the 10.11 SDK */ /* Fix build with the 10.11 SDK */

View File

@ -75,6 +75,7 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void)
} }
device->driverdata = (SDL_VideoData *)CFBridgingRetain(data); device->driverdata = (SDL_VideoData *)CFBridgingRetain(data);
device->wakeup_lock = SDL_CreateMutex(); device->wakeup_lock = SDL_CreateMutex();
device->system_theme = Cocoa_GetSystemTheme();
/* Set the function pointers */ /* Set the function pointers */
device->VideoInit = Cocoa_VideoInit; 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 */ /* This function assumes that it's called from within an autorelease pool */
NSImage *Cocoa_CreateImage(SDL_Surface *surface) NSImage *Cocoa_CreateImage(SDL_Surface *surface)
{ {

View File

@ -43,4 +43,6 @@ void UIKit_ForceUpdateHomeIndicator(void);
SDL_bool UIKit_IsSystemVersionAtLeast(double version); SDL_bool UIKit_IsSystemVersionAtLeast(double version);
SDL_SystemTheme UIKit_GetSystemTheme(void);
#endif /* SDL_uikitvideo_h_ */ #endif /* SDL_uikitvideo_h_ */

View File

@ -74,6 +74,7 @@ static SDL_VideoDevice *UIKit_CreateDevice(void)
} }
device->driverdata = (SDL_VideoData *)CFBridgingRetain(data); device->driverdata = (SDL_VideoData *)CFBridgingRetain(data);
device->system_theme = UIKit_GetSystemTheme();
/* Set the function pointers */ /* Set the function pointers */
device->VideoInit = UIKit_VideoInit; device->VideoInit = UIKit_VideoInit;
@ -175,14 +176,27 @@ int UIKit_SuspendScreenSaver(_THIS)
return 0; return 0;
} }
SDL_bool SDL_bool UIKit_IsSystemVersionAtLeast(double version)
UIKit_IsSystemVersionAtLeast(double version)
{ {
return [[UIDevice currentDevice].systemVersion doubleValue] >= version; return [[UIDevice currentDevice].systemVersion doubleValue] >= version;
} }
CGRect SDL_SystemTheme UIKit_GetSystemTheme(void)
UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen) {
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; SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->driverdata;
CGRect frame = screen.bounds; CGRect frame = screen.bounds;

View File

@ -45,6 +45,8 @@
- (instancetype)initWithSDLWindow:(SDL_Window *)_window; - (instancetype)initWithSDLWindow:(SDL_Window *)_window;
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;
- (void)setAnimationCallback:(int)interval - (void)setAnimationCallback:(int)interval
callback:(void (*)(void *))callback callback:(void (*)(void *))callback
callbackParam:(void *)callbackParam; callbackParam:(void *)callbackParam;

View File

@ -136,6 +136,11 @@ static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char
#endif #endif
} }
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
SDL_SetSystemTheme(UIKit_GetSystemTheme());
}
- (void)setAnimationCallback:(int)interval - (void)setAnimationCallback:(int)interval
callback:(void (*)(void *))callback callback:(void (*)(void *))callback
callbackParam:(void *)callbackParam callbackParam:(void *)callbackParam

View File

@ -1723,6 +1723,10 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
break; break;
case WM_SETTINGCHANGE: 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) { if (wParam == SPI_SETMOUSE || wParam == SPI_SETMOUSESPEED) {
WIN_UpdateMouseSystemScale(); WIN_UpdateMouseSystemScale();
} }

View File

@ -116,6 +116,7 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
} }
device->driverdata = data; device->driverdata = data;
device->wakeup_lock = SDL_CreateMutex(); device->wakeup_lock = SDL_CreateMutex();
device->system_theme = WIN_GetSystemTheme();
#if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__)
data->userDLL = SDL_LoadObject("USER32.DLL"); data->userDLL = SDL_LoadObject("USER32.DLL");
@ -675,8 +676,26 @@ SDL_bool SDL_DXGIGetOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int *
#endif #endif
} }
SDL_bool SDL_SystemTheme WIN_GetSystemTheme(void)
WIN_IsPerMonitorV2DPIAware(_THIS) {
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__) #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__)
SDL_VideoData *data = _this->driverdata; SDL_VideoData *data = _this->driverdata;

View File

@ -466,6 +466,7 @@ extern SDL_bool g_WindowFrameUsableWhileCursorHidden;
typedef struct IDirect3D9 IDirect3D9; typedef struct IDirect3D9 IDirect3D9;
extern SDL_bool D3D_LoadDLL(void **pD3DDLL, IDirect3D9 **pDirect3D9Interface); extern SDL_bool D3D_LoadDLL(void **pD3DDLL, IDirect3D9 **pDirect3D9Interface);
extern SDL_SystemTheme WIN_GetSystemTheme(void);
extern SDL_bool WIN_IsPerMonitorV2DPIAware(_THIS); extern SDL_bool WIN_IsPerMonitorV2DPIAware(_THIS);
#endif /* SDL_windowsvideo_h_ */ #endif /* SDL_windowsvideo_h_ */

View File

@ -40,6 +40,12 @@
#include <SDL3/SDL_syswm.h> #include <SDL3/SDL_syswm.h>
/* 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 */ /* Windows CE compatibility */
#ifndef SWP_NOCOPYBITS #ifndef SWP_NOCOPYBITS
#define SWP_NOCOPYBITS 0 #define SWP_NOCOPYBITS 0
@ -511,6 +517,8 @@ int WIN_CreateWindow(_THIS, SDL_Window *window)
return WIN_SetError("Couldn't create window"); return WIN_SetError("Couldn't create window");
} }
WIN_UpdateDarkModeForHWND(hwnd);
WIN_PumpEvents(_this); WIN_PumpEvents(_this);
if (SetupWindowData(_this, window, hwnd, parent, SDL_TRUE) < 0) { 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__)*/ #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 */ #endif /* SDL_VIDEO_DRIVER_WINDOWS */

View File

@ -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_ClientPointFromSDLFloat(const SDL_Window *window, float x, float y, LONG *xOut, LONG *yOut);
extern void WIN_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept); extern void WIN_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept);
extern int WIN_FlashWindow(_THIS, SDL_Window *window, SDL_FlashOperation operation); extern int WIN_FlashWindow(_THIS, SDL_Window *window, SDL_FlashOperation operation);
extern void WIN_UpdateDarkModeForHWND(HWND hwnd);
/* Ends C function definitions when using C++ */ /* Ends C function definitions when using C++ */
#ifdef __cplusplus #ifdef __cplusplus