Skip to content

Commit f21029b

Browse files
feat!(nsis): add an option to customize start menu folder (#9994)
1 parent a15a975 commit f21029b

File tree

10 files changed

+155
-18
lines changed

10 files changed

+155
-18
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri-bundler": "patch:breaking"
3+
---
4+
5+
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`

.changes/nsis-start-menu-folder.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-utils": "patch:feat"
3+
"tauri-bundler": "patch:feat"
4+
---
5+
6+
Add `bundle > nsis > startMenuFolder` option to customize start menu folder for NSIS installer

core/tauri-config-schema/schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,6 +2306,13 @@
23062306
}
23072307
]
23082308
},
2309+
"startMenuFolder": {
2310+
"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\\<your-app>.lnk`\n - If unset, shortcut will be placed in `%AppData%\\Microsoft\\Windows\\Start Menu\\Programs\\<your-app>.lnk`",
2311+
"type": [
2312+
"string",
2313+
"null"
2314+
]
2315+
},
23092316
"installerHooks": {
23102317
"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 ```",
23112318
"type": [

core/tauri-utils/src/config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,16 @@ pub struct NsisConfig {
776776
/// See <https://nsis.sourceforge.io/Reference/SetCompressor>
777777
#[serde(default)]
778778
pub compression: NsisCompression,
779+
/// Set the folder name for the start menu shortcut.
780+
///
781+
/// Use this option if you have multiple apps and wish to group their shortcuts under one folder
782+
/// or if you generally prefer to set your shortcut inside a folder.
783+
///
784+
/// Examples:
785+
/// - `AwesomePublisher`, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\AwesomePublisher\<your-app>.lnk`
786+
/// - If unset, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\<your-app>.lnk`
787+
#[serde(alias = "start-menu-folder")]
788+
pub start_menu_folder: Option<String>,
779789
/// A path to a `.nsh` file that contains special NSIS macros to be hooked into the
780790
/// main installer.nsi script.
781791
///

tooling/bundler/src/bundle/settings.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,15 @@ pub struct NsisSettings {
417417
pub display_language_selector: bool,
418418
/// Set compression algorithm used to compress files in the installer.
419419
pub compression: NsisCompression,
420+
/// Set the folder name for the start menu shortcut.
421+
///
422+
/// Use this option if you have multiple apps and wish to group their shortcuts under one folder
423+
/// or if you generally prefer to set your shortcut inside a folder.
424+
///
425+
/// Examples:
426+
/// - `AwesomePublisher`, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\AwesomePublisher\<your-app>.lnk`
427+
/// - If unset, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\<your-app>.lnk`
428+
pub start_menu_folder: Option<String>,
420429
/// A path to a `.nsh` file that contains special NSIS macros to be hooked into the
421430
/// main installer.nsi script.
422431
///
@@ -831,9 +840,7 @@ impl Settings {
831840

832841
/// Returns the path to the specified binary.
833842
pub fn binary_path(&self, binary: &BundleBinary) -> PathBuf {
834-
let mut path = self.project_out_directory.clone();
835-
path.push(binary.name());
836-
path
843+
self.project_out_directory.join(binary.name())
837844
}
838845

839846
/// Returns the list of binaries to bundle.

tooling/bundler/src/bundle/windows/nsis.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ fn build_nsis_app_installer(
253253
let installer_hooks = dunce::canonicalize(installer_hooks)?;
254254
data.insert("installer_hooks", to_json(installer_hooks));
255255
}
256+
257+
if let Some(start_menu_folder) = &nsis.start_menu_folder {
258+
data.insert("start_menu_folder", to_json(start_menu_folder));
259+
}
256260
}
257261

258262
let compression = settings
@@ -330,7 +334,12 @@ fn build_nsis_app_installer(
330334
let main_binary_path = settings.binary_path(main_binary).with_extension("exe");
331335
data.insert(
332336
"main_binary_name",
333-
to_json(main_binary.name().replace(".exe", "")),
337+
to_json(
338+
main_binary_path
339+
.file_stem()
340+
.and_then(|file_name| file_name.to_str())
341+
.unwrap_or_else(|| main_binary.name()),
342+
),
334343
);
335344
data.insert("main_binary_path", to_json(&main_binary_path));
336345

tooling/bundler/src/bundle/windows/templates/installer.nsi

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ ManifestDPIAwareness PerMonitorV2
99
!if "{{compression}}" == "none"
1010
SetCompress off
1111
!else
12-
; Set the compression algorithm. Default is LZMA.
12+
; Set the compression algorithm. We default to LZMA.
1313
SetCompressor /SOLID "{{compression}}"
1414
!endif
1515

@@ -57,6 +57,7 @@ ${StrLoc}
5757
!define MANUPRODUCTKEY "Software\${MANUFACTURER}\${PRODUCTNAME}"
5858
!define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}"
5959
!define ESTIMATEDSIZE "{{estimated_size}}"
60+
!define STARTMENUFOLDER "{{start_menu_folder}}"
6061

6162
Var PassiveMode
6263
Var UpdateMode
@@ -337,7 +338,12 @@ FunctionEnd
337338

338339
; 6. Start menu shortcut page
339340
Var AppStartMenuFolder
340-
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
341+
!if "${STARTMENUFOLDER}" != ""
342+
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
343+
!define MUI_STARTMENUPAGE_DEFAULTFOLDER "${STARTMENUFOLDER}"
344+
!else
345+
!define MUI_PAGE_CUSTOMFUNCTION_PRE Skip
346+
!endif
341347
!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder
342348

343349
; 7. Installation page
@@ -722,13 +728,27 @@ Section Uninstall
722728

723729
; Remove start menu shortcut
724730
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
725-
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
726-
Delete "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
727-
RMDir "$SMPROGRAMS\$AppStartMenuFolder"
731+
!insertmacro IsShortcutTarget "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
732+
Pop $0
733+
${If} $0 = 1
734+
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
735+
Delete "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
736+
RMDir "$SMPROGRAMS\$AppStartMenuFolder"
737+
${EndIf}
738+
!insertmacro IsShortcutTarget "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
739+
Pop $0
740+
${If} $0 = 1
741+
!insertmacro UnpinShortcut "$SMPROGRAMS\${PRODUCTNAME}.lnk"
742+
Delete "$SMPROGRAMS\${PRODUCTNAME}.lnk"
743+
${EndIf}
728744

729745
; Remove desktop shortcuts
730-
!insertmacro UnpinShortcut "$DESKTOP\${PRODUCTNAME}.lnk"
731-
Delete "$DESKTOP\${PRODUCTNAME}.lnk"
746+
!insertmacro IsShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
747+
Pop $0
748+
${If} $0 = 1
749+
!insertmacro UnpinShortcut "$DESKTOP\${PRODUCTNAME}.lnk"
750+
Delete "$DESKTOP\${PRODUCTNAME}.lnk"
751+
${EndIf}
732752
${EndIf}
733753

734754
; Remove registry information for add/remove programs
@@ -767,15 +787,34 @@ Function RestorePreviousInstallLocation
767787
StrCpy $INSTDIR $4
768788
FunctionEnd
769789

790+
Function Skip
791+
Abort
792+
FunctionEnd
793+
770794
Function SkipIfPassive
771795
${IfThen} $PassiveMode = 1 ${|} Abort ${|}
772796
FunctionEnd
773797

774798
Function CreateOrUpdateStartMenuShortcut
775799
; We used to use product name as MAINBINARYNAME
776800
; migrate old shortcuts to target the new MAINBINARYNAME
777-
${If} ${FileExists} "$DESKTOP\${PRODUCTNAME}.lnk"
778-
!insertmacro SetShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
801+
StrCpy $R0 0
802+
803+
!insertmacro IsShortcutTarget "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCTNAME}.exe"
804+
Pop $0
805+
${If} $0 = 1
806+
!insertmacro SetShortcutTarget "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
807+
StrCpy $R0 1
808+
${EndIf}
809+
810+
!insertmacro IsShortcutTarget "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCTNAME}.exe"
811+
Pop $0
812+
${If} $0 = 1
813+
!insertmacro SetShortcutTarget "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
814+
StrCpy $R0 1
815+
${EndIf}
816+
817+
${If} $R0 = 1
779818
Return
780819
${EndIf}
781820

@@ -785,16 +824,23 @@ Function CreateOrUpdateStartMenuShortcut
785824
Return
786825
${EndIf}
787826

788-
CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder"
789-
CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
790-
!insertmacro SetLnkAppUserModelId "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
827+
!if "${STARTMENUFOLDER}" != ""
828+
CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder"
829+
CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
830+
!insertmacro SetLnkAppUserModelId "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
831+
!else
832+
CreateShortcut "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
833+
!insertmacro SetLnkAppUserModelId "$SMPROGRAMS\${PRODUCTNAME}.lnk"
834+
!endif
791835
FunctionEnd
792836

793837
Function CreateOrUpdateDesktopShortcut
794838
; We used to use product name as MAINBINARYNAME
795839
; migrate old shortcuts to target the new MAINBINARYNAME
796-
${If} ${FileExists} "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
797-
!insertmacro SetShortcutTarget "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
840+
!insertmacro IsShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCTNAME}.exe"
841+
Pop $0
842+
${If} $0 = 1
843+
!insertmacro SetShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
798844
Return
799845
${EndIf}
800846

tooling/bundler/src/bundle/windows/templates/utils.nsh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,42 @@
131131
${IUnknown::Release} $0 ""
132132
${EndIf}
133133
!macroend
134+
135+
!define /ifndef MAX_PATH 260
136+
!define /ifndef SLGP_RAWPATH 0x4
137+
138+
; Test if a .lnk shortcut's target is target,
139+
; use Pop to get the result, 1 is yes, 0 is no,
140+
; note that this macro modifies $0, $1, $2, $3
141+
;
142+
; Exmaple usage:
143+
; !insertmacro "IsShortCutTarget" "C:\Users\Public\Desktop\App.lnk" "C:\Program Files\App\App.exe"
144+
; Pop $0
145+
; ${If} $0 = 1
146+
; MessageBox MB_OK "shortcut target matches"
147+
; ${EndIf}
148+
!macro IsShortcutTarget shortcut target
149+
; $0: IShellLink
150+
; $1: IPersistFile
151+
; $2: Target path
152+
; $3: Return value
153+
154+
StrCpy $3 0
155+
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r0 ""
156+
${If} $0 P<> 0
157+
${IUnknown::QueryInterface} $0 '("${IID_IPersistFile}", .r1)'
158+
${If} $1 P<> 0
159+
${IPersistFile::Load} $1 '("${shortcut}", ${STGM_READ})'
160+
System::Alloc MAX_PATH
161+
Pop $2
162+
${IShellLink::GetPath} $0 '(.r2, ${MAX_PATH}, 0, ${SLGP_RAWPATH})'
163+
${If} $2 == "${target}"
164+
StrCpy $3 1
165+
${EndIf}
166+
System::Free $2
167+
${IUnknown::Release} $1 ""
168+
${EndIf}
169+
${IUnknown::Release} $0 ""
170+
${EndIf}
171+
Push $3
172+
!macroend

tooling/cli/schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,6 +2306,13 @@
23062306
}
23072307
]
23082308
},
2309+
"startMenuFolder": {
2310+
"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\\<your-app>.lnk`\n - If unset, shortcut will be placed in `%AppData%\\Microsoft\\Windows\\Start Menu\\Programs\\<your-app>.lnk`",
2311+
"type": [
2312+
"string",
2313+
"null"
2314+
]
2315+
},
23092316
"installerHooks": {
23102317
"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 ```",
23112318
"type": [

tooling/cli/src/helpers/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ pub fn nsis_settings(config: NsisConfig) -> tauri_bundler::NsisSettings {
105105
custom_language_files: config.custom_language_files,
106106
display_language_selector: config.display_language_selector,
107107
compression: config.compression,
108+
start_menu_folder: config.start_menu_folder,
108109
installer_hooks: config.installer_hooks,
109110
}
110111
}

0 commit comments

Comments
 (0)