diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 528ce8b381..ac4e8dab47 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -118,6 +118,7 @@ typedef enum * \sa SDL_SetWindowResizable() * \sa SDL_SetWindowTitle() * \sa SDL_ShowWindow() + * \sa SDL_ShowWindowSystemMenu() */ typedef struct SDL_Window SDL_Window; @@ -1675,6 +1676,28 @@ extern DECLSPEC int SDLCALL SDL_SetWindowModalFor(SDL_Window *modal_window, SDL_ */ extern DECLSPEC int SDLCALL SDL_SetWindowInputFocus(SDL_Window *window); +/** + * Display the system-level window menu. + * + * This default window menu is provided by the system and on some platforms + * provides functionality for setting or changing privileged state on the + * window, such as moving it between workspaces or displays, or toggling the + * always-on-top property. + * + * On platforms or desktops where this is unsupported, this function + * does nothing. + * + * \param window the window for which the menu will be displayed + * \param x the x coordinate of the menu, relative to the origin (top-left) of the client area + * \param y the y coordinate of the menu, relative to the origin (top-left) of the client area + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + */ +extern DECLSPEC int SDLCALL SDL_ShowWindowSystemMenu(SDL_Window *window, int x, int y); + /** * Possible return values from the SDL_HitTest callback. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index da0735115d..59123eaa1f 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -887,6 +887,7 @@ SDL3_0.0.0 { SDL_UnpauseAudioDevice; SDL_IsAudioDevicePaused; SDL_GetAudioStreamBinding; + SDL_ShowWindowSystemMenu; # 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 0cc9bb23a7..da10b99114 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -912,3 +912,4 @@ #define SDL_UnpauseAudioDevice SDL_UnpauseAudioDevice_REAL #define SDL_IsAudioDevicePaused SDL_IsAudioDevicePaused_REAL #define SDL_GetAudioStreamBinding SDL_GetAudioStreamBinding_REAL +#define SDL_ShowWindowSystemMenu SDL_ShowWindowSystemMenu_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 45d90eed0b..00521cc500 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -956,3 +956,4 @@ SDL_DYNAPI_PROC(int,SDL_PauseAudioDevice,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(int,SDL_UnpauseAudioDevice,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_IsAudioDevicePaused,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(SDL_AudioDeviceID,SDL_GetAudioStreamBinding,(SDL_AudioStream *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_ShowWindowSystemMenu,(SDL_Window *a, int b, int c),(a,b,c),return) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 034d802b00..26d3912068 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -354,6 +354,9 @@ struct SDL_VideoDevice /* Tell window that app enabled drag'n'drop events */ void (*AcceptDragAndDrop)(SDL_Window *window, SDL_bool accept); + /* Display the system-level window menu */ + void (*ShowWindowSystemMenu)(SDL_Window *window, int x, int y); + /* * * */ /* Data common to all drivers */ SDL_threadID thread; diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 32cec7965a..708cbc3768 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -5054,6 +5054,19 @@ SDL_bool SDL_ShouldAllowTopmost(void) return SDL_GetHintBoolean(SDL_HINT_ALLOW_TOPMOST, SDL_TRUE); } +int SDL_ShowWindowSystemMenu(SDL_Window *window, int x, int y) +{ + CHECK_WINDOW_MAGIC(window, -1) + CHECK_WINDOW_NOT_POPUP(window, -1) + + if (_this->ShowWindowSystemMenu) { + _this->ShowWindowSystemMenu(window, x, y); + return 0; + } + + return SDL_Unsupported(); +} + int SDL_SetWindowHitTest(SDL_Window *window, SDL_HitTest callback, void *callback_data) { CHECK_WINDOW_MAGIC(window, -1); diff --git a/src/video/wayland/SDL_waylanddyn.h b/src/video/wayland/SDL_waylanddyn.h index fa6b500357..76cfb295c9 100644 --- a/src/video/wayland/SDL_waylanddyn.h +++ b/src/video/wayland/SDL_waylanddyn.h @@ -158,6 +158,7 @@ void SDL_WAYLAND_UnloadSymbols(void); #define libdecor_frame_is_visible (*WAYLAND_libdecor_frame_is_visible) #define libdecor_frame_is_floating (*WAYLAND_libdecor_frame_is_floating) #define libdecor_frame_set_parent (*WAYLAND_libdecor_frame_set_parent) +#define libdecor_frame_show_window_menu (*WAYLAND_libdecor_frame_show_window_menu) #define libdecor_frame_get_xdg_surface (*WAYLAND_libdecor_frame_get_xdg_surface) #define libdecor_frame_get_xdg_toplevel (*WAYLAND_libdecor_frame_get_xdg_toplevel) #define libdecor_frame_translate_coordinate (*WAYLAND_libdecor_frame_translate_coordinate) diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 9ee285f899..5a14655eaf 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -554,6 +554,19 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer, } if (input->pointer_focus) { + SDL_WindowData *wind = (SDL_WindowData *)wl_surface_get_user_data(surface); + + if (wind) { + /* Clear the capture flag and raise all buttons */ + wind->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE; + + SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, 0, SDL_RELEASED, SDL_BUTTON_LEFT); + SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, 0, SDL_RELEASED, SDL_BUTTON_RIGHT); + SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, 0, SDL_RELEASED, SDL_BUTTON_MIDDLE); + SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, 0, SDL_RELEASED, SDL_BUTTON_X1); + SDL_SendMouseButton(Wayland_GetPointerTimestamp(input, 0), wind->sdlwindow, 0, SDL_RELEASED, SDL_BUTTON_X2); + } + SDL_SetMouseFocus(NULL); input->pointer_focus = NULL; } diff --git a/src/video/wayland/SDL_waylandsym.h b/src/video/wayland/SDL_waylandsym.h index 2d0eb73557..494104b15d 100644 --- a/src/video/wayland/SDL_waylandsym.h +++ b/src/video/wayland/SDL_waylandsym.h @@ -204,6 +204,7 @@ SDL_WAYLAND_SYM(bool, libdecor_frame_is_visible, (struct libdecor_frame *)) SDL_WAYLAND_SYM(bool, libdecor_frame_is_floating, (struct libdecor_frame *)) SDL_WAYLAND_SYM(void, libdecor_frame_set_parent, (struct libdecor_frame *,\ struct libdecor_frame *)) +SDL_WAYLAND_SYM(void, libdecor_frame_show_window_menu, (struct libdecor_frame *, struct wl_seat *, uint32_t, int, int)) SDL_WAYLAND_SYM(struct xdg_surface *, libdecor_frame_get_xdg_surface, (struct libdecor_frame *)) SDL_WAYLAND_SYM(struct xdg_toplevel *, libdecor_frame_get_xdg_toplevel, (struct libdecor_frame *)) SDL_WAYLAND_SYM(void, libdecor_frame_translate_coordinate, (struct libdecor_frame *, int, int, int *, int *)) diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index cec877530c..a891b9079e 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -209,6 +209,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->SetWindowHitTest = Wayland_SetWindowHitTest; device->FlashWindow = Wayland_FlashWindow; device->HasScreenKeyboardSupport = Wayland_HasScreenKeyboardSupport; + device->ShowWindowSystemMenu = Wayland_ShowWindowSystemMenu; #ifdef SDL_USE_LIBDBUS if (SDL_SystemTheme_Init()) diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 442224f21a..4568ecb4d4 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -2357,6 +2357,23 @@ void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) WAYLAND_wl_display_flush(viddata->display); } +void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y) +{ + SDL_WindowData *wind = window->driverdata; +#ifdef HAVE_LIBDECOR_H + if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { + if (wind->shell_surface.libdecor.frame) { + libdecor_frame_show_window_menu(wind->shell_surface.libdecor.frame, wind->waylandData->input->seat, wind->waylandData->input->last_implicit_grab_serial, x, y); + } + } else +#endif + if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) { + if (wind->shell_surface.xdg.roleobj.toplevel) { + xdg_toplevel_show_window_menu(wind->shell_surface.xdg.roleobj.toplevel, wind->waylandData->input->seat, wind->waylandData->input->last_implicit_grab_serial, x, y); + } + } +} + int Wayland_SuspendScreenSaver(SDL_VideoDevice *_this) { SDL_VideoData *data = _this->driverdata; diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 25e42fb231..98dc8b757b 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -150,6 +150,7 @@ extern void Wayland_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *win extern void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h); extern int Wayland_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window); extern void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); +extern void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y); extern void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); extern int Wayland_SuspendScreenSaver(SDL_VideoDevice *_this); diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index bb7fe15240..52bbb8e67e 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -207,6 +207,7 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->SetWindowHitTest = WIN_SetWindowHitTest; device->AcceptDragAndDrop = WIN_AcceptDragAndDrop; device->FlashWindow = WIN_FlashWindow; + device->ShowWindowSystemMenu = WIN_ShowWindowSystemMenu; device->shape_driver.CreateShaper = Win32_CreateShaper; device->shape_driver.SetWindowShape = Win32_SetWindowShape; diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index 076cccee3e..6218078e28 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -51,6 +51,14 @@ typedef HRESULT (WINAPI *DwmSetWindowAttribute_t)(HWND hwnd, DWORD dwAttribute, #define SWP_NOCOPYBITS 0 #endif +/* An undocumented message to create a popup system menu + * - wParam is always 0 + * - lParam = MAKELONG(x, y) where x and y are the screen coordinates where the menu should be displayed + */ +#ifndef WM_POPUPSYSTEMMENU +#define WM_POPUPSYSTEMMENU 0x313 +#endif + /* #define HIGHDPI_DEBUG */ /* Fake window to help with DirectInput events. */ @@ -1485,6 +1493,17 @@ int WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperati return 0; } + +void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y) +{ + const SDL_WindowData *data = window->driverdata; + POINT pt; + + pt.x = x; + pt.y = y; + ClientToScreen(data->hwnd, &pt); + SendMessage(data->hwnd, WM_POPUPSYSTEMMENU, 0, MAKELPARAM(pt.x, pt.y)); +} #endif /*!defined(__XBOXONE__) && !defined(__XBOXSERIES__)*/ void WIN_UpdateDarkModeForHWND(HWND hwnd) diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h index 956c5ec304..f6f0331a3e 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -108,6 +108,7 @@ extern void WIN_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept); extern int WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); extern void WIN_UpdateDarkModeForHWND(HWND hwnd); extern int WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags); +extern void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y); /* Ends C function definitions when using C++ */ #ifdef __cplusplus diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 8b14e55c45..70cc85a35e 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -213,6 +213,7 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->SetWindowHitTest = X11_SetWindowHitTest; device->AcceptDragAndDrop = X11_AcceptDragAndDrop; device->FlashWindow = X11_FlashWindow; + device->ShowWindowSystemMenu = X11_ShowWindowSystemMenu; #ifdef SDL_VIDEO_DRIVER_X11_XFIXES device->SetWindowMouseRect = X11_SetWindowMouseRect; diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c index 36e7c9f2c9..e7d04a8ccb 100644 --- a/src/video/x11/SDL_x11window.c +++ b/src/video/x11/SDL_x11window.c @@ -1957,4 +1957,29 @@ int SDL_X11_SetWindowTitle(Display *display, Window xwindow, char *title) return 0; } +void X11_ShowWindowSystemMenu(SDL_Window *window, int x, int y) +{ + SDL_WindowData *data = window->driverdata; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + Display *display = data->videodata->display; + Window root = RootWindow(display, displaydata->screen); + XClientMessageEvent e; + Window childReturn; + int wx, wy; + + SDL_zero(e); + X11_XTranslateCoordinates(display, data->xwindow, root, x, y, &wx, &wy, &childReturn); + + e.type = ClientMessage; + e.window = data->xwindow; + e.message_type = X11_XInternAtom(display, "_GTK_SHOW_WINDOW_MENU", 0); + e.data.l[0] = 0; /* GTK device ID (unused) */ + e.data.l[1] = wx; /* X coordinate relative to root */ + e.data.l[2] = wy; /* Y coordinate relative to root */ + e.format = 32; + + X11_XSendEvent(display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&e); + X11_XFlush(display); +} + #endif /* SDL_VIDEO_DRIVER_X11 */ diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h index b5da61a4ce..ff6b98ba37 100644 --- a/src/video/x11/SDL_x11window.h +++ b/src/video/x11/SDL_x11window.h @@ -115,6 +115,7 @@ extern int X11_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, struc extern int X11_SetWindowHitTest(SDL_Window *window, SDL_bool enabled); extern void X11_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept); extern int X11_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); +extern void X11_ShowWindowSystemMenu(SDL_Window *window, int x, int y); int SDL_X11_SetWindowTitle(Display *display, Window xwindow, char *title); void X11_UpdateWindowPosition(SDL_Window *window);