029a31832f9b9f221656c3f9eedcf47f707f01cb
[platform/upstream/SDL.git] / src / video / cocoa / SDL_cocoamouse.m
1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2018 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_COCOA
24
25 #include "SDL_assert.h"
26 #include "SDL_events.h"
27 #include "SDL_cocoamouse.h"
28 #include "SDL_cocoamousetap.h"
29 #include "SDL_cocoavideo.h"
30
31 #include "../../events/SDL_mouse_c.h"
32
33 /* #define DEBUG_COCOAMOUSE */
34
35 #ifdef DEBUG_COCOAMOUSE
36 #define DLog(fmt, ...) printf("%s: " fmt "\n", __func__, ##__VA_ARGS__)
37 #else
38 #define DLog(...) do { } while (0)
39 #endif
40
41 @implementation NSCursor (InvisibleCursor)
42 + (NSCursor *)invisibleCursor
43 {
44     static NSCursor *invisibleCursor = NULL;
45     if (!invisibleCursor) {
46         /* RAW 16x16 transparent GIF */
47         static unsigned char cursorBytes[] = {
48             0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80,
49             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04,
50             0x01, 0x00, 0x00, 0x01, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x10,
51             0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x8C, 0x8F, 0xA9, 0xCB, 0xED,
52             0x0F, 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B
53         };
54
55         NSData *cursorData = [NSData dataWithBytesNoCopy:&cursorBytes[0]
56                                                   length:sizeof(cursorBytes)
57                                             freeWhenDone:NO];
58         NSImage *cursorImage = [[[NSImage alloc] initWithData:cursorData] autorelease];
59         invisibleCursor = [[NSCursor alloc] initWithImage:cursorImage
60                                                   hotSpot:NSZeroPoint];
61     }
62
63     return invisibleCursor;
64 }
65 @end
66
67
68 static SDL_Cursor *
69 Cocoa_CreateDefaultCursor()
70 { @autoreleasepool
71 {
72     NSCursor *nscursor;
73     SDL_Cursor *cursor = NULL;
74
75     nscursor = [NSCursor arrowCursor];
76
77     if (nscursor) {
78         cursor = SDL_calloc(1, sizeof(*cursor));
79         if (cursor) {
80             cursor->driverdata = nscursor;
81             [nscursor retain];
82         }
83     }
84
85     return cursor;
86 }}
87
88 static SDL_Cursor *
89 Cocoa_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
90 { @autoreleasepool
91 {
92     NSImage *nsimage;
93     NSCursor *nscursor = NULL;
94     SDL_Cursor *cursor = NULL;
95
96     nsimage = Cocoa_CreateImage(surface);
97     if (nsimage) {
98         nscursor = [[NSCursor alloc] initWithImage: nsimage hotSpot: NSMakePoint(hot_x, hot_y)];
99     }
100
101     if (nscursor) {
102         cursor = SDL_calloc(1, sizeof(*cursor));
103         if (cursor) {
104             cursor->driverdata = nscursor;
105         } else {
106             [nscursor release];
107         }
108     }
109
110     return cursor;
111 }}
112
113 static SDL_Cursor *
114 Cocoa_CreateSystemCursor(SDL_SystemCursor id)
115 { @autoreleasepool
116 {
117     NSCursor *nscursor = NULL;
118     SDL_Cursor *cursor = NULL;
119
120     switch(id) {
121     case SDL_SYSTEM_CURSOR_ARROW:
122         nscursor = [NSCursor arrowCursor];
123         break;
124     case SDL_SYSTEM_CURSOR_IBEAM:
125         nscursor = [NSCursor IBeamCursor];
126         break;
127     case SDL_SYSTEM_CURSOR_WAIT:
128         nscursor = [NSCursor arrowCursor];
129         break;
130     case SDL_SYSTEM_CURSOR_CROSSHAIR:
131         nscursor = [NSCursor crosshairCursor];
132         break;
133     case SDL_SYSTEM_CURSOR_WAITARROW:
134         nscursor = [NSCursor arrowCursor];
135         break;
136     case SDL_SYSTEM_CURSOR_SIZENWSE:
137     case SDL_SYSTEM_CURSOR_SIZENESW:
138         nscursor = [NSCursor closedHandCursor];
139         break;
140     case SDL_SYSTEM_CURSOR_SIZEWE:
141         nscursor = [NSCursor resizeLeftRightCursor];
142         break;
143     case SDL_SYSTEM_CURSOR_SIZENS:
144         nscursor = [NSCursor resizeUpDownCursor];
145         break;
146     case SDL_SYSTEM_CURSOR_SIZEALL:
147         nscursor = [NSCursor closedHandCursor];
148         break;
149     case SDL_SYSTEM_CURSOR_NO:
150         nscursor = [NSCursor operationNotAllowedCursor];
151         break;
152     case SDL_SYSTEM_CURSOR_HAND:
153         nscursor = [NSCursor pointingHandCursor];
154         break;
155     default:
156         SDL_assert(!"Unknown system cursor");
157         return NULL;
158     }
159
160     if (nscursor) {
161         cursor = SDL_calloc(1, sizeof(*cursor));
162         if (cursor) {
163             /* We'll free it later, so retain it here */
164             [nscursor retain];
165             cursor->driverdata = nscursor;
166         }
167     }
168
169     return cursor;
170 }}
171
172 static void
173 Cocoa_FreeCursor(SDL_Cursor * cursor)
174 { @autoreleasepool
175 {
176     NSCursor *nscursor = (NSCursor *)cursor->driverdata;
177
178     [nscursor release];
179     SDL_free(cursor);
180 }}
181
182 static int
183 Cocoa_ShowCursor(SDL_Cursor * cursor)
184 { @autoreleasepool
185 {
186     SDL_VideoDevice *device = SDL_GetVideoDevice();
187     SDL_Window *window = (device ? device->windows : NULL);
188     for (; window != NULL; window = window->next) {
189         SDL_WindowData *driverdata = (SDL_WindowData *)window->driverdata;
190         if (driverdata) {
191             [driverdata->nswindow performSelectorOnMainThread:@selector(invalidateCursorRectsForView:)
192                                                    withObject:[driverdata->nswindow contentView]
193                                                 waitUntilDone:NO];
194         }
195     }
196     return 0;
197 }}
198
199 static SDL_Window *
200 SDL_FindWindowAtPoint(const int x, const int y)
201 {
202     const SDL_Point pt = { x, y };
203     SDL_Window *i;
204     for (i = SDL_GetVideoDevice()->windows; i; i = i->next) {
205         const SDL_Rect r = { i->x, i->y, i->w, i->h };
206         if (SDL_PointInRect(&pt, &r)) {
207             return i;
208         }
209     }
210
211     return NULL;
212 }
213
214 static int
215 Cocoa_WarpMouseGlobal(int x, int y)
216 {
217     SDL_Mouse *mouse = SDL_GetMouse();
218     if (mouse->focus) {
219         SDL_WindowData *data = (SDL_WindowData *) mouse->focus->driverdata;
220         if ([data->listener isMoving]) {
221             DLog("Postponing warp, window being moved.");
222             [data->listener setPendingMoveX:x Y:y];
223             return 0;
224         }
225     }
226     const CGPoint point = CGPointMake((float)x, (float)y);
227
228     Cocoa_HandleMouseWarp(point.x, point.y);
229
230     CGWarpMouseCursorPosition(point);
231
232     /* CGWarpMouse causes a short delay by default, which is preventable by
233      * Calling this directly after. CGSetLocalEventsSuppressionInterval can also
234      * prevent it, but it's deprecated as of OS X 10.6.
235      */
236     if (!mouse->relative_mode) {
237         CGAssociateMouseAndMouseCursorPosition(YES);
238     }
239
240     /* CGWarpMouseCursorPosition doesn't generate a window event, unlike our
241      * other implementations' APIs. Send what's appropriate.
242      */
243     if (!mouse->relative_mode) {
244         SDL_Window *win = SDL_FindWindowAtPoint(x, y);
245         SDL_SetMouseFocus(win);
246         if (win) {
247             SDL_assert(win == mouse->focus);
248             SDL_SendMouseMotion(win, mouse->mouseID, 0, x - win->x, y - win->y);
249         }
250     }
251
252     return 0;
253 }
254
255 static void
256 Cocoa_WarpMouse(SDL_Window * window, int x, int y)
257 {
258     Cocoa_WarpMouseGlobal(x + window->x, y + window->y);
259 }
260
261 static int
262 Cocoa_SetRelativeMouseMode(SDL_bool enabled)
263 {
264     /* We will re-apply the relative mode when the window gets focus, if it
265      * doesn't have focus right now.
266      */
267     SDL_Window *window = SDL_GetMouseFocus();
268     if (!window) {
269       return 0;
270     }
271
272     /* We will re-apply the relative mode when the window finishes being moved,
273      * if it is being moved right now.
274      */
275     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
276     if ([data->listener isMoving]) {
277         return 0;
278     }
279
280     CGError result;
281     if (enabled) {
282         DLog("Turning on.");
283         result = CGAssociateMouseAndMouseCursorPosition(NO);
284     } else {
285         DLog("Turning off.");
286         result = CGAssociateMouseAndMouseCursorPosition(YES);
287     }
288     if (result != kCGErrorSuccess) {
289         return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed");
290     }
291
292     /* The hide/unhide calls are redundant most of the time, but they fix
293      * https://bugzilla.libsdl.org/show_bug.cgi?id=2550
294      */
295     if (enabled) {
296         [NSCursor hide];
297     } else {
298         [NSCursor unhide];
299     }
300     return 0;
301 }
302
303 static int
304 Cocoa_CaptureMouse(SDL_Window *window)
305 {
306     /* our Cocoa event code already tracks the mouse outside the window,
307         so all we have to do here is say "okay" and do what we always do. */
308     return 0;
309 }
310
311 static Uint32
312 Cocoa_GetGlobalMouseState(int *x, int *y)
313 {
314     const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons];
315     const NSPoint cocoaLocation = [NSEvent mouseLocation];
316     Uint32 retval = 0;
317
318     for (NSScreen *screen in [NSScreen screens]) {
319         NSRect frame = [screen frame];
320         if (NSMouseInRect(cocoaLocation, frame, NO)) {
321             *x = (int) cocoaLocation.x;
322             *y = (int) ((frame.origin.y + frame.size.height) - cocoaLocation.y);
323             break;
324         }
325     }
326
327     retval |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0;
328     retval |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0;
329     retval |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0;
330     retval |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0;
331     retval |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0;
332
333     return retval;
334 }
335
336 void
337 Cocoa_InitMouse(_THIS)
338 {
339     SDL_Mouse *mouse = SDL_GetMouse();
340
341     mouse->driverdata = SDL_calloc(1, sizeof(SDL_MouseData));
342
343     mouse->CreateCursor = Cocoa_CreateCursor;
344     mouse->CreateSystemCursor = Cocoa_CreateSystemCursor;
345     mouse->ShowCursor = Cocoa_ShowCursor;
346     mouse->FreeCursor = Cocoa_FreeCursor;
347     mouse->WarpMouse = Cocoa_WarpMouse;
348     mouse->WarpMouseGlobal = Cocoa_WarpMouseGlobal;
349     mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode;
350     mouse->CaptureMouse = Cocoa_CaptureMouse;
351     mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState;
352
353     SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor());
354
355     Cocoa_InitMouseEventTap(mouse->driverdata);
356
357     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
358     const NSPoint location =  [NSEvent mouseLocation];
359     driverdata->lastMoveX = location.x;
360     driverdata->lastMoveY = location.y;
361 }
362
363 void
364 Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
365 {
366     switch ([event type]) {
367         case NSEventTypeMouseMoved:
368         case NSEventTypeLeftMouseDragged:
369         case NSEventTypeRightMouseDragged:
370         case NSEventTypeOtherMouseDragged:
371             break;
372
373         default:
374             /* Ignore any other events. */
375             return;
376     }
377
378     SDL_Mouse *mouse = SDL_GetMouse();
379     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
380     if (!driverdata) {
381         return;  /* can happen when returning from fullscreen Space on shutdown */
382     }
383
384     const SDL_bool seenWarp = driverdata->seenWarp;
385     driverdata->seenWarp = NO;
386
387     const NSPoint location =  [NSEvent mouseLocation];
388     const CGFloat lastMoveX = driverdata->lastMoveX;
389     const CGFloat lastMoveY = driverdata->lastMoveY;
390     driverdata->lastMoveX = location.x;
391     driverdata->lastMoveY = location.y;
392     DLog("Last seen mouse: (%g, %g)", location.x, location.y);
393
394     /* Non-relative movement is handled in -[Cocoa_WindowListener mouseMoved:] */
395     if (!mouse->relative_mode) {
396         return;
397     }
398
399     /* Ignore events that aren't inside the client area (i.e. title bar.) */
400     if ([event window]) {
401         NSRect windowRect = [[[event window] contentView] frame];
402         if (!NSMouseInRect([event locationInWindow], windowRect, NO)) {
403             return;
404         }
405     }
406
407     float deltaX = [event deltaX];
408     float deltaY = [event deltaY];
409
410     if (seenWarp) {
411         deltaX += (lastMoveX - driverdata->lastWarpX);
412         deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - driverdata->lastWarpY);
413
414         DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY);
415     }
416
417     SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 1, (int)deltaX, (int)deltaY);
418 }
419
420 void
421 Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event)
422 {
423     SDL_Mouse *mouse = SDL_GetMouse();
424
425     CGFloat x = -[event deltaX];
426     CGFloat y = [event deltaY];
427     SDL_MouseWheelDirection direction = SDL_MOUSEWHEEL_NORMAL;
428
429     if ([event respondsToSelector:@selector(isDirectionInvertedFromDevice)]) {
430         if ([event isDirectionInvertedFromDevice] == YES) {
431             direction = SDL_MOUSEWHEEL_FLIPPED;
432         }
433     }
434
435     SDL_SendMouseWheel(window, mouse->mouseID, x, y, direction);
436 }
437
438 void
439 Cocoa_HandleMouseWarp(CGFloat x, CGFloat y)
440 {
441     /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp,
442      * since it gets included in the next movement event.
443      */
444     SDL_MouseData *driverdata = (SDL_MouseData*)SDL_GetMouse()->driverdata;
445     driverdata->lastWarpX = x;
446     driverdata->lastWarpY = y;
447     driverdata->seenWarp = SDL_TRUE;
448
449     DLog("(%g, %g)", x, y);
450 }
451
452 void
453 Cocoa_QuitMouse(_THIS)
454 {
455     SDL_Mouse *mouse = SDL_GetMouse();
456     if (mouse) {
457         if (mouse->driverdata) {
458             Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata));
459         }
460
461         SDL_free(mouse->driverdata);
462     }
463 }
464
465 #endif /* SDL_VIDEO_DRIVER_COCOA */
466
467 /* vi: set ts=4 sw=4 expandtab: */