fix(tray): rebuild Linux tray icon as monochrome multi-size pixmaps (#362)

The previous tray-light.png / tray-dark.png were 128x128 RGBA glyphs with a
black radial background. GNOME's StatusNotifierItem path expects monochrome
silhouettes around 22px base size and was rendering ours as the "three dots"
fallback.

Regenerate from icons/icon-symbolic.svg using Inkscape with a corrected
square viewBox (149 1 944 944, centred on the path bbox) and emit four
sizes per theme (22/32/44/64). decode_tray_icons() now returns Vec<Icon>
so ksni can offer the panel the best match for its bar height and HiDPI.

cc
This commit is contained in:
vicrodh committed 2026-04-27 22:52:01 -06:00
1 parent 5bfb748876
commit f533660a43
12 files changed
+32 -11

No files matched your search

+3 -3
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="50 60 1100 1100"
width="1100"
height="1100"
viewBox="149 1 944 944"
width="944"
height="944"
fill="currentColor"
stroke="currentColor">
<g transform="translate(4.763702,7.069182)">

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

+29 -8
View File
@@ -22,8 +22,18 @@ use ksni::{
};
use tauri::{AppHandle, Emitter, Manager};
const TRAY_ICON_DARK_PNG: &[u8] = include_bytes!("../icons/tray-dark.png");
const TRAY_ICON_LIGHT_PNG: &[u8] = include_bytes!("../icons/tray-light.png");
// Multiple pixmap sizes per StatusNotifierItem spec — panels pick the best
// match for their bar height (22 = base, 44/64 = HiDPI). All are monochromatic
// silhouettes of the qbz glyph: black on transparent for light panels, white
// for dark panels. Generated from icons/icon-symbolic.svg via Inkscape.
const TRAY_ICON_LIGHT_22: &[u8] = include_bytes!("../icons/tray-light-22.png");
const TRAY_ICON_LIGHT_32: &[u8] = include_bytes!("../icons/tray-light-32.png");
const TRAY_ICON_LIGHT_44: &[u8] = include_bytes!("../icons/tray-light-44.png");
const TRAY_ICON_LIGHT_64: &[u8] = include_bytes!("../icons/tray-light-64.png");
const TRAY_ICON_DARK_22: &[u8] = include_bytes!("../icons/tray-dark-22.png");
const TRAY_ICON_DARK_32: &[u8] = include_bytes!("../icons/tray-dark-32.png");
const TRAY_ICON_DARK_44: &[u8] = include_bytes!("../icons/tray-dark-44.png");
const TRAY_ICON_DARK_64: &[u8] = include_bytes!("../icons/tray-dark-64.png");
fn is_flatpak() -> bool {
std::env::var("FLATPAK_ID").is_ok() || std::path::Path::new("/.flatpak-info").exists()
@@ -66,8 +76,7 @@ fn prefer_dark_tray() -> bool {
}
/// Convert an embedded RGBA PNG to the ARGB32 big-endian layout ksni expects.
fn decode_tray_icon() -> Result<Icon, String> {
let bytes = if prefer_dark_tray() { TRAY_ICON_DARK_PNG } else { TRAY_ICON_LIGHT_PNG };
fn decode_pixmap(bytes: &[u8]) -> Result<Icon, String> {
let img = image::load_from_memory(bytes)
.map_err(|e| format!("decode tray icon: {e}"))?;
let (width, height) = img.dimensions();
@@ -85,9 +94,21 @@ fn decode_tray_icon() -> Result<Icon, String> {
})
}
/// Decode all monochromatic pixmap sizes for the active theme. Panels pick the
/// best size from the list; supplying 22/32/44/64 covers standard SNI bar
/// heights and HiDPI variants.
fn decode_tray_icons() -> Result<Vec<Icon>, String> {
let sources: [&[u8]; 4] = if prefer_dark_tray() {
[TRAY_ICON_DARK_22, TRAY_ICON_DARK_32, TRAY_ICON_DARK_44, TRAY_ICON_DARK_64]
} else {
[TRAY_ICON_LIGHT_22, TRAY_ICON_LIGHT_32, TRAY_ICON_LIGHT_44, TRAY_ICON_LIGHT_64]
};
sources.iter().map(|b| decode_pixmap(b)).collect()
}
struct QbzTray {
app: AppHandle,
icon: Icon,
icons: Vec<Icon>,
}
impl QbzTray {
@@ -132,7 +153,7 @@ impl Tray for QbzTray {
}
fn icon_pixmap(&self) -> Vec<Icon> {
vec![self.icon.clone()]
self.icons.clone()
}
fn tool_tip(&self) -> ToolTip {
@@ -206,10 +227,10 @@ impl Tray for QbzTray {
pub fn init(app: &AppHandle) -> Result<(), Box<dyn std::error::Error>> {
log::info!("Initializing ksni tray (Linux, SNI primary-activate enabled)");
let icon = decode_tray_icon()?;
let icons = decode_tray_icons()?;
let tray = QbzTray {
app: app.clone(),
icon,
icons,
};
// Flatpak requires disabling the well-known DBus name because the sandbox