From 6677fad1c81ddc816169f736098755fc0c06fea2 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 9 Oct 2025 15:11:13 -0700
Subject: [PATCH] Added SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED
Fixes https://github.com/libsdl-org/SDL/issues/12785
---
include/SDL3/SDL_events.h | 3 +-
src/events/SDL_events.c | 1 +
src/test/SDL_test_common.c | 8 +++++
src/video/SDL_video.c | 2 ++
src/video/cocoa/SDL_cocoamodes.h | 1 +
src/video/cocoa/SDL_cocoamodes.m | 46 ++++++++++++++++-----------
src/video/windows/SDL_windowsevents.c | 3 ++
src/video/windows/SDL_windowsmodes.c | 8 +++++
src/video/windows/SDL_windowsmodes.h | 1 +
src/video/x11/SDL_x11events.c | 40 ++++++++++++-----------
10 files changed, 75 insertions(+), 38 deletions(-)
diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index 32dc1b53bfbf5..470dd608d1674 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -127,8 +127,9 @@ typedef enum SDL_EventType
SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED, /**< Display has changed desktop mode */
SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED, /**< Display has changed current mode */
SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED, /**< Display has changed content scale */
+ SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, /**< Display has changed usable bounds */
SDL_EVENT_DISPLAY_FIRST = SDL_EVENT_DISPLAY_ORIENTATION,
- SDL_EVENT_DISPLAY_LAST = SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED,
+ SDL_EVENT_DISPLAY_LAST = SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED,
/* Window events */
/* 0x200 was SDL_WINDOWEVENT, reserve the number for sdl2-compat */
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index e98af739902d9..fa1122140a961 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -525,6 +525,7 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen)
SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED);
SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED);
SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED);
+ SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED);
#undef SDL_DISPLAYEVENT_CASE
#define SDL_WINDOWEVENT_CASE(x) \
diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c
index bffa61527c4df..b5ffc9a46f75a 100644
--- a/src/test/SDL_test_common.c
+++ b/src/test/SDL_test_common.c
@@ -1626,6 +1626,14 @@ void SDLTest_PrintEvent(const SDL_Event *event)
event->display.displayID, (int)(scale * 100.0f));
}
break;
+ case SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED:
+ {
+ SDL_Rect bounds;
+ SDL_GetDisplayUsableBounds(event->display.displayID, &bounds);
+ SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " changed usable bounds to %dx%d at %d,%d",
+ event->display.displayID, bounds.w, bounds.h, bounds.x, bounds.y);
+ }
+ break;
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " desktop mode changed to %" SDL_PRIs32 "x%" SDL_PRIs32,
event->display.displayID, event->display.data1, event->display.data2);
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index d961db578945e..a016f0800c8ff 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -1064,6 +1064,7 @@ bool SDL_GetDisplayBounds(SDL_DisplayID displayID, SDL_Rect *rect)
}
if (_this->GetDisplayBounds) {
+ SDL_zerop(rect);
if (_this->GetDisplayBounds(_this, display, rect)) {
return true;
}
@@ -1103,6 +1104,7 @@ bool SDL_GetDisplayUsableBounds(SDL_DisplayID displayID, SDL_Rect *rect)
}
if (_this->GetDisplayUsableBounds) {
+ SDL_zerop(rect);
if (_this->GetDisplayUsableBounds(_this, display, rect)) {
return true;
}
diff --git a/src/video/cocoa/SDL_cocoamodes.h b/src/video/cocoa/SDL_cocoamodes.h
index 37f3aa584029c..1dc000c304e33 100644
--- a/src/video/cocoa/SDL_cocoamodes.h
+++ b/src/video/cocoa/SDL_cocoamodes.h
@@ -26,6 +26,7 @@
struct SDL_DisplayData
{
CGDirectDisplayID display;
+ SDL_Rect usable_bounds;
};
struct SDL_DisplayModeData
diff --git a/src/video/cocoa/SDL_cocoamodes.m b/src/video/cocoa/SDL_cocoamodes.m
index 4c168de8d356e..974ef570b8d2d 100644
--- a/src/video/cocoa/SDL_cocoamodes.m
+++ b/src/video/cocoa/SDL_cocoamodes.m
@@ -323,6 +323,21 @@ static void Cocoa_GetHDRProperties(CGDirectDisplayID displayID, SDL_HDROutputPro
}
}
+static bool Cocoa_GetUsableBounds(CGDirectDisplayID displayID, SDL_Rect *rect)
+{
+ NSScreen *screen = GetNSScreenForDisplayID(displayID);
+
+ if (screen == nil) {
+ return false;
+ }
+
+ const NSRect frame = [screen visibleFrame];
+ rect->x = (int)frame.origin.x;
+ rect->y = (int)(CGDisplayPixelsHigh(kCGDirectMainDisplay) - frame.origin.y - frame.size.height);
+ rect->w = (int)frame.size.width;
+ rect->h = (int)frame.size.height;
+ return true;
+}
bool Cocoa_AddDisplay(CGDirectDisplayID display, bool send_event)
{
@@ -331,7 +346,7 @@ bool Cocoa_AddDisplay(CGDirectDisplayID display, bool send_event)
return false;
}
- SDL_DisplayData *displaydata = (SDL_DisplayData *)SDL_malloc(sizeof(*displaydata));
+ SDL_DisplayData *displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata));
if (!displaydata) {
CGDisplayModeRelease(moderef);
return false;
@@ -359,6 +374,8 @@ bool Cocoa_AddDisplay(CGDirectDisplayID display, bool send_event)
Cocoa_GetHDRProperties(displaydata->display, &viddisplay.HDR);
+ Cocoa_GetUsableBounds(displaydata->display, &displaydata->usable_bounds);
+
viddisplay.desktop_mode = mode;
viddisplay.internal = displaydata;
const bool retval = SDL_AddVideoDisplay(&viddisplay, send_event);
@@ -538,6 +555,13 @@ void Cocoa_UpdateDisplays(SDL_VideoDevice *_this)
Cocoa_GetHDRProperties(displaydata->display, &HDR);
SDL_SetDisplayHDRProperties(display, &HDR);
+
+ SDL_Rect rect;
+ if (Cocoa_GetUsableBounds(displaydata->display, &rect) &&
+ SDL_memcmp(&displaydata->usable_bounds, &rect, sizeof(rect)) != 0) {
+ SDL_memcpy(&displaydata->usable_bounds, &rect, sizeof(rect));
+ SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, 0, 0);
+ }
}
}
@@ -556,24 +580,10 @@ bool Cocoa_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, S
bool Cocoa_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
{
- @autoreleasepool {
- SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal;
- NSScreen *screen = GetNSScreenForDisplayID(displaydata->display);
-
- if (screen == nil) {
- return SDL_SetError("Couldn't get NSScreen for display");
- }
-
- {
- const NSRect frame = [screen visibleFrame];
- rect->x = (int)frame.origin.x;
- rect->y = (int)(CGDisplayPixelsHigh(kCGDirectMainDisplay) - frame.origin.y - frame.size.height);
- rect->w = (int)frame.size.width;
- rect->h = (int)frame.size.height;
- }
+ SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal;
- return true;
- }
+ SDL_memcpy(rect, &displaydata->usable_bounds, sizeof(*rect));
+ return true;
}
bool Cocoa_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 667223c27cc6f..9854767c3054e 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -2422,6 +2422,9 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
if (wParam == SPI_SETMOUSE || wParam == SPI_SETMOUSESPEED) {
WIN_UpdateMouseSystemScale();
}
+ if (wParam == SPI_SETWORKAREA) {
+ WIN_UpdateDisplayUsableBounds(SDL_GetVideoDevice());
+ }
break;
#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
diff --git a/src/video/windows/SDL_windowsmodes.c b/src/video/windows/SDL_windowsmodes.c
index 833f78c937396..6123d8232c78f 100644
--- a/src/video/windows/SDL_windowsmodes.c
+++ b/src/video/windows/SDL_windowsmodes.c
@@ -933,6 +933,14 @@ void WIN_RefreshDisplays(SDL_VideoDevice *_this)
}
}
+void WIN_UpdateDisplayUsableBounds(SDL_VideoDevice *_this)
+{
+ // This almost never happens, so just go ahead and send update events for all displays
+ for (int i = 0; i < _this->num_displays; ++i) {
+ SDL_SendDisplayEvent(_this->displays[i], SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, 0, 0);
+ }
+}
+
void WIN_QuitModes(SDL_VideoDevice *_this)
{
// All fullscreen windows should have restored modes by now
diff --git a/src/video/windows/SDL_windowsmodes.h b/src/video/windows/SDL_windowsmodes.h
index e49817ca9540c..7942a670a59f7 100644
--- a/src/video/windows/SDL_windowsmodes.h
+++ b/src/video/windows/SDL_windowsmodes.h
@@ -50,6 +50,7 @@ extern bool WIN_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay
extern bool WIN_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display);
extern bool WIN_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode);
extern void WIN_RefreshDisplays(SDL_VideoDevice *_this);
+extern void WIN_UpdateDisplayUsableBounds(SDL_VideoDevice *_this);
extern void WIN_QuitModes(SDL_VideoDevice *_this);
#endif // SDL_windowsmodes_h_
diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index 1299be7368393..15066f95a2dd9 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -1418,31 +1418,33 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
X11_UpdateKeymap(_this, true);
}
- } else if (xevent->type == PropertyNotify && videodata && videodata->windowlist) {
+ } else if (xevent->type == PropertyNotify && videodata) {
char *name_of_atom = X11_XGetAtomName(display, xevent->xproperty.atom);
-
- if (SDL_strncmp(name_of_atom, "_ICC_PROFILE", sizeof("_ICC_PROFILE") - 1) == 0) {
- XWindowAttributes attrib;
- int screennum;
- for (i = 0; i < videodata->numwindows; ++i) {
- if (videodata->windowlist[i] != NULL) {
- data = videodata->windowlist[i];
- X11_XGetWindowAttributes(display, data->xwindow, &attrib);
- screennum = X11_XScreenNumberOfScreen(attrib.screen);
- if (screennum == 0 && SDL_strcmp(name_of_atom, "_ICC_PROFILE") == 0) {
- SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
- } else if (SDL_strncmp(name_of_atom, "_ICC_PROFILE_", sizeof("_ICC_PROFILE_") - 1) == 0 && SDL_strlen(name_of_atom) > sizeof("_ICC_PROFILE_") - 1) {
- int iccscreennum = SDL_atoi(&name_of_atom[sizeof("_ICC_PROFILE_") - 1]);
-
- if (screennum == iccscreennum) {
+ if (name_of_atom) {
+ if (SDL_startswith(name_of_atom, "_ICC_PROFILE")) {
+ XWindowAttributes attrib;
+ int screennum;
+ for (i = 0; i < videodata->numwindows; ++i) {
+ if (videodata->windowlist[i] != NULL) {
+ data = videodata->windowlist[i];
+ X11_XGetWindowAttributes(display, data->xwindow, &attrib);
+ screennum = X11_XScreenNumberOfScreen(attrib.screen);
+ if (screennum == 0 && SDL_strcmp(name_of_atom, "_ICC_PROFILE") == 0) {
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
+ } else if (SDL_strncmp(name_of_atom, "_ICC_PROFILE_", sizeof("_ICC_PROFILE_") - 1) == 0 && SDL_strlen(name_of_atom) > sizeof("_ICC_PROFILE_") - 1) {
+ int iccscreennum = SDL_atoi(&name_of_atom[sizeof("_ICC_PROFILE_") - 1]);
+
+ if (screennum == iccscreennum) {
+ SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
+ }
}
}
}
+ } else if (SDL_strcmp(name_of_atom, "_NET_WORKAREA") == 0) {
+ for (i = 0; i < _this->num_displays; ++i) {
+ SDL_SendDisplayEvent(_this->displays[i], SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, 0, 0);
+ }
}
- }
-
- if (name_of_atom) {
X11_XFree(name_of_atom);
}
}