├── .github └── workflows │ └── objective-c-xcode.yml ├── .gitmodules ├── LICENSE ├── README.md ├── SwiftTermApp.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved ├── SwiftTermApp ├── AppDelegate.swift ├── Assets.xcassets │ ├── .DS_Store │ ├── Accent Color.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 128.png │ │ ├── 128@2x.png │ │ ├── 16.png │ │ ├── 16@2x.png │ │ ├── 256.png │ │ ├── 256@2x.png │ │ ├── 32.png │ │ ├── 32@2x.png │ │ ├── 512.png │ │ ├── 512@2x.png │ │ ├── App_store_1024_1x.png │ │ ├── Contents.json │ │ ├── iPad_App_76_1x.png │ │ ├── iPad_App_76_2x.png │ │ ├── iPad_Notifications_20_1x.png │ │ ├── iPad_Notifications_20_2x.png │ │ ├── iPad_Pro_App_83.5_2x.png │ │ ├── iPad_Settings_29_1x.png │ │ ├── iPad_Settings_29_2x.png │ │ ├── iPad_Spotlight_40_1x.png │ │ ├── iPad_Spotlight_40_2x.png │ │ ├── iPhone_App_60_2x.png │ │ ├── iPhone_App_60_3x.png │ │ ├── iPhone_Notifications_20_2x.png │ │ ├── iPhone_Notifications_20_3x.png │ │ ├── iPhone_Settings_29_2x.png │ │ ├── iPhone_Settings_29_3x.png │ │ ├── iPhone_Spotlight_40_2x.png │ │ └── iPhone_Spotlight_40_3x.png │ ├── Contents.json │ ├── Debug Accent Color.colorset │ │ └── Contents.json │ ├── I.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 128.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 16.png │ │ ├── 167.png │ │ ├── 172.png │ │ ├── 180.png │ │ ├── 196.png │ │ ├── 20.png │ │ ├── 216.png │ │ ├── 256.png │ │ ├── 29.png │ │ ├── 32.png │ │ ├── 40.png │ │ ├── 48.png │ │ ├── 50.png │ │ ├── 512.png │ │ ├── 55.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 64.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ ├── 88.png │ │ └── Contents.json │ ├── apple.imageset │ │ ├── Contents.json │ │ ├── apple-1.pdf │ │ └── apple.pdf │ ├── fedora.imageset │ │ ├── Contents.json │ │ ├── fedora-1.pdf │ │ └── fedora.pdf │ ├── freebsd.imageset │ │ ├── Contents.json │ │ └── freebsd.pdf │ ├── linux.imageset │ │ ├── Contents.json │ │ ├── linux-1.pdf │ │ └── linux.pdf │ ├── netbsd.imageset │ │ ├── Contents.json │ │ ├── netbsd-1.pdf │ │ └── netbsd.pdf │ ├── raspberry-pi.imageset │ │ ├── Contents.json │ │ ├── raspberry-pi-1.pdf │ │ └── raspberry-pi.pdf │ ├── redhat.imageset │ │ ├── Contents.json │ │ ├── redhat-1.pdf │ │ └── redhat.pdf │ ├── server.imageset │ │ ├── Contents.json │ │ ├── server-1.pdf │ │ └── server.pdf │ ├── suse.imageset │ │ ├── Contents.json │ │ ├── suse-1.pdf │ │ └── suse.pdf │ ├── ubuntu.imageset │ │ ├── Contents.json │ │ ├── ubuntu-1.pdf │ │ └── ubuntu.pdf │ └── windows.imageset │ │ ├── Contents.json │ │ ├── windows-1.pdf │ │ └── windows.pdf ├── Base.lproj │ └── LaunchScreen.storyboard ├── Command │ └── CommandPicker.swift ├── Commands.swift ├── Connections.swift ├── DataExtensions.swift ├── DataStore.swift ├── External │ ├── SwCrypt │ │ └── SwCrypt.swift │ └── SwiftyRSA │ │ ├── Asn1Parser.swift │ │ └── PEMtoSSH.swift ├── FirstRun │ ├── Onboard.swift │ └── OnboardWelcome.swift ├── Fonts │ ├── SourceCodePro-Bold.ttf │ ├── SourceCodePro-BoldItalic.ttf │ ├── SourceCodePro-Italic.ttf │ └── SourceCodePro-Medium.ttf ├── History │ ├── History.xcdatamodeld │ │ └── History.xcdatamodel │ │ │ └── contents │ ├── HistoryController.swift │ └── HistoryView.swift ├── Home │ ├── CreditsView.swift │ ├── DefaultHomeView.swift │ ├── Home.swift │ └── RecentHostsView.swift ├── HostKeys │ └── HostKeysList.swift ├── Hosts │ ├── EnvironmentVariableEdit.swift │ ├── EnvironmentVariables.swift │ ├── HostEditView.swift │ └── Hosts.swift ├── Info.plist ├── Keys │ ├── AddKeyManually.swift │ ├── CreateLocalKeyButtons.swift │ ├── GenerateKey.swift │ ├── GenerateSecureEnclave.swift │ ├── KeyManagementView.swift │ ├── KeySummaryView.swift │ ├── KeyTools.swift │ ├── KeychainTools.swift │ └── SshUtil.swift ├── Licenses │ ├── libssh.txt │ ├── openssl_1_1_1h.txt │ ├── source-code-pro.txt │ ├── swcrypt.txt │ ├── swiftterm.txt │ └── swiftui-introspect.txt ├── Location │ └── LocationTracking.swift ├── Main.xcdatamodeld │ └── Main.xcdatamodel │ │ └── contents ├── Metal │ ├── DigitalBrain.metal │ ├── MetalHost.swift │ ├── MetalSwiftUI.swift │ ├── PlasmaGlobe.metal │ ├── ShadersBase.metal │ ├── StarNest.metal │ └── SwiftTermShaders.h ├── Model │ ├── CHost-DataHelpers.swift │ ├── CKey-DataHelpers.swift │ ├── CUserSnippet-DataHelper.swift │ ├── DataController.swift │ ├── DataModelTypes.swift │ └── MemoryModels.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── QuickLauch.swift ├── SceneDelegate.swift ├── Secrets.swift ├── Sessions │ └── SessionsView.swift ├── Settings │ ├── ColorLoader.swift │ ├── ColumnSelector.swift │ └── SettingsView.swift ├── Snippets │ ├── SnippetBrowser.swift │ ├── SnippetEditor.swift │ └── SnippetSummary.swift ├── Ssh │ ├── Channel.swift │ ├── Errors.swift │ ├── LibsshKnownHost.swift │ ├── SFTP.swift │ ├── Session.swift │ └── SessionActor.swift ├── Stats │ └── HostStatusDetail.swift ├── SwiftTermApp-Bridging-Header.h ├── SwiftTermApp.entitlements ├── Terminal │ ├── AppTerminalView.swift │ ├── ConfigurableTerminal.swift │ ├── InputKeyboard.swift │ ├── SshTerminalView.swift │ └── TerminalViewController.swift ├── UIs │ ├── ButtonColors.swift │ ├── Colors.swift │ ├── ConnectionUI │ │ ├── ConnectionMessage.swift │ │ ├── GenericConnectionIssue.swift │ │ ├── HostAuthKeyMismatch.swift │ │ ├── HostAuthUnknown.swift │ │ ├── HostConnectionClosed.swift │ │ └── HostConnectionError.swift │ ├── Dialogs.swift │ ├── DirectorySelector.swift │ ├── InteractiveLogin.swift │ ├── LazyView.swift │ ├── Passphrase.swift │ ├── STButton.swift │ ├── STFilePicker.swift │ ├── SideHostStatus.swift │ ├── UIKitRoot.swift │ └── UIViewController+SwiftUI.swift ├── Utilities │ └── DateFormatting.swift └── es-419.lproj │ ├── InfoPlist.strings │ ├── LaunchScreen.strings │ └── Localizable.strings ├── THIRD_PARTY_NOTICES.md ├── UITests ├── UITests.swift └── UITestsLaunchTests.swift └── ci_scripts └── ci_pre_xcodebuild.sh /.github/workflows/objective-c-xcode.yml: -------------------------------------------------------------------------------- 1 | name: Xcode - Build and Analyze 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | name: Build and analyse default scheme using xcodebuild command 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: Build 18 | run: | 19 | xcodebuild clean build analyze CODE_SIGNING_REQUIRED=NO -allowProvisioningUpdates -scheme SwiftTermApp -project SwiftTermApp.xcodeproj | xcpretty && exit ${PIPESTATUS[0]} 20 | 21 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/.gitmodules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Miguel de Icaza (https://github.com/migueldeicaza) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftTermApp 2 | 3 | SwiftTermApp is a terminal emulator and SSH client application for iOS 4 | using the [SwiftTerm](https://github.com/migueldeicaza/SwiftTerm) engine and written in SwiftUI. It is the open 5 | core of [La Terminal](https://apps.apple.com/us/app/la-terminal-ssh-client/id1629902861), available in the 6 | App Store for iPhone and iPad. 7 | 8 | This repository is not actively updated, but you can still use it as sample code, but I wont like be fixing 9 | or updating it. 10 | 11 | 12 | Some of the features: 13 | * Comprehensive terminal emulator, based on SwiftTerm - which is both very comprehensive in terms of emulation, 14 | and has extensive support for international terminals, both for generated output as well as the support for 15 | iOS input methods for dictation and international input. 16 | * Supports for keys stored on the secure enclave, so your private key can never be found in plain text. 17 | * Themable user interface: 18 | * Using the user interface provided or 19 | * Programatically, through the terminal support for configuring colors dynamically. 20 | * Metal shaders for some cool live effects 21 | 22 | ![Screenshot](https://user-images.githubusercontent.com/36863/81033655-645d5980-8e62-11ea-91c5-1d8b1931c7ce.png) 23 | 24 | ![Screenshot](https://user-images.githubusercontent.com/36863/82780270-a441ac00-9e24-11ea-9ee1-e32357e8ab58.png) 25 | 26 | Animated Metal Backgrounds: 27 | 28 | image 29 | 30 | Configure your settings: 31 | 32 | image 33 | 34 | Some icons are from FontAwesome 35 | -------------------------------------------------------------------------------- /SwiftTermApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftTermApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftTermApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "libssh2prebuild", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/migueldeicaza/Libssh2Prebuild", 7 | "state" : { 8 | "branch" : "master", 9 | "revision" : "2b9a34880aa7906a76872ac03077ea5aa313c72b" 10 | } 11 | }, 12 | { 13 | "identity" : "shake-ios", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/shakebugs/shake-ios", 16 | "state" : { 17 | "branch" : "master", 18 | "revision" : "c94e4c9437f1c1f8090f494a80ef7a1345f5fa1c" 19 | } 20 | }, 21 | { 22 | "identity" : "swiftterm", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/migueldeicaza/SwiftTerm.git", 25 | "state" : { 26 | "branch" : "main", 27 | "revision" : "7976db83cbc8729c3b1ade6aa9cf990445164354" 28 | } 29 | }, 30 | { 31 | "identity" : "swiftui-introspect", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/siteline/SwiftUI-Introspect.git", 34 | "state" : { 35 | "revision" : "f2616860a41f9d9932da412a8978fec79c06fe24", 36 | "version" : "0.1.4" 37 | } 38 | } 39 | ], 40 | "version" : 2 41 | } 42 | -------------------------------------------------------------------------------- /SwiftTermApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // 4 | // Created by Miguel de Icaza on 4/25/20. 5 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 6 | // 7 | 8 | import SwiftUI 9 | //import Shake 10 | 11 | /// Databases (both configurations, local and cloudKit are here) 12 | var globalDataController = DataController () 13 | 14 | @main 15 | struct SampleApp: App { 16 | @State var dates = [Date]() 17 | @State var launchHost: Host? 18 | @StateObject var dataController: DataController 19 | @Environment(\.scenePhase) var scenePhase 20 | 21 | func extendLifetime () { 22 | // Attempts to keep the app alive, so our sockets are not killed within one second of going into the background 23 | var backgroundHandle: UIBackgroundTaskIdentifier? = nil 24 | backgroundHandle = UIApplication.shared.beginBackgroundTask(withName: "lifetime extender") { 25 | if let handle = backgroundHandle { 26 | UIApplication.shared.endBackgroundTask(handle) 27 | } 28 | } 29 | } 30 | init () { 31 | // if shakeKey != "" { 32 | // Shake.configuration.isCrashReportingEnabled = true 33 | // Shake.configuration.isAskForCrashDescriptionEnabled = true 34 | // Shake.start(clientId: shakeId, clientSecret: shakeKey) 35 | // if let userId = UIDevice.current.identifierForVendor?.uuidString { 36 | // Shake.registerUser(userId: userId) 37 | // } 38 | // } 39 | if settings.locationTrack { 40 | locationTrackerStart() 41 | } 42 | // for family in UIFont.familyNames.sorted() { 43 | // let names = UIFont.fontNames(forFamilyName: family) 44 | // print("Family: \(family) Font names: \(names)") 45 | // } 46 | // print ("here") 47 | 48 | _dataController = StateObject(wrappedValue: globalDataController) 49 | } 50 | 51 | var body: some Scene { 52 | WindowGroup { 53 | ContentView() 54 | .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in 55 | if Connections.shared.sessions.count > 0 { 56 | locationTrackerResume() 57 | } 58 | } 59 | .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in 60 | locationTrackerSuspend() 61 | } 62 | .onChange(of: scenePhase) { newPhase in 63 | if newPhase == .background { 64 | extendLifetime () 65 | } 66 | } 67 | .environment(\.managedObjectContext, dataController.container.viewContext) 68 | .environmentObject(dataController) 69 | 70 | } 71 | .commands { 72 | TerminalCommands() 73 | } 74 | #if os(macOS) 75 | Settings { 76 | Text ("These are the macOS settings") 77 | } 78 | #endif 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/Accent Color.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "246", 9 | "green" : "120", 10 | "red" : "51" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "247", 27 | "green" : "130", 28 | "red" : "58" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/128@2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/16@2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/256@2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/32@2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/512@2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/App_store_1024_1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/App_store_1024_1x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_App_76_1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_App_76_1x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_App_76_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_App_76_2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Notifications_20_1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Notifications_20_1x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Notifications_20_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Notifications_20_2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Pro_App_83.5_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Pro_App_83.5_2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Settings_29_1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Settings_29_1x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Settings_29_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Settings_29_2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Spotlight_40_1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Spotlight_40_1x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Spotlight_40_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPad_Spotlight_40_2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_App_60_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_App_60_2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_App_60_3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_App_60_3x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Notifications_20_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Notifications_20_2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Notifications_20_3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Notifications_20_3x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Settings_29_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Settings_29_2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Settings_29_3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Settings_29_3x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Spotlight_40_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Spotlight_40_2x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Spotlight_40_3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/AppIcon.appiconset/iPhone_Spotlight_40_3x.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/Debug Accent Color.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "61", 9 | "green" : "77", 10 | "red" : "235" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "69", 27 | "green" : "85", 28 | "red" : "235" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/100.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/1024.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/114.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/120.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/128.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/144.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/152.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/16.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/167.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/172.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/180.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/196.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/20.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/216.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/216.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/256.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/29.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/32.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/40.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/48.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/50.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/512.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/55.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/57.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/58.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/60.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/64.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/72.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/76.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/80.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/87.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/I.appiconset/88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/I.appiconset/88.png -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/apple.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "apple.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "apple-1.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/apple.imageset/apple-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/apple.imageset/apple-1.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/apple.imageset/apple.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/apple.imageset/apple.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/fedora.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "fedora.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "fedora-1.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/fedora.imageset/fedora-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/fedora.imageset/fedora-1.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/fedora.imageset/fedora.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/fedora.imageset/fedora.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/freebsd.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "freebsd.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "idiom" : "universal" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/freebsd.imageset/freebsd.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/freebsd.imageset/freebsd.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/linux.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "linux.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "linux-1.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/linux.imageset/linux-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/linux.imageset/linux-1.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/linux.imageset/linux.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/linux.imageset/linux.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/netbsd.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "netbsd.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "netbsd-1.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/netbsd.imageset/netbsd-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/netbsd.imageset/netbsd-1.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/netbsd.imageset/netbsd.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/netbsd.imageset/netbsd.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/raspberry-pi.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "raspberry-pi.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "raspberry-pi-1.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/raspberry-pi.imageset/raspberry-pi-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/raspberry-pi.imageset/raspberry-pi-1.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/raspberry-pi.imageset/raspberry-pi.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/raspberry-pi.imageset/raspberry-pi.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/redhat.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "redhat.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "redhat-1.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/redhat.imageset/redhat-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/redhat.imageset/redhat-1.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/redhat.imageset/redhat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/redhat.imageset/redhat.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/server.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "server.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "server-1.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/server.imageset/server-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/server.imageset/server-1.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/server.imageset/server.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/server.imageset/server.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/suse.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "suse.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "suse-1.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/suse.imageset/suse-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/suse.imageset/suse-1.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/suse.imageset/suse.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/suse.imageset/suse.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/ubuntu.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ubuntu.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "ubuntu-1.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/ubuntu.imageset/ubuntu-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/ubuntu.imageset/ubuntu-1.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/ubuntu.imageset/ubuntu.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/ubuntu.imageset/ubuntu.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/windows.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "windows.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "windows-1.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/windows.imageset/windows-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/windows.imageset/windows-1.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Assets.xcassets/windows.imageset/windows.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Assets.xcassets/windows.imageset/windows.pdf -------------------------------------------------------------------------------- /SwiftTermApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftTermApp/Command/CommandPicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommandPicker.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 3/25/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct CommandPicker: View { 12 | @EnvironmentObject var dataController: DataController 13 | private var snippets: FetchRequest 14 | @Environment(\.managedObjectContext) var moc 15 | @Environment(\.dismiss) private var dismiss 16 | @State var terminalGetter: ()->AppTerminalView? 17 | @State private var searchText = "" 18 | 19 | init (terminalGetter: @escaping ()->AppTerminalView?) { 20 | snippets = FetchRequest(entity: CUserSnippet.entity(), sortDescriptors: [ 21 | NSSortDescriptor(keyPath: \CUserSnippet.sTitle, ascending: true) 22 | ]) 23 | self._terminalGetter = State (initialValue: terminalGetter) 24 | } 25 | 26 | var body: some View { 27 | List { 28 | ForEach(searchResults, id: \.id) { snippet in 29 | SnippetSummary(snippet: snippet) 30 | .onTapGesture { 31 | guard let terminal = terminalGetter () else { 32 | return 33 | } 34 | dismiss() 35 | terminal.send(txt: snippet.command) 36 | } 37 | } 38 | } 39 | .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always)) 40 | .toolbar { 41 | ToolbarItem (placement: .navigationBarLeading) { 42 | Button ("Dismiss") { 43 | dismiss() 44 | } 45 | } 46 | } 47 | } 48 | 49 | var searchResults: [CUserSnippet] { 50 | if searchText.isEmpty { 51 | return snippets.wrappedValue.map { $0 } 52 | } else { 53 | return snippets.wrappedValue.filter { snippet in 54 | snippet.title.localizedCaseInsensitiveContains(searchText) || snippet.command.localizedCaseInsensitiveContains (searchText) 55 | } 56 | } 57 | } 58 | } 59 | 60 | struct CommandPicker_Previews: PreviewProvider { 61 | static var previews: some View { 62 | CommandPicker { return nil } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /SwiftTermApp/Connections.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Connections.swift 3 | // testMasterDetail 4 | // 5 | // Created by Miguel de Icaza on 4/28/20. 6 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | import UIKit 12 | import SwiftTerm 13 | 14 | /// 15 | /// Tracks the active SSH Sessions, which often contain one or more terminals. 16 | /// 17 | /// Note: terminal views are typically created first, and tracked before a session is created, which only takes place later 18 | /// 19 | class Connections: ObservableObject { 20 | public static var shared: Connections = Connections() 21 | 22 | @Published public var sessions: [Session] = [ 23 | ] 24 | 25 | public var terminalsCount: Int { 26 | dispatchPrecondition(condition: .onQueue(DispatchQueue.main)) 27 | 28 | var count = 0 29 | for session in sessions { 30 | count += session.terminals.count 31 | } 32 | return count 33 | } 34 | 35 | public func active () -> Bool { 36 | return sessions.count > 0 37 | } 38 | 39 | // Returns a newly allocated array with all active terminals 40 | public func getTerminals () -> [SshTerminalView] { 41 | dispatchPrecondition(condition: .onQueue(DispatchQueue.main)) 42 | 43 | return sessions.flatMap { $0.terminals } 44 | } 45 | 46 | /// Tracks the terminal 47 | public static func track (session: Session) 48 | { 49 | DispatchQueue.main.async { 50 | if shared.sessions.contains(session) { 51 | return 52 | } 53 | shared.sessions.append(session) 54 | 55 | // This is used to track whether we should keep the display on, only when we have active sessions 56 | settings.updateKeepOn() 57 | } 58 | } 59 | 60 | public static func lookupActiveTerminal (host: Host) -> SshTerminalView? 61 | { 62 | dispatchPrecondition(condition: .onQueue(DispatchQueue.main)) 63 | if let session = lookupActiveSession(host: host) { 64 | return session.terminals.first 65 | } 66 | return nil 67 | } 68 | 69 | public static func unregister (session: Session) { 70 | DispatchQueue.main.async { 71 | if let idx = shared.sessions.firstIndex(of: session) { 72 | shared.sessions.remove(at: idx) 73 | } 74 | settings.updateKeepOn() 75 | } 76 | } 77 | 78 | 79 | public static func lookupActiveSession (host: Host) -> Session? 80 | { 81 | dispatchPrecondition(condition: .onQueue(DispatchQueue.main)) 82 | return shared.sessions.first { $0.host.id == host.id } 83 | } 84 | 85 | // struct ConnectionState: Encodable, Decodable { 86 | // var hostId: UUID 87 | // var reconnectType: String 88 | // 89 | // // The serial associated with the host that we are saving 90 | // var serial: Int 91 | // } 92 | // 93 | // // Saves all the connections that could be restored 94 | // public static func saveState () { 95 | // guard let d = DataStore.shared.defaults else { 96 | // return 97 | // } 98 | // var state: [ConnectionState] = [] 99 | // for x in shared.connections { 100 | // guard x.host.reconnectType != "" else { continue } 101 | // state.append (ConnectionState (hostId: x.host.id, reconnectType: x.host.reconnectType, serial: x.serial)) 102 | // } 103 | // guard state.count > 0 else { return } 104 | // let coder = JSONEncoder () 105 | // if let encoded = try? coder.encode(state) { 106 | // d.set (encoded, forKey: DataStore.shared.connectionsArrayKey) 107 | // } 108 | // d.synchronize() 109 | // } 110 | // 111 | // public static func getRestorableConnections () -> [ConnectionState] { 112 | // guard let d = DataStore.shared.defaults else { 113 | // return [] 114 | // } 115 | // let decoder = JSONDecoder () 116 | // 117 | // if let data = d.data(forKey: DataStore.shared.connectionsArrayKey) { 118 | // if let h = try? decoder.decode ([ConnectionState].self, from: data) { 119 | // return h 120 | // } 121 | // } 122 | // return [] 123 | // } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /SwiftTermApp/DataExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataExtensions.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 2/7/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Data { 12 | public func getDump(indent: String = "") -> String { 13 | let res = self.withUnsafeBytes { data -> String in 14 | var hexstr = String() 15 | var txt = String () 16 | var n = 0 17 | for i in data.bindMemory(to: UInt8.self) { 18 | if (n % 16) == 0 { 19 | hexstr += " \(txt)\n" + String (format: "%04x: ", n) 20 | txt = "" 21 | } 22 | n += 1 23 | hexstr += String(format: "%02X ", i) 24 | txt += (i > 32 && i < 127 ? String (Unicode.Scalar (i)) : ".") 25 | } 26 | hexstr += " \(txt)" 27 | return hexstr.replacingOccurrences(of: "\n", with: "\n\(indent)") 28 | } 29 | return res 30 | } 31 | 32 | public func dump() { 33 | print (getDump ()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SwiftTermApp/External/SwiftyRSA/PEMtoSSH.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PEMtoSSH.swift - from https://github.com/App-Maker-Software/GitProviders/blob/main/Sources/GitProviders/Work/Crypto/PEMToSSHPublicKeyFormat.swift 3 | // Which is licensed under the MIT license 4 | // 5 | // 6 | 7 | import Foundation 8 | 9 | /// Takes a public RSA key produced by CC.RSA.generateKeyPair and returns it in an ssh public key compaible form 10 | func publicPEMKeyToSSHFormat(data: Data) throws -> String { 11 | let node = try! Asn1Parser.parse(data: data) 12 | 13 | // Ensure the raw data is an ASN1 sequence 14 | guard case .sequence(let nodes) = node else { 15 | throw NSError() 16 | } 17 | 18 | let RSA_HEADER = "ssh-rsa" 19 | 20 | var ssh:String = RSA_HEADER + " " 21 | var rsaBytes:Data = Data() 22 | 23 | // Get size of the header 24 | var byteCount: UInt32 = UInt32(RSA_HEADER.count).bigEndian 25 | var sizeData = Data(bytes: &byteCount, count: MemoryLayout.size(ofValue: byteCount)) 26 | 27 | // Append size of header and content of header 28 | rsaBytes.append(sizeData) 29 | rsaBytes += RSA_HEADER.data(using: .utf8)! 30 | 31 | // Get the exponent 32 | if let exp = nodes.last, case .integer(let exponent) = exp { 33 | // Get size of exponent 34 | byteCount = UInt32(exponent.count).bigEndian 35 | sizeData = Data(bytes: &byteCount, count: MemoryLayout.size(ofValue: byteCount)) 36 | 37 | // Append size of exponent and content of exponent 38 | rsaBytes.append(sizeData) 39 | rsaBytes += exponent 40 | } 41 | else{ 42 | throw NSError() 43 | } 44 | 45 | // Get the modulus 46 | if let mod = nodes.first, case .integer(let modulus) = mod { 47 | // Get size of modulus 48 | byteCount = UInt32(modulus.count).bigEndian 49 | sizeData = Data(bytes: &byteCount, count: MemoryLayout.size(ofValue: byteCount)) 50 | 51 | // Append size of modulus and content of modulus 52 | rsaBytes.append(sizeData) 53 | rsaBytes += modulus 54 | } 55 | else{ 56 | throw NSError() 57 | } 58 | 59 | ssh += rsaBytes.base64EncodedString() + "\n" 60 | return ssh 61 | } 62 | -------------------------------------------------------------------------------- /SwiftTermApp/FirstRun/Onboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirstRunWelcome.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 4/4/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct OnboardView: View { 12 | let last = 4 13 | @State var selection = 1 14 | @State var host = "" 15 | @State var port = "" 16 | 17 | var body: some View { 18 | VStack { 19 | TabView (selection: $selection) { 20 | OnboardWelcome (showOnboarding: .constant (true)) 21 | Text ("Third") 22 | Text ("Fourth") 23 | } 24 | HStack { 25 | Spacer() 26 | Button { 27 | withAnimation { 28 | if selection == 0 { 29 | selection += 1 30 | } 31 | } 32 | } label: { 33 | HStack { 34 | Text ("Continue") 35 | .fontWeight(.semibold) 36 | .font(.title) 37 | if selection == last { 38 | Image(systemName: "checkmark.circle") 39 | .font(.largeTitle) 40 | } else { 41 | Image(systemName: "chevron.right.circle") 42 | .font(.largeTitle) 43 | } 44 | }.padding() 45 | .foregroundColor(.white) 46 | .background(Color.primary) 47 | .cornerRadius(20) 48 | 49 | } 50 | } 51 | .padding() 52 | .foregroundColor(.primary) 53 | } 54 | } 55 | } 56 | 57 | struct FirstRunWelcome_Previews: PreviewProvider { 58 | static var previews: some View { 59 | OnboardView() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SwiftTermApp/FirstRun/OnboardWelcome.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Welcome.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 4/4/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct OnboardWelcome: View { 12 | @Binding var showOnboarding: Bool 13 | 14 | var body: some View { 15 | VStack (alignment: .leading){ 16 | HStack (alignment: .top){ 17 | Image (systemName: "terminal.fill") 18 | .font (.system(size: 72)) 19 | VStack (alignment: .leading){ 20 | Text ("Welcome to SwiftTerm") 21 | .fontWeight(.semibold) 22 | .font (.title) 23 | Text ("A terminal emulator and SSH client for iOS") 24 | .font (.title2) 25 | } 26 | } 27 | VStack { 28 | HStack (alignment: .top){ 29 | Image (systemName: "desktopcomputer") 30 | .font(.system(size: 30)) 31 | .foregroundColor(Color.accentColor) 32 | .frame(minWidth: 50) 33 | Text ("To get started, create a new host definition that includes the address of the machine you want to connect to, and the username to login as.") 34 | .minimumScaleFactor(0.7) 35 | Spacer () 36 | }.padding () 37 | 38 | HStack (alignment: .top){ 39 | Image (systemName: "key") 40 | .font(.system(size: 30)) 41 | .foregroundColor(Color.accentColor) 42 | .frame(minWidth: 50) 43 | Text ("You can then import existing SSH keys, or create new ones.") 44 | .minimumScaleFactor(0.7) 45 | Spacer () 46 | }.padding() 47 | 48 | HStack (alignment: .top){ 49 | Image (systemName: "questionmark") 50 | .font(.system(size: 30)) 51 | .foregroundColor(Color.accentColor) 52 | .frame(minWidth: 50) 53 | Text ("Need help? Shake your device or use the 'Support' menu option to report a bug or to share feedback or a feature request.") 54 | .minimumScaleFactor(0.7) 55 | Spacer () 56 | }.padding() 57 | Button ("Dismiss") { 58 | showOnboarding = false 59 | } 60 | } 61 | } 62 | .padding () 63 | } 64 | } 65 | 66 | struct Welcome_Previews: PreviewProvider { 67 | static var previews: some View { 68 | OnboardWelcome (showOnboarding: .constant(true)) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /SwiftTermApp/Fonts/SourceCodePro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Fonts/SourceCodePro-Bold.ttf -------------------------------------------------------------------------------- /SwiftTermApp/Fonts/SourceCodePro-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Fonts/SourceCodePro-BoldItalic.ttf -------------------------------------------------------------------------------- /SwiftTermApp/Fonts/SourceCodePro-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Fonts/SourceCodePro-Italic.ttf -------------------------------------------------------------------------------- /SwiftTermApp/Fonts/SourceCodePro-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migueldeicaza/SwiftTermApp/958b1b546cafef0c90aa73fc71560540fe06253a/SwiftTermApp/Fonts/SourceCodePro-Medium.ttf -------------------------------------------------------------------------------- /SwiftTermApp/History/History.xcdatamodeld/History.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SwiftTermApp/History/HistoryController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HistoryRecord.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 3/30/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | import CoreData 12 | 13 | // Simplified version of a CLLocation, that can be serialized 14 | struct HistoryLocation: Codable, Identifiable { 15 | var id = UUID() 16 | var latitude: CLLocationDegrees 17 | var longitude: CLLocationDegrees 18 | 19 | var coordinate: CLLocationCoordinate2D { 20 | CLLocationCoordinate2D(latitude: latitude, longitude: longitude) 21 | } 22 | } 23 | 24 | let historyEncoder = JSONEncoder() 25 | let historyDecoder = JSONDecoder() 26 | 27 | enum HistoryOperation: Codable { 28 | case none 29 | case connected (at: HistoryLocation?) 30 | case disconnected (at: HistoryLocation?) 31 | case moved (newLocation: HistoryLocation?) 32 | 33 | func getAsData () -> Data { 34 | return (try? historyEncoder.encode(self)) ?? Data() 35 | } 36 | } 37 | 38 | extension HistoryRecord { 39 | // Used to 40 | @objc var renderedDate: String { 41 | guard let date = date else { 42 | return "Unknown" 43 | } 44 | return dateMediumFormatter.string(from: date) 45 | } 46 | 47 | var alias: String { 48 | guard let hid = hostId else { return "" } 49 | if let host = globalDataController.lookupHost(id: hid) { 50 | return host.alias 51 | } 52 | return "" 53 | } 54 | 55 | var typedEvent: HistoryOperation { 56 | guard let d = event else { return .none } 57 | guard let v = try? historyDecoder.decode(HistoryOperation.self, from: d) else { 58 | return .none 59 | } 60 | return v 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SwiftTermApp/Home/CreditsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreditsView.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/17/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct LicenseFull: View { 12 | var name: String 13 | var url: String? 14 | var authors: String? 15 | var license: String 16 | 17 | var body: some View { 18 | ScrollView { 19 | VStack (alignment: .leading) { 20 | Text (name).font (.title) 21 | if let authors = authors { 22 | Text ("Created by: \(authors)") 23 | .font (.title2) 24 | } 25 | if let url = url { 26 | HStack { 27 | Text ("Project site: [\(url)](\(url))") 28 | Spacer () 29 | } 30 | } 31 | Text ("") 32 | Text (license) 33 | .font(.system(.body, design: .monospaced)) 34 | .minimumScaleFactor(0.3) 35 | .scaledToFit() 36 | Spacer () 37 | }.padding ([.leading, .trailing]) 38 | } 39 | } 40 | } 41 | 42 | struct LicenseShort: View { 43 | var name: String 44 | var url: String? 45 | var authors: String? 46 | var license: String 47 | 48 | func getLicense () -> String { 49 | if let licenseUrl = Bundle.main.url(forResource:license, withExtension: "txt") { 50 | if let contents = try? String (contentsOf: licenseUrl) { 51 | return contents 52 | } else { 53 | return "Unable to load license" 54 | } 55 | } else { 56 | return "License not found" 57 | } 58 | } 59 | 60 | var body: some View { 61 | NavigationLink (destination: LicenseFull (name: name, url: url, authors: authors, license: getLicense ())) { 62 | VStack { 63 | HStack { 64 | Text ("\(name)") 65 | .bold() 66 | Spacer () 67 | } 68 | if let authors = authors { 69 | HStack { 70 | Text ("Authors: \(authors)") 71 | Spacer () 72 | } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | struct CreditsView: View { 79 | 80 | var body: some View { 81 | VStack { 82 | HStack { 83 | Text ("SwiftTermApp is built using the kind work of many open source developers, these are the projects that are used by SwiftTermApp") 84 | }.padding() 85 | List { 86 | // Seems like I no longer use it? 87 | //LicenseShort (name: "LazyView", authors: "Chris Eidhof", license: "") 88 | LicenseShort (name: "libssh", url: "https://www.libssh2.org", authors: "libssh2 project", license: "libssh") 89 | LicenseShort (name: "OpenSSL", authors: "Eric Young, OpenSSL project", license: "openssl_1_1_1h") 90 | LicenseShort (name: "SwCrypt", authors: "Soyer", license: "swcrypt") 91 | LicenseShort (name: "SwiftTerm", authors: "Miguel de Icaza, others", license: "swiftterm") 92 | LicenseShort (name: "SwiftUI-Introspect", url: "https://github.com/siteline/SwiftUI-Introspect/", authors: "SiteLine", license: "swiftui-introspect") 93 | LicenseShort (name: "Source Code Pro", url: "https://github.com/adobe-fonts/source-code-pro", authors: "Paul D. Hunt, Adobe Inc", license: "source-code-pro") 94 | } 95 | } 96 | } 97 | } 98 | 99 | struct CreditsView_Previews: PreviewProvider { 100 | static var previews: some View { 101 | CreditsView() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /SwiftTermApp/Home/DefaultHomeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultHomeView.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/15/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DefaultHomeView: View { 12 | var body: some View { 13 | SessionsView() 14 | } 15 | } 16 | 17 | struct DefaultHomeView_Previews: PreviewProvider { 18 | static var previews: some View { 19 | DefaultHomeView() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftTermApp/Home/RecentHostsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecentHostsView.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 4/5/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import CoreData 11 | 12 | struct RecentHostsView: View { 13 | @State var limit = 3 14 | @FetchRequest 15 | var hosts: FetchedResults 16 | 17 | init (limit: Int = 3) { 18 | _limit = State (initialValue: limit) 19 | let request: NSFetchRequest = CHost.fetchRequest() 20 | 21 | request.sortDescriptors = [ 22 | NSSortDescriptor(keyPath: \CHost.sLastUsed, ascending: false) 23 | ] 24 | request.predicate = NSPredicate (format: "sLastUsed != nil") 25 | request.fetchLimit = limit 26 | _hosts = FetchRequest(fetchRequest: request) 27 | } 28 | 29 | var body: some View { 30 | ForEach(hosts, id: \.self) { host in 31 | HostSummaryView (host: host) 32 | } 33 | } 34 | } 35 | 36 | struct RecentHostsView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | RecentHostsView() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SwiftTermApp/HostKeys/HostKeysList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HostKeysList.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/17/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct Explanation: View { 12 | @Binding var shown: Bool 13 | 14 | var body: some View { 15 | NavigationView { 16 | VStack (alignment: .leading){ 17 | Text ("SwiftTermApp remembers the digital fingerprints of each host you have connected to. These fingerprints are used to ensure that you do not accidentally log into a malicious host that can steal information from you.\n\nIn some scenarios, when a computer system is reinstalled the keys might change, and you might want to consider removing it from the list of known hosts.\n\nYou can use the fingerprint to visually inspect if the key matches the host you are trying to connect to.") 18 | Spacer () 19 | } 20 | .padding() 21 | 22 | .toolbar { 23 | ToolbarItem (placement: .navigationBarTrailing) { 24 | Button ("Dismiss") { 25 | self.shown = false 26 | } 27 | } 28 | } 29 | } 30 | } 31 | } 32 | struct HostKeysList: View { 33 | @ObservedObject var store: DataStore = DataStore.shared 34 | @State var showInfo = false 35 | 36 | var body: some View { 37 | VStack { 38 | if store.knownHosts.count == 0 { 39 | HStack (alignment: .top){ 40 | Image (systemName: "lock.desktopcomputer") 41 | .font (.title) 42 | Text ("The lists of hosts you have connected to, along with their fingerprints will be listed here. These fingerprints are checked on each connection to ensure that a machine is not swapped behind your back and you get tricked into logging into a machine controlled by an adversary") 43 | .font (.body) 44 | }.padding () 45 | Spacer () 46 | 47 | } else { 48 | List { 49 | ForEach (store.knownHosts) { record in 50 | VStack (alignment: .leading) { 51 | HStack { 52 | Text ("Endpoint:") 53 | Spacer () 54 | Text ("\(record.host)") 55 | } 56 | HStack { 57 | Text ("Key Type:") 58 | Spacer () 59 | Text ("\(record.keyType)") 60 | } 61 | Text ("Key:") 62 | Text ("\(record.key)") 63 | .font(.system(size: 12, weight: .light, design: .monospaced)) 64 | 65 | } 66 | } 67 | .onDelete(perform: delete) 68 | } 69 | } 70 | } 71 | .toolbar { 72 | ToolbarItem (placement: .navigationBarTrailing) { 73 | Button (action: { self.showInfo = true }) { 74 | Image(systemName: "info.circle") 75 | } 76 | } 77 | ToolbarItem (placement: .navigationBarTrailing) { 78 | EditButton() 79 | } 80 | } 81 | .sheet(isPresented: self.$showInfo) { Explanation (shown: self.$showInfo) } 82 | } 83 | 84 | func delete (at offsets: IndexSet) { 85 | DataStore.shared.knownHosts.remove(atOffsets: offsets) 86 | DataStore.shared.saveKnownHosts() 87 | } 88 | } 89 | 90 | struct HostKeysList_Previews: PreviewProvider { 91 | static var previews: some View { 92 | HostKeysList() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /SwiftTermApp/Hosts/EnvironmentVariableEdit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnvironmentVariableEdit.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 3/24/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct EnvironmentVariableEdit: View { 12 | @Binding var variables: [String:String] 13 | @Environment(\.dismiss) private var dismiss 14 | 15 | @State var variable: EnvSelection 16 | @State var newVariable: Bool 17 | 18 | // If it is a new variable, do not allow overriding an existing value 19 | var disableSave: Bool { 20 | get { 21 | (newVariable && variables.keys.contains(variable.name)) || variable.name == "" 22 | } 23 | } 24 | 25 | var body: some View { 26 | NavigationView { 27 | Form { 28 | Section { 29 | VStack (alignment: .leading) { 30 | HStack { 31 | Text ("Variable") 32 | Spacer () 33 | if disableSave { 34 | Button { 35 | print("Edit button was tapped") 36 | } label: { 37 | if variable.name != "" { 38 | Text("Name Conflict") 39 | .foregroundColor(.red) 40 | } 41 | } 42 | } 43 | } 44 | 45 | TextField ("name", text: $variable.name) 46 | .font (.system (.body, design: .monospaced)) 47 | .foregroundColor(newVariable ? .primary : .secondary) 48 | padding() 49 | Text ("Value") 50 | TextField ("value", text: $variable.value) 51 | .lineLimit(3) 52 | .keyboardType(.emailAddress) 53 | .autocapitalization(.none) 54 | .font (.system (.body, design: .monospaced)) 55 | } 56 | } 57 | } 58 | .navigationBarTitleDisplayMode(.inline) 59 | .toolbar { 60 | ToolbarItem (placement: .navigationBarLeading) { 61 | Button ("Cancel") { 62 | dismiss() 63 | } 64 | } 65 | ToolbarItem (placement: .navigationBarTrailing) { 66 | Button("Save") { 67 | // Because we are delaying the hiding of the window, 68 | // disable the check below, as we are already proceeding 69 | // and this hides an ugly artifact for half a second, showing 70 | // that the variable is clashing when it is indeed, not clashing. 71 | variables [variable.name] = variable.value 72 | 73 | dismiss () 74 | }.disabled (disableSave) 75 | } 76 | } 77 | 78 | } 79 | } 80 | } 81 | 82 | struct EnvironmentVariableEdit_Previews: PreviewProvider { 83 | @State static var variables = ["PATH":"/Library/Apple/usr/bin:/Library/Frameworks/Mono.framework/Versions/Current/Commands:/cvs/fuchsia/fuchsia/.jiri_root/bin", "SHELL":"bash" ] 84 | @State static var showVariableEdit = false 85 | 86 | static var previews: some View { 87 | VStack { 88 | EnvironmentVariableEdit(variables: $variables, variable: EnvSelection(name: "MYPATH", value: "/usr/local"), newVariable: true) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /SwiftTermApp/Hosts/EnvironmentVariables.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnvironmentVariables.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 3/23/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct EnvSelection: Identifiable { 12 | var name: String 13 | var value: String 14 | var id: String { return name } 15 | } 16 | 17 | struct EnvironmentVariables: View { 18 | @Binding var variables: [String:String] 19 | @State var activatedItem: EnvSelection? = nil 20 | 21 | func delete (at offsets: IndexSet) { 22 | let ordered = variables.sorted(by: >) 23 | var keys: [String] = [] 24 | for itemOffset in offsets { 25 | if itemOffset < ordered.count { 26 | keys.append (ordered [itemOffset].key) 27 | } 28 | } 29 | for key in keys { 30 | variables.removeValue(forKey: key) 31 | } 32 | } 33 | 34 | var body: some View { 35 | List { 36 | STButton (text: "Add Variable", icon: "plus.circle") { 37 | activatedItem = EnvSelection (name: "", value: "") 38 | } 39 | 40 | Section { 41 | ForEach(variables.sorted(by: >), id: \.key) { key, value in 42 | 43 | VStack (alignment: .leading){ 44 | Text ("\(key)") 45 | .font (.system (.headline, design: .monospaced)) 46 | Text ("\(value)").font(.body) 47 | .font (.system (.body, design: .monospaced)) 48 | .lineLimit(1) 49 | .foregroundColor(.gray) 50 | }.onTapGesture { 51 | activatedItem = EnvSelection (name: key, value: value) 52 | } 53 | } 54 | .onDelete(perform: delete) 55 | } 56 | Section { 57 | Text ("While it is possible to configure environment variables, some servers disable setting environment variables. Check your `sshd_config` file on your server for the *PermitUserEnvironment* setting") 58 | } 59 | } 60 | .listStyle(.grouped) 61 | .navigationTitle(Text("Environment Variables")) 62 | .toolbar { 63 | ToolbarItem (placement: .navigationBarTrailing) { 64 | EditButton () 65 | } 66 | } 67 | .sheet (item: $activatedItem) { item in 68 | EnvironmentVariableEdit(variables: $variables, 69 | variable: item, 70 | newVariable: item.name == "") 71 | } 72 | } 73 | } 74 | 75 | struct EnvironmentVariables_Previews: PreviewProvider { 76 | @State static var variables = ["PATH":"/Library/Apple/usr/bin:/Library/Frameworks/Mono.framework/Versions/Current/Commands:/cvs/fuchsia/fuchsia/.jiri_root/bin", "SHELL":"bash" ] 77 | 78 | static var previews: some View { 79 | EnvironmentVariables(variables: $variables) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /SwiftTermApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BGTaskSchedulerPermittedIdentifiers 6 | 7 | $(PRODUCT_BUNDLE_IDENTIFIER) 8 | 9 | CFBundleDevelopmentRegion 10 | $(DEVELOPMENT_LANGUAGE) 11 | CFBundleDocumentTypes 12 | 13 | 14 | CFBundleTypeName 15 | public.data 16 | LSHandlerRank 17 | None 18 | 19 | 20 | CFBundleTypeName 21 | public.item 22 | LSHandlerRank 23 | None 24 | 25 | 26 | CFBundleTypeName 27 | public.content 28 | LSHandlerRank 29 | None 30 | 31 | 32 | CFBundleExecutable 33 | $(EXECUTABLE_NAME) 34 | CFBundleIdentifier 35 | $(PRODUCT_BUNDLE_IDENTIFIER) 36 | CFBundleInfoDictionaryVersion 37 | 6.0 38 | CFBundleName 39 | $(PRODUCT_NAME) 40 | CFBundlePackageType 41 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 42 | CFBundleShortVersionString 43 | $(MARKETING_VERSION) 44 | CFBundleURLTypes 45 | 46 | 47 | CFBundleTypeRole 48 | Viewer 49 | CFBundleURLName 50 | org.tirania.SwiftTermAppX 51 | CFBundleURLSchemes 52 | 53 | swiftterm 54 | 55 | 56 | 57 | CFBundleTypeRole 58 | Viewer 59 | CFBundleURLName 60 | org.tirania.SwiftTermAppX 61 | CFBundleURLSchemes 62 | 63 | ssh 64 | 65 | 66 | 67 | CFBundleVersion 68 | 1 69 | ITSAppUsesNonExemptEncryption 70 | 71 | LSRequiresIPhoneOS 72 | 73 | LSSupportsOpeningDocumentsInPlace 74 | 75 | NSBonjourServices 76 | 77 | _ssh._tcp 78 | 79 | NSLocalNetworkUsageDescription 80 | Needed to connect to SSH servers on your local network 81 | NSLocationAlwaysAndWhenInUseUsageDescription 82 | By tracking the location, iOS will keep the terminal running and your connection alive 83 | NSLocationWhenInUseUsageDescription 84 | Initial request: By tracking the location, iOS will keep the terminal running and your connection alive 85 | NSMicrophoneUsageDescription 86 | SwiftTermApp needs access to your microphone so you can attach voice notes when reporting an issue 87 | NSPhotoLibraryUsageDescription 88 | SwiftTermApp needs access to your photo library so you can attach images when reporting an issue. 89 | UIAppFonts 90 | 91 | SourceCodePro-Medium.ttf 92 | SourceCodePro-Bold.ttf 93 | SourceCodePro-BoldItalic.ttf 94 | SourceCodePro-Italic.ttf 95 | 96 | UIApplicationSceneManifest 97 | 98 | UIApplicationSupportsMultipleScenes 99 | 100 | UISceneConfigurations 101 | 102 | UIWindowSceneSessionRoleApplication 103 | 104 | 105 | UISceneConfigurationName 106 | Default Configuration 107 | UISceneDelegateClassName 108 | $(PRODUCT_MODULE_NAME).SceneDelegate 109 | 110 | 111 | 112 | 113 | UIBackgroundModes 114 | 115 | fetch 116 | location 117 | processing 118 | remote-notification 119 | 120 | UILaunchStoryboardName 121 | LaunchScreen 122 | UIRequiredDeviceCapabilities 123 | 124 | armv7 125 | 126 | UIStatusBarTintParameters 127 | 128 | UINavigationBar 129 | 130 | Style 131 | UIBarStyleDefault 132 | Translucent 133 | 134 | 135 | 136 | UISupportedInterfaceOrientations 137 | 138 | UIInterfaceOrientationPortrait 139 | UIInterfaceOrientationLandscapeLeft 140 | UIInterfaceOrientationLandscapeRight 141 | 142 | UISupportedInterfaceOrientations~ipad 143 | 144 | UIInterfaceOrientationPortrait 145 | UIInterfaceOrientationPortraitUpsideDown 146 | UIInterfaceOrientationLandscapeLeft 147 | UIInterfaceOrientationLandscapeRight 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /SwiftTermApp/Keys/CreateLocalKeyButtons.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateLocalKeyButtons.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/13/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Security 11 | import CryptoKit 12 | 13 | // 14 | // This either uses the secure enclave to store the key (which is limited to the 15 | // EC key, or an RSA key. 16 | // 17 | struct CreateLocalKeyButtons: View { 18 | @State var showEnclaveGenerator = false 19 | @State var showLocalGenerator = false 20 | @State var addKeyManuallyShown = false 21 | 22 | var body: some View { 23 | LazyVGrid (columns: [GridItem (.adaptive(minimum: 300))]) { 24 | if SecureEnclave.isAvailable { 25 | STButton(text: "Create Enclave Key", icon: "lock.iphone", centered: false) { 26 | self.showEnclaveGenerator = true 27 | } 28 | .keyboardShortcut(KeyEquivalent("e"), modifiers: [.command]) 29 | } 30 | 31 | STButton (text: "Create New Key", icon: "key", centered: false) { 32 | self.showLocalGenerator = true 33 | } 34 | .keyboardShortcut(KeyEquivalent("n"), modifiers: [.command]) 35 | 36 | STButton (text: "Add Existing Key", icon: "plus.circle", centered: false){ 37 | self.addKeyManuallyShown = true 38 | } 39 | .keyboardShortcut(KeyEquivalent("a"), modifiers: [.command]) 40 | } 41 | .sheet(isPresented: self.$showLocalGenerator) { 42 | GenerateKey (showGenerator: self.$showLocalGenerator) 43 | } 44 | .sheet(isPresented: self.$showEnclaveGenerator) { 45 | GenerateSecureEnclave (showGenerator: self.$showEnclaveGenerator) 46 | } 47 | .sheet (isPresented: self.$addKeyManuallyShown) { 48 | AddKeyManually (key: nil) 49 | } 50 | } 51 | } 52 | 53 | struct CreateLocalKeyButtons_Previews: PreviewProvider { 54 | static var previews: some View { 55 | CreateLocalKeyButtons() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /SwiftTermApp/Keys/GenerateSecureEnclave.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenerateSecureEnclave.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/12/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | let secureEnclaveKeyTag = "SwiftTermSecureEnclaveKeyTag" 12 | 13 | struct GenerateSecureEnclave: View { 14 | @EnvironmentObject var dataController: DataController 15 | @Environment(\.managedObjectContext) var moc 16 | @State var title = "SwiftTerm Enclave Key on \(UIDevice.current.name)" 17 | @Binding var showGenerator: Bool 18 | @State var showAlert: Bool = false 19 | 20 | // Externally settable 21 | var keyName: String = "" 22 | 23 | func haveKey (_ keyName: String) -> Bool 24 | { 25 | do { 26 | if try SwKeyStore.getKey(keyName) != "" { 27 | return true 28 | } 29 | } catch { 30 | } 31 | return false 32 | } 33 | 34 | func callGenerateKey () 35 | { 36 | if let generated = KeyTools.generateKey (type: .ecdsa(inEnclave:true), secureEnclaveKeyTag: secureEnclaveKeyTag, comment: title, passphrase: "") { 37 | let _ = CKey (context: moc, blueprint: generated) 38 | dataController.save () 39 | } 40 | } 41 | 42 | var body: some View { 43 | NavigationView { 44 | VStack { 45 | List { 46 | Section (header: Text ("Secure Enclave Key")) { 47 | HStack { 48 | Text ("Type") 49 | Spacer () 50 | Text ("ecdsa-sha2-nistp256") 51 | .foregroundColor(.gray) 52 | } 53 | HStack { 54 | Text ("Private Key Storage") 55 | Spacer () 56 | Text ("Secure Enclave") 57 | .foregroundColor(.gray) 58 | } 59 | 60 | } 61 | Section { 62 | VStack (alignment: .leading) { 63 | Text ("Comment") 64 | TextField ("", text: self.$title) 65 | 66 | } 67 | } 68 | } 69 | .listStyle(GroupedListStyle ()) 70 | } 71 | .environment(\.horizontalSizeClass, .regular) 72 | .toolbar { 73 | ToolbarItem (placement: .navigationBarLeading) { 74 | Button ("Cancel") { 75 | self.showGenerator = false 76 | } 77 | } 78 | ToolbarItem (placement: .navigationBarTrailing) { 79 | Button("Generate") { 80 | if false || self.haveKey(self.keyName) { 81 | self.showAlert = true 82 | } else { 83 | self.callGenerateKey() 84 | self.showGenerator = false 85 | } 86 | } 87 | } 88 | } 89 | } 90 | .alert(isPresented: self.$showAlert){ 91 | Alert (title: Text ("Replace SSH Key"), 92 | message: Text ("If you generate a new key, this will remove the previous key and any systems that had that key recorded will no longer accept connections from here.\nAre you sure you want to replace the existing SSH key?"), 93 | primaryButton: Alert.Button.cancel({}), 94 | 95 | secondaryButton: .destructive(Text ("Replace"), action: self.callGenerateKey)) 96 | } 97 | } 98 | } 99 | 100 | struct GenerateSecureEnclave_Previews: PreviewProvider { 101 | static var previews: some View { 102 | GenerateSecureEnclave(showGenerator: .constant(true)) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /SwiftTermApp/Keys/KeyManagementView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyManagementView.swift 3 | // testMasterDetail 4 | // 5 | // Created by Miguel de Icaza on 4/26/20. 6 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct PasteKeyButton: View { 12 | @Binding var addKeyManuallyShown: Bool 13 | 14 | var body: some View { 15 | STButton (text: "Create", icon: "plus.circle") { 16 | self.addKeyManuallyShown = true 17 | } 18 | } 19 | } 20 | 21 | 22 | struct KeyManagementView: View { 23 | @EnvironmentObject var dataController: DataController 24 | private var keys: FetchRequest 25 | @Environment(\.managedObjectContext) var moc 26 | @State var newKeyShown = false 27 | @State var addFromFileShown = false 28 | @State private var editMode = EditMode.inactive 29 | @State var action: ((Key)-> ())? = nil 30 | 31 | init (action: ((Key)->())? = nil) { 32 | _action = State (initialValue: action) 33 | keys = FetchRequest(entity: CKey.entity(), sortDescriptors: [ 34 | NSSortDescriptor(keyPath: \CKey.sName, ascending: true) 35 | ]) 36 | } 37 | 38 | func delete (at offsets: IndexSet) 39 | { 40 | let keyItems = keys.wrappedValue 41 | for offset in offsets { 42 | dataController.delete(key: keyItems [offset]) 43 | } 44 | 45 | dataController.save() 46 | } 47 | 48 | var body: some View { 49 | VStack { 50 | CreateLocalKeyButtons () 51 | if keys.wrappedValue.count == 0 { 52 | HStack (alignment: .top){ 53 | Image (systemName: "key") 54 | .font (.title) 55 | Text ("Keys allow you to easily log into hosts without having to type in passwords.\n\n[Learn More...](https://github.com/migueldeicaza/SwiftTermApp/wiki/Keys)") 56 | .font (.body) 57 | }.padding () 58 | Spacer () 59 | } else { 60 | List { 61 | 62 | // STButton (text: "Import Key from File", icon: "folder.badge.plus", centered: false) 63 | // .onTapGesture { 64 | // self.addFromFileShown = true 65 | // } 66 | // .sheet (isPresented: self.$addFromFileShown, onDismiss: { self.addFromFileShown = false }) { 67 | // STFilePicker() 68 | // } 69 | ForEach(keys.wrappedValue, id: \.self) { key in 70 | KeySummaryView (key: key, action: self.action) 71 | } 72 | .onDelete(perform: delete) 73 | .environment(\.editMode, $editMode) 74 | .cornerRadius(10) 75 | }.listStyle(DefaultListStyle()) 76 | } 77 | } 78 | .navigationTitle("Keys") 79 | .toolbar { 80 | ToolbarItem (placement: .navigationBarTrailing) { 81 | EditButton() 82 | } 83 | } 84 | } 85 | } 86 | 87 | struct KeyManagementView_Previews: PreviewProvider { 88 | static var previews: some View { 89 | Group { 90 | KeyManagementView() 91 | } 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /SwiftTermApp/Keys/KeySummaryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyView.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/13/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import UIKit 11 | 12 | struct ShareKeyView: UIViewControllerRepresentable { 13 | @Binding var publicKey: String 14 | 15 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIActivityViewController { 16 | let controller = UIActivityViewController( 17 | activityItems: [publicKey], 18 | applicationActivities: nil) 19 | return controller 20 | } 21 | 22 | func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext) { 23 | } 24 | } 25 | 26 | struct KeySummaryView: View { 27 | @ObservedObject var key: CKey 28 | @State var showEdit = false 29 | @State var showSharing = false 30 | var action: ((Key)-> ())? 31 | 32 | var body: some View { 33 | HStack(alignment: .center, spacing: 10) { 34 | HStack (alignment: .top) { 35 | Image(systemName: "key") 36 | .resizable() 37 | .aspectRatio(contentMode: .fit) 38 | .frame (height: 20) 39 | .padding(8) 40 | 41 | VStack (alignment: .leading) { 42 | Text ("\(key.name)") 43 | .font(.body) 44 | Text ("Key Type: \(key.type.description)") 45 | .font(.subheadline) 46 | .foregroundColor(.gray) 47 | } 48 | Spacer () 49 | } 50 | .onTapGesture { 51 | if let a = self.action { 52 | a (self.key) 53 | } else { 54 | self.showEdit = true 55 | } 56 | }.sheet(isPresented: $showEdit) { 57 | EditKey(key: self.key, disableChangePassword: true) 58 | }.sheet(isPresented: $showSharing) { 59 | ShareKeyView(publicKey: $key.publicKey) 60 | } 61 | Button (action: { showSharing.toggle () }) { 62 | Image(systemName: "square.and.arrow.up") 63 | //.resizable() 64 | //.aspectRatio(contentMode: .fit) 65 | .foregroundColor(.blue) 66 | } 67 | } 68 | } 69 | } 70 | 71 | 72 | struct KeyView_Previews: PreviewProvider { 73 | @State static var key: CKey = DataController.preview.createSampleKey() 74 | static var previews: some View { 75 | KeySummaryView(key: key) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /SwiftTermApp/Licenses/libssh.txt: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2004-2007 Sara Golemon 2 | * Copyright (c) 2005,2006 Mikhail Gusarov 3 | * Copyright (c) 2006-2007 The Written Word, Inc. 4 | * Copyright (c) 2007 Eli Fant 5 | * Copyright (c) 2009-2021 Daniel Stenberg 6 | * Copyright (C) 2008, 2009 Simon Josefsson 7 | * Copyright (c) 2000 Markus Friedl 8 | * Copyright (c) 2015 Microsoft Corp. 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, 12 | * with or without modification, are permitted provided 13 | * that the following conditions are met: 14 | * 15 | * Redistributions of source code must retain the above 16 | * copyright notice, this list of conditions and the 17 | * following disclaimer. 18 | * 19 | * Redistributions in binary form must reproduce the above 20 | * copyright notice, this list of conditions and the following 21 | * disclaimer in the documentation and/or other materials 22 | * provided with the distribution. 23 | * 24 | * Neither the name of the copyright holder nor the names 25 | * of any other contributors may be used to endorse or 26 | * promote products derived from this software without 27 | * specific prior written permission. 28 | * 29 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 30 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 31 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 32 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 34 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 36 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 37 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 39 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 40 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 41 | * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 42 | * OF SUCH DAMAGE. 43 | */ 44 | -------------------------------------------------------------------------------- /SwiftTermApp/Licenses/source-code-pro.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010-2019 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | 5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /SwiftTermApp/Licenses/swcrypt.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Soyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SwiftTermApp/Licenses/swiftterm.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2022 Miguel de Icaza (https://github.com/migueldeicaza) 2 | Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js) 3 | Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com) 4 | Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /SwiftTermApp/Licenses/swiftui-introspect.txt: -------------------------------------------------------------------------------- 1 | Copyright 2019 Timber Software 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /SwiftTermApp/Location/LocationTracking.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationTracking.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 11/2/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | import os 12 | 13 | var locationTracker: LocationTracker? 14 | var geoLog = Logger(subsystem: "org.tirania.SwiftTermApp", category: "geo") 15 | var lastLocation: CLLocation? 16 | 17 | // Returns the last known location that we have, nor nil if we do not have one 18 | func getLocation () -> HistoryLocation? { 19 | guard let last = lastLocation else { 20 | return nil 21 | } 22 | return HistoryLocation (latitude: last.coordinate.latitude, longitude: last.coordinate.longitude) 23 | } 24 | 25 | class LocationTracker: NSObject, CLLocationManagerDelegate { 26 | var locationManager: CLLocationManager 27 | var running = false 28 | 29 | override init () { 30 | locationManager = CLLocationManager () 31 | super.init () 32 | 33 | locationManager.delegate = self 34 | } 35 | 36 | func start () { 37 | locationManager.distanceFilter = kCLDistanceFilterNone 38 | locationManager.allowsBackgroundLocationUpdates = true 39 | locationManager.pausesLocationUpdatesAutomatically = false 40 | locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers 41 | locationManager.activityType = .other 42 | locationManager.requestWhenInUseAuthorization() 43 | 44 | resume () 45 | } 46 | 47 | func stop () { 48 | locationManager.stopUpdatingLocation() 49 | } 50 | 51 | func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { 52 | switch manager.authorizationStatus { 53 | case .notDetermined, .authorizedWhenInUse: 54 | manager.requestAlwaysAuthorization() 55 | default: 56 | break 57 | } 58 | } 59 | 60 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 61 | for loc in locations { 62 | lastLocation = loc 63 | print ("Got new location \(loc)") 64 | } 65 | } 66 | 67 | func suspend() { 68 | locationManager.stopUpdatingLocation() 69 | } 70 | 71 | func resume() { 72 | locationManager.startUpdatingLocation() 73 | } 74 | } 75 | 76 | func locationTrackerStart () { 77 | if locationTracker != nil { 78 | return 79 | } 80 | if !CLLocationManager.locationServicesEnabled() { 81 | geoLog.log(level: .info, "locationServices are disabled") 82 | return 83 | } 84 | locationTracker = LocationTracker() 85 | locationTracker!.start () 86 | } 87 | 88 | func locationTrackerStop () { 89 | guard let loc = locationTracker else { 90 | return 91 | } 92 | loc.stop () 93 | locationTracker = nil 94 | } 95 | 96 | func locationTrackerSuspend () { 97 | guard let loc = locationTracker else { 98 | return 99 | } 100 | loc.suspend () 101 | } 102 | 103 | func locationTrackerResume () { 104 | guard let loc = locationTracker else { 105 | return 106 | } 107 | loc.resume () 108 | } 109 | -------------------------------------------------------------------------------- /SwiftTermApp/Metal/DigitalBrain.metal: -------------------------------------------------------------------------------- 1 | // DigitalBrain.metal 2 | // SwiftTermApp 3 | // 4 | // Created by Miguel de Icaza on 6/11/20. 5 | // by struss 2013, Shader Toy, modified to move less, and have fewer highlights, no input noise and ported to Metal by Miguel 6 | // 7 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 8 | // 9 | 10 | #include 11 | using namespace metal; 12 | 13 | #include "SwiftTermShaders.h" 14 | #define iTime iGlobalTime 15 | 16 | // by srtuss, 2013 17 | 18 | // rotate position around axis 19 | float2 rotate(float2 p, float a) 20 | { 21 | return float2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a)); 22 | } 23 | 24 | // 1D random numbers 25 | float rand(float n) 26 | { 27 | return fract(sin(n) * 43758.5453123); 28 | } 29 | 30 | // 2D random numbers 31 | float2 rand2(float2 p) 32 | { 33 | return fract(float2(sin(p.x * 591.32 + p.y * 154.077), cos(p.x * 391.32 + p.y * 49.077))); 34 | } 35 | 36 | // 1D noise 37 | float noise1(float p) 38 | { 39 | float fl = floor(p); 40 | float fc = fract(p); 41 | return mix(rand(fl), rand(fl + 1.0), fc); 42 | } 43 | 44 | // voronoi distance noise, based on iq's articles 45 | float voronoi(float2 x) 46 | { 47 | float2 p = floor(x); 48 | float2 f = fract(x); 49 | 50 | float2 res = float2(8.0); 51 | for(int j = -1; j <= 1; j ++) 52 | { 53 | for(int i = -1; i <= 1; i ++) 54 | { 55 | float2 b = float2(i, j); 56 | float2 r = float2(b) - f + rand2(p + b); 57 | 58 | // chebyshev distance, one of many ways to do this 59 | float d = max(abs(r.x), abs(r.y)); 60 | 61 | if(d < res.x) 62 | { 63 | res.y = res.x; 64 | res.x = d; 65 | } 66 | else if(d < res.y) 67 | { 68 | res.y = d; 69 | } 70 | } 71 | } 72 | return res.y - res.x; 73 | } 74 | 75 | 76 | 77 | fragment half4 digitalbrain_fragment_texture(ProjectedVertex in [[stage_in]], constant Uniforms &uniforms [[buffer(0)]]) 78 | { 79 | float flicker = noise1(iTime * 0.1) * 0.8 + 0.4; 80 | 81 | float2 uv = in.texCoords; // / iResolution.xy; 82 | uv = (uv - 0.5) * 2.0; 83 | float2 suv = uv; 84 | uv.x *= iResolution.x / iResolution.y; 85 | float v = 0.0; 86 | 87 | // that looks highly interesting: 88 | //v = 1.0 - length(uv) * 1.3; 89 | // a bit of camera movement 90 | uv *= 0.6 + sin(iTime * 0.1) * 0.4; 91 | uv = rotate(uv, sin(iTime * 0.1) * 1.0); 92 | uv += iTime * 0.1; 93 | 94 | // add some noise octaves 95 | float a = 0.6, f = 1.0; 96 | 97 | for(int i = 0; i < 3; i ++) // 4 octaves also look nice, its getting a bit slow though 98 | { 99 | float v1 = voronoi(uv * f + 5.0); 100 | float v2 = 0.0; 101 | 102 | // make the moving electrons-effect for higher octaves 103 | if(i > 0) 104 | { 105 | // of course everything based on voronoi 106 | v2 = voronoi(uv * f * 0.5 + 50.0 + iTime*0.1); 107 | 108 | float va = 0.0, vb = 0.0; 109 | va = 1.0 - smoothstep(0.0, 0.1, v1); 110 | vb = 1.0 - smoothstep(0.0, 0.08, v2); 111 | v += a * pow(va * (0.5 + vb), 2.0); 112 | } 113 | 114 | // make sharp edges 115 | v1 = 0.5 - smoothstep(0.0, 0.3, v1); 116 | 117 | // noise is used as intensity map 118 | v2 = a * (noise1(v1 * 5.5 + 0.1)); 119 | 120 | // octave 0's intensity changes a bit 121 | if(i == 0) 122 | v += v2 * flicker; 123 | else 124 | v += v2; 125 | 126 | f *= 3.0; 127 | a *= 0.7; 128 | } 129 | 130 | // slight vignetting 131 | v *= exp(-0.6 * length(suv)) * 1.2; 132 | 133 | // use texture channel0 for color? why not. 134 | //float3 cexp = texture(iChannel0, uv * 0.001).xyz * 3.0 + texture(iChannel0, uv * 0.01).xyz;//float3(1.0, 2.0, 4.0); 135 | 136 | 137 | // old blueish color set 138 | float3 cexp = float3(6.0, 4.0, 2.0); 139 | 140 | 141 | //float3 col = float3(pow(v, cexp.x), pow(v, cexp.y), pow(v, cexp.z)) * 2.0; 142 | float3 col = float3(pow(v, cexp.x), pow(v, cexp.y), pow(v, cexp.z)) * 0.5; 143 | //col = float3(v,v,v)*0.4; 144 | return half4 (float4(col, 1.0)); 145 | } 146 | -------------------------------------------------------------------------------- /SwiftTermApp/Metal/MetalSwiftUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetalSwiftUI.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/6/20. 6 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SwiftUI 12 | 13 | class MetalHostView: UIView { 14 | var metal: MetalHost! 15 | 16 | public init (frame: CGRect, fragmentName: String) 17 | { 18 | let metalLayer = CAMetalLayer() 19 | metalLayer.pixelFormat = .bgra8Unorm 20 | metal = MetalHost(target: metalLayer, fragmentName: fragmentName) 21 | super.init (frame: frame) 22 | if metal != nil { 23 | layer.addSublayer(metalLayer) 24 | metal.startRunning() 25 | } 26 | } 27 | 28 | public required init (coder: NSCoder) 29 | { 30 | fatalError() 31 | } 32 | 33 | deinit { 34 | DispatchQueue.main.async { 35 | self.metal.stopRunning() 36 | } 37 | } 38 | 39 | public override var frame: CGRect { 40 | didSet { 41 | metal.target.frame = frame 42 | } 43 | } 44 | } 45 | 46 | struct MetalView: UIViewRepresentable { 47 | let shaderFunc: String 48 | 49 | func makeUIView(context: Context) -> UIView { 50 | let view = MetalHostView(frame: CGRect(x: 0, y: 0, width: 100, height: 100), fragmentName: shaderFunc) 51 | return view 52 | } 53 | 54 | func updateUIView(_ uiView: UIView, context: Context) 55 | { 56 | 57 | } 58 | } 59 | 60 | struct MetalSwiftUI_Previews: PreviewProvider { 61 | static var previews: some View { 62 | VStack { 63 | Text ("Top") 64 | HStack { 65 | Text ("Hello") 66 | MetalView (shaderFunc: "digitalbrain_fragment_texture") 67 | Text ("World") 68 | } 69 | Text ("Bottom") 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SwiftTermApp/Metal/ShadersBase.metal: -------------------------------------------------------------------------------- 1 | // 2 | // This is the header from Shadertweak: github.com/warrenm/Shadertweak 3 | // 4 | // Original licenses: 5 | // 6 | // MIT License 7 | // 8 | // Copyright (c) 2017 Warren Moore 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | // 28 | #include 29 | using namespace metal; 30 | 31 | #include "SwiftTermShaders.h" 32 | 33 | vertex ProjectedVertex vertex_reshape(Vertex2D currentVertex [[stage_in]]) 34 | { 35 | ProjectedVertex out; 36 | out.position = float4(currentVertex.position, 0.0, 1.0); 37 | out.texCoords = currentVertex.texCoords; 38 | return out; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /SwiftTermApp/Metal/StarNest.metal: -------------------------------------------------------------------------------- 1 | // 2 | // StarNest.metal 3 | // SwiftTermApp 4 | // 5 | // This is a port of Star Nest by Pablo Roman Andrioli 6 | // licensed under the MIT X11 license, found here: 7 | // https://www.shadertoy.com/view/XlfGRj 8 | // 9 | // The port to metal came from the MIT licensed 10 | // github.com/warrenm/Shadertweak by Warren Moore 11 | 12 | #include 13 | using namespace metal; 14 | #include "SwiftTermShaders.h" 15 | 16 | #define iterations 17 17 | #define formuparam 0.53 18 | 19 | #define volsteps 9 20 | #define stepsize 0.1 21 | 22 | #define zoom 0.800 23 | #define tile 0.850 24 | #define speed 0.0010 25 | 26 | #define brightness 0.005 27 | #define darkmatter 0.150 28 | #define distfading 0.630 29 | #define saturation 0.850 30 | 31 | fragment half4 starnest_fragment_texture(ProjectedVertex in [[stage_in]], 32 | constant Uniforms &uniforms [[buffer(0)]]) 33 | { 34 | //get coords and direction 35 | float2 uv=in.texCoords;//fragCoord.xy/iResolution.xy-.5; 36 | uv.y*=iResolution.y/iResolution.x; 37 | float3 dir=float3(uv*zoom,1.); 38 | float time=iGlobalTime*speed+.25; 39 | 40 | //mouse rotation 41 | float a1=0; 42 | float a2=0; 43 | float2x2 rot1=float2x2(float2(cos(a1),sin(a1)),float2(-sin(a1),cos(a1))); 44 | float2x2 rot2=float2x2(float2(cos(a2),sin(a2)),float2(-sin(a2),cos(a2))); 45 | dir.xz=dir.xz*rot1; 46 | dir.xy=dir.xy*rot2; 47 | float3 from=float3(1.,.5,0.5); 48 | from+=float3(time*2.,time,-2.); 49 | from.xz=from.xz*rot1; 50 | from.xy=from.xy*rot2; 51 | 52 | //volumetric rendering 53 | float s=.01,fade=2; 54 | float3 v=float3(0.); 55 | for (int r=0; r6) fade*=1.-dm; // dark matter, don't render near 67 | //v+=vec3(dm,dm*.5,0.); 68 | v+=fade; 69 | v+=float3(s,s*s,s*s*s*s)*a*brightness*fade; // coloring based on distance 70 | fade*=distfading; // distance fading 71 | s+=stepsize; 72 | } 73 | v=mix(float3(length(v)),v,saturation); //color adjust 74 | return half4(float4(v*.01,1.)); 75 | } 76 | -------------------------------------------------------------------------------- /SwiftTermApp/Metal/SwiftTermShaders.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftTermShaders.h 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/6/20. 6 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | #ifndef SwiftTermShaders_h 10 | #define SwiftTermShaders_h 11 | 12 | struct Vertex2D 13 | { 14 | float2 position [[attribute(0)]]; 15 | float2 texCoords [[attribute(1)]]; 16 | }; 17 | 18 | struct ProjectedVertex { 19 | float4 position [[position]]; 20 | float2 texCoords; 21 | }; 22 | 23 | struct Uniforms { 24 | float2 resolution; 25 | float time; 26 | float deltaTime; 27 | int frameIndex; 28 | }; 29 | 30 | #define iGlobalTime (uniforms.time) 31 | #define iResolution (uniforms.resolution) 32 | #define iTimeDelta (uniforms.deltaTime) 33 | #define iTouches (uniforms.touches) 34 | 35 | #endif /* SwiftTermShaders_h */ 36 | -------------------------------------------------------------------------------- /SwiftTermApp/Model/CUserSnippet-DataHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CUserSnippet.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 5/21/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension CUserSnippet: UserSnippet { 12 | var title: String { 13 | get { sTitle ?? "" } 14 | set { sTitle = newValue } 15 | } 16 | 17 | var command: String { 18 | get { sCommand ?? "" } 19 | set { sCommand = newValue } 20 | } 21 | 22 | var platforms: [String] { 23 | get { 24 | guard let _s = sPlatforms else { 25 | return [] 26 | } 27 | return _s.split(separator: ",").map { String ($0) } 28 | } 29 | set { 30 | sPlatforms = newValue.joined(separator: ",") 31 | } 32 | } 33 | 34 | public var id: UUID { 35 | get { 36 | if sId == nil { 37 | sId = UUID () 38 | } 39 | return sId! 40 | } 41 | set { sId = newValue } 42 | } 43 | 44 | func toMemoryUserSnippet() -> MemoryUserSnippet { 45 | MemoryUserSnippet (title: title, command: command, platforms: platforms) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /SwiftTermApp/Model/DataModelTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataModelTypes.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 5/19/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Represents a host we connect to, the data structure we save, and keep at runtime 12 | /// Most properties are used at connection time, and a handful can be changed at runtime: 13 | /// 14 | /// Properties that can be changed at runtime: 15 | /// - style: 16 | /// - background 17 | /// - backspaceAsControlH 18 | /// 19 | /// Possibly should add; Font and FontSize 20 | protocol Host { 21 | /// Unique ID, used both inside Swift to differentiate structures and for tracking our records and keys 22 | var id: UUID { get set } 23 | 24 | /// Public name for this host 25 | var alias: String { get set } 26 | 27 | /// Address to connect to 28 | var hostname: String { get set } 29 | 30 | /// Setting for this host, should the backspace key send a control-h 31 | var backspaceAsControlH: Bool { get set } 32 | 33 | /// The port to which we will connect in the host 34 | var port: Int { get set } 35 | 36 | /// If true, this host uses a password, rather than a key 37 | var usePassword: Bool { get set } 38 | 39 | /// Username to login as 40 | var username: String { get set } 41 | 42 | /// If usePassword is set, this might contain the password to provide for authentication 43 | var password: String { get set } 44 | 45 | /// The guessed host type - this is used to pick the icon for the host, and it is sometimes automatically guessed and set 46 | var hostKind: String { get set } 47 | 48 | /// Environment variables to pass on the connection 49 | var environmentVariables: [String:String] { get set } 50 | 51 | /// Scripts to execute on startup 52 | var startupScripts: [String] { get set } 53 | 54 | /// This is the UUID of the key registered with the app 55 | var sshKey: UUID? { get set } 56 | 57 | /// The current color theme style name, if the style is "" it means "use the default" 58 | var style: String { get set } 59 | 60 | /// The values are `default` to pick the value from settings, "" to use a solid color, or the name of a live background 61 | var background: String { get set } 62 | 63 | /// Last time this host was used, to show the sorted list of hosts used 64 | var lastUsed: Date { get set } 65 | 66 | /// Reconnection type, one of "" or "tmux" 67 | var reconnectType: String { get set } 68 | 69 | /// Converts this host into a memory representation, not a database representation, so we can safely access CoreData structures from the background 70 | func asMemory () -> MemoryHost 71 | } 72 | 73 | extension Host { 74 | func summary() -> String { 75 | hostname + (style != "" ? ", \(style)" : "") 76 | } 77 | } 78 | 79 | protocol Key { 80 | /// Unique ID, used both inside Swift to differentiate structures and for tracking our records and keys 81 | var id: UUID { get set } 82 | 83 | /// The type of this key 84 | var type: KeyType { get set } 85 | 86 | /// The user visible name for the key 87 | var name: String { get set } 88 | 89 | /// This stores the private key as pasted by the user, or if it is a type = .ecdsa(inSecureEnclave:true) the tag for the key in the KeyChain 90 | var privateKey: String { get set } 91 | 92 | /// This stores the public key as pasted by the user 93 | var publicKey: String { get set } 94 | 95 | /// This stores a passphrase to decode the private key, if provided 96 | var passphrase: String { get set } 97 | 98 | func toMemoryKey () -> MemoryKey 99 | } 100 | 101 | extension Key { 102 | /// Turns the `publicKey` that contains base64 data into a `Data` object 103 | public func getPublicKeyAsData () -> Data { 104 | let values = publicKey.split (separator: " ") 105 | if values.count > 2 { 106 | if let decoded = Data (base64Encoded: String (values [1])) { 107 | return decoded 108 | } 109 | } 110 | return Data() 111 | } 112 | 113 | /// If the key is stored in the KeyChain, returns the handle 114 | public func getKeyHandle () -> SecKey? { 115 | switch type { 116 | case .ecdsa(inEnclave: true): 117 | let query = getKeyQuery(forId: id) 118 | var result: CFTypeRef! 119 | 120 | if SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess && result != nil { 121 | return (result as! SecKey) 122 | } 123 | return nil 124 | default: 125 | return nil 126 | } 127 | } 128 | 129 | } 130 | 131 | protocol UserSnippet { 132 | var title: String { get set } 133 | var command: String { get set } 134 | var platforms: [String] { get set } 135 | var id: UUID { get set } 136 | 137 | func toMemoryUserSnippet () -> MemoryUserSnippet 138 | } 139 | 140 | 141 | -------------------------------------------------------------------------------- /SwiftTermApp/Model/MemoryModels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MemoryModels.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 5/20/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// An pure in-memory implementation of the Host protocol 12 | public class MemoryHost: Host { 13 | internal init(id: UUID = UUID(), alias: String = "", hostname: String = "", backspaceAsControlH: Bool = false, port: Int = 22, usePassword: Bool = true, username: String = "", password: String = "", hostKind: String = "", environmentVariables: [String:String] = [:], startupScripts: [String] = [], sshKey: UUID? = nil, style: String = "", background: String = "", lastUsed: Date = Date.distantPast) { 14 | self.id = id 15 | self.alias = alias 16 | self.hostname = hostname 17 | self.backspaceAsControlH = backspaceAsControlH 18 | self.port = port 19 | self.usePassword = usePassword 20 | self.username = username 21 | self.password = password 22 | self.hostKind = hostKind 23 | self.environmentVariables = environmentVariables 24 | self.startupScripts = startupScripts 25 | self.sshKey = sshKey 26 | self.style = style 27 | self.background = background 28 | self.lastUsed = lastUsed 29 | } 30 | 31 | var id = UUID() 32 | var alias: String = "" 33 | var hostname: String = "" 34 | var backspaceAsControlH: Bool = false 35 | var port: Int = 22 36 | var usePassword: Bool = true 37 | var username: String = "" 38 | var password: String = "" 39 | var hostKind: String = "" 40 | var environmentVariables: [String:String] = [:] 41 | var startupScripts: [String] = [] 42 | var sshKey: UUID? 43 | var style: String = "" 44 | var background: String = "" 45 | var lastUsed: Date = Date.distantPast 46 | var reconnectType: String = "" 47 | 48 | // The list of keys that are serialized to Json, this is used to prevent both 49 | // password from being stored in plaintext. 50 | enum CodingKeys: CodingKey { 51 | case id 52 | case alias 53 | case hostname 54 | case backspaceAsControlH 55 | case port 56 | case usePassword 57 | case username 58 | case hostKind 59 | case environmentVariables 60 | case startupScripts 61 | case sshKey 62 | case style 63 | case background 64 | case lastUsed 65 | case reconnectType 66 | #if DEBUG 67 | case password 68 | #endif 69 | } 70 | 71 | func asMemory() -> MemoryHost { 72 | return self 73 | } 74 | } 75 | 76 | class MemoryKey: Key, Codable, Identifiable { 77 | var id: UUID 78 | var type: KeyType = .rsa(4096) 79 | var name: String = "" 80 | 81 | // This stores the private key as pasted by the user, or if it is a type = .ecdsa(inSecureEnclave:true) the tag for the key in the KeyChain 82 | var privateKey: String = "" 83 | // This stores the public key as pasted by the user 84 | var publicKey: String = "" 85 | var passphrase: String = "" 86 | 87 | // The list of keys that are serialized to Json, this is used to prevent both 88 | // passphrase and privateKey from being stored in plaintext. 89 | enum CodingKeys: CodingKey { 90 | case id 91 | case type 92 | case name 93 | case publicKey 94 | #if DEBUG 95 | case passphrase 96 | case privateKey 97 | #endif 98 | } 99 | 100 | public init (id: UUID = UUID(), type: KeyType = .rsa(4096), name: String = "", privateKey: String = "", publicKey: String = "", passphrase: String = "") 101 | { 102 | self.id = id 103 | self.type = type 104 | self.name = name 105 | self.privateKey = privateKey 106 | self.publicKey = publicKey 107 | self.passphrase = passphrase 108 | } 109 | 110 | func toMemoryKey() -> MemoryKey { 111 | return self 112 | } 113 | } 114 | 115 | class MemoryUserSnippet: UserSnippet, Codable, Identifiable { 116 | var title: String 117 | var command: String 118 | var platforms: [String] 119 | var id: UUID 120 | 121 | public init (id: UUID = UUID (), title: String, command: String, platforms: [String]) { 122 | self.id = id 123 | self.title = title 124 | self.command = command 125 | self.platforms = platforms 126 | } 127 | 128 | func toMemoryUserSnippet() -> MemoryUserSnippet { 129 | return self 130 | } 131 | } 132 | 133 | -------------------------------------------------------------------------------- /SwiftTermApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftTermApp/QuickLauch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuickLauch.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 5/20/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct DelayedSwiftUITerminal: View { 13 | @Binding var destHost: Host? 14 | 15 | var body: some View { 16 | SwiftUITerminal(host: self.destHost!, existing: nil, createNew: true, interactive: true) 17 | } 18 | } 19 | struct QuickLaunch: View { 20 | @State var quickCommand: String = "" 21 | @State var destHost: Host? 22 | @State var activate: Bool = false 23 | 24 | func go () { 25 | guard quickCommand.count > 0 else { return } 26 | let sp1 = quickCommand.split (separator: ":") 27 | let sp2 = String (sp1 [0]).split (separator: "@") 28 | let user, host: String 29 | if sp2.count == 1 { 30 | host = String (sp2 [0]) 31 | user = "" 32 | } else { 33 | user = String (sp2 [0]) 34 | host = String (sp2 [1]) 35 | } 36 | let port = sp1.count > 1 ? Int (String (sp1 [1])) ?? 22 : 22 37 | 38 | destHost = MemoryHost( 39 | alias: quickCommand, 40 | hostname: host, 41 | port: port, 42 | username: user, 43 | lastUsed: Date()) 44 | activate = true 45 | } 46 | 47 | var body: some View { 48 | NavigationLink(destination: DelayedSwiftUITerminal (destHost: $destHost), isActive: $activate) { 49 | HStack { 50 | 51 | TextField("user@hostname:22", text: $quickCommand) 52 | .keyboardType(.emailAddress) 53 | .autocapitalization(.none) 54 | .disableAutocorrection(true) 55 | 56 | Button(action: { self.go () }) { 57 | Text ("Connect") 58 | } 59 | } 60 | } 61 | .isDetailLink(false) 62 | .allowsHitTesting(false) 63 | .onAppear() { 64 | self.destHost = nil 65 | } 66 | } 67 | } 68 | 69 | struct QuickLaunch_Previews: PreviewProvider { 70 | static var previews: some View { 71 | QuickLaunch() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SwiftTermApp/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // testMasterDetail 4 | // 5 | // Created by Miguel de Icaza on 4/25/20. 6 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | import CryptoKit 12 | 13 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | 18 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 19 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 20 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 21 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 22 | 23 | // Create the SwiftUI view that provides the window contents. 24 | let contentView = ContentView() 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /SwiftTermApp/Secrets.swift: -------------------------------------------------------------------------------- 1 | let shakeId = "" 2 | let shakeKey = "" 3 | -------------------------------------------------------------------------------- /SwiftTermApp/Settings/ColorLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorLoader.swift 3 | // Loads colors from the XRDB format and distributed by the iTerm2colorschemes.com site 4 | // 5 | // Created by Miguel de Icaza on 5/29/20. 6 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftTerm 11 | 12 | struct ThemeColor: Hashable, Equatable { 13 | var name: String 14 | var ansi: [Color] 15 | var background: Color 16 | var foreground: Color 17 | var cursor: Color 18 | var cursorText: Color 19 | var selectedText: Color 20 | var selectionColor: Color 21 | 22 | static func parseColor (_ txt: [Substring.Element]) -> Color? 23 | { 24 | func getHex (_ idx: Int) -> UInt16 { 25 | var n: UInt16 = 0 26 | let c = txt [idx].asciiValue ?? 0 27 | 28 | if c >= UInt8(ascii: "0") && c <= UInt8 (ascii: "9"){ 29 | n = UInt16 (c - UInt8(ascii: "0")) 30 | } else if c >= UInt8(ascii: "a") && c <= UInt8 (ascii: "f") { 31 | n = UInt16 ((c - UInt8(ascii:"a") + 10)) 32 | } else if c >= UInt8(ascii: "A") && c <= UInt8 (ascii: "F") { 33 | n = UInt16 ((c - UInt8(ascii:"A") + 10)) 34 | } 35 | return n 36 | } 37 | guard txt.count == 7 else { return nil } 38 | guard txt [0] == "#" else { return nil } 39 | 40 | let r = getHex (1) << 4 | getHex (2) 41 | let g = getHex (3) << 4 | getHex (4) 42 | let b = getHex (5) << 4 | getHex (6) 43 | return Color (red: r*257, green: g*257, blue: b*257) 44 | } 45 | 46 | // Returns a ThemeColor from an Xrdb string (this should contain the whole file) 47 | // xrdb is one of the simpler formats supported on the iTerm2 web site with themes 48 | static func fromXrdb (title: String, xrdb: String) -> ThemeColor? { 49 | var ansi: [Int:Color] = [:] 50 | var background: Color? 51 | var foreground: Color? 52 | var cursor: Color? 53 | var cursorText: Color? 54 | var selectedText: Color? 55 | var selectionColor: Color? 56 | 57 | for l in xrdb.split (separator: "\n") { 58 | let elements = l.split (separator: " ") 59 | let color = parseColor (Array (elements [2])) 60 | switch elements [1]{ 61 | case "Ansi_0_Color": 62 | ansi [0] = color 63 | case "Ansi_1_Color": 64 | ansi [1] = color 65 | case "Ansi_10_Color": 66 | ansi [10] = color 67 | case "Ansi_11_Color": 68 | ansi [11] = color 69 | case "Ansi_12_Color": 70 | ansi [12] = color 71 | case "Ansi_13_Color": 72 | ansi [13] = color 73 | case "Ansi_14_Color": 74 | ansi [14] = color 75 | case "Ansi_15_Color": 76 | ansi [15] = color 77 | case "Ansi_2_Color": 78 | ansi [2] = color 79 | case "Ansi_3_Color": 80 | ansi [3] = color 81 | case "Ansi_4_Color": 82 | ansi [4] = color 83 | case "Ansi_5_Color": 84 | ansi [5] = color 85 | case "Ansi_6_Color": 86 | ansi [6] = color 87 | case "Ansi_7_Color": 88 | ansi [7] = color 89 | case "Ansi_8_Color": 90 | ansi [8] = color 91 | case "Ansi_9_Color": 92 | ansi [9] = color 93 | case "Background_Color": 94 | background = color 95 | case "Cursor_Color": 96 | cursor = color 97 | case "Cursor_Text_Color": 98 | cursorText = color 99 | case "Foreground_Color": 100 | foreground = color 101 | case "Selected_Text_Color": 102 | selectedText = color 103 | case "Selection_Color": 104 | selectionColor = color 105 | default: 106 | break 107 | } 108 | } 109 | if ansi.count == 16 { 110 | if let bg = background, let fg = foreground, let ct = cursorText, 111 | let cu = cursor, let st = selectedText, let sc = selectionColor { 112 | 113 | return ThemeColor (name: title, 114 | ansi: [Color] (ansi.keys.sorted().map { v in ansi [v]! }), 115 | background: bg, 116 | foreground: fg, 117 | cursor: cu, 118 | cursorText: ct, 119 | selectedText: st, 120 | selectionColor: sc) 121 | } 122 | 123 | } 124 | return nil 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /SwiftTermApp/Settings/ColumnSelector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColumnSelector.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/28/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ColumnSelector: View { 12 | @State private var selectedCols = 0 13 | 14 | var body: some View { 15 | HStack { 16 | Text ("Columns") 17 | Spacer () 18 | Picker("Columns", selection: $selectedCols) { 19 | Text("60").tag(60) 20 | Text("80").tag(80) 21 | Text("132").tag(132) 22 | } 23 | .pickerStyle(.segmented) 24 | } 25 | } 26 | } 27 | 28 | struct ColumnSelector_Previews: PreviewProvider { 29 | static var previews: some View { 30 | ColumnSelector() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SwiftTermApp/Snippets/SnippetBrowser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnippetBrowser.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 3/25/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SnippetBrowser: View { 12 | @EnvironmentObject var dataController: DataController 13 | private var snippets: FetchRequest 14 | @Environment(\.managedObjectContext) var moc 15 | @State var activatedItem: CUserSnippet? = nil 16 | @State var newSnippet: Bool = false 17 | 18 | func delete (at offsets: IndexSet) { 19 | let snippetItems = snippets.wrappedValue 20 | for offset in offsets { 21 | dataController.delete(snippet: snippetItems [offset]) 22 | } 23 | 24 | dataController.save() 25 | } 26 | 27 | init () { 28 | snippets = FetchRequest(entity: CUserSnippet.entity(), sortDescriptors: [ 29 | NSSortDescriptor(keyPath: \CUserSnippet.sTitle, ascending: true) 30 | ]) 31 | } 32 | 33 | var body: some View { 34 | VStack { 35 | STButton (text: "Add Snippet", icon: "plus.circle") { 36 | self.newSnippet = true 37 | } 38 | if snippets.wrappedValue.count > 0 { 39 | List { 40 | Section { 41 | ForEach(snippets.wrappedValue, id: \.self) { snippet in 42 | SnippetSummary (snippet: snippet) 43 | .onTapGesture { 44 | activatedItem = snippet 45 | } 46 | } 47 | .onDelete(perform: delete) 48 | } 49 | } 50 | .listStyle(.grouped) 51 | .navigationTitle(Text("Snippets")) 52 | .toolbar { 53 | ToolbarItem (placement: .navigationBarTrailing) { 54 | EditButton () 55 | } 56 | } 57 | } else { 58 | HStack (alignment: .top){ 59 | Image (systemName: "note.text") 60 | .font (.title) 61 | Text ("Snippets are groups of commands that you can paste in your terminal with the snippet icon.") 62 | .font (.body) 63 | }.padding () 64 | Spacer () 65 | } 66 | } 67 | .sheet(isPresented: $newSnippet) { 68 | SnippetEditor (snippet: nil) 69 | } 70 | .sheet(item: $activatedItem) { item in 71 | SnippetEditor (snippet: item) 72 | } 73 | } 74 | } 75 | 76 | struct SnippetBrowser_Previews: PreviewProvider { 77 | static var previews: some View { 78 | SnippetBrowser() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /SwiftTermApp/Snippets/SnippetEditor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnippetEditor.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 3/25/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SnippetEditor: View { 12 | @EnvironmentObject var dataController: DataController 13 | @State var snippet: CUserSnippet? 14 | @State var title: String = "" 15 | @State var command: String = "" 16 | @State var platforms: [String] = [] 17 | @Environment(\.dismiss) private var dismiss 18 | 19 | init (snippet: CUserSnippet?) { 20 | self._snippet = State (initialValue: snippet) 21 | 22 | if let snippet = snippet { 23 | _title = State (initialValue: snippet.title) 24 | _command = State (initialValue: snippet.command) 25 | _platforms = State (initialValue: snippet.platforms) 26 | } 27 | } 28 | 29 | func saveAndLeave () { 30 | let snippet: CUserSnippet 31 | if let existingSnippet = self.snippet { 32 | snippet = existingSnippet 33 | } else { 34 | snippet = CUserSnippet (context: dataController.container.viewContext) 35 | } 36 | dismiss() 37 | snippet.objectWillChange.send () 38 | snippet.platforms = platforms 39 | snippet.command = command 40 | snippet.title = title 41 | dataController.save() 42 | } 43 | 44 | var body: some View { 45 | NavigationView { 46 | Form { 47 | Section { 48 | VStack (alignment: .leading) { 49 | Text ("Title:") 50 | TextField ("name", text: $title) 51 | } 52 | } 53 | Section { 54 | Text ("Command") 55 | TextEditor (text: $command) 56 | .keyboardType(.asciiCapable) 57 | .autocapitalization(.none) 58 | .font (.system (.body, design: .monospaced)) 59 | .frame(minHeight: 80) 60 | } 61 | } 62 | .navigationBarTitleDisplayMode(.inline) 63 | .toolbar { 64 | ToolbarItem (placement: .navigationBarLeading) { 65 | Button ("Cancel") { 66 | dismiss() 67 | } 68 | } 69 | ToolbarItem (placement: .navigationBarTrailing) { 70 | Button("Save") { 71 | self.saveAndLeave () 72 | } 73 | } 74 | } 75 | 76 | } 77 | } 78 | } 79 | 80 | struct SnippetEditor_Previews: PreviewProvider { 81 | static var previews: some View { 82 | SnippetEditor(snippet: DataController.preview.createSampleSnippet ()) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /SwiftTermApp/Snippets/SnippetSummary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnippetSummary.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 3/25/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SnippetSummary: View { 12 | @State var snippet: CUserSnippet 13 | var body: some View { 14 | VStack (alignment: .leading){ 15 | Text (snippet.title) 16 | .bold() 17 | Text (snippet.command) 18 | .lineLimit(1) 19 | .foregroundColor(.secondary) 20 | .font (.system (.body, design: .monospaced)) 21 | } 22 | } 23 | } 24 | 25 | struct SnippetSummary_Previews: PreviewProvider { 26 | static var previews: some View { 27 | SnippetSummary(snippet: DataController.preview.createSampleSnippet()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SwiftTermApp/Ssh/Channel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Channel.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 12/11/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @_implementationOnly import CSSH 11 | 12 | /// Surfaces operations on channels, channels need to be activated on the session to receive data 13 | public class Channel: Equatable { 14 | static var serial = 0 15 | static var channelLock = NSLock () 16 | var channelHandle: OpaquePointer 17 | weak var sessionActor: SessionActor! 18 | weak var session: Session! 19 | var buffer, bufferError: UnsafeMutablePointer 20 | let bufferSize = 32*1024 21 | var sendQueue = DispatchQueue (label: "channelSend", qos: .userInitiated) 22 | var readCallback: ((Channel, Data?, Data?, Bool)async->()) 23 | var type: String 24 | var id: Int 25 | 26 | /// - Parameters: 27 | /// - readCallback: this callback is invoked when new data is received from the connection, and it 28 | /// contains the channel were the data was received, the stdout, stderr and an indicator whether this 29 | /// has reached the end (eof) 30 | init (session: Session, channelHandle: OpaquePointer, readCallback: @escaping (Channel, Data?, Data?, Bool)async->(), type: String) { 31 | Channel.channelLock.lock () 32 | Channel.serial += 1 33 | id = Channel.serial 34 | Channel.channelLock.unlock () 35 | 36 | self.channelHandle = channelHandle 37 | self.sessionActor = session.sessionActor 38 | self.session = session 39 | self.readCallback = readCallback 40 | self.type = type 41 | buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) 42 | bufferError = UnsafeMutablePointer.allocate(capacity: bufferSize) 43 | libssh2_channel_set_blocking(channelHandle, 0) 44 | } 45 | 46 | deinit { 47 | let s = sessionActor! 48 | let t = channelHandle 49 | buffer.deallocate() 50 | bufferError.deallocate() 51 | Task { 52 | await s.free (channelHandle: t) 53 | } 54 | } 55 | 56 | // Equatable.func == 57 | public static func == (lhs: Channel, rhs: Channel) -> Bool { 58 | lhs.channelHandle == rhs.channelHandle 59 | } 60 | 61 | public func setEnvironment (name: String, value: String) async { 62 | let _ = await sessionActor.setEnv (channel: self, name: name, value: value) 63 | } 64 | 65 | // Returns 0 on success, or a LIBSSH2 error otherwise, always retries operations, so EAGAIN is never returned 66 | public func requestPseudoTerminal (name: String, cols: Int, rows: Int) async -> Int32 { 67 | return await sessionActor.requestPseudoTerminal(channel: self, name: name, cols: cols, rows: rows) 68 | } 69 | 70 | public func setTerminalSize (cols: Int, rows: Int, pixelWidth: Int, pixelHeight: Int) async { 71 | return await sessionActor.setTerminalSize(channel: self, cols: cols, rows: rows, pixelWidth: pixelWidth, pixelHeight: pixelHeight) 72 | } 73 | 74 | // Returns 0 on success, or a LIBSSH2 error otherwise, always retries operations, so EAGAIN is never returned 75 | public func processStartup (request: String, message: String?) async -> Int32 { 76 | return await sessionActor.processStartup(channel: self, request: request, message: message) 77 | } 78 | 79 | public var receivedEOF: Bool { 80 | get async { 81 | return await sessionActor.receivedEof (channel: self) 82 | } 83 | } 84 | 85 | // Invoked when there is some data received on the session, and we try to fetch it for the channel 86 | // if it is available, we dispatch it. Returns true if the channel is still active 87 | func ping () async -> Bool { 88 | var eof: Bool = true 89 | let pair = await sessionActor.ping(channel: self, eofDetected: &eof) 90 | 91 | if let channelData = pair { 92 | await readCallback (self, channelData.0, channelData.1, eof) 93 | } 94 | return !eof 95 | } 96 | 97 | func close () async { 98 | await sessionActor.close (channel: self) 99 | } 100 | 101 | /// Sends the provided data to the channel, and invokes the callback with the status code when doneaaaa 102 | func send (_ data: Data, callback: @escaping (Int)->()) async { 103 | await sessionActor.send (channel: self, data: data, callback: callback) 104 | } 105 | 106 | func exec (_ command: String) async -> Int32 { 107 | return await sessionActor.exec (channel: self, command: command) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /SwiftTermApp/Ssh/Errors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Errors.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 12/11/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @_implementationOnly import CSSH 11 | 12 | public func libSsh2ErrorToString (error: CInt) -> String { 13 | switch error { 14 | case LIBSSH2_ERROR_SOCKET_NONE: return "LIBSSH2_ERROR_SOCKET_NONE" 15 | case LIBSSH2_ERROR_BANNER_RECV: return "LIBSSH2_ERROR_BANNER_RECV" 16 | case LIBSSH2_ERROR_BANNER_SEND: return "LIBSSH2_ERROR_BANNER_SEND" 17 | case LIBSSH2_ERROR_INVALID_MAC: return "LIBSSH2_ERROR_INVALID_MAC" 18 | case LIBSSH2_ERROR_KEX_FAILURE: return "LIBSSH2_ERROR_KEX_FAILURE" 19 | case LIBSSH2_ERROR_ALLOC: return "LIBSSH2_ERROR_ALLOC" 20 | case LIBSSH2_ERROR_SOCKET_SEND: return "LIBSSH2_ERROR_SOCKET_SEND" 21 | case LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE: return "LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE" 22 | case LIBSSH2_ERROR_TIMEOUT: return "LIBSSH2_ERROR_TIMEOUT" 23 | case LIBSSH2_ERROR_HOSTKEY_INIT: return "LIBSSH2_ERROR_HOSTKEY_INIT" 24 | case LIBSSH2_ERROR_HOSTKEY_SIGN: return "LIBSSH2_ERROR_HOSTKEY_SIGN" 25 | case LIBSSH2_ERROR_DECRYPT: return "LIBSSH2_ERROR_DECRYPT" 26 | case LIBSSH2_ERROR_SOCKET_DISCONNECT: return "LIBSSH2_ERROR_SOCKET_DISCONNECT" 27 | case LIBSSH2_ERROR_PROTO: return "LIBSSH2_ERROR_PROTO" 28 | case LIBSSH2_ERROR_PASSWORD_EXPIRED: return "LIBSSH2_ERROR_PASSWORD_EXPIRED" 29 | case LIBSSH2_ERROR_FILE: return "LIBSSH2_ERROR_FILE" 30 | case LIBSSH2_ERROR_METHOD_NONE: return "LIBSSH2_ERROR_METHOD_NONE" 31 | case LIBSSH2_ERROR_AUTHENTICATION_FAILED: return "LIBSSH2_ERROR_AUTHENTICATION_FAILED" 32 | case LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED: return "LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED" 33 | case LIBSSH2_ERROR_CHANNEL_OUTOFORDER: return "LIBSSH2_ERROR_CHANNEL_OUTOFORDER" 34 | case LIBSSH2_ERROR_CHANNEL_FAILURE: return "LIBSSH2_ERROR_CHANNEL_FAILURE" 35 | case LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED: return "LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED" 36 | case LIBSSH2_ERROR_CHANNEL_UNKNOWN: return "LIBSSH2_ERROR_CHANNEL_UNKNOWN" 37 | case LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED: return "LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED" 38 | case LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED: return "LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED" 39 | case LIBSSH2_ERROR_CHANNEL_CLOSED: return "LIBSSH2_ERROR_CHANNEL_CLOSED" 40 | case LIBSSH2_ERROR_CHANNEL_EOF_SENT: return "LIBSSH2_ERROR_CHANNEL_EOF_SENT" 41 | case LIBSSH2_ERROR_SCP_PROTOCOL: return "LIBSSH2_ERROR_SCP_PROTOCOL" 42 | case LIBSSH2_ERROR_ZLIB: return "LIBSSH2_ERROR_ZLIB" 43 | case LIBSSH2_ERROR_SOCKET_TIMEOUT: return "LIBSSH2_ERROR_SOCKET_TIMEOUT" 44 | case LIBSSH2_ERROR_SFTP_PROTOCOL: return "LIBSSH2_ERROR_SFTP_PROTOCOL" 45 | case LIBSSH2_ERROR_REQUEST_DENIED: return "LIBSSH2_ERROR_REQUEST_DENIED" 46 | case LIBSSH2_ERROR_METHOD_NOT_SUPPORTED: return "LIBSSH2_ERROR_METHOD_NOT_SUPPORTED" 47 | case LIBSSH2_ERROR_INVAL: return "LIBSSH2_ERROR_INVAL" 48 | case LIBSSH2_ERROR_INVALID_POLL_TYPE: return "LIBSSH2_ERROR_INVALID_POLL_TYPE" 49 | case LIBSSH2_ERROR_PUBLICKEY_PROTOCOL: return "LIBSSH2_ERROR_PUBLICKEY_PROTOCOL" 50 | case LIBSSH2_ERROR_EAGAIN: return "LIBSSH2_ERROR_EAGAIN" 51 | case LIBSSH2_ERROR_BUFFER_TOO_SMALL: return "LIBSSH2_ERROR_BUFFER_TOO_SMALL" 52 | case LIBSSH2_ERROR_BAD_USE: return "LIBSSH2_ERROR_BAD_USE" 53 | case LIBSSH2_ERROR_COMPRESS: return "LIBSSH2_ERROR_COMPRESS" 54 | case LIBSSH2_ERROR_OUT_OF_BOUNDARY: return "LIBSSH2_ERROR_OUT_OF_BOUNDARY" 55 | case LIBSSH2_ERROR_AGENT_PROTOCOL: return "LIBSSH2_ERROR_AGENT_PROTOCOL" 56 | case LIBSSH2_ERROR_SOCKET_RECV: return "LIBSSH2_ERROR_SOCKET_RECV" 57 | case LIBSSH2_ERROR_ENCRYPT: return "LIBSSH2_ERROR_ENCRYPT" 58 | case LIBSSH2_ERROR_BAD_SOCKET: return "LIBSSH2_ERROR_BAD_SOCKET" 59 | case LIBSSH2_ERROR_KNOWN_HOSTS: return "LIBSSH2_ERROR_KNOWN_HOSTS" 60 | case LIBSSH2_ERROR_CHANNEL_WINDOW_FULL: return "LIBSSH2_ERROR_CHANNEL_WINDOW_FULL" 61 | case LIBSSH2_ERROR_KEYFILE_AUTH_FAILED: return "LIBSSH2_ERROR_KEYFILE_AUTH_FAILED" 62 | default: 63 | return "Unknown SSH2 Code: \(error)" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /SwiftTermApp/Ssh/LibsshKnownHost.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LibsshKnownHost.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 1/20/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @_implementationOnly import CSSH 11 | 12 | public enum KnownHostStatus { 13 | /// hosts and keys match. 14 | case match 15 | /// something prevented the check to be made 16 | case failure 17 | /// host was found, but the keys didn't match 18 | case keyMismatch 19 | /// no host match was found 20 | case notFound 21 | } 22 | 23 | class LibsshKnownHost { 24 | func check(hostName: String, port: Int32, key: [Int8]) -> (status: KnownHostStatus, key: String?) { // (status: KnownHostStatus, knownHost: libssh2_knownhost?) { 25 | 26 | var ptr: UnsafeMutablePointer? = UnsafeMutablePointer.allocate(capacity: 1) 27 | var kcopy = key 28 | 29 | let r = libssh2_knownhost_checkp(khHandle, hostName, port, &kcopy, key.count, LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW, &ptr) 30 | defer { 31 | //ptr?.deallocate() 32 | } 33 | switch r { 34 | 35 | case LIBSSH2_KNOWNHOST_CHECK_FAILURE: 36 | return (.failure, nil) 37 | case LIBSSH2_KNOWNHOST_CHECK_MATCH: 38 | let x: libssh2_knownhost = ptr?.pointee ?? libssh2_knownhost() 39 | let keyStr = String(cString: x.key) 40 | return (.match, keyStr) 41 | case LIBSSH2_KNOWNHOST_CHECK_MISMATCH: 42 | let x: libssh2_knownhost = ptr?.pointee ?? libssh2_knownhost() 43 | let keyStr = String(cString: x.key) 44 | return (.keyMismatch, keyStr) 45 | case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND: 46 | return (.notFound, nil) 47 | default: 48 | return (.failure, nil) 49 | } 50 | } 51 | 52 | var khHandle: OpaquePointer 53 | weak var sessionActor: SessionActor! 54 | 55 | init (sessionActor: SessionActor, knownHost: OpaquePointer){ 56 | self.sessionActor = sessionActor 57 | self.khHandle = knownHost 58 | } 59 | 60 | deinit { 61 | libssh2_knownhost_free (khHandle) 62 | } 63 | 64 | // returns nil on success, otherwise an error description 65 | func readFile (filename: String) async -> String? { 66 | return await sessionActor.readFile (knownHost: self, filename: filename) 67 | } 68 | 69 | // returns nil on success, otherwise an error description 70 | func writeFile (filename: String) async -> String? { 71 | return await sessionActor.writeFile (knownHost: self, filename: filename) 72 | } 73 | 74 | /// Returns nil on success, otherwise a string describing the error 75 | func add(hostname: String, port: Int32? = nil, key: [Int8], keyType: String, comment: String) async -> String? { 76 | 77 | let fullhostname: String 78 | if let p = port { 79 | fullhostname = "[\(hostname)]:\(p)" 80 | } else { 81 | fullhostname = hostname 82 | } 83 | 84 | let keyTypeCode: Int32 85 | switch keyType { 86 | case "ssh-rsa": 87 | keyTypeCode = LIBSSH2_KNOWNHOST_KEY_SSHRSA 88 | case "ssh-dss": 89 | keyTypeCode = LIBSSH2_KNOWNHOST_KEY_SSHDSS 90 | case "ecdsa-sha2-nistp256": 91 | keyTypeCode = LIBSSH2_KNOWNHOST_KEY_ECDSA_256 92 | case "ecdsa-sha2-nistp384": 93 | keyTypeCode = LIBSSH2_KNOWNHOST_KEY_ECDSA_384 94 | case "ecdsa-sha2-nistp521": 95 | keyTypeCode = LIBSSH2_KNOWNHOST_KEY_ECDSA_521 96 | case "ssh-ed25519", "ed25519": 97 | keyTypeCode = LIBSSH2_KNOWNHOST_KEY_ED25519 98 | default: 99 | return "knownHost.add: the provided key type is \(keyType) which is not currently supported" 100 | } 101 | 102 | return await sessionActor.add (knownHost: self, fullhostname: fullhostname, key: key, keyTypeCode: keyTypeCode, comment: comment) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /SwiftTermApp/Ssh/SFTP.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SFTP.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 2/1/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @_implementationOnly import CSSH 11 | 12 | public class SftpHandle { 13 | weak var session: Session! 14 | weak var sessionActor: SessionActor! 15 | var handle: OpaquePointer! 16 | 17 | init (_ sftpHandle: OpaquePointer, session: Session) { 18 | self.handle = sftpHandle 19 | self.session = session 20 | self.sessionActor = session.sessionActor 21 | } 22 | deinit { 23 | if let h = handle { 24 | let k = sessionActor 25 | Task { 26 | await k?.sftpClose(sftpHandle: h) 27 | } 28 | } 29 | } 30 | 31 | func close () async { 32 | if let h = handle { 33 | await sessionActor.sftpClose(sftpHandle: h) 34 | handle = nil 35 | } 36 | } 37 | } 38 | 39 | public class SftpFileHandle : SftpHandle { 40 | override init (_ sftpHandle: OpaquePointer, session: Session) { 41 | super.init (sftpHandle, session: session) 42 | } 43 | } 44 | 45 | public class SftpDirHandle : SftpHandle { 46 | override init (_ sftpHandle: OpaquePointer, session: Session) { 47 | super.init (sftpHandle, session: session) 48 | } 49 | 50 | /// Reads the next directory entry 51 | /// - Returns: nil at the end, or a tuple containing the file attributes, the file string, and an `ls -l` style renderinf of the contents. The string values can be nil, if there were file contents that could not be represented as utf8. 52 | func readDir () async -> (attrs: LIBSSH2_SFTP_ATTRIBUTES, name: Data, rendered: Data)? { 53 | return await sessionActor.sftpReaddir(sftpHandle: handle) 54 | } 55 | } 56 | 57 | public class SFTP { 58 | var handle: OpaquePointer 59 | weak var session: Session! 60 | 61 | init (session: Session, sftpHandle: OpaquePointer) { 62 | self.handle = sftpHandle 63 | self.session = session 64 | } 65 | 66 | deinit { 67 | let h = handle 68 | let k = session.sessionActor 69 | Task { 70 | await k.sftpShutdown (h) 71 | } 72 | } 73 | 74 | func stat (path: String) async -> LIBSSH2_SFTP_ATTRIBUTES? { 75 | await session.sessionActor.sftpStat (self, path: path) 76 | } 77 | 78 | func open (path: String, flags: UInt) async -> SftpHandle? { 79 | guard let h = await session.sessionActor.sftpOpen (self, path: path, flags: flags, file: true) else { 80 | return nil 81 | } 82 | return SftpHandle (h, session: session) 83 | } 84 | 85 | func openDir (path: String, flags: UInt) async -> SftpDirHandle? { 86 | guard let h = await session.sessionActor.sftpOpen (self, path: path, flags: flags, file: false) else { 87 | return nil 88 | } 89 | return SftpDirHandle (h, session: session) 90 | } 91 | 92 | func readFile (path: String, limit: Int) async -> [Int8]? { 93 | await session.sessionActor.sftpReadFile (self, path: path, limit: limit) 94 | } 95 | 96 | func readFileAsString (path: String, limit: Int) async -> String? { 97 | if let bytes = await readFile (path: path, limit: limit) { 98 | let d = Data (bytes: bytes, count: bytes.count) 99 | return String (bytes: d, encoding: .utf8) 100 | } 101 | return nil 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /SwiftTermApp/SwiftTermApp-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Bridge-Header.h 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 5/31/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | #ifndef Bridge_Header_h 10 | #define Bridge_Header_h 11 | 12 | #endif /* Bridge_Header_h */ 13 | -------------------------------------------------------------------------------- /SwiftTermApp/SwiftTermApp.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.org.tirania.SwiftTermKeys 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /SwiftTermApp/Terminal/InputKeyboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputKeyboard.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/28/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct KbdButton: View { 12 | var text: String 13 | var sequence: [UInt8] 14 | 15 | var body: some View { 16 | Text (text) 17 | .frame(minWidth: 30, minHeight: 30) 18 | .lineLimit(1) 19 | .font (.system (size: 16)) 20 | .foregroundColor(.primary) 21 | .padding(3) 22 | .background (Color (UIColor.systemBackground)) 23 | .cornerRadius(3) 24 | .shadow(color: .gray, radius: 1, x: 0, y: 1) 25 | 26 | } 27 | } 28 | 29 | struct KbdLargeButton: View { 30 | var text: String 31 | var sequence: [UInt8] 32 | 33 | var body: some View { 34 | Text (text) 35 | .frame(minWidth: 40, minHeight: 30) 36 | .font (.system (size: 12)) 37 | .foregroundColor(.primary) 38 | .padding(3) 39 | .background (Color (UIColor.tertiarySystemBackground)) 40 | .cornerRadius(3) 41 | .shadow(color: .gray, radius: 1, x: 0, y: 1) 42 | 43 | } 44 | } 45 | 46 | struct KbdImage: View { 47 | var image: String 48 | var sequence: [UInt8] 49 | 50 | var body: some View { 51 | Image (systemName: image) 52 | .frame(minWidth: 40, minHeight: 30) 53 | .font (.system (size: 16)) 54 | .foregroundColor(.primary) 55 | .padding(3) 56 | .background (Color (UIColor.tertiarySystemBackground)) 57 | .cornerRadius(3) 58 | .shadow(color: .gray, radius: 1, x: 0, y: 1) 59 | 60 | } 61 | } 62 | 63 | struct KbdControls: View { 64 | let spec = [ 65 | GridItem(.flexible(minimum: 40, maximum: 40)), 66 | GridItem(.flexible(minimum: 40, maximum: 40)), 67 | GridItem(.flexible(minimum: 40, maximum: 40)) 68 | ] 69 | 70 | var body: some View { 71 | VStack { 72 | LazyVGrid (columns: spec) { 73 | KbdLargeButton (text: "Ins", sequence: []) 74 | KbdLargeButton (text: "Home", sequence: []) 75 | KbdLargeButton (text: "Page\nUp", sequence: []) 76 | KbdImage (image: "delete.forward", sequence: []) 77 | KbdLargeButton (text: "End", sequence: []) 78 | KbdLargeButton (text: "Page\nDown", sequence: []) 79 | } 80 | } 81 | } 82 | } 83 | 84 | struct KbdAssortedKeys: View { 85 | let spec = [ 86 | GridItem(.flexible(minimum: 30, maximum: 30)), 87 | GridItem(.flexible(minimum: 30, maximum: 30)), 88 | GridItem(.flexible(minimum: 30, maximum: 30)), 89 | GridItem(.flexible(minimum: 30, maximum: 30)), 90 | GridItem(.flexible(minimum: 30, maximum: 30)), 91 | GridItem(.flexible(minimum: 30, maximum: 30)), 92 | ] 93 | let keys: [(String, [UInt8])] = [ 94 | ("{", []), 95 | ("}", []), 96 | ("(", []), 97 | (")", []), 98 | ("[", []), 99 | ("]", []), 100 | 101 | // Already on the top entry ~ | / - 102 | ("~" , []), 103 | ("?" , []), 104 | ("\\", []), 105 | (":" , []), 106 | (";" , []), 107 | ("\"", []), 108 | ("'" , []), 109 | ("+", []), 110 | ("-", []), 111 | ("*", []), 112 | ] 113 | 114 | var body: some View { 115 | VStack { 116 | LazyVGrid (columns: spec) { 117 | ForEach (keys.indices, id: \.self) { idx in 118 | KbdButton(text: keys[idx].0, sequence: keys[idx].1) 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | struct InputKeyboard: View { 126 | let columns = [ 127 | GridItem(.adaptive(minimum: 30)) 128 | ] 129 | 130 | var body: some View { 131 | VStack { 132 | LazyVGrid (columns: columns){ 133 | KbdButton (text: "F1", sequence: []) 134 | KbdButton (text: "F2", sequence: []) 135 | KbdButton (text: "F3", sequence: []) 136 | KbdButton (text: "F4", sequence: []) 137 | KbdButton (text: "F5", sequence: []) 138 | KbdButton (text: "F6", sequence: []) 139 | KbdButton (text: "F7", sequence: []) 140 | KbdButton (text: "F8", sequence: []) 141 | KbdButton (text: "F9", sequence: []) 142 | KbdButton (text: "F10", sequence: []) 143 | 144 | } 145 | HStack (alignment: .top) { 146 | KbdAssortedKeys () 147 | KbdControls () 148 | } 149 | } 150 | } 151 | } 152 | 153 | struct InputKeyboard_Previews: PreviewProvider { 154 | static var previews: some View { 155 | InputKeyboard() 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/ButtonColors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Colors.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 5/6/20. 6 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct ButtonColors { 13 | static let highColor = Color(#colorLiteral(red: 0.007762347814, green: 0.4766914248, blue: 0.9985215068, alpha: 1)) 14 | static let backgroundColor = Color(#colorLiteral(red: 0.8980392157, green: 0.9137254902, blue: 0.968627451, alpha: 1)) 15 | static let darkHighColor = Color(#colorLiteral(red: 0.007762347814, green: 0.4766914248, blue: 0.9985215068, alpha: 1)) 16 | static let darkBackgroundColor = Color(#colorLiteral(red: 0.05098039216, green: 0.1176470588, blue: 0.2431372549, alpha: 1)) 17 | } 18 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/Colors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Colors.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 5/6/20. 6 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ButtonColors { 12 | static let highColor = Color(#colorLiteral(red: 0.007762347814, green: 0.4766914248, blue: 0.9985215068, alpha: 1)) 13 | static let backgroundColor = Color(#colorLiteral(red: 0.9307063222, green: 0.945577085, blue: 0.9838711619, alpha: 1)) 14 | } 15 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/ConnectionUI/ConnectionMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConnectionMessage.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 2/15/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ConnectionMessage { 12 | init (host: Host, message: String, ok: @escaping ()->()) 13 | } 14 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/ConnectionUI/GenericConnectionIssue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TmuxSessionGone.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 2/15/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct GenericConnectionIssue: View, ConnectionMessage { 12 | init(host: Host, message: String, ok: @escaping () -> ()) { 13 | self._host = State (initialValue: host) 14 | self._error = State (initialValue: message) 15 | self._ok = State (initialValue: ok) 16 | } 17 | 18 | @State var host: Host 19 | @State var error: String 20 | @State var ok: () -> () = { } 21 | 22 | var body: some View { 23 | VStack (alignment: .center){ 24 | HStack (alignment: .center){ 25 | Image (systemName: "desktopcomputer.trianglebadge.exclamationmark") 26 | .symbolRenderingMode(.multicolor) 27 | .resizable() 28 | .aspectRatio(contentMode: .fit) 29 | .frame(width: 30) 30 | .padding (10) 31 | Text ("\(host.alias)") 32 | .font(.title) 33 | Spacer () 34 | } 35 | .padding() 36 | .background(Color(UIColor.secondarySystemBackground)) 37 | //.background(.yellow) 38 | VStack (alignment: .center){ 39 | Text (error) 40 | .padding ([.bottom]) 41 | HStack (alignment: .center, spacing: 20) { 42 | Button ("Ok") { ok () } 43 | .buttonStyle(.bordered) 44 | .controlSize(.large) 45 | } 46 | } 47 | .padding() 48 | Spacer () 49 | } 50 | } 51 | } 52 | 53 | struct TmuxSessionGone_Previews: PreviewProvider { 54 | struct WrapperView: View { 55 | var host: Host 56 | 57 | init () { 58 | host = CHost (context: DataController.preview.container.viewContext) 59 | host.alias = "dbserver" 60 | host.hostname = "dbserver.domain.com" 61 | } 62 | 63 | var body: some View { 64 | HostConnectionError(host: host, error: "Other end got unhappy") 65 | } 66 | } 67 | static var previews: some View { 68 | WrapperView() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/ConnectionUI/HostAuthKeyMismatch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HostAuthKeyMismatch.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/21/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct HostAuthKeyMismatch: View { 12 | @State var alias: String 13 | @State var hostString: String 14 | @State var fingerprint: String 15 | var callback: () -> () 16 | 17 | var body: some View { 18 | VStack (alignment: .leading){ 19 | HStack (alignment: .top){ 20 | Image (systemName: "exclamationmark.triangle") 21 | .resizable() 22 | .aspectRatio(1, contentMode: .fit) 23 | .frame(width: 30) 24 | .padding (10) 25 | VStack (alignment: .leading){ 26 | Text ("Warning - Remote Host Identification Has Changed") 27 | .font(.headline) 28 | .padding([.bottom]) 29 | Text ("**Host:** \(alias)") 30 | .font(.subheadline) 31 | 32 | } 33 | Spacer () 34 | } 35 | .padding() 36 | .background(.yellow) 37 | VStack { 38 | Text ("**It is possible that someone is doing something nasty**.\n\nSomeone could be eavesdropping on you right now (man-in-the-middle attack).\n\nIt is also possible that the host key has just been changed. The fingerprint for the RSA key sent by the remote host is:\n\n`\(fingerprint)`\n\nContact your system administrator to verify.\n\nIf this is expected, you can remove the existing known key from the Known Hosts settings.") 39 | .padding ([.bottom]) 40 | .minimumScaleFactor(0.4) 41 | 42 | Button ("Go Back", role: .cancel) { callback () } 43 | .buttonStyle(.borderedProminent) 44 | .controlSize(.large) 45 | .tint(Color.red) 46 | } 47 | .padding() 48 | } 49 | .toolbar { 50 | ToolbarItem (placement: .navigationBarTrailing) { 51 | Button ("Go Back") { 52 | callback () 53 | } 54 | } 55 | } 56 | }} 57 | 58 | struct HostAuthKeyMismatch_Previews: PreviewProvider { 59 | static var previews: some View { 60 | HostAuthKeyMismatch(alias: "mac", hostString: "localhost:20", fingerprint: "ECDSA SHA256:AAAAB3NzaC1yc2EAAAADAQABAAABgQDCOFP4DoqHmagF") {} 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/ConnectionUI/HostAuthUnknown.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HostAuthUnknown.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/21/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct HostAuthUnknown: View { 12 | @State var alias: String 13 | @State var hostString: String 14 | @State var fingerprint: String 15 | var cancelCallback: () -> () = { } 16 | var okCallback: () -> () = { } 17 | 18 | var body: some View { 19 | VStack (alignment: .leading){ 20 | HStack (alignment: .center){ 21 | Image (systemName: "info.circle") 22 | .symbolRenderingMode(.hierarchical) 23 | .resizable() 24 | .aspectRatio(1, contentMode: .fit) 25 | .foregroundColor(Color.accentColor) 26 | .frame(width: 30) 27 | .padding (10) 28 | Text ("Host: \(alias)") 29 | .font(.title) 30 | Spacer () 31 | } 32 | .padding() 33 | .background(Color(UIColor.secondarySystemBackground)) 34 | //.background(.yellow) 35 | VStack { 36 | Text ("The authenticity of host '`\(hostString)`' can not be established.\n\nIf this is the first time you connect to this host, you can check that the fingertprint for the host matches the fingerprint you recognize and then proceed. Otherwise, select cancel.\n\nFingerprint:\n\n`\(fingerprint)`\n\nDo you want to continue connecting?") 37 | .padding ([.bottom]) 38 | HStack (alignment: .center, spacing: 20) { 39 | Button ("Cancel", role: .cancel) { cancelCallback () } 40 | .buttonStyle(.bordered) 41 | .controlSize(.large) 42 | .tint(Color.red) 43 | 44 | Button ("Yes", role: .none) { okCallback ()} 45 | .keyboardShortcut(.defaultAction) 46 | .buttonStyle(.bordered) 47 | .controlSize(.large) 48 | } 49 | } 50 | .padding() 51 | } 52 | } 53 | } 54 | 55 | struct HostAuthUnknown_Previews: PreviewProvider { 56 | static var previews: some View { 57 | HostAuthUnknown(alias: "mac", hostString: "localhost:22", fingerprint: "ECDSA SHA256:AAAAB3NzaC1yc2EAAAADAQABAAABgQDCOFP4DoqHmagF") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/ConnectionUI/HostConnectionClosed.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HostConnectionClosed.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/23/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct HostConnectionClosed: View { 12 | @State var host: Host 13 | @State var receivedEOF: Bool 14 | @State var ok: () -> () = { } 15 | 16 | var body: some View { 17 | VStack (alignment: .center){ 18 | HStack (alignment: .center){ 19 | 20 | Image (systemName: receivedEOF ? "info.circle" : "desktopcomputer.trianglebadge.exclamationmark") 21 | .symbolRenderingMode(.multicolor) 22 | .resizable() 23 | .aspectRatio(contentMode: .fit) 24 | .frame(width: 40) 25 | .padding (10) 26 | Text ("\(host.alias)") 27 | .font(.title) 28 | Spacer () 29 | } 30 | .padding() 31 | .background(Color(UIColor.secondarySystemBackground)) 32 | //.background(.yellow) 33 | VStack (alignment: .center){ 34 | if receivedEOF { 35 | Text ("Connection to `\(host.hostname):\(String (host.port))` was closed") 36 | .padding ([.bottom]) 37 | } else { 38 | Text ("Connection to `\(host.hostname):\(String (host.port))` was terminated") 39 | .padding ([.bottom]) 40 | } 41 | 42 | Spacer () 43 | HStack (alignment: .center, spacing: 20) { 44 | Button ("Ok") { ok () } 45 | .buttonStyle(.bordered) 46 | .controlSize(.large) 47 | } 48 | } 49 | .padding() 50 | Spacer () 51 | } 52 | } 53 | } 54 | 55 | struct HostConnectionClosed_Previews: PreviewProvider { 56 | static var previews: some View { 57 | WrapperView () 58 | } 59 | 60 | struct WrapperView: View { 61 | 62 | var host: Host 63 | init () { 64 | host = CHost (context: DataController.preview.container.viewContext) 65 | host.alias = "dbserver" 66 | host.hostname = "dbserver.domain.com" 67 | } 68 | 69 | var body: some View { 70 | HostConnectionClosed(host: host, receivedEOF: false) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/ConnectionUI/HostConnectionError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HostConnectionError.swift 3 | // HostConnectionError 4 | // 5 | // Created by Miguel de Icaza on 7/22/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct HostConnectionError: View { 13 | @State var host: Host 14 | @State var error: String 15 | @State var ok: () -> () = { } 16 | 17 | var body: some View { 18 | VStack (alignment: .center){ 19 | HStack (alignment: .center){ 20 | Image (systemName: "desktopcomputer.trianglebadge.exclamationmark") 21 | .symbolRenderingMode(.multicolor) 22 | .resizable() 23 | .aspectRatio(contentMode: .fit) 24 | .frame(width: 30) 25 | .padding (10) 26 | Text ("\(host.alias)") 27 | .font(.title) 28 | Spacer () 29 | } 30 | .padding() 31 | .background(Color(UIColor.secondarySystemBackground)) 32 | //.background(.yellow) 33 | VStack (alignment: .center){ 34 | Text ("`\(host.hostname):\(String (host.port))` - Connection error\n\nDetails: \(error)") 35 | .padding ([.bottom]) 36 | HStack (alignment: .center, spacing: 20) { 37 | Button ("Ok") { ok () } 38 | .buttonStyle(.bordered) 39 | .controlSize(.large) 40 | } 41 | } 42 | .padding() 43 | Spacer () 44 | } 45 | } 46 | } 47 | 48 | struct HostConnectionError_Previews: PreviewProvider { 49 | static var previews: some View { 50 | WrapperView () 51 | } 52 | 53 | struct WrapperView: View { 54 | var host: Host 55 | 56 | init () { 57 | host = CHost (context: DataController.preview.container.viewContext) 58 | host.alias = "dbserver" 59 | host.hostname = "dbserver.domain.com" 60 | } 61 | 62 | var body: some View { 63 | HostConnectionError(host: host, error: "Connection closed") 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/Dialogs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Prompts.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 5/7/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SwiftUI 12 | 13 | /// Provides synchronous static methods for UI tasks like prompts or messages. 14 | // 15 | /// These must be invoked from a background thread 16 | /// - `password (vc:challenge:)` 17 | /// - `user(vc:)` 18 | /// 19 | public class Dialogs { 20 | 21 | var vc: UIViewController 22 | init (parentVC: UIViewController) { 23 | dispatchPrecondition(condition: .notOnQueue(DispatchQueue.main)) 24 | 25 | vc = parentVC 26 | } 27 | 28 | public static func password (vc: UIViewController, challenge: String) -> String { 29 | let p = Dialogs (parentVC: vc) 30 | return p.passwordPrompt (challenge: challenge) 31 | } 32 | 33 | public static func user (vc: UIViewController) -> String { 34 | let p = Dialogs (parentVC: vc) 35 | return p.userPrompt () 36 | } 37 | 38 | /// Interactive prompt to request a password 39 | /// - Parameters: 40 | /// - challenge: the text to display to the user 41 | /// - Returns: the entered password 42 | func passwordPrompt (challenge: String) -> String { 43 | let semaphore = DispatchSemaphore(value: 0) 44 | DispatchQueue.main.async { 45 | let alertController = UIAlertController(title: String (localized: "Authentication challenge"), message: challenge, preferredStyle: .alert) 46 | alertController.addTextField { [unowned self] (textField) in 47 | textField.placeholder = challenge 48 | textField.isSecureTextEntry = true 49 | self.passwordTextField = textField 50 | } 51 | alertController.addAction(UIAlertAction(title: String (localized: "OK"), style: .default) { [unowned self] _ in 52 | if let tf = self.passwordTextField { 53 | promptedPassword = tf.text ?? "" 54 | } 55 | semaphore.signal() 56 | 57 | }) 58 | self.vc.present(alertController, animated: true, completion: nil) 59 | } 60 | 61 | let _ = semaphore.wait(timeout: DispatchTime.distantFuture) 62 | return promptedPassword 63 | } 64 | 65 | /// Interactive prompt to request a username 66 | var usernameTextField: UITextField? 67 | 68 | func userPrompt () -> String { 69 | let semaphore = DispatchSemaphore(value: 0) 70 | DispatchQueue.main.async { 71 | let alertController = UIAlertController(title: "Username", message: "Enter your username", preferredStyle: .alert) 72 | alertController.addTextField { [unowned self] (textField) in 73 | textField.placeholder = "" 74 | self.usernameTextField = textField 75 | } 76 | alertController.addAction(UIAlertAction(title: "OK", style: .default) { [unowned self] _ in 77 | if let tf = self.usernameTextField { 78 | promptedUser = tf.text ?? "" 79 | } 80 | semaphore.signal() 81 | 82 | }) 83 | self.vc.present(alertController, animated: true, completion: nil) 84 | } 85 | 86 | let _ = semaphore.wait(timeout: DispatchTime.distantFuture) 87 | return promptedUser 88 | } 89 | var passwordTextField: UITextField? 90 | } 91 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/DirectorySelector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DirectorySelector.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 2/8/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DirectorySelector: View { 12 | var body: some View { 13 | Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) 14 | } 15 | } 16 | 17 | struct DirectorySelector_Previews: PreviewProvider { 18 | static var previews: some View { 19 | DirectorySelector() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/InteractiveLogin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InteractiveLogin.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/10/20. 6 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct InteractiveLogin: View { 12 | @State var host: Host? 13 | @State var username: String = "" 14 | 15 | var body: some View { 16 | VStack { 17 | Text ("Connecting") 18 | .padding () 19 | Text (host?.hostname ?? "No Host") 20 | TextField ("Username", text: $username) 21 | .padding(4) 22 | .background(RoundedRectangle(cornerRadius: 4)) 23 | .foregroundColor(Color (#colorLiteral(red: 0.921431005, green: 0.9214526415, blue: 0.9214410186, alpha: 1))) 24 | 25 | .padding() 26 | .frame(maxWidth: 240) 27 | HStack { 28 | Button (action: {}) { Text ("Connect") } 29 | } 30 | Spacer () 31 | } 32 | } 33 | } 34 | 35 | struct InteractivePassword: View { 36 | @State var host: Host? 37 | @State var username: String = "" 38 | 39 | var body: some View { 40 | VStack { 41 | Text ("Password") 42 | .padding () 43 | Text (host?.hostname ?? "No Host") 44 | TextField ("Password", text: $username) 45 | .padding(4) 46 | .background(RoundedRectangle(cornerRadius: 4)) 47 | .foregroundColor(Color (#colorLiteral(red: 0.921431005, green: 0.9214526415, blue: 0.9214410186, alpha: 1))) 48 | 49 | .padding() 50 | .frame(maxWidth: 240) 51 | HStack { 52 | Button (action: {}) { Text ("Connect") } 53 | } 54 | Spacer () 55 | } 56 | } 57 | } 58 | 59 | struct InteractiveLogin_Previews: PreviewProvider { 60 | static var previews: some View { 61 | Text ("Test") 62 | .sheet(isPresented: .constant(true)) { 63 | HStack { 64 | InteractiveLogin () 65 | InteractivePassword() 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/LazyView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LazyView.swift 3 | // SwiftTermApp 4 | // 5 | // From: Chris Eidhof: https://gist.github.com/chriseidhof/d2fcafb53843df343fe07f3c0dac41d5 6 | // Discussion: https://twitter.com/chriseidhof/status/1144242544680849410?lang=en 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct LazyView: View { 12 | let build: () -> Content 13 | init(_ build: @autoclosure @escaping () -> Content) { 14 | self.build = build 15 | } 16 | var body: Content { 17 | build() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/Passphrase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Passphrase.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 6/14/21. 6 | // Copyright © 2021 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct Passphrase: View { 12 | @State var showingPassword = false 13 | @Binding var passphrase: String 14 | @State var disabled = false 15 | 16 | var body: some View { 17 | HStack { 18 | Text ("Passphrase") 19 | if showingPassword { 20 | TextField ("•••••••", text: $passphrase) 21 | .multilineTextAlignment(.trailing) 22 | .autocapitalization(.none) 23 | .disabled(disabled) 24 | } else { 25 | SecureField ("•••••••", text: $passphrase) 26 | .multilineTextAlignment(.trailing) 27 | .autocapitalization(.none) 28 | .disabled(disabled) 29 | } 30 | Button (action: { 31 | self.showingPassword.toggle () 32 | }, label: { 33 | Image(systemName: self.showingPassword ? "eye" : "eye.slash") 34 | 35 | }) 36 | } 37 | } 38 | } 39 | 40 | struct Passphrase_Previews: PreviewProvider { 41 | @State static var passphrase = "secret" 42 | 43 | static var previews: some View { 44 | VStack { 45 | Passphrase(passphrase: Passphrase_Previews.$passphrase) 46 | Passphrase(showingPassword: true, passphrase: Passphrase_Previews.$passphrase) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/STButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TerminalButton.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 5/1/20. 6 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct STButton: View { 13 | var text: LocalizedStringKey 14 | let icon: String 15 | @State var centered = true 16 | @Environment(\.colorScheme) var colorScheme 17 | var action: ()->Void 18 | 19 | var body: some View { 20 | Button (action: action) { 21 | HStack { 22 | if centered { 23 | Spacer() 24 | } 25 | Image (systemName: icon) 26 | .foregroundColor(colorScheme == .dark ? ButtonColors.darkHighColor : ButtonColors.highColor) 27 | .font(Font.title.weight(.semibold)) 28 | Text (self.text) 29 | .foregroundColor(colorScheme == .dark ? ButtonColors.darkHighColor : ButtonColors.highColor) 30 | .fontWeight(.bold) 31 | Spacer() 32 | } 33 | .padding (10) 34 | .background(colorScheme == .dark ? ButtonColors.darkBackgroundColor : ButtonColors.backgroundColor) 35 | .cornerRadius(12) 36 | .foregroundColor(colorScheme == .dark ? ButtonColors.darkHighColor : ButtonColors.highColor) 37 | .padding([.horizontal]) 38 | //.frame (maxWidth: 400) 39 | } 40 | } 41 | } 42 | 43 | struct STButton_Previews: PreviewProvider { 44 | static var previews: some View { 45 | Group { 46 | VStack { 47 | STButton(text: "Hello", icon: "gear") {} 48 | STButton(text: "Centered=false", icon: "gear", centered: false) {} 49 | } 50 | .preferredColorScheme(.dark) 51 | .previewInterfaceOrientation(.portrait) 52 | VStack { 53 | STButton(text: "Hello", icon: "gear"){} 54 | STButton(text: "Centered=false", icon: "gear", centered: false) {} 55 | } 56 | .previewInterfaceOrientation(.portrait) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/STFilePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddKeyFromFile.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 5/4/20. 6 | // Copyright © 2020 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import UniformTypeIdentifiers 11 | 12 | final class FilePicker: NSObject, UIViewControllerRepresentable, UIDocumentPickerDelegate { 13 | typealias UIViewControllerType = UIDocumentPickerViewController 14 | var callback: ([URL]) -> () 15 | 16 | public init (callback: @escaping ([URL]) -> ()) 17 | { 18 | self.callback = callback 19 | } 20 | 21 | lazy var viewController : UIDocumentPickerViewController = { 22 | let vc = UIDocumentPickerViewController (forOpeningContentTypes: [UTType.item, UTType.data, UTType.content], asCopy: true) 23 | vc.allowsMultipleSelection = false 24 | vc.shouldShowFileExtensions = true 25 | return vc 26 | }() 27 | 28 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIDocumentPickerViewController { 29 | viewController.delegate = self 30 | return viewController 31 | } 32 | 33 | func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext) { 34 | } 35 | 36 | func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { 37 | controller.dismiss(animated: true) {} 38 | } 39 | 40 | func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { 41 | callback (urls) 42 | } 43 | } 44 | 45 | //struct STFilePicker: View { 46 | // func saveKey (urls: [URL]) 47 | // { 48 | // guard let url = urls.first else { 49 | // return 50 | // } 51 | // if let contents = try? String (contentsOf: url) { 52 | // if contents.contains("PRIVATE KEY") { 53 | //// let k = Key(id: UUID(), type: , name: url.lastPathComponent, privateKey: contents, publicKey: "", passphrase: "") 54 | //// DataStore.shared.save(key: k) 55 | // } else { 56 | // print ("That was not a key we know much about") 57 | // } 58 | // } 59 | // } 60 | // 61 | // var body: some View { 62 | // FilePicker (callback: saveKey) 63 | // } 64 | //} 65 | 66 | /// 67 | /// A button that shows a file icon, and when selected, inserts the contents 68 | /// of the file into the target field 69 | /// 70 | struct ContentsFromFile: View { 71 | @Binding var target: String 72 | @State var pickerShown = false 73 | 74 | func setTarget (urls: [URL]) 75 | { 76 | guard let url = urls.first else { 77 | return 78 | } 79 | if let contents = try? String (contentsOf: url) { 80 | target = contents 81 | } 82 | pickerShown = false 83 | } 84 | 85 | var body: some View { 86 | Image (systemName: "folder") 87 | .foregroundColor(ButtonColors.highColor) 88 | .font(Font.headline.weight(.light)) 89 | .onTapGesture { self.pickerShown = true } 90 | .sheet(isPresented: self.$pickerShown) { 91 | FilePicker (callback: self.setTarget) 92 | } 93 | .help ("Pick a file") 94 | } 95 | } 96 | 97 | struct STFilePicker_Previews: PreviewProvider { 98 | static var previews: some View { 99 | ContentsFromFile (target: .constant ("")) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/SideHostStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SideHostStatus.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 3/12/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SideHostStatus: View { 12 | var body: some View { 13 | Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) 14 | } 15 | } 16 | 17 | struct SideHostStatus_Previews: PreviewProvider { 18 | static var previews: some View { 19 | SideHostStatus() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/UIKitRoot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIKitRoot.swift: UIKit APIs to deal with root windows, and root view controllers 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 5/9/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SwiftUI 12 | 13 | /// getCurrentKeyWindow: returns the current key window from the application 14 | @MainActor 15 | func getCurrentKeyWindow () -> UIWindow? { 16 | func tryGetWindow (desiredState: UIScene.ActivationState) -> UIWindow? { 17 | return UIApplication.shared.connectedScenes 18 | .filter { $0.activationState == desiredState } 19 | .compactMap { $0 as? UIWindowScene } 20 | .first?.windows 21 | .filter { $0.isKeyWindow } 22 | .first 23 | } 24 | let states: [UIScene.ActivationState] = [.foregroundActive, .foregroundInactive, .background, .unattached] 25 | for x in states { 26 | if let window = tryGetWindow(desiredState: x) { 27 | return window 28 | } 29 | } 30 | return nil 31 | } 32 | 33 | @MainActor 34 | func getParentViewController (hint: UIResponder? = nil) -> UIViewController? { 35 | var parentResponder = hint 36 | while parentResponder != nil { 37 | parentResponder = parentResponder?.next 38 | if let viewController = parentResponder as? UIViewController { 39 | return viewController 40 | } 41 | } 42 | 43 | // playing with fire here 44 | return getCurrentKeyWindow()?.rootViewController 45 | } 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /SwiftTermApp/UIs/UIViewController+SwiftUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+SwiftUI.swift 3 | // SwiftTermApp 4 | // 5 | // From Timothy Costa 6 | // 7 | // https://gist.github.com/timothycosta/a43dfe25f1d8a37c71341a1ebaf82213 8 | // https://stackoverflow.com/questions/56756318/swiftui-presentationbutton-with-modal-that-is-full-screen 9 | // 10 | 11 | import Foundation 12 | import UIKit 13 | import SwiftUI 14 | 15 | //struct ViewControllerHolder { 16 | // weak var value: UIViewController? 17 | // init(_ value: UIViewController?) { 18 | // self.value = value 19 | // } 20 | //} 21 | // 22 | //struct ViewControllerKey: EnvironmentKey { 23 | // static var defaultValue: ViewControllerHolder { return ViewControllerHolder(UIApplication.shared.windows.first?.rootViewController ) } 24 | //} 25 | // 26 | //extension EnvironmentValues { 27 | // var viewController: ViewControllerHolder { 28 | // get { return self[ViewControllerKey.self] } 29 | // set { self[ViewControllerKey.self] = newValue } 30 | // } 31 | //} 32 | // 33 | //extension UIViewController { 34 | // func present(presentationStyle: UIModalPresentationStyle = .automatic, transitionStyle: UIModalTransitionStyle = .coverVertical, animated: Bool = true, completion: @escaping () -> Void = {}, @ViewBuilder builder: () -> Content) { 35 | // let toPresent = UIHostingController(rootView: AnyView(EmptyView())) 36 | // toPresent.modalPresentationStyle = presentationStyle 37 | // toPresent.rootView = AnyView( 38 | // builder() 39 | // .environment(\.viewController, ViewControllerHolder(toPresent)) 40 | // ) 41 | // if presentationStyle == .overCurrentContext { 42 | // toPresent.view.backgroundColor = .clear 43 | // } 44 | // self.present(toPresent, animated: animated, completion: completion) 45 | // } 46 | //} 47 | -------------------------------------------------------------------------------- /SwiftTermApp/Utilities/DateFormatting.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateFormatting.swift 3 | // SwiftTermApp 4 | // 5 | // Created by Miguel de Icaza on 3/31/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let dateTimeFormatter: DateFormatter = { 12 | let dateFormatter = DateFormatter() 13 | dateFormatter.dateStyle = .short 14 | dateFormatter.timeStyle = .medium 15 | return dateFormatter 16 | }() 17 | 18 | let timeStampFormatter: DateFormatter = { 19 | let dateFormatter = DateFormatter() 20 | dateFormatter.dateStyle = .short 21 | dateFormatter.timeStyle = .long 22 | return dateFormatter 23 | }() 24 | 25 | let timeFormatter: DateFormatter = { 26 | let dateFormatter = DateFormatter() 27 | dateFormatter.dateStyle = .none 28 | dateFormatter.timeStyle = .medium 29 | return dateFormatter 30 | }() 31 | 32 | let dateMediumFormatter: DateFormatter = { 33 | let dateFormatter = DateFormatter() 34 | dateFormatter.dateStyle = .medium 35 | dateFormatter.timeStyle = .none 36 | return dateFormatter 37 | }() 38 | -------------------------------------------------------------------------------- /SwiftTermApp/es-419.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Privacy - Local Network Usage Description */ 2 | "NSLocalNetworkUsageDescription" = "Necesario para conectarse a computadoras con SSH en su red local"; 3 | 4 | /* Privacy - Location Always and When In Use Usage Description */ 5 | "NSLocationAlwaysAndWhenInUseUsageDescription" = "Al usar tu posición geográfica, iOS mantendrá a la terminal ejecutandose sin interrumpir la conección cuando la aplicación se pasa al fondo."; 6 | 7 | /* Privacy - Location When In Use Usage Description */ 8 | "NSLocationWhenInUseUsageDescription" = "Petición inicial: Al usar tu posición geográfica, iOS mantendrá la terminal activa y conecciones a sus computadoras y servidores activos."; 9 | 10 | -------------------------------------------------------------------------------- /SwiftTermApp/es-419.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /THIRD_PARTY_NOTICES.md: -------------------------------------------------------------------------------- 1 | # ShaderTweak 2 | 3 | The MetalHost class contains bits of code from Shadertweak by Warren Moore, 4 | which is licensed under the MIT License: 5 | 6 | https://github.com/warrenm/Shadertweak 7 | 8 | MIT License 9 | 10 | Copyright (c) 2017 Warren Moore 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in all 20 | copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. 29 | 30 | # Shaders 31 | 32 | The Shaders included in this distribution come from: 33 | 34 | https://github.com/warrenm/Shadertweak 35 | 36 | Also under the MIT license, but the original authors as far as I can tell 37 | are: 38 | 39 | Plasma Globe by nimitz (twitter: @stormoid) 40 | 41 | Starcraddle by Pablo Roman Andrioli 42 | 43 | # Color Themes 44 | 45 | The Color themes come from iTerm2 Color Schemes Project: 46 | 47 | https://github.com/mbadolato/iTerm2-Color-Schemes/ 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /UITests/UITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITests.swift 3 | // UITests 4 | // 5 | // Created by Miguel de Icaza on 3/10/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class UITests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDownWithError() throws { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testAddHost() throws { 27 | 28 | } 29 | 30 | //let password = try String (contentsOf: URL (fileURLWithPath: "/Users/miguel/password")) 31 | 32 | func testAddHostLoginPassword() throws { 33 | // UI tests must launch the application that they test. 34 | let app = XCUIApplication() 35 | app.launch() 36 | 37 | // Use recording to get started writing UI tests. 38 | // Use XCTAssert and related functions to verify your tests produce the correct results. 39 | 40 | let tablesQuery = app.tables 41 | tablesQuery/*@START_MENU_TOKEN@*/.buttons["Hosts"]/*[[".cells[\"Hosts\"].buttons[\"Hosts\"]",".buttons[\"Hosts\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 42 | tablesQuery.cells["Add, Add Host"].children(matching: .other).element(boundBy: 0).children(matching: .other).element.tap() 43 | let name = tablesQuery/*@START_MENU_TOKEN@*/.textFields["name"]/*[[".cells[\"Alias, name\"].textFields[\"name\"]",".textFields[\"name\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/ 44 | name.tap() 45 | name.typeText("dbserver") 46 | 47 | tablesQuery/*@START_MENU_TOKEN@*/.textFields["192.168.1.100"]/*[[".cells[\"Host, 192.168.1.100\"].textFields[\"192.168.1.100\"]",".textFields[\"192.168.1.100\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 48 | tablesQuery.textFields["192.168.1.100"].typeText("172.25.2.1") 49 | tablesQuery/*@START_MENU_TOKEN@*/.textFields["user"]/*[[".cells[\"Username, user\"].textFields[\"user\"]",".textFields[\"user\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 50 | tablesQuery.textFields["user"].typeText("sa") 51 | tablesQuery/*@START_MENU_TOKEN@*/.secureTextFields["•••••••"]/*[[".cells[\"Password, •••••••, Show\"].secureTextFields[\"•••••••\"]",".secureTextFields[\"•••••••\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 52 | tablesQuery/*@START_MENU_TOKEN@*/.secureTextFields["•••••••"]/*[[".cells[\"Password, •••••••, Show\"].secureTextFields[\"•••••••\"]",".secureTextFields[\"•••••••\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.typeText("pass") 53 | let port: XCUIElement = tablesQuery.textFields ["22"] 54 | 55 | port.doubleTap() 56 | port.typeText ("2201") 57 | 58 | app.navigationBars["_TtGC7SwiftUI19UIHosting"].buttons["Save"].tap() 59 | } 60 | 61 | func testLogin () { 62 | // UI tests must launch the application that they test. 63 | let app = XCUIApplication() 64 | app.launch() 65 | 66 | app.tables.buttons["dbserver, 172.25.2.1"].tap() 67 | 68 | // Needed when we do not trust yet 69 | //app.buttons["Yes"].tap() 70 | 71 | app.typeText("mc\ncd /usr\n\tcd /usr/bin\n") 72 | print ("Here") 73 | } 74 | func testLaunchPerformance() throws { 75 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 76 | // This measures how long it takes to launch your application. 77 | measure(metrics: [XCTApplicationLaunchMetric()]) { 78 | XCUIApplication().launch() 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /UITests/UITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITestsLaunchTests.swift 3 | // UITests 4 | // 5 | // Created by Miguel de Icaza on 3/10/22. 6 | // Copyright © 2022 Miguel de Icaza. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class UITestsLaunchTests: XCTestCase { 12 | 13 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 14 | true 15 | } 16 | 17 | override func setUpWithError() throws { 18 | continueAfterFailure = false 19 | } 20 | 21 | func testLaunch() throws { 22 | let app = XCUIApplication() 23 | app.launch() 24 | 25 | // Insert steps here to perform after app launch but before taking a screenshot, 26 | // such as logging into a test account or navigating somewhere in the app 27 | 28 | let attachment = XCTAttachment(screenshot: app.screenshot()) 29 | attachment.name = "Launch Screen" 30 | attachment.lifetime = .keepAlways 31 | add(attachment) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ci_scripts/ci_pre_xcodebuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ci_pre_xcodebuild.sh 4 | # SwiftTermApp 5 | # 6 | # Created by Miguel de Icaza on 3/29/22. 7 | # Copyright © 2022 Miguel de Icaza. All rights reserved. 8 | echo running at 9 | pwd 10 | 11 | echo "let shakeId = \"${SHAKEID_SECRET}\"" > ${CI_WORKSPACE}/SwiftTermApp/Secrets.swift 12 | echo "let shakeKey = \"${SHAKEKEY_SECRET}\"" >> ${CI_WORKSPACE}/SwiftTermApp/Secrets.swift 13 | --------------------------------------------------------------------------------