From 88c8688b4704a9d604503c775f2ccf28398c7a71 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 29 Jul 2026 14:35:21 +0200 Subject: [PATCH] feat(client/apple): host cards wear the OS mark, and the store learns it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `DiscoveredHost` reads the new `os=` TXT (sanitized in PunktfunkShared's OsChain.swift — the pure grammar + walk, mirrored from pf-client-core so every platform resolves identically, and dependency-free so the widget could use it); `StoredHost` appends optional `osChain` per the frozen app↔widget contract (optional + appended last; legacy JSON still decodes, pinned by the round-trip tests), and `HostStore.updateOsChain` learns it beside the MACs. The art rides PunktfunkKit's proven resource path (the fonts precedent): ten template vector imagesets in Resources/OsIcons.xcassets, compiled by the Xcode build into the Kit bundle's Assets.car (verified: all ten resolve in the built app) and tinting via foregroundStyle like an SF Symbol. Saved and discovered cards lead their status line with the mark; a host that advertises nothing renders exactly as before. GamepadHome tile + widget marks are follow-ups. Co-Authored-By: Claude Fable 5 --- clients/apple/Package.swift | 4 ++ .../Sources/PunktfunkClient/ContentView.swift | 4 +- .../PunktfunkClient/Home/HostCards.swift | 18 ++++++++ .../PunktfunkClient/Stores/HostStore.swift | 9 ++++ .../Connection/HostDiscovery.swift | 10 +++- .../apple/Sources/PunktfunkKit/OsIcon.swift | 24 ++++++++++ .../Resources/OsIcons.xcassets/Contents.json | 3 ++ .../os-apple.imageset/Contents.json | 10 ++++ .../os-apple.imageset/apple.pdf | Bin 0 -> 1365 bytes .../os-arch.imageset/Contents.json | 10 ++++ .../os-arch.imageset/arch.pdf | Bin 0 -> 1416 bytes .../os-debian.imageset/Contents.json | 10 ++++ .../os-debian.imageset/debian.pdf | Bin 0 -> 2951 bytes .../os-fedora.imageset/Contents.json | 10 ++++ .../os-fedora.imageset/fedora.pdf | Bin 0 -> 2838 bytes .../os-linux.imageset/Contents.json | 10 ++++ .../os-linux.imageset/linux.pdf | Bin 0 -> 3667 bytes .../os-nixos.imageset/Contents.json | 10 ++++ .../os-nixos.imageset/nixos.pdf | Bin 0 -> 1387 bytes .../os-opensuse.imageset/Contents.json | 10 ++++ .../os-opensuse.imageset/opensuse.pdf | Bin 0 -> 2102 bytes .../os-steam.imageset/Contents.json | 10 ++++ .../os-steam.imageset/steam.pdf | Bin 0 -> 1585 bytes .../os-ubuntu.imageset/Contents.json | 10 ++++ .../os-ubuntu.imageset/ubuntu.pdf | Bin 0 -> 1634 bytes .../os-windows.imageset/Contents.json | 10 ++++ .../os-windows.imageset/windows.pdf | Bin 0 -> 1121 bytes .../Sources/PunktfunkShared/OsChain.swift | 43 ++++++++++++++++++ .../Sources/PunktfunkShared/StoredHost.swift | 8 +++- .../SharedFoundationTests.swift | 25 +++++++++- 30 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 clients/apple/Sources/PunktfunkKit/OsIcon.swift create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-apple.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-apple.imageset/apple.pdf create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-arch.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-arch.imageset/arch.pdf create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-debian.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-debian.imageset/debian.pdf create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-fedora.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-fedora.imageset/fedora.pdf create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-linux.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-linux.imageset/linux.pdf create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-nixos.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-nixos.imageset/nixos.pdf create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-opensuse.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-opensuse.imageset/opensuse.pdf create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-steam.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-steam.imageset/steam.pdf create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-ubuntu.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-ubuntu.imageset/ubuntu.pdf create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-windows.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-windows.imageset/windows.pdf create mode 100644 clients/apple/Sources/PunktfunkShared/OsChain.swift diff --git a/clients/apple/Package.swift b/clients/apple/Package.swift index 3fd28e27..24655d13 100644 --- a/clients/apple/Package.swift +++ b/clients/apple/Package.swift @@ -34,6 +34,10 @@ let package = Package( // Geist (SIL OFL 1.1) — the brand typeface, shared with punktfunk-website. // Registered with Core Text at first use; see BrandFont.swift. .copy("Resources/Fonts"), + // The host cards' OS marks (template vector imagesets derived from the + // assets/os-icons masters — FA brands CC BY 4.0 + Simple Icons CC0, see that + // README). `.process` compiles the catalog; loaded via OsIcon.swift. + .process("Resources/OsIcons.xcassets"), ], linkerSettings: [ // Rust staticlib system deps. diff --git a/clients/apple/Sources/PunktfunkClient/ContentView.swift b/clients/apple/Sources/PunktfunkClient/ContentView.swift index 175a282f..e1264200 100644 --- a/clients/apple/Sources/PunktfunkClient/ContentView.swift +++ b/clients/apple/Sources/PunktfunkClient/ContentView.swift @@ -916,6 +916,7 @@ struct ContentView: View { private func prepareWake(for host: StoredHost) { if let live = discovery.hosts.first(where: { host.matches($0) }) { store.updateMacs(host.id, macs: live.macAddresses) // learn — on every platform + store.updateOsChain(host.id, chain: live.osChain) // ditto for the card's OS mark } else if autoWakeEnabled, PunktfunkConnection.wakeOnLANAvailable, !host.wakeMacs.isEmpty { // Auto-wake only: fire the up-front packet so a genuinely-asleep host is booting while the // dial times out. With auto-wake off, connects go straight through (no packet). @@ -970,7 +971,8 @@ struct ContentView: View { guard !model.isBusy else { return } let host = StoredHost( name: d.name, address: d.host, port: d.port, - macAddresses: d.macAddresses.isEmpty ? nil : d.macAddresses) + macAddresses: d.macAddresses.isEmpty ? nil : d.macAddresses, + osChain: d.osChain.isEmpty ? nil : d.osChain) store.add(host) if d.allowsTofu { connect(host, allowTofu: true) diff --git a/clients/apple/Sources/PunktfunkClient/Home/HostCards.swift b/clients/apple/Sources/PunktfunkClient/Home/HostCards.swift index 24be520a..1aee3cb5 100644 --- a/clients/apple/Sources/PunktfunkClient/Home/HostCards.swift +++ b/clients/apple/Sources/PunktfunkClient/Home/HostCards.swift @@ -295,6 +295,16 @@ struct HostCardView: View { /// certificate is pinned (the lock state, spelled out). @ViewBuilder private func statusRow(_ m: CardMetrics) -> some View { HStack(spacing: 6) { + // The host's OS mark leads the row (template asset — tints like an SF Symbol); + // absent entirely for a host that never advertised one, so those cards render + // exactly as they always did. + if let mark = osIconImage(for: host.osChain) { + mark + .resizable() + .scaledToFit() + .frame(width: m.status + 2, height: m.status + 2) + .accessibilityLabel(host.osChain ?? "") + } RoundedRectangle(cornerRadius: 1.5) .fill(isOnline ? Color.green : Color.secondary.opacity(0.4)) .frame(width: 6, height: 6) @@ -365,6 +375,14 @@ struct DiscoveredCardView: View { .foregroundStyle(.secondary) .lineLimit(1) HStack(spacing: 6) { + // Same leading OS mark as a saved card's status row — live from the advert. + if let mark = osIconImage(for: discovered.osChain) { + mark + .resizable() + .scaledToFit() + .frame(width: m.status + 2, height: m.status + 2) + .accessibilityLabel(discovered.osChain) + } Image(systemName: discovered.requiresPairing ? "lock.fill" : "antenna.radiowaves.left.and.right") .font(.system(size: m.status)) diff --git a/clients/apple/Sources/PunktfunkClient/Stores/HostStore.swift b/clients/apple/Sources/PunktfunkClient/Stores/HostStore.swift index 15bab6f8..29ecebce 100644 --- a/clients/apple/Sources/PunktfunkClient/Stores/HostStore.swift +++ b/clients/apple/Sources/PunktfunkClient/Stores/HostStore.swift @@ -153,6 +153,15 @@ final class HostStore: ObservableObject { hosts[i].macAddresses = macs } + /// Learn/refresh this host's OS-identity chain from its live advert — same contract as + /// [`updateMacs`]: no-op when empty or unchanged, so discovery ticks don't churn UserDefaults. + func updateOsChain(_ hostID: UUID, chain: String) { + guard !chain.isEmpty, + let i = hosts.firstIndex(where: { $0.id == hostID }), + hosts[i].osChain != chain else { return } + hosts[i].osChain = chain + } + /// Bind this host to a settings profile, or to "Default settings" (nil) — the ONLY way the /// default changes. A one-off "Connect with ▸" deliberately never lands here (§5.2: /// predictable, not sticky). diff --git a/clients/apple/Sources/PunktfunkKit/Connection/HostDiscovery.swift b/clients/apple/Sources/PunktfunkKit/Connection/HostDiscovery.swift index 301e23d1..d7ccb78c 100644 --- a/clients/apple/Sources/PunktfunkKit/Connection/HostDiscovery.swift +++ b/clients/apple/Sources/PunktfunkKit/Connection/HostDiscovery.swift @@ -13,6 +13,7 @@ #if canImport(Network) import Foundation import Network +import PunktfunkShared /// A punktfunk/1 host found on the LAN. `fingerprintHex` is advisory (see file header). public struct DiscoveredHost: Identifiable, Sendable, Equatable { @@ -37,6 +38,10 @@ public struct DiscoveredHost: Identifiable, Sendable, Equatable { /// value only makes a wake fail — the magic packet is inert and the fingerprint still gates /// the connection). public let macAddresses: [String] + /// The host's OS-identity chain (mDNS `os` TXT, e.g. `linux/fedora/bazzite`), sanitized + /// (`sanitizeOsChain`) — drives the host card's OS mark and is persisted like the MACs. + /// Empty when not advertised (older host). Advisory/unauthenticated like the rest. + public let osChain: String } @MainActor @@ -118,6 +123,7 @@ public final class HostDiscovery: ObservableObject { var pair: String? var id: String? var macs: [String] = [] + var osChain = "" if case let .bonjour(txt) = result.metadata { fp = Self.entry(txt, "fp") pair = Self.entry(txt, "pair") @@ -126,6 +132,7 @@ public final class HostDiscovery: ObservableObject { .split(separator: ",") .map { $0.trimmingCharacters(in: .whitespaces) } .filter { !$0.isEmpty } + osChain = sanitizeOsChain(Self.entry(txt, "os") ?? "") } // Resolve over IPv4 only: Network.framework prefers IPv6 (RFC 6724), and the host's OS // mDNS responder often answers AAAA for its hostname even though the punktfunk host stack @@ -149,7 +156,8 @@ public final class HostDiscovery: ObservableObject { id: (id?.isEmpty == false) ? id! : name, name: name, host: address, port: port.rawValue, fingerprintHex: fp, requiresPairing: pair == "required", - allowsTofu: pair == "optional", macAddresses: macs) + allowsTofu: pair == "optional", macAddresses: macs, + osChain: osChain) self.publish() } conn.cancel() diff --git a/clients/apple/Sources/PunktfunkKit/OsIcon.swift b/clients/apple/Sources/PunktfunkKit/OsIcon.swift new file mode 100644 index 00000000..b75df6eb --- /dev/null +++ b/clients/apple/Sources/PunktfunkKit/OsIcon.swift @@ -0,0 +1,24 @@ +// The host cards' OS marks: template vector imagesets in Resources/OsIcons.xcassets +// (derived from the repo's assets/os-icons masters — Font Awesome Free brands CC BY 4.0 + +// Simple Icons CC0; provenance in that directory's README), resolved from the host's +// OS-identity chain via PunktfunkShared's `osIconTokens` walk. Template rendering means +// they tint with `foregroundStyle` like an SF Symbol. + +import PunktfunkShared +import SwiftUI + +/// The icon tokens this client ships art for. A distro without its own mark (Bazzite, +/// CachyOS, ...) degrades to its family's and finally to Tux via the chain walk. +private let osIconTokensShipped: Set = [ + "windows", "apple", "linux", "steam", "ubuntu", "fedora", "arch", "debian", "nixos", + "opensuse", +] + +/// The mark for an OS-identity chain (`linux/fedora/bazzite`, ...), or nil — no view at +/// all — when the host doesn't advertise one / nothing in the chain is recognized, so +/// those cards render exactly as they did before the field existed. +public func osIconImage(for chain: String?) -> Image? { + osIconTokens(chain) + .first(where: osIconTokensShipped.contains) + .map { Image("os-\($0)", bundle: .module) } +} diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/Contents.json b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/Contents.json new file mode 100644 index 00000000..d8b757af --- /dev/null +++ b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/Contents.json @@ -0,0 +1,3 @@ +{ + "info" : { "author" : "xcode", "version" : 1 } +} diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-apple.imageset/Contents.json b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-apple.imageset/Contents.json new file mode 100644 index 00000000..c5e156c2 --- /dev/null +++ b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-apple.imageset/Contents.json @@ -0,0 +1,10 @@ +{ + "images" : [ + { "filename" : "apple.pdf", "idiom" : "universal" } + ], + "info" : { "author" : "xcode", "version" : 1 }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-apple.imageset/apple.pdf b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-apple.imageset/apple.pdf new file mode 100644 index 0000000000000000000000000000000000000000..103f1801c815d6ec453630379d2db238f825c36b GIT binary patch literal 1365 zcmY!laBlN%J>7i-Az`@Gf-2TcKmlSaE*UU?Y*o!P(b z_w=k2KB2v#_h7hbC71GS+1E8AY}3+Iq0IIQ)mMw5fTNuZ!a^*F`h0THU?nT(!vhi|5b! zvwM2-AM*?FO}oyOnwJ8N2_Op;hoFc9(ZHxPwlD_CgLua97zW3wYek8Ba0xIz6>Mxk z0zjajXaM9W=sP)ASyWu2U;<_1Pfo_c1`oQPbv9Zp*N(7q_J<;2 z?YztX%v|@A`*g&)%j&1(HG9)0?~kbXa4m7akXx&a;!TcwSC@)f|9$*8=zm3^g6<~O zlIMHwC`Es9U)(O2D%ob>FQ+{B;>loTtK01Qm9sx;&yl=h^LFp61^c2`zIv?ZFyHb+ zRHUuMN}WqbKR*#)I(t!ceQW*KZ-+Yd@7}rnll#u;%8U9c)p@ruqX!(y(D*Sjw}3^E zAtC|75>7-=Y8rB~F}1`Q4WQ&1oLQBsU7#JGWr6eSzJYW~<+3uM1#zAbdYR3&;B|V2LhD{u4 pZqqatNO&tUGc!mZVcUc0%i@y6qLPXtpa%>Mje(`8s;j>n7XWlN;pNcgKKd_ixdE4xy)~>HL=b-LmLF9=~z> zw*Ty2T9P@-HggEHbGcsJvenGyzKDM8*XMix=;?ab+r9cOzqam=uJ;|c+$6hKd)tm& zx|Lc|)0bD;(I2}!!lhJ_O?1It^$WMS*I2S?B~0Cw;y3NroCU2rtn1dgHQs16XI=K? z;JbhcF;`@Ixuz<%<%de%nt@RtD_n zI+{*g^EC10m?*8-V)$djdu}CPHm3u9HjQ8V_H9y4kh`(<`J>Dkl{Q_@Nex~%#Lk+W z=9@h`W5eRY0?)^nJ=`8mt5&*xq#(*_scACDop;JyA+_IP4o%v;c7yS)2Szg$lxBIF z-MnHtPyOC5^S@`G&9T^C6xnRO_^n*)>TN2^<5iX{-`JyHBU^3#&Y7Jhz@nw<{@0?} zTpjzlda{?quekS`xk{}(*>-dFQJWRgKWDl=`V?XzU2uEN{zr4lFMQ|L39r5XIy0`~ zp2=tSGxujZH#Q}#wQxxJa@yRP^XA*ETHE^is1wFN`K``>zLQXJ>iPeG{Z8L&FX(^h zz8my<@1Cjk9#O|!xKi^{ps@#Jf#Mw$*&rGi-6qDSAbAka7@jJ?>A}pm&}dhtbO%O)Pz72@qP*S&bMQm|RJdGelhKSbxuELf|xe$~fl&z$~thT2C5 zqPJ8T3F)SkpP!sRC66=julyhBbwYkMFaMd%zW+bhME@6Sh)P}-X6%3i85%uC=H{^Y zF+`+4Sh|S_N=-vfHKvw0V*!*pgEOmA6$~NK7OoI&s9>aE6bsf7l%HRsU}y-BT+h6; zd<8=TFdw;WOHN4m@qd12C57h#G;alBA^Eh4J|CVR8?L5-M9d8{v%re literal 0 HcmV?d00001 diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-debian.imageset/Contents.json b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-debian.imageset/Contents.json new file mode 100644 index 00000000..f7b67a40 --- /dev/null +++ b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-debian.imageset/Contents.json @@ -0,0 +1,10 @@ +{ + "images" : [ + { "filename" : "debian.pdf", "idiom" : "universal" } + ], + "info" : { "author" : "xcode", "version" : 1 }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-debian.imageset/debian.pdf b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-debian.imageset/debian.pdf new file mode 100644 index 0000000000000000000000000000000000000000..300d72c894523bbad5cb2292eed5f1b5abba84ed GIT binary patch literal 2951 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 LkA#GtDH`-|Jkl5u literal 0 HcmV?d00001 diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-fedora.imageset/Contents.json b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-fedora.imageset/Contents.json new file mode 100644 index 00000000..8d9e5b77 --- /dev/null +++ b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-fedora.imageset/Contents.json @@ -0,0 +1,10 @@ +{ + "images" : [ + { "filename" : "fedora.pdf", "idiom" : "universal" } + ], + "info" : { "author" : "xcode", "version" : 1 }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-fedora.imageset/fedora.pdf b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-fedora.imageset/fedora.pdf new file mode 100644 index 0000000000000000000000000000000000000000..74f4dd114897aa2b882439a5786bd0b2b9df1cef GIT binary patch literal 2838 zcma);2Q*x18^=dyh|U@%E+TA{nNf#`ZU~}{7G>381fvZh%4!icS_BC)ddaHMGr9yx z5JZUaDG&5Fprco;GXxWBu)E6{Uqd-<3Qu;yu_vE!j1R) ztJ&=tHizvdpDMSk8yihLg@f$|#FOXXIYADGsr$B1w%YzQx9-;7Z}9qKn(ociTXWaM zaf&zohW_lls}yAfzdLiT75*zywF7H*%b>LuS&!Y9rlfmv0XY;Gvts6VyQDrk~3z~%U zezNgtHbe3tKzP#y8Vn^-_d#rQorI>`V4BDf=xMEAZR&YEx9Y<7rGRNRJM8-f2#p|< z80mHNfbZN?hyhi4(}keLK;}-XWIZ9-F?hn6H$y^^9M+J6-#ALBnkhslUyZ05!Kbx; zwX03qr~s5ldBJBbobJF1Tp%a2rO)lPD?NTgmFUiKN7PZ4fD#1tPnj z@izB#bRx85ucLPctnaqKjQuZxdb=1M4We0F;G^RlVyqB_f^+!f7dfBqt>0_)q1k54 z^Pq-^s?{g=#LU%JQ!9b#gG`+h#XlJU-}&aB?*1)7u4rOM&Lz~vZl8=ebH$B>CN3ZMY3TzDUJ)}!RnU?= z%Mh7^4OFQw+c|Hwo$!7YWfmF~e*d$U6BjuZJtt;GXNSq-cC=(xD*r9bkUJkp%lyQ{ zxC((^FUoYixpNWOs;+FSTsmu}Z%;)4EoPxBFwCO}f$D|!Jtem7?j9|1_7=QeWG7`E zRhPmGr@|2Cu6Qx|sLW8nL^7F5HQnKDZ!bZpKUEKtqgIVaz?T@0Aq@O{(Oz{D6Unci zkj|%9>%8`uwHU3yCr2KHag&8ONPXCNa`Dc>v$fp9uy~}M%rGiz!bia}-q9q7z356e z&U}?Yry;_dHQ;8Uaaps1n9y+|Spq0J4Wp1F6pm+bwZ!M$OI<@Ac_Pb;3Ol_PR4d## z(1p>4<&^S|eHf`0^0>4;XoaMGn6~uoUNBdf6@&9P<0@dSoApNwC=YH)+o%uD+(W&S z%z3ocD0d@jg~8{W`QCTHvvP$WL>C3cTXR=a=FUt;4SQ;>*R=3-0#-Uq75EwD<@|nj z4ugk!*vqL(!)1E&<{DOs?hcow16k;gvJv~@cV8Y!w6b85TF5MuGu|@nM#=nOm)=? z3G(;OaiT5EUg*`}hTqYqTyT`WC_#?LA>xm>9v+P@UZlp)Y#+Pyf{zMv9-N%Y!gb@ zGxpkTLS6AW`H?|eB3ouno$}qXk7na>Qsa+62kRH0@iU3%9yUij<_csFmIIr>cdW># z5uXKixMe3tZkI{kEj#b)pL?x#hwll;OZ%`<@+;s22QCZD*|{S(=Bc%W7izxd#EI6x z3K}-&Gc@F_kYq3(Wp|^ak2m**NYA^FUFhERRERr~O}(D$BS9m;M)0h%6brV*MRz!+ z<&z+ariYOoiJgL-RWT9Qt1Z$s6sl^#BDW*df1i$Uj|qi+9ieM;iVgD^A3I96CX0Qv zIWsxAPDKUb;Ue=;erAq)Hn*fOmf_yoO?ZF?6_dJiO5b=l7bFGP3rEPx zo*p=zO8-RkHz;cb;Az<_V^STRL!EUb6i9nYHqEYUz;=c1Xbn%xV;wL&+dhN>;4Xbk&GmXWlDH)8wQ1 zJ&@|ov@A8hJ%CvDQ!nNYrsw8m979%`NrTH-!1HcY++(&m0tBmzS)pnA_2VJU0o}d2 zO~x|67YaA!u8CoVyRQw?&ZI~Lmp}%u?rBa535+=I{6fz(G`%%xqxYak#w<6n+&iim51r(%i#svQ4d^`<0RiLmA!3)5Hg!3_NmO1eY15-D^{+H ztQ_EWU!wPp#gn)jQmBb*K|TrHLnBF#yE|?OHy!IWkODTzNxlZV^uSZ>#6Ru%_Lsl= zBPEacVG#Vk1^A8X?ddDZlD49e|)n5tl@wZAZ7dQ3llFd8~}&^ zboVmW#S4JLzRmxB({!@8-~aj`E+RdGOcMsL^K!JecV3`jeg84$Uh#RdAFBp@XRhk=EKwG5HqzX1OA;zj@f literal 0 HcmV?d00001 diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-linux.imageset/Contents.json b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-linux.imageset/Contents.json new file mode 100644 index 00000000..a63cc240 --- /dev/null +++ b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-linux.imageset/Contents.json @@ -0,0 +1,10 @@ +{ + "images" : [ + { "filename" : "linux.pdf", "idiom" : "universal" } + ], + "info" : { "author" : "xcode", "version" : 1 }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-linux.imageset/linux.pdf b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-linux.imageset/linux.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0a5025e4f02cc4a50e2ba32cce97d7d0e34d2fc8 GIT binary patch literal 3667 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 CYlRC*aPsNa<^}AiGzQD1B=8uL&EgC^a-7k-x zxf6eiS!<<7p!d>$XZ|z4W8SZRtbOl(yIc3oKE1qO{r{`cuiyWdEy zm*xIe?_x7%G)#%$m3wUAaiU#~amJjr2M>qesF<*;=Aq(N>jL3?�LpKlOg>R-7`u z?(5?tMGl8#DphBfPC2(~a>#`I71Ij4lnX;L=LROGe4hT=%5Ygv>^{@ytvL+8a zwqA0%qx3=fm}Sz#(o20WHv1Jma5~+m8N2z)vZtN1qc<#m_;|IClyYHP>pYb6z};9 zC$4PTwk)b$qS@uG!0Q<6U7AJ@6AYp>ww%hit#r9#4==mOD(%>PJF^$h-SyIci%Y-d znyRQRhV|W>IV%&_-EH4fRlWRHYt6%*hDTaLZb+Y>8@^R#cZ)^ox*gY!oPYemx-{ly z#L-(>_m$<6wDjxe-@lysN-QQx`p4!U=hN=pD*nx2cW+k?S884gG=_jIP~3td6hs4~ z)WpCXBoE>l!($#C*RB;M?!hI%cvi5n0SN$sexd=8qoD8Xs9>m|pPUFJfw2#i0Fpoi zHOCxoj)HzjWkITfzJF3ya7ixMRA`d2fF~(G1w*ieTV_#liGm4~jX#+g2OB)-de-%n ziGj_6F&5K*Ak(4#Gc@6*+Nk8jZbRq?5Ml3+v&50_Zs0VZXah_#g= z(F#zS;U7wogdwgO*;BSU(BZB z^@aN!&Wi3HW{u@pQ*XSMm!JN#d3M3{6MH`=asQ01HK>wVpyMp~>Sp31ZmY^Hm;Ckz zD(kuHSkv92R-Ia%^($;rUwdtXUEu@wO;sk7bk@9^r+#1KuFRcZ_CL&HROkG-bT`** z|Nq+=abLJYjq!mE`6UX5hVaPs%uCBxFf;)3k;|{-goGde=XW+vZe(;ZFf?L}?BV(2 z(Dc#Rqeu2c5*Lf$bjKsy9ae4v2hW@c;t)xGnUKK1@Z$tqGNvbsOA?DpDvE&aH#9Ue M;8Im}^>^a}00^KWuK)l5 literal 0 HcmV?d00001 diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-opensuse.imageset/Contents.json b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-opensuse.imageset/Contents.json new file mode 100644 index 00000000..5e5ded45 --- /dev/null +++ b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-opensuse.imageset/Contents.json @@ -0,0 +1,10 @@ +{ + "images" : [ + { "filename" : "opensuse.pdf", "idiom" : "universal" } + ], + "info" : { "author" : "xcode", "version" : 1 }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-opensuse.imageset/opensuse.pdf b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-opensuse.imageset/opensuse.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a01bed03e04ffe71c1cf09d3236fb0a6a53ab2af GIT binary patch literal 2102 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 PhQ_AA8ba08-;E0ZD_nYy literal 0 HcmV?d00001 diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-ubuntu.imageset/Contents.json b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-ubuntu.imageset/Contents.json new file mode 100644 index 00000000..f5aa7661 --- /dev/null +++ b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-ubuntu.imageset/Contents.json @@ -0,0 +1,10 @@ +{ + "images" : [ + { "filename" : "ubuntu.pdf", "idiom" : "universal" } + ], + "info" : { "author" : "xcode", "version" : 1 }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-ubuntu.imageset/ubuntu.pdf b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-ubuntu.imageset/ubuntu.pdf new file mode 100644 index 0000000000000000000000000000000000000000..156db4e3084f0729dcb24f86480ba41e95d46953 GIT binary patch literal 1634 zcmY!laBlN;i_PQ;Ms&Ueis#-_J_-Xs^WToLL&dWLDo zewUkmQI9x#P3D&TS*aC1i~HgC){ob>O#fZ`cbCS!zyCDi_Xq#{QM~b-(dpe8tBp1+ zTJ%g?D<>$==4e%HGW(XLM}z-ViU)pG&e8jGlJje7+HbAbZ!bh{dKg<5r?i+aGnLoA zQam&0rsJ1eY+YX^B}1g;jJ6bAH%i~Sb_o)1A9 zFALursjPe>K9_mZ1&*HT*+Fy9URuSMy!_LZhTY!t-^PZ$sQf0d(|Ow5zd62oE${AT z&YsDBS^bQkbNV5c*pCMdwk~n3eSI;@T}PkWv)pF!6#4r{XEirXQBd6^=NWmz^q6q= ztgE5P^2SG=nlaziUKATYZ@2F|!=xq2*ZjM_h~~W4(8yl+;B}^&$9Ii}&dk!z+@`&J zyA2+F3ws>O$z0q1{f?P$l-I_!H*dURT4?;sZTE?7Y^QD=+qPF(>!#gX)+sB$|CI6!e`P6$};hlM{g?Fv$ZYfFuw> z%`u0Yqo5yBS&*ur@1K+vT#^em6`GGM;Q7c;!4NFrmRVF>qF@4L292Pn;F#C{!NbCL2+UHKsFxucb z%TN6%d-McDwxC++Y7WCI2Qs`S_5FN5J><1S&*mIXUCg=Ev1|6rJssl+xq$ESLx-c2HRGrUYKz;+wZ4w>?d)xh=WIkpY~5Vr#^XqL`BE7 z%zFK~ewIO-1oyn2JTdyW@Z(EAKB>Cz6Z(E3wxfu@@cR2XvQ<-3r7qQcm3Z00H___m zPrJ&Mr(5C$?<`opvTIdTz4X%Acg+sYu>9y&ZEdmC_S5d?ih8RGdfz=3{daYB%7plB zd$)Fr@0i-XC|==I-ZjkV0f#a)evHg5U=d`9NPw_}6A_e}hMa6nEpbKzD0v2FR;4N! zLgFo4A=*&ENWmx;tRpBtzeK^%5FWdpd1?6yh6Z3ha;cr1knrRG{LaS7jf^e^hDLvZ z{FDdmLO%KqNpBp)HcNL{Fe<%sx?+--WPwDsEHg8Mnk~B=rZ0<25{pVIihv$4 PG%+yaQdM>JcjE#8({rAa literal 0 HcmV?d00001 diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-windows.imageset/Contents.json b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-windows.imageset/Contents.json new file mode 100644 index 00000000..7060c6ae --- /dev/null +++ b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-windows.imageset/Contents.json @@ -0,0 +1,10 @@ +{ + "images" : [ + { "filename" : "windows.pdf", "idiom" : "universal" } + ], + "info" : { "author" : "xcode", "version" : 1 }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-windows.imageset/windows.pdf b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-windows.imageset/windows.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5511f150e4a6005d242fa568284a23e951411cde GIT binary patch literal 1121 zcmY!laBliTObb=ZK%<-1+0UO@PzAJsi_3q@w>%yPZ` z+j*+MaXpqsQ>oXyLP@K;Z!`vAz%`SHCy?oAI zA3C!5_^ZssnC6@E8!U)^zoqE(W5@G;i+&|}I@~aBaPi35zxvhg+|NCHTVwZ2|2<&( zr2bODIlH@O|1uoZv#I7v%}as03&;Zb2;>bA4fKeii6KZH#50Eb7wkjViW2wW5}@xC zY-~URK%k#!0OTm>J3A^ED(ELC0!g5sff7Ixh@j?}!_86952-9jRnYfO$_g&Y1)B;D zNDFvC`Y9NKCEPNLic1tspltksYaDFwpzB%JQziyB3&vPX|A9=0`OggIEK4GsWoU%t zE|^YM?th#;fEA2eh zLs{oJZcosyUM6`q@|(*IWyOn0pTxQO&)s@|J;rN5{Yg(qNO{06sPCBc#zAbcb%zC~(mTg1hD{u4 oZqIlYNEF*LGc%|bu#01QvbZF%sHCC@=za@JQ!Z6iSARDy0Qbd^_y7O^ literal 0 HcmV?d00001 diff --git a/clients/apple/Sources/PunktfunkShared/OsChain.swift b/clients/apple/Sources/PunktfunkShared/OsChain.swift new file mode 100644 index 00000000..a190e2eb --- /dev/null +++ b/clients/apple/Sources/PunktfunkShared/OsChain.swift @@ -0,0 +1,43 @@ +// The client half of the host's OS-identity advertisement (mDNS `os` TXT — producer: +// punktfunk-host's osinfo.rs): sanitize the untrusted chain once, and turn it into the +// icon-lookup order every surface walks. Mirrors pf-client-core's `os.rs` (and the Kotlin +// port) so all platforms resolve identically; in the dependency-free shared module so the +// widget could use it too. The Image lookup itself lives in PunktfunkKit (OsIcon.swift). + +import Foundation + +/// Reduce a raw `os` TXT value (e.g. `linux/fedora/bazzite`) to the trusted grammar: +/// lowercase slash-separated tokens of `[a-z0-9._-]`, each ≤ 32 chars, at most 5. mDNS is +/// unauthenticated input — anything outside the grammar is dropped, and a value that +/// sanitizes to nothing becomes `""` (same rendering as a host that advertises no `os`). +public func sanitizeOsChain(_ raw: String) -> String { + raw.lowercased() + .split(separator: "/", omittingEmptySubsequences: true) + .map { token -> String in + let kept = token.unicodeScalars.filter { s in + (0x61...0x7A).contains(s.value) // a-z + || (0x30...0x39).contains(s.value) // 0-9 + || s == "." || s == "_" || s == "-" + } + return String(String.UnicodeScalarView(kept.prefix(32))) + } + .filter { !$0.isEmpty } + .prefix(5) + .joined(separator: "/") +} + +/// The icon-lookup order for a chain: sanitized tokens most-specific-first, with brand +/// aliases applied (`macos` → `apple` art, `steamos` → `steam` art). A surface takes the +/// first token it has art for; empty means "no OS icon" (older host / garbage advert). +public func osIconTokens(_ chain: String?) -> [String] { + sanitizeOsChain(chain ?? "") + .split(separator: "/") + .reversed() + .map { + switch $0 { + case "macos": return "apple" + case "steamos": return "steam" + default: return String($0) + } + } +} diff --git a/clients/apple/Sources/PunktfunkShared/StoredHost.swift b/clients/apple/Sources/PunktfunkShared/StoredHost.swift index 46697a33..35ef7907 100644 --- a/clients/apple/Sources/PunktfunkShared/StoredHost.swift +++ b/clients/apple/Sources/PunktfunkShared/StoredHost.swift @@ -53,12 +53,17 @@ public struct StoredHost: Identifiable, Codable, Hashable, Sendable { /// appended last for the same widget-contract reason as the rest; hosts saved before it /// existed have none, and keep their stored order, which IS the order they were added in. public var addedAt: Date? + /// The host's OS-identity chain (`windows` | `linux//`, ...) learned from its + /// mDNS `os` TXT while online, so the card's OS mark survives the host going to sleep. + /// Optional and appended last for the same widget-contract reason; nil until first learned. + public var osChain: String? public init( id: UUID = UUID(), name: String, address: String, port: UInt16 = 9777, pinnedSHA256: Data? = nil, lastConnected: Date? = nil, mgmtPort: UInt16? = nil, macAddresses: [String]? = nil, clipboardSync: Bool? = nil, - profileID: String? = nil, pinnedProfileIDs: [String]? = nil, addedAt: Date? = nil + profileID: String? = nil, pinnedProfileIDs: [String]? = nil, addedAt: Date? = nil, + osChain: String? = nil ) { self.id = id self.name = name @@ -72,6 +77,7 @@ public struct StoredHost: Identifiable, Codable, Hashable, Sendable { self.profileID = profileID self.pinnedProfileIDs = pinnedProfileIDs self.addedAt = addedAt + self.osChain = osChain } public var displayName: String { name.isEmpty ? address : name } diff --git a/clients/apple/Tests/PunktfunkKitTests/SharedFoundationTests.swift b/clients/apple/Tests/PunktfunkKitTests/SharedFoundationTests.swift index 42b2cee5..8926b995 100644 --- a/clients/apple/Tests/PunktfunkKitTests/SharedFoundationTests.swift +++ b/clients/apple/Tests/PunktfunkKitTests/SharedFoundationTests.swift @@ -25,11 +25,13 @@ final class SharedFoundationTests: XCTestCase { lastConnected: Date(timeIntervalSince1970: 1_700_000_000), mgmtPort: 47990, macAddresses: ["aa:bb:cc:dd:ee:ff"], clipboardSync: true, profileID: "a1b2c3d4e5f6", pinnedProfileIDs: ["0f0f0f0f0f0f"], - addedAt: Date(timeIntervalSince1970: 1_600_000_000)) + addedAt: Date(timeIntervalSince1970: 1_600_000_000), + osChain: "linux/fedora/bazzite") let data = try JSONEncoder().encode(host) let decoded = try JSONDecoder().decode(StoredHost.self, from: data) XCTAssertEqual(decoded, host) + XCTAssertEqual(decoded.osChain, "linux/fedora/bazzite") } /// Older saved hosts predate `mgmtPort`/`macAddresses` — and now `profileID`/ @@ -51,6 +53,7 @@ final class SharedFoundationTests: XCTestCase { XCTAssertNil(decoded.profileID) XCTAssertNil(decoded.pinnedProfileIDs) XCTAssertNil(decoded.addedAt) + XCTAssertNil(decoded.osChain) // Resolvers fall back cleanly. XCTAssertEqual(decoded.effectiveMgmtPort, punktfunkDefaultMgmtPort) XCTAssertEqual(decoded.wakeMacs, []) @@ -62,6 +65,26 @@ final class SharedFoundationTests: XCTestCase { XCTAssertEqual(host.displayName, "10.0.0.9") } + // MARK: - OS-identity chain (mDNS `os` TXT → card icon walk) + + /// Mirrors pf-client-core's `os.rs` tests — the two implementations must agree on the + /// grammar (lowercase `[a-z0-9._-]` tokens, ≤ 32 chars each, ≤ 5 of them) and the walk + /// order (most-specific-first, `macos`→`apple`, `steamos`→`steam`). + func testOsChainSanitizeAndWalk() { + XCTAssertEqual(sanitizeOsChain("linux/fedora/bazzite"), "linux/fedora/bazzite") + XCTAssertEqual(sanitizeOsChain("Linux/Fe do!ra"), "linux/fedora") + XCTAssertEqual(sanitizeOsChain("///"), "") + XCTAssertEqual(sanitizeOsChain("a/b/c/d/e/f/g"), "a/b/c/d/e") + XCTAssertEqual(sanitizeOsChain(String(repeating: "x", count: 80)), + String(repeating: "x", count: 32)) + + XCTAssertEqual(osIconTokens("linux/fedora/bazzite"), ["bazzite", "fedora", "linux"]) + XCTAssertEqual(osIconTokens("linux/arch/steamos"), ["steam", "arch", "linux"]) + XCTAssertEqual(osIconTokens("macos"), ["apple"]) + XCTAssertEqual(osIconTokens(nil), []) + XCTAssertEqual(osIconTokens("!!!"), []) + } + // MARK: - DeepLink grammar (the cross-language vector file) private struct VectorFile: Decodable {