feat(linux): add tray icon support

This commit is contained in:
juve 2026-07-12 23:00:04 +08:00 committed by GitHub
parent fa76c742c8
commit 1ecd8cf406
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 97 additions and 18 deletions

1
Cargo.lock generated
View File

@ -1857,6 +1857,7 @@ dependencies = [
"duckdb",
"font-kit",
"futures",
"libloading 0.8.9",
"log",
"mongodb",
"objc2",

View File

@ -48,11 +48,11 @@
]
},
"locked": {
"lastModified": 1783488441,
"narHash": "sha256-jmWf+H3iC/0z7/mmvTovaJx1E/LME1QyHkyk+AFfJPk=",
"lastModified": 1783614422,
"narHash": "sha256-zSX553aXiIKJHWzUF6nrDKgeNRlfNK/SdyNYSbs2nkM=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "7dc3a177a239ed879c5581a80f6dc246c7e102f1",
"rev": "e8e9d70b96113fd49ccde6c3e08fb260b6ef5dd6",
"type": "github"
},
"original": {

View File

@ -43,6 +43,7 @@
webkitgtk_4_1
gtk3
libappindicator-gtk3
libayatana-appindicator # provides libayatana-appindicator3.so.1 (dlopen'd at runtime)
librsvg
patchelf
openssl
@ -205,6 +206,8 @@
pkgs.rustPlatform.cargoSetupHook # sets CARGO_HOME to cargoDeps
pkgs.pnpmConfigHook # sets up pnpm offline store
pkgs.desktop-file-utils # for `desktop-file-validate`
pkgs.copyDesktopItems # installs desktopItem into share/applications
pkgs.imagemagick # generates correctly sized hicolor icons
]
++ pkgs.lib.optionals pkgs.stdenv.isLinux (
with pkgs;
@ -272,6 +275,21 @@
# Tauri reads the version from this env var during build
TAURI_SKIP_DEVSERVER_CHECK = "true";
# ── Runtime library path injection ───────────────────────────────── #
# libappindicator-sys uses dlopen() at runtime to load the appindicator
# shared library. dlopen() does NOT honour the binary's RPATH — it only
# searches LD_LIBRARY_PATH and system paths. In a Nix derivation the
# library lives in the store, not in /usr/lib, so we must inject the
# path explicitly into the wrapGAppsHook3 C-wrapper.
#
# IMPORTANT: wrapGAppsHook3 uses its own `gappsWrapperArgs` bash array
# (NOT `makeWrapperArgs`) — inject via preFixup before the hook runs.
preFixup = pkgs.lib.optionalString pkgs.stdenv.isLinux ''
gappsWrapperArgs+=(
--prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath linuxTauriDeps}"
)
'';
# ── Build phases ─────────────────────────────────────────────────── #
preConfigure = ''
export HOME=$TMPDIR
@ -312,9 +330,10 @@
# tauri build --no-bundle puts the binary at target/release/dbx
cp target/release/dbx $out/bin/dbx
# Copy desktop integration files if present.
# Install every PNG size Tauri ships so the hicolor theme lookup
# (e.g. panels @ 48px, launchers @ 128px) always succeeds.
# Install icon files into the hicolor theme tree so that all
# desktop environments (GNOME Shell, KDE Plasma, XFCE, etc.) can
# find the right size: task-switcher (32px), panel (48px),
# launcher (64px), app-menu (128px), HiDPI launcher (256px).
if [ -d src-tauri/icons ]; then
for size in 32 128; do
if [ -f "src-tauri/icons/''${size}x''${size}.png" ]; then
@ -323,12 +342,36 @@
"$out/share/icons/hicolor/''${size}x''${size}/apps/dbx.png"
fi
done
# @2x retina variant for 128px
# @2x retina variant (128x128@2x) → install as 256x256
if [ -f "src-tauri/icons/128x128@2x.png" ]; then
mkdir -p "$out/share/icons/hicolor/256x256/apps"
cp "src-tauri/icons/128x128@2x.png" \
"$out/share/icons/hicolor/256x256/apps/dbx.png"
fi
# Generate missing common sizes so hicolor directory metadata
# always matches the actual PNG dimensions.
for size in 16 48 64; do
mkdir -p "$out/share/icons/hicolor/''${size}x''${size}/apps"
if [ "$size" -le 32 ] && [ -f "src-tauri/icons/32x32.png" ]; then
src="src-tauri/icons/32x32.png"
elif [ -f "src-tauri/icons/128x128.png" ]; then
src="src-tauri/icons/128x128.png"
else
continue
fi
magick "$src" -resize "''${size}x''${size}" \
"$out/share/icons/hicolor/''${size}x''${size}/apps/dbx.png"
done
# Install the full-size icon.png as the scalable fallback so that
# Tauri's default_window_icon() and the taskbar always have an image.
if [ -f "src-tauri/icons/icon.png" ]; then
mkdir -p "$out/share/icons/hicolor/512x512/apps"
cp "src-tauri/icons/icon.png" \
"$out/share/icons/hicolor/512x512/apps/dbx.png"
fi
fi
# Register the freedesktop .desktop file so app launchers (GNOME

View File

@ -66,3 +66,6 @@ tauri-plugin-clipboard-manager = "2.3.2"
objc2 = { version = "0.6.4", default-features = false, features = ["std"] }
objc2-app-kit = { version = "0.3.2", default-features = false, features = ["NSApplication", "NSImage"] }
objc2-foundation = { version = "0.3.2", default-features = false, features = ["NSData"] }
[target.'cfg(target_os = "linux")'.dependencies]
libloading = "0.8"

View File

@ -69,8 +69,25 @@ fn should_hide_window_on_close(target_os: &str) -> bool {
matches!(target_os, "macos" | "windows")
}
fn should_setup_desktop_tray(target_os: &str, show_tray_icon: bool) -> bool {
show_tray_icon && matches!(target_os, "macos" | "windows")
fn should_setup_desktop_tray(target_os: &str, show_tray_icon: bool, linux_appindicator_available: bool) -> bool {
show_tray_icon
&& (matches!(target_os, "macos" | "windows") || (target_os == "linux" && linux_appindicator_available))
}
#[cfg(target_os = "linux")]
fn linux_appindicator_available() -> bool {
const APPINDICATOR_LIBRARIES: &[&str] = &["libayatana-appindicator3.so.1", "libappindicator3.so.1"];
APPINDICATOR_LIBRARIES.iter().any(|library| {
// tray-icon loads AppIndicator dynamically and panics when neither ABI is
// installed, so probe the same libraries before entering that code path.
unsafe { libloading::Library::new(library).is_ok() }
})
}
#[cfg(not(target_os = "linux"))]
fn linux_appindicator_available() -> bool {
false
}
#[cfg(test)]
@ -449,7 +466,15 @@ fn apply_desktop_tray_icon_theme(app: &tauri::AppHandle, _icon_theme: DesktopIco
};
_tray.set_icon(icon)?;
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
#[cfg(target_os = "linux")]
{
let icon = match _icon_theme {
DesktopIconTheme::Default => app.default_window_icon().cloned(),
DesktopIconTheme::Black => Some(BLACK_APP_ICON),
};
_tray.set_icon(icon)?;
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
let _ = (_tray, _icon_theme);
}
@ -460,7 +485,8 @@ fn apply_desktop_tray_icon_theme(app: &tauri::AppHandle, _icon_theme: DesktopIco
pub(crate) fn apply_desktop_settings(app: &tauri::AppHandle, desktop_settings: &DesktopSettings) -> tauri::Result<()> {
apply_debug_log_level(desktop_settings.debug_logging_enabled);
apply_desktop_icon_theme(app, desktop_settings.icon_theme)?;
if matches!(std::env::consts::OS, "macos" | "windows") {
if should_setup_desktop_tray(std::env::consts::OS, desktop_settings.show_tray_icon, linux_appindicator_available())
{
if let Some(tray) = app.tray_by_id(DESKTOP_TRAY_ID) {
tray.set_visible(desktop_settings.show_tray_icon)?;
apply_desktop_tray_icon_theme(app, desktop_settings.icon_theme)?;
@ -496,12 +522,14 @@ mod tests {
}
#[test]
fn sets_up_desktop_tray_for_windows_and_macos() {
assert!(should_setup_desktop_tray("windows", true));
assert!(should_setup_desktop_tray("macos", true));
assert!(!should_setup_desktop_tray("windows", false));
assert!(!should_setup_desktop_tray("macos", false));
assert!(!should_setup_desktop_tray("linux", true));
fn sets_up_desktop_tray_for_windows_macos_and_linux() {
assert!(should_setup_desktop_tray("windows", true, false));
assert!(should_setup_desktop_tray("macos", true, false));
assert!(should_setup_desktop_tray("linux", true, true));
assert!(!should_setup_desktop_tray("linux", true, false));
assert!(!should_setup_desktop_tray("windows", false, true));
assert!(!should_setup_desktop_tray("macos", false, true));
assert!(!should_setup_desktop_tray("linux", false, true));
}
#[cfg(target_os = "macos")]
@ -800,7 +828,11 @@ pub fn run() {
let _ = window.set_decorations(decorations);
}
}
if should_setup_desktop_tray(std::env::consts::OS, desktop_settings.show_tray_icon) {
if should_setup_desktop_tray(
std::env::consts::OS,
desktop_settings.show_tray_icon,
linux_appindicator_available(),
) {
setup_desktop_tray(app, desktop_settings.icon_theme)?;
}
apply_desktop_icon_theme(app.handle(), desktop_settings.icon_theme)?;