Atlas - SDL_uikitevents.m

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