├── .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 | 
23 |
24 | 
25 |
26 | Animated Metal Backgrounds:
27 |
28 |
29 |
30 | Configure your settings:
31 |
32 |
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 |
--------------------------------------------------------------------------------