summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m
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/video/uikit/SDL_uikitevents.m
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m')
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m461
1 files changed, 461 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m
new file mode 100644
index 0000000..86224f7
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m
@@ -0,0 +1,461 @@
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#ifdef SDL_VIDEO_DRIVER_UIKIT
24
25#include "../../events/SDL_events_c.h"
26#include "../../main/SDL_main_callbacks.h"
27
28#include "SDL_uikitevents.h"
29#include "SDL_uikitopengles.h"
30#include "SDL_uikitvideo.h"
31#include "SDL_uikitwindow.h"
32
33#import <Foundation/Foundation.h>
34#import <GameController/GameController.h>
35
36static BOOL UIKit_EventPumpEnabled = YES;
37
38@interface SDL_LifecycleObserver : NSObject
39@property(nonatomic, assign) BOOL isObservingNotifications;
40@end
41
42@implementation SDL_LifecycleObserver
43
44- (void)update
45{
46 NSNotificationCenter *notificationCenter = NSNotificationCenter.defaultCenter;
47 bool wants_observation = (UIKit_EventPumpEnabled || SDL_HasMainCallbacks());
48 if (!wants_observation) {
49 // Make sure no windows have active animation callbacks
50 int num_windows = 0;
51 SDL_free(SDL_GetWindows(&num_windows));
52 if (num_windows > 0) {
53 wants_observation = true;
54 }
55 }
56 if (wants_observation && !self.isObservingNotifications) {
57 self.isObservingNotifications = YES;
58 [notificationCenter addObserver:self selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
59 [notificationCenter addObserver:self selector:@selector(applicationWillResignActive) name:UIApplicationWillResignActiveNotification object:nil];
60 [notificationCenter addObserver:self selector:@selector(applicationDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
61 [notificationCenter addObserver:self selector:@selector(applicationWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
62 [notificationCenter addObserver:self selector:@selector(applicationWillTerminate) name:UIApplicationWillTerminateNotification object:nil];
63 [notificationCenter addObserver:self selector:@selector(applicationDidReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
64#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
65 [notificationCenter addObserver:self
66 selector:@selector(applicationDidChangeStatusBarOrientation)
67 name:UIApplicationDidChangeStatusBarOrientationNotification
68 object:nil];
69#endif
70 } else if (!wants_observation && self.isObservingNotifications) {
71 self.isObservingNotifications = NO;
72 [notificationCenter removeObserver:self];
73 }
74}
75
76- (void)applicationDidBecomeActive
77{
78 SDL_OnApplicationDidEnterForeground();
79}
80
81- (void)applicationWillResignActive
82{
83 SDL_OnApplicationWillEnterBackground();
84}
85
86- (void)applicationDidEnterBackground
87{
88 SDL_OnApplicationDidEnterBackground();
89}
90
91- (void)applicationWillEnterForeground
92{
93 SDL_OnApplicationWillEnterForeground();
94}
95
96- (void)applicationWillTerminate
97{
98 SDL_OnApplicationWillTerminate();
99}
100
101- (void)applicationDidReceiveMemoryWarning
102{
103 SDL_OnApplicationDidReceiveMemoryWarning();
104}
105
106#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
107- (void)applicationDidChangeStatusBarOrientation
108{
109 SDL_OnApplicationDidChangeStatusBarOrientation();
110}
111#endif
112
113@end
114
115void SDL_UpdateLifecycleObserver(void)
116{
117 static SDL_LifecycleObserver *lifecycleObserver;
118 static dispatch_once_t onceToken;
119 dispatch_once(&onceToken, ^{
120 lifecycleObserver = [SDL_LifecycleObserver new];
121 });
122 [lifecycleObserver update];
123}
124
125void SDL_SetiOSEventPump(bool enabled)
126{
127 UIKit_EventPumpEnabled = enabled;
128
129 SDL_UpdateLifecycleObserver();
130}
131
132Uint64 UIKit_GetEventTimestamp(NSTimeInterval nsTimestamp)
133{
134 static Uint64 timestamp_offset;
135 Uint64 timestamp = (Uint64)(nsTimestamp * SDL_NS_PER_SECOND);
136 Uint64 now = SDL_GetTicksNS();
137
138 if (!timestamp_offset) {
139 timestamp_offset = (now - timestamp);
140 }
141 timestamp += timestamp_offset;
142
143 if (timestamp > now) {
144 timestamp_offset -= (timestamp - now);
145 timestamp = now;
146 }
147 return timestamp;
148}
149
150void UIKit_PumpEvents(SDL_VideoDevice *_this)
151{
152 if (!UIKit_EventPumpEnabled) {
153 return;
154 }
155
156 /* Let the run loop run for a short amount of time: long enough for
157 touch events to get processed (which is important to get certain
158 elements of Game Center's GKLeaderboardViewController to respond
159 to touch input), but not long enough to introduce a significant
160 delay in the rest of the app.
161 */
162 const CFTimeInterval seconds = 0.000002;
163
164 // Pump most event types.
165 SInt32 result;
166 do {
167 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, TRUE);
168 } while (result == kCFRunLoopRunHandledSource);
169
170 // Make sure UIScrollView objects scroll properly.
171 do {
172 result = CFRunLoopRunInMode((CFStringRef)UITrackingRunLoopMode, seconds, TRUE);
173 } while (result == kCFRunLoopRunHandledSource);
174
175 // See the comment in the function definition.
176#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
177 UIKit_GL_RestoreCurrentContext();
178#endif
179}
180
181static id keyboard_connect_observer = nil;
182static id keyboard_disconnect_observer = nil;
183
184static void OnGCKeyboardConnected(GCKeyboard *keyboard) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
185{
186 SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)keyboard;
187
188 SDL_AddKeyboard(keyboardID, NULL, true);
189
190 keyboard.keyboardInput.keyChangedHandler = ^(GCKeyboardInput *kbrd, GCControllerButtonInput *key, GCKeyCode keyCode, BOOL pressed) {
191 Uint64 timestamp = SDL_GetTicksNS();
192 SDL_SendKeyboardKey(timestamp, keyboardID, 0, (SDL_Scancode)keyCode, pressed);
193 };
194
195 dispatch_queue_t queue = dispatch_queue_create("org.libsdl.input.keyboard", DISPATCH_QUEUE_SERIAL);
196 dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
197 keyboard.handlerQueue = queue;
198}
199
200static void OnGCKeyboardDisconnected(GCKeyboard *keyboard) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
201{
202 SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)keyboard;
203
204 SDL_RemoveKeyboard(keyboardID, true);
205
206 keyboard.keyboardInput.keyChangedHandler = nil;
207}
208
209void SDL_InitGCKeyboard(void)
210{
211 @autoreleasepool {
212 if (@available(iOS 14.0, tvOS 14.0, *)) {
213 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
214
215 keyboard_connect_observer = [center addObserverForName:GCKeyboardDidConnectNotification
216 object:nil
217 queue:nil
218 usingBlock:^(NSNotification *note) {
219 GCKeyboard *keyboard = note.object;
220 OnGCKeyboardConnected(keyboard);
221 }];
222
223 keyboard_disconnect_observer = [center addObserverForName:GCKeyboardDidDisconnectNotification
224 object:nil
225 queue:nil
226 usingBlock:^(NSNotification *note) {
227 GCKeyboard *keyboard = note.object;
228 OnGCKeyboardDisconnected(keyboard);
229 }];
230
231 if (GCKeyboard.coalescedKeyboard != nil) {
232 OnGCKeyboardConnected(GCKeyboard.coalescedKeyboard);
233 }
234 }
235 }
236}
237
238void SDL_QuitGCKeyboard(void)
239{
240 @autoreleasepool {
241 if (@available(iOS 14.0, tvOS 14.0, *)) {
242 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
243
244 if (keyboard_connect_observer) {
245 [center removeObserver:keyboard_connect_observer name:GCKeyboardDidConnectNotification object:nil];
246 keyboard_connect_observer = nil;
247 }
248
249 if (keyboard_disconnect_observer) {
250 [center removeObserver:keyboard_disconnect_observer name:GCKeyboardDidDisconnectNotification object:nil];
251 keyboard_disconnect_observer = nil;
252 }
253
254 if (GCKeyboard.coalescedKeyboard != nil) {
255 OnGCKeyboardDisconnected(GCKeyboard.coalescedKeyboard);
256 }
257 }
258 }
259}
260
261static id mouse_connect_observer = nil;
262static id mouse_disconnect_observer = nil;
263static bool mouse_relative_mode = false;
264static SDL_MouseWheelDirection mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL;
265
266static void UpdateScrollDirection(void)
267{
268#if 0 // This code doesn't work for some reason
269 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
270 if ([userDefaults boolForKey:@"com.apple.swipescrolldirection"]) {
271 mouse_scroll_direction = SDL_MOUSEWHEEL_FLIPPED;
272 } else {
273 mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL;
274 }
275#else
276 Boolean keyExistsAndHasValidFormat = NO;
277 Boolean naturalScrollDirection = CFPreferencesGetAppBooleanValue(CFSTR("com.apple.swipescrolldirection"), kCFPreferencesAnyApplication, &keyExistsAndHasValidFormat);
278 if (!keyExistsAndHasValidFormat) {
279 // Couldn't read the preference, assume natural scrolling direction
280 naturalScrollDirection = YES;
281 }
282 if (naturalScrollDirection) {
283 mouse_scroll_direction = SDL_MOUSEWHEEL_FLIPPED;
284 } else {
285 mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL;
286 }
287#endif
288}
289
290static void UpdatePointerLock(void)
291{
292 SDL_VideoDevice *_this = SDL_GetVideoDevice();
293 SDL_Window *window;
294
295 for (window = _this->windows; window != NULL; window = window->next) {
296 UIKit_UpdatePointerLock(_this, window);
297 }
298}
299
300static bool SetGCMouseRelativeMode(bool enabled)
301{
302 mouse_relative_mode = enabled;
303 UpdatePointerLock();
304 return true;
305}
306
307static void OnGCMouseButtonChanged(SDL_MouseID mouseID, Uint8 button, BOOL pressed)
308{
309 Uint64 timestamp = SDL_GetTicksNS();
310 SDL_SendMouseButton(timestamp, SDL_GetMouseFocus(), mouseID, button, pressed);
311}
312
313static void OnGCMouseConnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
314{
315 SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)mouse;
316
317 SDL_AddMouse(mouseID, NULL, true);
318
319 mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
320 OnGCMouseButtonChanged(mouseID, SDL_BUTTON_LEFT, pressed);
321 };
322 mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
323 OnGCMouseButtonChanged(mouseID, SDL_BUTTON_MIDDLE, pressed);
324 };
325 mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
326 OnGCMouseButtonChanged(mouseID, SDL_BUTTON_RIGHT, pressed);
327 };
328
329 int auxiliary_button = SDL_BUTTON_X1;
330 for (GCControllerButtonInput *btn in mouse.mouseInput.auxiliaryButtons) {
331 btn.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
332 OnGCMouseButtonChanged(mouseID, auxiliary_button, pressed);
333 };
334 ++auxiliary_button;
335 }
336
337 mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput *mouseInput, float deltaX, float deltaY) {
338 Uint64 timestamp = SDL_GetTicksNS();
339
340 if (SDL_GCMouseRelativeMode()) {
341 SDL_SendMouseMotion(timestamp, SDL_GetMouseFocus(), mouseID, true, deltaX, -deltaY);
342 }
343 };
344
345 mouse.mouseInput.scroll.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
346 Uint64 timestamp = SDL_GetTicksNS();
347
348 /* Raw scroll values come in here, vertical values in the first axis, horizontal values in the second axis.
349 * The vertical values are negative moving the mouse wheel up and positive moving it down.
350 * The horizontal values are negative moving the mouse wheel left and positive moving it right.
351 * The vertical values are inverted compared to SDL, and the horizontal values are as expected.
352 */
353 float vertical = -xValue;
354 float horizontal = yValue;
355
356 if (mouse_scroll_direction == SDL_MOUSEWHEEL_FLIPPED) {
357 // Since these are raw values, we need to flip them ourselves
358 vertical = -vertical;
359 horizontal = -horizontal;
360 }
361 SDL_SendMouseWheel(timestamp, SDL_GetMouseFocus(), mouseID, horizontal, vertical, mouse_scroll_direction);
362 };
363 UpdateScrollDirection();
364
365 dispatch_queue_t queue = dispatch_queue_create("org.libsdl.input.mouse", DISPATCH_QUEUE_SERIAL);
366 dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
367 mouse.handlerQueue = queue;
368
369 UpdatePointerLock();
370}
371
372static void OnGCMouseDisconnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
373{
374 SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)mouse;
375
376 mouse.mouseInput.mouseMovedHandler = nil;
377
378 mouse.mouseInput.leftButton.pressedChangedHandler = nil;
379 mouse.mouseInput.middleButton.pressedChangedHandler = nil;
380 mouse.mouseInput.rightButton.pressedChangedHandler = nil;
381
382 for (GCControllerButtonInput *button in mouse.mouseInput.auxiliaryButtons) {
383 button.pressedChangedHandler = nil;
384 }
385
386 UpdatePointerLock();
387
388 SDL_RemoveMouse(mouseID, true);
389}
390
391void SDL_InitGCMouse(void)
392{
393 @autoreleasepool {
394 // There is a bug where mouse accumulates duplicate deltas over time in iOS 14.0
395 if (@available(iOS 14.1, tvOS 14.1, *)) {
396 /* iOS will not send the new pointer touch events if you don't have this key,
397 * and we need them to differentiate between mouse events and real touch events.
398 */
399 BOOL indirect_input_available = [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"UIApplicationSupportsIndirectInputEvents"] boolValue];
400 if (indirect_input_available) {
401 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
402
403 mouse_connect_observer = [center addObserverForName:GCMouseDidConnectNotification
404 object:nil
405 queue:nil
406 usingBlock:^(NSNotification *note) {
407 GCMouse *mouse = note.object;
408 OnGCMouseConnected(mouse);
409 }];
410
411 mouse_disconnect_observer = [center addObserverForName:GCMouseDidDisconnectNotification
412 object:nil
413 queue:nil
414 usingBlock:^(NSNotification *note) {
415 GCMouse *mouse = note.object;
416 OnGCMouseDisconnected(mouse);
417 }];
418
419 for (GCMouse *mouse in [GCMouse mice]) {
420 OnGCMouseConnected(mouse);
421 }
422
423 SDL_GetMouse()->SetRelativeMouseMode = SetGCMouseRelativeMode;
424 } else {
425 NSLog(@"You need UIApplicationSupportsIndirectInputEvents in your Info.plist for mouse support");
426 }
427 }
428 }
429}
430
431bool SDL_GCMouseRelativeMode(void)
432{
433 return mouse_relative_mode;
434}
435
436void SDL_QuitGCMouse(void)
437{
438 @autoreleasepool {
439 if (@available(iOS 14.1, tvOS 14.1, *)) {
440 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
441
442 if (mouse_connect_observer) {
443 [center removeObserver:mouse_connect_observer name:GCMouseDidConnectNotification object:nil];
444 mouse_connect_observer = nil;
445 }
446
447 if (mouse_disconnect_observer) {
448 [center removeObserver:mouse_disconnect_observer name:GCMouseDidDisconnectNotification object:nil];
449 mouse_disconnect_observer = nil;
450 }
451
452 for (GCMouse *mouse in [GCMouse mice]) {
453 OnGCMouseDisconnected(mouse);
454 }
455
456 SDL_GetMouse()->SetRelativeMouseMode = NULL;
457 }
458 }
459}
460
461#endif // SDL_VIDEO_DRIVER_UIKIT