diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 64d026bcae..2703d87cbd 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -150,7 +150,8 @@ typedef enum SDL_WINDOW_KEYBOARD_GRABBED = 0x00100000, /**< window has grabbed keyboard input */ SDL_WINDOW_VULKAN = 0x10000000, /**< window usable for Vulkan surface */ SDL_WINDOW_METAL = 0x20000000, /**< window usable for Metal view */ - SDL_WINDOW_TRANSPARENT = 0x40000000 /**< window with transparent buffer */ + SDL_WINDOW_TRANSPARENT = 0x40000000, /**< window with transparent buffer */ + SDL_WINDOW_NOT_FOCUSABLE = 0x80000000, /**< window should not be focusable */ } SDL_WindowFlags; @@ -1676,6 +1677,19 @@ extern DECLSPEC int SDLCALL SDL_SetWindowModalFor(SDL_Window *modal_window, SDL_ */ extern DECLSPEC int SDLCALL SDL_SetWindowInputFocus(SDL_Window *window); +/** + * Set whether the window may have input focus. + * + * \param window the window to set focusable state + * \param focusable SDL_TRUE to allow input focus, SDL_FALSE to not allow input focus + * \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_SetWindowFocusable(SDL_Window *window, SDL_bool focusable); + + /** * Display the system-level window menu. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index bf92d4b9a7..0887e4ba36 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -901,6 +901,7 @@ SDL3_0.0.0 { SDL_WriteS64LE; SDL_WriteS64BE; SDL_GDKGetDefaultUser; + SDL_SetWindowFocusable; # 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 1d038f6fd8..93d5db6b85 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -926,3 +926,4 @@ #define SDL_WriteS64LE SDL_WriteS64LE_REAL #define SDL_WriteS64BE SDL_WriteS64BE_REAL #define SDL_GDKGetDefaultUser SDL_GDKGetDefaultUser_REAL +#define SDL_SetWindowFocusable SDL_SetWindowFocusable_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index f4deb2db7d..61bf6a26ce 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -972,3 +972,4 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_WriteS64BE,(SDL_RWops *a, Sint64 b),(a,b),return) #ifdef __GDK__ SDL_DYNAPI_PROC(int,SDL_GDKGetDefaultUser,(XUserHandle *a),(a),return) #endif +SDL_DYNAPI_PROC(int,SDL_SetWindowFocusable,(SDL_Window *a, SDL_bool b),(a,b),return) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index d41ba21380..a1bde5773e 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -264,6 +264,7 @@ struct SDL_VideoDevice void (*DestroyWindowFramebuffer)(SDL_VideoDevice *_this, SDL_Window *window); void (*OnWindowEnter)(SDL_VideoDevice *_this, SDL_Window *window); int (*FlashWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); + int (*SetWindowFocusable)(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable); /* * * */ /* diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 708cbc3768..616e9d7567 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -1740,7 +1740,7 @@ Uint32 SDL_GetWindowPixelFormat(SDL_Window *window) } #define CREATE_FLAGS \ - (SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_POPUP_MENU | SDL_WINDOW_UTILITY | SDL_WINDOW_TOOLTIP | SDL_WINDOW_VULKAN | SDL_WINDOW_MINIMIZED | SDL_WINDOW_METAL | SDL_WINDOW_TRANSPARENT) + (SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_POPUP_MENU | SDL_WINDOW_UTILITY | SDL_WINDOW_TOOLTIP | SDL_WINDOW_VULKAN | SDL_WINDOW_MINIMIZED | SDL_WINDOW_METAL | SDL_WINDOW_TRANSPARENT | SDL_WINDOW_NOT_FOCUSABLE) static SDL_INLINE SDL_bool IsAcceptingDragAndDrop(void) { @@ -3205,6 +3205,24 @@ int SDL_SetWindowInputFocus(SDL_Window *window) return _this->SetWindowInputFocus(_this, window); } +int SDL_SetWindowFocusable(SDL_Window *window, SDL_bool focusable) +{ + CHECK_WINDOW_MAGIC(window, -1); + + const int want = (focusable != SDL_FALSE); /* normalize the flag. */ + const int have = !(window->flags & SDL_WINDOW_NOT_FOCUSABLE); + if ((want != have) && (_this->SetWindowFocusable)) { + if (want) { + window->flags &= ~SDL_WINDOW_NOT_FOCUSABLE; + } else { + window->flags |= SDL_WINDOW_NOT_FOCUSABLE; + } + _this->SetWindowFocusable(_this, window, (SDL_bool)want); + } + + return 0; +} + void SDL_UpdateWindowGrab(SDL_Window *window) { SDL_bool keyboard_grabbed, mouse_grabbed; diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index d59590aee6..3b6092b00c 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -119,6 +119,7 @@ static SDL_VideoDevice *Cocoa_CreateDevice(void) device->SetWindowHitTest = Cocoa_SetWindowHitTest; device->AcceptDragAndDrop = Cocoa_AcceptDragAndDrop; device->FlashWindow = Cocoa_FlashWindow; + device->SetWindowFocusable = Cocoa_SetWindowFocusable; device->shape_driver.CreateShaper = Cocoa_CreateShaper; device->shape_driver.SetWindowShape = Cocoa_SetWindowShape; diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index ceb27db92a..1408784b28 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -169,5 +169,6 @@ extern int Cocoa_GetWindowWMInfo(SDL_VideoDevice *_this, SDL_Window *window, str extern int Cocoa_SetWindowHitTest(SDL_Window *window, SDL_bool enabled); extern void Cocoa_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept); extern int Cocoa_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); +extern int Cocoa_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable); #endif /* SDL_cocoawindow_h_ */ diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 7a3af36234..3dcff6bb20 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -116,7 +116,7 @@ - (BOOL)canBecomeKeyWindow { SDL_Window *window = [self findSDLWindow]; - if (window && !(window->flags & SDL_WINDOW_TOOLTIP)) { + if (window && !(window->flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_NOT_FOCUSABLE))) { return YES; } else { return NO; @@ -2678,6 +2678,11 @@ int Cocoa_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOpera } } +int Cocoa_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable) +{ + return 0; /* just succeed, the real work is done elsewhere. */ +} + int Cocoa_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity) { @autoreleasepool { diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index daa2bed521..66ad8e9b18 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -211,6 +211,7 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->AcceptDragAndDrop = WIN_AcceptDragAndDrop; device->FlashWindow = WIN_FlashWindow; device->ShowWindowSystemMenu = WIN_ShowWindowSystemMenu; + device->SetWindowFocusable = WIN_SetWindowFocusable; 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 75b4fa02a0..79cf00f6b9 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -144,10 +144,11 @@ static DWORD GetWindowStyleEx(SDL_Window *window) { DWORD style = 0; - if (SDL_WINDOW_IS_POPUP(window)) { - style = WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE; - } else if (window->flags & SDL_WINDOW_UTILITY) { - style = WS_EX_TOOLWINDOW; + if (SDL_WINDOW_IS_POPUP(window) || (window->flags & SDL_WINDOW_UTILITY)) { + style |= WS_EX_TOOLWINDOW; + } + if (SDL_WINDOW_IS_POPUP(window) || (window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + style |= WS_EX_NOACTIVATE; } return style; } @@ -1539,6 +1540,31 @@ void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y) ClientToScreen(data->hwnd, &pt); SendMessage(data->hwnd, WM_POPUPSYSTEMMENU, 0, MAKELPARAM(pt.x, pt.y)); } + +int WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable) +{ + SDL_WindowData *data = window->driverdata; + HWND hwnd = data->hwnd; + const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); + + SDL_assert(style != 0); + + if (focusable) { + if (style & WS_EX_NOACTIVATE) { + if (SetWindowLong(hwnd, GWL_EXSTYLE, style & ~WS_EX_NOACTIVATE) == 0) { + return WIN_SetError("SetWindowLong()"); + } + } + } else { + if (!(style & WS_EX_NOACTIVATE)) { + if (SetWindowLong(hwnd, GWL_EXSTYLE, style | WS_EX_NOACTIVATE) == 0) { + return WIN_SetError("SetWindowLong()"); + } + } + } + + return 0; +} #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 f6f0331a3e..527fee78d1 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -109,6 +109,7 @@ extern int WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Flash 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); +extern int WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable); /* 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 5122bcf589..4192bf331c 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -212,6 +212,7 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->AcceptDragAndDrop = X11_AcceptDragAndDrop; device->FlashWindow = X11_FlashWindow; device->ShowWindowSystemMenu = X11_ShowWindowSystemMenu; + device->SetWindowFocusable = X11_SetWindowFocusable; #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 664de3603e..7c211de23d 100644 --- a/src/video/x11/SDL_x11window.c +++ b/src/video/x11/SDL_x11window.c @@ -623,7 +623,7 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window) /* Setup the input hints so we get keyboard input */ wmhints = X11_XAllocWMHints(); - wmhints->input = True; + wmhints->input = !(window->flags & SDL_WINDOW_NOT_FOCUSABLE) ? True : False; wmhints->window_group = data->window_group; wmhints->flags = InputHint | WindowGroupHint; @@ -1982,4 +1982,24 @@ void X11_ShowWindowSystemMenu(SDL_Window *window, int x, int y) X11_XFlush(display); } +int X11_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable) +{ + SDL_WindowData *data = window->driverdata; + Display *display = data->videodata->display; + XWMHints *wmhints; + + wmhints = X11_XGetWMHints(display, data->xwindow); + if (wmhints == NULL) { + return SDL_SetError("Couldn't get WM hints"); + } + + wmhints->input = focusable ? True : False; + wmhints->flags |= InputHint; + + X11_XSetWMHints(display, data->xwindow, wmhints); + X11_XFree(wmhints); + + return 0; +} + #endif /* SDL_VIDEO_DRIVER_X11 */ diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h index ff6b98ba37..b9d6ab0d0e 100644 --- a/src/video/x11/SDL_x11window.h +++ b/src/video/x11/SDL_x11window.h @@ -116,6 +116,7 @@ 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); +extern int X11_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool focusable); int SDL_X11_SetWindowTitle(Display *display, Window xwindow, char *title); void X11_UpdateWindowPosition(SDL_Window *window);