1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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#if 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 "../../events/SDL_mouse_c.h"
30
31
32/* FIXME: Find a better place to put this... */
33static Cursor x11_empty_cursor = None;
34
35static Display *
36GetDisplay(void)
37{
38 return ((SDL_VideoData *)SDL_GetVideoDevice()->driverdata)->display;
39}
40
41static Cursor
42X11_CreateEmptyCursor()
43{
44 if (x11_empty_cursor == None) {
45 Display *display = GetDisplay();
46 char data[1];
47 XColor color;
48 Pixmap pixmap;
49
50 SDL_zeroa(data);
51 color.red = color.green = color.blue = 0;
52 pixmap = X11_XCreateBitmapFromData(display, DefaultRootWindow(display),
53 data, 1, 1);
54 if (pixmap) {
55 x11_empty_cursor = X11_XCreatePixmapCursor(display, pixmap, pixmap,
56 &color, &color, 0, 0);
57 X11_XFreePixmap(display, pixmap);
58 }
59 }
60 return x11_empty_cursor;
61}
62
63static void
64X11_DestroyEmptyCursor(void)
65{
66 if (x11_empty_cursor != None) {
67 X11_XFreeCursor(GetDisplay(), x11_empty_cursor);
68 x11_empty_cursor = None;
69 }
70}
71
72static SDL_Cursor *
73X11_CreateDefaultCursor()
74{
75 SDL_Cursor *cursor;
76
77 cursor = SDL_calloc(1, sizeof(*cursor));
78 if (cursor) {
79 /* None is used to indicate the default cursor */
80 cursor->driverdata = (void*)None;
81 } else {
82 SDL_OutOfMemory();
83 }
84
85 return cursor;
86}
87
88#if SDL_VIDEO_DRIVER_X11_XCURSOR
89static Cursor
90X11_CreateXCursorCursor(SDL_Surface * surface, int hot_x, int hot_y)
91{
92 Display *display = GetDisplay();
93 Cursor cursor = None;
94 XcursorImage *image;
95
96 image = X11_XcursorImageCreate(surface->w, surface->h);
97 if (!image) {
98 SDL_OutOfMemory();
99 return None;
100 }
101 image->xhot = hot_x;
102 image->yhot = hot_y;
103 image->delay = 0;
104
105 SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888);
106 SDL_assert(surface->pitch == surface->w * 4);
107 SDL_memcpy(image->pixels, surface->pixels, surface->h * surface->pitch);
108
109 cursor = X11_XcursorImageLoadCursor(display, image);
110
111 X11_XcursorImageDestroy(image);
112
113 return cursor;
114}
115#endif /* SDL_VIDEO_DRIVER_X11_XCURSOR */
116
117static Cursor
118X11_CreatePixmapCursor(SDL_Surface * surface, int hot_x, int hot_y)
119{
120 Display *display = GetDisplay();
121 XColor fg, bg;
122 Cursor cursor = None;
123 Uint32 *ptr;
124 Uint8 *data_bits, *mask_bits;
125 Pixmap data_pixmap, mask_pixmap;
126 int x, y;
127 unsigned int rfg, gfg, bfg, rbg, gbg, bbg, fgBits, bgBits;
128 unsigned int width_bytes = ((surface->w + 7) & ~7) / 8;
129
130 data_bits = SDL_calloc(1, surface->h * width_bytes);
131 if (!data_bits) {
132 SDL_OutOfMemory();
133 return None;
134 }
135
136 mask_bits = SDL_calloc(1, surface->h * width_bytes);
137 if (!mask_bits) {
138 SDL_free(data_bits);
139 SDL_OutOfMemory();
140 return None;
141 }
142
143 /* Code below assumes ARGB pixel format */
144 SDL_assert(surface->format->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 }
179 else fg.red = fg.green = fg.blue = 0;
180
181 if (bgBits) {
182 bg.red = rbg * 257 / bgBits;
183 bg.green = gbg * 257 / bgBits;
184 bg.blue = bbg * 257 / bgBits;
185 }
186 else bg.red = bg.green = bg.blue = 0;
187
188 data_pixmap = X11_XCreateBitmapFromData(display, DefaultRootWindow(display),
189 (char*)data_bits,
190 surface->w, surface->h);
191 mask_pixmap = X11_XCreateBitmapFromData(display, DefaultRootWindow(display),
192 (char*)mask_bits,
193 surface->w, surface->h);
194 cursor = X11_XCreatePixmapCursor(display, data_pixmap, mask_pixmap,
195 &fg, &bg, hot_x, hot_y);
196 X11_XFreePixmap(display, data_pixmap);
197 X11_XFreePixmap(display, mask_pixmap);
198
199 return cursor;
200}
201
202static SDL_Cursor *
203X11_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
204{
205 SDL_Cursor *cursor;
206
207 cursor = SDL_calloc(1, sizeof(*cursor));
208 if (cursor) {
209 Cursor x11_cursor = None;
210
211#if SDL_VIDEO_DRIVER_X11_XCURSOR
212 if (SDL_X11_HAVE_XCURSOR) {
213 x11_cursor = X11_CreateXCursorCursor(surface, hot_x, hot_y);
214 }
215#endif
216 if (x11_cursor == None) {
217 x11_cursor = X11_CreatePixmapCursor(surface, hot_x, hot_y);
218 }
219 cursor->driverdata = (void*)x11_cursor;
220 } else {
221 SDL_OutOfMemory();
222 }
223
224 return cursor;
225}
226
227static SDL_Cursor *
228X11_CreateSystemCursor(SDL_SystemCursor id)
229{
230 SDL_Cursor *cursor;
231 unsigned int shape;
232
233 switch(id)
234 {
235 default:
236 SDL_assert(0);
237 return NULL;
238 /* X Font Cursors reference: */
239 /* http://tronche.com/gui/x/xlib/appendix/b/ */
240 case SDL_SYSTEM_CURSOR_ARROW: shape = XC_left_ptr; break;
241 case SDL_SYSTEM_CURSOR_IBEAM: shape = XC_xterm; break;
242 case SDL_SYSTEM_CURSOR_WAIT: shape = XC_watch; break;
243 case SDL_SYSTEM_CURSOR_CROSSHAIR: shape = XC_tcross; break;
244 case SDL_SYSTEM_CURSOR_WAITARROW: shape = XC_watch; break;
245 case SDL_SYSTEM_CURSOR_SIZENWSE: shape = XC_fleur; break;
246 case SDL_SYSTEM_CURSOR_SIZENESW: shape = XC_fleur; break;
247 case SDL_SYSTEM_CURSOR_SIZEWE: shape = XC_sb_h_double_arrow; break;
248 case SDL_SYSTEM_CURSOR_SIZENS: shape = XC_sb_v_double_arrow; break;
249 case SDL_SYSTEM_CURSOR_SIZEALL: shape = XC_fleur; break;
250 case SDL_SYSTEM_CURSOR_NO: shape = XC_pirate; break;
251 case SDL_SYSTEM_CURSOR_HAND: shape = XC_hand2; break;
252 }
253
254 cursor = SDL_calloc(1, sizeof(*cursor));
255 if (cursor) {
256 Cursor x11_cursor;
257
258 x11_cursor = X11_XCreateFontCursor(GetDisplay(), shape);
259
260 cursor->driverdata = (void*)x11_cursor;
261 } else {
262 SDL_OutOfMemory();
263 }
264
265 return cursor;
266}
267
268static void
269X11_FreeCursor(SDL_Cursor * cursor)
270{
271 Cursor x11_cursor = (Cursor)cursor->driverdata;
272
273 if (x11_cursor != None) {
274 X11_XFreeCursor(GetDisplay(), x11_cursor);
275 }
276 SDL_free(cursor);
277}
278
279static int
280X11_ShowCursor(SDL_Cursor * cursor)
281{
282 Cursor x11_cursor = 0;
283
284 if (cursor) {
285 x11_cursor = (Cursor)cursor->driverdata;
286 } else {
287 x11_cursor = X11_CreateEmptyCursor();
288 }
289
290 /* FIXME: Is there a better way than this? */
291 {
292 SDL_VideoDevice *video = SDL_GetVideoDevice();
293 Display *display = GetDisplay();
294 SDL_Window *window;
295 SDL_WindowData *data;
296
297 for (window = video->windows; window; window = window->next) {
298 data = (SDL_WindowData *)window->driverdata;
299 if (x11_cursor != None) {
300 X11_XDefineCursor(display, data->xwindow, x11_cursor);
301 } else {
302 X11_XUndefineCursor(display, data->xwindow);
303 }
304 }
305 X11_XFlush(display);
306 }
307 return 0;
308}
309
310static void
311WarpMouseInternal(Window xwindow, const int x, const int y)
312{
313 SDL_VideoData *videodata = (SDL_VideoData *) SDL_GetVideoDevice()->driverdata;
314 Display *display = videodata->display;
315 X11_XWarpPointer(display, None, xwindow, 0, 0, 0, 0, x, y);
316 X11_XSync(display, False);
317 videodata->global_mouse_changed = SDL_TRUE;
318}
319
320static void
321X11_WarpMouse(SDL_Window * window, int x, int y)
322{
323 SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
324 WarpMouseInternal(data->xwindow, x, y);
325}
326
327static int
328X11_WarpMouseGlobal(int x, int y)
329{
330 WarpMouseInternal(DefaultRootWindow(GetDisplay()), x, y);
331 return 0;
332}
333
334static int
335X11_SetRelativeMouseMode(SDL_bool enabled)
336{
337#if SDL_VIDEO_DRIVER_X11_XINPUT2
338 if(X11_Xinput2IsInitialized())
339 return 0;
340#else
341 SDL_Unsupported();
342#endif
343 return -1;
344}
345
346static int
347X11_CaptureMouse(SDL_Window *window)
348{
349 Display *display = GetDisplay();
350
351 if (window) {
352 SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
353 const unsigned int mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask;
354 const int rc = X11_XGrabPointer(display, data->xwindow, False,
355 mask, GrabModeAsync, GrabModeAsync,
356 None, None, CurrentTime);
357 if (rc != GrabSuccess) {
358 return SDL_SetError("X server refused mouse capture");
359 }
360 } else {
361 X11_XUngrabPointer(display, CurrentTime);
362 }
363
364 X11_XSync(display, False);
365
366 return 0;
367}
368
369static Uint32
370X11_GetGlobalMouseState(int *x, int *y)
371{
372 SDL_VideoData *videodata = (SDL_VideoData *) SDL_GetVideoDevice()->driverdata;
373 Display *display = GetDisplay();
374 const int num_screens = SDL_GetNumVideoDisplays();
375 int i;
376
377 /* !!! FIXME: should we XSync() here first? */
378
379#if !SDL_VIDEO_DRIVER_X11_XINPUT2
380 videodata->global_mouse_changed = SDL_TRUE;
381#endif
382
383 /* check if we have this cached since XInput last saw the mouse move. */
384 /* !!! FIXME: can we just calculate this from XInput's events? */
385 if (videodata->global_mouse_changed) {
386 for (i = 0; i < num_screens; i++) {
387 SDL_DisplayData *data = (SDL_DisplayData *) SDL_GetDisplayDriverData(i);
388 if (data != NULL) {
389 Window root, child;
390 int rootx, rooty, winx, winy;
391 unsigned int mask;
392 if (X11_XQueryPointer(display, RootWindow(display, data->screen), &root, &child, &rootx, &rooty, &winx, &winy, &mask)) {
393 XWindowAttributes root_attrs;
394 Uint32 buttons = 0;
395 buttons |= (mask & Button1Mask) ? SDL_BUTTON_LMASK : 0;
396 buttons |= (mask & Button2Mask) ? SDL_BUTTON_MMASK : 0;
397 buttons |= (mask & Button3Mask) ? SDL_BUTTON_RMASK : 0;
398 /* SDL_DisplayData->x,y point to screen origin, and adding them to mouse coordinates relative to root window doesn't do the right thing
399 * (observed on dual monitor setup with primary display being the rightmost one - mouse was offset to the right).
400 *
401 * Adding root position to root-relative coordinates seems to be a better way to get absolute position. */
402 X11_XGetWindowAttributes(display, root, &root_attrs);
403 videodata->global_mouse_position.x = root_attrs.x + rootx;
404 videodata->global_mouse_position.y = root_attrs.y + rooty;
405 videodata->global_mouse_buttons = buttons;
406 videodata->global_mouse_changed = SDL_FALSE;
407 break;
408 }
409 }
410 }
411 }
412
413 SDL_assert(!videodata->global_mouse_changed); /* The pointer wasn't on any X11 screen?! */
414
415 *x = videodata->global_mouse_position.x;
416 *y = videodata->global_mouse_position.y;
417 return videodata->global_mouse_buttons;
418}
419
420
421void
422X11_InitMouse(_THIS)
423{
424 SDL_Mouse *mouse = SDL_GetMouse();
425
426 mouse->CreateCursor = X11_CreateCursor;
427 mouse->CreateSystemCursor = X11_CreateSystemCursor;
428 mouse->ShowCursor = X11_ShowCursor;
429 mouse->FreeCursor = X11_FreeCursor;
430 mouse->WarpMouse = X11_WarpMouse;
431 mouse->WarpMouseGlobal = X11_WarpMouseGlobal;
432 mouse->SetRelativeMouseMode = X11_SetRelativeMouseMode;
433 mouse->CaptureMouse = X11_CaptureMouse;
434 mouse->GetGlobalMouseState = X11_GetGlobalMouseState;
435
436 SDL_SetDefaultCursor(X11_CreateDefaultCursor());
437}
438
439void
440X11_QuitMouse(_THIS)
441{
442 X11_DestroyEmptyCursor();
443}
444
445#endif /* SDL_VIDEO_DRIVER_X11 */
446
447/* vi: set ts=4 sw=4 expandtab: */
448