Atlas - SDL_uikitevents.m
Home / ext / SDL / src / video / uikit Lines: 1 | Size: 17624 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 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 [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); 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); 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); 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); 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 462[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.