added a snippet picker and it executes a callback when one is selected

added a snippet button to shelltabview
fix crash if hostkey changed message shows up and not connected anymore (such as failed auth)
cleaned up about view and added a libssh link
update bundle.swift to add version and build number getting
This commit is contained in:
neon443
2025-08-03 17:52:20 +01:00
parent 18d708cf0c
commit ee6fc96f01
6 changed files with 104 additions and 11 deletions

View File

@@ -79,6 +79,7 @@
A9BA1D1E2E1EAD51005BDCEF /* SF-Mono-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */; }; A9BA1D1E2E1EAD51005BDCEF /* SF-Mono-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */; };
A9BA1D1F2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */; }; A9BA1D1F2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */; };
A9C060EB2E357FD300CA9374 /* Haptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C060EA2E357FD300CA9374 /* Haptics.swift */; }; A9C060EB2E357FD300CA9374 /* Haptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C060EA2E357FD300CA9374 /* Haptics.swift */; };
A9C060ED2E3FBCD000CA9374 /* SnippetPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C060EC2E3FBCD000CA9374 /* SnippetPicker.swift */; };
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; }; A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; };
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; }; A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; }; A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; };
@@ -208,6 +209,7 @@
A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-Bold.otf"; sourceTree = "<group>"; }; A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-Bold.otf"; sourceTree = "<group>"; };
A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-BoldItalic.otf"; sourceTree = "<group>"; }; A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-BoldItalic.otf"; sourceTree = "<group>"; };
A9C060EA2E357FD300CA9374 /* Haptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Haptics.swift; sourceTree = "<group>"; }; A9C060EA2E357FD300CA9374 /* Haptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Haptics.swift; sourceTree = "<group>"; };
A9C060EC2E3FBCD000CA9374 /* SnippetPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnippetPicker.swift; sourceTree = "<group>"; };
A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.swift; sourceTree = "<group>"; }; A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.swift; sourceTree = "<group>"; };
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = "<group>"; }; A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = "<group>"; };
A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; }; A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
@@ -458,6 +460,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A93F283C2E2A5DCB0092B8D5 /* SnippetManagerView.swift */, A93F283C2E2A5DCB0092B8D5 /* SnippetManagerView.swift */,
A9C060EC2E3FBCD000CA9374 /* SnippetPicker.swift */,
A9A2F4F52E3001D300D0AE9B /* AddSnippetView.swift */, A9A2F4F52E3001D300D0AE9B /* AddSnippetView.swift */,
); );
path = Snippets; path = Snippets;
@@ -788,6 +791,7 @@
A9DA97732E0D40C100142DDC /* HostSymbolPreview.swift in Sources */, A9DA97732E0D40C100142DDC /* HostSymbolPreview.swift in Sources */,
A90B38342E3EA046002B56FC /* Bundle.swift in Sources */, A90B38342E3EA046002B56FC /* Bundle.swift in Sources */,
A9FD376B2E16DABF005319A8 /* AnsiPickerView.swift in Sources */, A9FD376B2E16DABF005319A8 /* AnsiPickerView.swift in Sources */,
A9C060ED2E3FBCD000CA9374 /* SnippetPicker.swift in Sources */,
A96BE6A62E113DB000C0FEE9 /* ColorCodable.swift in Sources */, A96BE6A62E113DB000C0FEE9 /* ColorCodable.swift in Sources */,
A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */, A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */,
A96BE6A42E113D9400C0FEE9 /* ThemeCodable.swift in Sources */, A96BE6A42E113D9400C0FEE9 /* ThemeCodable.swift in Sources */,

View File

@@ -10,7 +10,7 @@ import SwiftUI
//app icon //app icon
extension Bundle { extension Bundle {
var iconFilename: String? { var appIconFilename: String? {
guard let icons = infoDictionary?["CFBundleIcons"] as? [String: Any], guard let icons = infoDictionary?["CFBundleIcons"] as? [String: Any],
let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any], let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any],
let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String], let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String],
@@ -23,9 +23,23 @@ extension Bundle {
extension UIImage { extension UIImage {
var appIcon: Image { var appIcon: Image {
// let fallback = Image(uiImage: UIImage()) // let fallback = Image(uiImage: UIImage())
// guard let filename = Bundle.main.iconFilename else { return fallback } // guard let filename = Bundle.main.appIconFilename else { return fallback }
// guard let uiImage = UIImage(named: filename) else { return fallback } // guard let uiImage = UIImage(named: filename) else { return fallback }
// return uiImage // return uiImage
return Image("Icon") return Image("Icon")
} }
} }
extension Bundle {
var appVersion: String {
let version = infoDictionary?["CFBundleShortVersionString"] as? String
return version ?? "0.0"
}
}
extension Bundle {
var appBuild: String {
let build = infoDictionary?["CFBundleVersion"] as? String
return "(" + (build ?? "0") + ")"
}
}

View File

@@ -47,6 +47,8 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
} }
func getHostkey() -> String? { func getHostkey() -> String? {
guard connected else { return nil }
var hostkey: ssh_key? var hostkey: ssh_key?
ssh_get_server_publickey(session, &hostkey) ssh_get_server_publickey(session, &hostkey)

View File

@@ -14,16 +14,35 @@ struct AboutView: View {
ZStack { ZStack {
hostsManager.selectedTheme.background.suiColor.opacity(0.7) hostsManager.selectedTheme.background.suiColor.opacity(0.7)
.ignoresSafeArea(.all) .ignoresSafeArea(.all)
List { // List {
VStack(alignment: .leading) {
UIImage().appIcon
.resizable().scaledToFit()
.frame(width: 100)
.clipShape(RoundedRectangle(cornerRadius: 26))
Text("ShhShell")
.font(.largeTitle.monospaced())
HStack { HStack {
UIImage().appIcon Text(Bundle.main.appVersion)
.resizable().scaledToFit() .monospaced()
.frame(width: 100) .font(.subheadline)
.clipShape(RoundedRectangle(cornerRadius: 26)) Text(Bundle.main.appBuild)
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) .monospaced()
.font(.callout)
}
.padding(.bottom)
Section("Thanks to") {
Link(destination: URL(string: "https://libssh.org")!) {
Text("LibSSH")
.background(.red)
}
} }
} }
.scrollContentBackground(.hidden) .frame(maxWidth: .infinity)
.padding()
// }
// .scrollContentBackground(.hidden)
} }
} }
} }

View File

@@ -0,0 +1,41 @@
//
// SnippetPicker.swift
// ShhShell
//
// Created by neon443 on 03/08/2025.
//
import SwiftUI
struct SnippetPicker: View {
@ObservedObject var hostsManager: HostsManager
var callback: ((Snippet) -> Void)?
@Environment(\.dismiss) var dismiss
var body: some View {
NavigationStack {
List {
ForEach(hostsManager.snippets) { snip in
Text(snip.name)
.onTapGesture {
dismiss()
callback?(snip)
}
}
.toolbar {
Button() {
dismiss()
} label: {
Image(systemName: "xmark")
}
}
}
.listStyle(.grouped)
}
}
}
#Preview {
SnippetPicker(hostsManager: HostsManager(), callback: nil)
}

View File

@@ -14,6 +14,8 @@ struct ShellTabView: View {
@ObservedObject var container = TerminalViewContainer.shared @ObservedObject var container = TerminalViewContainer.shared
@State var selectedID: UUID? @State var selectedID: UUID?
@State var showSnippetPicker: Bool = false
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
var foreground: Color { var foreground: Color {
@@ -75,11 +77,22 @@ struct ShellTabView: View {
} }
} }
Spacer() Spacer()
Button() {
showSnippetPicker.toggle()
} label: {
Image(systemName: "paperclip")
}
.foregroundStyle(foreground)
.sheet(isPresented: $showSnippetPicker) {
SnippetPicker(hostsManager: hostsManager) {
container.sessions[selectedID ?? UUID()]?.handler.writeToChannel($0.content)
}
}
} }
.padding(.horizontal, 10) .padding(.horizontal, 10)
.padding(.vertical, 5) .padding(.vertical, 10)
.background(hostsManager.tint, ignoresSafeAreaEdges: .all) .background(hostsManager.tint, ignoresSafeAreaEdges: .all)
.frame(height: 30) .frame(height: 40)
if container.sessionIDs.count > 1 { if container.sessionIDs.count > 1 {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {