From 46e0efd4e1feb497676e683b7c12e2ec90ea5cd5 Mon Sep 17 00:00:00 2001 From: Adam Demasi Date: Thu, 24 Mar 2022 20:37:51 +1030 Subject: [PATCH] [app] Start implementing ssh:// url scheme --- App/Controllers/TerminalSceneDelegate.swift | 69 +++++++++++++++---- App/Supporting Files/Info.plist | 13 ++++ App/View Controllers/RootViewController.swift | 12 ++-- .../TerminalSessionViewController.swift | 26 +++---- Common/Controllers/TerminalController.swift | 2 +- 5 files changed, 86 insertions(+), 36 deletions(-) diff --git a/App/Controllers/TerminalSceneDelegate.swift b/App/Controllers/TerminalSceneDelegate.swift index 9adae57..5fea382 100644 --- a/App/Controllers/TerminalSceneDelegate.swift +++ b/App/Controllers/TerminalSceneDelegate.swift @@ -19,18 +19,22 @@ class TerminalSceneDelegate: UIResponder, UIWindowSceneDelegate, IdentifiableSce var window: UIWindow? + private var rootViewController: RootViewController! { + (window?.rootViewController as? UINavigationController)?.viewControllers.first as? RootViewController + } + override init() { super.init() - NotificationCenter.default.addObserver(self, selector: #selector(self.preferencesUpdated), name: Preferences.didChangeNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(preferencesUpdated), name: Preferences.didChangeNotification, object: nil) } func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - guard let windowScene = scene as? UIWindowScene else { + guard let scene = scene as? UIWindowScene else { return } - window = UIWindow(windowScene: windowScene) + window = UIWindow(windowScene: scene) window!.tintColor = .tint window!.rootViewController = UINavigationController(rootViewController: RootViewController()) window!.makeKeyAndVisible() @@ -38,25 +42,64 @@ class TerminalSceneDelegate: UIResponder, UIWindowSceneDelegate, IdentifiableSce scene.title = .localize("TERMINAL", comment: "Generic title displayed before the terminal sets a proper title.") #if targetEnvironment(macCatalyst) - windowScene.titlebar?.separatorStyle = .none + scene.titlebar?.separatorStyle = .none #endif preferencesUpdated() } + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + for context in URLContexts { + let url = context.url + switch url.scheme { + case "ssh": + createWindow(asTab: true, openingURL: url) + + default: break + } + } + } + // MARK: - Window management - func createWindow(asTab: Bool) { - let options = UIScene.ActivationRequestOptions() - #if targetEnvironment(macCatalyst) - if asTab { + func createWindow(asTab: Bool, openingURL url: URL? = nil) { + // Handle SSH URL + var sshPayload: String? + if let url = url, + let host = url.host, + url.scheme == "ssh" { + sshPayload = host + if let user = url.user { + sshPayload = "\(user)@\(host)" + } + let port = url.port ?? 22 + if port != 22 { + sshPayload = "\(sshPayload!) -p \(port)" + } + } + + if UIApplication.shared.supportsMultipleScenes { + let options = UIScene.ActivationRequestOptions() + #if targetEnvironment(macCatalyst) + if asTab { + options.requestingScene = window!.windowScene + } + options.collectionJoinBehavior = asTab ? .preferred : .disallowed + #else options.requestingScene = window!.windowScene + #endif + + let activity = NSUserActivity(activityType: Self.activityType) + activity.userInfo = [:] + activity.userInfo!["sshPayload"] = sshPayload + + UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: options, errorHandler: nil) + } else { + if let sshPayload = sshPayload { + rootViewController.initialCommand = "ssh \(sshPayload)" + } + rootViewController.addTerminal() } - options.collectionJoinBehavior = asTab ? .preferred : .disallowed - #else - options.requestingScene = window!.windowScene - #endif - UIApplication.shared.requestSceneSessionActivation(nil, userActivity: nil, options: options, errorHandler: nil) } @objc func removeWindow() { diff --git a/App/Supporting Files/Info.plist b/App/Supporting Files/Info.plist index e7e5358..e099f73 100644 --- a/App/Supporting Files/Info.plist +++ b/App/Supporting Files/Info.plist @@ -22,6 +22,19 @@ APPL CFBundleShortVersionString $(MARKETING_VERSION) + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + ssh + CFBundleURLSchemes + + ssh + + + CFBundleVersion $(CURRENT_PROJECT_VERSION) LSApplicationCategoryType diff --git a/App/View Controllers/RootViewController.swift b/App/View Controllers/RootViewController.swift index bad99a4..6d57cf7 100644 --- a/App/View Controllers/RootViewController.swift +++ b/App/View Controllers/RootViewController.swift @@ -13,8 +13,10 @@ class RootViewController: UIViewController { static let settingsViewDoneNotification = Notification.Name(rawValue: "RootViewControllerSettingsViewDoneNotification") + var initialCommand: String? + private var terminals: [UIViewController] = [] - private var selectedTabIndex = Int(0) + private var selectedTabIndex = 0 private var tabToolbar: TabToolbarViewController? @@ -62,7 +64,7 @@ class RootViewController: UIViewController { modifierFlags: .command)) #endif - let digits = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] + let digits = [Array(1...9) + [0]].map { "\($0)" } for digit in digits { addKeyCommand(UIKeyCommand(action: #selector(self.selectTabFromKeyCommand), input: digit, @@ -133,16 +135,18 @@ class RootViewController: UIViewController { func addTerminal() { let index = min(selectedTabIndex + 1, terminals.count) - addTerminal(at: index) + addTerminal(at: index, initialCommand: initialCommand) selectTerminal(at: index) + initialCommand = nil } - private func addTerminal(at index: Int, axis: NSLayoutConstraint.Axis? = nil) { + private func addTerminal(at index: Int, axis: NSLayoutConstraint.Axis? = nil, initialCommand: String? = nil) { let splitViewController = TerminalSplitViewController() splitViewController.view.autoresizingMask = [ .flexibleWidth, .flexibleHeight ] splitViewController.view.frame = view.bounds let newTerminal = TerminalSessionViewController() + newTerminal.initialCommand = initialCommand addChild(splitViewController) splitViewController.willMove(toParent: self) diff --git a/App/View Controllers/TerminalSessionViewController.swift b/App/View Controllers/TerminalSessionViewController.swift index f261ed0..5e03d28 100644 --- a/App/View Controllers/TerminalSessionViewController.swift +++ b/App/View Controllers/TerminalSessionViewController.swift @@ -23,6 +23,8 @@ class TerminalSessionViewController: UIViewController, TerminalSplitViewControll fatalError("Couldn’t initialise bell sound") }() + var initialCommand: String? + var isSplitViewResizing = false { didSet { updateIsSplitViewResizing() } } @@ -107,24 +109,6 @@ class TerminalSessionViewController: UIViewController, TerminalSplitViewControll modifierFlags: [ .command, .alternate ])) #endif - if #available(iOS 13.4, *) { - // Handled by TerminalKeyInput - } else { - addKeyCommand(UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(TerminalKeyInput.upKeyPressed))) - addKeyCommand(UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(TerminalKeyInput.downKeyPressed))) - addKeyCommand(UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: [], action: #selector(TerminalKeyInput.leftKeyPressed))) - addKeyCommand(UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(TerminalKeyInput.rightKeyPressed))) - addKeyCommand(UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(TerminalKeyInput.metaKeyPressed))) - - let letters = [ - "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", - "s", "t", "u", "v", "w", "x", "y", "z" - ] - for key in letters { - addKeyCommand(UIKeyCommand(input: key, modifierFlags: [ .control ], action: #selector(TerminalKeyInput.ctrlKeyCommandPressed(_:)))) - } - } - if UIApplication.shared.supportsMultipleScenes { NotificationCenter.default.addObserver(self, selector: #selector(self.sceneDidEnterBackground), name: UIWindowScene.didEnterBackgroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.sceneWillEnterForeground), name: UIWindowScene.willEnterForegroundNotification, object: nil) @@ -145,7 +129,13 @@ class TerminalSessionViewController: UIViewController, TerminalSplitViewControll if let error = failureError { didReceiveError(error: error) + } else { + if let initialCommand = initialCommand?.data(using: .utf8) { + terminalController.write(initialCommand + EscapeSequences.return) + } } + + initialCommand = nil } override func viewWillDisappear(_ animated: Bool) { diff --git a/Common/Controllers/TerminalController.swift b/Common/Controllers/TerminalController.swift index c9c4731..f774abe 100644 --- a/Common/Controllers/TerminalController.swift +++ b/Common/Controllers/TerminalController.swift @@ -266,7 +266,7 @@ public class TerminalController { } if let hostname = hostname { let user = self.user == NSUserName() ? nil : self.user - let cleanedHostname = hostname.replacingOccurrences(of: "\\.local$", with: "", options: .regularExpression, range: hostname.startIndex..