diff options
Diffstat (limited to 'platform')
27 files changed, 899 insertions, 678 deletions
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index 49c77468ed..1dd37dabe5 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -18,7 +18,9 @@ iphone_lib = [ "godot_view.mm", "display_layer.mm", "godot_view_renderer.mm", - "godot_view_gesture_recognizer.m", + "godot_view_gesture_recognizer.mm", + "device_metrics.m", + "native_video_view.m", ] env_ios = env.Clone() diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index 7edbcc4667..40a63d7ad2 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -62,7 +62,7 @@ static ViewController *mainViewController = nil; CGRect windowBounds = [[UIScreen mainScreen] bounds]; // Create a full-screen window - self.window = [[[UIWindow alloc] initWithFrame:windowBounds] autorelease]; + self.window = [[UIWindow alloc] initWithFrame:windowBounds]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; @@ -140,7 +140,6 @@ static ViewController *mainViewController = nil; - (void)dealloc { self.window = nil; - [super dealloc]; } @end diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index 66579c1ad7..5ebabdd3dc 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -129,7 +129,7 @@ def configure(env): detect_darwin_sdk_path("iphone", env) env.Append( CCFLAGS=( - "-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing" + "-fobjc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing" " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits" " -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb" ' "-DIBOutlet=__attribute__((iboutlet))"' @@ -141,7 +141,7 @@ def configure(env): detect_darwin_sdk_path("iphone", env) env.Append( CCFLAGS=( - "-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing" + "-fobjc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing" " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits" " -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=11.0" " -isysroot $IPHONESDK".split() diff --git a/platform/iphone/device_metrics.h b/platform/iphone/device_metrics.h new file mode 100644 index 0000000000..6d0ff49077 --- /dev/null +++ b/platform/iphone/device_metrics.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* device_metrics.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#import <Foundation/Foundation.h> + +@interface GodotDeviceMetrics : NSObject + +@property(nonatomic, class, readonly, strong) NSDictionary<NSArray *, NSNumber *> *dpiList; + +@end diff --git a/platform/iphone/device_metrics.m b/platform/iphone/device_metrics.m new file mode 100644 index 0000000000..747872bc49 --- /dev/null +++ b/platform/iphone/device_metrics.m @@ -0,0 +1,152 @@ +/*************************************************************************/ +/* device_metrics.m */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#import "device_metrics.h" + +@implementation GodotDeviceMetrics + ++ (NSDictionary *)dpiList { + return @{ + @[ + @"iPad1,1", + @"iPad2,1", + @"iPad2,2", + @"iPad2,3", + @"iPad2,4", + ] : @132, + @[ + @"iPhone1,1", + @"iPhone1,2", + @"iPhone2,1", + @"iPad2,5", + @"iPad2,6", + @"iPad2,7", + @"iPod1,1", + @"iPod2,1", + @"iPod3,1", + ] : @163, + @[ + @"iPad3,1", + @"iPad3,2", + @"iPad3,3", + @"iPad3,4", + @"iPad3,5", + @"iPad3,6", + @"iPad4,1", + @"iPad4,2", + @"iPad4,3", + @"iPad5,3", + @"iPad5,4", + @"iPad6,3", + @"iPad6,4", + @"iPad6,7", + @"iPad6,8", + @"iPad6,11", + @"iPad6,12", + @"iPad7,1", + @"iPad7,2", + @"iPad7,3", + @"iPad7,4", + @"iPad7,5", + @"iPad7,6", + @"iPad7,11", + @"iPad7,12", + @"iPad8,1", + @"iPad8,2", + @"iPad8,3", + @"iPad8,4", + @"iPad8,5", + @"iPad8,6", + @"iPad8,7", + @"iPad8,8", + @"iPad8,9", + @"iPad8,10", + @"iPad8,11", + @"iPad8,12", + @"iPad11,3", + @"iPad11,4", + ] : @264, + @[ + @"iPhone3,1", + @"iPhone3,2", + @"iPhone3,3", + @"iPhone4,1", + @"iPhone5,1", + @"iPhone5,2", + @"iPhone5,3", + @"iPhone5,4", + @"iPhone6,1", + @"iPhone6,2", + @"iPhone7,2", + @"iPhone8,1", + @"iPhone8,4", + @"iPhone9,1", + @"iPhone9,3", + @"iPhone10,1", + @"iPhone10,4", + @"iPhone11,8", + @"iPhone12,1", + @"iPhone12,8", + @"iPad4,4", + @"iPad4,5", + @"iPad4,6", + @"iPad4,7", + @"iPad4,8", + @"iPad4,9", + @"iPad5,1", + @"iPad5,2", + @"iPad11,1", + @"iPad11,2", + @"iPod4,1", + @"iPod5,1", + @"iPod7,1", + @"iPod9,1", + ] : @326, + @[ + @"iPhone7,1", + @"iPhone8,2", + @"iPhone9,2", + @"iPhone9,4", + @"iPhone10,2", + @"iPhone10,5", + ] : @401, + @[ + @"iPhone10,3", + @"iPhone10,6", + @"iPhone11,2", + @"iPhone11,4", + @"iPhone11,6", + @"iPhone12,3", + @"iPhone12,5", + ] : @458, + }; +} + +@end diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm index 5ec94fb471..2716538b89 100644 --- a/platform/iphone/display_layer.mm +++ b/platform/iphone/display_layer.mm @@ -124,11 +124,8 @@ } if (context) { - [context release]; context = nil; } - - [super dealloc]; } - (BOOL)createFramebuffer { diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm index eea87cecc9..d456f9cbf6 100644 --- a/platform/iphone/display_server_iphone.mm +++ b/platform/iphone/display_server_iphone.mm @@ -32,8 +32,10 @@ #import "app_delegate.h" #include "core/io/file_access_pack.h" #include "core/project_settings.h" +#import "device_metrics.h" #import "godot_view.h" #include "ios.h" +#import "native_video_view.h" #include "os_iphone.h" #import "view_controller.h" @@ -41,120 +43,6 @@ #import <sys/utsname.h> static const float kDisplayServerIPhoneAcceleration = 1; -static NSDictionary *iOSModelToDPI = @{ - @[ - @"iPad1,1", - @"iPad2,1", - @"iPad2,2", - @"iPad2,3", - @"iPad2,4", - ] : @132, - @[ - @"iPhone1,1", - @"iPhone1,2", - @"iPhone2,1", - @"iPad2,5", - @"iPad2,6", - @"iPad2,7", - @"iPod1,1", - @"iPod2,1", - @"iPod3,1", - ] : @163, - @[ - @"iPad3,1", - @"iPad3,2", - @"iPad3,3", - @"iPad3,4", - @"iPad3,5", - @"iPad3,6", - @"iPad4,1", - @"iPad4,2", - @"iPad4,3", - @"iPad5,3", - @"iPad5,4", - @"iPad6,3", - @"iPad6,4", - @"iPad6,7", - @"iPad6,8", - @"iPad6,11", - @"iPad6,12", - @"iPad7,1", - @"iPad7,2", - @"iPad7,3", - @"iPad7,4", - @"iPad7,5", - @"iPad7,6", - @"iPad7,11", - @"iPad7,12", - @"iPad8,1", - @"iPad8,2", - @"iPad8,3", - @"iPad8,4", - @"iPad8,5", - @"iPad8,6", - @"iPad8,7", - @"iPad8,8", - @"iPad8,9", - @"iPad8,10", - @"iPad8,11", - @"iPad8,12", - @"iPad11,3", - @"iPad11,4", - ] : @264, - @[ - @"iPhone3,1", - @"iPhone3,2", - @"iPhone3,3", - @"iPhone4,1", - @"iPhone5,1", - @"iPhone5,2", - @"iPhone5,3", - @"iPhone5,4", - @"iPhone6,1", - @"iPhone6,2", - @"iPhone7,2", - @"iPhone8,1", - @"iPhone8,4", - @"iPhone9,1", - @"iPhone9,3", - @"iPhone10,1", - @"iPhone10,4", - @"iPhone11,8", - @"iPhone12,1", - @"iPhone12,8", - @"iPad4,4", - @"iPad4,5", - @"iPad4,6", - @"iPad4,7", - @"iPad4,8", - @"iPad4,9", - @"iPad5,1", - @"iPad5,2", - @"iPad11,1", - @"iPad11,2", - @"iPod4,1", - @"iPod5,1", - @"iPod7,1", - @"iPod9,1", - ] : @326, - @[ - @"iPhone7,1", - @"iPhone8,2", - @"iPhone9,2", - @"iPhone9,4", - @"iPhone10,2", - @"iPhone10,5", - ] : @401, - @[ - @"iPhone10,3", - @"iPhone10,6", - @"iPhone11,2", - @"iPhone11,4", - @"iPhone11,6", - @"iPhone12,3", - @"iPhone12,5", - ] : @458, -}; DisplayServerIPhone *DisplayServerIPhone::get_singleton() { return (DisplayServerIPhone *)DisplayServer::get_singleton(); @@ -383,8 +271,7 @@ void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) { Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z)); }; -void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, - float p_z) { +void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, float p_z) { // Found out the Z should not be negated! Pass as is! Vector3 v_accelerometer = Vector3( p_x / kDisplayServerIPhoneAcceleration, @@ -392,39 +279,6 @@ void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, p_z / kDisplayServerIPhoneAcceleration); Input::get_singleton()->set_accelerometer(v_accelerometer); - - /* - if (p_x != last_accel.x) { - //printf("updating accel x %f\n", p_x); - InputEvent ev; - ev.type = InputEvent::JOYPAD_MOTION; - ev.device = 0; - ev.joy_motion.axis = JOY_ANALOG_0; - ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE); - last_accel.x = p_x; - queue_event(ev); - }; - if (p_y != last_accel.y) { - //printf("updating accel y %f\n", p_y); - InputEvent ev; - ev.type = InputEvent::JOYPAD_MOTION; - ev.device = 0; - ev.joy_motion.axis = JOY_ANALOG_1; - ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE); - last_accel.y = p_y; - queue_event(ev); - }; - if (p_z != last_accel.z) { - //printf("updating accel z %f\n", p_z); - InputEvent ev; - ev.type = InputEvent::JOYPAD_MOTION; - ev.device = 0; - ev.joy_motion.axis = JOY_ANALOG_2; - ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE); - last_accel.z = p_z; - queue_event(ev); - }; - */ }; void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) { @@ -516,6 +370,8 @@ int DisplayServerIPhone::screen_get_dpi(int p_screen) const { NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; + NSDictionary *iOSModelToDPI = [GodotDeviceMetrics dpiList]; + for (NSArray *keyArray in iOSModelToDPI) { if ([keyArray containsObject:string]) { NSNumber *value = iOSModelToDPI[keyArray]; @@ -523,7 +379,26 @@ int DisplayServerIPhone::screen_get_dpi(int p_screen) const { } } - return 163; + // If device wasn't found in dictionary + // make a best guess from device metrics. + CGFloat scale = [UIScreen mainScreen].scale; + + UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom; + + switch (idiom) { + case UIUserInterfaceIdiomPad: + return scale == 2 ? 264 : 132; + case UIUserInterfaceIdiomPhone: { + if (scale == 3) { + CGFloat nativeScale = [UIScreen mainScreen].nativeScale; + return nativeScale == 3 ? 458 : 401; + } + + return 326; + } + default: + return 72; + } } float DisplayServerIPhone::screen_get_scale(int p_screen) const { @@ -716,7 +591,7 @@ Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, Stri String file_path = ProjectSettings::get_singleton()->globalize_path(p_path); - NSString *filePath = [[[NSString alloc] initWithUTF8String:file_path.utf8().get_data()] autorelease]; + NSString *filePath = [[NSString alloc] initWithUTF8String:file_path.utf8().get_data()]; NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()]; NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()]; @@ -731,22 +606,22 @@ Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, Stri } bool DisplayServerIPhone::native_video_is_playing() const { - return [AppDelegate.viewController isVideoPlaying]; + return [AppDelegate.viewController.videoView isVideoPlaying]; } void DisplayServerIPhone::native_video_pause() { if (native_video_is_playing()) { - [AppDelegate.viewController pauseVideo]; + [AppDelegate.viewController.videoView pauseVideo]; } } void DisplayServerIPhone::native_video_unpause() { - [AppDelegate.viewController unpauseVideo]; + [AppDelegate.viewController.videoView unpauseVideo]; }; void DisplayServerIPhone::native_video_stop() { if (native_video_is_playing()) { - [AppDelegate.viewController stopVideo]; + [AppDelegate.viewController.videoView stopVideo]; } } diff --git a/platform/iphone/game_center.h b/platform/iphone/game_center.h index 1e9a68fe48..6705674ac6 100644 --- a/platform/iphone/game_center.h +++ b/platform/iphone/game_center.h @@ -48,7 +48,7 @@ class GameCenter : public Object { void return_connect_error(const char *p_error_description); public: - void connect(); + Error authenticate(); bool is_authenticated(); Error post_score(Dictionary p_score); diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm index 4481775c32..0f8c0100c3 100644 --- a/platform/iphone/game_center.mm +++ b/platform/iphone/game_center.mm @@ -52,6 +52,7 @@ extern "C" { GameCenter *GameCenter::instance = NULL; void GameCenter::_bind_methods() { + ClassDB::bind_method(D_METHOD("authenticate"), &GameCenter::authenticate); ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated); ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score); @@ -66,40 +67,28 @@ void GameCenter::_bind_methods() { ClassDB::bind_method(D_METHOD("pop_pending_event"), &GameCenter::pop_pending_event); }; -void GameCenter::return_connect_error(const char *p_error_description) { - authenticated = false; - Dictionary ret; - ret["type"] = "authentication"; - ret["result"] = "error"; - ret["error_code"] = 0; - ret["error_description"] = p_error_description; - pending_events.push_back(ret); -} - -void GameCenter::connect() { +Error GameCenter::authenticate() { //if this class isn't available, game center isn't implemented if ((NSClassFromString(@"GKLocalPlayer")) == nil) { - return_connect_error("GameCenter not available"); - return; + return ERR_UNAVAILABLE; } GKLocalPlayer *player = [GKLocalPlayer localPlayer]; - if (![player respondsToSelector:@selector(authenticateHandler)]) { - return_connect_error("GameCenter doesn't respond to 'authenticateHandler'"); - return; - } + ERR_FAIL_COND_V(![player respondsToSelector:@selector(authenticateHandler)], ERR_UNAVAILABLE); ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController; - if (!root_controller) { - return_connect_error("Window doesn't have root ViewController"); - return; - } + ERR_FAIL_COND_V(!root_controller, FAILED); // This handler is called several times. First when the view needs to be shown, then again // after the view is cancelled or the user logs in. Or if the user's already logged in, it's // called just once to confirm they're authenticated. This is why no result needs to be specified // in the presentViewController phase. In this case, more calls to this function will follow. + _weakify(root_controller); + _weakify(player); player.authenticateHandler = (^(UIViewController *controller, NSError *error) { + _strongify(root_controller); + _strongify(player); + if (controller) { [root_controller presentViewController:controller animated:YES completion:nil]; } else { @@ -126,6 +115,8 @@ void GameCenter::connect() { pending_events.push_back(ret); }; }); + + return OK; }; bool GameCenter::is_authenticated() { @@ -137,8 +128,8 @@ Error GameCenter::post_score(Dictionary p_score) { float score = p_score["score"]; String category = p_score["category"]; - NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease]; - GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease]; + NSString *cat_str = [[NSString alloc] initWithUTF8String:category.utf8().get_data()]; + GKScore *reporter = [[GKScore alloc] initWithLeaderboardIdentifier:cat_str]; reporter.value = score; ERR_FAIL_COND_V([GKScore respondsToSelector:@selector(reportScores)], ERR_UNAVAILABLE); @@ -166,8 +157,8 @@ Error GameCenter::award_achievement(Dictionary p_params) { String name = p_params["name"]; float progress = p_params["progress"]; - NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease]; - GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease]; + NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()]; + GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:name_str]; ERR_FAIL_COND_V(!achievement, FAILED); ERR_FAIL_COND_V([GKAchievement respondsToSelector:@selector(reportAchievements)], ERR_UNAVAILABLE); @@ -311,7 +302,7 @@ Error GameCenter::show_game_center(Dictionary p_params) { } } - GKGameCenterViewController *controller = [[[GKGameCenterViewController alloc] init] autorelease]; + GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init]; ERR_FAIL_COND_V(!controller, FAILED); ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController; @@ -323,7 +314,7 @@ Error GameCenter::show_game_center(Dictionary p_params) { controller.leaderboardIdentifier = nil; if (p_params.has("leaderboard_name")) { String name = p_params["leaderboard_name"]; - NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease]; + NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()]; controller.leaderboardIdentifier = name_str; } } diff --git a/platform/iphone/godot_iphone.mm b/platform/iphone/godot_iphone.mm index 090b772947..6d95276f37 100644 --- a/platform/iphone/godot_iphone.mm +++ b/platform/iphone/godot_iphone.mm @@ -49,10 +49,8 @@ int add_path(int p_argc, char **p_args) { } p_args[p_argc++] = (char *)"--path"; - [str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; p_args[p_argc] = NULL; - [str release]; return p_argc; }; @@ -68,9 +66,7 @@ int add_cmdline(int p_argc, char **p_args) { if (!str) { continue; } - [str retain]; // @todo delete these at some point p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; - [str release]; }; p_args[p_argc] = NULL; diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm index c0a31549c4..3b4344c46d 100644 --- a/platform/iphone/godot_view.mm +++ b/platform/iphone/godot_view.mm @@ -145,8 +145,6 @@ static const int max_touches = 8; if (self.delayGestureRecognizer) { self.delayGestureRecognizer = nil; } - - [super dealloc]; } - (void)godot_commonInit { @@ -156,7 +154,7 @@ static const int max_touches = 8; // Configure and start accelerometer if (!self.motionManager) { - self.motionManager = [[[CMMotionManager alloc] init] autorelease]; + self.motionManager = [[CMMotionManager alloc] init]; if (self.motionManager.deviceMotionAvailable) { self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0; [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical]; @@ -169,7 +167,6 @@ static const int max_touches = 8; GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init]; self.delayGestureRecognizer = gestureRecognizer; [self addGestureRecognizer:self.delayGestureRecognizer]; - [gestureRecognizer release]; } - (void)stopRendering { @@ -204,14 +201,11 @@ static const int max_touches = 8; if (self.useCADisplayLink) { self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)]; - // if (@available(iOS 10, *)) { - self.displayLink.preferredFramesPerSecond = (NSInteger)(1.0 / self.renderingInterval); - // } else { - // // Approximate frame rate - // // assumes device refreshes at 60 fps - // int frameInterval = (int)floor(self.renderingInterval * 60.0f); - // [self.displayLink setFrameInterval:frameInterval]; - // } + // Approximate frame rate + // assumes device refreshes at 60 fps + int displayFPS = (NSInteger)(1.0 / self.renderingInterval); + + self.displayLink.preferredFramesPerSecond = displayFPS; // Setup DisplayLink in main thread [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; diff --git a/platform/iphone/godot_view_gesture_recognizer.h b/platform/iphone/godot_view_gesture_recognizer.h index 8d84914712..1431a9fb89 100644 --- a/platform/iphone/godot_view_gesture_recognizer.h +++ b/platform/iphone/godot_view_gesture_recognizer.h @@ -39,6 +39,8 @@ @interface GodotViewGestureRecognizer : UIGestureRecognizer +@property(nonatomic, readonly, assign) NSTimeInterval delayTimeInterval; + - (instancetype)init; @end diff --git a/platform/iphone/godot_view_gesture_recognizer.m b/platform/iphone/godot_view_gesture_recognizer.mm index 377ccd52a5..71367a629c 100644 --- a/platform/iphone/godot_view_gesture_recognizer.m +++ b/platform/iphone/godot_view_gesture_recognizer.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* godot_view_gesture_recognizer.m */ +/* godot_view_gesture_recognizer.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,8 +30,7 @@ #import "godot_view_gesture_recognizer.h" -// Using same delay interval that is used for `UIScrollView` -const NSTimeInterval kGLGestureDelayInterval = 0.150; +#include "core/project_settings.h" // Minimum distance for touches to move to fire // a delay timer before scheduled time. @@ -41,6 +40,12 @@ const CGFloat kGLGestureMovementDistance = 0.5; @interface GodotViewGestureRecognizer () +@property(nonatomic, readwrite, assign) NSTimeInterval delayTimeInterval; + +@end + +@interface GodotViewGestureRecognizer () + // Timer used to delay begin touch message. // Should work as simple emulation of UIDelayedAction @property(strong, nonatomic) NSTimer *delayTimer; @@ -60,6 +65,8 @@ const CGFloat kGLGestureMovementDistance = 0.5; self.delaysTouchesBegan = YES; self.delaysTouchesEnded = YES; + self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay"); + return self; } @@ -76,8 +83,6 @@ const CGFloat kGLGestureMovementDistance = 0.5; if (self.delayedEvent) { self.delayedEvent = nil; } - - [super dealloc]; } - (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event { @@ -87,7 +92,7 @@ const CGFloat kGLGestureMovementDistance = 0.5; self.delayedEvent = event; self.delayTimer = [NSTimer - scheduledTimerWithTimeInterval:kGLGestureDelayInterval + scheduledTimerWithTimeInterval:self.delayTimeInterval target:self selector:@selector(fireDelayedTouches:) userInfo:nil @@ -109,7 +114,6 @@ const CGFloat kGLGestureMovementDistance = 0.5; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan]; [self delayTouches:cleared andEvent:event]; - [cleared release]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { @@ -130,17 +134,14 @@ const CGFloat kGLGestureMovementDistance = 0.5; if (distance > kGLGestureMovementDistance) { [self.delayTimer fire]; [self.view touchesMoved:cleared withEvent:event]; - [cleared release]; return; } } - [cleared release]; return; } [self.view touchesMoved:cleared withEvent:event]; - [cleared release]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { @@ -148,7 +149,6 @@ const CGFloat kGLGestureMovementDistance = 0.5; NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded]; [self.view touchesEnded:cleared withEvent:event]; - [cleared release]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm index d3086e6cea..3d81349883 100644 --- a/platform/iphone/icloud.mm +++ b/platform/iphone/icloud.mm @@ -150,7 +150,7 @@ Variant nsobject_to_variant(NSObject *object) { NSObject *variant_to_nsobject(Variant v) { if (v.get_type() == Variant::STRING) { - return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease]; + return [[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()]; } else if (v.get_type() == Variant::FLOAT) { return [NSNumber numberWithDouble:(double)v]; } else if (v.get_type() == Variant::INT) { @@ -158,11 +158,11 @@ NSObject *variant_to_nsobject(Variant v) { } else if (v.get_type() == Variant::BOOL) { return [NSNumber numberWithBool:BOOL((bool)v)]; } else if (v.get_type() == Variant::DICTIONARY) { - NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease]; + NSMutableDictionary *result = [[NSMutableDictionary alloc] init]; Dictionary dic = v; Array keys = dic.keys(); for (int i = 0; i < keys.size(); ++i) { - NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease]; + NSString *key = [[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()]; NSObject *value = variant_to_nsobject(dic[keys[i]]); if (key == NULL || value == NULL) { @@ -173,7 +173,7 @@ NSObject *variant_to_nsobject(Variant v) { } return result; } else if (v.get_type() == Variant::ARRAY) { - NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease]; + NSMutableArray *result = [[NSMutableArray alloc] init]; Array arr = v; for (int i = 0; i < arr.size(); ++i) { NSObject *value = variant_to_nsobject(arr[i]); @@ -195,7 +195,7 @@ NSObject *variant_to_nsobject(Variant v) { } Error ICloud::remove_key(String p_param) { - NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease]; + NSString *key = [[NSString alloc] initWithUTF8String:p_param.utf8().get_data()]; NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; @@ -217,7 +217,7 @@ Array ICloud::set_key_values(Dictionary p_params) { String variant_key = keys[i]; Variant variant_value = p_params[variant_key]; - NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease]; + NSString *key = [[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()]; if (key == NULL) { error_keys.push_back(variant_key); continue; @@ -238,7 +238,7 @@ Array ICloud::set_key_values(Dictionary p_params) { } Variant ICloud::get_key_value(String p_param) { - NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease]; + NSString *key = [[NSString alloc] initWithUTF8String:p_param.utf8().get_data()]; NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; if (![[store dictionaryRepresentation] objectForKey:key]) { diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm index 1477f92200..2b973dfbcf 100644 --- a/platform/iphone/in_app_store.mm +++ b/platform/iphone/in_app_store.mm @@ -56,7 +56,6 @@ static NSArray *latestProducts; [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; [numberFormatter setLocale:self.priceLocale]; NSString *formattedString = [numberFormatter stringFromNumber:self.price]; - [numberFormatter release]; return formattedString; } @@ -124,8 +123,6 @@ void InAppStore::_bind_methods() { ret["invalid_ids"] = invalid_ids; InAppStore::get_singleton()->_post_event(ret); - - [request release]; }; @end @@ -136,14 +133,14 @@ Error InAppStore::request_product_info(Dictionary p_params) { PackedStringArray pids = p_params["product_ids"]; printf("************ request product info! %i\n", pids.size()); - NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease]; + NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:pids.size()]; for (int i = 0; i < pids.size(); i++) { printf("******** adding %s to product list\n", pids[i].utf8().get_data()); - NSString *pid = [[[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()] autorelease]; + NSString *pid = [[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()]; [array addObject:pid]; }; - NSSet *products = [[[NSSet alloc] initWithArray:array] autorelease]; + NSSet *products = [[NSSet alloc] initWithArray:array]; SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:products]; ProductsDelegate *delegate = [[ProductsDelegate alloc] init]; @@ -183,30 +180,14 @@ Error InAppStore::restore_purchases() { ret["transaction_id"] = transactionId; NSData *receipt = nil; - int sdk_version = 6; - - if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) { - NSURL *receiptFileURL = nil; - NSBundle *bundle = [NSBundle mainBundle]; - if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) { - // Get the transaction receipt file path location in the app bundle. - receiptFileURL = [bundle appStoreReceiptURL]; - - // Read in the contents of the transaction file. - receipt = [NSData dataWithContentsOfURL:receiptFileURL]; - sdk_version = 7; + int sdk_version = [[[UIDevice currentDevice] systemVersion] intValue]; - } else { - // Fall back to deprecated transaction receipt, - // which is still available in iOS 7. + NSBundle *bundle = [NSBundle mainBundle]; + // Get the transaction receipt file path location in the app bundle. + NSURL *receiptFileURL = [bundle appStoreReceiptURL]; - // Use SKPaymentTransaction's transactionReceipt. - receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]; - } - - } else { - receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]; - } + // Read in the contents of the transaction file. + receipt = [NSData dataWithContentsOfURL:receiptFileURL]; NSString *receipt_to_send = nil; if (receipt != nil) { @@ -265,7 +246,7 @@ Error InAppStore::purchase(Dictionary p_params) { printf("purchasing!\n"); ERR_FAIL_COND_V(!p_params.has("product_id"), ERR_INVALID_PARAMETER); - NSString *pid = [[[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()] autorelease]; + NSString *pid = [[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()]; SKProduct *product = nil; @@ -307,7 +288,7 @@ void InAppStore::_post_event(Variant p_event) { void InAppStore::_record_purchase(String product_id) { String skey = "purchased/" + product_id; - NSString *key = [[[NSString alloc] initWithUTF8String:skey.utf8().get_data()] autorelease]; + NSString *key = [[NSString alloc] initWithUTF8String:skey.utf8().get_data()]; [[NSUserDefaults standardUserDefaults] setBool:YES forKey:key]; [[NSUserDefaults standardUserDefaults] synchronize]; }; diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index 6d7699c0c9..d4e099063f 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -30,6 +30,7 @@ #include "ios.h" #import "app_delegate.h" +#import "view_controller.h" #import <UIKit/UIKit.h> #include <sys/sysctl.h> @@ -68,23 +69,9 @@ String iOS::get_model() const { } String iOS::get_rate_url(int p_app_id) const { - String templ = "itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID"; - String templ_iOS7 = "itms-apps://itunes.apple.com/app/idAPP_ID"; - String templ_iOS8 = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APP_ID&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software"; + String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID"; - //ios7 before - String ret = templ; - - if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 && [[[UIDevice currentDevice] systemVersion] floatValue] < 7.1) { - // iOS 7 needs a different templateReviewURL @see https://github.com/arashpayan/appirater/issues/131 - ret = templ_iOS7; - } else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) { - // iOS 8 needs a different templateReviewURL also @see https://github.com/arashpayan/appirater/issues/182 - ret = templ_iOS8; - } - - // ios7 for everything? - ret = templ_iOS7.replace("APP_ID", String::num(p_app_id)); + String ret = app_url_path.replace("APP_ID", String::num(p_app_id)); printf("returning rate url %s\n", ret.utf8().get_data()); return ret; diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm index 6088f1c25c..b0a8076b56 100644 --- a/platform/iphone/joypad_iphone.mm +++ b/platform/iphone/joypad_iphone.mm @@ -131,8 +131,6 @@ void JoypadIPhone::start_processing() { - (void)dealloc { [self finishObserving]; - - [super dealloc]; } - (int)getJoyIdForController:(GCController *)controller { @@ -251,8 +249,13 @@ void JoypadIPhone::start_processing() { // The extended gamepad profile has all the input you could possibly find on // a gamepad but will only be active if your gamepad actually has all of // these... - controller.extendedGamepad.valueChangedHandler = ^( - GCExtendedGamepad *gamepad, GCControllerElement *element) { + _weakify(self); + _weakify(controller); + + controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) { + _strongify(self); + _strongify(controller); + int joy_id = [self getJoyIdForController:controller]; if (element == gamepad.buttonA) { @@ -304,71 +307,33 @@ void JoypadIPhone::start_processing() { Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx); }; }; + } else if (controller.microGamepad != nil) { + // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad + _weakify(self); + _weakify(controller); + + controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) { + _strongify(self); + _strongify(controller); + + int joy_id = [self getJoyIdForController:controller]; + + if (element == gamepad.buttonA) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, + gamepad.buttonA.isPressed); + } else if (element == gamepad.buttonX) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, + gamepad.buttonX.isPressed); + } else if (element == gamepad.dpad) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, + gamepad.dpad.up.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, + gamepad.dpad.down.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, gamepad.dpad.left.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, gamepad.dpad.right.isPressed); + }; + }; } - // else if (controller.gamepad != nil) { - // // gamepad is the standard profile with 4 buttons, shoulder buttons and a - // // D-pad - // controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, - // GCControllerElement *element) { - // int joy_id = [self getJoyIdForController:controller]; - // - // if (element == gamepad.buttonA) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, - // gamepad.buttonA.isPressed); - // } else if (element == gamepad.buttonB) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, - // gamepad.buttonB.isPressed); - // } else if (element == gamepad.buttonX) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, - // gamepad.buttonX.isPressed); - // } else if (element == gamepad.buttonY) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, - // gamepad.buttonY.isPressed); - // } else if (element == gamepad.leftShoulder) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, - // gamepad.leftShoulder.isPressed); - // } else if (element == gamepad.rightShoulder) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, - // gamepad.rightShoulder.isPressed); - // } else if (element == gamepad.dpad) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, - // gamepad.dpad.up.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, - // gamepad.dpad.down.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, - // gamepad.dpad.left.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, - // gamepad.dpad.right.isPressed); - // }; - // }; - //#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+, - // // while we are setting that as the minimum, seems our - // // build environment doesn't like it - // } else if (controller.microGamepad != nil) { - // // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad - // controller.microGamepad.valueChangedHandler = - // ^(GCMicroGamepad *gamepad, GCControllerElement *element) { - // int joy_id = [self getJoyIdForController:controller]; - // - // if (element == gamepad.buttonA) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, - // gamepad.buttonA.isPressed); - // } else if (element == gamepad.buttonX) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, - // gamepad.buttonX.isPressed); - // } else if (element == gamepad.dpad) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, - // gamepad.dpad.up.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, - // gamepad.dpad.down.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, - // gamepad.dpad.left.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, - // gamepad.dpad.right.isPressed); - // }; - // }; - //#endif - // }; ///@TODO need to add support for controller.motion which gives us access to /// the orientation of the device (if supported) diff --git a/platform/iphone/native_video_view.h b/platform/iphone/native_video_view.h new file mode 100644 index 0000000000..d8687b3538 --- /dev/null +++ b/platform/iphone/native_video_view.h @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* native_video_view.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#import <UIKit/UIKit.h> + +@interface GodotNativeVideoView : UIView + +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack; +- (BOOL)isVideoPlaying; +- (void)pauseVideo; +- (void)unpauseVideo; +- (void)stopVideo; + +@end diff --git a/platform/iphone/native_video_view.m b/platform/iphone/native_video_view.m new file mode 100644 index 0000000000..a4e9f209f0 --- /dev/null +++ b/platform/iphone/native_video_view.m @@ -0,0 +1,260 @@ +/*************************************************************************/ +/* native_video_view.m */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#import "native_video_view.h" +#import <AVFoundation/AVFoundation.h> + +@interface GodotNativeVideoView () + +@property(strong, nonatomic) AVAsset *avAsset; +@property(strong, nonatomic) AVPlayerItem *avPlayerItem; +@property(strong, nonatomic) AVPlayer *avPlayer; +@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; +@property(assign, nonatomic) CMTime videoCurrentTime; +@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying; + +@end + +@implementation GodotNativeVideoView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (void)godot_commonInit { + self.isVideoCurrentlyPlaying = NO; + self.videoCurrentTime = kCMTimeZero; + + [self observeVideoAudio]; +} + +- (void)observeVideoAudio { + printf("******** adding observer for sound routing changes\n"); + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(audioRouteChangeListenerCallback:) + name:AVAudioSessionRouteChangeNotification + object:nil]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) { + [self handleVideoOrPlayerStatus]; + } + + if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) { + [self handleVideoPlayRate]; + } +} + +// MARK: Video Audio + +- (void)audioRouteChangeListenerCallback:(NSNotification *)notification { + printf("*********** route changed!\n"); + NSDictionary *interuptionDict = notification.userInfo; + + NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; + + switch (routeChangeReason) { + case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { + NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); + NSLog(@"Headphone/Line plugged in"); + } break; + case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { + NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); + NSLog(@"Headphone/Line was pulled. Resuming video play...."); + if ([self isVideoPlaying]) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self.avPlayer play]; // NOTE: change this line according your current player implementation + NSLog(@"resumed play"); + }); + } + } break; + case AVAudioSessionRouteChangeReasonCategoryChange: { + // called at start - also when other audio wants to play + NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange"); + } break; + } +} + +// MARK: Native Video Player + +- (void)handleVideoOrPlayerStatus { + if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) { + [self stopVideo]; + } + + if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) { + // NSLog(@"time: %@", self.video_current_time); + [self.avPlayer seekToTime:self.videoCurrentTime]; + self.videoCurrentTime = kCMTimeZero; + } +} + +- (void)handleVideoPlayRate { + NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate); + if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self.avPlayer play]; // NOTE: change this line according your current player implementation + NSLog(@"resumed play"); + }); + + NSLog(@" . . . PAUSED (or just started)"); + } +} + +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack { + self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]]; + + self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset]; + [self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil]; + + self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem]; + self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; + + [self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(playerItemDidReachEnd:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:[self.avPlayer currentItem]]; + + [self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0]; + + [self.avPlayerLayer setFrame:self.bounds]; + [self.layer addSublayer:self.avPlayerLayer]; + [self.avPlayer play]; + + AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + + NSMutableArray *allAudioParams = [NSMutableArray array]; + for (id track in audioGroup.options) { + NSString *language = [[track locale] localeIdentifier]; + NSLog(@"subtitle lang: %@", language); + + if ([language isEqualToString:audioTrack]) { + AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; + [audioInputParams setVolume:videoVolume atTime:kCMTimeZero]; + [audioInputParams setTrackID:[track trackID]]; + [allAudioParams addObject:audioInputParams]; + + AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; + [audioMix setInputParameters:allAudioParams]; + + [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup]; + [self.avPlayer.currentItem setAudioMix:audioMix]; + + break; + } + } + + AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; + NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; + + for (id track in useableTracks) { + NSString *language = [[track locale] localeIdentifier]; + NSLog(@"subtitle lang: %@", language); + + if ([language isEqualToString:subtitleTrack]) { + [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup]; + break; + } + } + + self.isVideoCurrentlyPlaying = YES; + + return true; +} + +- (BOOL)isVideoPlaying { + if (self.avPlayer.error) { + printf("Error during playback\n"); + } + return (self.avPlayer.rate > 0 && !self.avPlayer.error); +} + +- (void)pauseVideo { + self.videoCurrentTime = self.avPlayer.currentTime; + [self.avPlayer pause]; + self.isVideoCurrentlyPlaying = NO; +} + +- (void)unpauseVideo { + [self.avPlayer play]; + self.isVideoCurrentlyPlaying = YES; +} + +- (void)playerItemDidReachEnd:(NSNotification *)notification { + [self stopVideo]; +} + +- (void)finishPlayingVideo { + [self.avPlayer pause]; + [self.avPlayerLayer removeFromSuperlayer]; + self.avPlayerLayer = nil; + + if (self.avPlayerItem) { + [self.avPlayerItem removeObserver:self forKeyPath:@"status"]; + self.avPlayerItem = nil; + } + + if (self.avPlayer) { + [self.avPlayer removeObserver:self forKeyPath:@"status"]; + self.avPlayer = nil; + } + + self.avAsset = nil; + + self.isVideoCurrentlyPlaying = NO; +} + +- (void)stopVideo { + [self finishPlayingVideo]; + + [self removeFromSuperview]; +} + +@end diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index 946fd51923..e007276b4b 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -128,7 +128,6 @@ void OSIPhone::initialize_modules() { #ifdef GAME_CENTER_ENABLED game_center = memnew(GameCenter); Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center)); - game_center->connect(); #endif #ifdef STOREKIT_ENABLED @@ -272,7 +271,6 @@ String OSIPhone::get_model_name() const { Error OSIPhone::shell_open(String p_uri) { NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()]; NSURL *url = [NSURL URLWithString:urlPath]; - [urlPath release]; if (![[UIApplication sharedApplication] canOpenURL:url]) { return ERR_CANT_OPEN; @@ -280,11 +278,7 @@ Error OSIPhone::shell_open(String p_uri) { printf("opening url %s\n", p_uri.utf8().get_data()); - // if (@available(iOS 10, *)) { [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; - // } else { - // [[UIApplication sharedApplication] openURL:url]; - // } return OK; }; diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h index 2bbbe47c0d..ec39ad0ba4 100644 --- a/platform/iphone/platform_config.h +++ b/platform/iphone/platform_config.h @@ -33,3 +33,10 @@ #define PLATFORM_REFCOUNT #define PTHREAD_RENAME_SELF + +#define _weakify(var) __weak typeof(var) GDWeak_##var = var; +#define _strongify(var) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wshadow\"") \ + __strong typeof(var) var = GDWeak_##var; \ + _Pragma("clang diagnostic pop") diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h index dffdc01d4a..b0b31ae377 100644 --- a/platform/iphone/view_controller.h +++ b/platform/iphone/view_controller.h @@ -32,17 +32,15 @@ #import <UIKit/UIKit.h> @class GodotView; +@class GodotNativeVideoView; @interface ViewController : UIViewController <GKGameCenterControllerDelegate> -- (GodotView *)godotView; +@property(nonatomic, readonly, strong) GodotView *godotView; +@property(nonatomic, readonly, strong) GodotNativeVideoView *videoView; // MARK: Native Video Player - (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack; -- (BOOL)isVideoPlaying; -- (void)pauseVideo; -- (void)unpauseVideo; -- (void)stopVideo; @end diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index 31597f7856..fb25041779 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -33,6 +33,7 @@ #include "display_server_iphone.h" #import "godot_view.h" #import "godot_view_renderer.h" +#import "native_video_view.h" #include "os_iphone.h" #import <GameController/GameController.h> @@ -40,16 +41,7 @@ @interface ViewController () @property(strong, nonatomic) GodotViewRenderer *renderer; - -// TODO: separate view to handle video -// AVPlayer-related properties -@property(strong, nonatomic) AVAsset *avAsset; -@property(strong, nonatomic) AVPlayerItem *avPlayerItem; -@property(strong, nonatomic) AVPlayer *avPlayer; -@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; -@property(assign, nonatomic) CMTime videoCurrentTime; -@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying; -@property(assign, nonatomic) BOOL videoHasFoundError; +@property(strong, nonatomic) GodotNativeVideoView *videoView; @end @@ -67,9 +59,6 @@ self.view = view; view.renderer = self.renderer; - - [renderer release]; - [view release]; } - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { @@ -93,9 +82,7 @@ } - (void)godot_commonInit { - self.isVideoCurrentlyPlaying = NO; - self.videoCurrentTime = kCMTimeZero; - self.videoHasFoundError = false; + // Initialize view controller values. } - (void)didReceiveMemoryWarning { @@ -107,7 +94,6 @@ [super viewDidLoad]; [self observeKeyboard]; - [self observeAudio]; if (@available(iOS 11.0, *)) { [self setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; @@ -128,33 +114,13 @@ object:nil]; } -- (void)observeAudio { - printf("******** adding observer for sound routing changes\n"); - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(audioRouteChangeListenerCallback:) - name:AVAudioSessionRouteChangeNotification - object:nil]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) { - [self handleVideoOrPlayerStatus]; - } - - if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) { - [self handleVideoPlayRate]; - } -} - - (void)dealloc { - [self stopVideo]; + [self.videoView stopVideo]; + self.videoView = nil; self.renderer = nil; [[NSNotificationCenter defaultCenter] removeObserver:self]; - - [super dealloc]; } // MARK: Orientation @@ -233,166 +199,19 @@ } } -// MARK: Audio - -- (void)audioRouteChangeListenerCallback:(NSNotification *)notification { - printf("*********** route changed!\n"); - NSDictionary *interuptionDict = notification.userInfo; - - NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; - - switch (routeChangeReason) { - case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { - NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); - NSLog(@"Headphone/Line plugged in"); - } break; - case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { - NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); - NSLog(@"Headphone/Line was pulled. Resuming video play...."); - if ([self isVideoPlaying]) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [self.avPlayer play]; // NOTE: change this line according your current player implementation - NSLog(@"resumed play"); - }); - } - } break; - case AVAudioSessionRouteChangeReasonCategoryChange: { - // called at start - also when other audio wants to play - NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange"); - } break; - } -} - // MARK: Native Video Player -- (void)handleVideoOrPlayerStatus { - if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) { - [self stopVideo]; - self.videoHasFoundError = true; - } - - if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) { - // NSLog(@"time: %@", self.video_current_time); - [self.avPlayer seekToTime:self.videoCurrentTime]; - self.videoCurrentTime = kCMTimeZero; - } -} - -- (void)handleVideoPlayRate { - NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate); - if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [self.avPlayer play]; // NOTE: change this line according your current player implementation - NSLog(@"resumed play"); - }); - - NSLog(@" . . . PAUSED (or just started)"); - } -} - - (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack { - self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]]; - - self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset]; - [self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil]; - - self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem]; - self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; - - [self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil]; - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(playerItemDidReachEnd:) - name:AVPlayerItemDidPlayToEndTimeNotification - object:[self.avPlayer currentItem]]; - - [self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0]; - - [self.avPlayerLayer setFrame:self.view.bounds]; - [self.view.layer addSublayer:self.avPlayerLayer]; - [self.avPlayer play]; - - AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; - - NSMutableArray *allAudioParams = [NSMutableArray array]; - for (id track in audioGroup.options) { - NSString *language = [[track locale] localeIdentifier]; - NSLog(@"subtitle lang: %@", language); - - if ([language isEqualToString:audioTrack]) { - AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; - [audioInputParams setVolume:videoVolume atTime:kCMTimeZero]; - [audioInputParams setTrackID:[track trackID]]; - [allAudioParams addObject:audioInputParams]; - - AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; - [audioMix setInputParameters:allAudioParams]; - - [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup]; - [self.avPlayer.currentItem setAudioMix:audioMix]; - - break; - } - } - - AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; - NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; - - for (id track in useableTracks) { - NSString *language = [[track locale] localeIdentifier]; - NSLog(@"subtitle lang: %@", language); - - if ([language isEqualToString:subtitleTrack]) { - [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup]; - break; - } - } - - self.isVideoCurrentlyPlaying = YES; - - return true; -} - -- (BOOL)isVideoPlaying { - if (self.avPlayer.error) { - printf("Error during playback\n"); - } - return (self.avPlayer.rate > 0 && !self.avPlayer.error); -} - -- (void)pauseVideo { - self.videoCurrentTime = self.avPlayer.currentTime; - [self.avPlayer pause]; - self.isVideoCurrentlyPlaying = NO; -} - -- (void)unpauseVideo { - [self.avPlayer play]; - self.isVideoCurrentlyPlaying = YES; -} - -- (void)playerItemDidReachEnd:(NSNotification *)notification { - [self stopVideo]; -} - -- (void)stopVideo { - [self.avPlayer pause]; - [self.avPlayerLayer removeFromSuperlayer]; - self.avPlayerLayer = nil; - - if (self.avPlayerItem) { - [self.avPlayerItem removeObserver:self forKeyPath:@"status"]; - self.avPlayerItem = nil; - } - - if (self.avPlayer) { - [self.avPlayer removeObserver:self forKeyPath:@"status"]; - self.avPlayer = nil; + // If we are showing some video already, reuse existing view for new video. + if (self.videoView) { + return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack]; + } else { + // Create autoresizing view for video playback. + GodotNativeVideoView *videoView = [[GodotNativeVideoView alloc] initWithFrame:self.view.bounds]; + videoView.autoresizingMask = UIViewAutoresizingFlexibleWidth & UIViewAutoresizingFlexibleHeight; + [self.view addSubview:videoView]; + return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack]; } - - self.avAsset = nil; - - self.isVideoCurrentlyPlaying = NO; } // MARK: Delegates diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm index cb4dbe7f85..d62e826957 100644 --- a/platform/iphone/vulkan_context_iphone.mm +++ b/platform/iphone/vulkan_context_iphone.mm @@ -35,14 +35,12 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const { return VK_MVK_IOS_SURFACE_EXTENSION_NAME; } -Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id, - CALayer *p_metal_layer, int p_width, - int p_height) { +Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height) { VkIOSSurfaceCreateInfoMVK createInfo; createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK; createInfo.pNext = NULL; createInfo.flags = 0; - createInfo.pView = p_metal_layer; + createInfo.pView = (__bridge const void *)p_metal_layer; VkSurfaceKHR surface; VkResult err = diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index e3cf992302..8b0d08d1cb 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -322,23 +322,19 @@ bool DisplayServerX11::_refresh_device_info() { } void DisplayServerX11::_flush_mouse_motion() { - while (true) { - if (XPending(x11_display) > 0) { - XEvent event; - XPeekEvent(x11_display, &event); - - if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { - XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; - - if (event_data->evtype == XI_RawMotion) { - XNextEvent(x11_display, &event); - } else { - break; - } - } else { - break; + // Block events polling while flushing motion events. + MutexLock mutex_lock(events_mutex); + + for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) { + XEvent &event = polled_events[event_index]; + if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { + XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; + if (event_data->evtype == XI_RawMotion) { + XFreeEventData(x11_display, &event.xcookie); + polled_events.remove(event_index--); + continue; } - } else { + XFreeEventData(x11_display, &event.xcookie); break; } } @@ -451,12 +447,25 @@ int DisplayServerX11::mouse_get_button_state() const { void DisplayServerX11::clipboard_set(const String &p_text) { _THREAD_SAFE_METHOD_ - internal_clipboard = p_text; + { + // The clipboard content can be accessed while polling for events. + MutexLock mutex_lock(events_mutex); + internal_clipboard = p_text; + } + XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime); XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime); } -static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) { +Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg) { + if (event->type == SelectionNotify && event->xselection.requestor == *(Window *)arg) { + return True; + } else { + return False; + } +} + +String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const { String ret; Atom type; @@ -464,23 +473,27 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x int format, result; unsigned long len, bytes_left, dummy; unsigned char *data; - Window Sown = XGetSelectionOwner(x11_display, p_source); + Window selection_owner = XGetSelectionOwner(x11_display, p_source); - if (Sown == x11_window) { - return p_internal_clipboard; - }; + if (selection_owner == x11_window) { + return internal_clipboard; + } - if (Sown != None) { - XConvertSelection(x11_display, p_source, target, selection, - x11_window, CurrentTime); - XFlush(x11_display); - while (true) { + if (selection_owner != None) { + { + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); + + XConvertSelection(x11_display, p_source, target, selection, + x11_window, CurrentTime); + + XFlush(x11_display); + + // Blocking wait for predicate to be True + // and remove the event from the queue. XEvent event; - XNextEvent(x11_display, &event); - if (event.type == SelectionNotify && event.xselection.requestor == x11_window) { - break; - }; - }; + XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); + } // // Do not get any data, see how much data is there @@ -514,14 +527,14 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x return ret; } -static String _clipboard_get(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) { +String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const { String ret; Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True); if (utf8_atom != None) { - ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, utf8_atom); + ret = _clipboard_get_impl(p_source, x11_window, utf8_atom); } - if (ret == "") { - ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING); + if (ret.empty()) { + ret = _clipboard_get_impl(p_source, x11_window, XA_STRING); } return ret; } @@ -530,11 +543,11 @@ String DisplayServerX11::clipboard_get() const { _THREAD_SAFE_METHOD_ String ret; - ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); + ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window); - if (ret == "") { - ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); - }; + if (ret.empty()) { + ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window); + } return ret; } @@ -1649,10 +1662,16 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win return; } + // Block events polling while changing input focus + // because it triggers some event polling internally. if (p_active) { - XSetICFocus(wd.xic); + { + MutexLock mutex_lock(events_mutex); + XSetICFocus(wd.xic); + } window_set_ime_position(wd.im_position, p_window); } else { + MutexLock mutex_lock(events_mutex); XUnsetICFocus(wd.xic); } } @@ -1673,7 +1692,14 @@ void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_ spot.x = short(p_pos.x); spot.y = short(p_pos.y); XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, nullptr); - XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr); + + { + // Block events polling during this call + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr); + } + XFree(preedit_attr); } @@ -1993,7 +2019,7 @@ unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button return last_button_state; } -void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo) { +void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) { WindowData wd = windows[p_window]; // X11 functions don't know what const is XKeyEvent *xkeyevent = p_event; @@ -2130,7 +2156,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, /* Phase 4, determine if event must be filtered */ // This seems to be a side-effect of using XIM. - // XEventFilter looks like a core X11 function, + // XFilterEvent looks like a core X11 function, // but it's actually just used to see if we must // ignore a deadkey, or events XIM determines // must not reach the actual gui. @@ -2164,17 +2190,16 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, // Echo characters in X11 are a keyrelease and a keypress // one after the other with the (almot) same timestamp. - // To detect them, i use XPeekEvent and check that their - // difference in time is below a threshold. + // To detect them, i compare to the next event in list and + // check that their difference in time is below a threshold. if (xkeyevent->type != KeyPress) { p_echo = false; // make sure there are events pending, // so this call won't block. - if (XPending(x11_display) > 0) { - XEvent peek_event; - XPeekEvent(x11_display, &peek_event); + if (p_event_index + 1 < p_events.size()) { + XEvent &peek_event = p_events[p_event_index + 1]; // I'm using a threshold of 5 msecs, // since sometimes there seems to be a little @@ -2189,9 +2214,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, KeySym rk; XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr); if (rk == keysym_keycode) { - XEvent event; - XNextEvent(x11_display, &event); //erase next event - _handle_key_event(p_window, (XKeyEvent *)&event, true); + // Consume to next event. + ++p_event_index; + _handle_key_event(p_window, (XKeyEvent *)&peek_event, p_events, p_event_index, true); return; //ignore current, echo next } } @@ -2246,6 +2271,66 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, Input::get_singleton()->accumulate_input_event(k); } +void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) { + XEvent respond; + if (p_event->target == XInternAtom(x11_display, "UTF8_STRING", 0) || + p_event->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || + p_event->target == XInternAtom(x11_display, "TEXT", 0) || + p_event->target == XA_STRING || + p_event->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || + p_event->target == XInternAtom(x11_display, "text/plain", 0)) { + // Directly using internal clipboard because we know our window + // is the owner during a selection request. + CharString clip = internal_clipboard.utf8(); + XChangeProperty(x11_display, + p_event->requestor, + p_event->property, + p_event->target, + 8, + PropModeReplace, + (unsigned char *)clip.get_data(), + clip.length()); + respond.xselection.property = p_event->property; + } else if (p_event->target == XInternAtom(x11_display, "TARGETS", 0)) { + Atom data[7]; + data[0] = XInternAtom(x11_display, "TARGETS", 0); + data[1] = XInternAtom(x11_display, "UTF8_STRING", 0); + data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); + data[3] = XInternAtom(x11_display, "TEXT", 0); + data[4] = XA_STRING; + data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); + data[6] = XInternAtom(x11_display, "text/plain", 0); + + XChangeProperty(x11_display, + p_event->requestor, + p_event->property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char *)&data, + sizeof(data) / sizeof(data[0])); + respond.xselection.property = p_event->property; + + } else { + char *targetname = XGetAtomName(x11_display, p_event->target); + printf("No Target '%s'\n", targetname); + if (targetname) { + XFree(targetname); + } + respond.xselection.property = None; + } + + respond.xselection.type = SelectionNotify; + respond.xselection.display = p_event->display; + respond.xselection.requestor = p_event->requestor; + respond.xselection.selection = p_event->selection; + respond.xselection.target = p_event->target; + respond.xselection.time = p_event->time; + + XSendEvent(x11_display, p_event->requestor, True, NoEventMask, &respond); + XFlush(x11_display); +} + void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data, ::XPointer call_data) { WARN_PRINT("Input method stopped"); @@ -2358,6 +2443,65 @@ void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_ev } } +void DisplayServerX11::_poll_events_thread(void *ud) { + DisplayServerX11 *display_server = (DisplayServerX11 *)ud; + display_server->_poll_events(); +} + +Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XPointer arg) { + // Just accept all events. + return True; +} + +void DisplayServerX11::_poll_events() { + int x11_fd = ConnectionNumber(x11_display); + fd_set in_fds; + + while (!events_thread_done) { + XFlush(x11_display); + + FD_ZERO(&in_fds); + FD_SET(x11_fd, &in_fds); + + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = 1; + + // Wait for next event or timeout. + int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv); + if (num_ready_fds < 0) { + ERR_PRINT("_poll_events: select error: " + itos(errno)); + } + + // Process events from the queue. + { + MutexLock mutex_lock(events_mutex); + + // Non-blocking wait for next event + // and remove it from the queue. + XEvent ev; + while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) { + // Check if the input manager wants to process the event. + if (XFilterEvent(&ev, None)) { + // Event has been filtered by the Input Manager, + // it has to be ignored and a new one will be received. + continue; + } + + // Handle selection request events directly in the event thread, because + // communication through the x server takes several events sent back and forth + // and we don't want to block other programs while processing only one each frame. + if (ev.type == SelectionRequest) { + _handle_selection_request_event(&(ev.xselectionrequest)); + continue; + } + + polled_events.push_back(ev); + } + } + } +} + void DisplayServerX11::process_events() { _THREAD_SAFE_METHOD_ @@ -2401,13 +2545,16 @@ void DisplayServerX11::process_events() { xi.tilt = Vector2(); xi.pressure_supported = false; - while (XPending(x11_display) > 0) { - XEvent event; - XNextEvent(x11_display, &event); + LocalVector<XEvent> events; + { + // Block events polling while flushing events. + MutexLock mutex_lock(events_mutex); + events = polled_events; + polled_events.clear(); + } - if (XFilterEvent(&event, None)) { - continue; - } + for (uint32_t event_index = 0; event_index < events.size(); ++event_index) { + XEvent &event = events[event_index]; WindowID window_id = MAIN_WINDOW_ID; @@ -2666,6 +2813,9 @@ void DisplayServerX11::process_events() { }*/ #endif if (wd.xic) { + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); XSetICFocus(wd.xic); } @@ -2715,7 +2865,10 @@ void DisplayServerX11::process_events() { xi.state.clear(); #endif if (wd.xic) { - XSetICFocus(wd.xic); + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + XUnsetICFocus(wd.xic); } } break; @@ -2834,11 +2987,11 @@ void DisplayServerX11::process_events() { break; } - if (XPending(x11_display) > 0) { - XEvent tevent; - XPeekEvent(x11_display, &tevent); - if (tevent.type == MotionNotify) { - XNextEvent(x11_display, &event); + if (event_index + 1 < events.size()) { + const XEvent &next_event = events[event_index + 1]; + if (next_event.type == MotionNotify) { + ++event_index; + event = next_event; } else { break; } @@ -2969,67 +3122,7 @@ void DisplayServerX11::process_events() { // key event is a little complex, so // it will be handled in its own function. - _handle_key_event(window_id, (XKeyEvent *)&event); - } break; - case SelectionRequest: { - XSelectionRequestEvent *req; - XEvent e, respond; - e = event; - - req = &(e.xselectionrequest); - if (req->target == XInternAtom(x11_display, "UTF8_STRING", 0) || - req->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || - req->target == XInternAtom(x11_display, "TEXT", 0) || - req->target == XA_STRING || - req->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || - req->target == XInternAtom(x11_display, "text/plain", 0)) { - CharString clip = clipboard_get().utf8(); - XChangeProperty(x11_display, - req->requestor, - req->property, - req->target, - 8, - PropModeReplace, - (unsigned char *)clip.get_data(), - clip.length()); - respond.xselection.property = req->property; - } else if (req->target == XInternAtom(x11_display, "TARGETS", 0)) { - Atom data[7]; - data[0] = XInternAtom(x11_display, "TARGETS", 0); - data[1] = XInternAtom(x11_display, "UTF8_STRING", 0); - data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); - data[3] = XInternAtom(x11_display, "TEXT", 0); - data[4] = XA_STRING; - data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); - data[6] = XInternAtom(x11_display, "text/plain", 0); - - XChangeProperty(x11_display, - req->requestor, - req->property, - XA_ATOM, - 32, - PropModeReplace, - (unsigned char *)&data, - sizeof(data) / sizeof(data[0])); - respond.xselection.property = req->property; - - } else { - char *targetname = XGetAtomName(x11_display, req->target); - printf("No Target '%s'\n", targetname); - if (targetname) { - XFree(targetname); - } - respond.xselection.property = None; - } - - respond.xselection.type = SelectionNotify; - respond.xselection.display = req->display; - respond.xselection.requestor = req->requestor; - respond.xselection.selection = req->selection; - respond.xselection.target = req->target; - respond.xselection.time = req->time; - XSendEvent(x11_display, req->requestor, True, NoEventMask, &respond); - XFlush(x11_display); + _handle_key_event(window_id, (XKeyEvent *)&event, events, event_index); } break; case SelectionNotify: @@ -3424,6 +3517,10 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1); if (xim && xim_style) { + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + wd.xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, wd.x11_window, XNFocusWindow, wd.x11_window, (char *)nullptr); if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) { WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value"); @@ -3926,12 +4023,19 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } } + events_thread = Thread::create(_poll_events_thread, this); + _update_real_mouse_position(windows[MAIN_WINDOW_ID]); r_error = OK; } DisplayServerX11::~DisplayServerX11() { + events_thread_done = true; + Thread::wait_to_finish(events_thread); + memdelete(events_thread); + events_thread = nullptr; + //destroy all windows for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { #ifdef VULKAN_ENABLED diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index e894cf515c..740bf81fd9 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -36,6 +36,7 @@ #include "servers/display_server.h" #include "core/input/input.h" +#include "core/local_vector.h" #include "drivers/alsa/audio_driver_alsa.h" #include "drivers/alsamidi/midi_driver_alsamidi.h" #include "drivers/pulseaudio/audio_driver_pulseaudio.h" @@ -202,7 +203,11 @@ class DisplayServerX11 : public DisplayServer { MouseMode mouse_mode; Point2i center; - void _handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo = false); + void _handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo = false); + void _handle_selection_request_event(XSelectionRequestEvent *p_event); + + String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const; + String _clipboard_get(Atom p_source, Window x11_window) const; //bool minimized; //bool window_has_focus; @@ -252,6 +257,16 @@ class DisplayServerX11 : public DisplayServer { static void _dispatch_input_events(const Ref<InputEvent> &p_event); void _dispatch_input_event(const Ref<InputEvent> &p_event); + mutable Mutex events_mutex; + Thread *events_thread = nullptr; + bool events_thread_done = false; + LocalVector<XEvent> polled_events; + static void _poll_events_thread(void *ud); + void _poll_events(); + + static Bool _predicate_all_events(Display *display, XEvent *event, XPointer arg); + static Bool _predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg); + protected: void _window_changed(XEvent *event); diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 49f0e7bfa3..cd5b71890c 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -2246,11 +2246,18 @@ int DisplayServerOSX::screen_get_dpi(int p_screen) const { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; - NSSize displayDPI = [[description objectForKey:NSDeviceResolution] sizeValue]; - return (displayDPI.width + displayDPI.height) / 2; + + const NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; + const CGSize displayPhysicalSize = CGDisplayScreenSize([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + float scale = [[screenArray objectAtIndex:p_screen] backingScaleFactor]; + + float den2 = (displayPhysicalSize.width / 25.4f) * (displayPhysicalSize.width / 25.4f) + (displayPhysicalSize.height / 25.4f) * (displayPhysicalSize.height / 25.4f); + if (den2 > 0.0f) { + return ceil(sqrt(displayPixelSize.width * displayPixelSize.width + displayPixelSize.height * displayPixelSize.height) / sqrt(den2) * scale); + } } - return 96; + return 72; } float DisplayServerOSX::screen_get_scale(int p_screen) const { |