1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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_c.h"
26#include "../video/SDL_sysvideo.h"
27#include "SDL_events_c.h"
28#include "SDL_mouse_c.h"
29#if defined(SDL_PLATFORM_WINDOWS)
30#include "../core/windows/SDL_windows.h" // For GetDoubleClickTime()
31#endif
32
33// #define DEBUG_MOUSE
34
35#define WARP_EMULATION_THRESHOLD_NS SDL_MS_TO_NS(30)
36
37typedef struct SDL_MouseInstance
38{
39 SDL_MouseID instance_id;
40 char *name;
41} SDL_MouseInstance;
42
43// The mouse state
44static SDL_Mouse SDL_mouse;
45static int SDL_mouse_count;
46static SDL_MouseInstance *SDL_mice;
47
48// for mapping mouse events to touch
49static bool track_mouse_down = false;
50
51static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, bool relative, float x, float y);
52
53static void SDLCALL SDL_MouseDoubleClickTimeChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
54{
55 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
56
57 if (hint && *hint) {
58 mouse->double_click_time = SDL_atoi(hint);
59 } else {
60#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)
61 mouse->double_click_time = GetDoubleClickTime();
62#else
63 mouse->double_click_time = 500;
64#endif
65 }
66}
67
68static void SDLCALL SDL_MouseDoubleClickRadiusChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
69{
70 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
71
72 if (hint && *hint) {
73 mouse->double_click_radius = SDL_atoi(hint);
74 } else {
75 mouse->double_click_radius = 32; // 32 pixels seems about right for touch interfaces
76 }
77}
78
79static void SDLCALL SDL_MouseNormalSpeedScaleChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
80{
81 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
82
83 if (hint && *hint) {
84 mouse->enable_normal_speed_scale = true;
85 mouse->normal_speed_scale = (float)SDL_atof(hint);
86 } else {
87 mouse->enable_normal_speed_scale = false;
88 mouse->normal_speed_scale = 1.0f;
89 }
90}
91
92static void SDLCALL SDL_MouseRelativeSpeedScaleChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
93{
94 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
95
96 if (hint && *hint) {
97 mouse->enable_relative_speed_scale = true;
98 mouse->relative_speed_scale = (float)SDL_atof(hint);
99 } else {
100 mouse->enable_relative_speed_scale = false;
101 mouse->relative_speed_scale = 1.0f;
102 }
103}
104
105static void SDLCALL SDL_MouseRelativeModeCenterChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
106{
107 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
108
109 mouse->relative_mode_center = SDL_GetStringBoolean(hint, true);
110}
111
112static void SDLCALL SDL_MouseRelativeSystemScaleChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
113{
114 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
115
116 mouse->enable_relative_system_scale = SDL_GetStringBoolean(hint, false);
117}
118
119static void SDLCALL SDL_MouseWarpEmulationChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
120{
121 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
122
123 mouse->warp_emulation_hint = SDL_GetStringBoolean(hint, true);
124
125 if (!mouse->warp_emulation_hint && mouse->warp_emulation_active) {
126 SDL_SetRelativeMouseMode(false);
127 mouse->warp_emulation_active = false;
128 }
129}
130
131static void SDLCALL SDL_TouchMouseEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
132{
133 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
134
135 mouse->touch_mouse_events = SDL_GetStringBoolean(hint, true);
136}
137
138#ifdef SDL_PLATFORM_VITA
139static void SDLCALL SDL_VitaTouchMouseDeviceChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
140{
141 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
142 if (hint) {
143 switch (*hint) {
144 default:
145 case '0':
146 mouse->vita_touch_mouse_device = 1;
147 break;
148 case '1':
149 mouse->vita_touch_mouse_device = 2;
150 break;
151 case '2':
152 mouse->vita_touch_mouse_device = 3;
153 break;
154 }
155 }
156}
157#endif
158
159static void SDLCALL SDL_MouseTouchEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
160{
161 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
162 bool default_value;
163
164#if defined(SDL_PLATFORM_ANDROID) || (defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_TVOS))
165 default_value = true;
166#else
167 default_value = false;
168#endif
169 mouse->mouse_touch_events = SDL_GetStringBoolean(hint, default_value);
170
171 if (mouse->mouse_touch_events) {
172 if (!mouse->added_mouse_touch_device) {
173 SDL_AddTouch(SDL_MOUSE_TOUCHID, SDL_TOUCH_DEVICE_DIRECT, "mouse_input");
174 mouse->added_mouse_touch_device = true;
175 }
176 } else {
177 if (mouse->added_mouse_touch_device) {
178 SDL_DelTouch(SDL_MOUSE_TOUCHID);
179 mouse->added_mouse_touch_device = false;
180 }
181 }
182}
183
184static void SDLCALL SDL_PenMouseEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
185{
186 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
187
188 mouse->pen_mouse_events = SDL_GetStringBoolean(hint, true);
189}
190
191static void SDLCALL SDL_PenTouchEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
192{
193 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
194
195 mouse->pen_touch_events = SDL_GetStringBoolean(hint, true);
196
197 if (mouse->pen_touch_events) {
198 if (!mouse->added_pen_touch_device) {
199 SDL_AddTouch(SDL_PEN_TOUCHID, SDL_TOUCH_DEVICE_DIRECT, "pen_input");
200 mouse->added_pen_touch_device = true;
201 }
202 } else {
203 if (mouse->added_pen_touch_device) {
204 SDL_DelTouch(SDL_PEN_TOUCHID);
205 mouse->added_pen_touch_device = false;
206 }
207 }
208}
209
210static void SDLCALL SDL_MouseAutoCaptureChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
211{
212 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
213 bool auto_capture = SDL_GetStringBoolean(hint, true);
214
215 if (auto_capture != mouse->auto_capture) {
216 mouse->auto_capture = auto_capture;
217 SDL_UpdateMouseCapture(false);
218 }
219}
220
221static void SDLCALL SDL_MouseRelativeWarpMotionChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
222{
223 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
224
225 mouse->relative_mode_warp_motion = SDL_GetStringBoolean(hint, false);
226}
227
228static void SDLCALL SDL_MouseRelativeCursorVisibleChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
229{
230 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
231
232 mouse->relative_mode_cursor_visible = SDL_GetStringBoolean(hint, false);
233
234 SDL_SetCursor(NULL); // Update cursor visibility
235}
236
237static void SDLCALL SDL_MouseIntegerModeChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
238{
239 SDL_Mouse *mouse = (SDL_Mouse *)userdata;
240
241 if (hint && *hint) {
242 mouse->integer_mode = (Uint8)SDL_atoi(hint);
243 } else {
244 mouse->integer_mode = 0;
245 }
246}
247
248// Public functions
249bool SDL_PreInitMouse(void)
250{
251 SDL_Mouse *mouse = SDL_GetMouse();
252
253 SDL_zerop(mouse);
254
255 SDL_AddHintCallback(SDL_HINT_MOUSE_DOUBLE_CLICK_TIME,
256 SDL_MouseDoubleClickTimeChanged, mouse);
257
258 SDL_AddHintCallback(SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS,
259 SDL_MouseDoubleClickRadiusChanged, mouse);
260
261 SDL_AddHintCallback(SDL_HINT_MOUSE_NORMAL_SPEED_SCALE,
262 SDL_MouseNormalSpeedScaleChanged, mouse);
263
264 SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE,
265 SDL_MouseRelativeSpeedScaleChanged, mouse);
266
267 SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE,
268 SDL_MouseRelativeSystemScaleChanged, mouse);
269
270 SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER,
271 SDL_MouseRelativeModeCenterChanged, mouse);
272
273 SDL_AddHintCallback(SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE,
274 SDL_MouseWarpEmulationChanged, mouse);
275
276 SDL_AddHintCallback(SDL_HINT_TOUCH_MOUSE_EVENTS,
277 SDL_TouchMouseEventsChanged, mouse);
278
279#ifdef SDL_PLATFORM_VITA
280 SDL_AddHintCallback(SDL_HINT_VITA_TOUCH_MOUSE_DEVICE,
281 SDL_VitaTouchMouseDeviceChanged, mouse);
282#endif
283
284 SDL_AddHintCallback(SDL_HINT_MOUSE_TOUCH_EVENTS,
285 SDL_MouseTouchEventsChanged, mouse);
286
287 SDL_AddHintCallback(SDL_HINT_PEN_MOUSE_EVENTS,
288 SDL_PenMouseEventsChanged, mouse);
289
290 SDL_AddHintCallback(SDL_HINT_PEN_TOUCH_EVENTS,
291 SDL_PenTouchEventsChanged, mouse);
292
293 SDL_AddHintCallback(SDL_HINT_MOUSE_AUTO_CAPTURE,
294 SDL_MouseAutoCaptureChanged, mouse);
295
296 SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_WARP_MOTION,
297 SDL_MouseRelativeWarpMotionChanged, mouse);
298
299 SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
300 SDL_MouseRelativeCursorVisibleChanged, mouse);
301
302 SDL_AddHintCallback("SDL_MOUSE_INTEGER_MODE",
303 SDL_MouseIntegerModeChanged, mouse);
304
305 mouse->was_touch_mouse_events = false; // no touch to mouse movement event pending
306
307 mouse->cursor_shown = true;
308
309 return true;
310}
311
312void SDL_PostInitMouse(void)
313{
314 SDL_Mouse *mouse = SDL_GetMouse();
315
316 /* Create a dummy mouse cursor for video backends that don't support true cursors,
317 * so that mouse grab and focus functionality will work.
318 */
319 if (!mouse->def_cursor) {
320 SDL_Surface *surface = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_ARGB8888);
321 if (surface) {
322 SDL_memset(surface->pixels, 0, (size_t)surface->h * surface->pitch);
323 SDL_SetDefaultCursor(SDL_CreateColorCursor(surface, 0, 0));
324 SDL_DestroySurface(surface);
325 }
326 }
327}
328
329bool SDL_IsMouse(Uint16 vendor, Uint16 product)
330{
331 // Eventually we'll have a blacklist of devices that enumerate as mice but aren't really
332 return true;
333}
334
335static int SDL_GetMouseIndex(SDL_MouseID mouseID)
336{
337 for (int i = 0; i < SDL_mouse_count; ++i) {
338 if (mouseID == SDL_mice[i].instance_id) {
339 return i;
340 }
341 }
342 return -1;
343}
344
345void SDL_AddMouse(SDL_MouseID mouseID, const char *name, bool send_event)
346{
347 int mouse_index = SDL_GetMouseIndex(mouseID);
348 if (mouse_index >= 0) {
349 // We already know about this mouse
350 return;
351 }
352
353 SDL_assert(mouseID != 0);
354
355 SDL_MouseInstance *mice = (SDL_MouseInstance *)SDL_realloc(SDL_mice, (SDL_mouse_count + 1) * sizeof(*mice));
356 if (!mice) {
357 return;
358 }
359 SDL_MouseInstance *instance = &mice[SDL_mouse_count];
360 instance->instance_id = mouseID;
361 instance->name = SDL_strdup(name ? name : "");
362 SDL_mice = mice;
363 ++SDL_mouse_count;
364
365 if (send_event) {
366 SDL_Event event;
367 SDL_zero(event);
368 event.type = SDL_EVENT_MOUSE_ADDED;
369 event.mdevice.which = mouseID;
370 SDL_PushEvent(&event);
371 }
372}
373
374void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event)
375{
376 int mouse_index = SDL_GetMouseIndex(mouseID);
377 if (mouse_index < 0) {
378 // We don't know about this mouse
379 return;
380 }
381
382 SDL_free(SDL_mice[mouse_index].name);
383
384 if (mouse_index != SDL_mouse_count - 1) {
385 SDL_memmove(&SDL_mice[mouse_index], &SDL_mice[mouse_index + 1], (SDL_mouse_count - mouse_index - 1) * sizeof(SDL_mice[mouse_index]));
386 }
387 --SDL_mouse_count;
388
389 // Remove any mouse input sources for this mouseID
390 SDL_Mouse *mouse = SDL_GetMouse();
391 for (int i = 0; i < mouse->num_sources; ++i) {
392 SDL_MouseInputSource *source = &mouse->sources[i];
393 if (source->mouseID == mouseID) {
394 SDL_free(source->clickstate);
395 if (i != mouse->num_sources - 1) {
396 SDL_memmove(&mouse->sources[i], &mouse->sources[i + 1], (mouse->num_sources - i - 1) * sizeof(mouse->sources[i]));
397 }
398 --mouse->num_sources;
399 break;
400 }
401 }
402
403 if (send_event) {
404 SDL_Event event;
405 SDL_zero(event);
406 event.type = SDL_EVENT_MOUSE_REMOVED;
407 event.mdevice.which = mouseID;
408 SDL_PushEvent(&event);
409 }
410}
411
412bool SDL_HasMouse(void)
413{
414 return (SDL_mouse_count > 0);
415}
416
417SDL_MouseID *SDL_GetMice(int *count)
418{
419 int i;
420 SDL_MouseID *mice;
421
422 mice = (SDL_JoystickID *)SDL_malloc((SDL_mouse_count + 1) * sizeof(*mice));
423 if (mice) {
424 if (count) {
425 *count = SDL_mouse_count;
426 }
427
428 for (i = 0; i < SDL_mouse_count; ++i) {
429 mice[i] = SDL_mice[i].instance_id;
430 }
431 mice[i] = 0;
432 } else {
433 if (count) {
434 *count = 0;
435 }
436 }
437
438 return mice;
439}
440
441const char *SDL_GetMouseNameForID(SDL_MouseID instance_id)
442{
443 int mouse_index = SDL_GetMouseIndex(instance_id);
444 if (mouse_index < 0) {
445 SDL_SetError("Mouse %" SDL_PRIu32 " not found", instance_id);
446 return NULL;
447 }
448 return SDL_GetPersistentString(SDL_mice[mouse_index].name);
449}
450
451void SDL_SetDefaultCursor(SDL_Cursor *cursor)
452{
453 SDL_Mouse *mouse = SDL_GetMouse();
454
455 if (cursor == mouse->def_cursor) {
456 return;
457 }
458
459 if (mouse->def_cursor) {
460 SDL_Cursor *default_cursor = mouse->def_cursor;
461 SDL_Cursor *prev, *curr;
462
463 if (mouse->cur_cursor == mouse->def_cursor) {
464 mouse->cur_cursor = NULL;
465 }
466 mouse->def_cursor = NULL;
467
468 for (prev = NULL, curr = mouse->cursors; curr;
469 prev = curr, curr = curr->next) {
470 if (curr == default_cursor) {
471 if (prev) {
472 prev->next = curr->next;
473 } else {
474 mouse->cursors = curr->next;
475 }
476
477 break;
478 }
479 }
480
481 if (mouse->FreeCursor && default_cursor->internal) {
482 mouse->FreeCursor(default_cursor);
483 } else {
484 SDL_free(default_cursor);
485 }
486 }
487
488 mouse->def_cursor = cursor;
489
490 if (!mouse->cur_cursor) {
491 SDL_SetCursor(cursor);
492 }
493}
494
495SDL_SystemCursor SDL_GetDefaultSystemCursor(void)
496{
497 SDL_SystemCursor id = SDL_SYSTEM_CURSOR_DEFAULT;
498 const char *value = SDL_GetHint(SDL_HINT_MOUSE_DEFAULT_SYSTEM_CURSOR);
499 if (value) {
500 int index = SDL_atoi(value);
501 if (0 <= index && index < SDL_SYSTEM_CURSOR_COUNT) {
502 id = (SDL_SystemCursor)index;
503 }
504 }
505 return id;
506}
507
508SDL_Mouse *SDL_GetMouse(void)
509{
510 return &SDL_mouse;
511}
512
513static SDL_MouseButtonFlags SDL_GetMouseButtonState(SDL_Mouse *mouse, SDL_MouseID mouseID, bool include_touch)
514{
515 int i;
516 SDL_MouseButtonFlags buttonstate = 0;
517
518 for (i = 0; i < mouse->num_sources; ++i) {
519 if (mouseID == SDL_GLOBAL_MOUSE_ID || mouseID == SDL_TOUCH_MOUSEID) {
520 if (include_touch || mouse->sources[i].mouseID != SDL_TOUCH_MOUSEID) {
521 buttonstate |= mouse->sources[i].buttonstate;
522 }
523 } else {
524 if (mouseID == mouse->sources[i].mouseID) {
525 buttonstate |= mouse->sources[i].buttonstate;
526 break;
527 }
528 }
529 }
530 return buttonstate;
531}
532
533SDL_Window *SDL_GetMouseFocus(void)
534{
535 SDL_Mouse *mouse = SDL_GetMouse();
536
537 return mouse->focus;
538}
539
540/* TODO RECONNECT: Hello from the Wayland video driver!
541 * This was once removed from SDL, but it's been added back in comment form
542 * because we will need it when Wayland adds compositor reconnect support.
543 * If you need this before we do, great! Otherwise, leave this alone, we'll
544 * uncomment it at the right time.
545 * -flibit
546 */
547#if 0
548void SDL_ResetMouse(void)
549{
550 SDL_Mouse *mouse = SDL_GetMouse();
551 Uint32 buttonState = SDL_GetMouseButtonState(mouse, SDL_GLOBAL_MOUSE_ID, false);
552 int i;
553
554 for (i = 1; i <= sizeof(buttonState)*8; ++i) {
555 if (buttonState & SDL_BUTTON_MASK(i)) {
556 SDL_SendMouseButton(0, mouse->focus, mouse->mouseID, i, false);
557 }
558 }
559 SDL_assert(SDL_GetMouseButtonState(mouse, SDL_GLOBAL_MOUSE_ID, false) == 0);
560}
561#endif // 0
562
563void SDL_SetMouseFocus(SDL_Window *window)
564{
565 SDL_Mouse *mouse = SDL_GetMouse();
566
567 if (mouse->focus == window) {
568 return;
569 }
570
571 /* Actually, this ends up being a bad idea, because most operating
572 systems have an implicit grab when you press the mouse button down
573 so you can drag things out of the window and then get the mouse up
574 when it happens. So, #if 0...
575 */
576#if 0
577 if (mouse->focus && !window) {
578 // We won't get anymore mouse messages, so reset mouse state
579 SDL_ResetMouse();
580 }
581#endif
582
583 // See if the current window has lost focus
584 if (mouse->focus) {
585 SDL_SendWindowEvent(mouse->focus, SDL_EVENT_WINDOW_MOUSE_LEAVE, 0, 0);
586 }
587
588 mouse->focus = window;
589 mouse->has_position = false;
590
591 if (mouse->focus) {
592 SDL_SendWindowEvent(mouse->focus, SDL_EVENT_WINDOW_MOUSE_ENTER, 0, 0);
593 }
594
595 // Update cursor visibility
596 SDL_SetCursor(NULL);
597}
598
599bool SDL_MousePositionInWindow(SDL_Window *window, float x, float y)
600{
601 if (!window) {
602 return false;
603 }
604
605 if (window && !(window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
606 if (x < 0.0f || y < 0.0f || x >= (float)window->w || y >= (float)window->h) {
607 return false;
608 }
609 }
610 return true;
611}
612
613// Check to see if we need to synthesize focus events
614static bool SDL_UpdateMouseFocus(SDL_Window *window, float x, float y, Uint32 buttonstate, bool send_mouse_motion)
615{
616 SDL_Mouse *mouse = SDL_GetMouse();
617 bool inWindow = SDL_MousePositionInWindow(window, x, y);
618
619 if (!inWindow) {
620 if (window == mouse->focus) {
621#ifdef DEBUG_MOUSE
622 SDL_Log("Mouse left window, synthesizing move & focus lost event");
623#endif
624 if (send_mouse_motion) {
625 SDL_PrivateSendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, x, y);
626 }
627 SDL_SetMouseFocus(NULL);
628 }
629 return false;
630 }
631
632 if (window != mouse->focus) {
633#ifdef DEBUG_MOUSE
634 SDL_Log("Mouse entered window, synthesizing focus gain & move event");
635#endif
636 SDL_SetMouseFocus(window);
637 if (send_mouse_motion) {
638 SDL_PrivateSendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, x, y);
639 }
640 }
641 return true;
642}
643
644void SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, bool relative, float x, float y)
645{
646 if (window && !relative) {
647 SDL_Mouse *mouse = SDL_GetMouse();
648 if (!SDL_UpdateMouseFocus(window, x, y, SDL_GetMouseButtonState(mouse, mouseID, true), (mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID))) {
649 return;
650 }
651 }
652
653 SDL_PrivateSendMouseMotion(timestamp, window, mouseID, relative, x, y);
654}
655
656static void ConstrainMousePosition(SDL_Mouse *mouse, SDL_Window *window, float *x, float *y)
657{
658 /* make sure that the pointers find themselves inside the windows,
659 unless we have the mouse captured. */
660 if (window && !(window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
661 int x_min = 0, x_max = window->w - 1;
662 int y_min = 0, y_max = window->h - 1;
663 const SDL_Rect *confine = SDL_GetWindowMouseRect(window);
664
665 if (confine) {
666 SDL_Rect window_rect;
667 SDL_Rect mouse_rect;
668
669 window_rect.x = 0;
670 window_rect.y = 0;
671 window_rect.w = x_max + 1;
672 window_rect.h = y_max + 1;
673 if (SDL_GetRectIntersection(confine, &window_rect, &mouse_rect)) {
674 x_min = mouse_rect.x;
675 y_min = mouse_rect.y;
676 x_max = x_min + mouse_rect.w - 1;
677 y_max = y_min + mouse_rect.h - 1;
678 }
679 }
680
681 if (*x >= (float)(x_max + 1)) {
682 *x = SDL_max((float)x_max, mouse->last_x);
683 }
684 if (*x < (float)x_min) {
685 *x = (float)x_min;
686 }
687
688 if (*y >= (float)(y_max + 1)) {
689 *y = SDL_max((float)y_max, mouse->last_y);
690 }
691 if (*y < (float)y_min) {
692 *y = (float)y_min;
693 }
694 }
695}
696
697static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, bool relative, float x, float y)
698{
699 SDL_Mouse *mouse = SDL_GetMouse();
700 float xrel = 0.0f;
701 float yrel = 0.0f;
702 bool window_is_relative = mouse->focus && (mouse->focus->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE);
703
704 // SDL_HINT_MOUSE_TOUCH_EVENTS: controlling whether mouse events should generate synthetic touch events
705 if (mouse->mouse_touch_events) {
706 if (mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID && !relative && track_mouse_down) {
707 if (window) {
708 float normalized_x = x / (float)window->w;
709 float normalized_y = y / (float)window->h;
710 SDL_SendTouchMotion(timestamp, SDL_MOUSE_TOUCHID, SDL_BUTTON_LEFT, window, normalized_x, normalized_y, 1.0f);
711 }
712 }
713 }
714
715 // SDL_HINT_TOUCH_MOUSE_EVENTS: if not set, discard synthetic mouse events coming from platform layer
716 if (!mouse->touch_mouse_events && mouseID == SDL_TOUCH_MOUSEID) {
717 return;
718 }
719
720 if (relative) {
721 if (mouse->relative_mode) {
722 if (mouse->InputTransform) {
723 void *data = mouse->input_transform_data;
724 mouse->InputTransform(data, timestamp, window, mouseID, &x, &y);
725 } else {
726 if (mouse->enable_relative_system_scale) {
727 if (mouse->ApplySystemScale) {
728 mouse->ApplySystemScale(mouse->system_scale_data, timestamp, window, mouseID, &x, &y);
729 }
730 }
731 if (mouse->enable_relative_speed_scale) {
732 x *= mouse->relative_speed_scale;
733 y *= mouse->relative_speed_scale;
734 }
735 }
736 } else {
737 if (mouse->enable_normal_speed_scale) {
738 x *= mouse->normal_speed_scale;
739 y *= mouse->normal_speed_scale;
740 }
741 }
742 if (mouse->integer_mode >= 1) {
743 // Accumulate the fractional relative motion and only process the integer portion
744 mouse->xrel_frac = SDL_modff(mouse->xrel_frac + x, &x);
745 mouse->yrel_frac = SDL_modff(mouse->yrel_frac + y, &y);
746 }
747 xrel = x;
748 yrel = y;
749 x = (mouse->last_x + xrel);
750 y = (mouse->last_y + yrel);
751 ConstrainMousePosition(mouse, window, &x, &y);
752 } else {
753 if (mouse->integer_mode >= 1) {
754 // Discard the fractional component from absolute coordinates
755 x = SDL_truncf(x);
756 y = SDL_truncf(y);
757 }
758 ConstrainMousePosition(mouse, window, &x, &y);
759 if (mouse->has_position) {
760 xrel = x - mouse->last_x;
761 yrel = y - mouse->last_y;
762 }
763 }
764
765 if (mouse->has_position && xrel == 0.0f && yrel == 0.0f) { // Drop events that don't change state
766#ifdef DEBUG_MOUSE
767 SDL_Log("Mouse event didn't change state - dropped!");
768#endif
769 return;
770 }
771
772 // Ignore relative motion positioning the first touch
773 if (mouseID == SDL_TOUCH_MOUSEID && !SDL_GetMouseButtonState(mouse, mouseID, true)) {
774 xrel = 0.0f;
775 yrel = 0.0f;
776 }
777
778 // modify internal state
779 {
780 mouse->x_accu += xrel;
781 mouse->y_accu += yrel;
782
783 if (relative && mouse->has_position) {
784 mouse->x += xrel;
785 mouse->y += yrel;
786 ConstrainMousePosition(mouse, window, &mouse->x, &mouse->y);
787 } else {
788 mouse->x = x;
789 mouse->y = y;
790 }
791 mouse->has_position = true;
792
793 // Use unclamped values if we're getting events outside the window
794 mouse->last_x = relative ? mouse->x : x;
795 mouse->last_y = relative ? mouse->y : y;
796
797 mouse->click_motion_x += xrel;
798 mouse->click_motion_y += yrel;
799 }
800
801 // Move the mouse cursor, if needed
802 if (mouse->cursor_shown && !mouse->relative_mode &&
803 mouse->MoveCursor && mouse->cur_cursor) {
804 mouse->MoveCursor(mouse->cur_cursor);
805 }
806
807 // Post the event, if desired
808 if (SDL_EventEnabled(SDL_EVENT_MOUSE_MOTION)) {
809 if ((!mouse->relative_mode || mouse->warp_emulation_active) && mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID) {
810 // We're not in relative mode, so all mouse events are global mouse events
811 mouseID = SDL_GLOBAL_MOUSE_ID;
812 }
813
814 if (!relative && window_is_relative) {
815 if (!mouse->relative_mode_warp_motion) {
816 return;
817 }
818 xrel = 0.0f;
819 yrel = 0.0f;
820 }
821
822 SDL_Event event;
823 event.type = SDL_EVENT_MOUSE_MOTION;
824 event.common.timestamp = timestamp;
825 event.motion.windowID = mouse->focus ? mouse->focus->id : 0;
826 event.motion.which = mouseID;
827 // Set us pending (or clear during a normal mouse movement event) as having triggered
828 mouse->was_touch_mouse_events = (mouseID == SDL_TOUCH_MOUSEID);
829 event.motion.state = SDL_GetMouseButtonState(mouse, mouseID, true);
830 event.motion.x = mouse->x;
831 event.motion.y = mouse->y;
832 event.motion.xrel = xrel;
833 event.motion.yrel = yrel;
834 SDL_PushEvent(&event);
835 }
836}
837
838static SDL_MouseInputSource *GetMouseInputSource(SDL_Mouse *mouse, SDL_MouseID mouseID, bool down, Uint8 button)
839{
840 SDL_MouseInputSource *source, *match = NULL, *sources;
841 int i;
842
843 for (i = 0; i < mouse->num_sources; ++i) {
844 source = &mouse->sources[i];
845 if (source->mouseID == mouseID) {
846 match = source;
847 break;
848 }
849 }
850
851 if (!down && (!match || !(match->buttonstate & SDL_BUTTON_MASK(button)))) {
852 /* This might be a button release from a transition between mouse messages and raw input.
853 * See if there's another mouse source that already has that button down and use that.
854 */
855 for (i = 0; i < mouse->num_sources; ++i) {
856 source = &mouse->sources[i];
857 if ((source->buttonstate & SDL_BUTTON_MASK(button))) {
858 match = source;
859 break;
860 }
861 }
862 }
863 if (match) {
864 return match;
865 }
866
867 sources = (SDL_MouseInputSource *)SDL_realloc(mouse->sources, (mouse->num_sources + 1) * sizeof(*mouse->sources));
868 if (sources) {
869 mouse->sources = sources;
870 ++mouse->num_sources;
871 source = &sources[mouse->num_sources - 1];
872 SDL_zerop(source);
873 source->mouseID = mouseID;
874 return source;
875 }
876 return NULL;
877}
878
879static SDL_MouseClickState *GetMouseClickState(SDL_MouseInputSource *source, Uint8 button)
880{
881 if (button >= source->num_clickstates) {
882 int i, count = button + 1;
883 SDL_MouseClickState *clickstate = (SDL_MouseClickState *)SDL_realloc(source->clickstate, count * sizeof(*source->clickstate));
884 if (!clickstate) {
885 return NULL;
886 }
887 source->clickstate = clickstate;
888
889 for (i = source->num_clickstates; i < count; ++i) {
890 SDL_zero(source->clickstate[i]);
891 }
892 source->num_clickstates = count;
893 }
894 return &source->clickstate[button];
895}
896
897static void SDL_PrivateSendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, Uint8 button, bool down, int clicks)
898{
899 SDL_Mouse *mouse = SDL_GetMouse();
900 SDL_EventType type;
901 Uint32 buttonstate;
902 SDL_MouseInputSource *source;
903
904 source = GetMouseInputSource(mouse, mouseID, down, button);
905 if (!source) {
906 return;
907 }
908 buttonstate = source->buttonstate;
909
910 // SDL_HINT_MOUSE_TOUCH_EVENTS: controlling whether mouse events should generate synthetic touch events
911 if (mouse->mouse_touch_events) {
912 if (mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID && button == SDL_BUTTON_LEFT) {
913 if (down) {
914 track_mouse_down = true;
915 } else {
916 track_mouse_down = false;
917 }
918 if (window) {
919 type = track_mouse_down ? SDL_EVENT_FINGER_DOWN : SDL_EVENT_FINGER_UP;
920 float normalized_x = mouse->x / (float)window->w;
921 float normalized_y = mouse->y / (float)window->h;
922 SDL_SendTouch(timestamp, SDL_MOUSE_TOUCHID, SDL_BUTTON_LEFT, window, type, normalized_x, normalized_y, 1.0f);
923 }
924 }
925 }
926
927 // SDL_HINT_TOUCH_MOUSE_EVENTS: if not set, discard synthetic mouse events coming from platform layer
928 if (mouse->touch_mouse_events == 0) {
929 if (mouseID == SDL_TOUCH_MOUSEID) {
930 return;
931 }
932 }
933
934 // Figure out which event to perform
935 if (down) {
936 type = SDL_EVENT_MOUSE_BUTTON_DOWN;
937 buttonstate |= SDL_BUTTON_MASK(button);
938 } else {
939 type = SDL_EVENT_MOUSE_BUTTON_UP;
940 buttonstate &= ~SDL_BUTTON_MASK(button);
941 }
942
943 // We do this after calculating buttonstate so button presses gain focus
944 if (window && down) {
945 SDL_UpdateMouseFocus(window, mouse->x, mouse->y, buttonstate, true);
946 }
947
948 if (buttonstate == source->buttonstate) {
949 // Ignore this event, no state change
950 return;
951 }
952 source->buttonstate = buttonstate;
953
954 if (clicks < 0) {
955 SDL_MouseClickState *clickstate = GetMouseClickState(source, button);
956 if (clickstate) {
957 if (down) {
958 Uint64 now = SDL_GetTicks();
959
960 if (now >= (clickstate->last_timestamp + mouse->double_click_time) ||
961 SDL_fabs(mouse->click_motion_x - clickstate->click_motion_x) > mouse->double_click_radius ||
962 SDL_fabs(mouse->click_motion_y - clickstate->click_motion_y) > mouse->double_click_radius) {
963 clickstate->click_count = 0;
964 }
965 clickstate->last_timestamp = now;
966 clickstate->click_motion_x = mouse->click_motion_x;
967 clickstate->click_motion_y = mouse->click_motion_y;
968 if (clickstate->click_count < 255) {
969 ++clickstate->click_count;
970 }
971 }
972 clicks = clickstate->click_count;
973 } else {
974 clicks = 1;
975 }
976 }
977
978 // Post the event, if desired
979 if (SDL_EventEnabled(type)) {
980 if ((!mouse->relative_mode || mouse->warp_emulation_active) && mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID) {
981 // We're not in relative mode, so all mouse events are global mouse events
982 mouseID = SDL_GLOBAL_MOUSE_ID;
983 } else {
984 mouseID = source->mouseID;
985 }
986
987 SDL_Event event;
988 event.type = type;
989 event.common.timestamp = timestamp;
990 event.button.windowID = mouse->focus ? mouse->focus->id : 0;
991 event.button.which = mouseID;
992 event.button.down = down;
993 event.button.button = button;
994 event.button.clicks = (Uint8)SDL_min(clicks, 255);
995 event.button.x = mouse->x;
996 event.button.y = mouse->y;
997 SDL_PushEvent(&event);
998 }
999
1000 // We do this after dispatching event so button releases can lose focus
1001 if (window && !down) {
1002 SDL_UpdateMouseFocus(window, mouse->x, mouse->y, buttonstate, true);
1003 }
1004
1005 // Automatically capture the mouse while buttons are pressed
1006 if (mouse->auto_capture) {
1007 SDL_UpdateMouseCapture(false);
1008 }
1009}
1010
1011void SDL_SendMouseButtonClicks(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, Uint8 button, bool down, int clicks)
1012{
1013 clicks = SDL_max(clicks, 0);
1014 SDL_PrivateSendMouseButton(timestamp, window, mouseID, button, down, clicks);
1015}
1016
1017void SDL_SendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, Uint8 button, bool down)
1018{
1019 SDL_PrivateSendMouseButton(timestamp, window, mouseID, button, down, -1);
1020}
1021
1022void SDL_SendMouseWheel(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float x, float y, SDL_MouseWheelDirection direction)
1023{
1024 SDL_Mouse *mouse = SDL_GetMouse();
1025
1026 if (window) {
1027 SDL_SetMouseFocus(window);
1028 }
1029
1030 // Accumulate fractional wheel motion if integer mode is enabled
1031 if (mouse->integer_mode >= 2) {
1032 mouse->wheel_x_frac = SDL_modff(mouse->wheel_x_frac + x, &x);
1033 mouse->wheel_y_frac = SDL_modff(mouse->wheel_y_frac + y, &y);
1034 }
1035
1036 if (x == 0.0f && y == 0.0f) {
1037 return;
1038 }
1039
1040 // Post the event, if desired
1041 if (SDL_EventEnabled(SDL_EVENT_MOUSE_WHEEL)) {
1042 if (!mouse->relative_mode || mouse->warp_emulation_active) {
1043 // We're not in relative mode, so all mouse events are global mouse events
1044 mouseID = SDL_GLOBAL_MOUSE_ID;
1045 }
1046
1047 SDL_Event event;
1048 event.type = SDL_EVENT_MOUSE_WHEEL;
1049 event.common.timestamp = timestamp;
1050 event.wheel.windowID = mouse->focus ? mouse->focus->id : 0;
1051 event.wheel.which = mouseID;
1052 event.wheel.x = x;
1053 event.wheel.y = y;
1054 event.wheel.direction = direction;
1055 event.wheel.mouse_x = mouse->x;
1056 event.wheel.mouse_y = mouse->y;
1057 SDL_PushEvent(&event);
1058 }
1059}
1060
1061void SDL_QuitMouse(void)
1062{
1063 SDL_Cursor *cursor, *next;
1064 SDL_Mouse *mouse = SDL_GetMouse();
1065
1066 if (mouse->added_mouse_touch_device) {
1067 SDL_DelTouch(SDL_MOUSE_TOUCHID);
1068 }
1069
1070 if (mouse->added_pen_touch_device) {
1071 SDL_DelTouch(SDL_PEN_TOUCHID);
1072 }
1073
1074 if (mouse->CaptureMouse) {
1075 SDL_CaptureMouse(false);
1076 SDL_UpdateMouseCapture(true);
1077 }
1078 SDL_SetRelativeMouseMode(false);
1079 SDL_ShowCursor();
1080
1081 if (mouse->def_cursor) {
1082 SDL_SetDefaultCursor(NULL);
1083 }
1084
1085 cursor = mouse->cursors;
1086 while (cursor) {
1087 next = cursor->next;
1088 SDL_DestroyCursor(cursor);
1089 cursor = next;
1090 }
1091 mouse->cursors = NULL;
1092 mouse->cur_cursor = NULL;
1093
1094 if (mouse->sources) {
1095 for (int i = 0; i < mouse->num_sources; ++i) {
1096 SDL_MouseInputSource *source = &mouse->sources[i];
1097 SDL_free(source->clickstate);
1098 }
1099 SDL_free(mouse->sources);
1100 mouse->sources = NULL;
1101 }
1102 mouse->num_sources = 0;
1103
1104 SDL_RemoveHintCallback(SDL_HINT_MOUSE_DOUBLE_CLICK_TIME,
1105 SDL_MouseDoubleClickTimeChanged, mouse);
1106
1107 SDL_RemoveHintCallback(SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS,
1108 SDL_MouseDoubleClickRadiusChanged, mouse);
1109
1110 SDL_RemoveHintCallback(SDL_HINT_MOUSE_NORMAL_SPEED_SCALE,
1111 SDL_MouseNormalSpeedScaleChanged, mouse);
1112
1113 SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE,
1114 SDL_MouseRelativeSpeedScaleChanged, mouse);
1115
1116 SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE,
1117 SDL_MouseRelativeSystemScaleChanged, mouse);
1118
1119 SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER,
1120 SDL_MouseRelativeModeCenterChanged, mouse);
1121
1122 SDL_RemoveHintCallback(SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE,
1123 SDL_MouseWarpEmulationChanged, mouse);
1124
1125 SDL_RemoveHintCallback(SDL_HINT_TOUCH_MOUSE_EVENTS,
1126 SDL_TouchMouseEventsChanged, mouse);
1127
1128 SDL_RemoveHintCallback(SDL_HINT_MOUSE_TOUCH_EVENTS,
1129 SDL_MouseTouchEventsChanged, mouse);
1130
1131 SDL_RemoveHintCallback(SDL_HINT_PEN_MOUSE_EVENTS,
1132 SDL_PenMouseEventsChanged, mouse);
1133
1134 SDL_RemoveHintCallback(SDL_HINT_PEN_TOUCH_EVENTS,
1135 SDL_PenTouchEventsChanged, mouse);
1136
1137 SDL_RemoveHintCallback(SDL_HINT_MOUSE_AUTO_CAPTURE,
1138 SDL_MouseAutoCaptureChanged, mouse);
1139
1140 SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_WARP_MOTION,
1141 SDL_MouseRelativeWarpMotionChanged, mouse);
1142
1143 SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
1144 SDL_MouseRelativeCursorVisibleChanged, mouse);
1145
1146 SDL_RemoveHintCallback("SDL_MOUSE_INTEGER_MODE",
1147 SDL_MouseIntegerModeChanged, mouse);
1148
1149 for (int i = SDL_mouse_count; i--; ) {
1150 SDL_RemoveMouse(SDL_mice[i].instance_id, false);
1151 }
1152 SDL_free(SDL_mice);
1153 SDL_mice = NULL;
1154}
1155
1156bool SDL_SetRelativeMouseTransform(SDL_MouseMotionTransformCallback transform, void *userdata)
1157{
1158 SDL_Mouse *mouse = SDL_GetMouse();
1159 if (mouse->relative_mode) {
1160 return SDL_SetError("Can't set mouse transform while relative mode is active");
1161 }
1162 mouse->InputTransform = transform;
1163 mouse->input_transform_data = userdata;
1164 return true;
1165}
1166
1167SDL_MouseButtonFlags SDL_GetMouseState(float *x, float *y)
1168{
1169 SDL_Mouse *mouse = SDL_GetMouse();
1170
1171 if (x) {
1172 *x = mouse->x;
1173 }
1174 if (y) {
1175 *y = mouse->y;
1176 }
1177 return SDL_GetMouseButtonState(mouse, SDL_GLOBAL_MOUSE_ID, true);
1178}
1179
1180SDL_MouseButtonFlags SDL_GetRelativeMouseState(float *x, float *y)
1181{
1182 SDL_Mouse *mouse = SDL_GetMouse();
1183
1184 if (x) {
1185 *x = mouse->x_accu;
1186 }
1187 if (y) {
1188 *y = mouse->y_accu;
1189 }
1190 mouse->x_accu = 0.0f;
1191 mouse->y_accu = 0.0f;
1192 return SDL_GetMouseButtonState(mouse, SDL_GLOBAL_MOUSE_ID, true);
1193}
1194
1195SDL_MouseButtonFlags SDL_GetGlobalMouseState(float *x, float *y)
1196{
1197 SDL_Mouse *mouse = SDL_GetMouse();
1198
1199 if (mouse->GetGlobalMouseState) {
1200 float tmpx, tmpy;
1201
1202 // make sure these are never NULL for the backend implementations...
1203 if (!x) {
1204 x = &tmpx;
1205 }
1206 if (!y) {
1207 y = &tmpy;
1208 }
1209
1210 *x = *y = 0.0f;
1211
1212 return mouse->GetGlobalMouseState(x, y);
1213 } else {
1214 return SDL_GetMouseState(x, y);
1215 }
1216}
1217
1218void SDL_PerformWarpMouseInWindow(SDL_Window *window, float x, float y, bool ignore_relative_mode)
1219{
1220 SDL_Mouse *mouse = SDL_GetMouse();
1221
1222 if (!window) {
1223 window = mouse->focus;
1224 }
1225
1226 if (!window) {
1227 return;
1228 }
1229
1230 if ((window->flags & SDL_WINDOW_MINIMIZED) == SDL_WINDOW_MINIMIZED) {
1231 return;
1232 }
1233
1234 // Ignore the previous position when we warp
1235 mouse->last_x = x;
1236 mouse->last_y = y;
1237 mouse->has_position = false;
1238
1239 if (mouse->relative_mode && !ignore_relative_mode) {
1240 /* 2.0.22 made warping in relative mode actually functional, which
1241 * surprised many applications that weren't expecting the additional
1242 * mouse motion.
1243 *
1244 * So for now, warping in relative mode adjusts the absolution position
1245 * but doesn't generate motion events, unless SDL_HINT_MOUSE_RELATIVE_WARP_MOTION is set.
1246 */
1247 if (!mouse->relative_mode_warp_motion) {
1248 mouse->x = x;
1249 mouse->y = y;
1250 mouse->has_position = true;
1251 return;
1252 }
1253 }
1254
1255 if (mouse->WarpMouse && !mouse->relative_mode) {
1256 mouse->WarpMouse(window, x, y);
1257 } else {
1258 SDL_PrivateSendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, x, y);
1259 }
1260}
1261
1262void SDL_DisableMouseWarpEmulation(void)
1263{
1264 SDL_Mouse *mouse = SDL_GetMouse();
1265
1266 if (mouse->warp_emulation_active) {
1267 SDL_SetRelativeMouseMode(false);
1268 }
1269
1270 mouse->warp_emulation_prohibited = true;
1271}
1272
1273static void SDL_MaybeEnableWarpEmulation(SDL_Window *window, float x, float y)
1274{
1275 SDL_Mouse *mouse = SDL_GetMouse();
1276
1277 if (!mouse->warp_emulation_prohibited && mouse->warp_emulation_hint && !mouse->cursor_shown && !mouse->warp_emulation_active) {
1278 if (!window) {
1279 window = mouse->focus;
1280 }
1281
1282 if (window) {
1283 const float cx = window->w / 2.f;
1284 const float cy = window->h / 2.f;
1285 if (x >= SDL_floorf(cx) && x <= SDL_ceilf(cx) &&
1286 y >= SDL_floorf(cy) && y <= SDL_ceilf(cy)) {
1287
1288 // Require two consecutive warps to the center within a certain timespan to enter warp emulation mode.
1289 const Uint64 now = SDL_GetTicksNS();
1290 if (now - mouse->last_center_warp_time_ns < WARP_EMULATION_THRESHOLD_NS) {
1291 if (SDL_SetRelativeMouseMode(true)) {
1292 mouse->warp_emulation_active = true;
1293 }
1294 }
1295
1296 mouse->last_center_warp_time_ns = now;
1297 return;
1298 }
1299 }
1300
1301 mouse->last_center_warp_time_ns = 0;
1302 }
1303}
1304
1305void SDL_WarpMouseInWindow(SDL_Window *window, float x, float y)
1306{
1307 SDL_Mouse *mouse = SDL_GetMouse();
1308 SDL_MaybeEnableWarpEmulation(window, x, y);
1309
1310 SDL_PerformWarpMouseInWindow(window, x, y, mouse->warp_emulation_active);
1311}
1312
1313bool SDL_WarpMouseGlobal(float x, float y)
1314{
1315 SDL_Mouse *mouse = SDL_GetMouse();
1316
1317 if (mouse->WarpMouseGlobal) {
1318 return mouse->WarpMouseGlobal(x, y);
1319 }
1320
1321 return SDL_Unsupported();
1322}
1323
1324bool SDL_SetRelativeMouseMode(bool enabled)
1325{
1326 SDL_Mouse *mouse = SDL_GetMouse();
1327 SDL_Window *focusWindow = SDL_GetKeyboardFocus();
1328
1329 if (!enabled) {
1330 // If warps were being emulated, reset the flag.
1331 mouse->warp_emulation_active = false;
1332 }
1333
1334 if (enabled == mouse->relative_mode) {
1335 return true;
1336 }
1337
1338 // Set the relative mode
1339 if (!mouse->SetRelativeMouseMode || !mouse->SetRelativeMouseMode(enabled)) {
1340 if (enabled) {
1341 return SDL_SetError("No relative mode implementation available");
1342 }
1343 }
1344 mouse->relative_mode = enabled;
1345
1346 if (enabled) {
1347 // Update cursor visibility before we potentially warp the mouse
1348 SDL_SetCursor(NULL);
1349 }
1350
1351 if (enabled && focusWindow) {
1352 SDL_SetMouseFocus(focusWindow);
1353 }
1354
1355 if (focusWindow) {
1356 SDL_UpdateWindowGrab(focusWindow);
1357
1358 // Put the cursor back to where the application expects it
1359 if (!enabled) {
1360 SDL_PerformWarpMouseInWindow(focusWindow, mouse->x, mouse->y, true);
1361 }
1362
1363 SDL_UpdateMouseCapture(false);
1364 }
1365
1366 if (!enabled) {
1367 // Update cursor visibility after we restore the mouse position
1368 SDL_SetCursor(NULL);
1369 }
1370
1371 // Flush pending mouse motion - ideally we would pump events, but that's not always safe
1372 SDL_FlushEvent(SDL_EVENT_MOUSE_MOTION);
1373
1374 return true;
1375}
1376
1377bool SDL_GetRelativeMouseMode(void)
1378{
1379 SDL_Mouse *mouse = SDL_GetMouse();
1380
1381 return mouse->relative_mode;
1382}
1383
1384void SDL_UpdateRelativeMouseMode(void)
1385{
1386 SDL_Mouse *mouse = SDL_GetMouse();
1387 SDL_Window *focus = SDL_GetKeyboardFocus();
1388 bool relative_mode = (focus && (focus->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE));
1389
1390 if (relative_mode != mouse->relative_mode) {
1391 SDL_SetRelativeMouseMode(relative_mode);
1392 }
1393}
1394
1395bool SDL_UpdateMouseCapture(bool force_release)
1396{
1397 SDL_Mouse *mouse = SDL_GetMouse();
1398 SDL_Window *capture_window = NULL;
1399
1400 if (!mouse->CaptureMouse) {
1401 return true;
1402 }
1403
1404 if (!force_release) {
1405 if (SDL_GetMessageBoxCount() == 0 &&
1406 (mouse->capture_desired || (mouse->auto_capture && SDL_GetMouseButtonState(mouse, SDL_GLOBAL_MOUSE_ID, false) != 0))) {
1407 if (!mouse->relative_mode) {
1408 capture_window = mouse->focus;
1409 }
1410 }
1411 }
1412
1413 if (capture_window != mouse->capture_window) {
1414 /* We can get here recursively on Windows, so make sure we complete
1415 * all of the window state operations before we change the capture state
1416 * (e.g. https://github.com/libsdl-org/SDL/pull/5608)
1417 */
1418 SDL_Window *previous_capture = mouse->capture_window;
1419
1420 if (previous_capture) {
1421 previous_capture->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
1422 }
1423
1424 if (capture_window) {
1425 capture_window->flags |= SDL_WINDOW_MOUSE_CAPTURE;
1426 }
1427
1428 mouse->capture_window = capture_window;
1429
1430 if (!mouse->CaptureMouse(capture_window)) {
1431 // CaptureMouse() will have set an error, just restore the state
1432 if (previous_capture) {
1433 previous_capture->flags |= SDL_WINDOW_MOUSE_CAPTURE;
1434 }
1435 if (capture_window) {
1436 capture_window->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
1437 }
1438 mouse->capture_window = previous_capture;
1439
1440 return false;
1441 }
1442 }
1443 return true;
1444}
1445
1446bool SDL_CaptureMouse(bool enabled)
1447{
1448 SDL_Mouse *mouse = SDL_GetMouse();
1449
1450 if (!mouse->CaptureMouse) {
1451 return SDL_Unsupported();
1452 }
1453
1454#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)
1455 /* Windows mouse capture is tied to the current thread, and must be called
1456 * from the thread that created the window being captured. Since we update
1457 * the mouse capture state from the event processing, any application state
1458 * changes must be processed on that thread as well.
1459 */
1460 if (!SDL_OnVideoThread()) {
1461 return SDL_SetError("SDL_CaptureMouse() must be called on the main thread");
1462 }
1463#endif // defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)
1464
1465 if (enabled && SDL_GetKeyboardFocus() == NULL) {
1466 return SDL_SetError("No window has focus");
1467 }
1468 mouse->capture_desired = enabled;
1469
1470 return SDL_UpdateMouseCapture(false);
1471}
1472
1473SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h, int hot_x, int hot_y)
1474{
1475 SDL_Surface *surface;
1476 SDL_Cursor *cursor;
1477 int x, y;
1478 Uint32 *pixel;
1479 Uint8 datab = 0, maskb = 0;
1480 const Uint32 black = 0xFF000000;
1481 const Uint32 white = 0xFFFFFFFF;
1482 const Uint32 transparent = 0x00000000;
1483#if defined(SDL_PLATFORM_WIN32)
1484 // Only Windows backend supports inverted pixels in mono cursors.
1485 const Uint32 inverted = 0x00FFFFFF;
1486#else
1487 const Uint32 inverted = 0xFF000000;
1488#endif // defined(SDL_PLATFORM_WIN32)
1489
1490 // Make sure the width is a multiple of 8
1491 w = ((w + 7) & ~7);
1492
1493 // Create the surface from a bitmap
1494 surface = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_ARGB8888);
1495 if (!surface) {
1496 return NULL;
1497 }
1498 for (y = 0; y < h; ++y) {
1499 pixel = (Uint32 *)((Uint8 *)surface->pixels + y * surface->pitch);
1500 for (x = 0; x < w; ++x) {
1501 if ((x % 8) == 0) {
1502 datab = *data++;
1503 maskb = *mask++;
1504 }
1505 if (maskb & 0x80) {
1506 *pixel++ = (datab & 0x80) ? black : white;
1507 } else {
1508 *pixel++ = (datab & 0x80) ? inverted : transparent;
1509 }
1510 datab <<= 1;
1511 maskb <<= 1;
1512 }
1513 }
1514
1515 cursor = SDL_CreateColorCursor(surface, hot_x, hot_y);
1516
1517 SDL_DestroySurface(surface);
1518
1519 return cursor;
1520}
1521
1522SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y)
1523{
1524 SDL_Mouse *mouse = SDL_GetMouse();
1525 SDL_Surface *temp = NULL;
1526 SDL_Cursor *cursor;
1527
1528 if (!surface) {
1529 SDL_InvalidParamError("surface");
1530 return NULL;
1531 }
1532
1533 // Allow specifying the hot spot via properties on the surface
1534 SDL_PropertiesID props = SDL_GetSurfaceProperties(surface);
1535 hot_x = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, hot_x);
1536 hot_y = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, hot_y);
1537
1538 // Sanity check the hot spot
1539 if ((hot_x < 0) || (hot_y < 0) ||
1540 (hot_x >= surface->w) || (hot_y >= surface->h)) {
1541 SDL_SetError("Cursor hot spot doesn't lie within cursor");
1542 return NULL;
1543 }
1544
1545 if (surface->format != SDL_PIXELFORMAT_ARGB8888) {
1546 temp = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
1547 if (!temp) {
1548 return NULL;
1549 }
1550 surface = temp;
1551 }
1552
1553 if (mouse->CreateCursor) {
1554 cursor = mouse->CreateCursor(surface, hot_x, hot_y);
1555 } else {
1556 cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
1557 }
1558 if (cursor) {
1559 cursor->next = mouse->cursors;
1560 mouse->cursors = cursor;
1561 }
1562
1563 SDL_DestroySurface(temp);
1564
1565 return cursor;
1566}
1567
1568SDL_Cursor *SDL_CreateSystemCursor(SDL_SystemCursor id)
1569{
1570 SDL_Mouse *mouse = SDL_GetMouse();
1571 SDL_Cursor *cursor;
1572
1573 if (!mouse->CreateSystemCursor) {
1574 SDL_SetError("CreateSystemCursor is not currently supported");
1575 return NULL;
1576 }
1577
1578 cursor = mouse->CreateSystemCursor(id);
1579 if (cursor) {
1580 cursor->next = mouse->cursors;
1581 mouse->cursors = cursor;
1582 }
1583
1584 return cursor;
1585}
1586
1587/* SDL_SetCursor(NULL) can be used to force the cursor redraw,
1588 if this is desired for any reason. This is used when setting
1589 the video mode and when the SDL window gains the mouse focus.
1590 */
1591bool SDL_SetCursor(SDL_Cursor *cursor)
1592{
1593 SDL_Mouse *mouse = SDL_GetMouse();
1594
1595 // Return immediately if setting the cursor to the currently set one (fixes #7151)
1596 if (cursor == mouse->cur_cursor) {
1597 return true;
1598 }
1599
1600 // Set the new cursor
1601 if (cursor) {
1602 // Make sure the cursor is still valid for this mouse
1603 if (cursor != mouse->def_cursor) {
1604 SDL_Cursor *found;
1605 for (found = mouse->cursors; found; found = found->next) {
1606 if (found == cursor) {
1607 break;
1608 }
1609 }
1610 if (!found) {
1611 return SDL_SetError("Cursor not associated with the current mouse");
1612 }
1613 }
1614 mouse->cur_cursor = cursor;
1615 } else {
1616 if (mouse->focus) {
1617 cursor = mouse->cur_cursor;
1618 } else {
1619 cursor = mouse->def_cursor;
1620 }
1621 }
1622
1623 if (cursor && (!mouse->focus || (mouse->cursor_shown && (!mouse->relative_mode || mouse->relative_mode_cursor_visible)))) {
1624 if (mouse->ShowCursor) {
1625 mouse->ShowCursor(cursor);
1626 }
1627 } else {
1628 if (mouse->ShowCursor) {
1629 mouse->ShowCursor(NULL);
1630 }
1631 }
1632 return true;
1633}
1634
1635SDL_Cursor *SDL_GetCursor(void)
1636{
1637 SDL_Mouse *mouse = SDL_GetMouse();
1638
1639 if (!mouse) {
1640 return NULL;
1641 }
1642 return mouse->cur_cursor;
1643}
1644
1645SDL_Cursor *SDL_GetDefaultCursor(void)
1646{
1647 SDL_Mouse *mouse = SDL_GetMouse();
1648
1649 if (!mouse) {
1650 return NULL;
1651 }
1652 return mouse->def_cursor;
1653}
1654
1655void SDL_DestroyCursor(SDL_Cursor *cursor)
1656{
1657 SDL_Mouse *mouse = SDL_GetMouse();
1658 SDL_Cursor *curr, *prev;
1659
1660 if (!cursor) {
1661 return;
1662 }
1663
1664 if (cursor == mouse->def_cursor) {
1665 return;
1666 }
1667 if (cursor == mouse->cur_cursor) {
1668 SDL_SetCursor(mouse->def_cursor);
1669 }
1670
1671 for (prev = NULL, curr = mouse->cursors; curr;
1672 prev = curr, curr = curr->next) {
1673 if (curr == cursor) {
1674 if (prev) {
1675 prev->next = curr->next;
1676 } else {
1677 mouse->cursors = curr->next;
1678 }
1679
1680 if (mouse->FreeCursor && curr->internal) {
1681 mouse->FreeCursor(curr);
1682 } else {
1683 SDL_free(curr);
1684 }
1685 return;
1686 }
1687 }
1688}
1689
1690bool SDL_ShowCursor(void)
1691{
1692 SDL_Mouse *mouse = SDL_GetMouse();
1693
1694 if (mouse->warp_emulation_active) {
1695 SDL_SetRelativeMouseMode(false);
1696 mouse->warp_emulation_active = false;
1697 }
1698
1699 if (!mouse->cursor_shown) {
1700 mouse->cursor_shown = true;
1701 SDL_SetCursor(NULL);
1702 }
1703 return true;
1704}
1705
1706bool SDL_HideCursor(void)
1707{
1708 SDL_Mouse *mouse = SDL_GetMouse();
1709
1710 if (mouse->cursor_shown) {
1711 mouse->cursor_shown = false;
1712 SDL_SetCursor(NULL);
1713 }
1714 return true;
1715}
1716
1717bool SDL_CursorVisible(void)
1718{
1719 SDL_Mouse *mouse = SDL_GetMouse();
1720
1721 return mouse->cursor_shown;
1722}
1723