src/video/x11/SDL_x11mouse.c
author Sam Lantinga
Wed, 27 Jan 2021 21:30:25 -0800
changeset 14819 f57c6e0d2bf5
parent 14640 b2b3343a310d
permissions -rw-r--r--
Fixed compiler warnings
     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... */
    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: */