From 62f26e08b40b0ba2ff5e955d212c6745113aac1a Mon Sep 17 00:00:00 2001 From: QinRui Date: Mon, 17 Aug 2026 19:18:21 +0800 Subject: [PATCH 1/2] fix: normalize Windows root install directory --- build/installer.nsh | 20 +++++++++++++++----- test/release.test.ts | 6 ++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/build/installer.nsh b/build/installer.nsh index 09b0e7b..61f086d 100644 --- a/build/installer.nsh +++ b/build/installer.nsh @@ -7,14 +7,24 @@ Var DshDirectoryEdit Var DshDirectoryNormalizationActive - ; MUI invokes this after the assisted installer's directory page is ready. - ; Normalize a selected drive root immediately so the page does not reject it - ; before electron-builder's later install-time sanitization can run. + ; electron-builder declares its install-mode page before the directory page. + ; MUI consumes this callback on that first page, so use it to start a short + ; polling timer and attach to the directory edit control once that page exists. !define MUI_PAGE_CUSTOMFUNCTION_SHOW DshDirectoryPageShow Function DshDirectoryPageShow + ${NSD_CreateTimer} DshAttachDirectoryPage 50 + FunctionEnd + + Function DshAttachDirectoryPage FindWindow $DshDirectoryPage "#32770" "" $HWNDPARENT GetDlgItem $DshDirectoryEdit $DshDirectoryPage 1019 + + ${If} $DshDirectoryEdit == 0 + Return + ${EndIf} + + ${NSD_KillTimer} DshAttachDirectoryPage ${NSD_OnChange} $DshDirectoryEdit DshDirectoryChanged Call DshNormalizeDriveRoot FunctionEnd @@ -39,7 +49,7 @@ ${If} $2 != ":" Return ${EndIf} - StrCpy $3 "$0\${APP_FILENAME}" + StrCpy $3 "$0\DSH Desktop" ${ElseIf} $1 == 3 StrCpy $2 $0 1 1 ${If} $2 != ":" @@ -49,7 +59,7 @@ ${If} $2 != "\" Return ${EndIf} - StrCpy $3 "$0${APP_FILENAME}" + StrCpy $3 "$0DSH Desktop" ${Else} Return ${EndIf} diff --git a/test/release.test.ts b/test/release.test.ts index 98ce71c..264ac54 100644 --- a/test/release.test.ts +++ b/test/release.test.ts @@ -83,9 +83,11 @@ describe('GitHub release contract', () => { ) expect(installer).toContain('!define MUI_PAGE_CUSTOMFUNCTION_SHOW DshDirectoryPageShow') + expect(installer).toContain('${NSD_CreateTimer} DshAttachDirectoryPage 50') + expect(installer).toContain('${NSD_KillTimer} DshAttachDirectoryPage') expect(installer).toContain('${NSD_OnChange} $DshDirectoryEdit DshDirectoryChanged') - expect(installer).toContain('StrCpy $3 "$0\\${APP_FILENAME}"') - expect(installer).toContain('StrCpy $3 "$0${APP_FILENAME}"') + expect(installer).toContain('StrCpy $3 "$0\\DSH Desktop"') + expect(installer).toContain('StrCpy $3 "$0DSH Desktop"') expect(installer).toContain('${NSD_SetText} $DshDirectoryEdit $3') }) From 1a81095136c8e248720aa1cbdd148bdfe39c06f6 Mon Sep 17 00:00:00 2001 From: QinRui Date: Tue, 18 Aug 2026 10:37:21 +0800 Subject: [PATCH 2/2] fix: show app folder for custom Windows installs --- build/installer.nsh | 92 ++++++++++++++++++++++++++++++++------------ test/release.test.ts | 17 ++++++-- 2 files changed, 81 insertions(+), 28 deletions(-) diff --git a/build/installer.nsh b/build/installer.nsh index 61f086d..19d6f5a 100644 --- a/build/installer.nsh +++ b/build/installer.nsh @@ -5,6 +5,8 @@ Var DshDirectoryPage Var DshDirectoryEdit + Var DshDirectorySearchAfter + Var DshDirectoryAttached Var DshDirectoryNormalizationActive ; electron-builder declares its install-mode page before the directory page. @@ -17,51 +19,93 @@ FunctionEnd Function DshAttachDirectoryPage - FindWindow $DshDirectoryPage "#32770" "" $HWNDPARENT + ${If} $DshDirectoryAttached == "1" + System::Call 'USER32::IsWindowVisible(p $DshDirectoryPage)i.r0' + ${If} $0 == 0 + ; The user can navigate back to the install-mode page and then return. + ; Keep the timer alive so the directory page can be attached again. + StrCpy $DshDirectoryAttached "0" + StrCpy $DshDirectoryPage 0 + StrCpy $DshDirectoryEdit 0 + ${Else} + ; Keep polling while the directory page is visible. This also catches a + ; manually entered parent path as soon as the edit control loses focus. + Call DshNormalizeSelectedDirectory + Return + ${EndIf} + ${EndIf} + + StrCpy $DshDirectorySearchAfter 0 + + DshFindDirectoryPage: + FindWindow $DshDirectoryPage "#32770" "" $HWNDPARENT $DshDirectorySearchAfter + + ${If} $DshDirectoryPage == 0 + Return + ${EndIf} + GetDlgItem $DshDirectoryEdit $DshDirectoryPage 1019 ${If} $DshDirectoryEdit == 0 - Return + ; MUI keeps earlier custom pages as hidden child dialogs. Continue until + ; the child containing the actual directory edit control is found. + StrCpy $DshDirectorySearchAfter $DshDirectoryPage + Goto DshFindDirectoryPage + ${EndIf} + + System::Call 'USER32::IsWindowVisible(p $DshDirectoryPage)i.r0' + ${If} $0 == 0 + StrCpy $DshDirectorySearchAfter $DshDirectoryPage + Goto DshFindDirectoryPage ${EndIf} - ${NSD_KillTimer} DshAttachDirectoryPage ${NSD_OnChange} $DshDirectoryEdit DshDirectoryChanged - Call DshNormalizeDriveRoot + StrCpy $DshDirectoryAttached "1" + Call DshNormalizeSelectedDirectory FunctionEnd Function DshDirectoryChanged Pop $0 - Call DshNormalizeDriveRoot + Call DshNormalizeSelectedDirectory FunctionEnd - Function DshNormalizeDriveRoot + Function DshNormalizeSelectedDirectory ${If} $DshDirectoryNormalizationActive == "1" Return ${EndIf} + ; A Browse selection updates the edit while focus remains on the Browse + ; button. Do not rewrite the path character-by-character when the user is + ; typing directly into the edit control. + System::Call 'USER32::GetFocus()p.r4' + ${If} $4 == $DshDirectoryEdit + Return + ${EndIf} + ${NSD_GetText} $DshDirectoryEdit $0 - StrLen $1 $0 - ; Accept both forms produced by typing or the Windows folder picker: - ; "D:" and "D:\". Any non-root directory is left untouched. - ${If} $1 == 2 - StrCpy $2 $0 1 1 - ${If} $2 != ":" - Return - ${EndIf} - StrCpy $3 "$0\DSH Desktop" - ${ElseIf} $1 == 3 - StrCpy $2 $0 1 1 - ${If} $2 != ":" - Return - ${EndIf} - StrCpy $2 $0 1 2 - ${If} $2 != "\" + ${If} $0 == "" + Return + ${EndIf} + + ; Keep the default path and an already-normalized custom path unchanged. + StrLen $1 "${APP_FILENAME}" + StrLen $2 $0 + ${If} $2 >= $1 + IntOp $4 $2 - $1 + StrCpy $3 $0 $1 $4 + ${If} $3 == "${APP_FILENAME}" Return ${EndIf} - StrCpy $3 "$0DSH Desktop" + ${EndIf} + + ; The directory picker returns the selected parent directory. Make the + ; application subdirectory visible immediately for every custom location. + StrCpy $1 $0 1 -1 + ${If} $1 == "\" + StrCpy $3 "$0${APP_FILENAME}" ${Else} - Return + StrCpy $3 "$0\${APP_FILENAME}" ${EndIf} StrCpy $DshDirectoryNormalizationActive "1" diff --git a/test/release.test.ts b/test/release.test.ts index 264ac54..bd988c2 100644 --- a/test/release.test.ts +++ b/test/release.test.ts @@ -76,7 +76,7 @@ describe('GitHub release contract', () => { expect(packageJson.build.portable).toBeUndefined() }) - it('turns a selected Windows drive root into an application directory', async () => { + it('shows the application subdirectory for every custom Windows install location', async () => { const installer = await readFile( path.join(projectRoot, 'build', 'installer.nsh'), 'utf8' @@ -84,10 +84,19 @@ describe('GitHub release contract', () => { expect(installer).toContain('!define MUI_PAGE_CUSTOMFUNCTION_SHOW DshDirectoryPageShow') expect(installer).toContain('${NSD_CreateTimer} DshAttachDirectoryPage 50') - expect(installer).toContain('${NSD_KillTimer} DshAttachDirectoryPage') + expect(installer).toContain( + 'FindWindow $DshDirectoryPage "#32770" "" $HWNDPARENT $DshDirectorySearchAfter' + ) + expect(installer).toContain('Goto DshFindDirectoryPage') + expect(installer).toContain('StrCpy $DshDirectoryAttached "0"') expect(installer).toContain('${NSD_OnChange} $DshDirectoryEdit DshDirectoryChanged') - expect(installer).toContain('StrCpy $3 "$0\\DSH Desktop"') - expect(installer).toContain('StrCpy $3 "$0DSH Desktop"') + expect(installer).toContain( + "System::Call 'USER32::IsWindowVisible(p $DshDirectoryPage)i.r0'" + ) + expect(installer).toContain("System::Call 'USER32::GetFocus()p.r4'") + expect(installer).toContain('StrLen $1 "${APP_FILENAME}"') + expect(installer).toContain('StrCpy $3 "$0\\${APP_FILENAME}"') + expect(installer).toContain('StrCpy $3 "$0${APP_FILENAME}"') expect(installer).toContain('${NSD_SetText} $DshDirectoryEdit $3') })