From a364e3fd21c4d1e5ff64191ecc59213104edc205 Mon Sep 17 00:00:00 2001 From: Damiano Ricciardi Date: Sat, 12 Jul 2025 20:01:26 +0200 Subject: [PATCH 1/3] Add c code --- src_c/display.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src_c/display.c b/src_c/display.c index 6934ca808d..74e812928c 100644 --- a/src_c/display.c +++ b/src_c/display.c @@ -2296,6 +2296,51 @@ pg_get_desktop_screen_sizes(PyObject *self, PyObject *_null) return result; } +static PyObject * +pg_get_desktop_usable_bounds(PyObject *self, PyObject *_null) +{ + int display_count, i; + + VIDEO_INIT_CHECK(); + +#if PG_SDL3 + SDL_DisplayID *displays = SDL_GetDisplays(&display_count); + if (displays == NULL) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } +#else + display_count = SDL_GetNumVideoDisplays(); + if (display_count < 0) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } +#endif + + result = PyList_New(display_count); + if (!result) { + return NULL; + } + + for (i = 0; i < display_count; i++) { + SDL_Rect bounds; +#if PG_SDL3 + SDL_DisplayID display_id = displays[i]; + if (SDL_GetDisplayUsableBounds(display_id, &bounds) == SDL_FALSE) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } +#else + if (SDL_GetDisplayUsableBounds(i, &bounds) < 0) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } +#endif + PyObject *pg_rect = pgRect_New(&bounds); + if (pg_rect == NULL) { + return NULL; /* exception already set */ + } + PyList_SET_ITEM(result, i, pg_rect); + } + return result; +} + static PyObject * pg_is_fullscreen(PyObject *self, PyObject *_null) { From f1f8177489aa4fbf51cf4bd14606513b3eabd6f8 Mon Sep 17 00:00:00 2001 From: Damiano Ricciardi Date: Sun, 13 Jul 2025 09:44:50 +0200 Subject: [PATCH 2/3] Finish adding the feature --- buildconfig/stubs/pygame/display.pyi | 21 ++++++++++++++++++++- src_c/display.c | 3 +++ src_c/doc/display_doc.h | 1 + test/display_test.py | 10 ++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/buildconfig/stubs/pygame/display.pyi b/buildconfig/stubs/pygame/display.pyi index c46189eadc..b48361ce23 100644 --- a/buildconfig/stubs/pygame/display.pyi +++ b/buildconfig/stubs/pygame/display.pyi @@ -52,10 +52,11 @@ required). """ from collections.abc import Iterable -from typing import Literal, Optional, Union, overload +from typing import Literal, Optional, overload from pygame._sdl2 import Window from pygame.constants import FULLSCREEN +from pygame.rect import Rect from pygame.surface import Surface from pygame.typing import ( ColorLike, @@ -421,6 +422,24 @@ def get_desktop_sizes() -> list[tuple[int, int]]: .. versionaddedold:: 2.0.0 """ +def get_desktop_usable_bounds() -> list[Rect]: + """Get the bounding rects of the usable area of active desktops. + + Returns a list of :class:`pygame.Rect` representing the bounding rectangles + of the usable area of the currently configured virtual desktops. + + The usable area is the desktop area minus the areas that are used by + the operating system. This includes, for example, taskbars. The location + and size of the unusable areas depend on the operating system and its + configuration. The usable area is usually the area that maximized windows + fill. + + The length of the list is not the same as the number of attached monitors, + as a desktop can be mirrored across multiple monitors. + + .. versionadded:: 2.5.6 + """ + def list_modes( depth: int = 0, flags: int = FULLSCREEN, diff --git a/src_c/display.c b/src_c/display.c index 74e812928c..35b8916b6b 100644 --- a/src_c/display.c +++ b/src_c/display.c @@ -2300,6 +2300,7 @@ static PyObject * pg_get_desktop_usable_bounds(PyObject *self, PyObject *_null) { int display_count, i; + PyObject *result; VIDEO_INIT_CHECK(); @@ -3175,6 +3176,8 @@ static PyMethodDef _pg_display_methods[] = { METH_NOARGS, "provisional API, subject to change"}, {"get_desktop_sizes", (PyCFunction)pg_get_desktop_screen_sizes, METH_NOARGS, DOC_DISPLAY_GETDESKTOPSIZES}, + {"get_desktop_usable_bounds", (PyCFunction)pg_get_desktop_usable_bounds, + METH_NOARGS, DOC_DISPLAY_GETDESKTOPUSABLEBOUNDS}, {"is_fullscreen", (PyCFunction)pg_is_fullscreen, METH_NOARGS, DOC_DISPLAY_ISFULLSCREEN}, {"is_vsync", (PyCFunction)pg_is_vsync, METH_NOARGS, DOC_DISPLAY_ISVSYNC}, diff --git a/src_c/doc/display_doc.h b/src_c/doc/display_doc.h index f449077645..3b0b732f8c 100644 --- a/src_c/doc/display_doc.h +++ b/src_c/doc/display_doc.h @@ -11,6 +11,7 @@ #define DOC_DISPLAY_INFO "Info() -> _VidInfo\nCreate a video display information object." #define DOC_DISPLAY_GETWMINFO "get_wm_info() -> dict[str, int]\nGet information about the current windowing system." #define DOC_DISPLAY_GETDESKTOPSIZES "get_desktop_sizes() -> list[tuple[int, int]]\nGet sizes of active desktops." +#define DOC_DISPLAY_GETDESKTOPUSABLEBOUNDS "get_desktop_usable_bounds() -> list[Rect]\nGet the bounding rects of the usable area of active desktops." #define DOC_DISPLAY_LISTMODES "list_modes(depth=0, flags=FULLSCREEN, display=0) -> list[tuple[int, int]]\nGet list of available fullscreen modes." #define DOC_DISPLAY_MODEOK "mode_ok(size, flags=0, depth=0, display=0) -> int\nPick the best color depth for a display mode." #define DOC_DISPLAY_GLGETATTRIBUTE "gl_get_attribute(flag, /) -> int\nGet the value for an OpenGL flag for the current display." diff --git a/test/display_test.py b/test/display_test.py index 3d7b91641f..f03baa55e6 100644 --- a/test/display_test.py +++ b/test/display_test.py @@ -697,6 +697,16 @@ def test_get_set_window_position(self): self.assertEqual(position[0], 420) self.assertEqual(position[1], 360) + def test_get_desktop_usable_bounds(self): + bounds = pygame.display.get_desktop_usable_bounds() + sizes = pygame.display.get_desktop_sizes() + self.assertIsInstance(bounds, list) + for i, bound in enumerate(bounds): + self.assertIsInstance(bound, pygame.Rect) + size = sizes[i] + self.assertLessEqual(bound.w, size[0]) + self.assertLessEqual(bound.h, size[1]) + class DisplayUpdateTest(unittest.TestCase): def question(self, qstr): From 0cc2c49068573bdf2b9f69260aee98913b987920 Mon Sep 17 00:00:00 2001 From: Dan Lawrence Date: Mon, 25 Aug 2025 11:50:27 +0100 Subject: [PATCH 3/3] fix memory leaks Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src_c/display.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src_c/display.c b/src_c/display.c index 35b8916b6b..49a90536cd 100644 --- a/src_c/display.c +++ b/src_c/display.c @@ -2300,7 +2300,7 @@ static PyObject * pg_get_desktop_usable_bounds(PyObject *self, PyObject *_null) { int display_count, i; - PyObject *result; + PyObject *result = NULL; VIDEO_INIT_CHECK(); @@ -2318,6 +2318,9 @@ pg_get_desktop_usable_bounds(PyObject *self, PyObject *_null) result = PyList_New(display_count); if (!result) { +#if PG_SDL3 + SDL_free(displays); +#endif return NULL; } @@ -2326,22 +2329,32 @@ pg_get_desktop_usable_bounds(PyObject *self, PyObject *_null) #if PG_SDL3 SDL_DisplayID display_id = displays[i]; if (SDL_GetDisplayUsableBounds(display_id, &bounds) == SDL_FALSE) { + Py_DECREF(result); + SDL_free(displays); return RAISE(pgExc_SDLError, SDL_GetError()); } #else if (SDL_GetDisplayUsableBounds(i, &bounds) < 0) { + Py_DECREF(result); return RAISE(pgExc_SDLError, SDL_GetError()); } #endif PyObject *pg_rect = pgRect_New(&bounds); if (pg_rect == NULL) { +#if PG_SDL3 + SDL_free(displays); +#endif + Py_DECREF(result); return NULL; /* exception already set */ } PyList_SET_ITEM(result, i, pg_rect); } + +#if PG_SDL3 + SDL_free(displays); +#endif return result; } - static PyObject * pg_is_fullscreen(PyObject *self, PyObject *_null) {