src/events/SDL_touch.c
author Ryan C. Gordon <icculus@icculus.org>
Mon, 08 Jul 2019 13:41:01 -0400
changeset 12929 d6c5eb7a0afb
parent 12867 a38317a29191
child 12934 d03900e991a2
permissions -rw-r--r--
cocoa: Another attempt at mouse vs touch support.

This time, we make anything we think is a MacBook trackpad report its touches
as SDL_MOUSE_TOUCHID, even though they're not _actually_ synthesized events,
and let all mouse input--even if the OS synthesized it from a multitouch
trackpad on our behalf--look like physical input. This is backwards from
reality, but produces the results most apps will expect.

Note that if you have a real touch device that doesn't appear to be the
trackpad, it'll produce real touch events with unique device ids, so it's
not a total loss here, but also note that the way we decide if it was the
trackpad is an imperfect heuristic; it happens to work out right now, but
it's not impossible that a real touchscreen could come to the Mac at some
point and (incorrectly?) call it a "mouse" input, etc.

But for now, good enough.

Fixes Bugzilla #4690.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2019 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 /* General touch handling code for SDL */
    24 
    25 #include "SDL_assert.h"
    26 #include "SDL_events.h"
    27 #include "SDL_events_c.h"
    28 #include "../video/SDL_sysvideo.h"
    29 
    30 
    31 static int SDL_num_touch = 0;
    32 static SDL_Touch **SDL_touchDevices = NULL;
    33 
    34 /* for mapping touch events to mice */
    35 
    36 #define SYNTHESIZE_TOUCH_TO_MOUSE 1
    37 
    38 #if SYNTHESIZE_TOUCH_TO_MOUSE
    39 static SDL_bool finger_touching = SDL_FALSE;
    40 static SDL_FingerID track_fingerid;
    41 static SDL_TouchID  track_touchid;
    42 #endif
    43 
    44 /* Public functions */
    45 int
    46 SDL_TouchInit(void)
    47 {
    48     return (0);
    49 }
    50 
    51 int
    52 SDL_GetNumTouchDevices(void)
    53 {
    54     return SDL_num_touch;
    55 }
    56 
    57 SDL_TouchID
    58 SDL_GetTouchDevice(int index)
    59 {
    60     if (index < 0 || index >= SDL_num_touch) {
    61         SDL_SetError("Unknown touch device index %d", index);
    62         return 0;
    63     }
    64     return SDL_touchDevices[index]->id;
    65 }
    66 
    67 static int
    68 SDL_GetTouchIndex(SDL_TouchID id)
    69 {
    70     int index;
    71     SDL_Touch *touch;
    72 
    73     for (index = 0; index < SDL_num_touch; ++index) {
    74         touch = SDL_touchDevices[index];
    75         if (touch->id == id) {
    76             return index;
    77         }
    78     }
    79     return -1;
    80 }
    81 
    82 SDL_Touch *
    83 SDL_GetTouch(SDL_TouchID id)
    84 {
    85     int index = SDL_GetTouchIndex(id);
    86     if (index < 0 || index >= SDL_num_touch) {
    87         if (SDL_GetVideoDevice()->ResetTouch != NULL) {
    88             SDL_SetError("Unknown touch id %d, resetting", (int) id);
    89             (SDL_GetVideoDevice()->ResetTouch)(SDL_GetVideoDevice());
    90         } else {
    91             SDL_SetError("Unknown touch device id %d, cannot reset", (int) id);
    92         }
    93         return NULL;
    94     }
    95     return SDL_touchDevices[index];
    96 }
    97 
    98 SDL_TouchDeviceType
    99 SDL_GetTouchDeviceType(SDL_TouchID id)
   100 {
   101     SDL_Touch *touch = SDL_GetTouch(id);
   102     if (touch) {
   103         return touch->type;
   104     }
   105     return SDL_TOUCH_DEVICE_INVALID;
   106 }
   107 
   108 static int
   109 SDL_GetFingerIndex(const SDL_Touch * touch, SDL_FingerID fingerid)
   110 {
   111     int index;
   112     for (index = 0; index < touch->num_fingers; ++index) {
   113         if (touch->fingers[index]->id == fingerid) {
   114             return index;
   115         }
   116     }
   117     return -1;
   118 }
   119 
   120 static SDL_Finger *
   121 SDL_GetFinger(const SDL_Touch * touch, SDL_FingerID id)
   122 {
   123     int index = SDL_GetFingerIndex(touch, id);
   124     if (index < 0 || index >= touch->num_fingers) {
   125         return NULL;
   126     }
   127     return touch->fingers[index];
   128 }
   129 
   130 int
   131 SDL_GetNumTouchFingers(SDL_TouchID touchID)
   132 {
   133     SDL_Touch *touch = SDL_GetTouch(touchID);
   134     if (touch) {
   135         return touch->num_fingers;
   136     }
   137     return 0;
   138 }
   139 
   140 SDL_Finger *
   141 SDL_GetTouchFinger(SDL_TouchID touchID, int index)
   142 {
   143     SDL_Touch *touch = SDL_GetTouch(touchID);
   144     if (!touch) {
   145         return NULL;
   146     }
   147     if (index < 0 || index >= touch->num_fingers) {
   148         SDL_SetError("Unknown touch finger");
   149         return NULL;
   150     }
   151     return touch->fingers[index];
   152 }
   153 
   154 int
   155 SDL_AddTouch(SDL_TouchID touchID, SDL_TouchDeviceType type, const char *name)
   156 {
   157     SDL_Touch **touchDevices;
   158     int index;
   159 
   160     index = SDL_GetTouchIndex(touchID);
   161     if (index >= 0) {
   162         return index;
   163     }
   164 
   165     /* Add the touch to the list of touch */
   166     touchDevices = (SDL_Touch **) SDL_realloc(SDL_touchDevices,
   167                                       (SDL_num_touch + 1) * sizeof(*touchDevices));
   168     if (!touchDevices) {
   169         return SDL_OutOfMemory();
   170     }
   171 
   172     SDL_touchDevices = touchDevices;
   173     index = SDL_num_touch;
   174 
   175     SDL_touchDevices[index] = (SDL_Touch *) SDL_malloc(sizeof(*SDL_touchDevices[index]));
   176     if (!SDL_touchDevices[index]) {
   177         return SDL_OutOfMemory();
   178     }
   179 
   180     /* Added touch to list */
   181     ++SDL_num_touch;
   182 
   183     /* we're setting the touch properties */
   184     SDL_touchDevices[index]->id = touchID;
   185     SDL_touchDevices[index]->type = type;
   186     SDL_touchDevices[index]->num_fingers = 0;
   187     SDL_touchDevices[index]->max_fingers = 0;
   188     SDL_touchDevices[index]->fingers = NULL;
   189 
   190     /* Record this touch device for gestures */
   191     /* We could do this on the fly in the gesture code if we wanted */
   192     SDL_GestureAddTouch(touchID);
   193 
   194     return index;
   195 }
   196 
   197 static int
   198 SDL_AddFinger(SDL_Touch *touch, SDL_FingerID fingerid, float x, float y, float pressure)
   199 {
   200     SDL_Finger *finger;
   201 
   202     if (touch->num_fingers == touch->max_fingers) {
   203         SDL_Finger **new_fingers;
   204         new_fingers = (SDL_Finger **)SDL_realloc(touch->fingers, (touch->max_fingers+1)*sizeof(*touch->fingers));
   205         if (!new_fingers) {
   206             return SDL_OutOfMemory();
   207         }
   208         touch->fingers = new_fingers;
   209         touch->fingers[touch->max_fingers] = (SDL_Finger *)SDL_malloc(sizeof(*finger));
   210         if (!touch->fingers[touch->max_fingers]) {
   211             return SDL_OutOfMemory();
   212         }
   213         touch->max_fingers++;
   214     }
   215 
   216     finger = touch->fingers[touch->num_fingers++];
   217     finger->id = fingerid;
   218     finger->x = x;
   219     finger->y = y;
   220     finger->pressure = pressure;
   221     return 0;
   222 }
   223 
   224 static int
   225 SDL_DelFinger(SDL_Touch* touch, SDL_FingerID fingerid)
   226 {
   227     SDL_Finger *temp;
   228 
   229     int index = SDL_GetFingerIndex(touch, fingerid);
   230     if (index < 0) {
   231         return -1;
   232     }
   233 
   234     touch->num_fingers--;
   235     temp = touch->fingers[index];
   236     touch->fingers[index] = touch->fingers[touch->num_fingers];
   237     touch->fingers[touch->num_fingers] = temp;
   238     return 0;
   239 }
   240 
   241 int
   242 SDL_SendTouch(SDL_TouchID id, SDL_FingerID fingerid,
   243               SDL_bool down, float x, float y, float pressure)
   244 {
   245     int posted;
   246     SDL_Finger *finger;
   247 
   248     SDL_Touch* touch = SDL_GetTouch(id);
   249     if (!touch) {
   250         return -1;
   251     }
   252 
   253 #if SYNTHESIZE_TOUCH_TO_MOUSE
   254     /* SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events */
   255     {
   256         SDL_Mouse *mouse = SDL_GetMouse();
   257         if (mouse->touch_mouse_events) {
   258             /* FIXME: maybe we should only restrict to a few SDL_TouchDeviceType */
   259             if (id != SDL_MOUSE_TOUCHID) {
   260                 SDL_Window *window = SDL_GetMouseFocus();
   261                 if (window == NULL) {
   262                     /* Mouse focus may have been lost by e.g. the window resizing
   263                      * due to device orientation change while the mouse state is
   264                      * pressed (because its position is now out of the window).
   265                      * SendMouse* will update mouse focus again after that, but
   266                      * if those are never called then SDL might think the
   267                      * 'mouse' has no focus at all. */
   268                     window = SDL_GetKeyboardFocus();
   269                 }
   270                 if (window) {
   271                     if (down) {
   272                         if (finger_touching == SDL_FALSE) {
   273                             int pos_x = (int)(x * (float)window->w);
   274                             int pos_y = (int)(y * (float)window->h);
   275                             if (pos_x < 0) pos_x = 0;
   276                             if (pos_x > window->w - 1) pos_x = window->w - 1;
   277                             if (pos_y < 0) pos_y = 0;
   278                             if (pos_y > window->h - 1) pos_y = window->h - 1;
   279                             SDL_SendMouseMotion(window, SDL_TOUCH_MOUSEID, 0, pos_x, pos_y);
   280                             SDL_SendMouseButton(window, SDL_TOUCH_MOUSEID, SDL_PRESSED, SDL_BUTTON_LEFT);
   281                         }
   282                     } else {
   283                         if (finger_touching == SDL_TRUE && track_touchid == id && track_fingerid == fingerid) {
   284                             SDL_SendMouseButton(window, SDL_TOUCH_MOUSEID, SDL_RELEASED, SDL_BUTTON_LEFT);
   285                         }
   286                     }
   287                 }
   288                 if (down) {
   289                     if (finger_touching == SDL_FALSE) {
   290                         finger_touching = SDL_TRUE;
   291                         track_touchid = id;
   292                         track_fingerid = fingerid;
   293                     }
   294                 } else {
   295                     if (finger_touching == SDL_TRUE && track_touchid == id && track_fingerid == fingerid) {
   296                         finger_touching = SDL_FALSE;
   297                     }
   298                 }
   299             }
   300         }
   301     }
   302 #endif
   303 
   304     finger = SDL_GetFinger(touch, fingerid);
   305     if (down) {
   306         if (finger) {
   307             /* This finger is already down */
   308             return 0;
   309         }
   310 
   311         if (SDL_AddFinger(touch, fingerid, x, y, pressure) < 0) {
   312             return 0;
   313         }
   314 
   315         posted = 0;
   316         if (SDL_GetEventState(SDL_FINGERDOWN) == SDL_ENABLE) {
   317             SDL_Event event;
   318             event.tfinger.type = SDL_FINGERDOWN;
   319             event.tfinger.touchId = id;
   320             event.tfinger.fingerId = fingerid;
   321             event.tfinger.x = x;
   322             event.tfinger.y = y;
   323             event.tfinger.dx = 0;
   324             event.tfinger.dy = 0;
   325             event.tfinger.pressure = pressure;
   326             posted = (SDL_PushEvent(&event) > 0);
   327         }
   328     } else {
   329         if (!finger) {
   330             /* This finger is already up */
   331             return 0;
   332         }
   333 
   334         posted = 0;
   335         if (SDL_GetEventState(SDL_FINGERUP) == SDL_ENABLE) {
   336             SDL_Event event;
   337             event.tfinger.type = SDL_FINGERUP;
   338             event.tfinger.touchId =  id;
   339             event.tfinger.fingerId = fingerid;
   340             /* I don't trust the coordinates passed on fingerUp */
   341             event.tfinger.x = finger->x;
   342             event.tfinger.y = finger->y;
   343             event.tfinger.dx = 0;
   344             event.tfinger.dy = 0;
   345             event.tfinger.pressure = pressure;
   346             posted = (SDL_PushEvent(&event) > 0);
   347         }
   348 
   349         SDL_DelFinger(touch, fingerid);
   350     }
   351     return posted;
   352 }
   353 
   354 int
   355 SDL_SendTouchMotion(SDL_TouchID id, SDL_FingerID fingerid,
   356                     float x, float y, float pressure)
   357 {
   358     SDL_Touch *touch;
   359     SDL_Finger *finger;
   360     int posted;
   361     float xrel, yrel, prel;
   362 
   363     touch = SDL_GetTouch(id);
   364     if (!touch) {
   365         return -1;
   366     }
   367 
   368 #if SYNTHESIZE_TOUCH_TO_MOUSE
   369     /* SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events */
   370     {
   371         SDL_Mouse *mouse = SDL_GetMouse();
   372         if (mouse->touch_mouse_events) {
   373             if (id != SDL_MOUSE_TOUCHID) {
   374                 SDL_Window *window = SDL_GetMouseFocus();
   375                 if (window) {
   376                     if (finger_touching == SDL_TRUE && track_touchid == id && track_fingerid == fingerid) {
   377                         int pos_x = (int)(x * (float)window->w);
   378                         int pos_y = (int)(y * (float)window->h);
   379                         if (pos_x < 0) pos_x = 0;
   380                         if (pos_x > window->w - 1) pos_x = window->w - 1;
   381                         if (pos_y < 0) pos_y = 0;
   382                         if (pos_y > window->h - 1) pos_y = window->h - 1;
   383                         SDL_SendMouseMotion(window, SDL_TOUCH_MOUSEID, 0, pos_x, pos_y);
   384                     }
   385                 }
   386             }
   387         }
   388     }
   389 #endif
   390 
   391     finger = SDL_GetFinger(touch,fingerid);
   392     if (!finger) {
   393         return SDL_SendTouch(id, fingerid, SDL_TRUE, x, y, pressure);
   394     }
   395 
   396     xrel = x - finger->x;
   397     yrel = y - finger->y;
   398     prel = pressure - finger->pressure;
   399 
   400     /* Drop events that don't change state */
   401     if (xrel == 0.0f && yrel == 0.0f && prel == 0.0f) {
   402 #if 0
   403         printf("Touch event didn't change state - dropped!\n");
   404 #endif
   405         return 0;
   406     }
   407 
   408     /* Update internal touch coordinates */
   409     finger->x = x;
   410     finger->y = y;
   411     finger->pressure = pressure;
   412 
   413     /* Post the event, if desired */
   414     posted = 0;
   415     if (SDL_GetEventState(SDL_FINGERMOTION) == SDL_ENABLE) {
   416         SDL_Event event;
   417         event.tfinger.type = SDL_FINGERMOTION;
   418         event.tfinger.touchId = id;
   419         event.tfinger.fingerId = fingerid;
   420         event.tfinger.x = x;
   421         event.tfinger.y = y;
   422         event.tfinger.dx = xrel;
   423         event.tfinger.dy = yrel;
   424         event.tfinger.pressure = pressure;
   425         posted = (SDL_PushEvent(&event) > 0);
   426     }
   427     return posted;
   428 }
   429 
   430 void
   431 SDL_DelTouch(SDL_TouchID id)
   432 {
   433     int i;
   434     int index = SDL_GetTouchIndex(id);
   435     SDL_Touch *touch = SDL_GetTouch(id);
   436 
   437     if (!touch) {
   438         return;
   439     }
   440 
   441     for (i = 0; i < touch->max_fingers; ++i) {
   442         SDL_free(touch->fingers[i]);
   443     }
   444     SDL_free(touch->fingers);
   445     SDL_free(touch);
   446 
   447     SDL_num_touch--;
   448     SDL_touchDevices[index] = SDL_touchDevices[SDL_num_touch];
   449 
   450     /* Delete this touch device for gestures */
   451     SDL_GestureDelTouch(id);
   452 }
   453 
   454 void
   455 SDL_TouchQuit(void)
   456 {
   457     int i;
   458 
   459     for (i = SDL_num_touch; i--; ) {
   460         SDL_DelTouch(SDL_touchDevices[i]->id);
   461     }
   462     SDL_assert(SDL_num_touch == 0);
   463 
   464     SDL_free(SDL_touchDevices);
   465     SDL_touchDevices = NULL;
   466     SDL_GestureQuit();
   467 }
   468 
   469 /* vi: set ts=4 sw=4 expandtab: */