fix(linux): draw the macOS tray icon from a template silhouette (#114223)

* fix(linux): draw the macOS tray icon from a template silhouette

AppKit renders menu bar template images from the alpha channel alone, so the
opaque rounded-tile 32x32.png painted a solid white square instead of the
mascot. Add a dedicated tray-template.svg and its 36px render, a silhouette
with the eyes knocked back out, and select it only on macOS. Its geometry
mirrors the native macOS app's CritterIconRenderer at rest so both clients
wear the same face; other platforms keep the full-color 32x32.png.

* chore(linux): drop the root changelog entry from the tray icon fix

CHANGELOG.md is release-owned in this repo (AGENTS.md) and `scripts/pr
prepare-run` rejects normal PRs that touch it; release generation derives
entries from merged PRs. The release-note context lives in the PR body and
the preceding commit message instead.
This commit is contained in:
Peter Steinberger
2026-07-26 21:53:18 -04:00
committed by GitHub
parent f42cb65393
commit e6a6d478f2
4 changed files with 53 additions and 0 deletions
+9
View File
@@ -63,8 +63,17 @@ rsvg-convert -w 128 -h 128 icon-tile.svg -o 128x128.png
rsvg-convert -w 256 -h 256 icon-tile.svg -o 128x128@2x.png
rsvg-convert -w 512 -h 512 icon-tile.svg -o icon.png
magick icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico
rsvg-convert -w 36 -h 36 tray-template.svg -o tray-template.png
```
macOS gets its own tray asset, `icons/tray-template.svg`. AppKit template images
are drawn from the alpha channel alone, so a colored or edge-to-edge opaque icon
arrives in the menu bar as a featureless blob; the template source is a
silhouette with the eyes knocked back out of it. Its geometry mirrors the native
macOS app's `CritterIconRenderer` at rest so both clients wear the same face, and
the 36px render is the 2× backing store for the 18pt slot `tray-icon` scales
menu bar images into. Non-Apple platforms keep the full-color `32x32.png`.
## Packaging
Build a `.deb` and AppImage locally (the same command CI runs):
Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

@@ -0,0 +1,38 @@
<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<!--
macOS menu bar template icon. Template rendering keeps ONLY the alpha
channel, so this file must be a silhouette with knocked-out eyes: an opaque
tile (icon-tile.svg) or a plain filled body reads as a featureless blob up
there. Geometry mirrors the native macOS app's CritterIconRenderer at rest
(18pt canvas, no blink/wiggle/droop) so both clients wear the same face.
-->
<mask id="critter" maskUnits="userSpaceOnUse" x="0" y="0" width="18" height="18">
<!-- White keeps pixels, black punches them out. -->
<g fill="#fff">
<!-- Antennae first; the body fill covers their roots. -->
<g fill="none" stroke="#fff" stroke-width="2.07" stroke-linecap="round">
<path d="M6.926 4.563 Q6.149 1.35 3.816 1.62" />
<path d="M11.074 4.563 Q11.851 1.35 14.184 1.62" />
</g>
<!-- Stubby legs, overlapping the body so the silhouette stays connected. -->
<rect x="5.4" y="12.96" width="2.52" height="3.24" rx="1.26" />
<rect x="10.08" y="12.96" width="2.52" height="3.24" rx="1.26" />
<!-- Round arm nubs, slightly below body center. -->
<circle cx="2.7" cy="9.59" r="1.8" />
<circle cx="15.3" cy="9.59" r="1.8" />
<!-- Chubby near-circular body: the dominant shape at 18pt. -->
<ellipse cx="9" cy="8.64" rx="6.48" ry="5.94" />
</g>
<!-- Eyes cut clean through the silhouette. -->
<g fill="#000">
<ellipse cx="6.149" cy="7.69" rx="1.426" ry="1.544" />
<ellipse cx="11.851" cy="7.69" rx="1.426" ry="1.544" />
</g>
<!-- Glints restored inside each eye, echoing the mascot's shiny pupils. -->
<g fill="#fff">
<circle cx="5.522" cy="7.134" r="0.741" />
<circle cx="11.224" cy="7.134" r="0.741" />
</g>
</mask>
<rect width="18" height="18" fill="#000" mask="url(#critter)" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+6
View File
@@ -235,6 +235,12 @@ pub fn build(
])
.build()?;
// macOS draws menu bar icons from the alpha channel alone (see
// icon_as_template below), so it needs the knocked-out silhouette; the
// rounded-tile 32x32.png is opaque edge to edge and renders as a solid blob.
#[cfg(target_os = "macos")]
let tray_icon = tauri::image::Image::from_bytes(include_bytes!("../icons/tray-template.png"))?;
#[cfg(not(target_os = "macos"))]
let tray_icon = tauri::image::Image::from_bytes(include_bytes!("../icons/32x32.png"))?;
let menu_state = state.clone();
let menu_start_at_login = start_at_login.clone();