diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt
index c5715f22..288d1670 100644
--- a/THIRD-PARTY-NOTICES.txt
+++ b/THIRD-PARTY-NOTICES.txt
@@ -18,6 +18,7 @@ VENDORED THIRD-PARTY SOURCE (inside first-party crates)
Vulkan-Headers (vendored, crates/pyrowave-sys) — https://github.com/KhronosGroup/Vulkan-Headers
Font Awesome Free brand icons (vendored, assets/os-icons) — https://fontawesome.com
Simple Icons (vendored, assets/os-icons) — https://simpleicons.org
+ Bazzite logo (vendored, assets/os-icons) — https://github.com/ublue-os/bazzite
----------------------------------------------------------------------------
MANIFEST (crate version — SPDX license — source)
@@ -2930,6 +2931,25 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+----------------------------------------------------------------------------
+The following license (bazzite.txt) applies to: Bazzite logo (vendored, assets/os-icons)
+----------------------------------------------------------------------------
+Bazzite — the `bazzite` mark in assets/os-icons/ is derived from the Bazzite logo in
+the Bazzite source repository (repo_content/Bazzite.svg).
+
+Copyright (c) Universal Blue (https://github.com/ublue-os/bazzite)
+
+Licensed under the Apache License, Version 2.0,
+https://www.apache.org/licenses/LICENSE-2.0.
+
+Modifications: the logo's "b" letterform was lifted out of the surrounding badge, the
+gradient and decorative overlays were dropped, and the path was translated and scaled
+into a 24x24 box with a monochrome fill (fill="currentColor").
+
+Brand icons are trademarks of their respective owners and are used for identification
+purposes only; their use does not imply endorsement.
+
+
----------------------------------------------------------------------------
The following license (LICENSE) applies to: bindgen 0.72.1
----------------------------------------------------------------------------
@@ -6283,8 +6303,8 @@ the following restrictions:
----------------------------------------------------------------------------
The following license (font-awesome-brands.txt) applies to: Font Awesome Free brand icons (vendored, assets/os-icons)
----------------------------------------------------------------------------
-Font Awesome Free — brand icons (windows, apple, linux, steam, ubuntu, fedora,
-opensuse in assets/os-icons/) are from Font Awesome Free.
+Font Awesome Free — brand icons (apple, linux, steam, ubuntu, fedora, opensuse in
+assets/os-icons/) are from Font Awesome Free.
Copyright (c) Fonticons, Inc. (https://fontawesome.com)
@@ -12997,8 +13017,9 @@ SOFTWARE.
----------------------------------------------------------------------------
The following license (simple-icons.txt) applies to: Simple Icons (vendored, assets/os-icons)
----------------------------------------------------------------------------
-Simple Icons — brand icons (arch, nixos, debian in assets/os-icons/) are from
-Simple Icons (https://simpleicons.org, https://github.com/simple-icons/simple-icons).
+Simple Icons — brand icons (arch, nixos, debian, cachyos, nobara in assets/os-icons/)
+are from Simple Icons
+(https://simpleicons.org, https://github.com/simple-icons/simple-icons).
The Simple Icons SVG path data is released under CC0 1.0 Universal (public domain
dedication), https://creativecommons.org/publicdomain/zero/1.0/ — no attribution
diff --git a/assets/os-icons/LICENSES/bazzite.txt b/assets/os-icons/LICENSES/bazzite.txt
new file mode 100644
index 00000000..05fd1a8b
--- /dev/null
+++ b/assets/os-icons/LICENSES/bazzite.txt
@@ -0,0 +1,14 @@
+Bazzite — the `bazzite` mark in assets/os-icons/ is derived from the Bazzite logo in
+the Bazzite source repository (repo_content/Bazzite.svg).
+
+Copyright (c) Universal Blue (https://github.com/ublue-os/bazzite)
+
+Licensed under the Apache License, Version 2.0,
+https://www.apache.org/licenses/LICENSE-2.0.
+
+Modifications: the logo's "b" letterform was lifted out of the surrounding badge, the
+gradient and decorative overlays were dropped, and the path was translated and scaled
+into a 24x24 box with a monochrome fill (fill="currentColor").
+
+Brand icons are trademarks of their respective owners and are used for identification
+purposes only; their use does not imply endorsement.
diff --git a/assets/os-icons/LICENSES/font-awesome-brands.txt b/assets/os-icons/LICENSES/font-awesome-brands.txt
index 5159bcf0..4092ec9d 100644
--- a/assets/os-icons/LICENSES/font-awesome-brands.txt
+++ b/assets/os-icons/LICENSES/font-awesome-brands.txt
@@ -1,5 +1,5 @@
-Font Awesome Free — brand icons (windows, apple, linux, steam, ubuntu, fedora,
-opensuse in assets/os-icons/) are from Font Awesome Free.
+Font Awesome Free — brand icons (apple, linux, steam, ubuntu, fedora, opensuse in
+assets/os-icons/) are from Font Awesome Free.
Copyright (c) Fonticons, Inc. (https://fontawesome.com)
diff --git a/assets/os-icons/LICENSES/simple-icons.txt b/assets/os-icons/LICENSES/simple-icons.txt
index 4b1cea40..71591e22 100644
--- a/assets/os-icons/LICENSES/simple-icons.txt
+++ b/assets/os-icons/LICENSES/simple-icons.txt
@@ -1,5 +1,6 @@
-Simple Icons — brand icons (arch, nixos, debian in assets/os-icons/) are from
-Simple Icons (https://simpleicons.org, https://github.com/simple-icons/simple-icons).
+Simple Icons — brand icons (arch, nixos, debian, cachyos, nobara in assets/os-icons/)
+are from Simple Icons
+(https://simpleicons.org, https://github.com/simple-icons/simple-icons).
The Simple Icons SVG path data is released under CC0 1.0 Universal (public domain
dedication), https://creativecommons.org/publicdomain/zero/1.0/ — no attribution
diff --git a/assets/os-icons/README.md b/assets/os-icons/README.md
index f1e34bd5..e118f08b 100644
--- a/assets/os-icons/README.md
+++ b/assets/os-icons/README.md
@@ -7,7 +7,7 @@ Android `ImageVector`s). One file per **icon token** of the host's OS-identity c
| token | mark | source |
|---|---|---|
-| `windows` | Windows | Font Awesome Free brands (CC BY 4.0) |
+| `windows` | Windows (the current four-pane mark, no perspective skew) | own geometry |
| `apple` | Apple (also `macos` via alias) | Font Awesome Free brands (CC BY 4.0) |
| `linux` | Tux | Font Awesome Free brands (CC BY 4.0) |
| `steam` | Steam (also `steamos` via alias) | Font Awesome Free brands (CC BY 4.0) |
@@ -17,14 +17,36 @@ Android `ImageVector`s). One file per **icon token** of the host's OS-identity c
| `arch` | Arch Linux | Simple Icons (CC0 1.0) |
| `nixos` | NixOS | Simple Icons (CC0 1.0) |
| `debian` | Debian | Simple Icons (CC0 1.0) |
+| `bazzite` | Bazzite | ublue-os/bazzite (Apache-2.0) |
+| `cachyos` | CachyOS | Simple Icons (CC0 1.0) |
+| `nobara` | Nobara | Simple Icons (CC0 1.0, slug `nobaralinux`) |
-Distros with no file here (Bazzite, CachyOS, Nobara, Pop!_OS, Mint, …) are intentional:
-the host advertises the full chain (`linux/fedora/bazzite`), and clients walk it
-most-specific-first, so they degrade to the family mark and finally to Tux.
+The last three are **distro leaves, not families**: a chain walks most-specific-first, so
+`linux/fedora/bazzite` would otherwise draw the Fedora mark. They earn their own art because
+"a Bazzite box" and "a Fedora box" are different machines to the person reading the card, and
+they are what this project's hosts actually run. Every other distro with no file here (Pop!_OS,
+Mint, …) still degrades to its family's mark and finally to Tux — that fallback is the design,
+not a gap.
-All files are monochrome (`fill="currentColor"`), original per-icon viewBoxes preserved.
-Licensing: attribution notices live in `LICENSES/` and are folded into
-`THIRD-PARTY-NOTICES.txt` by `scripts/gen-third-party-notices.py`. The marks are
-trademarks of their respective owners; they are used here nominatively — to *identify*
-the operating system a host runs, the standard practice in this ecosystem — and imply no
-affiliation or endorsement.
+Windows is the one mark drawn here rather than sourced: every icon set that ships a "Windows"
+brand glyph still carries the **Windows 8/10 flag with the perspective skew**, which reads as
+dated next to the flat four-pane mark Microsoft has used since Windows 11. Four equal squares
+(11.377 + 1.246 gap) is the current proportion.
+
+All files are monochrome (`fill="currentColor"`), original per-icon viewBoxes preserved. Because
+those viewBoxes are not all square, a client must letterbox rather than stretch — see the aspect
+note in `clients/android/.../components/OsIcons.kt`.
+
+## Regenerating the per-client derivatives
+
+`bash scripts/gen-os-icons.sh [token ...]` turns a master into the three baked forms (GTK
+symbolic SVG, Windows PNG, Apple template PDF) and prints the path data for the three clients
+that inline it (web console, Decky plugin, Android). Adding a **new** token also means adding it
+to each client's shipped-token list — the script prints that checklist too.
+
+## Licensing
+
+Attribution notices live in `LICENSES/` and are folded into `THIRD-PARTY-NOTICES.txt` by
+`scripts/gen-third-party-notices.py`. The marks are trademarks of their respective owners; they
+are used here nominatively — to *identify* the operating system a host runs, the standard
+practice in this ecosystem — and imply no affiliation or endorsement.
diff --git a/assets/os-icons/bazzite.svg b/assets/os-icons/bazzite.svg
new file mode 100644
index 00000000..167605e4
--- /dev/null
+++ b/assets/os-icons/bazzite.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/assets/os-icons/cachyos.svg b/assets/os-icons/cachyos.svg
new file mode 100644
index 00000000..ad6c25b4
--- /dev/null
+++ b/assets/os-icons/cachyos.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/assets/os-icons/nobara.svg b/assets/os-icons/nobara.svg
new file mode 100644
index 00000000..2ccda4fc
--- /dev/null
+++ b/assets/os-icons/nobara.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/assets/os-icons/windows.svg b/assets/os-icons/windows.svg
index 3899dc4e..37832cea 100644
--- a/assets/os-icons/windows.svg
+++ b/assets/os-icons/windows.svg
@@ -1,2 +1,2 @@
-
-
+
+
diff --git a/clients/android/app/src/main/kotlin/io/unom/punktfunk/components/OsIcons.kt b/clients/android/app/src/main/kotlin/io/unom/punktfunk/components/OsIcons.kt
index 6b81c824..c65aa238 100644
--- a/clients/android/app/src/main/kotlin/io/unom/punktfunk/components/OsIcons.kt
+++ b/clients/android/app/src/main/kotlin/io/unom/punktfunk/components/OsIcons.kt
@@ -6,6 +6,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.PathParser
import androidx.compose.ui.unit.dp
import io.unom.punktfunk.kit.discovery.osIconTokens
+import kotlin.math.max
/**
* The host card's OS marks, resolved from the host's OS-identity chain (mDNS `os` TXT,
@@ -14,18 +15,19 @@ import io.unom.punktfunk.kit.discovery.osIconTokens
* ship, so an unknown distro degrades to its family's mark and finally to Tux; null means
* "no icon", rendering the card exactly as before the field existed.
*
- * Path data is vendored from the assets/os-icons masters (Font Awesome Free brands
- * CC BY 4.0 + Simple Icons CC0 — provenance in that directory's README); Material ships
- * no brand icons. Hand-kept as raw SVG path strings (one line each) rather than
- * transcribed ImageVector DSL — [PathParser] builds the vector once, then it's cached.
+ * Path data is vendored from the assets/os-icons masters (per-mark provenance and licensing
+ * in that directory's README; `bash scripts/gen-os-icons.sh ` prints a master's
+ * viewport + path ready to paste); Material ships no brand icons. Hand-kept as raw SVG path
+ * strings (one line each) rather than transcribed ImageVector DSL — [PathParser] builds the
+ * vector once, then it's cached.
*/
private class OsGlyph(val viewportWidth: Float, val viewportHeight: Float, val d: String)
private val GLYPHS: Map = mapOf(
"windows" to OsGlyph(
- viewportWidth = 448f,
- viewportHeight = 512f,
- d = "M0 93.7l183.6-25.3v177.4H0V93.7zm0 324.6l183.6 25.3V268.4H0v149.9zm203.8 28L448 480V268.4H203.8v177.9zm0-380.6v180.1H448V32L203.8 65.7z",
+ viewportWidth = 24f,
+ viewportHeight = 24f,
+ d = "M0 0h11.377v11.377H0zm12.623 0H24v11.377H12.623zM0 12.623h11.377V24H0zm12.623 0H24V24H12.623z",
),
"apple" to OsGlyph(
viewportWidth = 384f,
@@ -72,8 +74,28 @@ private val GLYPHS: Map = mapOf(
viewportHeight = 512f,
d = "M471.08 102.66s-.3 18.3-.3 20.3c-9.1-3-74.4-24.1-135.7-26.3-51.9-1.8-122.8-4.3-223 57.3-19.4 12.4-73.9 46.1-99.6 109.7C7 277-.12 307 7 335.06a111 111 0 0 0 16.5 35.7c17.4 25 46.6 41.6 78.1 44.4 44.4 3.9 78.1-16 90-53.3 8.2-25.8 0-63.6-31.5-82.9-25.6-15.7-53.3-12.1-69.2-1.6-13.9 9.2-21.8 23.5-21.6 39.2.3 27.8 24.3 42.6 41.5 42.6a49 49 0 0 0 15.8-2.7c6.5-1.8 13.3-6.5 13.3-14.9 0-12.1-11.6-14.8-16.8-13.9-2.9.5-4.5 2-11.8 2.4-2-.2-12-3.1-12-14V316c.2-12.3 13.2-18 25.5-16.9 32.3 2.8 47.7 40.7 28.5 65.7-18.3 23.7-76.6 23.2-99.7-20.4-26-49.2 12.7-111.2 87-98.4 33.2 5.7 83.6 35.5 102.4 104.3h45.9c-5.7-17.6-8.9-68.3 42.7-68.3 56.7 0 63.9 39.9 79.8 68.3H460c-12.8-18.3-21.7-38.7-18.9-55.8 5.6-33.8 39.7-18.4 82.4-17.4 66.5.4 102.1-27 103.1-28 3.7-3.1 6.5-15.8 7-17.7 1.3-5.1-3.2-2.4-3.2-2.4-8.7 5.2-30.5 15.2-50.9 15.6-25.3.5-76.2-25.4-81.6-28.2-.3-.4.1 1.2-11-25.5 88.4 58.3 118.3 40.5 145.2 21.7.8-.6 4.3-2.9 3.6-5.7-13.8-48.1-22.4-62.7-34.5-69.6-37-21.6-125-34.7-129.2-35.3.1-.1-.9-.3-.9.7zm60.4 72.8a37.54 37.54 0 0 1 38.9-36.3c33.4 1.2 48.8 42.3 24.4 65.2-24.2 22.7-64.4 4.6-63.3-28.9zm38.6-25.3a26.27 26.27 0 1 0 25.4 27.2 26.19 26.19 0 0 0-25.4-27.2zm4.3 28.8c-15.4 0-15.4-15.6 0-15.6s15.4 15.64 0 15.64z",
),
+ // The gaming distros get their own mark rather than their family's: "a Bazzite box" and
+ // "a Fedora box" are different machines to the person reading the card.
+ "bazzite" to OsGlyph(
+ viewportWidth = 24f,
+ viewportHeight = 24f,
+ d = "M7.178 0h3.589v7.178h7.524c3.153 0 5.709 2.556 5.709 5.709 0 6.138-4.976 11.113-11.113 11.113-3.153 0-5.709-2.556-5.709-5.709V10.766H0v-3.589h7.178zm3.589 10.766v7.524c0 1.171.949 2.12 2.12 2.12 4.156 0 7.524-3.369 7.524-7.524 0-1.171-.949-2.12-2.12-2.12z",
+ ),
+ "cachyos" to OsGlyph(
+ viewportWidth = 24f,
+ viewportHeight = 24f,
+ d = "M5.301 2.646 0 11.771l5.541 9.583h11.486l2.904-5.017H8.102l-2.56-4.429L8.067 7.54h6.063l2.83-4.893ZM20.058 4.12a.748.748 0 0 0 0 1.496.748.748 0 0 0 0-1.496m-1.983 4.303a1.45 1.45 0 0 0 0 2.9 1.45 1.45 0 0 0 0-2.9m4.02 3.98a1.904 1.904 0 0 0 0 3.809 1.904 1.904 0 0 0 0-3.81",
+ ),
+ "nobara" to OsGlyph(
+ viewportWidth = 24f,
+ viewportHeight = 24f,
+ d = "M23.808 11.808v8.281a3.542 3.542 0 0 1-3.542 3.527h-.46a3.543 3.543 0 0 1-3.083-3.513v-7.282l3.543-1.013-3.66-1.045a4.724 4.724 0 0 0-9.33 1.045v2.362a2.362 2.362 0 0 0 2.362 2.362 3.543 3.543 0 0 1 3.543 3.542V24a3.539 3.539 0 0 0-3.542-3.542 3.537 3.537 0 0 0-3.063 1.76 3.54 3.54 0 0 1-2.382 1.398h-.46A3.542 3.542 0 0 1 .192 20.09V3.543a3.542 3.542 0 0 1 6.323-2.194A11.756 11.756 0 0 1 12 0c6.521 0 11.808 5.287 11.808 11.808zm-9.446 0A2.359 2.359 0 0 1 12 14.17a2.362 2.362 0 1 1 2.362-2.362z",
+ ),
)
+/** Longest edge of a built mark, in dp — the box callers size the [Icon] to. */
+private const val GLYPH_DP = 24f
+
private val built = mutableMapOf()
/** The mark for a chain, or null (no icon). Vectors build lazily and cache per token. */
@@ -82,11 +104,18 @@ fun resolveOsIcon(chain: String): ImageVector? =
GLYPHS[token]?.let { glyph -> built.getOrPut(token) { glyph.build(token) } }
}
-private fun OsGlyph.build(token: String): ImageVector =
- ImageVector.Builder(
+private fun OsGlyph.build(token: String): ImageVector {
+ // The intrinsic size has to carry the VIEWPORT'S ASPECT RATIO, not a fixed square:
+ // a VectorPainter maps the viewport onto the default size with independent x and y
+ // scales, so declaring a 448x512 mark as 24x24 dp stretches it horizontally — which is
+ // exactly how Tux and the Apple mark used to come out on a phone. Scaling the longest
+ // edge to GLYPH_DP instead keeps the ratio, and Icon() paints with ContentScale.Fit, so
+ // the mark letterboxes inside whatever box the caller sized us to.
+ val longest = max(viewportWidth, viewportHeight)
+ return ImageVector.Builder(
name = "OsIcon.$token",
- defaultWidth = 24.dp,
- defaultHeight = 24.dp,
+ defaultWidth = (GLYPH_DP * viewportWidth / longest).dp,
+ defaultHeight = (GLYPH_DP * viewportHeight / longest).dp,
viewportWidth = viewportWidth,
viewportHeight = viewportHeight,
).apply {
@@ -96,3 +125,4 @@ private fun OsGlyph.build(token: String): ImageVector =
fill = SolidColor(Color.Black),
)
}.build()
+}
diff --git a/clients/android/app/src/test/kotlin/io/unom/punktfunk/OsIconsTest.kt b/clients/android/app/src/test/kotlin/io/unom/punktfunk/OsIconsTest.kt
new file mode 100644
index 00000000..51f81446
--- /dev/null
+++ b/clients/android/app/src/test/kotlin/io/unom/punktfunk/OsIconsTest.kt
@@ -0,0 +1,72 @@
+package io.unom.punktfunk
+
+import io.unom.punktfunk.components.resolveOsIcon
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+/**
+ * Pure JVM test of the host card's OS marks (`components/OsIcons.kt`). Run:
+ * `./gradlew :app:testDebugUnitTest`.
+ *
+ * The aspect assertions are the point: a [androidx.compose.ui.graphics.vector.VectorPainter] maps
+ * the viewport onto the vector's default size with independent x and y scales, so a mark whose
+ * default size does not carry its viewport's ratio renders STRETCHED — silently, with no crash and
+ * no warning. That is exactly how Tux and the Apple mark used to look on a phone.
+ */
+class OsIconsTest {
+ /** `ImageVector.name` is "OsIcon." — the only handle on which mark got resolved. */
+ private fun markOf(chain: String) = resolveOsIcon(chain)?.name?.removePrefix("OsIcon.")
+
+ @Test
+ fun defaultSizeCarriesTheViewportAspect() {
+ for (chain in listOf("windows", "linux", "opensuse", "steam", "apple", "bazzite")) {
+ val v = resolveOsIcon(chain)
+ assertNotNull("no mark for $chain", v)
+ v!!
+ assertEquals(
+ "$chain default size must keep the viewport ratio",
+ v.viewportWidth / v.viewportHeight,
+ v.defaultWidth.value / v.defaultHeight.value,
+ 0.001f,
+ )
+ assertEquals(
+ "$chain longest edge must be the 24dp box",
+ 24f,
+ maxOf(v.defaultWidth.value, v.defaultHeight.value),
+ 0.001f,
+ )
+ }
+ }
+
+ @Test
+ fun tallMarkIsNarrowerThanItsBox() {
+ // Tux is 448x512, so a correct build is 21x24dp — 24x24 would be the stretched bug.
+ val tux = resolveOsIcon("linux")!!
+ assertEquals(21f, tux.defaultWidth.value, 0.001f)
+ assertEquals(24f, tux.defaultHeight.value, 0.001f)
+ }
+
+ @Test
+ fun gamingDistrosResolveToTheirOwnMark() {
+ // The whole reason these three ship art: without it they'd draw their family's mark.
+ assertEquals("bazzite", markOf("linux/fedora/bazzite"))
+ assertEquals("cachyos", markOf("linux/arch/cachyos"))
+ assertEquals("nobara", markOf("linux/rhel/nobara"))
+ }
+
+ @Test
+ fun unknownDistroStillDegradesThroughItsFamily() {
+ assertEquals("fedora", markOf("linux/fedora/somethingnew"))
+ assertEquals("linux", markOf("linux/frontier/chimera"))
+ assertEquals("steam", markOf("linux/arch/steamos")) // brand alias
+ assertEquals("apple", markOf("macos"))
+ }
+
+ @Test
+ fun noChainMeansNoMark() {
+ assertNull(resolveOsIcon(""))
+ assertNull(resolveOsIcon("!!!"))
+ }
+}
diff --git a/clients/apple/Package.swift b/clients/apple/Package.swift
index 24655d13..9379f13b 100644
--- a/clients/apple/Package.swift
+++ b/clients/apple/Package.swift
@@ -34,9 +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.
+ // The host cards' OS marks (template vector imagesets generated from the
+ // assets/os-icons masters by scripts/gen-os-icons.sh — per-mark provenance and
+ // licensing in that README). `.process` compiles the catalog; loaded via
+ // OsIcon.swift.
.process("Resources/OsIcons.xcassets"),
],
linkerSettings: [
diff --git a/clients/apple/Sources/PunktfunkKit/OsIcon.swift b/clients/apple/Sources/PunktfunkKit/OsIcon.swift
index b75df6eb..70bc65c9 100644
--- a/clients/apple/Sources/PunktfunkKit/OsIcon.swift
+++ b/clients/apple/Sources/PunktfunkKit/OsIcon.swift
@@ -1,17 +1,19 @@
// 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
+// (generated from the repo's assets/os-icons masters by scripts/gen-os-icons.sh —
+// per-mark provenance and licensing 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.
+/// The icon tokens this client ships art for: the families a chain can land on, plus the
+/// gaming distros that earn their own mark because "a Bazzite box" and "a Fedora box" are
+/// different machines to the person reading the card. A distro with no mark of its own
+/// still 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",
+ "opensuse", "bazzite", "cachyos", "nobara",
]
/// The mark for an OS-identity chain (`linux/fedora/bazzite`, ...), or nil — no view at
diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-bazzite.imageset/Contents.json b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-bazzite.imageset/Contents.json
new file mode 100644
index 00000000..13de051b
--- /dev/null
+++ b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-bazzite.imageset/Contents.json
@@ -0,0 +1,10 @@
+{
+ "images" : [
+ { "filename" : "bazzite.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-bazzite.imageset/bazzite.pdf b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-bazzite.imageset/bazzite.pdf
new file mode 100644
index 00000000..d6b499f9
Binary files /dev/null and b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-bazzite.imageset/bazzite.pdf differ
diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-cachyos.imageset/Contents.json b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-cachyos.imageset/Contents.json
new file mode 100644
index 00000000..c5444f3f
--- /dev/null
+++ b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-cachyos.imageset/Contents.json
@@ -0,0 +1,10 @@
+{
+ "images" : [
+ { "filename" : "cachyos.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-cachyos.imageset/cachyos.pdf b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-cachyos.imageset/cachyos.pdf
new file mode 100644
index 00000000..56ee16fb
Binary files /dev/null and b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-cachyos.imageset/cachyos.pdf differ
diff --git a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-nobara.imageset/Contents.json b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-nobara.imageset/Contents.json
new file mode 100644
index 00000000..7efd91c5
--- /dev/null
+++ b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-nobara.imageset/Contents.json
@@ -0,0 +1,10 @@
+{
+ "images" : [
+ { "filename" : "nobara.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-nobara.imageset/nobara.pdf b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-nobara.imageset/nobara.pdf
new file mode 100644
index 00000000..ee677315
Binary files /dev/null and b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-nobara.imageset/nobara.pdf differ
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
index 5511f150..2a6b14e0 100644
Binary files a/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-windows.imageset/windows.pdf and b/clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-windows.imageset/windows.pdf differ
diff --git a/clients/decky/src/os-icon.tsx b/clients/decky/src/os-icon.tsx
index 9ad34452..1a151e45 100644
--- a/clients/decky/src/os-icon.tsx
+++ b/clients/decky/src/os-icon.tsx
@@ -1,40 +1,82 @@
// The host row's OS mark, resolved from the host's OS-identity chain (mDNS `os` TXT /
// `--list-hosts` `os`, e.g. "linux/fedora/bazzite"): walk the chain most-specific-first and
-// take the first token react-icons has a brand mark for, so an unknown distro degrades to its
-// family's mark and finally to Tux. Mirrors pf-client-core's `os_icon_tokens` (aliases
-// macos→apple, steamos→steam); null when the chain is absent or entirely unknown — the row
-// then renders exactly as it did before the field existed.
+// take the first token we ship a mark for, so an unknown distro degrades to its family's
+// mark and finally to Tux. Mirrors pf-client-core's `os_icon_tokens` (aliases macos→apple,
+// steamos→steam); null when the chain is absent or entirely unknown — the row then renders
+// exactly as it did before the field existed.
+//
+// Path data is transcribed from the assets/os-icons masters rather than pulled from
+// react-icons: that package's Font Awesome 5 brand set still carries the perspective-skewed
+// Windows flag, and it has no Bazzite or CachyOS mark at all. `bash scripts/gen-os-icons.sh
+// ` prints a master's viewBox + path ready to paste here.
import { FC } from "react";
-import {
- FaApple,
- FaFedora,
- FaLinux,
- FaSteam,
- FaSuse,
- FaUbuntu,
- FaWindows,
-} from "react-icons/fa";
-import { SiArchlinux, SiDebian, SiNixos } from "react-icons/si";
-import { IconType } from "react-icons";
-const OS_ICONS: Record = {
- windows: FaWindows,
- apple: FaApple,
- macos: FaApple,
- linux: FaLinux,
- steam: FaSteam,
- steamos: FaSteam,
- ubuntu: FaUbuntu,
- fedora: FaFedora,
- opensuse: FaSuse,
- arch: SiArchlinux,
- debian: SiDebian,
- nixos: SiNixos,
+/** One monochrome brand mark: original per-icon viewBox, drawn in currentColor. */
+const OS_ICONS: Record = {
+ windows: {
+ viewBox: "0 0 24 24",
+ d: "M0 0h11.377v11.377H0zm12.623 0H24v11.377H12.623zM0 12.623h11.377V24H0zm12.623 0H24V24H12.623z",
+ },
+ apple: {
+ viewBox: "0 0 384 512",
+ d: "M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z",
+ },
+ linux: {
+ viewBox: "0 0 448 512",
+ d: "M220.8 123.3c1 .5 1.8 1.7 3 1.7 1.1 0 2.8-.4 2.9-1.5.2-1.4-1.9-2.3-3.2-2.9-1.7-.7-3.9-1-5.5-.1-.4.2-.8.7-.6 1.1.3 1.3 2.3 1.1 3.4 1.7zm-21.9 1.7c1.2 0 2-1.2 3-1.7 1.1-.6 3.1-.4 3.5-1.6.2-.4-.2-.9-.6-1.1-1.6-.9-3.8-.6-5.5.1-1.3.6-3.4 1.5-3.2 2.9.1 1 1.8 1.5 2.8 1.4zM420 403.8c-3.6-4-5.3-11.6-7.2-19.7-1.8-8.1-3.9-16.8-10.5-22.4-1.3-1.1-2.6-2.1-4-2.9-1.3-.8-2.7-1.5-4.1-2 9.2-27.3 5.6-54.5-3.7-79.1-11.4-30.1-31.3-56.4-46.5-74.4-17.1-21.5-33.7-41.9-33.4-72C311.1 85.4 315.7.1 234.8 0 132.4-.2 158 103.4 156.9 135.2c-1.7 23.4-6.4 41.8-22.5 64.7-18.9 22.5-45.5 58.8-58.1 96.7-6 17.9-8.8 36.1-6.2 53.3-6.5 5.8-11.4 14.7-16.6 20.2-4.2 4.3-10.3 5.9-17 8.3s-14 6-18.5 14.5c-2.1 3.9-2.8 8.1-2.8 12.4 0 3.9.6 7.9 1.2 11.8 1.2 8.1 2.5 15.7.8 20.8-5.2 14.4-5.9 24.4-2.2 31.7 3.8 7.3 11.4 10.5 20.1 12.3 17.3 3.6 40.8 2.7 59.3 12.5 19.8 10.4 39.9 14.1 55.9 10.4 11.6-2.6 21.1-9.6 25.9-20.2 12.5-.1 26.3-5.4 48.3-6.6 14.9-1.2 33.6 5.3 55.1 4.1.6 2.3 1.4 4.6 2.5 6.7v.1c8.3 16.7 23.8 24.3 40.3 23 16.6-1.3 34.1-11 48.3-27.9 13.6-16.4 36-23.2 50.9-32.2 7.4-4.5 13.4-10.1 13.9-18.3.4-8.2-4.4-17.3-15.5-29.7zM223.7 87.3c9.8-22.2 34.2-21.8 44-.4 6.5 14.2 3.6 30.9-4.3 40.4-1.6-.8-5.9-2.6-12.6-4.9 1.1-1.2 3.1-2.7 3.9-4.6 4.8-11.8-.2-27-9.1-27.3-7.3-.5-13.9 10.8-11.8 23-4.1-2-9.4-3.5-13-4.4-1-6.9-.3-14.6 2.9-21.8zM183 75.8c10.1 0 20.8 14.2 19.1 33.5-3.5 1-7.1 2.5-10.2 4.6 1.2-8.9-3.3-20.1-9.6-19.6-8.4.7-9.8 21.2-1.8 28.1 1 .8 1.9-.2-5.9 5.5-15.6-14.6-10.5-52.1 8.4-52.1zm-13.6 60.7c6.2-4.6 13.6-10 14.1-10.5 4.7-4.4 13.5-14.2 27.9-14.2 7.1 0 15.6 2.3 25.9 8.9 6.3 4.1 11.3 4.4 22.6 9.3 8.4 3.5 13.7 9.7 10.5 18.2-2.6 7.1-11 14.4-22.7 18.1-11.1 3.6-19.8 16-38.2 14.9-3.9-.2-7-1-9.6-2.1-8-3.5-12.2-10.4-20-15-8.6-4.8-13.2-10.4-14.7-15.3-1.4-4.9 0-9 4.2-12.3zm3.3 334c-2.7 35.1-43.9 34.4-75.3 18-29.9-15.8-68.6-6.5-76.5-21.9-2.4-4.7-2.4-12.7 2.6-26.4v-.2c2.4-7.6.6-16-.6-23.9-1.2-7.8-1.8-15 .9-20 3.5-6.7 8.5-9.1 14.8-11.3 10.3-3.7 11.8-3.4 19.6-9.9 5.5-5.7 9.5-12.9 14.3-18 5.1-5.5 10-8.1 17.7-6.9 8.1 1.2 15.1 6.8 21.9 16l19.6 35.6c9.5 19.9 43.1 48.4 41 68.9zm-1.4-25.9c-4.1-6.6-9.6-13.6-14.4-19.6 7.1 0 14.2-2.2 16.7-8.9 2.3-6.2 0-14.9-7.4-24.9-13.5-18.2-38.3-32.5-38.3-32.5-13.5-8.4-21.1-18.7-24.6-29.9s-3-23.3-.3-35.2c5.2-22.9 18.6-45.2 27.2-59.2 2.3-1.7.8 3.2-8.7 20.8-8.5 16.1-24.4 53.3-2.6 82.4.6-20.7 5.5-41.8 13.8-61.5 12-27.4 37.3-74.9 39.3-112.7 1.1.8 4.6 3.2 6.2 4.1 4.6 2.7 8.1 6.7 12.6 10.3 12.4 10 28.5 9.2 42.4 1.2 6.2-3.5 11.2-7.5 15.9-9 9.9-3.1 17.8-8.6 22.3-15 7.7 30.4 25.7 74.3 37.2 95.7 6.1 11.4 18.3 35.5 23.6 64.6 3.3-.1 7 .4 10.9 1.4 13.8-35.7-11.7-74.2-23.3-84.9-4.7-4.6-4.9-6.6-2.6-6.5 12.6 11.2 29.2 33.7 35.2 59 2.8 11.6 3.3 23.7.4 35.7 16.4 6.8 35.9 17.9 30.7 34.8-2.2-.1-3.2 0-4.2 0 3.2-10.1-3.9-17.6-22.8-26.1-19.6-8.6-36-8.6-38.3 12.5-12.1 4.2-18.3 14.7-21.4 27.3-2.8 11.2-3.6 24.7-4.4 39.9-.5 7.7-3.6 18-6.8 29-32.1 22.9-76.7 32.9-114.3 7.2zm257.4-11.5c-.9 16.8-41.2 19.9-63.2 46.5-13.2 15.7-29.4 24.4-43.6 25.5s-26.5-4.8-33.7-19.3c-4.7-11.1-2.4-23.1 1.1-36.3 3.7-14.2 9.2-28.8 9.9-40.6.8-15.2 1.7-28.5 4.2-38.7 2.6-10.3 6.6-17.2 13.7-21.1.3-.2.7-.3 1-.5.8 13.2 7.3 26.6 18.8 29.5 12.6 3.3 30.7-7.5 38.4-16.3 9-.3 15.7-.9 22.6 5.1 9.9 8.5 7.1 30.3 17.1 41.6 10.6 11.6 14 19.5 13.7 24.6zM173.3 148.7c2 1.9 4.7 4.5 8 7.1 6.6 5.2 15.8 10.6 27.3 10.6 11.6 0 22.5-5.9 31.8-10.8 4.9-2.6 10.9-7 14.8-10.4s5.9-6.3 3.1-6.6-2.6 2.6-6 5.1c-4.4 3.2-9.7 7.4-13.9 9.8-7.4 4.2-19.5 10.2-29.9 10.2s-18.7-4.8-24.9-9.7c-3.1-2.5-5.7-5-7.7-6.9-1.5-1.4-1.9-4.6-4.3-4.9-1.4-.1-1.8 3.7 1.7 6.5z",
+ },
+ steam: {
+ viewBox: "0 0 496 512",
+ d: "M496 256c0 137-111.2 248-248.4 248-113.8 0-209.6-76.3-239-180.4l95.2 39.3c6.4 32.1 34.9 56.4 68.9 56.4 39.2 0 71.9-32.4 70.2-73.5l84.5-60.2c52.1 1.3 95.8-40.9 95.8-93.5 0-51.6-42-93.5-93.7-93.5s-93.7 42-93.7 93.5v1.2L176.6 279c-15.5-.9-30.7 3.4-43.5 12.1L0 236.1C10.2 108.4 117.1 8 247.6 8 384.8 8 496 119 496 256zM155.7 384.3l-30.5-12.6a52.79 52.79 0 0 0 27.2 25.8c26.9 11.2 57.8-1.6 69-28.4 5.4-13 5.5-27.3.1-40.3-5.4-13-15.5-23.2-28.5-28.6-12.9-5.4-26.7-5.2-38.9-.6l31.5 13c19.8 8.2 29.2 30.9 20.9 50.7-8.3 19.9-31 29.2-50.8 21zm173.8-129.9c-34.4 0-62.4-28-62.4-62.3s28-62.3 62.4-62.3 62.4 28 62.4 62.3-27.9 62.3-62.4 62.3zm.1-15.6c25.9 0 46.9-21 46.9-46.8 0-25.9-21-46.8-46.9-46.8s-46.9 21-46.9 46.8c.1 25.8 21.1 46.8 46.9 46.8z",
+ },
+ ubuntu: {
+ viewBox: "0 0 496 512",
+ d: "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm52.7 93c8.8-15.2 28.3-20.5 43.5-11.7 15.3 8.8 20.5 28.3 11.7 43.6-8.8 15.2-28.3 20.5-43.5 11.7-15.3-8.9-20.5-28.4-11.7-43.6zM87.4 287.9c-17.6 0-31.9-14.3-31.9-31.9 0-17.6 14.3-31.9 31.9-31.9 17.6 0 31.9 14.3 31.9 31.9 0 17.6-14.3 31.9-31.9 31.9zm28.1 3.1c22.3-17.9 22.4-51.9 0-69.9 8.6-32.8 29.1-60.7 56.5-79.1l23.7 39.6c-51.5 36.3-51.5 112.5 0 148.8L172 370c-27.4-18.3-47.8-46.3-56.5-79zm228.7 131.7c-15.3 8.8-34.7 3.6-43.5-11.7-8.8-15.3-3.6-34.8 11.7-43.6 15.2-8.8 34.7-3.6 43.5 11.7 8.8 15.3 3.6 34.8-11.7 43.6zm.3-69.5c-26.7-10.3-56.1 6.6-60.5 35-5.2 1.4-48.9 14.3-96.7-9.4l22.5-40.3c57 26.5 123.4-11.7 128.9-74.4l46.1.7c-2.3 34.5-17.3 65.5-40.3 88.4zm-5.9-105.3c-5.4-62-71.3-101.2-128.9-74.4l-22.5-40.3c47.9-23.7 91.5-10.8 96.7-9.4 4.4 28.3 33.8 45.3 60.5 35 23.1 22.9 38 53.9 40.2 88.5l-46 .6z",
+ },
+ fedora: {
+ viewBox: "0 0 448 512",
+ d: "M225 32C101.3 31.7.8 131.7.4 255.4L0 425.7a53.6 53.6 0 0 0 53.6 53.9l170.2.4c123.7.3 224.3-99.7 224.6-223.4S348.7 32.3 225 32zm169.8 157.2L333 126.6c2.3-4.7 3.8-9.2 3.8-14.3v-1.6l55.2 56.1a101 101 0 0 1 2.8 22.4zM331 94.3a106.06 106.06 0 0 1 58.5 63.8l-54.3-54.6a26.48 26.48 0 0 0-4.2-9.2zM118.1 247.2a49.66 49.66 0 0 0-7.7 11.4l-8.5-8.5a85.78 85.78 0 0 1 16.2-2.9zM97 251.4l11.8 11.9-.9 8a34.74 34.74 0 0 0 2.4 12.5l-27-27.2a80.6 80.6 0 0 1 13.7-5.2zm-18.2 7.4l38.2 38.4a53.17 53.17 0 0 0-14.1 4.7L67.6 266a107 107 0 0 1 11.2-7.2zm-15.2 9.8l35.3 35.5a67.25 67.25 0 0 0-10.5 8.5L53.5 278a64.33 64.33 0 0 1 10.1-9.4zm-13.3 12.3l34.9 35a56.84 56.84 0 0 0-7.7 11.4l-35.8-35.9c2.8-3.8 5.7-7.2 8.6-10.5zm-11 14.3l36.4 36.6a48.29 48.29 0 0 0-3.6 15.2l-39.5-39.8a99.81 99.81 0 0 1 6.7-12zm-8.8 16.3l41.3 41.8a63.47 63.47 0 0 0 6.7 26.2L25.8 326c1.4-4.9 2.9-9.6 4.7-14.5zm-7.9 43l61.9 62.2a31.24 31.24 0 0 0-3.6 14.3v1.1l-55.4-55.7a88.27 88.27 0 0 1-2.9-21.9zm5.3 30.7l54.3 54.6a28.44 28.44 0 0 0 4.2 9.2 106.32 106.32 0 0 1-58.5-63.8zm-5.3-37a80.69 80.69 0 0 1 2.1-17l72.2 72.5a37.59 37.59 0 0 0-9.9 8.7zm253.3-51.8l-42.6-.1-.1 56c-.2 69.3-64.4 115.8-125.7 102.9-5.7 0-19.9-8.7-19.9-24.2a24.89 24.89 0 0 1 24.5-24.6c6.3 0 6.3 1.6 15.7 1.6a55.91 55.91 0 0 0 56.1-55.9l.1-47c0-4.5-4.5-9-8.9-9l-33.6-.1c-32.6-.1-32.5-49.4.1-49.3l42.6.1.1-56a105.18 105.18 0 0 1 105.6-105 86.35 86.35 0 0 1 20.2 2.3c11.2 1.8 19.9 11.9 19.9 24 0 15.5-14.9 27.8-30.3 23.9-27.4-5.9-65.9 14.4-66 54.9l-.1 47a8.94 8.94 0 0 0 8.9 9l33.6.1c32.5.2 32.4 49.5-.2 49.4zm23.5-.3a35.58 35.58 0 0 0 7.6-11.4l8.5 8.5a102 102 0 0 1-16.1 2.9zm21-4.2L308.6 280l.9-8.1a34.74 34.74 0 0 0-2.4-12.5l27 27.2a74.89 74.89 0 0 1-13.7 5.3zm18-7.4l-38-38.4c4.9-1.1 9.6-2.4 13.7-4.7l36.2 35.9c-3.8 2.5-7.9 5-11.9 7.2zm15.5-9.8l-35.3-35.5a61.06 61.06 0 0 0 10.5-8.5l34.9 35a124.56 124.56 0 0 1-10.1 9zm13.2-12.3l-34.9-35a63.18 63.18 0 0 0 7.7-11.4l35.8 35.9a130.28 130.28 0 0 1-8.6 10.5zm11-14.3l-36.4-36.6a48.29 48.29 0 0 0 3.6-15.2l39.5 39.8a87.72 87.72 0 0 1-6.7 12zm13.5-30.9a140.63 140.63 0 0 1-4.7 14.3L345.6 190a58.19 58.19 0 0 0-7.1-26.2zm1-5.6l-71.9-72.1a32 32 0 0 0 9.9-9.2l64.3 64.7a90.93 90.93 0 0 1-2.3 16.6z",
+ },
+ arch: {
+ viewBox: "0 0 24 24",
+ d: "M11.39.605C10.376 3.092 9.764 4.72 8.635 7.132c.693.734 1.543 1.589 2.923 2.554-1.484-.61-2.496-1.224-3.252-1.86C6.86 10.842 4.596 15.138 0 23.395c3.612-2.085 6.412-3.37 9.021-3.862a6.61 6.61 0 01-.171-1.547l.003-.115c.058-2.315 1.261-4.095 2.687-3.973 1.426.12 2.534 2.096 2.478 4.409a6.52 6.52 0 01-.146 1.243c2.58.505 5.352 1.787 8.914 3.844-.702-1.293-1.33-2.459-1.929-3.57-.943-.73-1.926-1.682-3.933-2.713 1.38.359 2.367.772 3.137 1.234-6.09-11.334-6.582-12.84-8.67-17.74zM22.898 21.36v-.623h-.234v-.084h.562v.084h-.234v.623h.331v-.707h.142l.167.5.034.107a2.26 2.26 0 01.038-.114l.17-.493H24v.707h-.091v-.593l-.206.593h-.084l-.205-.602v.602h-.091",
+ },
+ debian: {
+ viewBox: "0 0 24 24",
+ d: "M13.88 12.685c-.4 0 .08.2.601.28.14-.1.27-.22.39-.33a3.001 3.001 0 01-.99.05m2.14-.53c.23-.33.4-.69.47-1.06-.06.27-.2.5-.33.73-.75.47-.07-.27 0-.56-.8 1.01-.11.6-.14.89m.781-2.05c.05-.721-.14-.501-.2-.221.07.04.13.5.2.22M12.38.31c.2.04.45.07.42.12.23-.05.28-.1-.43-.12m.43.12l-.15.03.14-.01V.43m6.633 9.944c.02.64-.2.95-.38 1.5l-.35.181c-.28.54.03.35-.17.78-.44.39-1.34 1.22-1.62 1.301-.201 0 .14-.25.19-.34-.591.4-.481.6-1.371.85l-.03-.06c-2.221 1.04-5.303-1.02-5.253-3.842-.03.17-.07.13-.12.2a3.551 3.552 0 012.001-3.501 3.361 3.362 0 013.732.48 3.341 3.342 0 00-2.721-1.3c-1.18.01-2.281.76-2.651 1.57-.6.38-.67 1.47-.93 1.661-.361 2.601.66 3.722 2.38 5.042.27.19.08.21.12.35a4.702 4.702 0 01-1.53-1.16c.23.33.47.66.8.91-.55-.18-1.27-1.3-1.48-1.35.93 1.66 3.78 2.921 5.261 2.3a6.203 6.203 0 01-2.33-.28c-.33-.16-.77-.51-.7-.57a5.802 5.803 0 005.902-.84c.44-.35.93-.94 1.07-.95-.2.32.04.16-.12.44.44-.72-.2-.3.46-1.24l.24.33c-.09-.6.74-1.321.66-2.262.19-.3.2.3 0 .97.29-.74.08-.85.15-1.46.08.2.18.42.23.63-.18-.7.2-1.2.28-1.6-.09-.05-.28.3-.32-.53 0-.37.1-.2.14-.28-.08-.05-.26-.32-.38-.861.08-.13.22.33.34.34-.08-.42-.2-.75-.2-1.08-.34-.68-.12.1-.4-.3-.34-1.091.3-.25.34-.74.54.77.84 1.96.981 2.46-.1-.6-.28-1.2-.49-1.76.16.07-.26-1.241.21-.37A7.823 7.824 0 0017.702 1.6c.18.17.42.39.33.42-.75-.45-.62-.48-.73-.67-.61-.25-.65.02-1.06 0C15.082.73 14.862.8 13.8.4l.05.23c-.77-.25-.9.1-1.73 0-.05-.04.27-.14.53-.18-.741.1-.701-.14-1.431.03.17-.13.36-.21.55-.32-.6.04-1.44.35-1.18.07C9.6.68 7.847 1.3 6.867 2.22L6.838 2c-.45.54-1.96 1.611-2.08 2.311l-.131.03c-.23.4-.38.85-.57 1.261-.3.52-.45.2-.4.28-.6 1.22-.9 2.251-1.16 3.102.18.27 0 1.65.07 2.76-.3 5.463 3.84 10.776 8.363 12.006.67.23 1.65.23 2.49.25-.99-.28-1.12-.15-2.08-.49-.7-.32-.85-.7-1.34-1.13l.2.35c-.971-.34-.57-.42-1.361-.67l.21-.27c-.31-.03-.83-.53-.97-.81l-.34.01c-.41-.501-.63-.871-.61-1.161l-.111.2c-.13-.21-1.52-1.901-.8-1.511-.13-.12-.31-.2-.5-.55l.14-.17c-.35-.44-.64-1.02-.62-1.2.2.24.32.3.45.33-.88-2.172-.93-.12-1.601-2.202l.15-.02c-.1-.16-.18-.34-.26-.51l.06-.6c-.63-.74-.18-3.102-.09-4.402.07-.54.53-1.1.88-1.981l-.21-.04c.4-.71 2.341-2.872 3.241-2.761.43-.55-.09 0-.18-.14.96-.991 1.26-.7 1.901-.88.7-.401-.6.16-.27-.151 1.2-.3.85-.7 2.421-.85.16.1-.39.14-.52.26 1-.49 3.151-.37 4.562.27 1.63.77 3.461 3.011 3.531 5.132l.08.02c-.04.85.13 1.821-.17 2.711l.2-.42M9.54 13.236l-.05.28c.26.35.47.73.8 1.01-.24-.47-.42-.66-.75-1.3m.62-.02c-.14-.15-.22-.34-.31-.52.08.32.26.6.43.88l-.12-.36m10.945-2.382l-.07.15c-.1.76-.34 1.511-.69 2.212.4-.73.65-1.541.75-2.362M12.45.12c.27-.1.66-.05.95-.12-.37.03-.74.05-1.1.1l.15.02M3.006 5.142c.07.57-.43.8.11.42.3-.66-.11-.18-.1-.42m-.64 2.661c.12-.39.15-.62.2-.84-.35.44-.17.53-.2.83",
+ },
+ nixos: {
+ viewBox: "0 0 24 24",
+ d: "M7.352 1.592l-1.364.002L5.32 2.75l1.557 2.713-3.137-.008-1.32 2.34H14.11l-1.353-2.332-3.192-.006-2.214-3.865zm6.175 0l-2.687.025 5.846 10.127 1.341-2.34-1.59-2.765 2.24-3.85-.683-1.182h-1.336l-1.57 2.705-1.56-2.72zm6.887 4.195l-5.846 10.125 2.696-.008 1.601-2.76 4.453.016.682-1.183-.666-1.157-3.13-.008L21.778 8.1l-1.365-2.313zM9.432 8.086l-2.696.008-1.601 2.76-4.453-.016L0 12.02l.666 1.157 3.13.008-1.575 2.71 1.365 2.315L9.432 8.086zM7.33 12.25l-.006.01-.002-.004-1.342 2.34 1.59 2.765-2.24 3.85.684 1.182H7.35l.004-.006h.001l1.567-2.698 1.558 2.72 2.688-.026-.004-.006h.01L7.33 12.25zm2.55 3.93l1.354 2.332 3.192.006 2.215 3.865 1.363-.002.668-1.156-1.557-2.713 3.137.008 1.32-2.34H9.881Z",
+ },
+ opensuse: {
+ viewBox: "0 0 640 512",
+ d: "M471.08 102.66s-.3 18.3-.3 20.3c-9.1-3-74.4-24.1-135.7-26.3-51.9-1.8-122.8-4.3-223 57.3-19.4 12.4-73.9 46.1-99.6 109.7C7 277-.12 307 7 335.06a111 111 0 0 0 16.5 35.7c17.4 25 46.6 41.6 78.1 44.4 44.4 3.9 78.1-16 90-53.3 8.2-25.8 0-63.6-31.5-82.9-25.6-15.7-53.3-12.1-69.2-1.6-13.9 9.2-21.8 23.5-21.6 39.2.3 27.8 24.3 42.6 41.5 42.6a49 49 0 0 0 15.8-2.7c6.5-1.8 13.3-6.5 13.3-14.9 0-12.1-11.6-14.8-16.8-13.9-2.9.5-4.5 2-11.8 2.4-2-.2-12-3.1-12-14V316c.2-12.3 13.2-18 25.5-16.9 32.3 2.8 47.7 40.7 28.5 65.7-18.3 23.7-76.6 23.2-99.7-20.4-26-49.2 12.7-111.2 87-98.4 33.2 5.7 83.6 35.5 102.4 104.3h45.9c-5.7-17.6-8.9-68.3 42.7-68.3 56.7 0 63.9 39.9 79.8 68.3H460c-12.8-18.3-21.7-38.7-18.9-55.8 5.6-33.8 39.7-18.4 82.4-17.4 66.5.4 102.1-27 103.1-28 3.7-3.1 6.5-15.8 7-17.7 1.3-5.1-3.2-2.4-3.2-2.4-8.7 5.2-30.5 15.2-50.9 15.6-25.3.5-76.2-25.4-81.6-28.2-.3-.4.1 1.2-11-25.5 88.4 58.3 118.3 40.5 145.2 21.7.8-.6 4.3-2.9 3.6-5.7-13.8-48.1-22.4-62.7-34.5-69.6-37-21.6-125-34.7-129.2-35.3.1-.1-.9-.3-.9.7zm60.4 72.8a37.54 37.54 0 0 1 38.9-36.3c33.4 1.2 48.8 42.3 24.4 65.2-24.2 22.7-64.4 4.6-63.3-28.9zm38.6-25.3a26.27 26.27 0 1 0 25.4 27.2 26.19 26.19 0 0 0-25.4-27.2zm4.3 28.8c-15.4 0-15.4-15.6 0-15.6s15.4 15.64 0 15.64z",
+ },
+ // The gaming distros get their own mark rather than their family's: on a Deck, "a Bazzite
+ // box" and "a Fedora box" are different machines to the person reading the row.
+ bazzite: {
+ viewBox: "0 0 24 24",
+ d: "M7.178 0h3.589v7.178h7.524c3.153 0 5.709 2.556 5.709 5.709 0 6.138-4.976 11.113-11.113 11.113-3.153 0-5.709-2.556-5.709-5.709V10.766H0v-3.589h7.178zm3.589 10.766v7.524c0 1.171.949 2.12 2.12 2.12 4.156 0 7.524-3.369 7.524-7.524 0-1.171-.949-2.12-2.12-2.12z",
+ },
+ cachyos: {
+ viewBox: "0 0 24 24",
+ d: "M5.301 2.646 0 11.771l5.541 9.583h11.486l2.904-5.017H8.102l-2.56-4.429L8.067 7.54h6.063l2.83-4.893ZM20.058 4.12a.748.748 0 0 0 0 1.496.748.748 0 0 0 0-1.496m-1.983 4.303a1.45 1.45 0 0 0 0 2.9 1.45 1.45 0 0 0 0-2.9m4.02 3.98a1.904 1.904 0 0 0 0 3.809 1.904 1.904 0 0 0 0-3.81",
+ },
+ nobara: {
+ viewBox: "0 0 24 24",
+ d: "M23.808 11.808v8.281a3.542 3.542 0 0 1-3.542 3.527h-.46a3.543 3.543 0 0 1-3.083-3.513v-7.282l3.543-1.013-3.66-1.045a4.724 4.724 0 0 0-9.33 1.045v2.362a2.362 2.362 0 0 0 2.362 2.362 3.543 3.543 0 0 1 3.543 3.542V24a3.539 3.539 0 0 0-3.542-3.542 3.537 3.537 0 0 0-3.063 1.76 3.54 3.54 0 0 1-2.382 1.398h-.46A3.542 3.542 0 0 1 .192 20.09V3.543a3.542 3.542 0 0 1 6.323-2.194A11.756 11.756 0 0 1 12 0c6.521 0 11.808 5.287 11.808 11.808zm-9.446 0A2.359 2.359 0 0 1 12 14.17a2.362 2.362 0 1 1 2.362-2.362z",
+ },
};
-export function resolveOsIcon(os: string | undefined): IconType | null {
- for (const token of (os ?? "").toLowerCase().split("/").reverse()) {
- const icon = OS_ICONS[token];
+/** `macos` renders the apple mark, `steamos` the steam mark — same aliases as every client. */
+const ALIASES: Record = { macos: "apple", steamos: "steam" };
+
+export function resolveOsIcon(
+ os: string | undefined,
+): { viewBox: string; d: string } | null {
+ for (const raw of (os ?? "").toLowerCase().split("/").reverse()) {
+ const icon = OS_ICONS[ALIASES[raw] ?? raw];
if (icon) {
return icon;
}
@@ -44,6 +86,20 @@ export function resolveOsIcon(os: string | undefined): IconType | null {
/** The mark itself, or nothing — sized/colored by the surrounding text like the lock glyph. */
export const OsMark: FC<{ os?: string }> = ({ os }) => {
- const Icon = resolveOsIcon(os);
- return Icon ? : null;
+ const icon = resolveOsIcon(os);
+ if (!icon) return null;
+ // 1em square with the viewBox letterboxed inside it: the same box react-icons drew, so a
+ // non-square mark keeps its aspect ratio instead of being stretched to fill.
+ return (
+
+ );
};
diff --git a/clients/linux/data/icons/scalable/actions/pf-os-bazzite-symbolic.svg b/clients/linux/data/icons/scalable/actions/pf-os-bazzite-symbolic.svg
new file mode 100644
index 00000000..5294d57d
--- /dev/null
+++ b/clients/linux/data/icons/scalable/actions/pf-os-bazzite-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/clients/linux/data/icons/scalable/actions/pf-os-cachyos-symbolic.svg b/clients/linux/data/icons/scalable/actions/pf-os-cachyos-symbolic.svg
new file mode 100644
index 00000000..b2d14159
--- /dev/null
+++ b/clients/linux/data/icons/scalable/actions/pf-os-cachyos-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/clients/linux/data/icons/scalable/actions/pf-os-nobara-symbolic.svg b/clients/linux/data/icons/scalable/actions/pf-os-nobara-symbolic.svg
new file mode 100644
index 00000000..2d751dee
--- /dev/null
+++ b/clients/linux/data/icons/scalable/actions/pf-os-nobara-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/clients/linux/data/icons/scalable/actions/pf-os-windows-symbolic.svg b/clients/linux/data/icons/scalable/actions/pf-os-windows-symbolic.svg
index f7f972bf..f8b2d1e1 100644
--- a/clients/linux/data/icons/scalable/actions/pf-os-windows-symbolic.svg
+++ b/clients/linux/data/icons/scalable/actions/pf-os-windows-symbolic.svg
@@ -1,2 +1,2 @@
-
-
+
+
diff --git a/clients/linux/data/resources.gresource.xml b/clients/linux/data/resources.gresource.xml
index 660e71e4..ff0944dd 100644
--- a/clients/linux/data/resources.gresource.xml
+++ b/clients/linux/data/resources.gresource.xml
@@ -14,5 +14,8 @@
icons/scalable/actions/pf-os-debian-symbolic.svg
icons/scalable/actions/pf-os-nixos-symbolic.svg
icons/scalable/actions/pf-os-opensuse-symbolic.svg
+ icons/scalable/actions/pf-os-bazzite-symbolic.svg
+ icons/scalable/actions/pf-os-cachyos-symbolic.svg
+ icons/scalable/actions/pf-os-nobara-symbolic.svg
diff --git a/clients/linux/src/ui_hosts.rs b/clients/linux/src/ui_hosts.rs
index ac538172..b1da11fd 100644
--- a/clients/linux/src/ui_hosts.rs
+++ b/clients/linux/src/ui_hosts.rs
@@ -583,10 +583,13 @@ const PROBE_INTERVAL: std::time::Duration = std::time::Duration::from_secs(12);
/// at a glance is the whole reason the chip exists. No colour set keeps the neutral pill, so
/// the palette stays opt-in.
/// The OS-icon tokens this shell ships symbolic art for (`data/icons/.../pf-os--symbolic.svg`,
-/// embedded via gresource). Chains walk most-specific-first, so a distro without its own mark
-/// (Bazzite, CachyOS, ...) lands on its family's and finally on plain Tux.
+/// embedded via gresource): the families a chain can land on, plus the gaming distros that earn
+/// their own mark because "a Bazzite box" and "a Fedora box" are different machines to the person
+/// reading the card. Chains walk most-specific-first, so a distro without a mark of its own still
+/// lands on its family's and finally on plain Tux.
const OS_ICON_TOKENS: &[&str] = &[
- "windows", "apple", "linux", "steam", "ubuntu", "fedora", "arch", "debian", "nixos", "opensuse",
+ "windows", "apple", "linux", "steam", "ubuntu", "fedora", "arch", "debian", "nixos",
+ "opensuse", "bazzite", "cachyos", "nobara",
];
/// The card's OS glyph for an advertised chain, or `None` (no widget) when the host doesn't
diff --git a/clients/windows/assets/os/bazzite.png b/clients/windows/assets/os/bazzite.png
new file mode 100644
index 00000000..bc9f2f83
Binary files /dev/null and b/clients/windows/assets/os/bazzite.png differ
diff --git a/clients/windows/assets/os/cachyos.png b/clients/windows/assets/os/cachyos.png
new file mode 100644
index 00000000..163bb893
Binary files /dev/null and b/clients/windows/assets/os/cachyos.png differ
diff --git a/clients/windows/assets/os/nobara.png b/clients/windows/assets/os/nobara.png
new file mode 100644
index 00000000..88b70942
Binary files /dev/null and b/clients/windows/assets/os/nobara.png differ
diff --git a/clients/windows/assets/os/windows.png b/clients/windows/assets/os/windows.png
index 3da00a82..8a661e62 100644
Binary files a/clients/windows/assets/os/windows.png and b/clients/windows/assets/os/windows.png differ
diff --git a/clients/windows/src/app/os_icons.rs b/clients/windows/src/app/os_icons.rs
index 9a3390ff..139dabcb 100644
--- a/clients/windows/src/app/os_icons.rs
+++ b/clients/windows/src/app/os_icons.rs
@@ -8,8 +8,10 @@
use std::path::PathBuf;
use std::sync::OnceLock;
-/// Embedded PNG per icon token, most-generic set the chain walk can land on. A distro
-/// without its own mark (Bazzite, CachyOS, ...) degrades to its family's and finally Tux.
+/// Embedded PNG per icon token: the families the chain walk can land on, plus the gaming
+/// distros that earn their own mark because "a Bazzite box" and "a Fedora box" are
+/// different machines to the person reading the tile. A distro with no mark of its own
+/// still degrades to its family's and finally to Tux.
const ICONS: &[(&str, &[u8])] = &[
("windows", include_bytes!("../../assets/os/windows.png")),
("apple", include_bytes!("../../assets/os/apple.png")),
@@ -21,6 +23,9 @@ const ICONS: &[(&str, &[u8])] = &[
("debian", include_bytes!("../../assets/os/debian.png")),
("nixos", include_bytes!("../../assets/os/nixos.png")),
("opensuse", include_bytes!("../../assets/os/opensuse.png")),
+ ("bazzite", include_bytes!("../../assets/os/bazzite.png")),
+ ("cachyos", include_bytes!("../../assets/os/cachyos.png")),
+ ("nobara", include_bytes!("../../assets/os/nobara.png")),
];
fn dir() -> Option {
diff --git a/crates/punktfunk-host/src/osinfo.rs b/crates/punktfunk-host/src/osinfo.rs
index 67ef876a..50f7e688 100644
--- a/crates/punktfunk-host/src/osinfo.rs
+++ b/crates/punktfunk-host/src/osinfo.rs
@@ -183,6 +183,19 @@ mod tests {
assert_eq!(chain, "linux/arch/cachyos");
}
+ #[test]
+ fn nobara_takes_rhel_the_first_recognized_ancestor() {
+ // Verified against a real Nobara 44 box: it declares `ID_LIKE="rhel centos fedora"`,
+ // so the family token is *rhel*, not the fedora one might expect. Clients ship a
+ // `nobara` mark, so the leaf matches before the family ever gets a say — but a
+ // RHEL-family distro we have no art for lands on plain Tux, not on Fedora.
+ let (chain, pretty) = parsed(
+ "NAME=\"Nobara Linux\"\nID=nobara\nID_LIKE=\"rhel centos fedora\"\nPRETTY_NAME=\"Nobara Linux 44 (KDE Plasma Desktop Edition)\"\n",
+ );
+ assert_eq!(chain, "linux/rhel/nobara");
+ assert_eq!(pretty, "Nobara Linux 44 (KDE Plasma Desktop Edition)");
+ }
+
#[test]
fn nixos_without_id_like_is_two_segments() {
let (chain, _) = parsed("ID=nixos\nPRETTY_NAME=\"NixOS 25.05 (Warbler)\"\n");
diff --git a/scripts/gen-os-icons.sh b/scripts/gen-os-icons.sh
new file mode 100755
index 00000000..ea6ffeba
--- /dev/null
+++ b/scripts/gen-os-icons.sh
@@ -0,0 +1,97 @@
+#!/usr/bin/env bash
+# Derive the per-client host-OS-icon assets from the assets/os-icons masters.
+#
+# The masters are monochrome `fill="currentColor"` SVGs, one per icon token of the host's
+# OS-identity chain (see assets/os-icons/README.md). Three clients need a baked derivative
+# because they cannot consume the master directly:
+#
+# GTK shell symbolic SVG, black fill -> clients/linux/data/icons/scalable/actions/
+# Windows shell PNG, h=32, mid-grey -> clients/windows/assets/os/
+# Apple clients vector PDF, black fill -> clients/apple/.../OsIcons.xcassets/
+#
+# The web console, the Decky plugin and the Android client transcribe the master's path
+# data inline instead — those are hand-kept, and this script prints them at the end so a
+# new token can be pasted straight in.
+#
+# Idempotent. Usage: bash scripts/gen-os-icons.sh [token ...] (default: every master)
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+
+MASTERS=assets/os-icons
+GTK=clients/linux/data/icons/scalable/actions
+WIN=clients/windows/assets/os
+APPLE=clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets
+
+# The Windows shell has no vector element and no theme-aware tint, so its PNGs are baked in
+# one mid-grey that stays legible on both the light and the dark WinUI theme.
+WIN_GREY='#8A8F98'
+WIN_HEIGHT=32
+
+log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
+
+command -v rsvg-convert >/dev/null 2>&1 || {
+ echo "rsvg-convert not found (brew install librsvg / apt install librsvg2-bin)" >&2
+ exit 1
+}
+
+tokens=("$@")
+if [ ${#tokens[@]} -eq 0 ]; then
+ for f in "$MASTERS"/*.svg; do tokens+=("$(basename "$f" .svg)"); done
+fi
+
+tmp=$(mktemp -d)
+trap 'rm -rf "$tmp"' EXIT
+
+for t in "${tokens[@]}"; do
+ src="$MASTERS/$t.svg"
+ [ -f "$src" ] || { echo "no master for token '$t' ($src)" >&2; exit 1; }
+ log "$t"
+
+ # GTK: the master with the fill resolved to black — Adwaita recolors a `-symbolic` icon
+ # from the fill it finds, so the value only has to be a real colour, not the final one.
+ sed 's/currentColor/#000000/' "$src" > "$GTK/pf-os-$t-symbolic.svg"
+
+ # Windows: same black-to-grey substitution, rasterized at a fixed height so every mark
+ # shares an optical size and keeps its own aspect ratio.
+ sed "s/currentColor/$WIN_GREY/" "$src" > "$tmp/$t.grey.svg"
+ rsvg-convert -h "$WIN_HEIGHT" -f png -o "$WIN/$t.png" "$tmp/$t.grey.svg"
+
+ # Apple: a vector PDF at the master's natural size, in a template imageset — SwiftUI
+ # tints it from foregroundStyle, so the baked colour is irrelevant.
+ sed 's/currentColor/#000000/' "$src" > "$tmp/$t.black.svg"
+ mkdir -p "$APPLE/os-$t.imageset"
+ rsvg-convert -f pdf -o "$APPLE/os-$t.imageset/$t.pdf" "$tmp/$t.black.svg"
+ cat > "$APPLE/os-$t.imageset/Contents.json" <]* d="([^"]+)"', svg).group(1)
+w, h = box.split()[2:]
+print(f'\n {sys.argv[2]}: viewBox "{box}" (viewport {w} x {h})\n {d}')
+PY
+done
+
+echo
+log "Remember: a NEW token also has to be added to each client's shipped-token list —"
+log " clients/linux/src/ui_hosts.rs, clients/linux/data/resources.gresource.xml,"
+log " clients/windows/src/app/os_icons.rs, clients/apple/.../PunktfunkKit/OsIcon.swift,"
+log " plus the three inline registries above."
diff --git a/scripts/gen-third-party-notices.py b/scripts/gen-third-party-notices.py
index 53a5fa4a..4f09e6f3 100755
--- a/scripts/gen-third-party-notices.py
+++ b/scripts/gen-third-party-notices.py
@@ -68,14 +68,17 @@ VENDORED_TREES = [
("Vulkan-Headers (vendored, crates/pyrowave-sys)",
"crates/pyrowave-sys/vendor/pyrowave/Granite/third_party/khronos/vulkan-headers/LICENSE.md",
"https://github.com/KhronosGroup/Vulkan-Headers"),
- # OS brand marks for the host-card OS icon (assets/os-icons/, CC BY 4.0 / CC0 —
- # see assets/os-icons/README.md; clients embed per-platform derivatives).
+ # OS brand marks for the host-card OS icon (assets/os-icons/, CC BY 4.0 / CC0 /
+ # Apache-2.0 — see assets/os-icons/README.md; clients embed per-platform derivatives).
("Font Awesome Free brand icons (vendored, assets/os-icons)",
"assets/os-icons/LICENSES/font-awesome-brands.txt",
"https://fontawesome.com"),
("Simple Icons (vendored, assets/os-icons)",
"assets/os-icons/LICENSES/simple-icons.txt",
"https://simpleicons.org"),
+ ("Bazzite logo (vendored, assets/os-icons)",
+ "assets/os-icons/LICENSES/bazzite.txt",
+ "https://github.com/ublue-os/bazzite"),
]
diff --git a/web/src/components/os-icon.tsx b/web/src/components/os-icon.tsx
index 79161a54..b0c2e09e 100644
--- a/web/src/components/os-icon.tsx
+++ b/web/src/components/os-icon.tsx
@@ -1,15 +1,16 @@
-// Host OS icon: the web-console derivative of the assets/os-icons masters (Font Awesome
-// Free brands CC BY 4.0 + Simple Icons CC0 — provenance in assets/os-icons/README.md;
-// lucide deliberately ships no brand marks). Follows the curated-registry pattern of
-// api/plugins.ts: a fixed token set, resolved by walking the host's OS-identity chain.
+// Host OS icon: the web-console derivative of the assets/os-icons masters (per-mark
+// provenance and licensing in assets/os-icons/README.md; lucide deliberately ships no
+// brand marks). Follows the curated-registry pattern of api/plugins.ts: a fixed token set,
+// resolved by walking the host's OS-identity chain. Path data is transcribed from the
+// masters — `bash scripts/gen-os-icons.sh ` prints it ready to paste.
import { Monitor } from "lucide-react";
import type { FC } from "react";
/** One monochrome brand mark: original per-icon viewBox, drawn in currentColor. */
const OS_ICONS: Record = {
windows: {
- viewBox: "0 0 448 512",
- d: "M0 93.7l183.6-25.3v177.4H0V93.7zm0 324.6l183.6 25.3V268.4H0v149.9zm203.8 28L448 480V268.4H203.8v177.9zm0-380.6v180.1H448V32L203.8 65.7z",
+ viewBox: "0 0 24 24",
+ d: "M0 0h11.377v11.377H0zm12.623 0H24v11.377H12.623zM0 12.623h11.377V24H0zm12.623 0H24V24H12.623z",
},
apple: {
viewBox: "0 0 384 512",
@@ -47,6 +48,21 @@ const OS_ICONS: Record = {
viewBox: "0 0 640 512",
d: "M471.08 102.66s-.3 18.3-.3 20.3c-9.1-3-74.4-24.1-135.7-26.3-51.9-1.8-122.8-4.3-223 57.3-19.4 12.4-73.9 46.1-99.6 109.7C7 277-.12 307 7 335.06a111 111 0 0 0 16.5 35.7c17.4 25 46.6 41.6 78.1 44.4 44.4 3.9 78.1-16 90-53.3 8.2-25.8 0-63.6-31.5-82.9-25.6-15.7-53.3-12.1-69.2-1.6-13.9 9.2-21.8 23.5-21.6 39.2.3 27.8 24.3 42.6 41.5 42.6a49 49 0 0 0 15.8-2.7c6.5-1.8 13.3-6.5 13.3-14.9 0-12.1-11.6-14.8-16.8-13.9-2.9.5-4.5 2-11.8 2.4-2-.2-12-3.1-12-14V316c.2-12.3 13.2-18 25.5-16.9 32.3 2.8 47.7 40.7 28.5 65.7-18.3 23.7-76.6 23.2-99.7-20.4-26-49.2 12.7-111.2 87-98.4 33.2 5.7 83.6 35.5 102.4 104.3h45.9c-5.7-17.6-8.9-68.3 42.7-68.3 56.7 0 63.9 39.9 79.8 68.3H460c-12.8-18.3-21.7-38.7-18.9-55.8 5.6-33.8 39.7-18.4 82.4-17.4 66.5.4 102.1-27 103.1-28 3.7-3.1 6.5-15.8 7-17.7 1.3-5.1-3.2-2.4-3.2-2.4-8.7 5.2-30.5 15.2-50.9 15.6-25.3.5-76.2-25.4-81.6-28.2-.3-.4.1 1.2-11-25.5 88.4 58.3 118.3 40.5 145.2 21.7.8-.6 4.3-2.9 3.6-5.7-13.8-48.1-22.4-62.7-34.5-69.6-37-21.6-125-34.7-129.2-35.3.1-.1-.9-.3-.9.7zm60.4 72.8a37.54 37.54 0 0 1 38.9-36.3c33.4 1.2 48.8 42.3 24.4 65.2-24.2 22.7-64.4 4.6-63.3-28.9zm38.6-25.3a26.27 26.27 0 1 0 25.4 27.2 26.19 26.19 0 0 0-25.4-27.2zm4.3 28.8c-15.4 0-15.4-15.6 0-15.6s15.4 15.64 0 15.64z",
},
+ // The gaming distros get their own mark rather than their family's: "a Bazzite box" and
+ // "a Fedora box" are different machines to the person reading the card, and these three
+ // are what this project's hosts actually run.
+ bazzite: {
+ viewBox: "0 0 24 24",
+ d: "M7.178 0h3.589v7.178h7.524c3.153 0 5.709 2.556 5.709 5.709 0 6.138-4.976 11.113-11.113 11.113-3.153 0-5.709-2.556-5.709-5.709V10.766H0v-3.589h7.178zm3.589 10.766v7.524c0 1.171.949 2.12 2.12 2.12 4.156 0 7.524-3.369 7.524-7.524 0-1.171-.949-2.12-2.12-2.12z",
+ },
+ cachyos: {
+ viewBox: "0 0 24 24",
+ d: "M5.301 2.646 0 11.771l5.541 9.583h11.486l2.904-5.017H8.102l-2.56-4.429L8.067 7.54h6.063l2.83-4.893ZM20.058 4.12a.748.748 0 0 0 0 1.496.748.748 0 0 0 0-1.496m-1.983 4.303a1.45 1.45 0 0 0 0 2.9 1.45 1.45 0 0 0 0-2.9m4.02 3.98a1.904 1.904 0 0 0 0 3.809 1.904 1.904 0 0 0 0-3.81",
+ },
+ nobara: {
+ viewBox: "0 0 24 24",
+ d: "M23.808 11.808v8.281a3.542 3.542 0 0 1-3.542 3.527h-.46a3.543 3.543 0 0 1-3.083-3.513v-7.282l3.543-1.013-3.66-1.045a4.724 4.724 0 0 0-9.33 1.045v2.362a2.362 2.362 0 0 0 2.362 2.362 3.543 3.543 0 0 1 3.543 3.542V24a3.539 3.539 0 0 0-3.542-3.542 3.537 3.537 0 0 0-3.063 1.76 3.54 3.54 0 0 1-2.382 1.398h-.46A3.542 3.542 0 0 1 .192 20.09V3.543a3.542 3.542 0 0 1 6.323-2.194A11.756 11.756 0 0 1 12 0c6.521 0 11.808 5.287 11.808 11.808zm-9.446 0A2.359 2.359 0 0 1 12 14.17a2.362 2.362 0 1 1 2.362-2.362z",
+ },
};
/** `macos` renders the apple mark, `steamos` the steam mark — same aliases as every client. */
diff --git a/web/src/stories/Host.stories.tsx b/web/src/stories/Host.stories.tsx
index 54ed4551..7ae75e7d 100644
--- a/web/src/stories/Host.stories.tsx
+++ b/web/src/stories/Host.stories.tsx
@@ -33,6 +33,18 @@ export const WindowsHost: Story = {
},
};
+/** A gaming distro wears its OWN mark, not its family's: `cachyos` is resolved before the
+ * `arch` it descends from, which is the whole point of shipping art for the leaf. */
+export const CachyOsHost: Story = {
+ args: {
+ host: {
+ data: { ...hostInfo, os: "linux/arch/cachyos", os_name: "CachyOS Linux" },
+ isLoading: false,
+ error: null,
+ },
+ },
+};
+
/** An unrecognized distro chain walks up to its family mark — here neither `chimera` nor
* `frontier` have art, so the icon degrades all the way to generic Tux. */
export const UnknownDistro: Story = {