SDL_x11mouse.c (13712B)
1 /* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2020 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... */ 33 static Cursor x11_empty_cursor = None; 34 35 static Display * 36 GetDisplay(void) 37 { 38 return ((SDL_VideoData *)SDL_GetVideoDevice()->driverdata)->display; 39 } 40 41 static Cursor 42 X11_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 63 static void 64 X11_DestroyEmptyCursor(void) 65 { 66 if (x11_empty_cursor != None) { 67 X11_XFreeCursor(GetDisplay(), x11_empty_cursor); 68 x11_empty_cursor = None; 69 } 70 } 71 72 static SDL_Cursor * 73 X11_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 89 static Cursor 90 X11_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 117 static Cursor 118 X11_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 202 static SDL_Cursor * 203 X11_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 227 static SDL_Cursor * 228 X11_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 268 static void 269 X11_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 279 static int 280 X11_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 310 static void 311 WarpMouseInternal(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 320 static void 321 X11_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 327 static int 328 X11_WarpMouseGlobal(int x, int y) 329 { 330 WarpMouseInternal(DefaultRootWindow(GetDisplay()), x, y); 331 return 0; 332 } 333 334 static int 335 X11_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 346 static int 347 X11_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 369 static Uint32 370 X11_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 421 void 422 X11_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 439 void 440 X11_QuitMouse(_THIS) 441 { 442 X11_DestroyEmptyCursor(); 443 } 444 445 #endif /* SDL_VIDEO_DRIVER_X11 */ 446 447 /* vi: set ts=4 sw=4 expandtab: */