2 Simple DirectMedia Layer
3 Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
21 #include "../../SDL_internal.h"
23 #if SDL_VIDEO_DRIVER_COCOA
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"
31 #include "../../events/SDL_mouse_c.h"
33 /* #define DEBUG_COCOAMOUSE */
35 #ifdef DEBUG_COCOAMOUSE
36 #define DLog(fmt, ...) printf("%s: " fmt "\n", __func__, ##__VA_ARGS__)
38 #define DLog(...) do { } while (0)
41 @implementation NSCursor (InvisibleCursor)
42 + (NSCursor *)invisibleCursor
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
55 NSData *cursorData = [NSData dataWithBytesNoCopy:&cursorBytes[0]
56 length:sizeof(cursorBytes)
58 NSImage *cursorImage = [[[NSImage alloc] initWithData:cursorData] autorelease];
59 invisibleCursor = [[NSCursor alloc] initWithImage:cursorImage
63 return invisibleCursor;
69 Cocoa_CreateDefaultCursor()
73 SDL_Cursor *cursor = NULL;
75 nscursor = [NSCursor arrowCursor];
78 cursor = SDL_calloc(1, sizeof(*cursor));
80 cursor->driverdata = nscursor;
89 Cocoa_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
93 NSCursor *nscursor = NULL;
94 SDL_Cursor *cursor = NULL;
96 nsimage = Cocoa_CreateImage(surface);
98 nscursor = [[NSCursor alloc] initWithImage: nsimage hotSpot: NSMakePoint(hot_x, hot_y)];
102 cursor = SDL_calloc(1, sizeof(*cursor));
104 cursor->driverdata = nscursor;
114 Cocoa_CreateSystemCursor(SDL_SystemCursor id)
117 NSCursor *nscursor = NULL;
118 SDL_Cursor *cursor = NULL;
121 case SDL_SYSTEM_CURSOR_ARROW:
122 nscursor = [NSCursor arrowCursor];
124 case SDL_SYSTEM_CURSOR_IBEAM:
125 nscursor = [NSCursor IBeamCursor];
127 case SDL_SYSTEM_CURSOR_WAIT:
128 nscursor = [NSCursor arrowCursor];
130 case SDL_SYSTEM_CURSOR_CROSSHAIR:
131 nscursor = [NSCursor crosshairCursor];
133 case SDL_SYSTEM_CURSOR_WAITARROW:
134 nscursor = [NSCursor arrowCursor];
136 case SDL_SYSTEM_CURSOR_SIZENWSE:
137 case SDL_SYSTEM_CURSOR_SIZENESW:
138 nscursor = [NSCursor closedHandCursor];
140 case SDL_SYSTEM_CURSOR_SIZEWE:
141 nscursor = [NSCursor resizeLeftRightCursor];
143 case SDL_SYSTEM_CURSOR_SIZENS:
144 nscursor = [NSCursor resizeUpDownCursor];
146 case SDL_SYSTEM_CURSOR_SIZEALL:
147 nscursor = [NSCursor closedHandCursor];
149 case SDL_SYSTEM_CURSOR_NO:
150 nscursor = [NSCursor operationNotAllowedCursor];
152 case SDL_SYSTEM_CURSOR_HAND:
153 nscursor = [NSCursor pointingHandCursor];
156 SDL_assert(!"Unknown system cursor");
161 cursor = SDL_calloc(1, sizeof(*cursor));
163 /* We'll free it later, so retain it here */
165 cursor->driverdata = nscursor;
173 Cocoa_FreeCursor(SDL_Cursor * cursor)
176 NSCursor *nscursor = (NSCursor *)cursor->driverdata;
183 Cocoa_ShowCursor(SDL_Cursor * cursor)
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;
191 [driverdata->nswindow performSelectorOnMainThread:@selector(invalidateCursorRectsForView:)
192 withObject:[driverdata->nswindow contentView]
200 SDL_FindWindowAtPoint(const int x, const int y)
202 const SDL_Point pt = { x, y };
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)) {
215 Cocoa_WarpMouseGlobal(int x, int y)
217 SDL_Mouse *mouse = SDL_GetMouse();
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];
226 const CGPoint point = CGPointMake((float)x, (float)y);
228 Cocoa_HandleMouseWarp(point.x, point.y);
230 CGWarpMouseCursorPosition(point);
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.
236 if (!mouse->relative_mode) {
237 CGAssociateMouseAndMouseCursorPosition(YES);
240 /* CGWarpMouseCursorPosition doesn't generate a window event, unlike our
241 * other implementations' APIs. Send what's appropriate.
243 if (!mouse->relative_mode) {
244 SDL_Window *win = SDL_FindWindowAtPoint(x, y);
245 SDL_SetMouseFocus(win);
247 SDL_assert(win == mouse->focus);
248 SDL_SendMouseMotion(win, mouse->mouseID, 0, x - win->x, y - win->y);
256 Cocoa_WarpMouse(SDL_Window * window, int x, int y)
258 Cocoa_WarpMouseGlobal(x + window->x, y + window->y);
262 Cocoa_SetRelativeMouseMode(SDL_bool enabled)
264 /* We will re-apply the relative mode when the window gets focus, if it
265 * doesn't have focus right now.
267 SDL_Window *window = SDL_GetMouseFocus();
272 /* We will re-apply the relative mode when the window finishes being moved,
273 * if it is being moved right now.
275 SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
276 if ([data->listener isMoving]) {
283 result = CGAssociateMouseAndMouseCursorPosition(NO);
285 DLog("Turning off.");
286 result = CGAssociateMouseAndMouseCursorPosition(YES);
288 if (result != kCGErrorSuccess) {
289 return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed");
292 /* The hide/unhide calls are redundant most of the time, but they fix
293 * https://bugzilla.libsdl.org/show_bug.cgi?id=2550
304 Cocoa_CaptureMouse(SDL_Window *window)
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. */
312 Cocoa_GetGlobalMouseState(int *x, int *y)
314 const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons];
315 const NSPoint cocoaLocation = [NSEvent mouseLocation];
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);
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;
337 Cocoa_InitMouse(_THIS)
339 SDL_Mouse *mouse = SDL_GetMouse();
341 mouse->driverdata = SDL_calloc(1, sizeof(SDL_MouseData));
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;
353 SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor());
355 Cocoa_InitMouseEventTap(mouse->driverdata);
357 SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
358 const NSPoint location = [NSEvent mouseLocation];
359 driverdata->lastMoveX = location.x;
360 driverdata->lastMoveY = location.y;
364 Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
366 switch ([event type]) {
367 case NSEventTypeMouseMoved:
368 case NSEventTypeLeftMouseDragged:
369 case NSEventTypeRightMouseDragged:
370 case NSEventTypeOtherMouseDragged:
374 /* Ignore any other events. */
378 SDL_Mouse *mouse = SDL_GetMouse();
379 SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
381 return; /* can happen when returning from fullscreen Space on shutdown */
384 const SDL_bool seenWarp = driverdata->seenWarp;
385 driverdata->seenWarp = NO;
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);
394 /* Non-relative movement is handled in -[Cocoa_WindowListener mouseMoved:] */
395 if (!mouse->relative_mode) {
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)) {
407 float deltaX = [event deltaX];
408 float deltaY = [event deltaY];
411 deltaX += (lastMoveX - driverdata->lastWarpX);
412 deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - driverdata->lastWarpY);
414 DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY);
417 SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 1, (int)deltaX, (int)deltaY);
421 Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event)
423 SDL_Mouse *mouse = SDL_GetMouse();
425 CGFloat x = -[event deltaX];
426 CGFloat y = [event deltaY];
427 SDL_MouseWheelDirection direction = SDL_MOUSEWHEEL_NORMAL;
429 if ([event respondsToSelector:@selector(isDirectionInvertedFromDevice)]) {
430 if ([event isDirectionInvertedFromDevice] == YES) {
431 direction = SDL_MOUSEWHEEL_FLIPPED;
435 SDL_SendMouseWheel(window, mouse->mouseID, x, y, direction);
439 Cocoa_HandleMouseWarp(CGFloat x, CGFloat y)
441 /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp,
442 * since it gets included in the next movement event.
444 SDL_MouseData *driverdata = (SDL_MouseData*)SDL_GetMouse()->driverdata;
445 driverdata->lastWarpX = x;
446 driverdata->lastWarpY = y;
447 driverdata->seenWarp = SDL_TRUE;
449 DLog("(%g, %g)", x, y);
453 Cocoa_QuitMouse(_THIS)
455 SDL_Mouse *mouse = SDL_GetMouse();
457 if (mouse->driverdata) {
458 Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata));
461 SDL_free(mouse->driverdata);
465 #endif /* SDL_VIDEO_DRIVER_COCOA */
467 /* vi: set ts=4 sw=4 expandtab: */