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/* General mouse handling code for SDL */
24
25#include "SDL_hints.h"
26#include "SDL_timer.h"
27#include "SDL_events.h"
28#include "SDL_events_c.h"
29#include "../SDL_hints_c.h"
30#include "../video/SDL_sysvideo.h"
31#ifdef __WIN32__
32#include "../core/windows/SDL_windows.h" // For GetDoubleClickTime()
33#endif
34#if defined(__OS2__)
35#define INCL_WIN
36#include <os2.h>
37#endif
38
39/* #define DEBUG_MOUSE */
40
41/* The mouse state */
42static SDL_Mouse SDL_mouse;
43
44/* for mapping mouse events to touch */
45static SDL_bool track_mouse_down = SDL_FALSE;
46
47static int
48SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int x, int y);
49
50static void SDLCALL
51SDL_MouseDoubleClickTimeChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
52{
53 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
54
55 if (hint && *hint) {
56 mouse->double_click_time = SDL_atoi(hint);
57 } else {
58#ifdef __WIN32__
59 mouse->double_click_time = GetDoubleClickTime();
60#elif defined(__OS2__)
61 mouse->double_click_time = WinQuerySysValue(HWND_DESKTOP, SV_DBLCLKTIME);
62#else
63 mouse->double_click_time = 500;
64#endif
65 }
66}
67
68static void SDLCALL
69SDL_MouseDoubleClickRadiusChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
70{
71 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
72
73 if (hint && *hint) {
74 mouse->double_click_radius = SDL_atoi(hint);
75 } else {
76 mouse->double_click_radius = 32; /* 32 pixels seems about right for touch interfaces */
77 }
78}
79
80static void SDLCALL
81SDL_MouseNormalSpeedScaleChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
82{
83 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
84
85 if (hint && *hint) {
86 mouse->normal_speed_scale = (float)SDL_atof(hint);
87 } else {
88 mouse->normal_speed_scale = 1.0f;
89 }
90}
91
92static void SDLCALL
93SDL_MouseRelativeSpeedScaleChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
94{
95 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
96
97 if (hint && *hint) {
98 mouse->relative_speed_scale = (float)SDL_atof(hint);
99 } else {
100 mouse->relative_speed_scale = 1.0f;
101 }
102}
103
104static void SDLCALL
105SDL_TouchMouseEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
106{
107 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
108
109 mouse->touch_mouse_events = SDL_GetStringBoolean(hint, SDL_TRUE);
110}
111
112static void SDLCALL
113SDL_MouseTouchEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
114{
115 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
116 SDL_bool default_value;
117
118#if defined(__ANDROID__) || (defined(__IPHONEOS__) && !defined(__TVOS__))
119 default_value = SDL_TRUE;
120#else
121 default_value = SDL_FALSE;
122#endif
123 mouse->mouse_touch_events = SDL_GetStringBoolean(hint, default_value);
124
125 if (mouse->mouse_touch_events) {
126 SDL_AddTouch(SDL_MOUSE_TOUCHID, SDL_TOUCH_DEVICE_DIRECT, "mouse_input");
127 }
128}
129
130/* Public functions */
131int
132SDL_MouseInit(void)
133{
134 SDL_Mouse *mouse = SDL_GetMouse();
135
136 SDL_zerop(mouse);
137
138 SDL_AddHintCallback(SDL_HINT_MOUSE_DOUBLE_CLICK_TIME,
139 SDL_MouseDoubleClickTimeChanged, mouse);
140
141 SDL_AddHintCallback(SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS,
142 SDL_MouseDoubleClickRadiusChanged, mouse);
143
144 SDL_AddHintCallback(SDL_HINT_MOUSE_NORMAL_SPEED_SCALE,
145 SDL_MouseNormalSpeedScaleChanged, mouse);
146
147 SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE,
148 SDL_MouseRelativeSpeedScaleChanged, mouse);
149
150 SDL_AddHintCallback(SDL_HINT_TOUCH_MOUSE_EVENTS,
151 SDL_TouchMouseEventsChanged, mouse);
152
153 SDL_AddHintCallback(SDL_HINT_MOUSE_TOUCH_EVENTS,
154 SDL_MouseTouchEventsChanged, mouse);
155
156 mouse->was_touch_mouse_events = SDL_FALSE; /* no touch to mouse movement event pending */
157
158 mouse->cursor_shown = SDL_TRUE;
159
160 return (0);
161}
162
163void
164SDL_SetDefaultCursor(SDL_Cursor * cursor)
165{
166 SDL_Mouse *mouse = SDL_GetMouse();
167
168 mouse->def_cursor = cursor;
169 if (!mouse->cur_cursor) {
170 SDL_SetCursor(cursor);
171 }
172}
173
174SDL_Mouse *
175SDL_GetMouse(void)
176{
177 return &SDL_mouse;
178}
179
180SDL_Window *
181SDL_GetMouseFocus(void)
182{
183 SDL_Mouse *mouse = SDL_GetMouse();
184
185 return mouse->focus;
186}
187
188#if 0
189void
190SDL_ResetMouse(void)
191{
192 SDL_Mouse *mouse = SDL_GetMouse();
193 Uint8 i;
194
195#ifdef DEBUG_MOUSE
196 printf("Resetting mouse\n");
197#endif
198 for (i = 1; i <= sizeof(mouse->buttonstate)*8; ++i) {
199 if (mouse->buttonstate & SDL_BUTTON(i)) {
200 SDL_SendMouseButton(mouse->focus, mouse->mouseID, SDL_RELEASED, i);
201 }
202 }
203 SDL_assert(mouse->buttonstate == 0);
204}
205#endif
206
207void
208SDL_SetMouseFocus(SDL_Window * window)
209{
210 SDL_Mouse *mouse = SDL_GetMouse();
211
212 if (mouse->focus == window) {
213 return;
214 }
215
216 /* Actually, this ends up being a bad idea, because most operating
217 systems have an implicit grab when you press the mouse button down
218 so you can drag things out of the window and then get the mouse up
219 when it happens. So, #if 0...
220 */
221#if 0
222 if (mouse->focus && !window) {
223 /* We won't get anymore mouse messages, so reset mouse state */
224 SDL_ResetMouse();
225 }
226#endif
227
228 /* See if the current window has lost focus */
229 if (mouse->focus) {
230 SDL_SendWindowEvent(mouse->focus, SDL_WINDOWEVENT_LEAVE, 0, 0);
231 }
232
233 mouse->focus = window;
234 mouse->has_position = SDL_FALSE;
235
236 if (mouse->focus) {
237 SDL_SendWindowEvent(mouse->focus, SDL_WINDOWEVENT_ENTER, 0, 0);
238 }
239
240 /* Update cursor visibility */
241 SDL_SetCursor(NULL);
242}
243
244/* Check to see if we need to synthesize focus events */
245static SDL_bool
246SDL_UpdateMouseFocus(SDL_Window * window, int x, int y, Uint32 buttonstate, SDL_bool send_mouse_motion)
247{
248 SDL_Mouse *mouse = SDL_GetMouse();
249 SDL_bool inWindow = SDL_TRUE;
250
251 if (window && ((window->flags & SDL_WINDOW_MOUSE_CAPTURE) == 0)) {
252 int w, h;
253 SDL_GetWindowSize(window, &w, &h);
254 if (x < 0 || y < 0 || x >= w || y >= h) {
255 inWindow = SDL_FALSE;
256 }
257 }
258
259/* Linux doesn't give you mouse events outside your window unless you grab
260 the pointer.
261
262 Windows doesn't give you mouse events outside your window unless you call
263 SetCapture().
264
265 Both of these are slightly scary changes, so for now we'll punt and if the
266 mouse leaves the window you'll lose mouse focus and reset button state.
267*/
268#ifdef SUPPORT_DRAG_OUTSIDE_WINDOW
269 if (!inWindow && !buttonstate) {
270#else
271 if (!inWindow) {
272#endif
273 if (window == mouse->focus) {
274#ifdef DEBUG_MOUSE
275 printf("Mouse left window, synthesizing move & focus lost event\n");
276#endif
277 if (send_mouse_motion) {
278 SDL_PrivateSendMouseMotion(window, mouse->mouseID, 0, x, y);
279 }
280 SDL_SetMouseFocus(NULL);
281 }
282 return SDL_FALSE;
283 }
284
285 if (window != mouse->focus) {
286#ifdef DEBUG_MOUSE
287 printf("Mouse entered window, synthesizing focus gain & move event\n");
288#endif
289 SDL_SetMouseFocus(window);
290 if (send_mouse_motion) {
291 SDL_PrivateSendMouseMotion(window, mouse->mouseID, 0, x, y);
292 }
293 }
294 return SDL_TRUE;
295}
296
297int
298SDL_SendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int x, int y)
299{
300 if (window && !relative) {
301 SDL_Mouse *mouse = SDL_GetMouse();
302 if (!SDL_UpdateMouseFocus(window, x, y, mouse->buttonstate, (mouseID == SDL_TOUCH_MOUSEID) ? SDL_FALSE : SDL_TRUE)) {
303 return 0;
304 }
305 }
306
307 return SDL_PrivateSendMouseMotion(window, mouseID, relative, x, y);
308}
309
310static int
311GetScaledMouseDelta(float scale, int value, float *accum)
312{
313 if (scale != 1.0f) {
314 *accum += scale * value;
315 if (*accum >= 0.0f) {
316 value = (int)SDL_floor(*accum);
317 } else {
318 value = (int)SDL_ceil(*accum);
319 }
320 *accum -= value;
321 }
322 return value;
323}
324
325static int
326SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relative, int x, int y)
327{
328 SDL_Mouse *mouse = SDL_GetMouse();
329 int posted;
330 int xrel;
331 int yrel;
332
333 /* SDL_HINT_MOUSE_TOUCH_EVENTS: controlling whether mouse events should generate synthetic touch events */
334 if (mouse->mouse_touch_events) {
335 if (mouseID != SDL_TOUCH_MOUSEID && !relative && track_mouse_down) {
336 if (window) {
337 float fx = (float)x / (float)window->w;
338 float fy = (float)y / (float)window->h;
339 SDL_SendTouchMotion(SDL_MOUSE_TOUCHID, 0, window, fx, fy, 1.0f);
340 }
341 }
342 }
343
344 /* SDL_HINT_TOUCH_MOUSE_EVENTS: if not set, discard synthetic mouse events coming from platform layer */
345 if (mouse->touch_mouse_events == 0) {
346 if (mouseID == SDL_TOUCH_MOUSEID) {
347 return 0;
348 }
349 }
350
351 if (mouseID != SDL_TOUCH_MOUSEID && mouse->relative_mode_warp) {
352 int center_x = 0, center_y = 0;
353 SDL_GetWindowSize(window, &center_x, &center_y);
354 center_x /= 2;
355 center_y /= 2;
356 if (x == center_x && y == center_y) {
357 mouse->last_x = center_x;
358 mouse->last_y = center_y;
359 return 0;
360 }
361 SDL_WarpMouseInWindow(window, center_x, center_y);
362 }
363
364 if (relative) {
365 if (mouse->relative_mode) {
366 x = GetScaledMouseDelta(mouse->relative_speed_scale, x, &mouse->scale_accum_x);
367 y = GetScaledMouseDelta(mouse->relative_speed_scale, y, &mouse->scale_accum_y);
368 } else {
369 x = GetScaledMouseDelta(mouse->normal_speed_scale, x, &mouse->scale_accum_x);
370 y = GetScaledMouseDelta(mouse->normal_speed_scale, y, &mouse->scale_accum_y);
371 }
372 xrel = x;
373 yrel = y;
374 x = (mouse->last_x + xrel);
375 y = (mouse->last_y + yrel);
376 } else {
377 xrel = x - mouse->last_x;
378 yrel = y - mouse->last_y;
379 }
380
381 /* Ignore relative motion when first positioning the mouse */
382 if (!mouse->has_position) {
383 xrel = 0;
384 yrel = 0;
385 mouse->has_position = SDL_TRUE;
386 } else if (!xrel && !yrel) { /* Drop events that don't change state */
387#ifdef DEBUG_MOUSE
388 printf("Mouse event didn't change state - dropped!\n");
389#endif
390 return 0;
391 }
392
393 /* Ignore relative motion positioning the first touch */
394 if (mouseID == SDL_TOUCH_MOUSEID && !mouse->buttonstate) {
395 xrel = 0;
396 yrel = 0;
397 }
398
399 /* Update internal mouse coordinates */
400 if (!mouse->relative_mode) {
401 mouse->x = x;
402 mouse->y = y;
403 } else {
404 mouse->x += xrel;
405 mouse->y += yrel;
406 }
407
408 /* make sure that the pointers find themselves inside the windows,
409 unless we have the mouse captured. */
410 if (window && ((window->flags & SDL_WINDOW_MOUSE_CAPTURE) == 0)) {
411 int x_max = 0, y_max = 0;
412
413 /* !!! FIXME: shouldn't this be (window) instead of (mouse->focus)? */
414 SDL_GetWindowSize(mouse->focus, &x_max, &y_max);
415 --x_max;
416 --y_max;
417
418 if (mouse->x > x_max) {
419 mouse->x = x_max;
420 }
421 if (mouse->x < 0) {
422 mouse->x = 0;
423 }
424
425 if (mouse->y > y_max) {
426 mouse->y = y_max;
427 }
428 if (mouse->y < 0) {
429 mouse->y = 0;
430 }
431 }
432
433 mouse->xdelta += xrel;
434 mouse->ydelta += yrel;
435
436 /* Move the mouse cursor, if needed */
437 if (mouse->cursor_shown && !mouse->relative_mode &&
438 mouse->MoveCursor && mouse->cur_cursor) {
439 mouse->MoveCursor(mouse->cur_cursor);
440 }
441
442 /* Post the event, if desired */
443 posted = 0;
444 if (SDL_GetEventState(SDL_MOUSEMOTION) == SDL_ENABLE) {
445 SDL_Event event;
446 event.motion.type = SDL_MOUSEMOTION;
447 event.motion.windowID = mouse->focus ? mouse->focus->id : 0;
448 event.motion.which = mouseID;
449 /* Set us pending (or clear during a normal mouse movement event) as having triggered */
450 mouse->was_touch_mouse_events = (mouseID == SDL_TOUCH_MOUSEID)? SDL_TRUE : SDL_FALSE;
451 event.motion.state = mouse->buttonstate;
452 event.motion.x = mouse->x;
453 event.motion.y = mouse->y;
454 event.motion.xrel = xrel;
455 event.motion.yrel = yrel;
456 posted = (SDL_PushEvent(&event) > 0);
457 }
458 if (relative) {
459 mouse->last_x = mouse->x;
460 mouse->last_y = mouse->y;
461 } else {
462 /* Use unclamped values if we're getting events outside the window */
463 mouse->last_x = x;
464 mouse->last_y = y;
465 }
466 return posted;
467}
468
469static SDL_MouseClickState *GetMouseClickState(SDL_Mouse *mouse, Uint8 button)
470{
471 if (button >= mouse->num_clickstates) {
472 int i, count = button + 1;
473 SDL_MouseClickState *clickstate = (SDL_MouseClickState *)SDL_realloc(mouse->clickstate, count * sizeof(*mouse->clickstate));
474 if (!clickstate) {
475 return NULL;
476 }
477 mouse->clickstate = clickstate;
478
479 for (i = mouse->num_clickstates; i < count; ++i) {
480 SDL_zero(mouse->clickstate[i]);
481 }
482 mouse->num_clickstates = count;
483 }
484 return &mouse->clickstate[button];
485}
486
487static int
488SDL_PrivateSendMouseButton(SDL_Window * window, SDL_MouseID mouseID, Uint8 state, Uint8 button, int clicks)
489{
490 SDL_Mouse *mouse = SDL_GetMouse();
491 int posted;
492 Uint32 type;
493 Uint32 buttonstate = mouse->buttonstate;
494
495 /* SDL_HINT_MOUSE_TOUCH_EVENTS: controlling whether mouse events should generate synthetic touch events */
496 if (mouse->mouse_touch_events) {
497 if (mouseID != SDL_TOUCH_MOUSEID && button == SDL_BUTTON_LEFT) {
498 if (state == SDL_PRESSED) {
499 track_mouse_down = SDL_TRUE;
500 } else {
501 track_mouse_down = SDL_FALSE;
502 }
503 if (window) {
504 float fx = (float)mouse->x / (float)window->w;
505 float fy = (float)mouse->y / (float)window->h;
506 SDL_SendTouch(SDL_MOUSE_TOUCHID, 0, window, track_mouse_down, fx, fy, 1.0f);
507 }
508 }
509 }
510
511 /* SDL_HINT_TOUCH_MOUSE_EVENTS: if not set, discard synthetic mouse events coming from platform layer */
512 if (mouse->touch_mouse_events == 0) {
513 if (mouseID == SDL_TOUCH_MOUSEID) {
514 return 0;
515 }
516 }
517
518 /* Figure out which event to perform */
519 switch (state) {
520 case SDL_PRESSED:
521 type = SDL_MOUSEBUTTONDOWN;
522 buttonstate |= SDL_BUTTON(button);
523 break;
524 case SDL_RELEASED:
525 type = SDL_MOUSEBUTTONUP;
526 buttonstate &= ~SDL_BUTTON(button);
527 break;
528 default:
529 /* Invalid state -- bail */
530 return 0;
531 }
532
533 /* We do this after calculating buttonstate so button presses gain focus */
534 if (window && state == SDL_PRESSED) {
535 SDL_UpdateMouseFocus(window, mouse->x, mouse->y, buttonstate, SDL_TRUE);
536 }
537
538 if (buttonstate == mouse->buttonstate) {
539 /* Ignore this event, no state change */
540 return 0;
541 }
542 mouse->buttonstate = buttonstate;
543
544 if (clicks < 0) {
545 SDL_MouseClickState *clickstate = GetMouseClickState(mouse, button);
546 if (clickstate) {
547 if (state == SDL_PRESSED) {
548 Uint32 now = SDL_GetTicks();
549
550 if (SDL_TICKS_PASSED(now, clickstate->last_timestamp + mouse->double_click_time) ||
551 SDL_abs(mouse->x - clickstate->last_x) > mouse->double_click_radius ||
552 SDL_abs(mouse->y - clickstate->last_y) > mouse->double_click_radius) {
553 clickstate->click_count = 0;
554 }
555 clickstate->last_timestamp = now;
556 clickstate->last_x = mouse->x;
557 clickstate->last_y = mouse->y;
558 if (clickstate->click_count < 255) {
559 ++clickstate->click_count;
560 }
561 }
562 clicks = clickstate->click_count;
563 } else {
564 clicks = 1;
565 }
566 }
567
568 /* Post the event, if desired */
569 posted = 0;
570 if (SDL_GetEventState(type) == SDL_ENABLE) {
571 SDL_Event event;
572 event.type = type;
573 event.button.windowID = mouse->focus ? mouse->focus->id : 0;
574 event.button.which = mouseID;
575 event.button.state = state;
576 event.button.button = button;
577 event.button.clicks = (Uint8) SDL_min(clicks, 255);
578 event.button.x = mouse->x;
579 event.button.y = mouse->y;
580 posted = (SDL_PushEvent(&event) > 0);
581 }
582
583 /* We do this after dispatching event so button releases can lose focus */
584 if (window && state == SDL_RELEASED) {
585 SDL_UpdateMouseFocus(window, mouse->x, mouse->y, buttonstate, SDL_TRUE);
586 }
587
588 return posted;
589}
590
591int
592SDL_SendMouseButtonClicks(SDL_Window * window, SDL_MouseID mouseID, Uint8 state, Uint8 button, int clicks)
593{
594 clicks = SDL_max(clicks, 0);
595 return SDL_PrivateSendMouseButton(window, mouseID, state, button, clicks);
596}
597
598int
599SDL_SendMouseButton(SDL_Window * window, SDL_MouseID mouseID, Uint8 state, Uint8 button)
600{
601 return SDL_PrivateSendMouseButton(window, mouseID, state, button, -1);
602}
603
604int
605SDL_SendMouseWheel(SDL_Window * window, SDL_MouseID mouseID, float x, float y, SDL_MouseWheelDirection direction)
606{
607 SDL_Mouse *mouse = SDL_GetMouse();
608 int posted;
609 int integral_x, integral_y;
610
611 if (window) {
612 SDL_SetMouseFocus(window);
613 }
614
615 if (x == 0.0f && y == 0.0f) {
616 return 0;
617 }
618
619 mouse->accumulated_wheel_x += x;
620 if (mouse->accumulated_wheel_x > 0) {
621 integral_x = (int)SDL_floor(mouse->accumulated_wheel_x);
622 } else if (mouse->accumulated_wheel_x < 0) {
623 integral_x = (int)SDL_ceil(mouse->accumulated_wheel_x);
624 } else {
625 integral_x = 0;
626 }
627 mouse->accumulated_wheel_x -= integral_x;
628
629 mouse->accumulated_wheel_y += y;
630 if (mouse->accumulated_wheel_y > 0) {
631 integral_y = (int)SDL_floor(mouse->accumulated_wheel_y);
632 } else if (mouse->accumulated_wheel_y < 0) {
633 integral_y = (int)SDL_ceil(mouse->accumulated_wheel_y);
634 } else {
635 integral_y = 0;
636 }
637 mouse->accumulated_wheel_y -= integral_y;
638
639 /* Post the event, if desired */
640 posted = 0;
641 if (SDL_GetEventState(SDL_MOUSEWHEEL) == SDL_ENABLE) {
642 SDL_Event event;
643 event.type = SDL_MOUSEWHEEL;
644 event.wheel.windowID = mouse->focus ? mouse->focus->id : 0;
645 event.wheel.which = mouseID;
646#if 0 /* Uncomment this when it goes in for SDL 2.1 */
647 event.wheel.preciseX = x;
648 event.wheel.preciseY = y;
649#endif
650 event.wheel.x = integral_x;
651 event.wheel.y = integral_y;
652 event.wheel.direction = (Uint32)direction;
653 posted = (SDL_PushEvent(&event) > 0);
654 }
655 return posted;
656}
657
658void
659SDL_MouseQuit(void)
660{
661 SDL_Cursor *cursor, *next;
662 SDL_Mouse *mouse = SDL_GetMouse();
663
664 if (mouse->CaptureMouse) {
665 SDL_CaptureMouse(SDL_FALSE);
666 }
667 SDL_SetRelativeMouseMode(SDL_FALSE);
668 SDL_ShowCursor(1);
669
670 cursor = mouse->cursors;
671 while (cursor) {
672 next = cursor->next;
673 SDL_FreeCursor(cursor);
674 cursor = next;
675 }
676 mouse->cursors = NULL;
677 mouse->cur_cursor = NULL;
678
679 if (mouse->def_cursor && mouse->FreeCursor) {
680 mouse->FreeCursor(mouse->def_cursor);
681 mouse->def_cursor = NULL;
682 }
683
684 if (mouse->clickstate) {
685 SDL_free(mouse->clickstate);
686 mouse->clickstate = NULL;
687 }
688
689 SDL_DelHintCallback(SDL_HINT_MOUSE_NORMAL_SPEED_SCALE,
690 SDL_MouseNormalSpeedScaleChanged, mouse);
691
692 SDL_DelHintCallback(SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE,
693 SDL_MouseRelativeSpeedScaleChanged, mouse);
694}
695
696Uint32
697SDL_GetMouseState(int *x, int *y)
698{
699 SDL_Mouse *mouse = SDL_GetMouse();
700
701 if (x) {
702 *x = mouse->x;
703 }
704 if (y) {
705 *y = mouse->y;
706 }
707 return mouse->buttonstate;
708}
709
710Uint32
711SDL_GetRelativeMouseState(int *x, int *y)
712{
713 SDL_Mouse *mouse = SDL_GetMouse();
714
715 if (x) {
716 *x = mouse->xdelta;
717 }
718 if (y) {
719 *y = mouse->ydelta;
720 }
721 mouse->xdelta = 0;
722 mouse->ydelta = 0;
723 return mouse->buttonstate;
724}
725
726Uint32
727SDL_GetGlobalMouseState(int *x, int *y)
728{
729 SDL_Mouse *mouse = SDL_GetMouse();
730
731 if (mouse->GetGlobalMouseState) {
732 int tmpx, tmpy;
733
734 /* make sure these are never NULL for the backend implementations... */
735 if (!x) {
736 x = &tmpx;
737 }
738 if (!y) {
739 y = &tmpy;
740 }
741
742 *x = *y = 0;
743
744 return mouse->GetGlobalMouseState(x, y);
745 } else {
746 return SDL_GetMouseState(x, y);
747 }
748}
749
750void
751SDL_WarpMouseInWindow(SDL_Window * window, int x, int y)
752{
753 SDL_Mouse *mouse = SDL_GetMouse();
754
755 if (window == NULL) {
756 window = mouse->focus;
757 }
758
759 if (window == NULL) {
760 return;
761 }
762
763 if (mouse->WarpMouse) {
764 mouse->WarpMouse(window, x, y);
765 } else {
766 SDL_SendMouseMotion(window, mouse->mouseID, 0, x, y);
767 }
768}
769
770int
771SDL_WarpMouseGlobal(int x, int y)
772{
773 SDL_Mouse *mouse = SDL_GetMouse();
774
775 if (mouse->WarpMouseGlobal) {
776 return mouse->WarpMouseGlobal(x, y);
777 }
778
779 return SDL_Unsupported();
780}
781
782static SDL_bool
783ShouldUseRelativeModeWarp(SDL_Mouse *mouse)
784{
785 if (!mouse->WarpMouse) {
786 /* Need this functionality for relative mode warp implementation */
787 return SDL_FALSE;
788 }
789
790 return SDL_GetHintBoolean(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, SDL_FALSE);
791}
792
793int
794SDL_SetRelativeMouseMode(SDL_bool enabled)
795{
796 SDL_Mouse *mouse = SDL_GetMouse();
797 SDL_Window *focusWindow = SDL_GetKeyboardFocus();
798
799 if (enabled == mouse->relative_mode) {
800 return 0;
801 }
802
803 /* Set the relative mode */
804 if (!enabled && mouse->relative_mode_warp) {
805 mouse->relative_mode_warp = SDL_FALSE;
806 } else if (enabled && ShouldUseRelativeModeWarp(mouse)) {
807 mouse->relative_mode_warp = SDL_TRUE;
808 } else if (!mouse->SetRelativeMouseMode || mouse->SetRelativeMouseMode(enabled) < 0) {
809 if (enabled) {
810 /* Fall back to warp mode if native relative mode failed */
811 if (!mouse->WarpMouse) {
812 return SDL_SetError("No relative mode implementation available");
813 }
814 mouse->relative_mode_warp = SDL_TRUE;
815 }
816 }
817 mouse->relative_mode = enabled;
818 mouse->scale_accum_x = 0.0f;
819 mouse->scale_accum_y = 0.0f;
820
821 if (enabled && focusWindow) {
822 /* Center it in the focused window to prevent clicks from going through
823 * to background windows.
824 */
825 SDL_SetMouseFocus(focusWindow);
826 SDL_WarpMouseInWindow(focusWindow, focusWindow->w/2, focusWindow->h/2);
827 }
828
829 if (mouse->focus) {
830 SDL_UpdateWindowGrab(mouse->focus);
831
832 /* Put the cursor back to where the application expects it */
833 if (!enabled) {
834 SDL_WarpMouseInWindow(mouse->focus, mouse->x, mouse->y);
835 }
836 }
837
838 /* Flush pending mouse motion - ideally we would pump events, but that's not always safe */
839 SDL_FlushEvent(SDL_MOUSEMOTION);
840
841 /* Update cursor visibility */
842 SDL_SetCursor(NULL);
843
844 return 0;
845}
846
847SDL_bool
848SDL_GetRelativeMouseMode()
849{
850 SDL_Mouse *mouse = SDL_GetMouse();
851
852 return mouse->relative_mode;
853}
854
855int
856SDL_CaptureMouse(SDL_bool enabled)
857{
858 SDL_Mouse *mouse = SDL_GetMouse();
859 SDL_Window *focusWindow;
860 SDL_bool isCaptured;
861
862 if (!mouse->CaptureMouse) {
863 return SDL_Unsupported();
864 }
865
866 focusWindow = SDL_GetKeyboardFocus();
867
868 isCaptured = focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE);
869 if (isCaptured == enabled) {
870 return 0; /* already done! */
871 }
872
873 if (enabled) {
874 if (!focusWindow) {
875 return SDL_SetError("No window has focus");
876 } else if (mouse->CaptureMouse(focusWindow) == -1) {
877 return -1; /* CaptureMouse() should call SetError */
878 }
879 focusWindow->flags |= SDL_WINDOW_MOUSE_CAPTURE;
880 } else {
881 if (mouse->CaptureMouse(NULL) == -1) {
882 return -1; /* CaptureMouse() should call SetError */
883 }
884 focusWindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
885 }
886
887 return 0;
888}
889
890SDL_Cursor *
891SDL_CreateCursor(const Uint8 * data, const Uint8 * mask,
892 int w, int h, int hot_x, int hot_y)
893{
894 SDL_Surface *surface;
895 SDL_Cursor *cursor;
896 int x, y;
897 Uint32 *pixel;
898 Uint8 datab = 0, maskb = 0;
899 const Uint32 black = 0xFF000000;
900 const Uint32 white = 0xFFFFFFFF;
901 const Uint32 transparent = 0x00000000;
902
903 /* Make sure the width is a multiple of 8 */
904 w = ((w + 7) & ~7);
905
906 /* Create the surface from a bitmap */
907 surface = SDL_CreateRGBSurface(0, w, h, 32,
908 0x00FF0000,
909 0x0000FF00,
910 0x000000FF,
911 0xFF000000);
912 if (!surface) {
913 return NULL;
914 }
915 for (y = 0; y < h; ++y) {
916 pixel = (Uint32 *) ((Uint8 *) surface->pixels + y * surface->pitch);
917 for (x = 0; x < w; ++x) {
918 if ((x % 8) == 0) {
919 datab = *data++;
920 maskb = *mask++;
921 }
922 if (maskb & 0x80) {
923 *pixel++ = (datab & 0x80) ? black : white;
924 } else {
925 *pixel++ = (datab & 0x80) ? black : transparent;
926 }
927 datab <<= 1;
928 maskb <<= 1;
929 }
930 }
931
932 cursor = SDL_CreateColorCursor(surface, hot_x, hot_y);
933
934 SDL_FreeSurface(surface);
935
936 return cursor;
937}
938
939SDL_Cursor *
940SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y)
941{
942 SDL_Mouse *mouse = SDL_GetMouse();
943 SDL_Surface *temp = NULL;
944 SDL_Cursor *cursor;
945
946 if (!surface) {
947 SDL_SetError("Passed NULL cursor surface");
948 return NULL;
949 }
950
951 if (!mouse->CreateCursor) {
952 SDL_SetError("Cursors are not currently supported");
953 return NULL;
954 }
955
956 /* Sanity check the hot spot */
957 if ((hot_x < 0) || (hot_y < 0) ||
958 (hot_x >= surface->w) || (hot_y >= surface->h)) {
959 SDL_SetError("Cursor hot spot doesn't lie within cursor");
960 return NULL;
961 }
962
963 if (surface->format->format != SDL_PIXELFORMAT_ARGB8888) {
964 temp = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ARGB8888, 0);
965 if (!temp) {
966 return NULL;
967 }
968 surface = temp;
969 }
970
971 cursor = mouse->CreateCursor(surface, hot_x, hot_y);
972 if (cursor) {
973 cursor->next = mouse->cursors;
974 mouse->cursors = cursor;
975 }
976
977 SDL_FreeSurface(temp);
978
979 return cursor;
980}
981
982SDL_Cursor *
983SDL_CreateSystemCursor(SDL_SystemCursor id)
984{
985 SDL_Mouse *mouse = SDL_GetMouse();
986 SDL_Cursor *cursor;
987
988 if (!mouse->CreateSystemCursor) {
989 SDL_SetError("CreateSystemCursor is not currently supported");
990 return NULL;
991 }
992
993 cursor = mouse->CreateSystemCursor(id);
994 if (cursor) {
995 cursor->next = mouse->cursors;
996 mouse->cursors = cursor;
997 }
998
999 return cursor;
1000}
1001
1002/* SDL_SetCursor(NULL) can be used to force the cursor redraw,
1003 if this is desired for any reason. This is used when setting
1004 the video mode and when the SDL window gains the mouse focus.
1005 */
1006void
1007SDL_SetCursor(SDL_Cursor * cursor)
1008{
1009 SDL_Mouse *mouse = SDL_GetMouse();
1010
1011 /* Set the new cursor */
1012 if (cursor) {
1013 /* Make sure the cursor is still valid for this mouse */
1014 if (cursor != mouse->def_cursor) {
1015 SDL_Cursor *found;
1016 for (found = mouse->cursors; found; found = found->next) {
1017 if (found == cursor) {
1018 break;
1019 }
1020 }
1021 if (!found) {
1022 SDL_SetError("Cursor not associated with the current mouse");
1023 return;
1024 }
1025 }
1026 mouse->cur_cursor = cursor;
1027 } else {
1028 if (mouse->focus) {
1029 cursor = mouse->cur_cursor;
1030 } else {
1031 cursor = mouse->def_cursor;
1032 }
1033 }
1034
1035 if (cursor && mouse->cursor_shown && !mouse->relative_mode) {
1036 if (mouse->ShowCursor) {
1037 mouse->ShowCursor(cursor);
1038 }
1039 } else {
1040 if (mouse->ShowCursor) {
1041 mouse->ShowCursor(NULL);
1042 }
1043 }
1044}
1045
1046SDL_Cursor *
1047SDL_GetCursor(void)
1048{
1049 SDL_Mouse *mouse = SDL_GetMouse();
1050
1051 if (!mouse) {
1052 return NULL;
1053 }
1054 return mouse->cur_cursor;
1055}
1056
1057SDL_Cursor *
1058SDL_GetDefaultCursor(void)
1059{
1060 SDL_Mouse *mouse = SDL_GetMouse();
1061
1062 if (!mouse) {
1063 return NULL;
1064 }
1065 return mouse->def_cursor;
1066}
1067
1068void
1069SDL_FreeCursor(SDL_Cursor * cursor)
1070{
1071 SDL_Mouse *mouse = SDL_GetMouse();
1072 SDL_Cursor *curr, *prev;
1073
1074 if (!cursor) {
1075 return;
1076 }
1077
1078 if (cursor == mouse->def_cursor) {
1079 return;
1080 }
1081 if (cursor == mouse->cur_cursor) {
1082 SDL_SetCursor(mouse->def_cursor);
1083 }
1084
1085 for (prev = NULL, curr = mouse->cursors; curr;
1086 prev = curr, curr = curr->next) {
1087 if (curr == cursor) {
1088 if (prev) {
1089 prev->next = curr->next;
1090 } else {
1091 mouse->cursors = curr->next;
1092 }
1093
1094 if (mouse->FreeCursor) {
1095 mouse->FreeCursor(curr);
1096 }
1097 return;
1098 }
1099 }
1100}
1101
1102int
1103SDL_ShowCursor(int toggle)
1104{
1105 SDL_Mouse *mouse = SDL_GetMouse();
1106 SDL_bool shown;
1107
1108 if (!mouse) {
1109 return 0;
1110 }
1111
1112 shown = mouse->cursor_shown;
1113 if (toggle >= 0) {
1114 if (toggle) {
1115 mouse->cursor_shown = SDL_TRUE;
1116 } else {
1117 mouse->cursor_shown = SDL_FALSE;
1118 }
1119 if (mouse->cursor_shown != shown) {
1120 SDL_SetCursor(NULL);
1121 }
1122 }
1123 return shown;
1124}
1125
1126/* vi: set ts=4 sw=4 expandtab: */
1127