diff --git a/FrameworkTests/HostIconTests.swift b/FrameworkTests/HostIconTests.swift
index 0f3a84be..87dabe40 100644
--- a/FrameworkTests/HostIconTests.swift
+++ b/FrameworkTests/HostIconTests.swift
@@ -62,7 +62,7 @@ struct HostIconTests {
#expect(
catalog.entry(matching: try #require(DetectedDistribution(id: "custom", likeIDs: ["debian"])))?.id
== "debian")
- #expect(catalog.selectableEntries.prefix(2).map(\.id) == ["macos", "windows"])
+ #expect(catalog.selectableEntries.map(\.id) == ["bsd", "debian", "linux", "macos", "ubuntu", "windows"])
#expect(Set(catalog.entries.map(\.id)).count == catalog.entries.count)
}
@@ -75,11 +75,45 @@ struct HostIconTests {
{"id":"ubuntu","title":"Ubuntu","asset":"LinuxDistro-ubuntu","ids":["ubuntu"],"hex":"E95420"},
{"id":"debian","title":"Debian","asset":"LinuxDistro-debian","ids":["debian"],"hex":"A81D33"},
{"id":"macos","title":"macOS","asset":"HostOS-macos","ids":["macos"],"hex":"000000"},
- {"id":"windows","title":"Windows","asset":"HostOS-windows","ids":["windows"],"hex":"0078D4"}
+ {"id":"windows","title":"Windows","asset":"HostOS-windows","ids":["windows"],"hex":"0078D4"},
+ {"id":"linux","title":"Linux","asset":"HostOS-linux","ids":["linux"],"hex":"FCC624"},
+ {"id":"bsd","title":"BSD","asset":"HostOS-bsd","ids":["bsd","dragonfly"],"hex":"AB2B28"}
]}
""".utf8))
}
+ @Test func genericArtworkDoesNotReplaceExactMatchesOrManualChoices() throws {
+ let catalog = try fixtureCatalog()
+ let unknown = try #require(DetectedDistribution(id: "new-linux"))
+ #expect(catalog.entry(matching: unknown)?.id == "linux")
+ #expect(catalog.entry(matching: try #require(DetectedDistribution(id: "dragonfly")))?.id == "bsd")
+ #expect(
+ catalog.entry(
+ matching: try #require(DetectedDistribution(id: "custom", likeIDs: ["missing", "debian", "ubuntu"])))?
+ .id
+ == "debian")
+ #expect(
+ catalog.entry(matching: try #require(DetectedDistribution(id: "ubuntu", likeIDs: ["debian"])))?.id
+ == "ubuntu")
+ #expect(catalog.resolvedEntry(overrideID: "server", distribution: unknown) == nil)
+ #expect(catalog.resolvedEntry(overrideID: "future-icon", distribution: unknown) == nil)
+ #expect(catalog.resolvedEntry(overrideID: nil, distribution: nil) == nil)
+ }
+
+ @Test func alphabeticalOrderIgnoresCaseAndUsesStableIDsForTies() throws {
+ var catalog = try fixtureCatalog()
+ let template = try #require(catalog.entries.first)
+ catalog.entries = [("z", "alpha"), ("b", "Beta"), ("a", "ALPHA"), ("w", "Windows")].map { id, title in
+ var entry = template
+ entry.id = id
+ entry.title = title
+ return entry
+ }
+ #expect(catalog.selectableEntries.map(\.id) == ["a", "z", "b", "w"])
+ catalog.entries.reverse()
+ #expect(catalog.selectableEntries.map(\.id) == ["a", "z", "b", "w"])
+ }
+
@Test func legacyDomainJSONDefaultsToAutomaticAndUnknownIDsSurvive() throws {
let host = Host(name: "Host", host: "example.invalid")
let data = try JSONEncoder().encode(host)
@@ -98,17 +132,19 @@ struct HostIconTests {
let store = HostPersistence(context: source.mainContext)
let remote = HostPersistence(context: destination.mainContext)
let library = HostLibraryModel(persistence: store)
- var host = Host(name: "Host", host: "example.invalid")
+ var host = Host(
+ name: "Host", host: "example.invalid", detectedDistribution: DetectedDistribution(id: "dragonfly"))
#expect(library.saveHost(host))
host = try #require(library.host(host.id))
let journal = VaultSyncJournal(context: source.mainContext)
- for choice: String? in ["windows", "macos", "future-icon", nil] {
+ for choice: String? in ["windows", "macos", "freebsd", "bsd", "linux", "future-icon", nil] {
host.iconOverrideID = choice
#expect(library.saveHost(host))
let envelope = try #require(try journal.envelope(for: .init(kind: .host, id: host.id)))
#expect(envelope.formatVersion == SyncRecordFormats.vault.writingVersion(for: "host"))
_ = try remote.applyRemote([VaultSyncCodec.decode(VaultSyncCodec.encode(envelope))])
#expect(try remote.load().hosts.first?.iconOverrideID == choice)
+ #expect(try remote.load().hosts.first?.detectedDistribution?.id == "dragonfly")
}
}
diff --git a/FrameworkTests/RemoteSystemInfoTests.swift b/FrameworkTests/RemoteSystemInfoTests.swift
index a960a733..1bc88ab9 100644
--- a/FrameworkTests/RemoteSystemInfoTests.swift
+++ b/FrameworkTests/RemoteSystemInfoTests.swift
@@ -5,8 +5,9 @@ import Testing
struct RemoteSystemInfoTests {
@Test func distributionDetectionDistinguishesUnknownLinuxFromANonLinuxHost() throws {
#expect(RemoteSystemInfo().distributionDetection == .unavailable)
- #expect(RemoteSystemInfo(kernelName: "Linux").distributionDetection == .unavailable)
- #expect(RemoteSystemInfo(kernelName: "FreeBSD").distributionDetection == .resolved(nil))
+ #expect(
+ RemoteSystemInfo(kernelName: "Linux").distributionDetection == .resolved(DetectedDistribution(id: "linux")))
+ #expect(RemoteSystemInfo(kernelName: "SunOS").distributionDetection == .resolved(nil))
let detected = try #require(DetectedDistribution(id: "Ubuntu", likeIDs: ["Debian"]))
#expect(
@@ -17,4 +18,15 @@ struct RemoteSystemInfoTests {
).distributionDetection == .resolved(detected)
)
}
+
+ @Test(arguments: ["FreeBSD", "OpenBSD", "NetBSD", "DragonFly"])
+ func bsdDetectionUsesKernelRatherThanLinuxMetadata(kernel: String) {
+ for spelling in [kernel, kernel.lowercased(), kernel.uppercased()] {
+ let info = RemoteSystemInfo(distributionID: "ubuntu", kernelName: spelling)
+ #expect(info.distributionDetection == .resolved(DetectedDistribution(id: kernel.lowercased())))
+ #expect(info.distributionID == "ubuntu")
+ }
+ #expect(
+ RemoteSystemInfo(kernelName: kernel).distributionDetection == .resolved(DetectedDistribution(id: kernel)))
+ }
}
diff --git a/FrameworkTests/TailscaleIntegrationTests.swift b/FrameworkTests/TailscaleIntegrationTests.swift
index 48d2f37b..54891fac 100644
--- a/FrameworkTests/TailscaleIntegrationTests.swift
+++ b/FrameworkTests/TailscaleIntegrationTests.swift
@@ -332,9 +332,13 @@ struct TailscaleIntegrationTests {
#expect(destinationLibrary.group(group.id)?.tailscale?.exitNodeID == "exit-node-stable-id")
#expect(discovered.name == "Renamed")
destinationLibrary.recordRemoteSystemInfo(RemoteSystemInfo(kernelName: "Linux"), for: discovered.id)
- #expect(destinationLibrary.host(discovered.id)?.detectedDistribution?.id == "ubuntu")
+ #expect(destinationLibrary.host(discovered.id)?.detectedDistribution?.id == "linux")
destinationLibrary.recordRemoteSystemInfo(RemoteSystemInfo(kernelName: "FreeBSD"), for: discovered.id)
destinationLibrary.replaceTailscalePeers([peer()], groupID: group.id)
+ #expect(destinationLibrary.host(discovered.id)?.detectedDistribution?.id == "freebsd")
+ destinationLibrary.recordRemoteSystemInfo(RemoteSystemInfo(), for: discovered.id)
+ #expect(destinationLibrary.host(discovered.id)?.detectedDistribution?.id == "freebsd")
+ destinationLibrary.recordRemoteSystemInfo(RemoteSystemInfo(kernelName: "SunOS"), for: discovered.id)
#expect(destinationLibrary.host(discovered.id)?.detectedDistribution == nil)
}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-bsd.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-bsd.imageset/Contents.json
new file mode 100644
index 00000000..30f204f8
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-bsd.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "HostOS-bsd.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-bsd.imageset/HostOS-bsd.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-bsd.imageset/HostOS-bsd.svg
new file mode 100644
index 00000000..bd6fd8eb
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-bsd.imageset/HostOS-bsd.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-freebsd.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-freebsd.imageset/Contents.json
new file mode 100644
index 00000000..f32bf8ff
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-freebsd.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "HostOS-freebsd.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-freebsd.imageset/HostOS-freebsd.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-freebsd.imageset/HostOS-freebsd.svg
new file mode 100644
index 00000000..8febe9d4
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-freebsd.imageset/HostOS-freebsd.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-linux.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-linux.imageset/Contents.json
new file mode 100644
index 00000000..42edeb83
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-linux.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "HostOS-linux.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-linux.imageset/HostOS-linux.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-linux.imageset/HostOS-linux.svg
new file mode 100644
index 00000000..381d3d8a
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-linux.imageset/HostOS-linux.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-netbsd.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-netbsd.imageset/Contents.json
new file mode 100644
index 00000000..317b835a
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-netbsd.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "HostOS-netbsd.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-netbsd.imageset/HostOS-netbsd.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-netbsd.imageset/HostOS-netbsd.svg
new file mode 100644
index 00000000..511f066e
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-netbsd.imageset/HostOS-netbsd.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-openbsd.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-openbsd.imageset/Contents.json
new file mode 100644
index 00000000..722d8635
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-openbsd.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "HostOS-openbsd.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-openbsd.imageset/HostOS-openbsd.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-openbsd.imageset/HostOS-openbsd.svg
new file mode 100644
index 00000000..e78fbc7e
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/HostOS-openbsd.imageset/HostOS-openbsd.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-artixlinux.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-artixlinux.imageset/Contents.json
new file mode 100644
index 00000000..20aab9eb
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-artixlinux.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-artixlinux.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-artixlinux.imageset/LinuxDistro-artixlinux.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-artixlinux.imageset/LinuxDistro-artixlinux.svg
new file mode 100644
index 00000000..4b653ddf
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-artixlinux.imageset/LinuxDistro-artixlinux.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-asahilinux.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-asahilinux.imageset/Contents.json
new file mode 100644
index 00000000..4cbf3a6b
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-asahilinux.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-asahilinux.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-asahilinux.imageset/LinuxDistro-asahilinux.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-asahilinux.imageset/LinuxDistro-asahilinux.svg
new file mode 100644
index 00000000..01e7ceec
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-asahilinux.imageset/LinuxDistro-asahilinux.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-cachyos.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-cachyos.imageset/Contents.json
new file mode 100644
index 00000000..47513f6f
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-cachyos.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-cachyos.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-cachyos.imageset/LinuxDistro-cachyos.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-cachyos.imageset/LinuxDistro-cachyos.svg
new file mode 100644
index 00000000..66e9e6c2
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-cachyos.imageset/LinuxDistro-cachyos.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-devuan.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-devuan.imageset/Contents.json
new file mode 100644
index 00000000..268fe5e0
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-devuan.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-devuan.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-devuan.imageset/LinuxDistro-devuan.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-devuan.imageset/LinuxDistro-devuan.svg
new file mode 100644
index 00000000..001980b6
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-devuan.imageset/LinuxDistro-devuan.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-garudalinux.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-garudalinux.imageset/Contents.json
new file mode 100644
index 00000000..3262e0b4
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-garudalinux.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-garudalinux.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-garudalinux.imageset/LinuxDistro-garudalinux.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-garudalinux.imageset/LinuxDistro-garudalinux.svg
new file mode 100644
index 00000000..d0f7d8ba
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-garudalinux.imageset/LinuxDistro-garudalinux.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-mxlinux.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-mxlinux.imageset/Contents.json
new file mode 100644
index 00000000..3732bd3f
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-mxlinux.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-mxlinux.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-mxlinux.imageset/LinuxDistro-mxlinux.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-mxlinux.imageset/LinuxDistro-mxlinux.svg
new file mode 100644
index 00000000..7805a528
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-mxlinux.imageset/LinuxDistro-mxlinux.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-nobaralinux.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-nobaralinux.imageset/Contents.json
new file mode 100644
index 00000000..2ee7e716
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-nobaralinux.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-nobaralinux.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-nobaralinux.imageset/LinuxDistro-nobaralinux.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-nobaralinux.imageset/LinuxDistro-nobaralinux.svg
new file mode 100644
index 00000000..6ce9e46e
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-nobaralinux.imageset/LinuxDistro-nobaralinux.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-openwrt.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-openwrt.imageset/Contents.json
new file mode 100644
index 00000000..976c5d26
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-openwrt.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-openwrt.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-openwrt.imageset/LinuxDistro-openwrt.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-openwrt.imageset/LinuxDistro-openwrt.svg
new file mode 100644
index 00000000..71a93b5c
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-openwrt.imageset/LinuxDistro-openwrt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-parrotsecurity.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-parrotsecurity.imageset/Contents.json
new file mode 100644
index 00000000..bc88008c
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-parrotsecurity.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-parrotsecurity.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-parrotsecurity.imageset/LinuxDistro-parrotsecurity.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-parrotsecurity.imageset/LinuxDistro-parrotsecurity.svg
new file mode 100644
index 00000000..8eefdbd0
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-parrotsecurity.imageset/LinuxDistro-parrotsecurity.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-qubesos.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-qubesos.imageset/Contents.json
new file mode 100644
index 00000000..478bf09c
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-qubesos.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-qubesos.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-qubesos.imageset/LinuxDistro-qubesos.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-qubesos.imageset/LinuxDistro-qubesos.svg
new file mode 100644
index 00000000..d7031d07
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-qubesos.imageset/LinuxDistro-qubesos.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-slackware.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-slackware.imageset/Contents.json
new file mode 100644
index 00000000..0cc60c7b
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-slackware.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-slackware.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-slackware.imageset/LinuxDistro-slackware.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-slackware.imageset/LinuxDistro-slackware.svg
new file mode 100644
index 00000000..611bf809
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-slackware.imageset/LinuxDistro-slackware.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-zorin.imageset/Contents.json b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-zorin.imageset/Contents.json
new file mode 100644
index 00000000..e859203f
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-zorin.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images": [
+ {
+ "filename": "LinuxDistro-zorin.svg",
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "properties": {
+ "preserves-vector-representation": true,
+ "template-rendering-intent": "template"
+ }
+}
diff --git a/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-zorin.imageset/LinuxDistro-zorin.svg b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-zorin.imageset/LinuxDistro-zorin.svg
new file mode 100644
index 00000000..e867f61b
--- /dev/null
+++ b/Frameworks/Resources/Assets.xcassets/LinuxDistributions/LinuxDistro-zorin.imageset/LinuxDistro-zorin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/Resources/HostIcons.json b/Frameworks/Resources/HostIcons.json
index 39d243bb..5f5ce6ac 100644
--- a/Frameworks/Resources/HostIcons.json
+++ b/Frameworks/Resources/HostIcons.json
@@ -40,6 +40,45 @@
"sourceURL": "https://www.archlinux.org/art",
"title": "Arch Linux"
},
+ {
+ "asset": "LinuxDistro-artixlinux",
+ "hex": "10A0CC",
+ "id": "artixlinux",
+ "ids": [
+ "artix"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "artixlinux",
+ "sourceURL": "https://gitea.artixlinux.org/artix/artwork/src/commit/256432e3d06b3e9024bfd6912768e80281ea3746/icons/logo-gray.svg",
+ "title": "Artix Linux"
+ },
+ {
+ "asset": "LinuxDistro-asahilinux",
+ "hex": "A61200",
+ "id": "asahilinux",
+ "ids": [
+ "asahi"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "asahilinux",
+ "sourceURL": "https://github.com/AsahiLinux/artwork/blob/292637c9658c1491ddc1128fb6134aec01d904dd/logos/svg/AsahiLinux_logomark_mono.svg",
+ "title": "Asahi Linux"
+ },
+ {
+ "asset": "LinuxDistro-cachyos",
+ "hex": "00AA88",
+ "id": "cachyos",
+ "ids": [
+ "cachyos"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "cachyos",
+ "sourceURL": "https://commons.wikimedia.org/wiki/File:CachyOS_Logo.svg",
+ "title": "CachyOS"
+ },
{
"asset": "LinuxDistro-centos",
"hex": "262577",
@@ -79,6 +118,19 @@
"sourceURL": "https://commons.wikimedia.org/wiki/File:Deepin_logo.svg",
"title": "deepin"
},
+ {
+ "asset": "LinuxDistro-devuan",
+ "hex": "004489",
+ "id": "devuan",
+ "ids": [
+ "devuan"
+ ],
+ "license": "CC-BY-SA-4.0",
+ "package": "simple-icons",
+ "slug": "devuan",
+ "sourceURL": "https://git.devuan.org/devuan/documentation/src/commit/f4931e70e17f043c2824d591bba7c7f70ed583b6/art/graphics/logo/devuan-emblem.svg",
+ "title": "Devuan"
+ },
{
"asset": "LinuxDistro-elementary",
"hex": "64BAFF",
@@ -118,6 +170,19 @@
"sourceURL": "https://docs.fedoraproject.org/en-US/project/brand/",
"title": "Fedora"
},
+ {
+ "asset": "LinuxDistro-garudalinux",
+ "hex": "8839EF",
+ "id": "garudalinux",
+ "ids": [
+ "garuda"
+ ],
+ "license": "GPL-3.0-only",
+ "package": "simple-icons",
+ "slug": "garudalinux",
+ "sourceURL": "https://gitlab.com/garuda-linux/themes-and-settings/artwork/garuda-icons/-/blob/aab26625fe01479ebc3a252103fca723baca9e88/usr/share/icons/garuda/distributor-logo-garuda-outline.svg",
+ "title": "Garuda Linux"
+ },
{
"asset": "LinuxDistro-gentoo",
"hex": "54487A",
@@ -196,6 +261,19 @@
"sourceURL": "https://manjaro.org",
"title": "Manjaro"
},
+ {
+ "asset": "LinuxDistro-mxlinux",
+ "hex": "000000",
+ "id": "mxlinux",
+ "ids": [
+ "mx"
+ ],
+ "license": "GPL-3.0-only",
+ "package": "simple-icons",
+ "slug": "mxlinux",
+ "sourceURL": "https://mxlinux.org/art/",
+ "title": "MX Linux"
+ },
{
"asset": "LinuxDistro-nixos",
"hex": "5277C3",
@@ -209,6 +287,19 @@
"sourceURL": "https://brand.nixos.org/logos/nixos-logomark-black-flat-minimal.svg",
"title": "NixOS"
},
+ {
+ "asset": "LinuxDistro-nobaralinux",
+ "hex": "000000",
+ "id": "nobaralinux",
+ "ids": [
+ "nobara"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "nobaralinux",
+ "sourceURL": "https://wiki.nobaraproject.org",
+ "title": "Nobara Linux"
+ },
{
"asset": "LinuxDistro-opensuse",
"hex": "73BA25",
@@ -225,6 +316,32 @@
"sourceURL": "https://github.com/openSUSE/artwork/blob/33e94aa76837c09f03d1712705949b71a246a53b/logos/buttons/button-colour.svg",
"title": "openSUSE"
},
+ {
+ "asset": "LinuxDistro-openwrt",
+ "hex": "00B5E2",
+ "id": "openwrt",
+ "ids": [
+ "openwrt"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "openwrt",
+ "sourceURL": "https://openwrt.org/docs/guide-graphic-designer/openwrt-logo",
+ "title": "OpenWrt"
+ },
+ {
+ "asset": "LinuxDistro-parrotsecurity",
+ "hex": "15E0ED",
+ "id": "parrotsecurity",
+ "ids": [
+ "parrot"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "parrotsecurity",
+ "sourceURL": "https://gitlab.com/parrotsec/project/documentation/-/blob/d1d426b9cb3ea0efd16a2b34056c1ebb21bb9af9/static/img/parrot-logo.svg",
+ "title": "Parrot Security"
+ },
{
"asset": "LinuxDistro-popos",
"hex": "48B9C7",
@@ -239,6 +356,19 @@
"sourceURL": "https://github.com/system76/brand/blob/7a31740b54f929b62a165baa61dfb0b5164261e8/Pop_OS%20branding/Pop_icon.svg",
"title": "Pop!_OS"
},
+ {
+ "asset": "LinuxDistro-qubesos",
+ "hex": "3874D8",
+ "id": "qubesos",
+ "ids": [
+ "qubes"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "qubesos",
+ "sourceURL": "https://github.com/QubesOS/qubes-attachment/blob/ed7e552eb8a5fca4e099361d137793d3551b3968/icons/qubes-logo-home.svg",
+ "title": "Qubes OS"
+ },
{
"asset": "LinuxDistro-raspberrypi",
"hex": "A22846",
@@ -280,6 +410,19 @@
"sourceURL": "https://github.com/rocky-linux/branding/blob/94e97dd30b87d909cc4f6a6838a2926f77f9ac47/logo/src/icon-black.svg",
"title": "Rocky Linux"
},
+ {
+ "asset": "LinuxDistro-slackware",
+ "hex": "000000",
+ "id": "slackware",
+ "ids": [
+ "slackware"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "slackware",
+ "sourceURL": "https://en.wikipedia.org/wiki/Slackware",
+ "title": "Slackware"
+ },
{
"asset": "LinuxDistro-solus",
"hex": "5294E2",
@@ -361,6 +504,19 @@
"sourceURL": "https://commons.wikimedia.org/wiki/File:Xubuntu_Icon.svg",
"title": "Xubuntu"
},
+ {
+ "asset": "LinuxDistro-zorin",
+ "hex": "15A6F0",
+ "id": "zorin",
+ "ids": [
+ "zorin"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "zorin",
+ "sourceURL": "https://zorin.com/press",
+ "title": "Zorin"
+ },
{
"asset": "HostOS-macos",
"hex": "000000",
@@ -374,6 +530,72 @@
"sourceURL": "https://www.apple.com",
"title": "macOS"
},
+ {
+ "asset": "HostOS-bsd",
+ "hex": "AB2B28",
+ "id": "bsd",
+ "ids": [
+ "bsd",
+ "dragonfly"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "bsd",
+ "sourceURL": "https://freebsdfoundation.org/about-us/about-the-foundation/project",
+ "title": "BSD"
+ },
+ {
+ "asset": "HostOS-freebsd",
+ "hex": "AB2B28",
+ "id": "freebsd",
+ "ids": [
+ "freebsd"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "freebsd",
+ "sourceURL": "https://www.freebsdfoundation.org/about/project/",
+ "title": "FreeBSD"
+ },
+ {
+ "asset": "HostOS-openbsd",
+ "hex": "F2CA30",
+ "id": "openbsd",
+ "ids": [
+ "openbsd"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "openbsd",
+ "sourceURL": "https://en.wikipedia.org/wiki/OpenBSD",
+ "title": "OpenBSD"
+ },
+ {
+ "asset": "HostOS-netbsd",
+ "hex": "FF6600",
+ "id": "netbsd",
+ "ids": [
+ "netbsd"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "netbsd",
+ "sourceURL": "https://www.netbsd.org",
+ "title": "NetBSD"
+ },
+ {
+ "asset": "HostOS-linux",
+ "hex": "FCC624",
+ "id": "linux",
+ "ids": [
+ "linux"
+ ],
+ "license": null,
+ "package": "simple-icons",
+ "slug": "linux",
+ "sourceURL": "https://www.linuxfoundation.org/the-linux-mark/",
+ "title": "Linux"
+ },
{
"asset": "HostOS-windows",
"colorSource": "Termind presentation choice",
diff --git a/Frameworks/TermindDomain/Sessions/RemoteSystemInfo.swift b/Frameworks/TermindDomain/Sessions/RemoteSystemInfo.swift
index 5d48df39..9f99681e 100644
--- a/Frameworks/TermindDomain/Sessions/RemoteSystemInfo.swift
+++ b/Frameworks/TermindDomain/Sessions/RemoteSystemInfo.swift
@@ -138,13 +138,14 @@ public nonisolated enum RemoteDistributionDetection: Hashable, Sendable {
extension RemoteSystemInfo {
public var distributionDetection: RemoteDistributionDetection {
guard let kernelName else { return .unavailable }
- if kernelName.caseInsensitiveCompare("Darwin") == .orderedSame {
+ let kernel = kernelName.lowercased()
+ if kernel == "darwin" {
return .resolved(DetectedDistribution(id: "macos"))
}
- guard kernelName.caseInsensitiveCompare("Linux") == .orderedSame else { return .resolved(nil) }
- guard let distributionID,
- let distribution = DetectedDistribution(id: distributionID, likeIDs: distributionLikeIDs)
- else { return .unavailable }
- return .resolved(distribution)
+ if ["freebsd", "openbsd", "netbsd", "dragonfly"].contains(kernel) {
+ return .resolved(DetectedDistribution(id: kernel))
+ }
+ guard kernel == "linux" else { return .resolved(nil) }
+ return .resolved(DetectedDistribution(id: distributionID ?? "linux", likeIDs: distributionLikeIDs))
}
}
diff --git a/Frameworks/TermindPresentation/HostIconCatalog.swift b/Frameworks/TermindPresentation/HostIconCatalog.swift
index 6f73a2b5..d702a44b 100644
--- a/Frameworks/TermindPresentation/HostIconCatalog.swift
+++ b/Frameworks/TermindPresentation/HostIconCatalog.swift
@@ -37,10 +37,11 @@ public nonisolated struct HostIconCatalog: Codable, Sendable {
}
public var selectableEntries: [HostIconEntry] {
- let platforms = ["macos", "windows"].compactMap { id in entries.first { $0.id == id } }
- return platforms
- + entries.filter { $0.id != "macos" && $0.id != "windows" }
- .sorted { $0.title.localizedStandardCompare($1.title) == .orderedAscending }
+ entries.sorted {
+ let comparison = $0.title.compare(
+ $1.title, options: [.caseInsensitive], locale: Locale(identifier: "en_US"))
+ return comparison == .orderedSame ? $0.id < $1.id : comparison == .orderedAscending
+ }
}
public func entry(matching distribution: DetectedDistribution) -> HostIconEntry? {
@@ -49,6 +50,8 @@ public nonisolated struct HostIconCatalog: Codable, Sendable {
uniquingKeysWith: { first, _ in first }
)
if let exact = byID[distribution.id] { return exact }
- return distribution.likeIDs.lazy.compactMap { byID[$0] }.first
+ if let related = distribution.likeIDs.lazy.compactMap({ byID[$0] }).first { return related }
+ // Detection only persists supported OS families; BSD and macOS match above.
+ return byID["linux"]
}
}
diff --git a/Scripts/linux-distribution-icons.json b/Scripts/linux-distribution-icons.json
index abdfa096..87947b25 100644
--- a/Scripts/linux-distribution-icons.json
+++ b/Scripts/linux-distribution-icons.json
@@ -2,32 +2,44 @@
{ "title": "AlmaLinux", "slug": "almalinux", "ids": ["almalinux", "alma"] },
{ "title": "Alpine Linux", "slug": "alpinelinux", "ids": ["alpine"] },
{ "title": "Arch Linux", "slug": "archlinux", "ids": ["arch"] },
+ { "title": "Artix Linux", "slug": "artixlinux", "ids": ["artix"] },
+ { "title": "Asahi Linux", "slug": "asahilinux", "ids": ["asahi"] },
+ { "title": "CachyOS", "slug": "cachyos", "ids": ["cachyos"] },
{ "title": "CentOS", "slug": "centos", "ids": ["centos"] },
{ "title": "Debian", "slug": "debian", "ids": ["debian"] },
{ "title": "deepin", "slug": "deepin", "ids": ["deepin"] },
+ { "title": "Devuan", "slug": "devuan", "ids": ["devuan"] },
{ "title": "elementary", "slug": "elementary", "ids": ["elementary"] },
{ "title": "EndeavourOS", "slug": "endeavouros", "ids": ["endeavouros"] },
{ "title": "Fedora", "slug": "fedora", "ids": ["fedora"] },
+ { "title": "Garuda Linux", "slug": "garudalinux", "ids": ["garuda"] },
{ "title": "Gentoo", "slug": "gentoo", "ids": ["gentoo"] },
{ "title": "Kali Linux", "slug": "kalilinux", "ids": ["kali"] },
{ "title": "Kubuntu", "slug": "kubuntu", "ids": ["kubuntu"] },
{ "title": "Linux Mint", "slug": "linuxmint", "ids": ["linuxmint"] },
{ "title": "Lubuntu", "slug": "lubuntu", "ids": ["lubuntu"] },
{ "title": "Manjaro", "slug": "manjaro", "ids": ["manjaro"] },
+ { "title": "MX Linux", "slug": "mxlinux", "ids": ["mx"] },
{ "title": "NixOS", "slug": "nixos", "ids": ["nixos"] },
+ { "title": "Nobara Linux", "slug": "nobaralinux", "ids": ["nobara"] },
{
"title": "openSUSE",
"slug": "opensuse",
"ids": ["opensuse", "opensuse-leap", "opensuse-tumbleweed", "opensuse-microos"]
},
+ { "title": "OpenWrt", "slug": "openwrt", "ids": ["openwrt"] },
+ { "title": "Parrot Security", "slug": "parrotsecurity", "ids": ["parrot"] },
{ "title": "Pop!_OS", "slug": "popos", "ids": ["pop", "popos"] },
+ { "title": "Qubes OS", "slug": "qubesos", "ids": ["qubes"] },
{ "title": "Raspberry Pi", "slug": "raspberrypi", "ids": ["raspbian", "raspberrypios"] },
{ "title": "Red Hat", "slug": "redhat", "ids": ["rhel", "redhat"] },
{ "title": "Rocky Linux", "slug": "rockylinux", "ids": ["rocky"] },
+ { "title": "Slackware", "slug": "slackware", "ids": ["slackware"] },
{ "title": "Solus", "slug": "solus", "ids": ["solus"] },
{ "title": "SUSE", "slug": "suse", "ids": ["suse", "sles", "sled"] },
{ "title": "Ubuntu", "slug": "ubuntu", "ids": ["ubuntu"] },
{ "title": "Ubuntu MATE", "slug": "ubuntumate", "ids": ["ubuntu-mate", "ubuntu-mate-desktop"] },
{ "title": "Void Linux", "slug": "voidlinux", "ids": ["void"] },
- { "title": "Xubuntu", "slug": "xubuntu", "ids": ["xubuntu"] }
+ { "title": "Xubuntu", "slug": "xubuntu", "ids": ["xubuntu"] },
+ { "title": "Zorin", "slug": "zorin", "ids": ["zorin"] }
]
diff --git a/Scripts/update-host-icons.py b/Scripts/update-host-icons.py
index 13443ae1..4bb19717 100644
--- a/Scripts/update-host-icons.py
+++ b/Scripts/update-host-icons.py
@@ -72,6 +72,14 @@ def main():
metadata = {entry["title"]: entry for entry in json.loads(read(simple, "data/simple-icons.json"))}
requested = json.loads((ROOT / "Scripts/linux-distribution-icons.json").read_text())
requested.append({"title": "Apple", "slug": "apple", "ids": ["macos"], "id": "macos"})
+ for title, slug, ids in [
+ ("BSD", "bsd", ["bsd", "dragonfly"]),
+ ("FreeBSD", "freebsd", ["freebsd"]),
+ ("OpenBSD", "openbsd", ["openbsd"]),
+ ("NetBSD", "netbsd", ["netbsd"]),
+ ("Linux", "linux", ["linux"]),
+ ]:
+ requested.append({"title": title, "slug": slug, "ids": ids, "id": slug})
with tempfile.TemporaryDirectory(prefix="termind-host-icons-") as temporary:
stage = Path(temporary)
assets = stage / "assets"
@@ -83,7 +91,7 @@ def main():
for item in requested:
info = metadata[item["title"]]
is_mac = item.get("id") == "macos"
- asset = "HostOS-macos" if is_mac else "LinuxDistro-" + item["slug"]
+ asset = "HostOS-" + item["id"] if "id" in item else "LinuxDistro-" + item["slug"]
write_icon(assets, asset, read(simple, "icons/" + item["slug"] + ".svg"))
entries.append({
"id": item.get("id", item["slug"]), "title": "macOS" if is_mac else item["title"],
diff --git a/TFITests/MobileAppTests.swift b/TFITests/MobileAppTests.swift
index 7c8d856f..c4eed964 100644
--- a/TFITests/MobileAppTests.swift
+++ b/TFITests/MobileAppTests.swift
@@ -1505,7 +1505,8 @@ private actor MobileTestSFTPClient: SFTPClientProtocol {
@Suite struct WorkspaceHostIconTests {
@Test @MainActor func operatingSystemArtworkIsBundledOnMobile() {
- #expect(HostIconCatalog.bundled.selectableEntries.prefix(2).map(\.id) == ["macos", "windows"])
+ #expect(HostIconCatalog.bundled.selectableEntries.first?.title == "AlmaLinux")
+ #expect(HostIconCatalog.bundled.selectableEntries.last?.title == "Zorin")
for entry in HostIconCatalog.bundled.entries {
#expect(UIImage(named: entry.asset) != nil, Comment(rawValue: entry.asset))
}
diff --git a/TFITests/MobileHostIntegrationRenderingTests.swift b/TFITests/MobileHostIntegrationRenderingTests.swift
index df104c7f..39fed0be 100644
--- a/TFITests/MobileHostIntegrationRenderingTests.swift
+++ b/TFITests/MobileHostIntegrationRenderingTests.swift
@@ -41,12 +41,13 @@ struct MobileHostIntegrationRenderingTests {
descendants(controller.view).compactMap { $0 as? UIButton }.first { $0.showsMenuAsPrimaryAction })
let actions = try await menuActions(in: picker, expectedCount: HostIconCatalog.bundled.entries.count + 2)
#expect(
- actions.prefix(4).map(\.title) == [
+ actions.prefix(2).map(\.title) == [
AppLocalization.string("Automatic", locale: Locale(identifier: language)),
- AppLocalization.string("Server", locale: Locale(identifier: language)), "macOS", "Windows",
+ AppLocalization.string("Server", locale: Locale(identifier: language)),
])
+ #expect(actions.dropFirst(2).map(\.title) == HostIconCatalog.bundled.selectableEntries.map(\.title))
#expect(actions.first?.state == .on)
- picker.sendAction(actions[3])
+ picker.sendAction(try #require(actions.first { $0.title == "Windows" }))
#expect(draft.host.iconOverrideID == "windows")
#expect(draft.host.detectedDistribution?.id == "macos")
for style in [UIUserInterfaceStyle.light, .dark] {
diff --git a/TFITests/MobileWorkspaceSessionTests.swift b/TFITests/MobileWorkspaceSessionTests.swift
index 1afda920..1aba8f1e 100644
--- a/TFITests/MobileWorkspaceSessionTests.swift
+++ b/TFITests/MobileWorkspaceSessionTests.swift
@@ -44,6 +44,11 @@ struct MobileWorkspaceSessionTests {
try await Task.sleep(for: .milliseconds(10))
}
#expect(workspace.activitySession.iconAsset == "LinuxDistro-ubuntu")
+ workspace.receiveShellEvent(.systemInfo(RemoteSystemInfo(kernelName: "DragonFly")))
+ #expect(workspace.activitySession.iconAsset == "HostOS-bsd")
+ #expect(app.library.host(host.id)?.detectedDistribution?.id == "dragonfly")
+ workspace.receiveShellEvent(.systemInfo(RemoteSystemInfo(distributionID: "custom-linux", kernelName: "Linux")))
+ #expect(workspace.activitySession.iconAsset == "HostOS-linux")
#expect(workspace.status == originalStatus)
#expect(session.openWorkspace(id) === workspace)
}
diff --git a/TFMTests/HostIconPresentationTests.swift b/TFMTests/HostIconPresentationTests.swift
index e3ce48c2..07a6d1fd 100644
--- a/TFMTests/HostIconPresentationTests.swift
+++ b/TFMTests/HostIconPresentationTests.swift
@@ -7,9 +7,23 @@ import Testing
@Suite @MainActor
struct HostIconPresentationTests {
+ @Test(arguments: [
+ ("artix", "Artix Linux"), ("asahi", "Asahi Linux"), ("cachyos", "CachyOS"),
+ ("devuan", "Devuan"), ("garuda", "Garuda Linux"), ("mx", "MX Linux"),
+ ("nobara", "Nobara Linux"), ("openwrt", "OpenWrt"), ("parrot", "Parrot Security"),
+ ("qubes", "Qubes OS"), ("slackware", "Slackware"), ("zorin", "Zorin"),
+ ("freebsd", "FreeBSD"), ("openbsd", "OpenBSD"), ("netbsd", "NetBSD"), ("dragonfly", "BSD"),
+ ("linux", "Linux"), ("unlisted-linux", "Linux"),
+ ])
+ func expandedCatalogMatchesSystemIdentifiers(id: String, title: String) throws {
+ let distribution = try #require(DetectedDistribution(id: id))
+ #expect(HostIconCatalog.entry(for: distribution)?.title == title)
+ }
+
@Test func bundledArtworkCanBeLoadedAndManualSelectionWins() throws {
let catalog = HostIconCatalog.bundled
- #expect(catalog.selectableEntries.prefix(2).map(\.id) == ["macos", "windows"])
+ #expect(catalog.selectableEntries.first?.title == "AlmaLinux")
+ #expect(catalog.selectableEntries.last?.title == "Zorin")
for entry in catalog.entries {
#expect(NSImage(named: entry.asset) != nil, Comment(rawValue: entry.asset))
}
diff --git a/TFMTests/ShellIntegrationBootstrapTests.swift b/TFMTests/ShellIntegrationBootstrapTests.swift
index da30d0fa..3050aab1 100644
--- a/TFMTests/ShellIntegrationBootstrapTests.swift
+++ b/TFMTests/ShellIntegrationBootstrapTests.swift
@@ -727,6 +727,17 @@ struct ShellIntegrationBootstrapTests {
#expect(completed.systemInfo?.architecture == "arm64")
}
+ @Test(arguments: ["FreeBSD", "OpenBSD", "NetBSD", "DragonFly"], ["||", "|||"])
+ func bsdKernelOnlyRecordsRetainTheirSystemIdentity(kernel: String, prefix: String) throws {
+ var bootstrap = RemoteShellIntegrationBootstrap(token: "BSD")
+ let marker = "__TRMD_BSD_SYSTEM__\(prefix)\(kernel)|14.1|amd64__\r\n__TRMD_BSD_SHELL__/bin/zsh__\r\n"
+ _ = try #require(bootstrap.receive(Data(marker.utf8)).command)
+ let completed = bootstrap.receive(Data("__TRMD_BSD_DONE__\r\n".utf8))
+ #expect(completed.integrationInstalled)
+ #expect(completed.systemInfo?.distributionDetection == .resolved(DetectedDistribution(id: kernel)))
+ #expect(completed.systemInfo?.distributionID == nil)
+ }
+
@Test func existingProbeReadsDarwinOnTheLocalMac() throws {
var bootstrap = RemoteShellIntegrationBootstrap(token: "LOCALMAC")
var script = Data("export SHELL=/bin/zsh\n".utf8)