Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 78 additions & 24 deletions build/installer.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,107 @@

Var DshDirectoryPage
Var DshDirectoryEdit
Var DshDirectorySearchAfter
Var DshDirectoryAttached
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
FindWindow $DshDirectoryPage "#32770" "" $HWNDPARENT
${NSD_CreateTimer} DshAttachDirectoryPage 50
FunctionEnd

Function DshAttachDirectoryPage
${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
; 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_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\${APP_FILENAME}"
${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}
${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"
Expand Down
13 changes: 12 additions & 1 deletion test/release.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,25 @@ 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'
)

expect(installer).toContain('!define MUI_PAGE_CUSTOMFUNCTION_SHOW DshDirectoryPageShow')
expect(installer).toContain('${NSD_CreateTimer} DshAttachDirectoryPage 50')
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(
"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')
Expand Down
Loading