diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index 56c0ec09..8823b476 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -260,6 +260,29 @@ } } }, + "[ Disconnect & quit app ]" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "[ 断开连接并退出应用 ]" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "[ 斷開連線並退出應用程式 ]" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "[ 斷開連線並退出應用程式 ]" + } + } + } + }, "[ Enter picture-in-picture mode ]" : { "extractionState" : "manual", "localizations" : { diff --git a/VoidLink/ViewControllers/MainFrameViewController.h b/VoidLink/ViewControllers/MainFrameViewController.h index 1f11da5a..8ce44440 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.h +++ b/VoidLink/ViewControllers/MainFrameViewController.h @@ -31,8 +31,9 @@ -(void)expandSettingsView; - (void)closeSettingViewAnimated:(BOOL)anaimated; --(void)reloadStreamConfig; --(bool)isIPhonePortrait; +- (void)reloadStreamConfig; +- (bool)isIPhonePortrait; +- (void)quitRunningApp; #endif - (void)fillResolutionTable:(CGSize*)resolutionTable externalDisplayMode:(NSInteger)externalDisplayMode; diff --git a/VoidLink/ViewControllers/MainFrameViewController.m b/VoidLink/ViewControllers/MainFrameViewController.m index 687ef78c..2992aae3 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.m +++ b/VoidLink/ViewControllers/MainFrameViewController.m @@ -74,6 +74,7 @@ StreamFrameViewController* streamFrameViewController; id navBarAppearanceStandard; bool _viewJustAppeared; + TemporaryApp * launchedApp; NSTimer *_foregroundHostUpdateTimer; @@ -771,6 +772,7 @@ static NSMutableSet* hostList; } - (void) prepareToStreamApp:(TemporaryApp *)app { + launchedApp = app; [self updateResolutionAccordingly]; self.revealViewController.isStreaming = true; // tell the revealViewController streaming is started. _streamConfig = [[StreamConfiguration alloc] init]; @@ -913,6 +915,55 @@ static NSMutableSet* hostList; #endif } +- (HttpResponse* )requestToQuitApp:(TemporaryApp* )app{ + HttpManager* hMan = [[HttpManager alloc] initWithHost:app.host]; + HttpResponse* quitResponse = [[HttpResponse alloc] init]; + HttpRequest* quitRequest = [HttpRequest requestForResponse: quitResponse withUrlRequest:[hMan newQuitAppRequest]]; + + // Exempt this host from discovery while handling the quit operation + [self->_discMan pauseDiscoveryForHost:app.host]; + [hMan executeRequestSynchronously:quitRequest]; + if (quitResponse.statusCode == 200) { + ServerInfoResponse* serverInfoResp = [[ServerInfoResponse alloc] init]; + [hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResp withUrlRequest:[hMan newServerInfoRequest:false] + fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]]; + if (![serverInfoResp isStatusOk] || [[serverInfoResp getStringTag:@"state"] hasSuffix:@"_SERVER_BUSY"]) { + // On newer GFE versions, the quit request succeeds even though the app doesn't + // really quit if another client tries to kill your app. We'll patch the response + // to look like the old error in that case, so the UI behaves. + quitResponse.statusCode = 599; + } + else if ([serverInfoResp isStatusOk]) { + // Update the host object with this info + [serverInfoResp populateHost:app.host]; + } + } + [self->_discMan resumeDiscoveryForHost:app.host]; + return quitResponse; +} + +- (void)quitRunningApp{ + [self showLoadingFrame: ^{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + HttpResponse* quitResponse = [self requestToQuitApp:self->launchedApp]; + // If it fails, display an error and stop the current operation + if (quitResponse.statusCode != 200) { + UIAlertController* alert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Quitting App Failed"] + message:[LocalizationHelper localizedStringForKey:@"Failed to quit app. If this app was started by another device, you'll need to quit from that device."] + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:nil]]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateAppsForHost:self->launchedApp.host]; + [self hideLoadingFrame: ^{ + [[self activeViewController] presentViewController:alert animated:YES completion:nil]; + }]; + }); + } + else dispatch_async(dispatch_get_main_queue(), ^{[self hideLoadingFrame:nil];}); + }); + }]; +} + - (void)appLongClicked:(TemporaryApp *)app view:(UIView *)view { Log(LOG_D, @"Long clicked app: %@", app.name); @@ -968,30 +1019,7 @@ static NSMutableSet* hostList; Log(LOG_I, @"Quitting application: %@", currentApp.name); [self showLoadingFrame: ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - HttpManager* hMan = [[HttpManager alloc] initWithHost:app.host]; - HttpResponse* quitResponse = [[HttpResponse alloc] init]; - HttpRequest* quitRequest = [HttpRequest requestForResponse: quitResponse withUrlRequest:[hMan newQuitAppRequest]]; - - // Exempt this host from discovery while handling the quit operation - [self->_discMan pauseDiscoveryForHost:app.host]; - [hMan executeRequestSynchronously:quitRequest]; - if (quitResponse.statusCode == 200) { - ServerInfoResponse* serverInfoResp = [[ServerInfoResponse alloc] init]; - [hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResp withUrlRequest:[hMan newServerInfoRequest:false] - fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]]; - if (![serverInfoResp isStatusOk] || [[serverInfoResp getStringTag:@"state"] hasSuffix:@"_SERVER_BUSY"]) { - // On newer GFE versions, the quit request succeeds even though the app doesn't - // really quit if another client tries to kill your app. We'll patch the response - // to look like the old error in that case, so the UI behaves. - quitResponse.statusCode = 599; - } - else if ([serverInfoResp isStatusOk]) { - // Update the host object with this info - [serverInfoResp populateHost:app.host]; - } - } - [self->_discMan resumeDiscoveryForHost:app.host]; - + HttpResponse* quitResponse = [self requestToQuitApp:app]; // If it fails, display an error and stop the current operation if (quitResponse.statusCode != 200) { UIAlertController* alert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Quitting App Failed"] diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index 089fe14e..ee5a0156 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -983,6 +983,17 @@ - (void)disconnectRemoteSession { Log(LOG_I, @"Settings view disconnect the session in stream view"); [self returnToMainFrame]; + +} + +- (void)disconnectAndQuitApp{ + [self returnToMainFrame]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + sleep(1.5); + dispatch_async(dispatch_get_main_queue(), ^{ + [self.mainFrameViewcontroller quitRunningApp]; + }); + }); } - (void) connectionStarted { diff --git a/VoidLink/ViewControllers/ToolboxViewController.swift b/VoidLink/ViewControllers/ToolboxViewController.swift index 37d07f9b..3143856c 100644 --- a/VoidLink/ViewControllers/ToolboxViewController.swift +++ b/VoidLink/ViewControllers/ToolboxViewController.swift @@ -13,6 +13,7 @@ import UIKit @objc optional func switchWidgetProfile() @objc optional func bringUpSoftKeyboard() @objc optional func enterPip() + @objc optional func disconnectAndQuitApp() } @@ -28,12 +29,13 @@ import UIKit private let viewBackgroundColor = UIColor(white: 0.2, alpha: 0.8); private let highlightColor = UIColor(white: 0.55, alpha: 0.8); private let titleLabel = UILabel() - @objc public var specialEntries : NSMutableArray = ["widgetSwitchTool", "widgetLayoutTool", "bringUpSoftKeyboard", "enterPip"] + @objc public var specialEntries : NSMutableArray = ["widgetSwitchTool", "widgetLayoutTool", "bringUpSoftKeyboard", "enterPip", "disconnectAndQuitApp"] private let specialEntryAliasDic : [String:String] = [ "widgetSwitchTool":SwiftLocalizationHelper.localizedString(forKey: "[ Switch on-screen widget profile ]"), "widgetLayoutTool":SwiftLocalizationHelper.localizedString(forKey: "[ On-screen widget tool ]"), "bringUpSoftKeyboard":SwiftLocalizationHelper.localizedString(forKey: "[ Bring up soft keyboard ]"), - "enterPip":SwiftLocalizationHelper.localizedString(forKey: "[ Enter picture-in-picture mode ]") + "enterPip":SwiftLocalizationHelper.localizedString(forKey: "[ Enter picture-in-picture mode ]"), + "disconnectAndQuitApp":SwiftLocalizationHelper.localizedString(forKey: "[ Disconnect & quit app ]") ] private var viewPinned: Bool = false @@ -379,6 +381,8 @@ import UIKit specialEntryDelegate?.bringUpSoftKeyboard?() case "enterPip": specialEntryDelegate?.enterPip?() + case "disconnectAndQuitApp": + specialEntryDelegate?.disconnectAndQuitApp?() default: break } }