FEAT: tray icon initial support

This commit is contained in:
valerio 2026-03-13 20:13:59 +01:00
parent 80fa105e31
commit 13f4cdb3f9
11 changed files with 352 additions and 46 deletions

View File

@ -13,6 +13,7 @@
"@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-shell": "~2",
"@tauri-apps/plugin-store": "~2",
"@tauri-apps/plugin-updater": "~2",
"@tauri-apps/plugin-upload": "~2",
"@tauri-apps/plugin-window-state": "~2",
"@webtui/css": "^0.1.6",
@ -238,6 +239,8 @@
"@tauri-apps/plugin-store": ["@tauri-apps/plugin-store@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-0ClHS50Oq9HEvLPhNzTNFxbWVOqoAp3dRvtewQBeqfIQ0z5m3JRnOISIn2ZVPCrQC0MyGyhTS9DWhHjpigQE7A=="],
"@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ=="],
"@tauri-apps/plugin-upload": ["@tauri-apps/plugin-upload@2.4.0", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ebhsqXmiELnpKu2p46EZG14UKxvbVP28BpJBiHzR+quWVrMxm40518PXTDlXXcJUW5CkbmP/6RL5ERSVXBL8sQ=="],
"@tauri-apps/plugin-window-state": ["@tauri-apps/plugin-window-state@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw=="],

View File

@ -24,6 +24,7 @@
"@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-shell": "~2",
"@tauri-apps/plugin-store": "~2",
"@tauri-apps/plugin-updater": "~2",
"@tauri-apps/plugin-upload": "~2",
"@tauri-apps/plugin-window-state": "~2",
"@webtui/css": "^0.1.6",

228
src-tauri/Cargo.lock generated
View File

@ -339,6 +339,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.11.1"
@ -1812,7 +1818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
dependencies = [
"byteorder",
"png",
"png 0.17.16",
]
[[package]]
@ -1929,6 +1935,19 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "image"
version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [
"bytemuck",
"byteorder-lite",
"moxcms",
"num-traits",
"png 0.18.1",
]
[[package]]
name = "indexmap"
version = "1.9.3"
@ -2324,6 +2343,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minisign-verify"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@ -2345,6 +2370,16 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "moxcms"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
dependencies = [
"num-traits",
"pxfm",
]
[[package]]
name = "muda"
version = "0.17.1"
@ -2360,7 +2395,7 @@ dependencies = [
"objc2-core-foundation",
"objc2-foundation",
"once_cell",
"png",
"png 0.17.16",
"serde",
"thiserror 2.0.18",
"windows-sys 0.60.2",
@ -2642,6 +2677,18 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-osa-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-app-kit",
"objc2-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
@ -2730,6 +2777,12 @@ dependencies = [
"pathdiff",
]
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "option-ext"
version = "0.2.0"
@ -2772,6 +2825,20 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "osakit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
dependencies = [
"objc2",
"objc2-foundation",
"objc2-osa-kit",
"serde",
"serde_json",
"thiserror 2.0.18",
]
[[package]]
name = "pango"
version = "0.18.3"
@ -3037,6 +3104,19 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "png"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [
"bitflags 2.11.0",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "polling"
version = "3.11.0"
@ -3175,6 +3255,12 @@ dependencies = [
"psl-types",
]
[[package]]
name = "pxfm"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
[[package]]
name = "quick-xml"
version = "0.37.5"
@ -3528,15 +3614,20 @@ dependencies = [
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tokio-util",
"tower",
"tower-http",
@ -3628,6 +3719,18 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
@ -3638,6 +3741,33 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
dependencies = [
"core-foundation 0.10.1",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.9"
@ -3670,6 +3800,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "schemars"
version = "0.8.22"
@ -3727,6 +3866,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags 2.11.0",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "selectors"
version = "0.24.0"
@ -4045,10 +4207,11 @@ dependencies = [
"tauri-plugin-persisted-scope",
"tauri-plugin-shell",
"tauri-plugin-store",
"tauri-plugin-updater",
"tauri-plugin-upload",
"tauri-plugin-window-state",
"xz2",
"zip",
"zip 1.1.4",
]
[[package]]
@ -4333,6 +4496,7 @@ dependencies = [
"gtk",
"heck 0.5.0",
"http",
"image",
"jni",
"libc",
"log",
@ -4400,7 +4564,7 @@ dependencies = [
"ico",
"json-patch",
"plist",
"png",
"png 0.17.16",
"proc-macro2",
"quote",
"semver",
@ -4623,6 +4787,39 @@ dependencies = [
"tracing",
]
[[package]]
name = "tauri-plugin-updater"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fe8e9bebd88fc222938ffdfbdcfa0307081423bd01e3252fc337d8bde81fc61"
dependencies = [
"base64 0.22.1",
"dirs",
"flate2",
"futures-util",
"http",
"infer",
"log",
"minisign-verify",
"osakit",
"percent-encoding",
"reqwest 0.13.2",
"rustls",
"semver",
"serde",
"serde_json",
"tar",
"tauri",
"tauri-plugin",
"tempfile",
"thiserror 2.0.18",
"time",
"tokio",
"url",
"windows-sys 0.60.2",
"zip 4.6.1",
]
[[package]]
name = "tauri-plugin-upload"
version = "2.4.0"
@ -5127,7 +5324,7 @@ dependencies = [
"objc2-core-graphics",
"objc2-foundation",
"once_cell",
"png",
"png 0.17.16",
"serde",
"thiserror 2.0.18",
"windows-sys 0.60.2",
@ -5540,6 +5737,15 @@ dependencies = [
"system-deps",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webpki-roots"
version = "1.0.6"
@ -6428,6 +6634,18 @@ dependencies = [
"zstd",
]
[[package]]
name = "zip"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
dependencies = [
"arbitrary",
"crc32fast",
"indexmap 2.13.0",
"memchr",
]
[[package]]
name = "zmij"
version = "1.0.21"

View File

@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri = { version = "2", features = ["tray-icon", "image-png"] }
tauri-plugin-opener = "2"
tauri-plugin-window-state = "2"
serde = { version = "1", features = ["derive"] }
@ -38,4 +38,5 @@ flate2 = "1.0"
bzip2 = "0.4"
tauri-plugin-upload = "2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2"
tauri-plugin-window-state = "2"

View File

@ -1,38 +1,56 @@
{
"identifier": "desktop-capability",
"platforms": ["macOS", "windows", "linux"],
"windows": ["main"],
"permissions": [
"core:default",
"opener:default",
"window-state:default",
"dialog:allow-open",
"dialog:allow-confirm",
"fs:allow-app-read-recursive",
"fs:allow-app-write-recursive",
"opener:allow-open-path",
"fs:allow-mkdir",
{
"identifier": "fs:scope",
"allow": [
{ "path": "$LOCALDATA/applications/**" },
{ "path": "$LOCALDATA/applications" },
{ "path": "$LOCALDATA/applications/*" }
]
},
"shell:allow-spawn",
{
"identifier": "http:default",
"allow": [
{
"url": "https://*.blender.org"
}
]
},
"notification:default",
"os:default",
"store:default",
"shell:default",
"upload:default"
]
"identifier": "desktop-capability",
"platforms": [
"macOS",
"windows",
"linux"
],
"windows": [
"main"
],
"permissions": [
"core:window:allow-close",
"core:window:allow-hide",
"core:window:allow-show",
"core:window:allow-is-visible",
"core:window:allow-destroy",
"core:default",
"opener:default",
"window-state:default",
"dialog:allow-open",
"dialog:allow-confirm",
"fs:allow-app-read-recursive",
"fs:allow-app-write-recursive",
"opener:allow-open-path",
"fs:allow-mkdir",
{
"identifier": "fs:scope",
"allow": [
{
"path": "$LOCALDATA/applications/**"
},
{
"path": "$LOCALDATA/applications"
},
{
"path": "$LOCALDATA/applications/*"
}
]
},
"shell:allow-spawn",
{
"identifier": "http:default",
"allow": [
{
"url": "https://*.blender.org"
}
]
},
"notification:default",
"os:default",
"store:default",
"shell:default",
"upload:default",
"updater:default"
]
}

View File

@ -280,6 +280,7 @@ async fn extract_archive(archive_path: String, target_dir: String) -> Result<Str
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_upload::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_fs::init())

View File

@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "smoothie",
"version": "0.1.0",
"version": "0.1.1",
"identifier": "smoothie",
"build": {
"beforeDevCommand": "bun run dev",
@ -31,6 +31,7 @@
"bundle": {
"active": true,
"targets": "all",
"createUpdaterArtifacts": true,
"icon": [
"icons/32x32.png",
"icons/128x128.png",
@ -38,5 +39,13 @@
"icons/icon.icns",
"icons/icon.ico"
]
},
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDZGMjVEM0E3RDQ1QTE4N0YKUldSL0dGclVwOU1sYjU5a1ZiV2xOV3V2SVBPakMvLys0Q2xFYlozZEk0cW0rR2p5bnBvYkpUek8K",
"endpoints": [
"https://git.floatingpoint.ch/valerio/smoothie/releases/download/latest/update.json"
]
}
}
}

View File

@ -4,6 +4,8 @@ import { currentSettings } from './settings';
import { writable } from 'svelte/store';
import { get } from 'svelte/store';
const RELEASES_URL = 'https://download.blender.org/release/';
/**
* @typedef {Object} FileLink
* @property {string} version - The version string (e.g., "5.0.0")
@ -32,7 +34,7 @@ export async function getBlenderReleases() {
const currentArch = settings.defaultArch;
try {
// Fetch the HTML page
const response = await fetch('https://download.blender.org/release/');
const response = await fetch(RELEASES_URL);
const html = await response.text();
// Parse the HTML

View File

@ -12,6 +12,7 @@ import { ensureLibraryStructure, selectDirectory } from './file_utils.js';
* @property {string} [theme] - Theme ('light', 'dark')
* @property {boolean} [autoCreateShortcuts] - should smoothie create a shortcut to newly installed blender versions
* @property {boolean} [keepDownloadedArchives] - Whether to keep downloaded archives after extraction
* @property {boolean} [closeToTray] - Whether to close the window to the tray instead of exiting when the close button is clicked
*/
// Settings lazy store
@ -32,7 +33,8 @@ export async function initSettings() {
defaultArch: detectedArch,
keepDownloadedArchives: false,
autoCreateShortcuts: true,
theme: 'light' //
theme: 'light', //
closeToTray: true
};
}

49
src/lib/trayicon.js Normal file
View File

@ -0,0 +1,49 @@
import { TrayIcon } from '@tauri-apps/api/tray';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { currentSettings } from './settings';
import { get } from 'svelte/store';
import { Menu } from '@tauri-apps/api/menu';
const mainWindow = getCurrentWindow();
export async function setupTrayIcon() {
// Prevent the window from closing when the close button is clicked
mainWindow.onCloseRequested((event) => {
const settings = get(currentSettings);
if (settings.closeToTray) {
event.preventDefault();
mainWindow.hide();
}
});
const menu = await Menu.new({
items: [
{
id: 'toggle',
text: 'Show/Hide',
action: async () => {
const visible = await mainWindow.isVisible();
console.log(visible);
if (visible) {
mainWindow.hide();
} else {
mainWindow.show();
}
}
},
{
id: 'quit',
text: 'Quit',
action: () => {
mainWindow.destroy();
}
}
]
});
const options = {
menu,
menuOnLeftClick: true
};
return await TrayIcon.new(options);
}

View File

@ -1,5 +1,6 @@
<script>
import '@webtui/css/components/spinner.css';
import { setupTrayIcon } from '$lib/trayicon';
import { fade } from 'svelte/transition';
import { onMount } from 'svelte';
import { getSettings, currentSettings } from '$lib/settings.js';
@ -46,6 +47,7 @@
await getSettings();
await getBlenderReleases();
await getInstalledVersions();
await setupTrayIcon();
initStoresListeners();
const elapsed = Date.now() - startTime;