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