src/events/SDL_mouse.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 21 Jul 2013 12:21:22 -0700
changeset 7493 7bfda8f0bfdf
parent 7191 75360622e65f
child 7677 871d43c6968a
permissions -rw-r--r--
Fixed cursor leak when quitting the mouse subsystem
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2013 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_config.h"
    22 
    23 /* General mouse handling code for SDL */
    24 
    25 #include "SDL_assert.h"
    26 #include "SDL_events.h"
    27 #include "SDL_events_c.h"
    28 #include "default_cursor.h"
    29 #include "../video/SDL_sysvideo.h"
    30 
    31 /*#define DEBUG_MOUSE*/
    32 
    33 /* The mouse state */
    34 static SDL_Mouse SDL_mouse;
    35 
    36 static int
    37 SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int x, int y);
    38 
    39 /* Public functions */
    40 int
    41 SDL_MouseInit(void)
    42 {
    43     SDL_Mouse *mouse = SDL_GetMouse();
    44 
    45     mouse->cursor_shown = SDL_TRUE;
    46 
    47     return (0);
    48 }
    49 
    50 void
    51 SDL_SetDefaultCursor(SDL_Cursor * cursor)
    52 {
    53     SDL_Mouse *mouse = SDL_GetMouse();
    54 
    55     mouse->def_cursor = cursor;
    56     if (!mouse->cur_cursor) {
    57         SDL_SetCursor(cursor);
    58     }
    59 }
    60 
    61 SDL_Mouse *
    62 SDL_GetMouse(void)
    63 {
    64     return &SDL_mouse;
    65 }
    66 
    67 SDL_Window *
    68 SDL_GetMouseFocus(void)
    69 {
    70     SDL_Mouse *mouse = SDL_GetMouse();
    71 
    72     return mouse->focus;
    73 }
    74 
    75 void
    76 SDL_ResetMouse(void)
    77 {
    78     SDL_Mouse *mouse = SDL_GetMouse();
    79     Uint8 i;
    80 
    81 #ifdef DEBUG_MOUSE
    82     printf("Resetting mouse\n");
    83 #endif
    84     for (i = 1; i <= sizeof(mouse->buttonstate)*8; ++i) {
    85         if (mouse->buttonstate & SDL_BUTTON(i)) {
    86             SDL_SendMouseButton(mouse->focus, mouse->mouseID, SDL_RELEASED, i);
    87         }
    88     }
    89     SDL_assert(mouse->buttonstate == 0);
    90 }
    91 
    92 void
    93 SDL_SetMouseFocus(SDL_Window * window)
    94 {
    95     SDL_Mouse *mouse = SDL_GetMouse();
    96 
    97     if (mouse->focus == window) {
    98         return;
    99     }
   100 
   101     /* Actually, this ends up being a bad idea, because most operating
   102        systems have an implicit grab when you press the mouse button down
   103        so you can drag things out of the window and then get the mouse up
   104        when it happens.  So, #if 0...
   105     */
   106 #if 0
   107     if (mouse->focus && !window) {
   108         /* We won't get anymore mouse messages, so reset mouse state */
   109         SDL_ResetMouse();
   110     }
   111 #endif
   112 
   113     /* See if the current window has lost focus */
   114     if (mouse->focus) {
   115         SDL_SendWindowEvent(mouse->focus, SDL_WINDOWEVENT_LEAVE, 0, 0);
   116     }
   117 
   118     mouse->focus = window;
   119 
   120     if (mouse->focus) {
   121         SDL_SendWindowEvent(mouse->focus, SDL_WINDOWEVENT_ENTER, 0, 0);
   122     }
   123 
   124     /* Update cursor visibility */
   125     SDL_SetCursor(NULL);
   126 }
   127 
   128 /* Check to see if we need to synthesize focus events */
   129 static SDL_bool
   130 SDL_UpdateMouseFocus(SDL_Window * window, int x, int y, Uint32 buttonstate)
   131 {
   132     SDL_Mouse *mouse = SDL_GetMouse();
   133     int w, h;
   134     SDL_bool inWindow;
   135 
   136     SDL_GetWindowSize(window, &w, &h);
   137     if (x < 0 || y < 0 || x >= w || y >= h) {
   138         inWindow = SDL_FALSE;
   139     } else {
   140         inWindow = SDL_TRUE;
   141     }
   142 
   143 /* Linux doesn't give you mouse events outside your window unless you grab
   144    the pointer.
   145 
   146    Windows doesn't give you mouse events outside your window unless you call
   147    SetCapture().
   148 
   149    Both of these are slightly scary changes, so for now we'll punt and if the
   150    mouse leaves the window you'll lose mouse focus and reset button state.
   151 */
   152 #ifdef SUPPORT_DRAG_OUTSIDE_WINDOW
   153     if (!inWindow && !buttonstate) {
   154 #else
   155     if (!inWindow) {
   156 #endif
   157         if (window == mouse->focus) {
   158 #ifdef DEBUG_MOUSE
   159             printf("Mouse left window, synthesizing move & focus lost event\n");
   160 #endif
   161             SDL_PrivateSendMouseMotion(window, mouse->mouseID, 0, x, y);
   162             SDL_SetMouseFocus(NULL);
   163         }
   164         return SDL_FALSE;
   165     }
   166 
   167     if (window != mouse->focus) {
   168 #ifdef DEBUG_MOUSE
   169          printf("Mouse entered window, synthesizing focus gain & move event\n");
   170 #endif
   171          SDL_SetMouseFocus(window);
   172          SDL_PrivateSendMouseMotion(window, mouse->mouseID, 0, x, y);
   173     }
   174     return SDL_TRUE;
   175 }
   176 
   177 int
   178 SDL_SendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int x, int y)
   179 {
   180     if (window && !relative) {
   181         SDL_Mouse *mouse = SDL_GetMouse();
   182         if (!SDL_UpdateMouseFocus(window, x, y, mouse->buttonstate)) {
   183             return 0;
   184         }
   185     }
   186 
   187     return SDL_PrivateSendMouseMotion(window, mouseID, relative, x, y);
   188 }
   189 
   190 static int
   191 SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int x, int y)
   192 {
   193     SDL_Mouse *mouse = SDL_GetMouse();
   194     int posted;
   195     int xrel;
   196     int yrel;
   197     int x_max = 0, y_max = 0;
   198 
   199     /* relative motion is calculated regarding the system cursor last position */
   200     if (relative) {
   201         xrel = x;
   202         yrel = y;
   203         x = (mouse->last_x + x);
   204         y = (mouse->last_y + y);
   205     } else {
   206         xrel = x - mouse->last_x;
   207         yrel = y - mouse->last_y;
   208     }
   209 
   210     /* Drop events that don't change state */
   211     if (!xrel && !yrel) {
   212 #ifdef DEBUG_MOUSE
   213         printf("Mouse event didn't change state - dropped!\n");
   214 #endif
   215         return 0;
   216     }
   217 
   218     /* Update internal mouse coordinates */
   219     if (mouse->relative_mode == SDL_FALSE) {
   220         mouse->x = x;
   221         mouse->y = y;
   222     } else {
   223         mouse->x += xrel;
   224         mouse->y += yrel;
   225     }
   226 
   227     SDL_GetWindowSize(mouse->focus, &x_max, &y_max);
   228     --x_max;
   229     --y_max;
   230 
   231     /* make sure that the pointers find themselves inside the windows */
   232     if (mouse->x > x_max) {
   233         mouse->x = x_max;
   234     }
   235     if (mouse->x < 0) {
   236         mouse->x = 0;
   237     }
   238 
   239     if (mouse->y > y_max) {
   240         mouse->y = y_max;
   241     }
   242     if (mouse->y < 0) {
   243         mouse->y = 0;
   244     }
   245 
   246     mouse->xdelta += xrel;
   247     mouse->ydelta += yrel;
   248 
   249 #if 0 /* FIXME */
   250     /* Move the mouse cursor, if needed */
   251     if (mouse->cursor_shown && !mouse->relative_mode &&
   252         mouse->MoveCursor && mouse->cur_cursor) {
   253         mouse->MoveCursor(mouse->cur_cursor);
   254     }
   255 #endif
   256 
   257     /* Post the event, if desired */
   258     posted = 0;
   259     if (SDL_GetEventState(SDL_MOUSEMOTION) == SDL_ENABLE) {
   260         SDL_Event event;
   261         event.motion.type = SDL_MOUSEMOTION;
   262         event.motion.windowID = mouse->focus ? mouse->focus->id : 0;
   263         event.motion.which = mouseID;
   264         event.motion.state = mouse->buttonstate;
   265         event.motion.x = mouse->x;
   266         event.motion.y = mouse->y;
   267         event.motion.xrel = xrel;
   268         event.motion.yrel = yrel;
   269         posted = (SDL_PushEvent(&event) > 0);
   270     }
   271     /* Use unclamped values if we're getting events outside the window */
   272     mouse->last_x = x;
   273     mouse->last_y = y;
   274     return posted;
   275 }
   276 
   277 int
   278 SDL_SendMouseButton(SDL_Window * window, SDL_MouseID mouseID, Uint8 state, Uint8 button)
   279 {
   280     SDL_Mouse *mouse = SDL_GetMouse();
   281     int posted;
   282     Uint32 type;
   283     Uint32 buttonstate = mouse->buttonstate;
   284 
   285     /* Figure out which event to perform */
   286     switch (state) {
   287     case SDL_PRESSED:
   288         type = SDL_MOUSEBUTTONDOWN;
   289         buttonstate |= SDL_BUTTON(button);
   290         break;
   291     case SDL_RELEASED:
   292         type = SDL_MOUSEBUTTONUP;
   293         buttonstate &= ~SDL_BUTTON(button);
   294         break;
   295     default:
   296         /* Invalid state -- bail */
   297         return 0;
   298     }
   299 
   300     /* We do this after calculating buttonstate so button presses gain focus */
   301     if (window && state == SDL_PRESSED) {
   302         SDL_UpdateMouseFocus(window, mouse->x, mouse->y, buttonstate);
   303     }
   304 
   305     if (buttonstate == mouse->buttonstate) {
   306         /* Ignore this event, no state change */
   307         return 0;
   308     }
   309     mouse->buttonstate = buttonstate;
   310 
   311     /* Post the event, if desired */
   312     posted = 0;
   313     if (SDL_GetEventState(type) == SDL_ENABLE) {
   314         SDL_Event event;
   315         event.type = type;
   316         event.button.windowID = mouse->focus ? mouse->focus->id : 0;
   317         event.button.which = mouseID;
   318         event.button.state = state;
   319         event.button.button = button;
   320         event.button.x = mouse->x;
   321         event.button.y = mouse->y;
   322         posted = (SDL_PushEvent(&event) > 0);
   323     }
   324 
   325     /* We do this after dispatching event so button releases can lose focus */
   326     if (window && state == SDL_RELEASED) {
   327         SDL_UpdateMouseFocus(window, mouse->x, mouse->y, buttonstate);
   328     }
   329 
   330     return posted;
   331 }
   332 
   333 int
   334 SDL_SendMouseWheel(SDL_Window * window, SDL_MouseID mouseID, int x, int y)
   335 {
   336     SDL_Mouse *mouse = SDL_GetMouse();
   337     int posted;
   338 
   339     if (window) {
   340         SDL_SetMouseFocus(window);
   341     }
   342 
   343     if (!x && !y) {
   344         return 0;
   345     }
   346 
   347     /* Post the event, if desired */
   348     posted = 0;
   349     if (SDL_GetEventState(SDL_MOUSEWHEEL) == SDL_ENABLE) {
   350         SDL_Event event;
   351         event.type = SDL_MOUSEWHEEL;
   352         event.wheel.windowID = mouse->focus ? mouse->focus->id : 0;
   353         event.wheel.which = mouseID;
   354         event.wheel.x = x;
   355         event.wheel.y = y;
   356         posted = (SDL_PushEvent(&event) > 0);
   357     }
   358     return posted;
   359 }
   360 
   361 void
   362 SDL_MouseQuit(void)
   363 {
   364     SDL_Cursor *cursor, *next;
   365     SDL_Mouse *mouse = SDL_GetMouse();
   366 
   367     SDL_ShowCursor(1);
   368 
   369     cursor = mouse->cursors;
   370     while (cursor) {
   371         next = cursor->next;
   372         SDL_FreeCursor(cursor);
   373         cursor = next;
   374     }
   375 
   376     if (mouse->def_cursor && mouse->FreeCursor) {
   377         mouse->FreeCursor(mouse->def_cursor);
   378     }
   379 
   380     SDL_zerop(mouse);
   381 }
   382 
   383 Uint32
   384 SDL_GetMouseState(int *x, int *y)
   385 {
   386     SDL_Mouse *mouse = SDL_GetMouse();
   387 
   388     if (x) {
   389         *x = mouse->x;
   390     }
   391     if (y) {
   392         *y = mouse->y;
   393     }
   394     return mouse->buttonstate;
   395 }
   396 
   397 Uint32
   398 SDL_GetRelativeMouseState(int *x, int *y)
   399 {
   400     SDL_Mouse *mouse = SDL_GetMouse();
   401 
   402     if (x) {
   403         *x = mouse->xdelta;
   404     }
   405     if (y) {
   406         *y = mouse->ydelta;
   407     }
   408     mouse->xdelta = 0;
   409     mouse->ydelta = 0;
   410     return mouse->buttonstate;
   411 }
   412 
   413 void
   414 SDL_WarpMouseInWindow(SDL_Window * window, int x, int y)
   415 {
   416     SDL_Mouse *mouse = SDL_GetMouse();
   417 
   418     if ( window == NULL )
   419         window = mouse->focus;
   420 
   421     if ( window == NULL )
   422         return;
   423 
   424     if (mouse->WarpMouse) {
   425         mouse->WarpMouse(window, x, y);
   426     } else {
   427         SDL_SendMouseMotion(window, mouse->mouseID, 0, x, y);
   428     }
   429 }
   430 
   431 int
   432 SDL_SetRelativeMouseMode(SDL_bool enabled)
   433 {
   434     SDL_Mouse *mouse = SDL_GetMouse();
   435     SDL_Window *focusWindow = SDL_GetKeyboardFocus();
   436     int original_x = mouse->x, original_y = mouse->y;
   437 
   438     if (enabled == mouse->relative_mode) {
   439         return 0;
   440     }
   441 
   442     if (!mouse->SetRelativeMouseMode) {
   443         return SDL_Unsupported();
   444     }
   445 
   446     if (enabled && focusWindow) {
   447         /* Center it in the focused window to prevent clicks from going through
   448          * to background windows.
   449          */
   450         SDL_SetMouseFocus(focusWindow);
   451         SDL_WarpMouseInWindow(focusWindow, focusWindow->w/2, focusWindow->h/2);
   452     }
   453 
   454     if (mouse->SetRelativeMouseMode(enabled) < 0) {
   455         return -1;
   456     }
   457 
   458     /* Set the relative mode */
   459     mouse->relative_mode = enabled;
   460 
   461     if (enabled) {
   462         /* Save the expected mouse position */
   463         mouse->original_x = original_x;
   464         mouse->original_y = original_y;
   465     } else if (mouse->focus) {
   466         /* Restore the expected mouse position */
   467         SDL_WarpMouseInWindow(mouse->focus, mouse->original_x, mouse->original_y);
   468     }
   469 
   470     /* Flush pending mouse motion */
   471     SDL_FlushEvent(SDL_MOUSEMOTION);
   472 
   473     /* Update cursor visibility */
   474     SDL_SetCursor(NULL);
   475 
   476     return 0;
   477 }
   478 
   479 SDL_bool
   480 SDL_GetRelativeMouseMode()
   481 {
   482     SDL_Mouse *mouse = SDL_GetMouse();
   483 
   484     return mouse->relative_mode;
   485 }
   486 
   487 SDL_Cursor *
   488 SDL_CreateCursor(const Uint8 * data, const Uint8 * mask,
   489                  int w, int h, int hot_x, int hot_y)
   490 {
   491     SDL_Surface *surface;
   492     SDL_Cursor *cursor;
   493     int x, y;
   494     Uint32 *pixel;
   495     Uint8 datab = 0, maskb = 0;
   496     const Uint32 black = 0xFF000000;
   497     const Uint32 white = 0xFFFFFFFF;
   498     const Uint32 transparent = 0x00000000;
   499 
   500     /* Make sure the width is a multiple of 8 */
   501     w = ((w + 7) & ~7);
   502 
   503     /* Create the surface from a bitmap */
   504     surface = SDL_CreateRGBSurface(0, w, h, 32,
   505                                    0x00FF0000,
   506                                    0x0000FF00,
   507                                    0x000000FF,
   508                                    0xFF000000);
   509     if (!surface) {
   510         return NULL;
   511     }
   512     for (y = 0; y < h; ++y) {
   513         pixel = (Uint32 *) ((Uint8 *) surface->pixels + y * surface->pitch);
   514         for (x = 0; x < w; ++x) {
   515             if ((x % 8) == 0) {
   516                 datab = *data++;
   517                 maskb = *mask++;
   518             }
   519             if (maskb & 0x80) {
   520                 *pixel++ = (datab & 0x80) ? black : white;
   521             } else {
   522                 *pixel++ = (datab & 0x80) ? black : transparent;
   523             }
   524             datab <<= 1;
   525             maskb <<= 1;
   526         }
   527     }
   528 
   529     cursor = SDL_CreateColorCursor(surface, hot_x, hot_y);
   530 
   531     SDL_FreeSurface(surface);
   532 
   533     return cursor;
   534 }
   535 
   536 SDL_Cursor *
   537 SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y)
   538 {
   539     SDL_Mouse *mouse = SDL_GetMouse();
   540     SDL_Surface *temp = NULL;
   541     SDL_Cursor *cursor;
   542 
   543     if (!surface) {
   544         SDL_SetError("Passed NULL cursor surface");
   545         return NULL;
   546     }
   547 
   548     if (!mouse->CreateCursor) {
   549         SDL_SetError("Cursors are not currently supported");
   550         return NULL;
   551     }
   552 
   553     /* Sanity check the hot spot */
   554     if ((hot_x < 0) || (hot_y < 0) ||
   555         (hot_x >= surface->w) || (hot_y >= surface->h)) {
   556         SDL_SetError("Cursor hot spot doesn't lie within cursor");
   557         return NULL;
   558     }
   559 
   560     if (surface->format->format != SDL_PIXELFORMAT_ARGB8888) {
   561         temp = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ARGB8888, 0);
   562         if (!temp) {
   563             return NULL;
   564         }
   565         surface = temp;
   566     }
   567 
   568     cursor = mouse->CreateCursor(surface, hot_x, hot_y);
   569     if (cursor) {
   570         cursor->next = mouse->cursors;
   571         mouse->cursors = cursor;
   572     }
   573 
   574     if (temp) {
   575         SDL_FreeSurface(temp);
   576     }
   577 
   578     return cursor;
   579 }
   580 
   581 SDL_Cursor *
   582 SDL_CreateSystemCursor(SDL_SystemCursor id)
   583 {
   584     SDL_Mouse *mouse = SDL_GetMouse();
   585     SDL_Cursor *cursor;
   586 
   587     if (!mouse->CreateSystemCursor) {
   588         SDL_SetError("CreateSystemCursor is not currently supported");
   589         return NULL;
   590     }
   591 
   592     cursor = mouse->CreateSystemCursor(id);
   593     if (cursor) {
   594         cursor->next = mouse->cursors;
   595         mouse->cursors = cursor;
   596     }
   597 
   598     return cursor;
   599 }
   600 
   601 /* SDL_SetCursor(NULL) can be used to force the cursor redraw,
   602    if this is desired for any reason.  This is used when setting
   603    the video mode and when the SDL window gains the mouse focus.
   604  */
   605 void
   606 SDL_SetCursor(SDL_Cursor * cursor)
   607 {
   608     SDL_Mouse *mouse = SDL_GetMouse();
   609 
   610     /* Set the new cursor */
   611     if (cursor) {
   612         /* Make sure the cursor is still valid for this mouse */
   613         if (cursor != mouse->def_cursor) {
   614             SDL_Cursor *found;
   615             for (found = mouse->cursors; found; found = found->next) {
   616                 if (found == cursor) {
   617                     break;
   618                 }
   619             }
   620             if (!found) {
   621                 SDL_SetError("Cursor not associated with the current mouse");
   622                 return;
   623             }
   624         }
   625         mouse->cur_cursor = cursor;
   626     } else {
   627         if (mouse->focus) {
   628             cursor = mouse->cur_cursor;
   629         } else {
   630             cursor = mouse->def_cursor;
   631         }
   632     }
   633 
   634     if (cursor && mouse->cursor_shown && !mouse->relative_mode) {
   635         if (mouse->ShowCursor) {
   636             mouse->ShowCursor(cursor);
   637         }
   638     } else {
   639         if (mouse->ShowCursor) {
   640             mouse->ShowCursor(NULL);
   641         }
   642     }
   643 }
   644 
   645 SDL_Cursor *
   646 SDL_GetCursor(void)
   647 {
   648     SDL_Mouse *mouse = SDL_GetMouse();
   649 
   650     if (!mouse) {
   651         return NULL;
   652     }
   653     return mouse->cur_cursor;
   654 }
   655 
   656 SDL_Cursor *
   657 SDL_GetDefaultCursor(void)
   658 {
   659     SDL_Mouse *mouse = SDL_GetMouse();
   660 
   661     if (!mouse) {
   662         return NULL;
   663     }
   664     return mouse->def_cursor;
   665 }
   666 
   667 void
   668 SDL_FreeCursor(SDL_Cursor * cursor)
   669 {
   670     SDL_Mouse *mouse = SDL_GetMouse();
   671     SDL_Cursor *curr, *prev;
   672 
   673     if (!cursor) {
   674         return;
   675     }
   676 
   677     if (cursor == mouse->def_cursor) {
   678         return;
   679     }
   680     if (cursor == mouse->cur_cursor) {
   681         SDL_SetCursor(mouse->def_cursor);
   682     }
   683 
   684     for (prev = NULL, curr = mouse->cursors; curr;
   685          prev = curr, curr = curr->next) {
   686         if (curr == cursor) {
   687             if (prev) {
   688                 prev->next = curr->next;
   689             } else {
   690                 mouse->cursors = curr->next;
   691             }
   692 
   693             if (mouse->FreeCursor) {
   694                 mouse->FreeCursor(curr);
   695             }
   696             return;
   697         }
   698     }
   699 }
   700 
   701 int
   702 SDL_ShowCursor(int toggle)
   703 {
   704     SDL_Mouse *mouse = SDL_GetMouse();
   705     SDL_bool shown;
   706 
   707     if (!mouse) {
   708         return 0;
   709     }
   710 
   711     shown = mouse->cursor_shown;
   712     if (toggle >= 0) {
   713         if (toggle) {
   714             mouse->cursor_shown = SDL_TRUE;
   715         } else {
   716             mouse->cursor_shown = SDL_FALSE;
   717         }
   718         if (mouse->cursor_shown != shown) {
   719             SDL_SetCursor(NULL);
   720         }
   721     }
   722     return shown;
   723 }
   724 
   725 /* vi: set ts=4 sw=4 expandtab: */