diff --git a/buildconfig/stubs/pygame/display.pyi b/buildconfig/stubs/pygame/display.pyi index b148536e50..dcee0cf44c 100644 --- a/buildconfig/stubs/pygame/display.pyi +++ b/buildconfig/stubs/pygame/display.pyi @@ -399,6 +399,7 @@ def get_wm_info() -> dict[str, int]: key with the value set to the system id for the current display. .. versionaddedold:: 1.7.1 + .. versionchanged:: 2.X.X added a new key ``subsystem`` used to identify windowing system - exists in most cases. """ def get_desktop_sizes() -> list[tuple[int, int]]: diff --git a/buildconfig/stubs/pygame/window.pyi b/buildconfig/stubs/pygame/window.pyi index f671a8ebb0..152bce9ccf 100644 --- a/buildconfig/stubs/pygame/window.pyi +++ b/buildconfig/stubs/pygame/window.pyi @@ -487,4 +487,17 @@ class Window: .. versionadded:: 2.5.2 """ + + def get_info(self) -> dict[str, int | str]: + """Return system-specific information about a given window. + + Creates a dictionary filled with string keys. It's empty if failed to query the system + info (eg.: the window was destroyed). Otherwise it contains at least one key ``subsystem`` + containing the name of the window's subsystem. The rest of values are integes. + Names of the rest keys and meaning of their values is system-dependent, + but most platforms will return a "window" key with the value + set to the system id for the current window. + + .. versionadded:: 2.X.X + """ relative_mouse: bool diff --git a/docs/reST/c_api/window.rst b/docs/reST/c_api/window.rst index 4ab8f083a2..0b257b6b7b 100644 --- a/docs/reST/c_api/window.rst +++ b/docs/reST/c_api/window.rst @@ -28,3 +28,10 @@ Header file: src_c/include/pygame.h Will return false if *x* is not a subclass of `Window`. This is a macro. No check is made that *x* is not *NULL*. + +.. c:function:: PyObject* pgWindow_GetInfo(SDL_Window *window) + + Return a python dict containing system-specific information about a window. + + .. seealso:: + :py:func:`pygame.window.Window.get_info` diff --git a/src_c/_pygame.h b/src_c/_pygame.h index 77fda23aeb..65a60e86a1 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -623,7 +623,7 @@ typedef enum { #define PYGAMEAPI_MATH_NUMSLOTS 2 #define PYGAMEAPI_BASE_NUMSLOTS 30 #define PYGAMEAPI_EVENT_NUMSLOTS 10 -#define PYGAMEAPI_WINDOW_NUMSLOTS 1 +#define PYGAMEAPI_WINDOW_NUMSLOTS 2 #define PYGAMEAPI_RENDER_NUMSLOTS 3 #define PYGAMEAPI_GEOMETRY_NUMSLOTS 2 #define PYGAMEAPI_BUFFERPROXY_NUMSLOTS 4 diff --git a/src_c/display.c b/src_c/display.c index 8f72dcbcc6..7fd7cb5749 100644 --- a/src_c/display.c +++ b/src_c/display.c @@ -467,122 +467,9 @@ pgInfo(PyObject *self, PyObject *_null) static PyObject * pg_get_wm_info(PyObject *self, PyObject *_null) { - PyObject *dict; - PyObject *tmp; - SDL_SysWMinfo info; - SDL_Window *win; - VIDEO_INIT_CHECK(); - SDL_VERSION(&(info.version)) - dict = PyDict_New(); - if (!dict) { - return NULL; - } - - win = pg_GetDefaultWindow(); - if (!win) { - return dict; - } - if (!SDL_GetWindowWMInfo(win, &info)) { - return dict; - } - - (void)tmp; -#if defined(SDL_VIDEO_DRIVER_WINDOWS) - tmp = PyLong_FromLongLong((long long)info.info.win.window); - PyDict_SetItemString(dict, "window", tmp); - Py_DECREF(tmp); - - tmp = PyLong_FromLongLong((long long)info.info.win.hdc); - PyDict_SetItemString(dict, "hdc", tmp); - Py_DECREF(tmp); - tmp = PyLong_FromLongLong((long long)info.info.win.hinstance); - PyDict_SetItemString(dict, "hinstance", tmp); - Py_DECREF(tmp); -#endif -#if defined(SDL_VIDEO_DRIVER_WINRT) - tmp = PyCapsule_New(info.info.winrt.window, "window", NULL); - PyDict_SetItemString(dict, "window", tmp); - Py_DECREF(tmp); -#endif -#if defined(SDL_VIDEO_DRIVER_X11) - tmp = PyLong_FromLong(info.info.x11.window); - PyDict_SetItemString(dict, "window", tmp); - Py_DECREF(tmp); - - tmp = PyCapsule_New(info.info.x11.display, "display", NULL); - PyDict_SetItemString(dict, "display", tmp); - Py_DECREF(tmp); -#endif -#if defined(SDL_VIDEO_DRIVER_DIRECTFB) - tmp = PyCapsule_New(info.info.dfb.dfb, "dfb", NULL); - PyDict_SetItemString(dict, "dfb", tmp); - Py_DECREF(tmp); - - tmp = PyCapsule_New(info.info.dfb.window, "window", NULL); - PyDict_SetItemString(dict, "window", tmp); - Py_DECREF(tmp); - - tmp = PyCapsule_New(info.info.dfb.surface, "surface", NULL); - PyDict_SetItemString(dict, "surface", tmp); - Py_DECREF(tmp); -#endif -#if defined(SDL_VIDEO_DRIVER_COCOA) - tmp = PyCapsule_New(info.info.cocoa.window, "window", NULL); - PyDict_SetItemString(dict, "window", tmp); - Py_DECREF(tmp); -#endif -#if defined(SDL_VIDEO_DRIVER_UIKIT) - tmp = PyCapsule_New(info.info.uikit.window, "window", NULL); - PyDict_SetItemString(dict, "window", tmp); - Py_DECREF(tmp); - - tmp = PyLong_FromLong(info.info.uikit.framebuffer); - PyDict_SetItemString(dict, "framebuffer", tmp); - Py_DECREF(tmp); - - tmp = PyLong_FromLong(info.info.uikit.colorbuffer); - PyDict_SetItemString(dict, "colorbuffer", tmp); - Py_DECREF(tmp); - - tmp = PyLong_FromLong(info.info.uikit.resolveFramebuffer); - PyDict_SetItemString(dict, "resolveFramebuffer", tmp); - Py_DECREF(tmp); -#endif -#if defined(SDL_VIDEO_DRIVER_WAYLAND) - tmp = PyCapsule_New(info.info.wl.display, "display", NULL); - PyDict_SetItemString(dict, "display", tmp); - Py_DECREF(tmp); - - tmp = PyCapsule_New(info.info.wl.surface, "surface", NULL); - PyDict_SetItemString(dict, "surface", tmp); - Py_DECREF(tmp); - - tmp = PyCapsule_New(info.info.wl.shell_surface, "shell_surface", NULL); - PyDict_SetItemString(dict, "shell_surface", tmp); - Py_DECREF(tmp); -#endif -#if defined(SDL_VIDEO_DRIVER_ANDROID) - tmp = PyCapsule_New(info.info.android.window, "window", NULL); - PyDict_SetItemString(dict, "window", tmp); - Py_DECREF(tmp); - - tmp = PyLong_FromLong((long)info.info.android.surface); - PyDict_SetItemString(dict, "surface", tmp); - Py_DECREF(tmp); -#endif -#if defined(SDL_VIDEO_DRIVER_VIVANTE) - tmp = PyLong_FromLong((long)info.info.vivante.display); - PyDict_SetItemString(dict, "display", tmp); - Py_DECREF(tmp); - - tmp = PyLong_FromLong((long)info.info.vivante.window); - PyDict_SetItemString(dict, "window", tmp); - Py_DECREF(tmp); -#endif - - return dict; + return pgWindow_GetInfo(pg_GetDefaultWindow()); } /* display functions */ diff --git a/src_c/doc/window_doc.h b/src_c/doc/window_doc.h index 6495da670e..02ff28ea10 100644 --- a/src_c/doc/window_doc.h +++ b/src_c/doc/window_doc.h @@ -33,3 +33,4 @@ #define DOC_WINDOW_SETICON "set_icon(icon, /) -> None\nSet the window icon." #define DOC_WINDOW_SETMODALFOR "set_modal_for(parent, /) -> None\nSet the window as a modal for a parent window." #define DOC_WINDOW_FLASH "flash(operation, /) -> None\nFlash a window to demand attention from the user." +#define DOC_WINDOW_GETINFO "get_info() -> dict[str, int | str]\nReturn system-specific information about a given window." diff --git a/src_c/include/_pygame.h b/src_c/include/_pygame.h index 6f9942cb6c..8b8647b669 100644 --- a/src_c/include/_pygame.h +++ b/src_c/include/_pygame.h @@ -546,6 +546,8 @@ typedef struct { #define pgWindow_Check(x) \ (PyObject_IsInstance((x), (PyObject *)&pgWindow_Type)) #define import_pygame_window() IMPORT_PYGAME_MODULE(window) +#define pgWindow_GetInfo \ + (*(PyObject * (*)(SDL_Window *)) PYGAMEAPI_GET_SLOT(window, 1)) #endif typedef struct pgTextureObject pgTextureObject; diff --git a/src_c/window.c b/src_c/window.c index 8e0b1bde50..3db9229db4 100644 --- a/src_c/window.c +++ b/src_c/window.c @@ -8,6 +8,11 @@ #include "doc/sdl2_video_doc.h" #include "doc/window_doc.h" +#if SDL_VERSION_ATLEAST(3, 0, 0) +#else /* !SDL_VERSION_ATLEAST(3, 0, 0) */ +#include +#endif + static int is_window_mod_init = 0; #if !defined(__APPLE__) @@ -198,6 +203,225 @@ window_get_surface(pgWindowObject *self, PyObject *_null) return (PyObject *)self->surf; } +#if !SDL_VERSION_ATLEAST(3, 0, 0) +static char * +decode_syswm_type(SDL_SYSWM_TYPE type) +{ + switch (type) { + case SDL_SYSWM_ANDROID: + return "android"; + case SDL_SYSWM_WINDOWS: + return "windows"; + case SDL_SYSWM_WINRT: + return "winrt"; + case SDL_SYSWM_X11: + return "x11"; + case SDL_SYSWM_DIRECTFB: + return "directfb"; + case SDL_SYSWM_COCOA: + return "cocoa"; + case SDL_SYSWM_UIKIT: + return "uikit"; + case SDL_SYSWM_WAYLAND: + return "wayland"; + case SDL_SYSWM_VIVANTE: + return "vivante"; + case SDL_SYSWM_UNKNOWN: + default: + return "unknown"; + } +} +#endif + +static PyObject * +pgWindow_GetInfo(SDL_Window *window) +{ + // TODO: support more platforms + + PyObject *dict = PyDict_New(); + if (!dict) { + return NULL; + } + + if (!window) { + return dict; + } + + PyObject *tmp; + +#if SDL_VERSION_ATLEAST(3, 1, 3) + +#define _LOAD_PROP_FLOAT(name, loc) \ + if (SDL_HasProperty(info, name)) { \ + tmp = PyFloat_FromDouble(SDL_GetFloatProperty(info, name, 0)); \ + PyDict_SetItemString(dict, loc, tmp); \ + Py_DECREF(tmp); \ + } +#define _LOAD_PROP_NUMBER(name, loc) \ + if (SDL_HasProperty(info, name)) { \ + tmp = PyLong_FromLongLong(SDL_GetNumberProperty(info, name, 0)); \ + PyDict_SetItemString(dict, loc, tmp); \ + Py_DECREF(tmp); \ + } +#define _LOAD_PROP_STRING(name, loc) \ + if (SDL_HasProperty(info, name)) { \ + tmp = PyUnicode_FromString(SDL_GetStringProperty(info, name, "")); \ + PyDict_SetItemString(dict, loc, tmp); \ + Py_DECREF(tmp); \ + } +#define _LOAD_PROP_BOOLEAN(name, loc) \ + if (SDL_HasProperty(info, name)) { \ + tmp = PyBool_FromLong(SDL_GetBooleanProperty(info, name, 0)); \ + PyDict_SetItemString(dict, loc, tmp); \ + Py_DECREF(tmp); \ + } +#define _LOAD_PROP_POINTER(name, loc) \ + if (SDL_HasProperty(info, name)) { \ + tmp = PyLong_FromLongLong( \ + (intptr_t)SDL_GetPointerProperty(info, name, 0)); \ + PyDict_SetItemString(dict, loc, tmp); \ + Py_DECREF(tmp); \ + } +#define _LOAD_PROP(name, type, loc) \ + _LOAD_PROP_##type(SDL_PROP_WINDOW_##name##_##type, loc) + + SDL_PropertiesID info = SDL_GetWindowProperties(window); + const char *driver_name = SDL_GetCurrentVideoDriver(); + if (driver_name == NULL) { + driver_name = "unknown"; + } + + tmp = PyUnicode_FromString(driver_name); + PyDict_SetItemString(dict, "subsystem", tmp); + Py_DECREF(tmp); + + // SDL_VIDEO_DRIVER_WINDOWS + _LOAD_PROP(WIN32_HWND, POINTER, "window") + _LOAD_PROP(WIN32_HDC, POINTER, "hdc") + _LOAD_PROP(WIN32_INSTANCE, POINTER, "hinstance") + + // SDL_VIDEO_DRIVER_WINRT - REMOVED from SDL3 + + // SDL_VIDEO_DRIVER_X11 + _LOAD_PROP(X11_WINDOW, NUMBER, "window") + _LOAD_PROP(X11_DISPLAY, POINTER, "display") + _LOAD_PROP(X11_SCREEN, NUMBER, "screen") + + // SDL_VIDEO_DRIVER_WAYLAND + _LOAD_PROP(WAYLAND_DISPLAY, POINTER, "display") + _LOAD_PROP(WAYLAND_SURFACE, POINTER, "surface") + // _LOAD_PROP(WAYLAND_SHELL_SURFACE, POINTER, "shell_surface") - + // nonexistant + + // SDL_VIDEO_DRIVER_DIRECTFB - REMOVED from SDL3 + + // SDL_VIDEO_DRIVER_COCOA + _LOAD_PROP(COCOA_WINDOW, POINTER, "window") + + // SDL_VIDEO_DRIVER_UIKIT + _LOAD_PROP(UIKIT_WINDOW, POINTER, "window") + _LOAD_PROP(UIKIT_OPENGL_FRAMEBUFFER, NUMBER, "framebuffer") + _LOAD_PROP(UIKIT_OPENGL_RENDERBUFFER, NUMBER, "renderbuffer") + // _LOAD_PROP(UIKIT_OPENGL_COLORBUFFER, NUMBER, "colorbuffer") - + // nonexistant + _LOAD_PROP(UIKIT_OPENGL_RESOLVE_FRAMEBUFFER, NUMBER, "resolveFramebuffer") + + // SDL_VIDEO_DRIVER_ANDROID + _LOAD_PROP(ANDROID_WINDOW, POINTER, "window") + _LOAD_PROP(ANDROID_SURFACE, POINTER, "surface") + + // SDL_VIDEO_DRIVER_VIVANTE + _LOAD_PROP(VIVANTE_WINDOW, POINTER, "window") + _LOAD_PROP(VIVANTE_DISPLAY, POINTER, "display") + _LOAD_PROP(VIVANTE_SURFACE, POINTER, "surface") + +#undef _LOAD_PROP_FLOAT +#undef _LOAD_PROP_NUMBER +#undef _LOAD_PROP_STRING +#undef _LOAD_PROP_BOOLEAN +#undef _LOAD_PROP_POINTER +#undef _LOAD_PROP + +#else /* !SDL_VERSION_ATLEAST(3, 1, 3) */ +#if SDL_VERSION_ATLEAST(3, 0, 0) + +#else + +#define _LOAD_NUM(from, prop) \ + { \ + tmp = PyLong_FromLongLong((long long)(info.info.from.prop)); \ + PyDict_SetItemString(dict, #prop, tmp); \ + Py_DECREF(tmp); \ + } +#define _LOAD_PTR(from, prop) \ + { \ + tmp = PyLong_FromLongLong((intptr_t)(info.info.from.prop)); \ + PyDict_SetItemString(dict, #prop, tmp); \ + Py_DECREF(tmp); \ + } + + SDL_SysWMinfo info; + SDL_VERSION(&(info.version)) + + if (!SDL_GetWindowWMInfo(window, &info)) { + return dict; + } + + tmp = PyUnicode_FromString(decode_syswm_type(info.subsystem)); + PyDict_SetItemString(dict, "subsystem", tmp); + Py_DECREF(tmp); + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) + _LOAD_NUM(win, window) + _LOAD_NUM(win, hdc) + _LOAD_NUM(win, hinstance) +#endif +#if defined(SDL_VIDEO_DRIVER_WINRT) + _LOAD_PTR(winrt, window) +#endif +#if defined(SDL_VIDEO_DRIVER_X11) + _LOAD_NUM(x11, window) + _LOAD_PTR(x11, display) +#endif +#if defined(SDL_VIDEO_DRIVER_DIRECTFB) + _LOAD_PTR(dfb, dfb) + _LOAD_PTR(dfb, window) + _LOAD_PTR(dfb, surface) +#endif +#if defined(SDL_VIDEO_DRIVER_COCOA) + _LOAD_PTR(cocoa, window) +#endif +#if defined(SDL_VIDEO_DRIVER_UIKIT) + _LOAD_PTR(uikit, window) + _LOAD_NUM(uikit, framebuffer) + _LOAD_NUM(uikit, colorbuffer) + _LOAD_NUM(uikit, resolveFramebuffer) +#endif +#if defined(SDL_VIDEO_DRIVER_WAYLAND) + _LOAD_PTR(wl, display) + _LOAD_PTR(wl, surface) + _LOAD_PTR(wl, shell_surface) +#endif +#if defined(SDL_VIDEO_DRIVER_ANDROID) + _LOAD_PTR(android, window) + _LOAD_NUM(android, surface) +#endif +#if defined(SDL_VIDEO_DRIVER_VIVANTE) + _LOAD_NUM(vivante, display) + _LOAD_NUM(vivante, window) +#endif + +#endif /* !SDL_VERSION_ATLEAST(3, 0, 0) */ +#endif + return dict; +} + +static PyObject * +window_get_info(pgWindowObject *self, PyObject *_null) +{ + return pgWindow_GetInfo(self->_win); +} + static PyObject * window_flip(pgWindowObject *self, PyObject *_null) { @@ -1396,6 +1620,7 @@ static PyMethodDef window_methods[] = { {"flip", (PyCFunction)window_flip, METH_NOARGS, DOC_WINDOW_FLIP}, {"get_surface", (PyCFunction)window_get_surface, METH_NOARGS, DOC_WINDOW_GETSURFACE}, + {"get_info", (PyCFunction)window_get_info, METH_NOARGS, "TODO"}, {"from_display_module", (PyCFunction)window_from_display_module, METH_CLASS | METH_NOARGS, DOC_WINDOW_FROMDISPLAYMODULE}, {"flash", (PyCFunction)window_flash, METH_O, DOC_WINDOW_FLASH}, @@ -1508,7 +1733,11 @@ MODINIT_DEFINE(window) return NULL; } + /* export the c api */ + assert(PYGAMEAPI_WINDOW_NUMSLOTS == 2); c_api[0] = &pgWindow_Type; + c_api[1] = &pgWindow_GetInfo; + apiobj = encapsulate_api(c_api, "window"); if (PyModule_AddObject(module, PYGAMEAPI_LOCAL_ENTRY, apiobj)) { Py_XDECREF(apiobj); diff --git a/test/display_test.py b/test/display_test.py index 881304b878..02c30e8f07 100644 --- a/test/display_test.py +++ b/test/display_test.py @@ -182,6 +182,7 @@ def test_get_surface__mode_not_set(self): self.assertIsNone(surface) def test_get_wm_info(self): + pygame.display.set_mode((1, 1)) wm_info = display.get_wm_info() # Assert function returns a dictionary type self.assertIsInstance(wm_info, dict) @@ -200,6 +201,7 @@ def test_get_wm_info(self): "lock_func", "resolveFramebuffer", "shell_surface", + "subsystem", "surface", "taskHandle", "unlock_func", diff --git a/test/window_test.py b/test/window_test.py index 5212b97b9c..aa9dfff445 100644 --- a/test/window_test.py +++ b/test/window_test.py @@ -458,6 +458,46 @@ def test_window_focused(self): window = pygame.Window() self.assertIsInstance(window.focused, bool) + def test_get_info(self): + info = self.win.get_info() + # Assert function returns a dictionary type + self.assertIsInstance(info, dict) + + wm_info_potential_keys = { + "colorbuffer", + "connection", + "data", + "dfb", + "display", + "framebuffer", + "fswindow", + "hdc", + "hglrc", + "hinstance", + "lock_func", + "resolveFramebuffer", + "shell_surface", + "subsystem", + "surface", + "taskHandle", + "unlock_func", + "wimpVersion", + "window", + "wmwindow", + } + + # If any unexpected dict keys are present, they + # will be stored in set wm_info_remaining_keys + wm_info_remaining_keys = set(info.keys()).difference(wm_info_potential_keys) + + # Assert set is empty (& therefore does not + # contain unexpected dict keys) + self.assertFalse(wm_info_remaining_keys) + + # Test for destroyed window + self.win.destroy() + self.assertFalse(self.win.get_info()) + def tearDown(self): self.win.destroy()