diff --git a/CMakeLists.txt b/CMakeLists.txt index 67389175e..ed3a2d2ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,6 +357,7 @@ dep_option(SDL_X11_XRANDR "Enable Xrandr support" "${SDL_X11_XRANDR_DEF dep_option(SDL_X11_XSCRNSAVER "Enable Xscrnsaver support" ON SDL_X11 OFF) dep_option(SDL_X11_XSHAPE "Enable XShape support" ON SDL_X11 OFF) dep_option(SDL_X11_XSYNC "Enable Xsync support" ON SDL_X11 OFF) +dep_option(SDL_X11_XTEST "Enable XTest support" ON SDL_X11 OFF) dep_option(SDL_WAYLAND "Use Wayland video driver" ${UNIX_SYS} "SDL_VIDEO" OFF) dep_option(SDL_WAYLAND_SHARED "Dynamically load Wayland support" ON "SDL_WAYLAND;SDL_DEPS_SHARED" OFF) dep_option(SDL_WAYLAND_LIBDECOR "Use client-side window decorations on Wayland" ON "SDL_WAYLAND" OFF) diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake index d95cbfa3a..e5670305f 100644 --- a/cmake/sdlchecks.cmake +++ b/cmake/sdlchecks.cmake @@ -274,10 +274,11 @@ macro(CheckX11) set(Xrandr_PKG_CONFIG_SPEC xrandr) set(Xrender_PKG_CONFIG_SPEC xrender) set(Xss_PKG_CONFIG_SPEC xscrnsaver) + set(Xtst_PKG_CONFIG_SPEC xtst) find_package(X11) - foreach(_LIB X11 Xext Xcursor Xi Xfixes Xrandr Xrender Xss) + foreach(_LIB X11 Xext Xcursor Xi Xfixes Xrandr Xrender Xss Xtst) get_filename_component(_libdir "${X11_${_LIB}_LIB}" DIRECTORY) FindLibraryAndSONAME("${_LIB}" LIBDIRS ${_libdir}) endforeach() @@ -310,6 +311,7 @@ macro(CheckX11) find_file(HAVE_XSYNC_H NAMES "X11/extensions/sync.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XSS_H NAMES "X11/extensions/scrnsaver.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XSHAPE_H NAMES "X11/extensions/shape.h" HINTS "${X11_INCLUDEDIR}") + find_file(HAVE_XTEST_H NAMES "X11/extensions/XTest.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XDBE_H NAMES "X11/extensions/Xdbe.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XEXT_H NAMES "X11/extensions/Xext.h" HINTS "${X11_INCLUDEDIR}") @@ -472,6 +474,16 @@ macro(CheckX11) set(SDL_VIDEO_DRIVER_X11_XSHAPE 1) set(HAVE_X11_XSHAPE TRUE) endif() + + if(SDL_X11_XTEST AND HAVE_XTEST_H AND XTST_LIB) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST "\"${XTST_LIB_SONAME}\"") + else() + sdl_link_dependency(xtst LIBS X11::Xtst CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xtst_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XTEST 1) + set(HAVE_X11_XTEST TRUE) + endif() endif() endif() if(NOT HAVE_X11) diff --git a/docs/README-linux.md b/docs/README-linux.md index 3f2d2c055..8399881cd 100644 --- a/docs/README-linux.md +++ b/docs/README-linux.md @@ -17,7 +17,7 @@ Ubuntu 18.04, all available features enabled: sudo apt-get install build-essential git make \ pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \ libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ - libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \ + libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \ libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \ libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev @@ -46,7 +46,7 @@ openSUSE Tumbleweed: libgbm-devel pipewire-devel libpulse-devel sndio-devel Mesa-libEGL-devel Arch: - sudo pacman -S alsa-lib cmake hidapi ibus jack libdecor libgl libpulse libusb libx11 libxcursor libxext libxinerama libxkbcommon libxrandr libxrender libxss mesa ninja pipewire sndio vulkan-driver vulkan-headers wayland wayland-protocols + sudo pacman -S alsa-lib cmake hidapi ibus jack libdecor libgl libpulse libusb libx11 libxcursor libxext libxinerama libxkbcommon libxrandr libxrender libxss libxtst mesa ninja pipewire sndio vulkan-driver vulkan-headers wayland wayland-protocols Joystick does not work diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 6ce491e7d..69117559c 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -390,6 +390,7 @@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 @SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR @SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS @SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS@ +#cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST @SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST@ #cmakedefine SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM 1 #cmakedefine SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XCURSOR 1 @@ -401,6 +402,7 @@ #cmakedefine SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XSHAPE 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XSYNC 1 +#cmakedefine SDL_VIDEO_DRIVER_X11_XTEST 1 #cmakedefine SDL_VIDEO_DRIVER_QNX 1 #cmakedefine SDL_VIDEO_RENDER_D3D 1 diff --git a/src/video/x11/SDL_x11dyn.c b/src/video/x11/SDL_x11dyn.c index 7c48ed5ce..fff97cab6 100644 --- a/src/video/x11/SDL_x11dyn.c +++ b/src/video/x11/SDL_x11dyn.c @@ -56,6 +56,9 @@ typedef struct #ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS #define SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS NULL #endif +#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST NULL +#endif static x11dynlib x11libs[] = { { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC }, @@ -64,7 +67,8 @@ static x11dynlib x11libs[] = { { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 }, { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES }, { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR }, - { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS } + { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS }, + { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST } }; static void *X11_GetSym(const char *fnname, int *pHasModule) diff --git a/src/video/x11/SDL_x11mouse.c b/src/video/x11/SDL_x11mouse.c index 5c72dbfae..343d86544 100644 --- a/src/video/x11/SDL_x11mouse.c +++ b/src/video/x11/SDL_x11mouse.c @@ -26,6 +26,7 @@ #include "SDL_x11video.h" #include "SDL_x11mouse.h" #include "SDL_x11xinput2.h" +#include "SDL_x11xtest.h" #include "../SDL_video_c.h" #include "../../events/SDL_mouse_c.h" @@ -367,6 +368,10 @@ static bool X11_WarpMouse(SDL_Window *window, float x, float y) { SDL_WindowData *data = window->internal; + if (X11_WarpMouseXTest(SDL_GetVideoDevice(), window, x, y)) { + return true; + } + #ifdef SDL_VIDEO_DRIVER_X11_XFIXES // If we have no barrier, we need to warp if (data->pointer_barrier_active == false) { @@ -380,6 +385,10 @@ static bool X11_WarpMouse(SDL_Window *window, float x, float y) static bool X11_WarpMouseGlobal(float x, float y) { + if (X11_WarpMouseXTest(SDL_GetVideoDevice(), NULL, x, y)) { + return true; + } + X11_WarpMouseInternal(DefaultRootWindow(GetDisplay()), x, y); return true; } diff --git a/src/video/x11/SDL_x11sym.h b/src/video/x11/SDL_x11sym.h index 68d70cd27..8e084d7ed 100644 --- a/src/video/x11/SDL_x11sym.h +++ b/src/video/x11/SDL_x11sym.h @@ -182,6 +182,12 @@ SDL_X11_SYM(Status, XSyncDestroyCounter, (Display* a, XSyncCounter b), (a, b), r SDL_X11_SYM(Status, XSyncSetCounter, (Display* a, XSyncCounter b, XSyncValue c), (a, b, c), return) #endif +#ifdef SDL_VIDEO_DRIVER_X11_XTEST +SDL_X11_MODULE(XTEST) +SDL_X11_SYM(Status, XTestQueryExtension, (Display* a, int* b, int* c), (a, b, c), return) +SDL_X11_SYM(int, XTestFakeMotionEvent, (Display* a, int b, int c, int d, unsigned long e), (a, b, c, d, e), return) +#endif + #ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS SDL_X11_SYM(Bool,XGetEventData,(Display* a,XGenericEventCookie* b),(a,b),return) SDL_X11_SYM(void,XFreeEventData,(Display* a,XGenericEventCookie* b),(a,b),) diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 75862db22..a03b97fbf 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -39,6 +39,7 @@ #include "SDL_x11messagebox.h" #include "SDL_x11shape.h" #include "SDL_x11xsync.h" +#include "SDL_x11xtest.h" #ifdef SDL_VIDEO_OPENGL_EGL #include "SDL_x11opengles.h" @@ -443,13 +444,17 @@ static bool X11_VideoInit(SDL_VideoDevice *_this) #ifdef SDL_VIDEO_DRIVER_X11_XFIXES X11_InitXfixes(_this); -#endif // SDL_VIDEO_DRIVER_X11_XFIXES +#endif X11_InitXsettings(_this); #ifdef SDL_VIDEO_DRIVER_X11_XSYNC X11_InitXsync(_this); -#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_XTEST + X11_InitXTest(_this); +#endif #ifndef X_HAVE_UTF8_STRING #warning X server does not support UTF8_STRING, a feature introduced in 2000! This is likely to become a hard error in a future libSDL3. diff --git a/src/video/x11/SDL_x11xtest.c b/src/video/x11/SDL_x11xtest.c new file mode 100644 index 000000000..691e5eb32 --- /dev/null +++ b/src/video/x11/SDL_x11xtest.c @@ -0,0 +1,83 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_X11) + +#include "SDL_x11video.h" +#include "SDL_x11xtest.h" + +static bool xtest_initialized = false; + +void X11_InitXTest(SDL_VideoDevice *_this) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XTEST + Display *display = _this->internal->display; + int event, error; + int opcode; + + if (!SDL_X11_HAVE_XTEST || + !X11_XQueryExtension(display, "XTEST", &opcode, &event, &error)) { + return; + } + + xtest_initialized = true; +#endif +} + +bool X11_XTestIsInitialized(void) +{ + return xtest_initialized; +} + +bool X11_WarpMouseXTest(SDL_VideoDevice *_this, SDL_Window *window, float x, float y) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XTEST + if (!X11_XTestIsInitialized()) { + return false; + } + + Display *display = _this->internal->display; + SDL_DisplayData *displaydata = window ? SDL_GetDisplayDriverDataForWindow(window) : SDL_GetDisplayDriverData(SDL_GetPrimaryDisplay()); + if (!displaydata) { + return false; + } + + int motion_x = (int)SDL_roundf(x); + int motion_y = (int)SDL_roundf(y); + if (window) { + motion_x += window->x; + motion_y += window->y; + } + + if (!X11_XTestFakeMotionEvent(display, displaydata->screen, motion_x, motion_y, CurrentTime)) { + return false; + } + X11_XSync(display, False); + + return true; +#else + return false; +#endif +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/src/video/x11/SDL_x11xtest.h b/src/video/x11/SDL_x11xtest.h new file mode 100644 index 000000000..c4ff07034 --- /dev/null +++ b/src/video/x11/SDL_x11xtest.h @@ -0,0 +1,31 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifndef SDL_x11xtest_h_ +#define SDL_x11xtest_h_ + +extern void X11_InitXTest(SDL_VideoDevice *_this); +extern bool X11_XTestIsInitialized(void); +extern bool X11_WarpMouseXTest(SDL_VideoDevice *_this, SDL_Window *window, float x, float y); + +#endif // SDL_x11xtest_h_