diff --git a/Resources/Assets.xcassets/custom.pc.symbolset/Contents.json b/Resources/Assets.xcassets/custom.pc.symbolset/Contents.json
new file mode 100644
index 0000000..571dcd7
--- /dev/null
+++ b/Resources/Assets.xcassets/custom.pc.symbolset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "symbols" : [
+ {
+ "filename" : "custom.pc.svg",
+ "idiom" : "universal"
+ }
+ ]
+}
diff --git a/Resources/Assets.xcassets/custom.pc.symbolset/custom.pc.svg b/Resources/Assets.xcassets/custom.pc.symbolset/custom.pc.svg
new file mode 100644
index 0000000..b112618
--- /dev/null
+++ b/Resources/Assets.xcassets/custom.pc.symbolset/custom.pc.svg
@@ -0,0 +1,117 @@
+
+
+
+
diff --git a/ShhShell.xcodeproj/project.pbxproj b/ShhShell.xcodeproj/project.pbxproj
index 92951b6..1357dd5 100644
--- a/ShhShell.xcodeproj/project.pbxproj
+++ b/ShhShell.xcodeproj/project.pbxproj
@@ -39,6 +39,8 @@
A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B15A992E0ABA0400F66E02 /* DialogView.swift */; };
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; };
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
+ A9DA97712E0D30ED00142DDC /* Symbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97702E0D30ED00142DDC /* Symbol.swift */; };
+ A9DA97732E0D40C100142DDC /* SymbolPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97722E0D40C100142DDC /* SymbolPreview.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -110,6 +112,8 @@
A9B15A992E0ABA0400F66E02 /* DialogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogView.swift; sourceTree = ""; };
A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.swift; sourceTree = ""; };
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = ""; };
+ A9DA97702E0D30ED00142DDC /* Symbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Symbol.swift; sourceTree = ""; };
+ A9DA97722E0D40C100142DDC /* SymbolPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolPreview.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -279,6 +283,8 @@
children = (
A93143BF2DF61B3200FCD5DB /* Host.swift */,
A98554602E058433009051BD /* HostsManager.swift */,
+ A9DA97702E0D30ED00142DDC /* Symbol.swift */,
+ A9DA97722E0D40C100142DDC /* SymbolPreview.swift */,
);
path = Host;
sourceTree = "";
@@ -452,11 +458,13 @@
A93143C62DF61FE300FCD5DB /* ViewModifiers.swift in Sources */,
A98554632E0587DF009051BD /* HostsView.swift in Sources */,
A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */,
+ A9DA97732E0D40C100142DDC /* SymbolPreview.swift in Sources */,
A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */,
A93143C02DF61B3200FCD5DB /* Host.swift in Sources */,
A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */,
A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */,
A96C6B002E0C45FE00F377FE /* KeyDetailView.swift in Sources */,
+ A9DA97712E0D30ED00142DDC /* Symbol.swift in Sources */,
A98554612E058433009051BD /* HostsManager.swift in Sources */,
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */,
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
diff --git a/ShhShell/Host/Host.swift b/ShhShell/Host/Host.swift
index 66381fc..84f3392 100644
--- a/ShhShell/Host/Host.swift
+++ b/ShhShell/Host/Host.swift
@@ -6,10 +6,13 @@
//
import Foundation
+import SwiftUI
protocol HostPr: Codable, Identifiable, Equatable {
var id: UUID { get set }
var name: String { get set }
+ var symbol: Symbol { get set }
+ var label: String { get set }
var address: String { get set }
var port: Int { get set }
var username: String { get set }
@@ -22,18 +25,22 @@ protocol HostPr: Codable, Identifiable, Equatable {
struct Host: HostPr {
var id = UUID()
- var name: String = ""
- var address: String = ""
+ var name: String
+ var symbol: Symbol
+ var label: String
+ var address: String
var port: Int
var username: String
var password: String
var publicKey: Data?
var privateKey: Data?
- var passphrase: String = ""
+ var passphrase: String
var key: String?
init(
name: String = "",
+ symbol: Symbol = .genericServer,
+ label: String = "",
address: String,
port: Int = 22,
username: String = "",
@@ -44,6 +51,8 @@ struct Host: HostPr {
hostkey: String? = nil
) {
self.name = name
+ self.symbol = symbol
+ self.label = label
self.address = address
self.port = port
self.username = username
@@ -62,6 +71,7 @@ extension Host {
static var debug: Host {
Host(
name: "name for localhost",
+ label: "lo0",
address: "localhost",
port: 22,
username: "neon443",
diff --git a/ShhShell/Host/HostsManager.swift b/ShhShell/Host/HostsManager.swift
index 74d1e50..ecb4fe4 100644
--- a/ShhShell/Host/HostsManager.swift
+++ b/ShhShell/Host/HostsManager.swift
@@ -50,6 +50,23 @@ class HostsManager: ObservableObject, @unchecked Sendable {
}
}
+ func makeLabel(forHost: Host) -> String {
+ if forHost.name.isEmpty && forHost.address.isEmpty {
+ return forHost.id.uuidString
+ } else if forHost.name.isEmpty {
+ return forHost.address
+ } else if forHost.address.isEmpty {
+ return forHost.name
+ } else {
+ return forHost.name
+ }
+ }
+
+ func moveHost(from: IndexSet, to: Int) {
+ savedHosts.move(fromOffsets: from, toOffset: to)
+ saveSavedHosts()
+ }
+
func loadSavedHosts() {
userDefaults.synchronize()
let decoder = JSONDecoder()
diff --git a/ShhShell/Host/Symbol.swift b/ShhShell/Host/Symbol.swift
new file mode 100644
index 0000000..fd12a5c
--- /dev/null
+++ b/ShhShell/Host/Symbol.swift
@@ -0,0 +1,64 @@
+//
+// Symbol.swift
+// ShhShell
+//
+// Created by neon443 on 26/06/2025.
+//
+
+import Foundation
+import SwiftUI
+
+enum Symbol: Codable, Hashable, Equatable, CaseIterable {
+ case desktopcomputer
+ case laptopcomputer
+
+ case trashcan
+
+ case genericPC
+ case genericServer
+ case genericServerVertical
+
+ var sf: String {
+ switch self {
+ case .desktopcomputer:
+ return "desktopcomputer"
+ case .laptopcomputer:
+ return "laptopcomputer"
+
+ case .trashcan:
+ return "macpro.gen2"
+
+ case .genericPC:
+ return "custom.pc"
+ case .genericServer:
+ return "rectangle"
+ case .genericServerVertical:
+ return "rectangle.portrait"
+ }
+
+ }
+
+ var isCustom: Bool {
+ switch self {
+ case .genericPC:
+ return true
+ default:
+ return false
+ }
+ }
+
+ var offset: CGSize {
+ var deltaHeight: Double
+ switch self {
+ case .desktopcomputer:
+ deltaHeight = -6
+ case .laptopcomputer:
+ deltaHeight = -2
+ case .genericPC:
+ deltaHeight = -6
+ default:
+ deltaHeight = 0
+ }
+ return CGSize(width: 0, height: deltaHeight)
+ }
+}
diff --git a/ShhShell/Host/SymbolPreview.swift b/ShhShell/Host/SymbolPreview.swift
new file mode 100644
index 0000000..f545a21
--- /dev/null
+++ b/ShhShell/Host/SymbolPreview.swift
@@ -0,0 +1,36 @@
+//
+// SymbolPreview.swift
+// ShhShell
+//
+// Created by neon443 on 26/06/2025.
+//
+
+import SwiftUI
+
+struct SymbolPreview: View {
+ @State var symbol: Symbol
+ @State var label: String
+
+ var body: some View {
+ ZStack(alignment: .center) {
+ if symbol.isCustom {
+ Image(symbol.sf)
+ .resizable().scaledToFit()
+ .symbolRenderingMode(.monochrome)
+ .padding(5)
+ } else {
+ Image(systemName: symbol.sf)
+ .resizable().scaledToFit()
+ .symbolRenderingMode(.monochrome)
+ .padding(5)
+ }
+ Text(label)
+ .font(.headline)
+ .offset(symbol.offset)
+ }
+ }
+}
+
+#Preview {
+ SymbolPreview(symbol: Symbol.desktopcomputer, label: "lo0")
+}
diff --git a/ShhShell/Views/Hosts/ConnectionView.swift b/ShhShell/Views/Hosts/ConnectionView.swift
index 9b16c19..705925b 100644
--- a/ShhShell/Views/Hosts/ConnectionView.swift
+++ b/ShhShell/Views/Hosts/ConnectionView.swift
@@ -24,6 +24,21 @@ struct ConnectionView: View {
var body: some View {
NavigationStack {
List {
+ Section {
+ HStack {
+ Picker("", selection: $handler.host.symbol) {
+ ForEach(Symbol.allCases, id: \.self) { symbol in
+ SymbolPreview(symbol: symbol, label: handler.host.label)
+ .tag(symbol)
+ .frame(width: 60, height: 60)
+ }
+ }
+ .pickerStyle(SegmentedPickerStyle())
+ }
+ .scrollIndicators(.hidden)
+ TextField("label", text: $handler.host.label)
+ .textFieldStyle(.roundedBorder)
+ }
Section {
HStack {
Text(handler.connected ? "connected" : "not connected")
@@ -33,6 +48,7 @@ struct ConnectionView: View {
.modifier(foregroundColorStyle(checkAuth(handler.state) ? .green : .red))
Text("\(handler.state)")
}
+
TextField("name", text: $handler.host.name)
.textFieldStyle(.roundedBorder)
diff --git a/ShhShell/Views/Hosts/HostsView.swift b/ShhShell/Views/Hosts/HostsView.swift
index a15e556..b49719d 100644
--- a/ShhShell/Views/Hosts/HostsView.swift
+++ b/ShhShell/Views/Hosts/HostsView.swift
@@ -44,13 +44,8 @@ struct HostsView: View {
keyManager: keyManager
)
} label: {
- if host.name.isEmpty {
- Text(host.address)
- } else if host.address.isEmpty {
- Text(host.name)
- } else {
- Text(host.id.uuidString)
- }
+ SymbolPreview(symbol: host.symbol, label: host.label)
+ .frame(width: 30, height: 30)
}
.animation(.default, value: host)
.swipeActions(edge: .trailing) {
@@ -66,6 +61,9 @@ struct HostsView: View {
}
}
}
+ .onMove(perform: {
+ hostsManager.moveHost(from: $0, to: $1)
+ })
}
.transition(.opacity)
.toolbar {