From 146464497edc006ba1cf795dffc947ef07c66ee1 Mon Sep 17 00:00:00 2001 From: Semphris Date: Tue, 24 Dec 2024 14:19:34 -0500 Subject: [PATCH] System preferences --- CMakeLists.txt | 1 + include/SDL3/SDL_events.h | 20 ++ include/SDL3/SDL_video.h | 80 ++++++ src/core/linux/SDL_system_preferences.c | 325 ++++++++++++++++++++++++ src/dynapi/SDL_dynapi.sym | 4 + src/dynapi/SDL_dynapi_overrides.h | 4 + src/dynapi/SDL_dynapi_procs.h | 4 + src/events/SDL_events.c | 12 + src/events/SDL_events_c.h | 1 + src/video/cocoa/SDL_cocoavideo.m | 63 +++++ src/video/windows/SDL_windowsvideo.c | 100 ++++++++ test/CMakeLists.txt | 1 + test/testsyspref.c | 88 +++++++ 13 files changed, 703 insertions(+) create mode 100644 src/core/linux/SDL_system_preferences.c create mode 100644 test/testsyspref.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f5bcf2a76b5dd..1aca2d6c9ea99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1708,6 +1708,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_dbus.c" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_system_theme.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_system_preferences.c" ) endif() diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 2850b76394a5a..242ed02b239aa 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -118,6 +118,11 @@ typedef enum SDL_EventType SDL_EVENT_SYSTEM_THEME_CHANGED, /**< The system theme changed */ + SDL_EVENT_SYSTEM_PREFERENCE_CHANGED, /**< A system preference setting changed */ + SDL_EVENT_SYSTEM_TEXT_SCALE_CHANGED, /**< The text scale changed */ + SDL_EVENT_SYSTEM_CURSOR_SCALE_CHANGED, /**< The cursor scale changed */ + SDL_EVENT_SYSTEM_ACCENT_COLOR_CHANGED, /**< The accent color changed */ + /* Display events */ /* 0x150 was SDL_DISPLAYEVENT, reserve the number for sdl2-compat */ SDL_EVENT_DISPLAY_ORIENTATION = 0x151, /**< Display orientation has changed to data1 */ @@ -924,6 +929,20 @@ typedef struct SDL_ClipboardEvent const char **mime_types; /**< current mime types */ } SDL_ClipboardEvent; +/** + * An event triggered when the clipboard contents have changed + * (event.clipboard.*) + * + * \since This struct is available since SDL 3.1.3. + */ +typedef struct SDL_PreferenceEvent +{ + SDL_EventType type; /**< SDL_EVENT_SYSTEM_PREFERENCE_CHANGED */ + Uint32 reserved; + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + SDL_SystemPreference pref; /**< The preference setting that changed */ +} SDL_PreferenceEvent; + /** * Sensor event structure (event.sensor.*) * @@ -1022,6 +1041,7 @@ typedef union SDL_Event SDL_RenderEvent render; /**< Render event data */ SDL_DropEvent drop; /**< Drag and drop event data */ SDL_ClipboardEvent clipboard; /**< Clipboard event data */ + SDL_PreferenceEvent pref; /**< Clipboard event data */ /* This is necessary for ABI compatibility between Visual C++ and GCC. Visual C++ will respect the push pack pragma and use 52 bytes (size of diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 388d1e7a93484..2a815e463d779 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -570,6 +570,86 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetCurrentVideoDriver(void); */ extern SDL_DECLSPEC SDL_SystemTheme SDLCALL SDL_GetSystemTheme(void); +/** + * An enumeration of various boolean system preferences. + * + * Some systems provide a variety of accessibility options that allow users to + * adapt their environment to various conditions. + * + * \since This enum is available since SDL 3.2.0. + * + * \sa SDL_GetSystemPreference + */ +typedef enum SDL_SystemPreference +{ + SDL_SYSTEM_PREFERENCE_REDUCED_MOTION, /**< Disable smooth graphical transitions */ + SDL_SYSTEM_PREFERENCE_REDUCED_TRANSPARENCY, /**< Reduce usage of semi-transparent objects */ + SDL_SYSTEM_PREFERENCE_HIGH_CONTRAST, /**< Use extreme color differences between different elements of the interface */ + SDL_SYSTEM_PREFERENCE_COLORBLIND, /**< Add shape-based distinction between color-coded elements, for example "0" and "1" on switches */ + SDL_SYSTEM_PREFERENCE_PERSIST_SCROLLBARS, /**< Always show scrollbars, don't hide them after a few seconds of inactivity */ + SDL_SYSTEM_PREFERENCE_SCREEN_READER, /**< A screen reader is currently active */ +} SDL_SystemPreference; + +/** + * Get whether or not a certain system preference was enabled by the user. + * + * \param preference the preference to be fetched. + * \returns true if the user enabled the system preference; false if the user + * disabled that setting, or the setting doesn't exist, or an error + * occured. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.2.0. + * + * \sa SDL_SystemPreference + */ +extern SDL_DECLSPEC bool SDLCALL SDL_GetSystemPreference(SDL_SystemPreference preference); + +/** + * Get the system's accent color, as chosen by the user. + * + * If the current system does not have an accent color, false is returned and + * the struct is unaffected. + * + * \param color a pointer to a struct to be filled with the color info. The + * alpha channel is what the operating system returned and may or + * may not be opaque. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.2.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_GetSystemAccentColor(SDL_Color *color); + +/** + * Get the scale factor for text, as set by the user for their system. + * + * If the system does not have a setting to scale the font, 1 is returned. + * + * \returns the preferred scale for text; a scale of 1 means no scaling. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.2.0. + */ +extern SDL_DECLSPEC float SDLCALL SDL_GetSystemTextScale(void); + +/** + * Get the scale factor for the cursor, as set by the user for their system. + * + * If the system does not have a setting to scale the cursor, 1 is returned. + * + * \returns the preferred scale for the cursor; a scale of 1 means no scaling. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.2.0. + */ +extern SDL_DECLSPEC float SDLCALL SDL_GetSystemCursorScale(void); + /** * Get a list of currently connected displays. * diff --git a/src/core/linux/SDL_system_preferences.c b/src/core/linux/SDL_system_preferences.c new file mode 100644 index 0000000000000..59b64af28ac58 --- /dev/null +++ b/src/core/linux/SDL_system_preferences.c @@ -0,0 +1,325 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_dbus.h" +#include "../../events/SDL_events_c.h" + +#include + +#define PORTAL_DESTINATION "org.freedesktop.portal.Desktop" +#define PORTAL_PATH "/org/freedesktop/portal/desktop" +#define PORTAL_INTERFACE "org.freedesktop.portal.Settings" +#define PORTAL_METHOD "ReadOne" + +#define SIGNAL_INTERFACE "org.freedesktop.portal.Settings" +#define SIGNAL_NAME "SettingChanged" +// Signal namespace and key will vary + +typedef struct SystemPrefData +{ + SDL_DBusContext *dbus; + Uint32 contrast; + Uint32 animations; + Uint32 shapes; + Uint32 hide_scrollbars; + Uint32 cursor_size; + double text_scale; +} SystemPrefData; + +static SystemPrefData system_pref_data; + +// FIXME: Type checking for setting Uint32 vs double +static bool DBus_ExtractPref(DBusMessageIter *iter, void *setting) { + SDL_DBusContext *dbus = system_pref_data.dbus; + DBusMessageIter variant_iter; + DBusMessageIter *data_iter; + + // Direct fetch returns UINT32 directly, event sends it wrapped in a variant + if (dbus->message_iter_get_arg_type(iter) == DBUS_TYPE_VARIANT) { + dbus->message_iter_recurse(iter, &variant_iter); + data_iter = &variant_iter; + } else { + data_iter = iter; + } + + switch (dbus->message_iter_get_arg_type(data_iter)) + { + case DBUS_TYPE_UINT32: + case DBUS_TYPE_INT32: + case DBUS_TYPE_BOOLEAN: + dbus->message_iter_get_basic(data_iter, (Uint32 *)setting); + break; + + case DBUS_TYPE_DOUBLE: + dbus->message_iter_get_basic(data_iter, (double *)setting); + break; + + default: + return false; + }; + + + return true; +} + +static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data) { + SDL_DBusContext *dbus = (SDL_DBusContext *)data; + + if (dbus->message_is_signal(msg, SIGNAL_INTERFACE, SIGNAL_NAME)) { + DBusMessageIter signal_iter; + const char *namespace, *key; + + dbus->message_iter_init(msg, &signal_iter); + // Check if the parameters are what we expect + if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING) + goto not_our_signal; + + dbus->message_iter_get_basic(&signal_iter, &namespace); + + // FIXME: For every setting outside org.freedesktop.appearance, DBus + // sends two events rather than one. + if (SDL_strcmp("org.freedesktop.appearance", namespace) == 0) { + if (!dbus->message_iter_next(&signal_iter)) + goto not_our_signal; + + if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING) + goto not_our_signal; + dbus->message_iter_get_basic(&signal_iter, &key); + + if (SDL_strcmp("contrast", key) != 0) + goto not_our_signal; + + if (!dbus->message_iter_next(&signal_iter)) + goto not_our_signal; + + if (!DBus_ExtractPref(&signal_iter, &system_pref_data.contrast)) + goto not_our_signal; + + SDL_SendSystemPreferenceChangedEvent(SDL_SYSTEM_PREFERENCE_HIGH_CONTRAST); + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (SDL_strcmp("org.gnome.desktop.interface", namespace) == 0) { + if (!dbus->message_iter_next(&signal_iter)) + goto not_our_signal; + + if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING) + goto not_our_signal; + + dbus->message_iter_get_basic(&signal_iter, &key); + + if (SDL_strcmp("enable-animations", key) == 0) { + if (!dbus->message_iter_next(&signal_iter)) + goto not_our_signal; + + if (!DBus_ExtractPref(&signal_iter, &system_pref_data.animations)) + goto not_our_signal; + + SDL_SendSystemPreferenceChangedEvent(SDL_SYSTEM_PREFERENCE_REDUCED_MOTION); + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (SDL_strcmp("overlay-scrolling", key) == 0) { + if (!dbus->message_iter_next(&signal_iter)) + goto not_our_signal; + + if (!DBus_ExtractPref(&signal_iter, &system_pref_data.hide_scrollbars)) + goto not_our_signal; + + SDL_SendSystemPreferenceChangedEvent(SDL_SYSTEM_PREFERENCE_PERSIST_SCROLLBARS); + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (SDL_strcmp("cursor-size", key) == 0) { + if (!dbus->message_iter_next(&signal_iter)) + goto not_our_signal; + + if (!DBus_ExtractPref(&signal_iter, &system_pref_data.cursor_size)) + goto not_our_signal; + + SDL_SendAppEvent(SDL_EVENT_SYSTEM_CURSOR_SCALE_CHANGED); + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (SDL_strcmp("text-scaling-factor", key) == 0) { + if (!dbus->message_iter_next(&signal_iter)) + goto not_our_signal; + + if (!DBus_ExtractPref(&signal_iter, &system_pref_data.text_scale)) + goto not_our_signal; + + SDL_SendAppEvent(SDL_EVENT_SYSTEM_TEXT_SCALE_CHANGED); + + return DBUS_HANDLER_RESULT_HANDLED; + } else { + goto not_our_signal; + } + } else if (SDL_strcmp("org.gnome.desktop.a11y.interface", namespace) == 0) { + if (!dbus->message_iter_next(&signal_iter)) + goto not_our_signal; + + if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING) + goto not_our_signal; + + dbus->message_iter_get_basic(&signal_iter, &key); + + if (SDL_strcmp("show-status-shapes", key) == 0) { + if (!dbus->message_iter_next(&signal_iter)) + goto not_our_signal; + + if (!DBus_ExtractPref(&signal_iter, &system_pref_data.shapes)) + goto not_our_signal; + + SDL_SendSystemPreferenceChangedEvent(SDL_SYSTEM_PREFERENCE_COLORBLIND); + + return DBUS_HANDLER_RESULT_HANDLED; + } else { + goto not_our_signal; + } + } else { + goto not_our_signal; + } + } +not_our_signal: + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +bool SDL_SystemPref_Init(void) +{ + SDL_DBusContext *dbus = SDL_DBus_GetContext(); + DBusMessage *msg; + static const char *namespaces[] = { + "org.freedesktop.appearance", + "org.gnome.desktop.interface", + "org.gnome.desktop.a11y.interface", + "org.gnome.desktop.interface", + "org.gnome.desktop.interface", + "org.gnome.desktop.interface", + }; + static const char *keys[] = { + "contrast", + "enable-animations", + "show-status-shapes", + "overlay-scrolling", + "cursor-size", + "text-scaling-factor", + }; + static void *ptrs[] = { + &system_pref_data.contrast, + &system_pref_data.animations, + &system_pref_data.shapes, + &system_pref_data.hide_scrollbars, + &system_pref_data.cursor_size, + &system_pref_data.text_scale, + }; + + system_pref_data.contrast = false; + system_pref_data.animations = true; + system_pref_data.shapes = false; + system_pref_data.hide_scrollbars = true; + system_pref_data.cursor_size = 24; + system_pref_data.text_scale = 1.0f; + + system_pref_data.dbus = dbus; + if (!dbus) { + return false; + } + + for (int i = 0; i < sizeof(namespaces) / sizeof(*namespaces); i++) { + const char *namespace = namespaces[i]; + const char *key = keys[i]; + + msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, PORTAL_METHOD); + if (msg) { + if (dbus->message_append_args(msg, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) { + DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, 300, NULL); + if (reply) { + DBusMessageIter reply_iter, variant_outer_iter; + + dbus->message_iter_init(reply, &reply_iter); + // The response has signature <> + if (dbus->message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_VARIANT) + goto incorrect_type; + + dbus->message_iter_recurse(&reply_iter, &variant_outer_iter); + if (!DBus_ExtractPref(&variant_outer_iter, ptrs[i])) + goto incorrect_type; + +incorrect_type: + dbus->message_unref(reply); + } + } + dbus->message_unref(msg); + } + + char buffer[2048]; + if (SDL_snprintf(buffer, sizeof(buffer), "type='signal', interface='"SIGNAL_INTERFACE"'," + "member='"SIGNAL_NAME"', arg0='%s',arg1='%s'", namespace, key) >= sizeof(buffer)) { + SDL_SetError("Binding system prefereces DBus key: buffer too small, this is a bug"); + return false; + } + + dbus->bus_add_match(dbus->session_conn, buffer, NULL); + } + + dbus->connection_add_filter(dbus->session_conn, + &DBus_MessageFilter, dbus, NULL); + dbus->connection_flush(dbus->session_conn); + return true; +} + +bool SDL_GetSystemPreference(SDL_SystemPreference preference) +{ + static bool is_init = false; + + if (!is_init && SDL_SystemPref_Init()) + is_init = true; + + switch (preference) + { + case SDL_SYSTEM_PREFERENCE_REDUCED_MOTION: + return !system_pref_data.animations; + + case SDL_SYSTEM_PREFERENCE_HIGH_CONTRAST: + return !!system_pref_data.contrast; + + case SDL_SYSTEM_PREFERENCE_COLORBLIND: + return !!system_pref_data.shapes; + + case SDL_SYSTEM_PREFERENCE_PERSIST_SCROLLBARS: + return !system_pref_data.hide_scrollbars; + + default: + return SDL_Unsupported(); + } +} + +bool SDL_GetSystemAccentColor(SDL_Color *color) +{ + return SDL_Unsupported(); +} + +float SDL_GetSystemTextScale(void) +{ + return (float) system_pref_data.text_scale; +} + +float SDL_GetSystemCursorScale(void) +{ + return (float) system_pref_data.cursor_size / 24.0f; +} diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 47ddb603d4150..c082ba8d2257a 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1230,6 +1230,10 @@ SDL3_0.0.0 { SDL_GetTrayMenuParentEntry; SDL_GetTrayMenuParentTray; SDL_GetThreadState; + SDL_GetSystemPreference; + SDL_GetSystemAccentColor; + SDL_GetSystemTextScale; + SDL_GetSystemCursorScale; # 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 cf47d1c93caa4..c38da4717d932 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1255,3 +1255,7 @@ #define SDL_GetTrayMenuParentEntry SDL_GetTrayMenuParentEntry_REAL #define SDL_GetTrayMenuParentTray SDL_GetTrayMenuParentTray_REAL #define SDL_GetThreadState SDL_GetThreadState_REAL +#define SDL_GetSystemPreference SDL_GetSystemPreference_REAL +#define SDL_GetSystemAccentColor SDL_GetSystemAccentColor_REAL +#define SDL_GetSystemTextScale SDL_GetSystemTextScale_REAL +#define SDL_GetSystemCursorScale SDL_GetSystemCursorScale_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 01f6a4de1df2d..f5bcfe0c760c0 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1263,3 +1263,7 @@ SDL_DYNAPI_PROC(SDL_TrayMenu*,SDL_GetTrayEntryParent,(SDL_TrayEntry *a),(a),retu SDL_DYNAPI_PROC(SDL_TrayEntry*,SDL_GetTrayMenuParentEntry,(SDL_TrayMenu *a),(a),return) SDL_DYNAPI_PROC(SDL_Tray*,SDL_GetTrayMenuParentTray,(SDL_TrayMenu *a),(a),return) SDL_DYNAPI_PROC(SDL_ThreadState,SDL_GetThreadState,(SDL_Thread *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_GetSystemPreference,(SDL_SystemPreference a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_GetSystemAccentColor,(SDL_Color *a),(a),return) +SDL_DYNAPI_PROC(float,SDL_GetSystemTextScale,(void),(),return) +SDL_DYNAPI_PROC(float,SDL_GetSystemCursorScale,(void),(),return) diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index bc04ee91a348c..0c4f62d2b5717 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -1958,6 +1958,18 @@ void SDL_SendSystemThemeChangedEvent(void) SDL_SendAppEvent(SDL_EVENT_SYSTEM_THEME_CHANGED); } +void SDL_SendSystemPreferenceChangedEvent(SDL_SystemPreference preference) +{ + if (SDL_EventEnabled(SDL_EVENT_SYSTEM_PREFERENCE_CHANGED)) { + SDL_Event event; + event.type = SDL_EVENT_SYSTEM_PREFERENCE_CHANGED; + event.common.timestamp = 0; + event.pref.pref = preference; + + SDL_PushEvent(&event); + } +} + bool SDL_InitEvents(void) { #ifdef SDL_PLATFORM_ANDROID diff --git a/src/events/SDL_events_c.h b/src/events/SDL_events_c.h index d8f86eb3c006a..9d212f28cca97 100644 --- a/src/events/SDL_events_c.h +++ b/src/events/SDL_events_c.h @@ -45,6 +45,7 @@ extern void SDL_SendAppEvent(SDL_EventType eventType); extern void SDL_SendKeymapChangedEvent(void); extern void SDL_SendLocaleChangedEvent(void); extern void SDL_SendSystemThemeChangedEvent(void); +extern void SDL_SendSystemPreferenceChangedEvent(SDL_SystemPreference preference); extern void *SDL_AllocateTemporaryMemory(size_t size); extern const char *SDL_CreateTemporaryString(const char *string); diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index c2c0bb3be03db..b1818076ed71c 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -256,6 +256,69 @@ SDL_SystemTheme Cocoa_GetSystemTheme(void) return SDL_SYSTEM_THEME_LIGHT; } +bool SDL_GetSystemPreference(SDL_SystemPreference preference) +{ + switch(preference) + { + case SDL_SYSTEM_PREFERENCE_REDUCED_MOTION: + return [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion]; + + case SDL_SYSTEM_PREFERENCE_REDUCED_TRANSPARENCY: + return [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceTransparency]; + + case SDL_SYSTEM_PREFERENCE_HIGH_CONTRAST: + return [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldIncreaseContrast]; + + case SDL_SYSTEM_PREFERENCE_COLORBLIND: + return [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldDifferentiateWithoutColor]; + + case SDL_SYSTEM_PREFERENCE_SCREEN_READER: + return [[NSWorkspace sharedWorkspace] isVoiceOverEnabled]; + + // FIXME: This doesn't work on my Mac, and I can't find any other way to do it. + // case SDL_SYSTEM_PREFERENCE_PERSIST_SCROLLBARS: + // return [[NSUserDefaults standardUserDefaults] integerForKey:@"AppleShowScrollBars"] == 1; + + default: + return SDL_Unsupported(); + } +} + +// https://stackoverflow.com/questions/58543327/detecting-when-macos-10-14-accent-color-has-changed +bool SDL_GetSystemAccentColor(SDL_Color *color) +{ + @autoreleasepool { + if (!color) { + return SDL_InvalidParamError("color"); + } + + NSColor *accent = [NSColor controlAccentColor]; + + NSColor *rgbAccent = [accent colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + + CGFloat r, g, b, a; + [rgbAccent getRed:&r green:&g blue:&b alpha:&a]; + + color->r = (Uint8) 255.0f * r; + color->g = (Uint8) 255.0f * g; + color->b = (Uint8) 255.0f * b; + color->a = (Uint8) 255.0f * a; + + return true; + } +} + +float SDL_GetSystemTextScale(void) +{ + return 1.0f; +} + +float SDL_GetSystemCursorScale(void) +{ + return 1.0f; +} + + // This function assumes that it's called from within an autorelease pool NSImage *Cocoa_CreateImage(SDL_Surface *surface) { diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 982c2a77a1ed9..033e0ba305742 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -749,6 +749,106 @@ SDL_SystemTheme WIN_GetSystemTheme(void) return theme; } +bool SDL_GetSystemPreference(SDL_SystemPreference preference) +{ + switch(preference) + { + case SDL_SYSTEM_PREFERENCE_REDUCED_MOTION: + { + BOOL option = false; + + if (!SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &option, 0)) { + return WIN_SetError("Could not invoke SystemParametersInfoW with SDL_SYSTEM_PREFERENCE_REDUCED_MOTION"); + } + + return !option; + } + + case SDL_SYSTEM_PREFERENCE_REDUCED_TRANSPARENCY: + { + HKEY hKey; + if (RegOpenKeyEx(HKEY_CURRENT_USER, + TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"), + 0, + KEY_READ, + &hKey) == ERROR_SUCCESS) { + DWORD enableTransparency; + DWORD size = sizeof(enableTransparency); + if (RegQueryValueEx(hKey, TEXT("EnableTransparency"), NULL, NULL, (LPBYTE)&enableTransparency, &size) == ERROR_SUCCESS) { + RegCloseKey(hKey); + return !enableTransparency; + } + RegCloseKey(hKey); + } + + return false; + } + + case SDL_SYSTEM_PREFERENCE_HIGH_CONTRAST: + { + HIGHCONTRAST high_contrast_data; + high_contrast_data.cbSize = sizeof(HIGHCONTRAST); + + if (!SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(HIGHCONTRAST), &high_contrast_data, 0)) { + return WIN_SetError("Could not invoke SystemParametersInfoW with SDL_SYSTEM_PREFERENCE_HIGH_CONTRAST"); + } + + return high_contrast_data.dwFlags & HCF_HIGHCONTRASTON; + } + + case SDL_SYSTEM_PREFERENCE_PERSIST_SCROLLBARS: + { + HKEY hKey; + if (RegOpenKeyEx(HKEY_CURRENT_USER, + TEXT("Control Panel\\Accessibility"), + 0, + KEY_READ, + &hKey) == ERROR_SUCCESS) { + DWORD autoHideScrollBars; + DWORD size = sizeof(autoHideScrollBars); + if (RegQueryValueEx(hKey, TEXT("DynamicScrollbars"), NULL, NULL, (LPBYTE)&autoHideScrollBars, &size) == ERROR_SUCCESS) { + RegCloseKey(hKey); + return !autoHideScrollBars; + } + RegCloseKey(hKey); + } + + return false; + } + + case SDL_SYSTEM_PREFERENCE_SCREEN_READER: + { + BOOL option = false; + + // FIXME: Documentation states that this won't work if the screen + // reader is Windows' built-in "Narrator" program + if (!SystemParametersInfoW(SPI_GETSCREENREADER, 0, &option, 0)) { + return WIN_SetError("Could not invoke SystemParametersInfoW with SPI_GETSCREENREADER"); + } + + return option; + } + + default: + return SDL_Unsupported(); + } +} + +bool SDL_GetSystemAccentColor(SDL_Color *color) +{ + return false; +} + +float SDL_GetSystemTextScale(void) +{ + return 1.0f; +} + +float SDL_GetSystemCursorScale(void) +{ + return 1.0f; +} + bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this) { #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1a799482b047e..9aa3803df0351 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -408,6 +408,7 @@ add_sdl_test_executable(testtime SOURCES testtime.c) add_sdl_test_executable(testmanymouse SOURCES testmanymouse.c) add_sdl_test_executable(testmodal SOURCES testmodal.c) add_sdl_test_executable(testtray SOURCES testtray.c) +add_sdl_test_executable(testsyspref SOURCES testsyspref.c) add_sdl_test_executable(testprocess diff --git a/test/testsyspref.c b/test/testsyspref.c new file mode 100644 index 0000000000000..cef9e6735794d --- /dev/null +++ b/test/testsyspref.c @@ -0,0 +1,88 @@ +#include +#include +#include + +int main(int argc, char *argv[]) +{ + SDL_Color color; + SDL_Event e; + SDLTest_CommonState *state; + int i; + + state = SDLTest_CommonCreateState(argv, 0); + if (state == NULL) { + return 1; + } + + /* Parse commandline */ + for (i = 1; i < argc;) { + int consumed; + + consumed = SDLTest_CommonArg(state, i); + + if (consumed <= 0) { + static const char *options[] = { NULL }; + SDLTest_CommonLogUsage(state, argv[0], options); + return 1; + } + + i += consumed; + } + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("SDL_Init failed (%s)", SDL_GetError()); + return 1; + } + +#define LOG(val) SDL_Log(#val ": %d\n", SDL_GetSystemPreference(val)) + LOG(SDL_SYSTEM_PREFERENCE_REDUCED_MOTION); + LOG(SDL_SYSTEM_PREFERENCE_REDUCED_TRANSPARENCY); + LOG(SDL_SYSTEM_PREFERENCE_HIGH_CONTRAST); + LOG(SDL_SYSTEM_PREFERENCE_COLORBLIND); + LOG(SDL_SYSTEM_PREFERENCE_PERSIST_SCROLLBARS); + LOG(SDL_SYSTEM_PREFERENCE_SCREEN_READER); +#undef LOG + + SDL_Log("Text scale: %f\n", SDL_GetSystemTextScale()); + SDL_Log("Cursor scale: %f\n", SDL_GetSystemCursorScale()); + + if (SDL_GetSystemAccentColor(&color)) { + SDL_Log("Accent color: %d %d %d %d\n", color.r, color.g, color.b, color.a); + } else { + SDL_Log("Could not get accent color: %s\n", SDL_GetError()); + } + + while (SDL_WaitEvent(&e)) { + if (e.type == SDL_EVENT_QUIT) { + break; + } else if (e.type == SDL_EVENT_SYSTEM_PREFERENCE_CHANGED) { + switch (e.pref.pref) { +#define CHECK(val) case val: SDL_Log(#val " updated: %d\n", SDL_GetSystemPreference(val)); break; + CHECK(SDL_SYSTEM_PREFERENCE_REDUCED_MOTION); + CHECK(SDL_SYSTEM_PREFERENCE_REDUCED_TRANSPARENCY); + CHECK(SDL_SYSTEM_PREFERENCE_HIGH_CONTRAST); + CHECK(SDL_SYSTEM_PREFERENCE_COLORBLIND); + CHECK(SDL_SYSTEM_PREFERENCE_PERSIST_SCROLLBARS); + CHECK(SDL_SYSTEM_PREFERENCE_SCREEN_READER); +#undef CHECK + default: + SDL_Log("Unknown value '%d' updated: %d\n", e.pref.pref, SDL_GetSystemPreference(e.pref.pref)); + } + } else if (e.type == SDL_EVENT_SYSTEM_TEXT_SCALE_CHANGED) { + SDL_Log("Text scaling updated: %f\n", SDL_GetSystemTextScale()); + } else if (e.type == SDL_EVENT_SYSTEM_CURSOR_SCALE_CHANGED) { + SDL_Log("Cursor scaling updated: %f\n", SDL_GetSystemCursorScale()); + } else if (e.type == SDL_EVENT_SYSTEM_ACCENT_COLOR_CHANGED) { + if (SDL_GetSystemAccentColor(&color)) { + SDL_Log("Accent color updated: %d %d %d %d\n", color.r, color.g, color.b, color.a); + } else { + SDL_Log("Accent color updated, could not get accent color: %s\n", SDL_GetError()); + } + } + } + + SDL_Quit(); + SDLTest_CommonDestroyState(state); + + return 0; +}