summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c')
-rw-r--r--contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c552
1 files changed, 552 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c
new file mode 100644
index 0000000..5c72dbf
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c
@@ -0,0 +1,552 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_DRIVER_X11
24
25#include <X11/cursorfont.h>
26#include "SDL_x11video.h"
27#include "SDL_x11mouse.h"
28#include "SDL_x11xinput2.h"
29#include "../SDL_video_c.h"
30#include "../../events/SDL_mouse_c.h"
31
32struct SDL_CursorData
33{
34 Cursor cursor;
35};
36
37// FIXME: Find a better place to put this...
38static Cursor x11_empty_cursor = None;
39static bool x11_cursor_visible = true;
40
41static SDL_Cursor *sys_cursors[SDL_HITTEST_RESIZE_LEFT + 1];
42
43static Display *GetDisplay(void)
44{
45 return SDL_GetVideoDevice()->internal->display;
46}
47
48static Cursor X11_CreateEmptyCursor(void)
49{
50 if (x11_empty_cursor == None) {
51 Display *display = GetDisplay();
52 char data[1];
53 XColor color;
54 Pixmap pixmap;
55
56 SDL_zeroa(data);
57 color.red = color.green = color.blue = 0;
58 pixmap = X11_XCreateBitmapFromData(display, DefaultRootWindow(display),
59 data, 1, 1);
60 if (pixmap) {
61 x11_empty_cursor = X11_XCreatePixmapCursor(display, pixmap, pixmap,
62 &color, &color, 0, 0);
63 X11_XFreePixmap(display, pixmap);
64 }
65 }
66 return x11_empty_cursor;
67}
68
69static void X11_DestroyEmptyCursor(void)
70{
71 if (x11_empty_cursor != None) {
72 X11_XFreeCursor(GetDisplay(), x11_empty_cursor);
73 x11_empty_cursor = None;
74 }
75}
76
77static SDL_Cursor *X11_CreateCursorAndData(Cursor x11_cursor)
78{
79 SDL_Cursor *cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
80 if (cursor) {
81 SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data));
82 if (!data) {
83 SDL_free(cursor);
84 return NULL;
85 }
86 data->cursor = x11_cursor;
87 cursor->internal = data;
88 }
89 return cursor;
90}
91
92#ifdef SDL_VIDEO_DRIVER_X11_XCURSOR
93static Cursor X11_CreateXCursorCursor(SDL_Surface *surface, int hot_x, int hot_y)
94{
95 Display *display = GetDisplay();
96 Cursor cursor = None;
97 XcursorImage *image;
98
99 image = X11_XcursorImageCreate(surface->w, surface->h);
100 if (!image) {
101 SDL_OutOfMemory();
102 return None;
103 }
104 image->xhot = hot_x;
105 image->yhot = hot_y;
106 image->delay = 0;
107
108 SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888);
109 SDL_assert(surface->pitch == surface->w * 4);
110 SDL_memcpy(image->pixels, surface->pixels, (size_t)surface->h * surface->pitch);
111
112 cursor = X11_XcursorImageLoadCursor(display, image);
113
114 X11_XcursorImageDestroy(image);
115
116 return cursor;
117}
118#endif // SDL_VIDEO_DRIVER_X11_XCURSOR
119
120static Cursor X11_CreatePixmapCursor(SDL_Surface *surface, int hot_x, int hot_y)
121{
122 Display *display = GetDisplay();
123 XColor fg, bg;
124 Cursor cursor = None;
125 Uint32 *ptr;
126 Uint8 *data_bits, *mask_bits;
127 Pixmap data_pixmap, mask_pixmap;
128 int x, y;
129 unsigned int rfg, gfg, bfg, rbg, gbg, bbg, fgBits, bgBits;
130 size_t width_bytes = ((surface->w + 7) & ~((size_t)7)) / 8;
131
132 data_bits = SDL_calloc(1, surface->h * width_bytes);
133 if (!data_bits) {
134 return None;
135 }
136
137 mask_bits = SDL_calloc(1, surface->h * width_bytes);
138 if (!mask_bits) {
139 SDL_free(data_bits);
140 return None;
141 }
142
143 // Code below assumes ARGB pixel format
144 SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888);
145
146 rfg = gfg = bfg = rbg = gbg = bbg = fgBits = bgBits = 0;
147 for (y = 0; y < surface->h; ++y) {
148 ptr = (Uint32 *)((Uint8 *)surface->pixels + y * surface->pitch);
149 for (x = 0; x < surface->w; ++x) {
150 int alpha = (*ptr >> 24) & 0xff;
151 int red = (*ptr >> 16) & 0xff;
152 int green = (*ptr >> 8) & 0xff;
153 int blue = (*ptr >> 0) & 0xff;
154 if (alpha > 25) {
155 mask_bits[y * width_bytes + x / 8] |= (0x01 << (x % 8));
156
157 if ((red + green + blue) > 0x40) {
158 fgBits++;
159 rfg += red;
160 gfg += green;
161 bfg += blue;
162 data_bits[y * width_bytes + x / 8] |= (0x01 << (x % 8));
163 } else {
164 bgBits++;
165 rbg += red;
166 gbg += green;
167 bbg += blue;
168 }
169 }
170 ++ptr;
171 }
172 }
173
174 if (fgBits) {
175 fg.red = rfg * 257 / fgBits;
176 fg.green = gfg * 257 / fgBits;
177 fg.blue = bfg * 257 / fgBits;
178 } else {
179 fg.red = fg.green = fg.blue = 0;
180 }
181
182 if (bgBits) {
183 bg.red = rbg * 257 / bgBits;
184 bg.green = gbg * 257 / bgBits;
185 bg.blue = bbg * 257 / bgBits;
186 } else {
187 bg.red = bg.green = bg.blue = 0;
188 }
189
190 data_pixmap = X11_XCreateBitmapFromData(display, DefaultRootWindow(display),
191 (char *)data_bits,
192 surface->w, surface->h);
193 mask_pixmap = X11_XCreateBitmapFromData(display, DefaultRootWindow(display),
194 (char *)mask_bits,
195 surface->w, surface->h);
196 cursor = X11_XCreatePixmapCursor(display, data_pixmap, mask_pixmap,
197 &fg, &bg, hot_x, hot_y);
198 X11_XFreePixmap(display, data_pixmap);
199 X11_XFreePixmap(display, mask_pixmap);
200 SDL_free(data_bits);
201 SDL_free(mask_bits);
202
203 return cursor;
204}
205
206static SDL_Cursor *X11_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
207{
208 Cursor x11_cursor = None;
209
210#ifdef SDL_VIDEO_DRIVER_X11_XCURSOR
211 if (SDL_X11_HAVE_XCURSOR) {
212 x11_cursor = X11_CreateXCursorCursor(surface, hot_x, hot_y);
213 }
214#endif
215 if (x11_cursor == None) {
216 x11_cursor = X11_CreatePixmapCursor(surface, hot_x, hot_y);
217 }
218 return X11_CreateCursorAndData(x11_cursor);
219}
220
221static unsigned int GetLegacySystemCursorShape(SDL_SystemCursor id)
222{
223 switch (id) {
224 // X Font Cursors reference:
225 // http://tronche.com/gui/x/xlib/appendix/b/
226 case SDL_SYSTEM_CURSOR_DEFAULT: return XC_left_ptr;
227 case SDL_SYSTEM_CURSOR_TEXT: return XC_xterm;
228 case SDL_SYSTEM_CURSOR_WAIT: return XC_watch;
229 case SDL_SYSTEM_CURSOR_CROSSHAIR: return XC_tcross;
230 case SDL_SYSTEM_CURSOR_PROGRESS: return XC_watch;
231 case SDL_SYSTEM_CURSOR_NWSE_RESIZE: return XC_top_left_corner;
232 case SDL_SYSTEM_CURSOR_NESW_RESIZE: return XC_top_right_corner;
233 case SDL_SYSTEM_CURSOR_EW_RESIZE: return XC_sb_h_double_arrow;
234 case SDL_SYSTEM_CURSOR_NS_RESIZE: return XC_sb_v_double_arrow;
235 case SDL_SYSTEM_CURSOR_MOVE: return XC_fleur;
236 case SDL_SYSTEM_CURSOR_NOT_ALLOWED: return XC_pirate;
237 case SDL_SYSTEM_CURSOR_POINTER: return XC_hand2;
238 case SDL_SYSTEM_CURSOR_NW_RESIZE: return XC_top_left_corner;
239 case SDL_SYSTEM_CURSOR_N_RESIZE: return XC_top_side;
240 case SDL_SYSTEM_CURSOR_NE_RESIZE: return XC_top_right_corner;
241 case SDL_SYSTEM_CURSOR_E_RESIZE: return XC_right_side;
242 case SDL_SYSTEM_CURSOR_SE_RESIZE: return XC_bottom_right_corner;
243 case SDL_SYSTEM_CURSOR_S_RESIZE: return XC_bottom_side;
244 case SDL_SYSTEM_CURSOR_SW_RESIZE: return XC_bottom_left_corner;
245 case SDL_SYSTEM_CURSOR_W_RESIZE: return XC_left_side;
246 case SDL_SYSTEM_CURSOR_COUNT: break; // so the compiler might notice if an enum value is missing here.
247 }
248
249 SDL_assert(0);
250 return 0;
251}
252
253static SDL_Cursor *X11_CreateSystemCursor(SDL_SystemCursor id)
254{
255 SDL_Cursor *cursor = NULL;
256 Display *dpy = GetDisplay();
257 Cursor x11_cursor = None;
258
259#ifdef SDL_VIDEO_DRIVER_X11_XCURSOR
260 if (SDL_X11_HAVE_XCURSOR) {
261 x11_cursor = X11_XcursorLibraryLoadCursor(dpy, SDL_GetCSSCursorName(id, NULL));
262 }
263#endif
264
265 if (x11_cursor == None) {
266 x11_cursor = X11_XCreateFontCursor(dpy, GetLegacySystemCursorShape(id));
267 }
268
269 if (x11_cursor != None) {
270 cursor = X11_CreateCursorAndData(x11_cursor);
271 }
272
273 return cursor;
274}
275
276static SDL_Cursor *X11_CreateDefaultCursor(void)
277{
278 SDL_SystemCursor id = SDL_GetDefaultSystemCursor();
279 return X11_CreateSystemCursor(id);
280}
281
282static void X11_FreeCursor(SDL_Cursor *cursor)
283{
284 Cursor x11_cursor = cursor->internal->cursor;
285
286 if (x11_cursor != None) {
287 X11_XFreeCursor(GetDisplay(), x11_cursor);
288 }
289 SDL_free(cursor->internal);
290 SDL_free(cursor);
291}
292
293static bool X11_ShowCursor(SDL_Cursor *cursor)
294{
295 Cursor x11_cursor = 0;
296
297 if (cursor) {
298 x11_cursor = cursor->internal->cursor;
299 } else {
300 x11_cursor = X11_CreateEmptyCursor();
301 }
302
303 // FIXME: Is there a better way than this?
304 {
305 SDL_VideoDevice *video = SDL_GetVideoDevice();
306 Display *display = GetDisplay();
307 SDL_Window *window;
308
309 x11_cursor_visible = !!cursor;
310
311 for (window = video->windows; window; window = window->next) {
312 SDL_WindowData *data = window->internal;
313 if (data) {
314 if (x11_cursor != None) {
315 X11_XDefineCursor(display, data->xwindow, x11_cursor);
316 } else {
317 X11_XUndefineCursor(display, data->xwindow);
318 }
319 }
320 }
321 X11_XFlush(display);
322 }
323 return true;
324}
325
326static void X11_WarpMouseInternal(Window xwindow, float x, float y)
327{
328 SDL_VideoData *videodata = SDL_GetVideoDevice()->internal;
329 Display *display = videodata->display;
330 bool warp_hack = false;
331
332 // XWayland will only warp the cursor if it is hidden, so this workaround is required.
333 if (videodata->is_xwayland && x11_cursor_visible) {
334 warp_hack = true;
335 }
336
337 if (warp_hack) {
338 X11_ShowCursor(NULL);
339 }
340#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
341 int deviceid = 0;
342 if (X11_Xinput2IsInitialized()) {
343 /* It seems XIWarpPointer() doesn't work correctly on multi-head setups:
344 * https://developer.blender.org/rB165caafb99c6846e53d11c4e966990aaffc06cea
345 */
346 if (ScreenCount(display) == 1) {
347 X11_XIGetClientPointer(display, None, &deviceid);
348 }
349 }
350 if (deviceid != 0) {
351 SDL_assert(SDL_X11_HAVE_XINPUT2);
352 X11_XIWarpPointer(display, deviceid, None, xwindow, 0.0, 0.0, 0, 0, x, y);
353 } else
354#endif
355 {
356 X11_XWarpPointer(display, None, xwindow, 0, 0, 0, 0, (int)x, (int)y);
357 }
358
359 if (warp_hack) {
360 X11_ShowCursor(SDL_GetCursor());
361 }
362 X11_XSync(display, False);
363 videodata->global_mouse_changed = true;
364}
365
366static bool X11_WarpMouse(SDL_Window *window, float x, float y)
367{
368 SDL_WindowData *data = window->internal;
369
370#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
371 // If we have no barrier, we need to warp
372 if (data->pointer_barrier_active == false) {
373 X11_WarpMouseInternal(data->xwindow, x, y);
374 }
375#else
376 X11_WarpMouseInternal(data->xwindow, x, y);
377#endif
378 return true;
379}
380
381static bool X11_WarpMouseGlobal(float x, float y)
382{
383 X11_WarpMouseInternal(DefaultRootWindow(GetDisplay()), x, y);
384 return true;
385}
386
387static bool X11_SetRelativeMouseMode(bool enabled)
388{
389 if (!X11_Xinput2IsInitialized()) {
390 return SDL_Unsupported();
391 }
392 return true;
393}
394
395static bool X11_CaptureMouse(SDL_Window *window)
396{
397 Display *display = GetDisplay();
398 SDL_Window *mouse_focus = SDL_GetMouseFocus();
399
400 if (window) {
401 SDL_WindowData *data = window->internal;
402
403 /* If XInput2 is handling the pointer input, non-confinement grabs will always fail with 'AlreadyGrabbed',
404 * since the pointer is being grabbed by XInput2.
405 */
406 if (!data->xinput2_mouse_enabled || data->mouse_grabbed) {
407 const unsigned int mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask;
408 Window confined = (data->mouse_grabbed ? data->xwindow : None);
409 const int rc = X11_XGrabPointer(display, data->xwindow, False,
410 mask, GrabModeAsync, GrabModeAsync,
411 confined, None, CurrentTime);
412 if (rc != GrabSuccess) {
413 return SDL_SetError("X server refused mouse capture");
414 }
415
416 if (data->mouse_grabbed) {
417 // XGrabPointer can warp the cursor when confining, so update the coordinates.
418 data->videodata->global_mouse_changed = true;
419 }
420 }
421 } else if (mouse_focus) {
422 SDL_UpdateWindowGrab(mouse_focus);
423 } else {
424 X11_XUngrabPointer(display, CurrentTime);
425 }
426
427 X11_XSync(display, False);
428
429 return true;
430}
431
432static SDL_MouseButtonFlags X11_GetGlobalMouseState(float *x, float *y)
433{
434 SDL_VideoData *videodata = SDL_GetVideoDevice()->internal;
435 SDL_DisplayID *displays;
436 Display *display = GetDisplay();
437 int i;
438
439 // !!! FIXME: should we XSync() here first?
440
441 if (!X11_Xinput2IsInitialized()) {
442 videodata->global_mouse_changed = true;
443 }
444
445 // check if we have this cached since XInput last saw the mouse move.
446 // !!! FIXME: can we just calculate this from XInput's events?
447 if (videodata->global_mouse_changed) {
448 displays = SDL_GetDisplays(NULL);
449 if (displays) {
450 for (i = 0; displays[i]; ++i) {
451 SDL_DisplayData *data = SDL_GetDisplayDriverData(displays[i]);
452 if (data) {
453 Window root, child;
454 int rootx, rooty, winx, winy;
455 unsigned int mask;
456 if (X11_XQueryPointer(display, RootWindow(display, data->screen), &root, &child, &rootx, &rooty, &winx, &winy, &mask)) {
457 XWindowAttributes root_attrs;
458 SDL_MouseButtonFlags buttons = 0;
459 buttons |= (mask & Button1Mask) ? SDL_BUTTON_LMASK : 0;
460 buttons |= (mask & Button2Mask) ? SDL_BUTTON_MMASK : 0;
461 buttons |= (mask & Button3Mask) ? SDL_BUTTON_RMASK : 0;
462 // Use the SDL state for the extended buttons - it's better than nothing
463 buttons |= (SDL_GetMouseState(NULL, NULL) & (SDL_BUTTON_X1MASK | SDL_BUTTON_X2MASK));
464 /* SDL_DisplayData->x,y point to screen origin, and adding them to mouse coordinates relative to root window doesn't do the right thing
465 * (observed on dual monitor setup with primary display being the rightmost one - mouse was offset to the right).
466 *
467 * Adding root position to root-relative coordinates seems to be a better way to get absolute position. */
468 X11_XGetWindowAttributes(display, root, &root_attrs);
469 videodata->global_mouse_position.x = root_attrs.x + rootx;
470 videodata->global_mouse_position.y = root_attrs.y + rooty;
471 videodata->global_mouse_buttons = buttons;
472 videodata->global_mouse_changed = false;
473 break;
474 }
475 }
476 }
477 SDL_free(displays);
478 }
479 }
480
481 SDL_assert(!videodata->global_mouse_changed); // The pointer wasn't on any X11 screen?!
482
483 *x = (float)videodata->global_mouse_position.x;
484 *y = (float)videodata->global_mouse_position.y;
485 return videodata->global_mouse_buttons;
486}
487
488void X11_InitMouse(SDL_VideoDevice *_this)
489{
490 SDL_Mouse *mouse = SDL_GetMouse();
491
492 mouse->CreateCursor = X11_CreateCursor;
493 mouse->CreateSystemCursor = X11_CreateSystemCursor;
494 mouse->ShowCursor = X11_ShowCursor;
495 mouse->FreeCursor = X11_FreeCursor;
496 mouse->WarpMouse = X11_WarpMouse;
497 mouse->WarpMouseGlobal = X11_WarpMouseGlobal;
498 mouse->SetRelativeMouseMode = X11_SetRelativeMouseMode;
499 mouse->CaptureMouse = X11_CaptureMouse;
500 mouse->GetGlobalMouseState = X11_GetGlobalMouseState;
501
502 SDL_HitTestResult r = SDL_HITTEST_NORMAL;
503 while (r <= SDL_HITTEST_RESIZE_LEFT) {
504 switch (r) {
505 case SDL_HITTEST_NORMAL: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); break;
506 case SDL_HITTEST_DRAGGABLE: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); break;
507 case SDL_HITTEST_RESIZE_TOPLEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_NW_RESIZE); break;
508 case SDL_HITTEST_RESIZE_TOP: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_N_RESIZE); break;
509 case SDL_HITTEST_RESIZE_TOPRIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_NE_RESIZE); break;
510 case SDL_HITTEST_RESIZE_RIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_E_RESIZE); break;
511 case SDL_HITTEST_RESIZE_BOTTOMRIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SE_RESIZE); break;
512 case SDL_HITTEST_RESIZE_BOTTOM: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_S_RESIZE); break;
513 case SDL_HITTEST_RESIZE_BOTTOMLEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SW_RESIZE); break;
514 case SDL_HITTEST_RESIZE_LEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_W_RESIZE); break;
515 }
516 r++;
517 }
518
519 SDL_SetDefaultCursor(X11_CreateDefaultCursor());
520}
521
522void X11_QuitMouse(SDL_VideoDevice *_this)
523{
524 SDL_VideoData *data = _this->internal;
525 SDL_XInput2DeviceInfo *i;
526 SDL_XInput2DeviceInfo *next;
527 int j;
528
529 for (j = 0; j < SDL_arraysize(sys_cursors); j++) {
530 X11_FreeCursor(sys_cursors[j]);
531 sys_cursors[j] = NULL;
532 }
533
534 for (i = data->mouse_device_info; i; i = next) {
535 next = i->next;
536 SDL_free(i);
537 }
538 data->mouse_device_info = NULL;
539
540 X11_DestroyEmptyCursor();
541}
542
543void X11_SetHitTestCursor(SDL_HitTestResult rc)
544{
545 if (rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) {
546 SDL_SetCursor(NULL);
547 } else {
548 X11_ShowCursor(sys_cursors[rc]);
549 }
550}
551
552#endif // SDL_VIDEO_DRIVER_X11