From 5520167958712982f06d4043fa594df5eccd0801 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Fri, 31 Jul 2026 13:55:54 +0200 Subject: [PATCH] feat(clients): the OS marks tell gaming distros apart, and Windows looks current MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things were wrong with the host-card OS icons. The Windows mark was Font Awesome 5's, which is still the Windows 8/10 flag with the perspective skew — dated next to the flat four-pane mark Microsoft has shipped since Windows 11. No icon set has the current one (Simple Icons carries no windows/microsoft slug at all), so it is drawn here: four equal squares at the authentic 11.377 + 1.246 proportion. The Decky plugin was pulling FaWindows straight from react-icons, so it now inlines the masters like the web console does, or it would have kept the old flag regardless. Bazzite, CachyOS and Nobara collapsed onto their family's mark. The host already advertises the full chain, so this is purely missing art: all three now ship a leaf mark, because "a Bazzite box" and "a Fedora box" are different machines to the person reading the card. CachyOS and Nobara come from Simple Icons; Bazzite has no icon anywhere, so its "b" is lifted out of the project's own badge (Apache-2.0, attributed). On Android every non-square mark was stretched. A VectorPainter maps the viewport onto the ImageVector's default size with independent x and y scales, so declaring a 448x512 Tux as 24x24 dp squashed it — silently, no crash, no warning. The longest viewport edge now sets the 24 dp box and the other follows the ratio, which is what Icon()'s ContentScale.Fit expects. A unit test pins the invariant; every other client was already correct. Also: scripts/gen-os-icons.sh replaces the undocumented hand-run pipeline that turns a master into the GTK symbolic SVG, the Windows PNG and the Apple template PDF. It reproduces the committed artifacts byte-identically. Verified: web and Decky typecheck, Decky bundles, Android compiles and its tests pass, PunktfunkKit builds, osinfo's tests pass. Not verified on glass, and the GTK/Windows client crates do not build on macOS. Co-Authored-By: Claude Opus 5 (1M context) --- THIRD-PARTY-NOTICES.txt | 29 ++++- assets/os-icons/LICENSES/bazzite.txt | 14 ++ .../os-icons/LICENSES/font-awesome-brands.txt | 4 +- assets/os-icons/LICENSES/simple-icons.txt | 5 +- assets/os-icons/README.md | 42 ++++-- assets/os-icons/bazzite.svg | 2 + assets/os-icons/cachyos.svg | 2 + assets/os-icons/nobara.svg | 2 + assets/os-icons/windows.svg | 4 +- .../io/unom/punktfunk/components/OsIcons.kt | 52 ++++++-- .../kotlin/io/unom/punktfunk/OsIconsTest.kt | 72 +++++++++++ clients/apple/Package.swift | 7 +- .../apple/Sources/PunktfunkKit/OsIcon.swift | 12 +- .../os-bazzite.imageset/Contents.json | 10 ++ .../os-bazzite.imageset/bazzite.pdf | Bin 0 -> 1154 bytes .../os-cachyos.imageset/Contents.json | 10 ++ .../os-cachyos.imageset/cachyos.pdf | Bin 0 -> 1276 bytes .../os-nobara.imageset/Contents.json | 10 ++ .../os-nobara.imageset/nobara.pdf | Bin 0 -> 1419 bytes .../os-windows.imageset/windows.pdf | Bin 1121 -> 1087 bytes clients/decky/src/os-icon.tsx | 122 +++++++++++++----- .../actions/pf-os-bazzite-symbolic.svg | 2 + .../actions/pf-os-cachyos-symbolic.svg | 2 + .../actions/pf-os-nobara-symbolic.svg | 2 + .../actions/pf-os-windows-symbolic.svg | 4 +- clients/linux/data/resources.gresource.xml | 3 + clients/linux/src/ui_hosts.rs | 9 +- clients/windows/assets/os/bazzite.png | Bin 0 -> 638 bytes clients/windows/assets/os/cachyos.png | Bin 0 -> 867 bytes clients/windows/assets/os/nobara.png | Bin 0 -> 1062 bytes clients/windows/assets/os/windows.png | Bin 355 -> 174 bytes clients/windows/src/app/os_icons.rs | 9 +- crates/punktfunk-host/src/osinfo.rs | 13 ++ scripts/gen-os-icons.sh | 97 ++++++++++++++ scripts/gen-third-party-notices.py | 7 +- web/src/components/os-icon.tsx | 28 +++- web/src/stories/Host.stories.tsx | 12 ++ 37 files changed, 500 insertions(+), 87 deletions(-) create mode 100644 assets/os-icons/LICENSES/bazzite.txt create mode 100644 assets/os-icons/bazzite.svg create mode 100644 assets/os-icons/cachyos.svg create mode 100644 assets/os-icons/nobara.svg create mode 100644 clients/android/app/src/test/kotlin/io/unom/punktfunk/OsIconsTest.kt create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-bazzite.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-bazzite.imageset/bazzite.pdf create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-cachyos.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-cachyos.imageset/cachyos.pdf create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-nobara.imageset/Contents.json create mode 100644 clients/apple/Sources/PunktfunkKit/Resources/OsIcons.xcassets/os-nobara.imageset/nobara.pdf create mode 100644 clients/linux/data/icons/scalable/actions/pf-os-bazzite-symbolic.svg create mode 100644 clients/linux/data/icons/scalable/actions/pf-os-cachyos-symbolic.svg create mode 100644 clients/linux/data/icons/scalable/actions/pf-os-nobara-symbolic.svg create mode 100644 clients/windows/assets/os/bazzite.png create mode 100644 clients/windows/assets/os/cachyos.png create mode 100644 clients/windows/assets/os/nobara.png create mode 100755 scripts/gen-os-icons.sh 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 0000000000000000000000000000000000000000..d6b499f926d2fb1b6b938f8fc70e7024b19c65b2 GIT binary patch literal 1154 zcmY!laB6YKBDcUXaE_jA#o2O@$p8q?o!1}`mb|F+@n z|4T7WS4y3@wh8wv*0B(Gy?E%eoBkxVz2f`swNB^nRh-6=`dCOseZf}c*K5`ormSV3 z+cafi@Q!B!t}`35W-L4MSucyr`P-|xwodZwZ`jnge-lnTwc}~T#Fq|ohx5|?H*v1s z=k5QFTk%{3w}+mx_B^kzDQ*I_4#6i`_s^=TGqQeL_U`ZJeC6L}|6j_LEY{!l!2ds2 zYF-M|4n zLk0chL?8(a6`%x=1R|(8=5TWq^g}8OQWf<5ld^(Ka>1rTgW3Wf)P4$vU!zM91tF z1A*4}|GIWg(PBO8@>Z|=O`_`SlKQhk7 z@ZX7uO|$+SZDP-t{pEis;Dq3$vk!W$RQY}#Kc4#Uf!Q6?PY?Foa4HO!@15tiVpTv( zRpE@6UFYUW8Qw6oIQhCO{=m%6YpcGkYTGw;$+rrQ3+jA(R9CAjpLDAGZu8si<+KOa z{vX``Fzexw>F@G4|NML3?en5|@3y=o%*X+UF*J6J%;C{vh)900WD^mTnueTcOf7Ln z0w{3?XI7;u7(!w#Tp`*}!AQX<7OW#EKfgr5&=4NCo_T5c3Wf$?K618BPDuFie|~4< z6g;tr!o^1Sm07#L0{v$A0N YvbZF%sHCC@=mAS3V3DKh>hHz{0E<7RX8-^I literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..56ee16fb764b4c33ae7daa302371ea2246c811e4 GIT binary patch literal 1276 zcmY!laB<2%tY@2CQg%lA2l#8-4Pv;CH^brM~hN_w36;HJ&H&S zJu1u1_F3wB;ag$Gt(FH8L?)~#KAX$2IAcz6tkL6Fw>R-vyc0WYlQlK^YQR$0<*5(e zh;N$tWL}Z{`;NSGs;5t^x#N2&Wx|0Y9}lQMI9t^@Q%0jFu_ec4x|wW$sT8~Qv!|=m zyc4HSUdvZl^ljhMTQ9Y1m(8IT1EY)}Y;XkZu{S(<_5K|Eu4RDc8D zwW7p5xC9vR3N|(%0U*#%GyrlG^qn0Q3>EZ~6M-Z!I)D;D5{RJYn8VFc&=09BNLA4H zPs$1|$pxDVjbjUV9Q!F4f+gHCi;7DWOrUK1@opS!@Sy8i*Hb12HVejBO#gvQhx*SD z?mbH)oMmW)tcEBM&eb9UBc z^^>1^6dz8J%X-N1sOr3AecElAiQ+w<)sM+bdWTKkAMxSATIYIYo>m#fn>_bc1V36) zUla3;|J|WjW&U&Bx;gJ&O}==UBUQ31K%dVz_TtT8Wj1Rz^~%_f+!n?cqeA0CFK?f8 zWnLfSuOp4yQ@*CCM4os*Fa5s8U72I#t{{Oc#;=XWuMCZk0Mh-ZP zVX4m?9zBMLy2|xbN?`)jh$mn8VXml;M1& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ee6773158361d7d18582d42ab800637224c6796b GIT binary patch literal 1419 zcmY!laBlRL>bFIJJK_4%46-j-Vpxe;#D17cUcRFK?q zcmI`S=~-^8=6%tVKeqpP4Cj9FWAf{N?U_Em_0g;A*?&LvzIy%s(6wnEu^<1(@ozE- zTAP1z?tX&>E>UO|2b{qq4Zml2e!yc z2pp7E&ADbKm6V@dCHi=dx5-WMcjB2K}?ejjbRp#7!(@#8J>(ia*(m&7e z&DHw+cRAm`%a^`?+WLpFyY-+aS884gGzNhzP~3we97F@7+{D-tBoE>l!&3%0Ex1;c zxCfU2(}9AG4M+e8^b-w$90h%6M+HL#{p3U-2}~V82_OkXP;<=T<|ycgR2HNv==&#S z1()Q4O@$^o3wVuWRQNE!ML~w(i#1)E*k)rP*;i`laHL zgS;Zjo;r8y@4H;rRo!{!`0PFA!YpEn%}uT{8xC*?e$wqfCA8`LYmW)0i`1nV9louy z;+8+q=*T_ac-0pZivuqTW>`xt-t9T5F@h`AB%y0}3G>IJJ1@T5oU?4N;A2 zPk!oAd^kle>mkRZs`HZdX}4u2iuZh0KPE5f9X5G?#D@oKo$Hl(T4fY(^4wby{Afje zP0Ta?cZXt?`OkIh=Dd3~`Ql}cRLQOYeLmyZi#LOn*{s>rD`P)$TNq!A3XKcBynWJ@ zd3}t(jx=sh`I@2a|6v}ZI_Jlwzqw}n|KHAt`@-!JofnT8Ip8pc z#*UFWJbDZf$q$xnB7#!WkQ0rmCC*3yCC=c?s#FC-NUVh`L>np?DHz3qbp++-mnaw- z!sFI6FD+le&;ZOwF5Z$85`O%j-`P01khHz{0H0?tIRF3v literal 0 HcmV?d00001 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 5511f150e4a6005d242fa568284a23e951411cde..2a6b14e0b1f28fc2e6bdc032f76a78ef25fc58a2 100644 GIT binary patch delta 485 zcmVu#udl~0NTGUlL}>SWOHqw9_q9+>xK=!IS3m?FNK?|QVs`NBC!02$8I~t?ANs@tB3S~54 zio0|J@x{5(?z3#By%FO@Ntn%!d)W|&WkN_8TyI*3>aBKASlF@gwYMrJpl b7;`KG0{{(r1~Zd81RV)BF$yImMNdWwcQVcv delta 500 zcmVi!o*6qe^E4lqb0YU>ML#~!fMY5+mJ1L zrWwsy_C(ktA<<&;7Yh&0+V9s~OP|9ho4}iFI(}3?E-rB|O*cq)*&4dHPHvE``jb}U zfTHk&pJ$JWF6iQ@=Diubm$)tX1y+x8nM$XsNqW%m3mjT*I(2I=Z~af`yv z(T%&p6u){UxBN@B_jc|A_i|433n0dB){`^?AOSd&RRfzT{AXZbWZ(x8FF|7LKo+Ar qh&Tx%?m^Tsf(T|t<_w@1b3FqC03~n-7n2zT9SJ!#3MC~)PeuxBui~5l 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 0000000000000000000000000000000000000000..bc9f2f833be43b3ab1d9a1e8973e33717134e2e8 GIT binary patch literal 638 zcmV-^0)hRBP)n1Boi;Ztlv&%ryJ}8>;vor{PzH273|pHGz|lndmQDth%9+2 zqVSbKb7^L3YQ+VhrXFE;Dg_pdnDpK0*mB48vjqZ#+uDAY&DU=J3xJLSB9wD^I|}}` zz^T<%uyrDUNMul^pchoR*6pe4V=?CKz%tq%Mddug6*k+@(O7Wfzzx}IUAK1QIkpOR z0<_v=3X8tYz`f~M4fxh$DyMt{5D^7FJM2-vZ^2Q=cf&UT%c@KOA&33JzZQ&`OZ46X zK$m?1P*qVi@&16k@&&*u)}o+i9d=`_G5%)H0kXyFX+g?PU!ao7M4FxeRN3g?z#Q~c zT+M4>%n_>p`YBoP_!x<{zqnAH+bUmGC6~9OqGk~|?y}=-IvxK3eN*YKY^iiiTES0_ zd{yOkDj9$B=O@Gc;ry7V>+w10GXW(`P0N_En911jpaYQG9lfWV1JMa;`)2_x9fJ`+lD<@6Eg!c&aBXi7WWNKhTtcQ5A{xY$ZpzHtA)rKhD<^S7^w< zlpvpoCWi|=usw~J+wn#Q`0UL5KCwEP9R0s6oR}IN{qxb-7~>2MJ)g76wdry9m$27~ zYT#Hc0fQa#e_yHfmBSM|pWVG5^qk}9%w}`T4xE~;yk(Wc9l{aSEk!wpt%f;<=L zR6Y&*tsmDY-E0P9V`KM|23UUv8vJWw;c&a2mXpRkT5q>;sdPR=Z|Wq%kPVobp2+?j z8{pL0%BwcuXk0T%dD>KRe7-!_#+u(nm{u=S3G^;O~g6Sqk4T}B6mZI#V?+b zzQ#>7d}bku9xCK>-*yvZ9X>lhDXOKlp#}Xw0E&KPM1)H*>u%L+eXnf3UXyBJVIlR$ zoqK;P>7mt$Zr}jbYSq!!H?7|RFveKxr1C(_(=OA#KexA= z0IuscfQuX3a(=_ds8p_e3A`88Kw%-}*iuxj9R*HscV&5bX>d66KFR@w#VIfQ&4vKJ zKex9j@*9xu6cop|D*HG8jKHR^;rMv&jv&Qth9fF-QSg=k0RPl>d=A{&Mt-%iqhaET zEdh)fSw*C1ljwEP)>14nJ=5>2vu zj~|-Ev`O3B)~yd*c+dNu^E?mlz4yHL3ORK3^tWKxohXk1mcmzp?hW}je-03d9IsJ# zvK2L+27MT~AE*Tk&@qrpAm>HxPqAu-g2DDA4j=&Go<5%lhw^a|EL4Mt+y}f0)Ho!) zZo^@hC3^w^@6{P|ySw{WS%!TPa5*8LBgRqL*VK4N#N+W;fWz&0v|_~k^{$2}EsY}lJ{jZDxg#6yakyv7_q8n!=wWY=JL#Qre zSAHDs>3uXS=^sUnK~ZBcL;7NLfB%|7uA#fivrOScS35HxjD)s&e=dY#97Vb7zhzd~ zwtX}ohhF#n1Z+h)4P>UJRk_ER0p-{HFcRrq4qDp2h31L1oC&}0Kxn(~sZ^!aAWECU zP#FhHojn6eIzLP%8^)^ZM$Gsb!qS_OTm46|#_jmR`T z8Q{Rwc~RoIH2}s_`ko?ZKtq%l2OKscmC5gQ)7{HDFJ1ogiirKfL;@#PHa5g37vY+{ z8#pu|aBp#(`?%=NT?>NKThJWtKJ@rP#TH_~g<*UUj`ckki5z$IUFZ~Z=!C2Q-q(th z)fMae9p$WH%bq~MJF*Z1^Er-PKqJD-woAW_#Cjc_Bmbv?V$>*dA{-v5T`U6t%cU~& z{9+maWc7S2D-^Ce=obK&6&(QiUIaJFI)rwV+bC_zAdEWT{G-AUK32suOX8qr+#7f9 zeC@BIxs%aoB4)ev)HF380_`)As`8PM99#B|z=av|zDrfhOH;%F2n4(%woRKt$!}L` z{K`q-i)qD$^Yu0Iu3?&0Ugr_P(EY2R?)coz7i zq)0d;8Eb>r>&;HO{rleRf5NcrH3)9h_$IV{(`VBO7b6@Vs4cfc0IDj{{e3NpJc5!H g;p?E^SJ;_<0h-HYuZzR~CIA2c07*qoM6N<$fzRXy#==uf;!8~aS4E%CdlT|)@kyWDjh;bEs&?5P(*2_i zGzSO{lsLRozj|`keD6u`$~RV@xqsk<@kRCLii{9xw@-{8rwDzYqrZ9sNWjz8&t;uc GLK6T6_d5Fk delta 346 zcmV-g0j2(~0pkK8iBL{Q4GJ0x0000DNk~Le0000S0000W2nGNE00FfaLXja9e*qXt zL_t(YiS3rLN<%>qMbF*0(LzLQgdkY#ijBSgi9chLP9%^r>HQA#0Rc;E8#_VLh+<-9 zEE2JZfyWC^LU^~nbI-7|#f5E`>Il8*_YYfBl6jFFPjV7?a&%q^*y(iQ?!nPXB+diz z 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 = {