diff --git a/.changes/nsis-start-menu-folder-breaking.md b/.changes/nsis-start-menu-folder-breaking.md new file mode 100644 index 000000000..324f4e350 --- /dev/null +++ b/.changes/nsis-start-menu-folder-breaking.md @@ -0,0 +1,5 @@ +--- +"tauri-bundler": "patch:breaking" +--- + +Changed NSIS start menu shortcut to be placed directly inside `%AppData%\Microsoft\Windows\Start Menu\Programs` without an additional folder. You can get the old behavior by setting `bundle > nsis > startMenuFolder` to the same value as your `productName` diff --git a/.changes/nsis-start-menu-folder.md b/.changes/nsis-start-menu-folder.md new file mode 100644 index 000000000..f7fa40bcc --- /dev/null +++ b/.changes/nsis-start-menu-folder.md @@ -0,0 +1,6 @@ +--- +"tauri-utils": "patch:feat" +"tauri-bundler": "patch:feat" +--- + +Add `bundle > nsis > startMenuFolder` option to customize start menu folder for NSIS installer diff --git a/core/tauri-config-schema/schema.json b/core/tauri-config-schema/schema.json index 207db3967..4a51c5adf 100644 --- a/core/tauri-config-schema/schema.json +++ b/core/tauri-config-schema/schema.json @@ -2306,6 +2306,13 @@ } ] }, + "startMenuFolder": { + "description": "Set the folder name for the start menu shortcut.\n\n Use this option if you have multiple apps and wish to group their shortcuts under one folder\n or if you generally prefer to set your shortcut inside a folder.\n\n Examples:\n - `AwesomePublisher`, shortcut will be placed in `%AppData%\\Microsoft\\Windows\\Start Menu\\Programs\\AwesomePublisher\\.lnk`\n - If unset, shortcut will be placed in `%AppData%\\Microsoft\\Windows\\Start Menu\\Programs\\.lnk`", + "type": [ + "string", + "null" + ] + }, "installerHooks": { "description": "A path to a `.nsh` file that contains special NSIS macros to be hooked into the\n main installer.nsi script.\n\n Supported hooks are:\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\n\n\n ### Example\n\n ```nsh\n !define NSIS_HOOK_PREINSTALL \"NSIS_HOOK_PREINSTALL_\"\n !macro NSIS_HOOK_PREINSTALL_\n MessageBox MB_OK \"PreInstall\"\n !macroend\n\n !define NSIS_HOOK_POSTINSTALL \"NSIS_HOOK_POSTINSTALL_\"\n !macro NSIS_HOOK_POSTINSTALL_\n MessageBox MB_OK \"PostInstall\"\n !macroend\n\n !define NSIS_HOOK_PREUNINSTALL \"NSIS_HOOK_PREUNINSTALL_\"\n !macro NSIS_HOOK_PREUNINSTALL_\n MessageBox MB_OK \"PreUnInstall\"\n !macroend\n\n !define NSIS_HOOK_POSTUNINSTALL \"NSIS_HOOK_POSTUNINSTALL_\"\n !macro NSIS_HOOK_POSTUNINSTALL_\n MessageBox MB_OK \"PostUninstall\"\n !macroend\n\n ```", "type": [ diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 076baaec8..3c9edaaad 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -776,6 +776,16 @@ pub struct NsisConfig { /// See #[serde(default)] pub compression: NsisCompression, + /// Set the folder name for the start menu shortcut. + /// + /// Use this option if you have multiple apps and wish to group their shortcuts under one folder + /// or if you generally prefer to set your shortcut inside a folder. + /// + /// Examples: + /// - `AwesomePublisher`, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\AwesomePublisher\.lnk` + /// - If unset, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\.lnk` + #[serde(alias = "start-menu-folder")] + pub start_menu_folder: Option, /// A path to a `.nsh` file that contains special NSIS macros to be hooked into the /// main installer.nsi script. /// diff --git a/tooling/bundler/src/bundle/settings.rs b/tooling/bundler/src/bundle/settings.rs index b77251109..43fde2fda 100644 --- a/tooling/bundler/src/bundle/settings.rs +++ b/tooling/bundler/src/bundle/settings.rs @@ -417,6 +417,15 @@ pub struct NsisSettings { pub display_language_selector: bool, /// Set compression algorithm used to compress files in the installer. pub compression: NsisCompression, + /// Set the folder name for the start menu shortcut. + /// + /// Use this option if you have multiple apps and wish to group their shortcuts under one folder + /// or if you generally prefer to set your shortcut inside a folder. + /// + /// Examples: + /// - `AwesomePublisher`, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\AwesomePublisher\.lnk` + /// - If unset, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\.lnk` + pub start_menu_folder: Option, /// A path to a `.nsh` file that contains special NSIS macros to be hooked into the /// main installer.nsi script. /// @@ -831,9 +840,7 @@ impl Settings { /// Returns the path to the specified binary. pub fn binary_path(&self, binary: &BundleBinary) -> PathBuf { - let mut path = self.project_out_directory.clone(); - path.push(binary.name()); - path + self.project_out_directory.join(binary.name()) } /// Returns the list of binaries to bundle. diff --git a/tooling/bundler/src/bundle/windows/nsis.rs b/tooling/bundler/src/bundle/windows/nsis.rs index 6bcf36508..aaafaef39 100644 --- a/tooling/bundler/src/bundle/windows/nsis.rs +++ b/tooling/bundler/src/bundle/windows/nsis.rs @@ -253,6 +253,10 @@ fn build_nsis_app_installer( let installer_hooks = dunce::canonicalize(installer_hooks)?; data.insert("installer_hooks", to_json(installer_hooks)); } + + if let Some(start_menu_folder) = &nsis.start_menu_folder { + data.insert("start_menu_folder", to_json(start_menu_folder)); + } } let compression = settings @@ -330,7 +334,12 @@ fn build_nsis_app_installer( let main_binary_path = settings.binary_path(main_binary).with_extension("exe"); data.insert( "main_binary_name", - to_json(main_binary.name().replace(".exe", "")), + to_json( + main_binary_path + .file_stem() + .and_then(|file_name| file_name.to_str()) + .unwrap_or_else(|| main_binary.name()), + ), ); data.insert("main_binary_path", to_json(&main_binary_path)); diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index bb8d47d66..2d201cadd 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -9,7 +9,7 @@ ManifestDPIAwareness PerMonitorV2 !if "{{compression}}" == "none" SetCompress off !else - ; Set the compression algorithm. Default is LZMA. + ; Set the compression algorithm. We default to LZMA. SetCompressor /SOLID "{{compression}}" !endif @@ -57,6 +57,7 @@ ${StrLoc} !define MANUPRODUCTKEY "Software\${MANUFACTURER}\${PRODUCTNAME}" !define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}" !define ESTIMATEDSIZE "{{estimated_size}}" +!define STARTMENUFOLDER "{{start_menu_folder}}" Var PassiveMode Var UpdateMode @@ -337,7 +338,12 @@ FunctionEnd ; 6. Start menu shortcut page Var AppStartMenuFolder -!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive +!if "${STARTMENUFOLDER}" != "" + !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive + !define MUI_STARTMENUPAGE_DEFAULTFOLDER "${STARTMENUFOLDER}" +!else + !define MUI_PAGE_CUSTOMFUNCTION_PRE Skip +!endif !insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder ; 7. Installation page @@ -722,13 +728,27 @@ Section Uninstall ; Remove start menu shortcut !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder - !insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" - Delete "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" - RMDir "$SMPROGRAMS\$AppStartMenuFolder" + !insertmacro IsShortcutTarget "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + Pop $0 + ${If} $0 = 1 + !insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" + Delete "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" + RMDir "$SMPROGRAMS\$AppStartMenuFolder" + ${EndIf} + !insertmacro IsShortcutTarget "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + Pop $0 + ${If} $0 = 1 + !insertmacro UnpinShortcut "$SMPROGRAMS\${PRODUCTNAME}.lnk" + Delete "$SMPROGRAMS\${PRODUCTNAME}.lnk" + ${EndIf} ; Remove desktop shortcuts - !insertmacro UnpinShortcut "$DESKTOP\${PRODUCTNAME}.lnk" - Delete "$DESKTOP\${PRODUCTNAME}.lnk" + !insertmacro IsShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + Pop $0 + ${If} $0 = 1 + !insertmacro UnpinShortcut "$DESKTOP\${PRODUCTNAME}.lnk" + Delete "$DESKTOP\${PRODUCTNAME}.lnk" + ${EndIf} ${EndIf} ; Remove registry information for add/remove programs @@ -767,6 +787,10 @@ Function RestorePreviousInstallLocation StrCpy $INSTDIR $4 FunctionEnd +Function Skip + Abort +FunctionEnd + Function SkipIfPassive ${IfThen} $PassiveMode = 1 ${|} Abort ${|} FunctionEnd @@ -774,8 +798,23 @@ FunctionEnd Function CreateOrUpdateStartMenuShortcut ; We used to use product name as MAINBINARYNAME ; migrate old shortcuts to target the new MAINBINARYNAME - ${If} ${FileExists} "$DESKTOP\${PRODUCTNAME}.lnk" - !insertmacro SetShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + StrCpy $R0 0 + + !insertmacro IsShortcutTarget "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCTNAME}.exe" + Pop $0 + ${If} $0 = 1 + !insertmacro SetShortcutTarget "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + StrCpy $R0 1 + ${EndIf} + + !insertmacro IsShortcutTarget "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCTNAME}.exe" + Pop $0 + ${If} $0 = 1 + !insertmacro SetShortcutTarget "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + StrCpy $R0 1 + ${EndIf} + + ${If} $R0 = 1 Return ${EndIf} @@ -785,16 +824,23 @@ Function CreateOrUpdateStartMenuShortcut Return ${EndIf} - CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder" - CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" - !insertmacro SetLnkAppUserModelId "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" + !if "${STARTMENUFOLDER}" != "" + CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder" + CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + !insertmacro SetLnkAppUserModelId "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" + !else + CreateShortcut "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + !insertmacro SetLnkAppUserModelId "$SMPROGRAMS\${PRODUCTNAME}.lnk" + !endif FunctionEnd Function CreateOrUpdateDesktopShortcut ; We used to use product name as MAINBINARYNAME ; migrate old shortcuts to target the new MAINBINARYNAME - ${If} ${FileExists} "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" - !insertmacro SetShortcutTarget "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + !insertmacro IsShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCTNAME}.exe" + Pop $0 + ${If} $0 = 1 + !insertmacro SetShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" Return ${EndIf} diff --git a/tooling/bundler/src/bundle/windows/templates/utils.nsh b/tooling/bundler/src/bundle/windows/templates/utils.nsh index e27c95a61..1040b76f5 100644 --- a/tooling/bundler/src/bundle/windows/templates/utils.nsh +++ b/tooling/bundler/src/bundle/windows/templates/utils.nsh @@ -131,3 +131,42 @@ ${IUnknown::Release} $0 "" ${EndIf} !macroend + +!define /ifndef MAX_PATH 260 +!define /ifndef SLGP_RAWPATH 0x4 + +; Test if a .lnk shortcut's target is target, +; use Pop to get the result, 1 is yes, 0 is no, +; note that this macro modifies $0, $1, $2, $3 +; +; Exmaple usage: +; !insertmacro "IsShortCutTarget" "C:\Users\Public\Desktop\App.lnk" "C:\Program Files\App\App.exe" +; Pop $0 +; ${If} $0 = 1 +; MessageBox MB_OK "shortcut target matches" +; ${EndIf} +!macro IsShortcutTarget shortcut target + ; $0: IShellLink + ; $1: IPersistFile + ; $2: Target path + ; $3: Return value + + StrCpy $3 0 + !insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r0 "" + ${If} $0 P<> 0 + ${IUnknown::QueryInterface} $0 '("${IID_IPersistFile}", .r1)' + ${If} $1 P<> 0 + ${IPersistFile::Load} $1 '("${shortcut}", ${STGM_READ})' + System::Alloc MAX_PATH + Pop $2 + ${IShellLink::GetPath} $0 '(.r2, ${MAX_PATH}, 0, ${SLGP_RAWPATH})' + ${If} $2 == "${target}" + StrCpy $3 1 + ${EndIf} + System::Free $2 + ${IUnknown::Release} $1 "" + ${EndIf} + ${IUnknown::Release} $0 "" + ${EndIf} + Push $3 +!macroend diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index 207db3967..4a51c5adf 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -2306,6 +2306,13 @@ } ] }, + "startMenuFolder": { + "description": "Set the folder name for the start menu shortcut.\n\n Use this option if you have multiple apps and wish to group their shortcuts under one folder\n or if you generally prefer to set your shortcut inside a folder.\n\n Examples:\n - `AwesomePublisher`, shortcut will be placed in `%AppData%\\Microsoft\\Windows\\Start Menu\\Programs\\AwesomePublisher\\.lnk`\n - If unset, shortcut will be placed in `%AppData%\\Microsoft\\Windows\\Start Menu\\Programs\\.lnk`", + "type": [ + "string", + "null" + ] + }, "installerHooks": { "description": "A path to a `.nsh` file that contains special NSIS macros to be hooked into the\n main installer.nsi script.\n\n Supported hooks are:\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\n\n\n ### Example\n\n ```nsh\n !define NSIS_HOOK_PREINSTALL \"NSIS_HOOK_PREINSTALL_\"\n !macro NSIS_HOOK_PREINSTALL_\n MessageBox MB_OK \"PreInstall\"\n !macroend\n\n !define NSIS_HOOK_POSTINSTALL \"NSIS_HOOK_POSTINSTALL_\"\n !macro NSIS_HOOK_POSTINSTALL_\n MessageBox MB_OK \"PostInstall\"\n !macroend\n\n !define NSIS_HOOK_PREUNINSTALL \"NSIS_HOOK_PREUNINSTALL_\"\n !macro NSIS_HOOK_PREUNINSTALL_\n MessageBox MB_OK \"PreUnInstall\"\n !macroend\n\n !define NSIS_HOOK_POSTUNINSTALL \"NSIS_HOOK_POSTUNINSTALL_\"\n !macro NSIS_HOOK_POSTUNINSTALL_\n MessageBox MB_OK \"PostUninstall\"\n !macroend\n\n ```", "type": [ diff --git a/tooling/cli/src/helpers/config.rs b/tooling/cli/src/helpers/config.rs index 525746264..13fd3823e 100644 --- a/tooling/cli/src/helpers/config.rs +++ b/tooling/cli/src/helpers/config.rs @@ -105,6 +105,7 @@ pub fn nsis_settings(config: NsisConfig) -> tauri_bundler::NsisSettings { custom_language_files: config.custom_language_files, display_language_selector: config.display_language_selector, compression: config.compression, + start_menu_folder: config.start_menu_folder, installer_hooks: config.installer_hooks, } }