Browse / github.com/storytold/craft-launcher / patch
feat(windows): add "Add desktop shortcut" to the app ⋯ menu
feat(windows): add "Add desktop shortcut" to the app ⋯ menu ## What Adds **Add desktop shortcut** to the ⋯ menu for installed apps on Windows. ## Why Some people want a desktop icon for a Craft app. This lets them create one from the launcher when they want it. ## How - On Windows, each installed app’s ⋯ menu gets a new item: **Add desktop shortcut** - Clicking it puts a shortcut on the Desktop that opens that app - The option only shows on Windows, and only for apps that are already installed - Other platforms (macOS, Linux, web) are unchanged <img width="1392" height="940" alt="Screenshot 2026-10-26 013826" src="https://github.com/user
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- yugisch (imported from pull request #897, open)
- Base
f0cb3b0439d15cae02d806e5ddc0132fd487c89c- Size
- +103 −4 in 15 files
- Review
- pending
- Shared
- 2026-10-28
- Id
7bc9b2f4984beff63d99cdfc
How it went
No other operator has reported using it yet.
Diff
raw · apps/artcraft-launcher-web/src/web.rs crates/core/src/protocol.rs crates/native/src/backend.rs crates/native/src/process.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/views.rs
@@ -320,7 +320,12 @@ impl Backend for WebBackend { } fn capabilities(&self) -> Capabilities {- Capabilities { manage_apps: false, reveal_label: "", self_update: false }+ Capabilities {+ manage_apps: false,+ reveal_label: "",+ self_update: false,+ desktop_shortcuts: false,+ } } fn platform(&self) -> Option<Platform> {@@ -39,6 +39,10 @@ pub enum Command { Reveal { app: String, },+ /// Create a desktop shortcut for an installed app (Windows).+ CreateDesktopShortcut {+ app: String,+ }, /// Pause a running download, keeping what's downloaded so far (`Install` resumes it). Pause { app: String,@@ -250,6 +254,8 @@ pub struct Capabilities { /// instead. A backend may only know this once it has found a newer launcher, so the UI reads /// it again on [`Event::LauncherUpdate`]. pub self_update: bool,+ /// Offer "Add desktop shortcut" for installed apps (Windows desktop only).+ pub desktop_shortcuts: bool, } /// The UI's view of a backend.@@ -1082,6 +1082,22 @@ impl Backend for NativeBackend { } } }+ Command::CreateDesktopShortcut { app } => {+ let Some(def) = CraftApp::by_id(&app) else { return };+ let result = match self.installed(&app) {+ Some(inst) => {+ let path = std::path::Path::new(&inst.location);+ crate::process::create_desktop_shortcut(def.name, path)+ }+ None => Err(format!("{} isn't installed", def.name)),+ };+ if let Err(e) = result {+ self.shared.emit(Event::JobFinished {+ app,+ result: Err(format!("Couldn't create the desktop shortcut: {e}")),+ });+ }+ } Command::UpdateLauncher => self.update_launcher(), Command::FetchScreenshots { app } => self.fetch_screenshots(&app), Command::Configure(options) => {@@ -1113,7 +1129,12 @@ impl Backend for NativeBackend { Some(Os::Windows) => "Show in Explorer", _ => "Show in Files", };- Capabilities { manage_apps: true, reveal_label, self_update: self.shared.self_install.get().is_some_and(Option::is_some) }+ Capabilities {+ manage_apps: true,+ reveal_label,+ self_update: self.shared.self_install.get().is_some_and(Option::is_some),+ desktop_shortcuts: matches!(self.platform.map(|p| p.os), Some(Os::Windows)),+ } } fn platform(&self) -> Option<Platform> {@@ -433,6 +433,49 @@ fn explorer_select_arg(path: &Path) -> Option<std::ffi::OsString> { Some(arg) } +/// Creates a `.lnk` on the user's Desktop that opens `target`.+///+/// Windows only. `name` is the shortcut's file name without `.lnk` (usually the app's display name).+#[cfg(windows)]+pub fn create_desktop_shortcut(name: &str, target: &Path) -> Result<(), String> {+ if !target.exists() {+ return Err(format!("{} isn't where it was installed", target.display()));+ }+ let desktop = dirs::desktop_dir().ok_or_else(|| "couldn't find the Desktop folder".to_string())?;+ let lnk = desktop.join(format!("{name}.lnk"));+ let target = dunce::canonicalize(target).unwrap_or_else(|_| target.to_path_buf());+ let work_dir = target.parent().map(Path::to_path_buf).unwrap_or_else(|| PathBuf::from("."));++ let target_s = target.to_str().ok_or_else(|| "path isn't valid Unicode".to_string())?;+ let lnk_s = lnk.to_str().ok_or_else(|| "path isn't valid Unicode".to_string())?;+ let dir_s = work_dir.to_str().ok_or_else(|| "path isn't valid Unicode".to_string())?;++ // Single-quote escape for PowerShell string literals.+ let esc = |s: &str| s.replace('\'', "''");+ let script = format!(+ "$s = (New-Object -ComObject WScript.Shell).CreateShortcut('{}'); \+ $s.TargetPath = '{}'; \+ $s.WorkingDirectory = '{}'; \+ $s.Save()",+ esc(lnk_s),+ esc(target_s),+ esc(dir_s),+ );++ let mut cmd = command("powershell.exe");+ cmd.args(["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", &script]);+ run_command(cmd, "powershell", Duration::from_secs(20)).map(|_| ())?;+ if !lnk.exists() {+ return Err("the shortcut was not created".into());+ }+ Ok(())+}++#[cfg(not(windows))]+pub fn create_desktop_shortcut(_name: &str, _target: &Path) -> Result<(), String> {+ Err("desktop shortcuts are only supported on Windows".into())+}+ /// Whether a process started from inside `path` (an exe, an AppImage, or a `.app`) is running. pub fn is_running(path: &Path) -> bool { if cfg!(windows) {@@ -58,6 +58,7 @@ Show in Explorer Zobrazit v Průzkumníku Show in Files Zobrazit v Souborech Reinstall Znovu nainstalovat+ Add desktop shortcut Přidat zástupce na plochu Uninstall… Odinstalovat… All releases Všechna vydání ArtCraft Launcher {version} is available. Je k dispozici ArtCraft Launcher {version}.@@ -244,6 +245,7 @@ column Latest Nejnovější column Released Vydáno filter Installed Nainstalováno App details Podrobnosti o aplikaci+ More actions for {name} Další akce pro {name} Close (Esc) Zavřít (Esc) Features Funkce Latest release Nejnovější verze@@ -71,6 +71,7 @@ Show in Explorer Im Explorer zeigen Show in Files Im Dateimanager zeigen Reinstall Neu installieren+ Add desktop shortcut Desktop-Verknüpfung hinzufügen Uninstall… Deinstallieren… All releases Alle Versionen ArtCraft Launcher {version} is available. ArtCraft Launcher {version} ist verfügbar.@@ -340,6 +341,7 @@ filter Installed Installiert ArtCraft on GitHub ArtCraft auf GitHub Join the ArtCraft Discord Dem ArtCraft-Discord beitreten App details App-Details+ More actions for {name} Weitere Aktionen für {name} Close (Esc) Schließen (Esc) Features Funktionen Latest release Neueste Version@@ -71,6 +71,7 @@ Show in Explorer Mostrar en el Explorador Show in Files Mostrar en Archivos Reinstall Reinstalar+ Add desktop shortcut Añadir acceso directo al escritorio Uninstall… Desinstalar… All releases Todas las versiones ArtCraft Launcher {version} is available. ArtCraft Launcher {version} está disponible.@@ -257,6 +258,7 @@ column Latest Última column Released Publicada filter Installed Instaladas App details Detalles de la app+ More actions for {name} Más acciones para {name} Close (Esc) Cerrar (Esc) Features Funciones Latest release Última versión@@ -69,6 +69,7 @@ Show in Explorer Afficher dans l'Explorateur Show in Files Afficher dans Fichiers Reinstall Réinstaller+ Add desktop shortcut Ajouter un raccourci bureau Uninstall… Désinstaller… All releases Toutes les versions ArtCraft Launcher {version} is available. ArtCraft Launcher {version} est disponible.@@ -338,6 +339,7 @@ filter Installed Installées ArtCraft on GitHub ArtCraft sur GitHub Join the ArtCraft Discord Rejoindre le Discord d'ArtCraft App details Détails de l'application+ More actions for {name} Autres actions pour {name} Close (Esc) Fermer (Esc) Features Fonctionnalités Latest release Dernière version@@ -71,6 +71,7 @@ Show in Explorer Megjelenítés a Fájlkezelőben Show in Files Megjelenítés a fájlkezelőben Reinstall Újratelepítés+ Add desktop shortcut Asztali parancsikon hozzáadása Uninstall… Eltávolítás… All releases Minden kiadás ArtCraft Launcher {version} is available. Elérhető az ArtCraft Launcher új verziója: {version}.@@ -268,6 +269,7 @@ column Released Dátum # Contextual: the filter tab names a group (the status pill says "Installed") filter Installed Telepítve App details Alkalmazás részletei+ More actions for {name} További műveletek: {name} Close (Esc) Bezárás (Esc) Features Funkciók Latest release Legújabb kiadás@@ -73,6 +73,7 @@ Show in Explorer エクスプローラーで表示 Show in Files ファイルマネージャーで表示 Reinstall 再インストール+ Add desktop shortcut デスクトップにショートカットを追加 Uninstall… アンインストール… All releases すべてのリリース ArtCraft Launcher {version} is available. ArtCraft Launcher {version} が利用可能です。@@ -270,6 +271,7 @@ column Released 公開日 # Contextual: the filter tab names a group (the status pill says "Installed") filter Installed インストール済み App details アプリの詳細+ More actions for {name} {name} のその他の操作 Close (Esc) 閉じる (Esc) Features 機能 Latest release 最新リリース@@ -72,6 +72,7 @@ Show in Explorer Pokaż w Eksploratorze plików Show in Files Pokaż w menedżerze plików Reinstall Przeinstaluj+ Add desktop shortcut Dodaj skrót na pulpicie Uninstall… Odinstaluj… All releases Wszystkie wydania ArtCraft Launcher {version} is available. Dostępna jest nowa wersja ArtCraft Launcher ({version}).@@ -269,6 +270,7 @@ column Released Data wydania # Contextual: the filter tab names a group (the status pill says "Installed") filter Installed Zainstalowane App details Szczegóły aplikacji+ More actions for {name} Więcej działań: {name} Close (Esc) Zamknij (Esc) Features Funkcje Latest release Najnowsze wydanie@@ -59,6 +59,7 @@ Show in Explorer Mostrar no Explorer Show in Files Mostrar no gerenciador de arquivos Reinstall Reinstalar+ Add desktop shortcut Adicionar atalho na área de trabalho Uninstall… Desinstalar… All releases Todos os lançamentos ArtCraft Launcher {version} is available. ArtCraft Launcher {version} está disponível.@@ -328,6 +329,7 @@ filter Installed Instalados ArtCraft on GitHub ArtCraft no GitHub Join the ArtCraft Discord Entrar no Discord da ArtCraft App details Detalhes do aplicativo+ More actions for {name} Mais ações para {name} Close (Esc) Fechar (Esc) Features Recursos Latest release Último lançamento@@ -69,6 +69,7 @@ Show in Explorer Показать в Проводнике Show in Files Показать в файловом менеджере Reinstall Переустановить+ Add desktop shortcut Добавить ярлык на рабочий стол Uninstall… Удалить… All releases Все релизы ArtCraft Launcher {version} is available. Доступен ArtCraft Launcher {version}.@@ -336,6 +337,7 @@ filter Installed Установлены ArtCraft on GitHub ArtCraft на GitHub Join the ArtCraft Discord Присоединиться к Discord ArtCraft App details О приложении+ More actions for {name} Другие действия для {name} Close (Esc) Закрыть (Esc) Features Возможности Latest release Последний релиз@@ -53,6 +53,7 @@ Discard download 丢弃下载 Release notes 更新说明 Reinstall 重新安装+ Add desktop shortcut 添加桌面快捷方式 Uninstall… 卸载… All releases 所有版本 ArtCraft Launcher {version} is available. ArtCraft Launcher {version} 已可更新。@@ -295,6 +296,7 @@ figcraft Interface design and prototyping 界面设计与原型制作 Flatpak bundle Flatpak 包 Tarball (.tar.gz) 压缩包 (.tar.gz) App details 应用详情+ More actions for {name} {name} 的更多操作 Close (Esc) 关闭(Esc) Features 功能特性 Latest release 最新发布@@ -991,8 +991,8 @@ impl LauncherUi { }); } - /// The menu items for an installed app: open, reveal in the file manager, reinstall, and- /// uninstall (which asks first).+ /// The menu items for an installed app: open, reveal in the file manager, desktop shortcut,+ /// reinstall, and uninstall (which asks first). fn menu_installed(&mut self, ui: &mut Ui, status: &AppStatus, id: &str, reveal: &'static str) { if ui.button(tl!("Open")).clicked() { self.launch_app(id);@@ -1002,6 +1002,10 @@ impl LauncherUi { self.send(Command::Reveal { app: id.to_string() }); ui.close(); }+ if self.caps.desktop_shortcuts && ui.button(tl!("Add desktop shortcut")).clicked() {+ self.send(Command::CreateDesktopShortcut { app: id.to_string() });+ ui.close();+ } if *status == AppStatus::UpToDate && ui.button(tl!("Reinstall")).clicked() { self.install_app(id); ui.close();