Browse for Folder: Difference between revisions

From NSIS Wiki
Jump to navigationJump to search
mNo edit summary
(Don't use the callback on <= XP/2003 because System is buggy?)
 
(3 intermediate revisions by the same user not shown)
Line 14: Line 14:
!define TVGN_CARET 0x9
!define TVGN_CARET 0x9
!define BFFM_INITIALIZED 1
!define BFFM_INITIALIZED 1
!define BFFM_VALIDATEFAILEDA 3
!define BFFM_VALIDATEFAILEDW 4
!if "${NSIS_CHAR_SIZE}" > 1
!if "${NSIS_CHAR_SIZE}" > 1
!define BFFM_VALIDATEFAILED ${BFFM_VALIDATEFAILEDW}
!define /math BFFM_SETSELECTION ${WM_USER} + 103
!define /math BFFM_SETSELECTION ${WM_USER} + 103
!else
!else
!define BFFM_VALIDATEFAILED ${BFFM_VALIDATEFAILEDA}
!define /math BFFM_SETSELECTION ${WM_USER} + 102
!define /math BFFM_SETSELECTION ${WM_USER} + 102
!endif
!endif
Line 25: Line 29:
Push $2
Push $2
System::Call 'SHELL32::SHParseDisplayName(w r1, p 0, *p 0r2, i 0, *i 0)i'
System::Call 'SHELL32::SHParseDisplayName(w r1, p 0, *p 0r2, i 0, *i 0)i'
${If} $2 P= 0
${If} $2 P= 0 ; SHParseDisplayName is XP+, this works everywhere but is not as clever
System::Call 'SHELL32::ILCreateFromPath(t r1)p.r2' ; SHParseDisplayName is XP+, this works everywhere but is not as clever
Push $3
System::Call 'SHELL32::SHGetDesktopFolder(*p.r3)' ; We leak this interface and don't care
System::Call '$3->3(p0, p0, wr1, *i, *p.r2, *i0)'
Pop $3
${EndIf}
${EndIf}
StrCpy $1 $2
StrCpy $1 $2
Line 47: Line 54:
!macroend
!macroend
!insertmacro BrowseForFolder_PathToPidl $1 $6
!insertmacro BrowseForFolder_PathToPidl $1 $6
!insertmacro BrowseForFolder_PathToPidl $3 $5
System::Call SHLWAPI::IsOS(i0x25)i.r5
${IfThen} $5 <> 0 ${|} !insertmacro BrowseForFolder_PathToPidl $3 $5 ${|} ; Only do the callback on Vista+
!if "${NSIS_PTR_SIZE}" > 4 ; Callbacks currently not supported on AMD64
StrCpy $4 "p0"
StrCpy $R8 ""
StrCpy $R9 0
!else
System::Get "(p.R1, i.R2, p, p.R3)i R8R8" ; BFFCALLBACK
System::Get "(p.R1, i.R2, p, p.R3)i R8R8" ; BFFCALLBACK
Pop $R9
Pop $R9
StrCpy $4 "kR9"
!endif
System::Call '*(&t261 "")p.r7' ; pszDisplayName buffer
System::Call '*(&t261 "")p.r7' ; pszDisplayName buffer
System::Call '*(p $hwndparent, pr6, pr7, t r2, i 0x41, kR9, pr5, i)p.r8' ; BROWSEINFO struct
System::Call '*(p $hwndparent, pr6, pr7, t r2, i 0x41, $4, pr5, i)p.r8' ; BROWSEINFO struct  
System::Call 'SHELL32::SHBrowseForFolder(t)(pr8)p.r9' ; Using the (t) hack to force the correct A/W function
!if "${NSIS_CHAR_SIZE}" > 1
System::Call 'SHELL32::SHBrowseForFolderW(pr8)p.r9'
!else
System::Call 'SHELL32::SHBrowseForFolderA(pr8)p.r9'
!endif
BFFCALLBACK_loop:
BFFCALLBACK_loop:
StrCmp $R8 "callback1" 0 BFFCALLBACK_done
StrCpy $R8 $R8 8 ; HACKHACK: Working around 2.x bug where the callback IDs are never released
StrCmp $R8 "callback" 0 BFFCALLBACK_done
${If} $R2 = ${BFFM_INITIALIZED}
${If} $R2 = ${BFFM_INITIALIZED}
${AndIf} $R3 P<> 0
${AndIf} $R3 P<> 0
Line 61: Line 81:
StrCpy $2 0
StrCpy $2 0
StrCpy $3 0
StrCpy $3 0
loop: ; BFFM_SETSELECTION is buggy and does scroll to the new item so we find the treeview and do it manually
loop: ; BFFM_SETSELECTION is buggy and does not scroll to the new item so we find the treeview and do it manually
FindWindow $2 "" "" $R1 $2 ; Assuming SysTreeView32 is a grandchild when using BIF_NEWDIALOGSTYLE
FindWindow $2 "" "" $R1 $2 ; Assuming SysTreeView32 is a grandchild when using BIF_NEWDIALOGSTYLE
IntCmp 0 $2 done
IntCmp 0 $2 done
Line 73: Line 93:
${EndIf}
${EndIf}
StrCpy $R8 0 ; Yep, the return value is in the same place as the callback id
StrCpy $R8 0 ; Yep, the return value is in the same place as the callback id
${IfThen} $R2 = ${BFFM_VALIDATEFAILED} ${|} StrCpy $R8 1 ${|}
System::Call $R9
System::Call $R9
goto BFFCALLBACK_loop
goto BFFCALLBACK_loop
BFFCALLBACK_done:
BFFCALLBACK_done:
System::Free $R9
System::Free $R9
System::Free $7
System::Free $8
System::Call 'OLE32::CoTaskMemFree(p r5)'
System::Call 'OLE32::CoTaskMemFree(p r6)'
${If} $9 Z<> 0
${If} $9 Z<> 0
System::Call 'SHELL32::SHGetPathFromIDList(p r9, t.s)i'
System::Call 'SHELL32::SHGetPathFromIDList(p r9, t.s)i'
Line 83: Line 108:
Push "" ; Error/cancel, return empty string
Push "" ; Error/cancel, return empty string
${EndIf}
${EndIf}
System::Call 'OLE32::CoTaskMemFree(p r5)'
System::Call 'OLE32::CoTaskMemFree(p r6)'
System::Free $7
System::Free $8
System::Store L
System::Store L
FunctionEnd
FunctionEnd

Latest revision as of 12:55, 18 September 2016

Author: Anders (talk, contrib)


This function displays the folder browser UI so the user can choose a file-system folder. You can specify a root folder or use "" for the default root (deskop). You can also set the initial selection if desired.

Code

!include LogicLib.nsh
!include WinMessages.nsh ; WM_USER
!define TV_FIRST  0x1100
!define /math TVM_GETNEXTITEM ${TV_FIRST} + 10
!define /math TVM_SELECTITEM ${TV_FIRST} + 11
!define /math TVM_ENSUREVISIBLE ${TV_FIRST} + 20
!define TVGN_FIRSTVISIBLE 0x5
!define TVGN_CARET 0x9
!define BFFM_INITIALIZED 1
!define BFFM_VALIDATEFAILEDA 3
!define BFFM_VALIDATEFAILEDW 4
!if "${NSIS_CHAR_SIZE}" > 1
!define BFFM_VALIDATEFAILED ${BFFM_VALIDATEFAILEDW}
!define /math BFFM_SETSELECTION ${WM_USER} + 103
!else
!define BFFM_VALIDATEFAILED ${BFFM_VALIDATEFAILEDA}
!define /math BFFM_SETSELECTION ${WM_USER} + 102
!endif
 
 
Function SHParseDisplayName ; NSIS 2.51+ INPUT:Path OUTPUT:Pidl
Exch $1
Push $2
System::Call 'SHELL32::SHParseDisplayName(w r1, p 0, *p 0r2, i 0, *i 0)i'
${If} $2 P= 0 ; SHParseDisplayName is XP+, this works everywhere but is not as clever
	Push $3
	System::Call 'SHELL32::SHGetDesktopFolder(*p.r3)' ; We leak this interface and don't care
	System::Call '$3->3(p0, p0, wr1, *i, *p.r2, *i0)'
	Pop $3
${EndIf}
StrCpy $1 $2
Pop $2
Exch $1
FunctionEnd
 
Function BrowseForFolder ; NSIS 2.51+ INPUT:RootPath, HeadingText, InitialPathSelection OUTPUT:Path
System::Store S
Pop $3 ; InitialPathSelection or ""
Pop $2 ; HeadingText
Pop $1 ; RootPath or ""
!macro BrowseForFolder_PathToPidl Path Pidl
StrCpy ${Pidl} ""
${If} "${Path}" != ""
	Push "${Path}"
	Call SHParseDisplayName
	Pop ${Pidl}
${EndIf}
!macroend
!insertmacro BrowseForFolder_PathToPidl $1 $6
System::Call SHLWAPI::IsOS(i0x25)i.r5
${IfThen} $5 <> 0 ${|} !insertmacro BrowseForFolder_PathToPidl $3 $5 ${|} ; Only do the callback on Vista+
!if "${NSIS_PTR_SIZE}" > 4 ; Callbacks currently not supported on AMD64
StrCpy $4 "p0"
StrCpy $R8 ""
StrCpy $R9 0
!else
System::Get "(p.R1, i.R2, p, p.R3)i R8R8" ; BFFCALLBACK
Pop $R9
StrCpy $4 "kR9"
!endif
System::Call '*(&t261 "")p.r7' ; pszDisplayName buffer
System::Call '*(p $hwndparent, pr6, pr7, t r2, i 0x41, $4, pr5, i)p.r8' ; BROWSEINFO struct 
!if "${NSIS_CHAR_SIZE}" > 1
System::Call 'SHELL32::SHBrowseForFolderW(pr8)p.r9'
!else
System::Call 'SHELL32::SHBrowseForFolderA(pr8)p.r9'
!endif
BFFCALLBACK_loop:
	StrCpy $R8 $R8 8 ; HACKHACK: Working around 2.x bug where the callback IDs are never released
	StrCmp $R8 "callback" 0 BFFCALLBACK_done
	${If} $R2 = ${BFFM_INITIALIZED}
	${AndIf} $R3 P<> 0
		SendMessage $R1 ${BFFM_SETSELECTION} 0 $R3
		System::Store S
		StrCpy $2 0
		StrCpy $3 0
		loop: ; BFFM_SETSELECTION is buggy and does not scroll to the new item so we find the treeview and do it manually
			FindWindow $2 "" "" $R1 $2 ; Assuming SysTreeView32 is a grandchild when using BIF_NEWDIALOGSTYLE
			IntCmp 0 $2 done
			FindWindow $3 "SysTreeView32" "" $2
			IntCmp 0 $3 loop
			SendMessage $3 ${TVM_GETNEXTITEM} ${TVGN_CARET} 0 $4
			IntCmp 0 $3 done
			System::Call 'USER32::PostMessage(p$3,i${TVM_ENSUREVISIBLE},p0,p$4)'
		done:
		System::Store L
	${EndIf}
	StrCpy $R8 0 ; Yep, the return value is in the same place as the callback id
	${IfThen} $R2 = ${BFFM_VALIDATEFAILED} ${|} StrCpy $R8 1 ${|}
	System::Call $R9
	goto BFFCALLBACK_loop
BFFCALLBACK_done:
System::Free $R9
System::Free $7
System::Free $8
System::Call 'OLE32::CoTaskMemFree(p r5)'
System::Call 'OLE32::CoTaskMemFree(p r6)'
${If} $9 Z<> 0
	System::Call 'SHELL32::SHGetPathFromIDList(p r9, t.s)i'
	System::Call 'OLE32::CoTaskMemFree(p r9)'
${Else}
	Push "" ; Error/cancel, return empty string
${EndIf}
System::Store L
FunctionEnd

Example

Section
Push "$Profile"
Push "Hello World"
Push "$AppData"
Call BrowseForFolder
Pop $0
DetailPrint BrowseForFolder=$0
SectionEnd