collective

Browse / github.com/storytold/craft-launcher / patch

feat(macos): option to hide the Dock icon while running from the menu bar

macOS: option to hide the Dock icon when running from the menu bar Version: ArtCraft Launcher 0.4.1 (macOS universal build) What happens: With "Keep running in the tray when the window is closed" turned on, the launcher shows its icon in the menu bar, but the Dock icon also stays visible the whole time, even after the window is closed. So the launcher takes up a spot in both the menu bar and the Dock. Request: One of these would solve it: 1. A "Show in Dock" toggle in Settings under Running in the Background. 2. Hide the Dock icon automatically when the window is closed and the launcher is running from the menu bar, then show it again when the window

Status
open
Score
0.00
Adopters
0 other operators
Author
thebruge (imported from pull request #555, open)
Base
307efcf45a080af9643e750d43034eb25540cef8
Size
+162 −10 in 20 files
Review
pending
Shared
2026-10-28
Id
52bcb0587a078462792ab82c

How it went

No other operator has reported using it yet.

Diff

raw · .DS_Store AGENTS.md apps/artcraft-launcher/Cargo.toml apps/artcraft-launcher/src/dock.rs apps/artcraft-launcher/src/main.rs crates/native/src/self_update.rs crates/ui-egui/src/app.rs crates/ui-egui/src/dialogs.rs crates/ui-egui/src/i18n/cs.tsv crates/ui-egui/src/i18n/de.tsv crates/ui-egui/src/i18n/es.tsv crates/ui-egui/src/i18n/fr.tsv crates/ui-egui/src/i18n/hu.tsv crates/ui-egui/src/i18n/ja.tsv crates/ui-egui/src/i18n/pl.tsv crates/ui-egui/src/i18n/pt-BR.tsv crates/ui-egui/src/i18n/ru.tsv crates/ui-egui/src/i18n/zh-CN.tsv crates/ui-egui/src/settings.rs crates/ui-egui/tests/ui.rs

diff --git a/.DS_Store b/.DS_Storenew file mode 100644index 0000000000000000000000000000000000000000..f5ba9d7707d97a5c7a5be6dcd6bf3c309354af01GIT binary patchliteral 8196zcmeHLv2N5r5S_goA%O-&K@<sOsHhMT1tm^oC_({JDjJ0EF1kd?;S+a}(&3sOQJ_?*z=-@A+bZJ2152*MB-t4ZeowcoWh$yos??gK@`<~yf*BKL$**F-j5v>r>5-zqkuA^&dz?B}b|dVb^qtU*4}n2P=J+Sag`OdHWUpbDr0s(>n>3aA4Ag93PGb6e_o?>n#ds(>nRzDHY)JA;HBq^|o}@9v$dx2>{#2ux<Dob%4!DyiL6=owcc$(^U_~ry5^k7?%!z!r`!~zx23Z#os3H-<B^T8P>e-KUSh+^q|R!u3aA221-N!!q=z)5oZh$8@AqT>q{y<telfr}z9=Y=QkM)!FX3Xf^nCVXVoxnb|eef95F3rU^gx7~Bn7{T(eERM21AmC!^b6cK7WZm}zOFMKx1A$I=egQp-h&XI|4vi?mq$5i3p;rLIz2-E<Zzh<SFRP(XawyMg9enPj2+^B<zf!j+lHTFSHGyJw`9%0}8I*<JlpQ^hPb4U*DF@i_pc?_`^bFkQsc-%~1S{{Kv;_;{6zk0K`2f?8t)k6YgWgLN2DfhR+*!}6!^p)bO(bf|MXG^qUo_gccZ%UJsyxO?dNd3Z--zu@e?<KWUHZ;L<W*2dkDy!LOe(zeTK!J1wwN0%qpv@yE}xKjie{!|vms)p-%j5whuuz33!<7-XrF5g$^x~8SMou;5a>!-jD-4)fxr)A%pga03ZKd*mDDc+EjrHtH6R|zQXnYzv+nQz7xw;?xGJCuoUefCXIt3@;(Tr0$mn4t!SxmwH;yZvwJGR0gvV_;4(rQtJC=A~c>TT()O+}7Pg>{wuKll)!?|)tZZI@bszlT#i6951Jliteral 0HcmV?d00001diff --git a/AGENTS.md b/AGENTS.md--- a/AGENTS.md+++ b/AGENTS.md@@ -155,7 +155,9 @@ Packaging facts the code relies on:   `ai.storyteller.artcraft-launcher`, which toast notifications need. `portable.txt` beside the exe switches on portable mode   (`ArtCraftLauncherData\` next to the exe). - The macOS bundle is `ArtCraft Launcher.app` (executable `Contents/MacOS/ArtCraft Launcher`), a-  normal Dock app that can also sit in the menu bar: `LSUIElement` must never be set.+  normal Dock app that can also sit in the menu bar: `LSUIElement` must never be set. The user+  can hide the Dock icon while the window is put away in the menu bar; that switches the+  activation policy at run time (`apps/artcraft-launcher/src/dock.rs`). - Linux: the tray uses StatusNotifierItem over D-Bus and notifications use   `org.freedesktop.Notifications`, both in pure Rust (no GTK, appindicator or libdbus). - Inside the Flatpak launcher (`process::in_sandbox()`), apps normally install as host Flatpaksdiff --git a/apps/artcraft-launcher/Cargo.toml b/apps/artcraft-launcher/Cargo.toml--- a/apps/artcraft-launcher/Cargo.toml+++ b/apps/artcraft-launcher/Cargo.toml@@ -39,13 +39,13 @@ zbus = "5" [target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies] tray-icon = "0.25" -# macOS only: which desktop (Space) the window shows on (spaces.rs) and the "Reduce motion"-# setting (motion.rs). Safe AppKit calls only, and already in Cargo.lock through winit and-# tray-icon; pinned exactly to those versions.+# macOS only: which desktop (Space) the window shows on (spaces.rs), whether there's a Dock icon+# (dock.rs) and the "Reduce motion" setting (motion.rs). Safe AppKit calls only, and already in+# Cargo.lock through winit and tray-icon; pinned exactly to those versions. [target.'cfg(target_os = "macos")'.dependencies] objc2 = "=0.6.5" objc2-foundation = { version = "=0.3.2", default-features = false, features = ["std", "NSArray", "NSEnumerator"] }-objc2-app-kit = { version = "=0.3.2", default-features = false, features = ["std", "NSAccessibility", "NSApplication", "NSResponder", "NSWindow", "NSWorkspace"] }+objc2-app-kit = { version = "=0.3.2", default-features = false, features = ["std", "NSAccessibility", "NSApplication", "NSResponder", "NSRunningApplication", "NSWindow", "NSWorkspace"] }  # Windows only: the "Animation effects" setting (motion.rs). Already in Cargo.lock through # artcraft-launcher-native.diff --git a/apps/artcraft-launcher/src/dock.rs b/apps/artcraft-launcher/src/dock.rsnew file mode 100644--- /dev/null+++ b/apps/artcraft-launcher/src/dock.rs@@ -0,0 +1,21 @@+//! macOS only: whether the launcher has a Dock icon.+//!+//! The launcher is a normal Dock app (`LSUIElement` is never set). With "hide the Dock icon while+//! the window is closed" on, it becomes a menu-bar-only (accessory) app while its window is put+//! away in the menu bar, and a Dock app again when the window comes back. Other platforms have no+//! Dock, so this module only exists on macOS.++use objc2::MainThreadMarker;+use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy};++/// Shows (`true`) or hides (`false`) the launcher's Dock icon, which also adds it to or takes it+/// out of the app switcher (Cmd-Tab). Must run on the main thread, as the UI does; elsewhere it+/// does nothing.+pub fn show_icon(on: bool) {+    let Some(mtm) = MainThreadMarker::new() else { return };+    let policy = if on { NSApplicationActivationPolicy::Regular } else { NSApplicationActivationPolicy::Accessory };+    let app = NSApplication::sharedApplication(mtm);+    if app.activationPolicy() != policy && !app.setActivationPolicy(policy) {+        log::warn!("couldn't {} the Dock icon", if on { "show" } else { "hide" });+    }+}diff --git a/apps/artcraft-launcher/src/main.rs b/apps/artcraft-launcher/src/main.rs--- a/apps/artcraft-launcher/src/main.rs+++ b/apps/artcraft-launcher/src/main.rs@@ -5,6 +5,8 @@ #![cfg_attr(windows, windows_subsystem = "windows")] #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +#[cfg(target_os = "macos")]+mod dock; mod logging; mod motion; mod settings_file;@@ -331,6 +333,13 @@ enum OnClose {     Quit, } +/// Whether the Dock icon shows (macOS): always while the window is out, and while it is put away in+/// the menu bar unless the user chose to hide it then.+#[cfg_attr(not(target_os = "macos"), allow(dead_code))]+fn shows_dock_icon(window_visible: bool, has_tray: bool, hide_dock_icon: bool) -> bool {+    window_visible || !(has_tray && hide_dock_icon)+}+ fn on_close(has_tray: bool, close_to_tray: bool, window_hides: bool) -> OnClose {     if !(has_tray && close_to_tray) {         OnClose::Quit@@ -424,6 +433,10 @@ impl Host for DesktopHost {         cfg!(target_os = "macos")     } +    fn has_dock(&self) -> bool {+        cfg!(target_os = "macos")+    }+     fn login_item(&self) -> Option<bool> {         Some(login::is_enabled())     }@@ -625,6 +638,9 @@ struct Shell<'a> {     /// The "show on the current desktop" setting last applied to the window (macOS).     #[cfg_attr(not(target_os = "macos"), allow(dead_code))]     applied_follow: Option<bool>,+    /// Whether the Dock icon was last shown (macOS).+    #[cfg_attr(not(target_os = "macos"), allow(dead_code))]+    applied_dock: Option<bool>, }  impl<'a> Shell<'a> {@@ -632,7 +648,7 @@ impl<'a> Shell<'a> {         let window_hides = window_can_hide(cc.display_handle().ok().map(|d| d.as_raw()));         // Started hidden at login but there's no tray to come back from: show the window.         let visible = !hidden || launcher.tray.is_none();-        let mut shell = Self { launcher, window_hides, visible: true, quitting: false, applied_follow: None };+        let mut shell = Self { launcher, window_hides, visible: true, quitting: false, applied_follow: None, applied_dock: None };         if !visible {             shell.put_away(&cc.egui_ctx);         } else if hidden {@@ -695,6 +711,16 @@ impl eframe::App for Shell<'_> {                 Request::Quit => self.quit(ctx),             }         }+        // After the requests, so a window being shown gets its Dock icon back before it comes out.+        #[cfg(target_os = "macos")]+        {+            let settings = &self.launcher.ui.settings;+            let on = shows_dock_icon(self.visible, self.launcher.tray.is_some(), settings.hide_dock_icon);+            if self.applied_dock != Some(on) {+                dock::show_icon(on);+                self.applied_dock = Some(on);+            }+        }         // The close button puts the window away in the tray unless the user chose to quit. That is         // the window manager's button or the header's own (no decorations), which asks to close         // with `ViewportCommand::Close` and arrives here the same way.@@ -837,6 +863,15 @@ mod tests {         }     } +    #[test]+    fn the_dock_icon_hides_only_while_the_window_is_away_in_the_menu_bar() {+        assert!(!shows_dock_icon(false, true, true));+        // The window is out, the user keeps the icon, or there's no menu bar icon to come back from.+        assert!(shows_dock_icon(true, true, true));+        assert!(shows_dock_icon(false, true, false));+        assert!(shows_dock_icon(false, false, true));+    }+     #[test]     fn the_tray_alone_waits_until_asked() {         assert_eq!(tray_step(&[]), TrayStep::Stay);@@ -965,7 +1000,8 @@ mod tests {     fn quitting_overrides_a_close_to_the_tray() {         let (mut launcher, _show, _) = launcher();         let ctx = launcher.windowless.clone();-        let mut shell = Shell { launcher: &mut launcher, window_hides: false, visible: true, quitting: false, applied_follow: None };+        let mut shell =+            Shell { launcher: &mut launcher, window_hides: false, visible: true, quitting: false, applied_follow: None, applied_dock: None };         shell.put_away(&ctx);         assert!(shell.launcher.closed_to_tray);         shell.quit(&ctx);@@ -976,7 +1012,8 @@ mod tests {     fn a_window_that_hides_is_not_closed_to_the_tray() {         let (mut launcher, _show, _) = launcher();         let ctx = launcher.windowless.clone();-        let mut shell = Shell { launcher: &mut launcher, window_hides: true, visible: true, quitting: false, applied_follow: None };+        let mut shell =+            Shell { launcher: &mut launcher, window_hides: true, visible: true, quitting: false, applied_follow: None, applied_dock: None };         shell.put_away(&ctx);         assert!(!shell.visible);         assert!(!shell.launcher.closed_to_tray);diff --git a/crates/native/src/self_update.rs b/crates/native/src/self_update.rs--- a/crates/native/src/self_update.rs+++ b/crates/native/src/self_update.rs@@ -181,7 +181,8 @@ pub fn update(         #[cfg(target_os = "macos")]         Install::MacApp(bundle) => {             let existing = Installed { version: None, source: InstallSource::MacApp, location: bundle.display().to_string(), package: None };-            let placed = install::macos::install_dmg(&LAUNCHER, &file, Some(&existing), None, paths, &install::macos::Expect::release(release));+            let placed =+                install::macos::install_dmg(&LAUNCHER, &file, Some(&existing), None, false, paths, &install::macos::Expect::release(release));             let _ = std::fs::remove_file(&file);             let placed = placed?;             relaunch_after_exit(pid, &[Path::new("/usr/bin/open").as_os_str(), placed.as_os_str()])@@ -371,7 +372,7 @@ mod tests {         let existing = Installed { version: None, source: InstallSource::MacApp, location: current.display().to_string(), package: None };         // An unsigned test bundle: the signature check has its own tests in `install`.         let expect = install::macos::Expect { team: None, version: None };-        let placed = install::macos::install_dmg(&LAUNCHER, &dmg, Some(&existing), None, &Paths::under(dir.path()), &expect).unwrap();+        let placed = install::macos::install_dmg(&LAUNCHER, &dmg, Some(&existing), None, false, &Paths::under(dir.path()), &expect).unwrap();         assert_eq!(placed, current);         assert_eq!(std::fs::read_to_string(current.join("Contents/MacOS/ArtCraft Launcher")).unwrap(), "0.2.0");         assert_eq!(diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs--- a/crates/ui-egui/src/app.rs+++ b/crates/ui-egui/src/app.rs@@ -46,6 +46,11 @@ pub trait Host {     fn has_desktops(&self) -> bool {         false     }+    /// The launcher has a Dock icon (macOS) that can be hidden while the window is put away in+    /// the menu bar.+    fn has_dock(&self) -> bool {+        false+    }     /// `Some(enabled)` where starting at login is supported.     fn login_item(&self) -> Option<bool> {         Nonediff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs--- a/crates/ui-egui/src/dialogs.rs+++ b/crates/ui-egui/src/dialogs.rs@@ -439,6 +439,13 @@ impl LauncherUi {             section(ui, tl!("Running in the background"));             if host.has_tray() {                 ui.checkbox(&mut self.settings.close_to_tray, tl!("Keep running in the tray when the window is closed"));+                if host.has_dock() {+                    ui.add_enabled(+                        self.settings.close_to_tray,+                        egui::Checkbox::new(&mut self.settings.hide_dock_icon, tl!("Hide the Dock icon while the window is closed")),+                    )+                    .on_hover_text(tl!("The launcher stays in the menu bar only. Its Dock icon comes back when you open the window."));+                }             }             if host.has_desktops() {                 ui.checkbox(&mut self.settings.show_on_current_desktop, tl!("Show the window on the current desktop"))diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv--- a/crates/ui-egui/src/i18n/cs.tsv+++ b/crates/ui-egui/src/i18n/cs.tsv@@ -83,6 +83,8 @@ 	Running in the background	Běh na pozadí 	Keep running in the tray when the window is closed	Po zavření okna ponechat spuštěné v oznamovací oblasti 	Show the window on the current desktop	Zobrazit okno na aktuální ploše+	Hide the Dock icon while the window is closed	Skrýt ikonu v Docku, když je okno zavřené+	The launcher stays in the menu bar only. Its Dock icon comes back when you open the window.	Launcher zůstane jen v řádku nabídek. Jeho ikona se do Docku vrátí, jakmile otevřete okno. 	Start ArtCraft Launcher when I log in	Spouštět ArtCraft Launcher po přihlášení 	Community	Komunita 	Questions, ideas and news from the ArtCraft team	Dotazy, nápady a novinky od týmu ArtCraftdiff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv--- a/crates/ui-egui/src/i18n/de.tsv+++ b/crates/ui-egui/src/i18n/de.tsv@@ -96,6 +96,8 @@ 	Running in the background	Im Hintergrund ausführen 	Keep running in the tray when the window is closed	Beim Schließen des Fensters im Infobereich weiterlaufen 	Show the window on the current desktop	Fenster auf dem aktuellen Schreibtisch zeigen+	Hide the Dock icon while the window is closed	Dock-Symbol ausblenden, solange das Fenster geschlossen ist+	The launcher stays in the menu bar only. Its Dock icon comes back when you open the window.	Der Launcher bleibt dann nur in der Menüleiste. Sein Dock-Symbol erscheint wieder, sobald du das Fenster öffnest. 	Start ArtCraft Launcher when I log in	ArtCraft Launcher bei der Anmeldung starten 	Community	Community 	Questions, ideas and news from the ArtCraft team	Fragen, Ideen und Neuigkeiten vom ArtCraft-Teamdiff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv--- a/crates/ui-egui/src/i18n/es.tsv+++ b/crates/ui-egui/src/i18n/es.tsv@@ -96,6 +96,8 @@ 	Running in the background	En segundo plano 	Keep running in the tray when the window is closed	Seguir abierto en la bandeja del sistema al cerrar la ventana 	Show the window on the current desktop	Mostrar la ventana en el escritorio actual+	Hide the Dock icon while the window is closed	Ocultar el icono del Dock mientras la ventana está cerrada+	The launcher stays in the menu bar only. Its Dock icon comes back when you open the window.	ArtCraft Launcher se queda solo en la barra de menús. Su icono vuelve al Dock cuando abres la ventana. 	Start ArtCraft Launcher when I log in	Abrir ArtCraft Launcher al iniciar sesión 	Community	Comunidad 	Questions, ideas and news from the ArtCraft team	Preguntas, ideas y noticias del equipo de ArtCraftdiff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv--- a/crates/ui-egui/src/i18n/fr.tsv+++ b/crates/ui-egui/src/i18n/fr.tsv@@ -94,6 +94,8 @@ 	Running in the background	Exécution en arrière-plan 	Keep running in the tray when the window is closed	Rester actif dans la zone de notification à la fermeture de la fenêtre 	Show the window on the current desktop	Afficher la fenêtre sur le bureau actuel+	Hide the Dock icon while the window is closed	Masquer l'icône du Dock quand la fenêtre est fermée+	The launcher stays in the menu bar only. Its Dock icon comes back when you open the window.	Le launcher reste uniquement dans la barre des menus. Son icône revient dans le Dock quand vous ouvrez la fenêtre. 	Start ArtCraft Launcher when I log in	Lancer ArtCraft Launcher à l'ouverture de session 	Community	Communauté 	Questions, ideas and news from the ArtCraft team	Questions, idées et actualités de l'équipe ArtCraftdiff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv--- a/crates/ui-egui/src/i18n/hu.tsv+++ b/crates/ui-egui/src/i18n/hu.tsv@@ -96,6 +96,8 @@ 	Running in the background	Futtatás a háttérben 	Keep running in the tray when the window is closed	Az ablak bezárásakor kerüljön a tálcára 	Show the window on the current desktop	Ablak megjelenítése az aktuális asztalon+	Hide the Dock icon while the window is closed	Dock-ikon elrejtése, amíg az ablak zárva van+	The launcher stays in the menu bar only. Its Dock icon comes back when you open the window.	Az ArtCraft Launcher ilyenkor csak a menüsorban látható. A Dock-ikonja az ablak megnyitásakor visszatér. 	Start ArtCraft Launcher when I log in	ArtCraft Launcher indítása bejelentkezéskor 	Community	Közösség 	Questions, ideas and news from the ArtCraft team	Kérdések, ötletek és hírek az ArtCraft csapatátóldiff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv--- a/crates/ui-egui/src/i18n/ja.tsv+++ b/crates/ui-egui/src/i18n/ja.tsv@@ -98,6 +98,8 @@ 	Running in the background	バックグラウンドで実行 	Keep running in the tray when the window is closed	ウィンドウを閉じてもトレイで実行し続ける 	Show the window on the current desktop	現在のデスクトップにウィンドウを表示+	Hide the Dock icon while the window is closed	ウィンドウを閉じている間は Dock のアイコンを隠す+	The launcher stays in the menu bar only. Its Dock icon comes back when you open the window.	ランチャーはメニューバーにだけ表示されます。ウィンドウを開くと Dock のアイコンが戻ります。 	Start ArtCraft Launcher when I log in	ログイン時に ArtCraft Launcher を起動 	Community	コミュニティ 	Questions, ideas and news from the ArtCraft team	ArtCraft チームからの質問、アイデア、ニュースdiff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv--- a/crates/ui-egui/src/i18n/pl.tsv+++ b/crates/ui-egui/src/i18n/pl.tsv@@ -97,6 +97,8 @@ 	Running in the background	Działanie w tle 	Keep running in the tray when the window is closed	Po zamknięciu okna działaj w zasobniku systemowym 	Show the window on the current desktop	Pokaż okno na bieżącym pulpicie+	Hide the Dock icon while the window is closed	Ukryj ikonę w Docku, gdy okno jest zamknięte+	The launcher stays in the menu bar only. Its Dock icon comes back when you open the window.	Launcher pozostaje wtedy tylko na pasku menu. Jego ikona wraca do Docka, gdy otworzysz okno. 	Start ArtCraft Launcher when I log in	Uruchom ArtCraft Launcher po zalogowaniu 	Community	Społeczność 	Questions, ideas and news from the ArtCraft team	Pytania, pomysły i nowości od zespołu ArtCraftdiff --git a/crates/ui-egui/src/i18n/pt-BR.tsv b/crates/ui-egui/src/i18n/pt-BR.tsv--- a/crates/ui-egui/src/i18n/pt-BR.tsv+++ b/crates/ui-egui/src/i18n/pt-BR.tsv@@ -84,6 +84,8 @@ 	Running in the background	Execução em segundo plano 	Keep running in the tray when the window is closed	Continuar na bandeja do sistema ao fechar a janela 	Show the window on the current desktop	Mostrar a janela na área de trabalho atual+	Hide the Dock icon while the window is closed	Ocultar o ícone do Dock enquanto a janela estiver fechada+	The launcher stays in the menu bar only. Its Dock icon comes back when you open the window.	O launcher fica apenas na barra de menus. O ícone volta ao Dock quando você abre a janela. 	Start ArtCraft Launcher when I log in	Iniciar o ArtCraft Launcher ao fazer login 	Community	Comunidade 	Questions, ideas and news from the ArtCraft team	Dúvidas, ideias e novidades da equipe ArtCraftdiff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv--- a/crates/ui-egui/src/i18n/ru.tsv+++ b/crates/ui-egui/src/i18n/ru.tsv@@ -94,6 +94,8 @@ 	Running in the background	Работа в фоне 	Keep running in the tray when the window is closed	Оставаться в трее при закрытии окна 	Show the window on the current desktop	Показывать окно на текущем рабочем столе+	Hide the Dock icon while the window is closed	Скрывать значок в Dock, пока окно закрыто+	The launcher stays in the menu bar only. Its Dock icon comes back when you open the window.	Лаунчер остаётся только в строке меню. Значок в Dock появится снова, когда вы откроете окно. 	Start ArtCraft Launcher when I log in	Запускать ArtCraft Launcher при входе в систему 	Community	Сообщество 	Questions, ideas and news from the ArtCraft team	Вопросы, идеи и новости от команды ArtCraftdiff --git a/crates/ui-egui/src/i18n/zh-CN.tsv b/crates/ui-egui/src/i18n/zh-CN.tsv--- a/crates/ui-egui/src/i18n/zh-CN.tsv+++ b/crates/ui-egui/src/i18n/zh-CN.tsv@@ -78,6 +78,8 @@ 	Running in the background	后台运行 	Keep running in the tray when the window is closed	关闭窗口后继续在系统托盘中运行 	Show the window on the current desktop	在当前桌面显示窗口+	Hide the Dock icon while the window is closed	窗口关闭时隐藏程序坞图标+	The launcher stays in the menu bar only. Its Dock icon comes back when you open the window.	启动器只保留在菜单栏中。打开窗口后,程序坞图标会重新出现。 	Start ArtCraft Launcher when I log in	登录时启动 ArtCraft Launcher 	Community	社区 	Questions, ideas and news from the ArtCraft team	与 ArtCraft 团队交流问题、想法和动态diff --git a/crates/ui-egui/src/settings.rs b/crates/ui-egui/src/settings.rs--- a/crates/ui-egui/src/settings.rs+++ b/crates/ui-egui/src/settings.rs@@ -98,6 +98,8 @@ pub struct Settings {     /// macOS: showing the window brings it to the current desktop (Space) instead of switching to     /// the desktop it was on.     pub show_on_current_desktop: bool,+    /// macOS: while the window is put away in the menu bar, the launcher has no Dock icon.+    pub hide_dock_icon: bool,     pub check_every: CheckEvery,     /// Automatically download and install updates when found.     #[serde(default)]@@ -179,6 +181,7 @@ impl Default for Settings {             language: "auto".to_string(),             close_to_tray: true,             show_on_current_desktop: true,+            hide_dock_icon: false,             check_every: CheckEvery::Hour,             auto_update: false,             notify: true,diff --git a/crates/ui-egui/tests/ui.rs b/crates/ui-egui/tests/ui.rs--- a/crates/ui-egui/tests/ui.rs+++ b/crates/ui-egui/tests/ui.rs@@ -1018,6 +1018,62 @@ fn the_current_desktop_setting_is_only_offered_where_the_os_has_desktops() {     assert!(Settings::default().show_on_current_desktop); } +#[test]+fn the_dock_icon_can_be_hidden_while_the_launcher_waits_in_the_menu_bar() {+    const HIDE: &str = "Hide the Dock icon while the window is closed";+    // NoHost has no Dock (Windows, Linux, web): no setting. It's off by default, so the Dock icon stays.+    let (mut h, _) = harness(ViewMode::Grid, egui::Theme::Dark, true);+    h.run_steps(6);+    h.get_by_label("Settings").click();+    h.run_steps(4);+    assert!(h.query_by_label(HIDE).is_none());+    assert!(!Settings::default().hide_dock_icon);++    // macOS with a menu bar icon (DesktopHost::has_dock).+    struct DockHost;+    impl Host for DockHost {+        fn has_tray(&self) -> bool {+            true+        }+        fn has_dock(&self) -> bool {+            true+        }+    }+    let mut backend = Some(Fake {+        events: events(),+        sent: Rc::default(),+        manage: true,+        pictures: Rc::default(),+        asked: Vec::new(),+        clock: Rc::new(Cell::new(NOW)),+    });+    let mut h = Harness::builder().with_size(egui::vec2(1180.0, 900.0)).build_ui_state(+        move |ui, state: &mut Option<LauncherUi>| {+            if state.is_none()+                && let Some(b) = backend.take()+            {+                *state = Some(LauncherUi::new(ui.ctx(), Box::new(b), Settings { notify: false, ..Settings::default() }));+            }+            if let Some(l) = state.as_mut() {+                l.logic(&ui.ctx().clone(), &mut DockHost);+                l.ui(ui, &mut DockHost);+            }+        },+        None,+    );+    let hide_dock_icon = |h: &Harness<'_, Option<LauncherUi>>| h.state().as_ref().is_some_and(|l| l.settings.hide_dock_icon);+    h.run_steps(6);+    h.get_by_label("Settings").click();+    h.run_steps(4);+    h.get_by_label(HIDE).click();+    h.run_steps(2);+    assert!(hide_dock_icon(&h));+    // Only meaningful while closing keeps the launcher in the menu bar.+    h.get_by_label("Keep running in the tray when the window is closed").click();+    h.run_steps(2);+    assert!(h.get_by_label(HIDE).accesskit_node().is_disabled());+}+ #[test] fn feed_announcements_and_alerts_show_until_dismissed() {     use artcraft_launcher_core::feed::Notice;