NSIS How to modify MUI_PAGE_INSTFILES during installtion? - user-interface

I am wondering if it will be possible to perform the following UI modifications / tweaks (programmatically in the nsi script) WHILE the installation is running on the MUI_PAGE_INSTFILES page:
Set the style of the progress bar to marque (continuous "scrolling")
Hide the details text field (where the progress text is displayed) and show a predefined picture instead (that was included with the setup)
[EDIT] Alternatively for number 2: maybe shrink the details field, and display the picture as a banner like label just below it (so that the details field would occupy only half the space vertically, and the other half below it would be used by the picture - that way a user can see both the progress and the "banner").
[EDIT] Even better still would be to have some sort of basic slide show: define several pictures and rotate them every 10 seconds
The idea behind this is that during parts of the installation that may take some time (say over 10 seconds) to display something more informative / valuable to the user, such as how to get started graphic, a sales promotion or something else.
How can this be done?
Thanks.

XPStyle on ; This must be on for the Marquee to show
!include nsDialogs.nsh ; For WS_* and NSD_SetImage
!include WinMessages.nsh
!ifndef PBS_MARQUEE
!define PBS_MARQUEE 8
!endif
!ifndef PBM_SETMARQUEE
!define PBM_SETMARQUEE 0x40A
!endif
!macro ProgressMarquee_Begin
FindWindow $0 "#32770" "" $HWNDPARENT ; Find inner dialog
GetDlgItem $0 $0 1004 ; Find progress-bar
System::Call USER32::GetWindowLong(ir0,i-16)i.r1
IntOp $1 $1 | ${PBS_MARQUEE}
System::Call USER32::SetWindowLong(ir0s,i-16,ir1s) ; This also saves the handle and style on the stack, we need those to turn it off again.
SendMessage $0 ${PBM_SETMARQUEE} 1 0 ; The last parameter is the speed or 0 for the default
!macroend
!macro ProgressMarquee_Remove
Exch
Pop $1
IntOp $0 ${PBS_MARQUEE} ~
IntOp $1 $1 & $0
System::Call USER32::SetWindowLong(is,i-16,ir1)
!macroend
Function DoLongOperation
DetailPrint "Starting long operation"
Sleep 4444
DetailPrint "Long operation ended..."
FunctionEnd
Section
DetailPrint "Simulating other install tasks..."
Sleep 222
Sleep 222
Sleep 222
Sleep 222
Sleep 222
Sleep 222
; Add a image and hide the log
FindWindow $2 "#32770" "" $HWNDPARENT ; Find inner dialog
GetDlgItem $0 $2 0x3F8 ; Find log window
System::Call *(i,i,i,i)i.r1 ; Allocate a RECT
System::Call 'USER32::GetWindowRect(ir0s,ir1)' ; Also saves log HWND on stack
ShowWindow $0 0 ; Hide log window
System::Call '*$1(i.r4,i.r5,i.r6,i.r7)'
IntOp $6 $6 - $4
IntOp $7 $7 - $5
System::Call 'USER32::ScreenToClient(ir2,ir1)'
System::Call '*$1(i.r4,i.r5)'
System::Free $1
System::Call 'USER32::CreateWindowEx(i0,t"STATIC",i0,i${WS_CHILD}|${WS_CLIPSIBLINGS}|${WS_VISIBLE}|${SS_BITMAP},ir4,ir5,ir6,ir7,ir2,i0,i0,i0)i.r2'
Push $2 ; Save HWND
SetDetailsPrint none
File "/oname=$pluginsdir\img.bmp" "${NSISDIR}\Contrib\Graphics\Header\orange.bmp"
SetDetailsPrint lastused
${NSD_SetImage} $2 "$pluginsdir\img.bmp" $9
Push $9 ; Save HBITMAP
!insertmacro ProgressMarquee_Begin
Call DoLongOperation
!insertmacro ProgressMarquee_Remove
; Remove and cleanup image
Pop $0
${NSD_FreeBitmap} $0
Pop $0
ShowWindow $0 0
Pop $0 ; Log window handle we saved
ShowWindow $0 1
SectionEnd
If you want to show multiple images then you need multiple calls to ${NSD_SetImage} but if you want to go down this route it is probably much better to write a NSIS plugin that does all this on a secondary thread...

Related

Make an app available to Priority App in Focus Assist on Windows

I have a python app that I'm installing with NSIS. What I cant figure out is how you get an App into the "priority list" that is in the Focus Assist area of windows. The dialog suggests you can add any app - but you can't. Its a really small amount of apps that are available. How can you make an app available to this Focus Assist priority list?
It might have something to do with AUMID. You need to set this when you install your app. When using NSIS these two options don't seem to fix this issue
https://github.com/safing/nsis-shortcut-properties
https://nsis.sourceforge.io/WinShell_plug-in
I'm not sure if setting the ToastActivatorCLSID is going to help but here is some NSIS code to do that. I noticed that OneDrive also sets the ID so this function sets both:
!include LogicLib.nsh
!include Win\COM.nsh
!include Win\Propkey.nsh
Function SetLnkToastActivatorCLSIDAndAUMI
System::Store S
Pop $3
Pop $4
Pop $6
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r1 ""
${If} $1 P<> 0
${IUnknown::QueryInterface} $1 '("${IID_IPersistFile}",.r2)'
${IUnknown::Release} $1 ""
${If} $2 P<> 0
${IPersistFile::Load} $2 '(r3,${STGM_READWRITE})i.r0'
${If} $0 >= 0
${IUnknown::QueryInterface} $2 '("${IID_IPropertyStore}",.r1)'
${If} $1 P<> 0
System::Call '*${SYSSTRUCT_PROPERTYKEY}(${PKEY_AppUserModel_ToastActivatorCLSID})p.r3'
System::Call '*(&g16"$4")p.r4'
System::Call '*${SYSSTRUCT_PROPVARIANT}(${VT_CLSID},,p$4)p.r5'
${IPropertyStore::SetValue} $1 '($3,$5)i.r0'
System::Free $4
${If} $6 != ""
System::Call '*$3${SYSSTRUCT_PROPERTYKEY}(${PKEY_AppUserModel_ID})'
System::Call '*(&w${NSIS_MAX_STRLEN}s)p.r4' $6
${V_SetPointer} $5 ${VT_LPWSTR} $4
${IPropertyStore::SetValue} $1 '($3,$5)'
System::Free $4
${EndIf}
${IPropertyStore::Commit} $1 ""
System::Free $3
System::Free $5
${IUnknown::Release} $1 ""
${If} $0 >= 0
${IPersistFile::Save} $2 '(p0,1)'
${EndIf}
${EndIf}
${EndIf}
${IUnknown::Release} $2 ""
${EndIf}
${EndIf}
System::Store L
FunctionEnd
!macro SetLnkToastActivatorCLSIDAndAUMI lnkpath clsid aumi
Push "${aumi}"
Push ${clsid}
Push "${lnkpath}"
Call SetLnkToastActivatorCLSIDAndAUMI
!macroend
Section
!define MYTOASTCLASSGUID {696a4cab-ef17-48d1-8bb5-6ebc36b94d0a} ; Replace this with your own CLSID
CreateShortcut /NoWorkingDir "$SMPrograms\NSIS_toast_test.lnk" "$sysdir\winver.exe"
!insertmacro SetLnkToastActivatorCLSIDAndAUMI "$SMPrograms\NSIS_toast_test.lnk" ${MYTOASTCLASSGUID} "My.AppModel.Id.Goes.Here"
SectionEnd

NSIS: How to get a finish- and a next-button?

I use the MUI_PAGE_INSTFILES. And this page has normally a finish-button, but I have a custom page after it. But I want that you could still finish the installer before the custom page and the next page is only optional. I could also show more code if wished.
!include "MUI2.nsh"
!insertmacro MUI_PAGE_LICENSE $(license)
!insertmacro MUI_PAGE_INSTFILES
Page custom TestSettings
At the moment I have a previous-, next- and cancel-button at the instfile page. But I want a prev-, next- and finish-button.
NSIS was never designed to support this but with the ButtonEvent plug-in you can make it work. I'm just not sure if it makes sense because if the last page is important then some users might skip the page by accident because they are not paying attention.
!include MUI2.nsh
!insertmacro MUI_PAGE_LICENSE $(license)
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE InstFilesLeave
!insertmacro MUI_PAGE_INSTFILES
Page custom TestSettings
!insertmacro MUI_LANGUAGE English
!include WinMessages.nsh
!include nsDialogs.nsh
Function InstFilesLeave
GetDlgItem $0 $hWndParent 2
ShowWindow $0 0
System::Call 'USER32::GetWindowRect(pr0,#r1)' ; NSIS 3+
System::Call 'USER32::MapWindowPoints(p0,p $hWndParent, p $1, i 2)'
System::Call '*$1(i.r2,i.r3,i.r4,i.r5)'
IntOp $4 $4 - $2
IntOp $5 $5 - $3
!define IDC_MYFINISHBTN 1337
System::Call 'USER32::CreateWindowEx(i 0, t "BUTTON", t "&Finish", i ${DEFAULT_STYLES}|${WS_TABSTOP}, ir2, ir3, i r4, i r5, p $hWndParent, p ${IDC_MYFINISHBTN}, p 0, p 0)p.r1'
SendMessage $0 ${WM_GETFONT} "" "" $0
SendMessage $1 ${WM_SETFONT} $0 1
GetFunctionAddress $0 OnFinishButton
ButtonEvent::AddEventHandler ${IDC_MYFINISHBTN} $0
FunctionEnd
Var JustFinish
Function OnFinishButton
StrCpy $JustFinish 1
SendMessage $hWndParent ${WM_COMMAND} 1 ""
FunctionEnd
Function TestSettings
StrCmp $JustFinish "" +2
Return
GetDlgItem $0 $hWndParent ${IDC_MYFINISHBTN}
ShowWindow $0 0
GetDlgItem $0 $hWndParent 2
ShowWindow $0 1
!insertmacro MUI_HEADER_TEXT "Configure" "Blah blah blah"
nsDialogs::Create 1018
Pop $0
; ...
nsDialogs::Show
FunctionEnd
If your custom page just contains a couple of checkboxes then you can use the MUI Finish page with custom texts instead.

Popping up a dialog/message after NSIS install in silent mode

I have an NSIS installer set to run in silent mode. This works very well.
I've had a customer request for an informational pop up once the install has complete...but still wants the installer portion to be silent!
I realize this is counter intuitive.
That being said is there away to override the silent at the end of the install?
I currently set the installer attribute below at the beginning of the .nsi script
SilentInstall silent
You can toggle silent mode on or off in .onInit with SetSilent but once that function returns the UI mode cannot be changed.
The simple solution is to simply use one of the Banner or Splash plug-ins at the end of your last Section to display a message/image.
Or you can create a fake silent mode with a minimal UI that only displays the progress bar:
!include LogicLib.nsh
Var Silent
Function .onInit
${If} ${Silent}
SetSilent Normal ; Turn off real silent mode
SetAutoClose True
StrCpy $Silent 1 ; Fake silent mode
${EndIf}
FunctionEnd
Page Components SkipPageIfSilent
Page Directory SkipPageIfSilent
Page InstFiles "" TweakInstfilesPage
Function SkipPageIfSilent
IntCmp $Silent 0 +2
Abort
FunctionEnd
Function TweakInstfilesPage
${If} $Silent <> 0
SetSilent Silent ; Make IfSilent return true
FindWindow $0 "#32770" "" $HWNDPARENT
GetDlgItem $0 $0 0x403 ; Show details button
ShowWindow $0 0
System::Call 'USER32::GetWindowRect(p$0,#r1)' ; NSIS v3+
System::Call 'USER32::GetWindowRect(p$HWNDPARENT,#r2)' ; NSIS v3+
System::Call '*$1(i,i.r3,i,i)'
System::Call '*$2(i.r5,i.r6,i.r7,ir3r8)'
IntOp $5 $7 - $5 ; Width
IntOp $6 $8 - $6 ; Height
System::Call 'USER32::SetWindowPos(p$HWNDPARENT,p,i,i,ir5,ir6,i0x12)'
SetDetailsView Hide
${EndIf}
FunctionEnd
Section
Sleep 333
Sleep 333
Sleep 333
Sleep 333
SectionEnd

NSIS - See if a given process has shown a window

I run a certain process via ExecShell from my NSIS installer. That process takes sometime to start (5 - 40 seconds maybe), during which I want the NSIS window to remain visible.
The problem is that while the process itself starts almost instantly, it is sometime before a user would see anything, thus I want the NSIS installer window to remain visible UNTIL the started process's main window (or any window for that matter is shown).
What I need to know thus is how do I get processid from ExecShell (can't use Exec or ExecWait for other reasons), and then how do I use that process id to see if the window has been shown (I know I can do it via a simple sleep, check, goto loop, so basically I am trying to figure out the check part of it)?
So, how exactly can I know if the process I spawned using ShellExec has shown a GUI. This needs to work in Windows XP SP3 and up.
Thank you.
RequestExecutionLevel user
Page InstFiles
!include LogicLib.nsh
!include WinMessages.nsh ; For SW_*
Var IsSlowFakeApp ; We can also pretend to be a silly little app that is slow to start up, this is just so the example code has no external dependencies
Function .onInit
StrCpy $0 $CMDLINE 1 -1
${If} $0 == "?"
StrCpy $IsSlowFakeApp 1
Sleep 3333
${EndIf}
FunctionEnd
Function StartMyAppAndWaitForWindow
StrCpy $0 "$ExePath" ; Application
StrCpy $1 "?" ; Parameters
!define SEE_MASK_NOCLOSEPROCESS 0x40
DetailPrint 'Starting "$0" $1'
System::Store S
System::Call '*(i60,i${SEE_MASK_NOCLOSEPROCESS},i$hwndparent,i0,tr0,tr1,i0,i${SW_SHOW},i,i,i,i,i,i,i)i.r0'
System::Call 'SHELL32::ShellExecuteEx(ir0)i.r1'
${If} $1 <> 0
System::Call '*$0(i,i,i,i,i,i,i,i,i,i,i,i,i,i,i.r1)'
System::Call 'USER32::WaitForInputIdle(ir1,2000)i'
System::Call 'KERNEL32::GetProcessId(ir1)i.r2' ; MSDN says this function is XP.SP1+
StrCpy $3 $2 ; Not found a window yet, keep looping
; Call EnumWindows until we find a window matching our target process id in $2
System::Get '(i.r5, i) iss'
Pop $R0
callEnumWindows:
System::Call 'USER32::EnumWindows(k R0, i) i.s'
loopEnumWindows:
Pop $4
StrCmp $4 "callback1" 0 doneEnumWindows
System::Call 'USER32::GetWindowThreadProcessId(ir5,*i0r4)'
${If} $4 = $2
System::Call 'USER32::IsWindowVisible(ir5)i.r4'
${IfThen} $4 <> 0 ${|} StrCpy $3 0 ${|} ; Found a visible Window
${EndIf}
Push $3 ; EnumWindows callback's return value
System::Call "$R0"
Goto loopEnumWindows
doneEnumWindows:
${If} $3 <> 0
Sleep 1000
Goto callEnumWindows
${EndIf}
System::Free $R0
; Hide installer while app runs
/*HideWindow
System::Call 'KERNEL32::WaitForSingleObject(ir1,i-1)'
BringToFront*/
System::Call 'KERNEL32::CloseHandle(ir1)'
${EndIf}
System::Free $0
System::Store L
FunctionEnd
Section
${If} $IsSlowFakeApp <> 0
SetCtlColors $HWNDPARENT 0xffffff 0xdd0000
FindWindow $0 "#32770" "" $HWNDPARENT
SetCtlColors $0 0xffffff 0xdd0000
DetailPrint "This is a fake slow app and it will close soon..."
Sleep 5555
Quit
${Else}
Call StartMyAppAndWaitForWindow
${EndIf}
SectionEnd

Disable 'next' button on the component page when there are no selected components

I'm using NSIS 2.46 to create a installer for my windows application, I have a component page with 12 checkboxes, that is 12 sections in my NSIS code, now I want to disable the 'Next' button if none of the sections are checked by the user, I'm using this code:
Somehow it doesn't accept R registers above R9...
SectionGetFlags ${section11} $R10
SectionGetFlags ${section12} $R11
The compiler error I'm getting is
Please tell me how to disable the 'Next' button if there are more than 10 components...
The basic NSIS registers are $0...$9 and $R0...$R9, so you should use $1 and $2 for the last two sections. Or you can create more variables if you want; Var /GLOBAL R10.
If section1 to section12 are numbered without gaps you can use a loop:
!include LogicLib.nsh
Section A S_1
SectionEnd
Section /o B S_2
SectionEnd
Section C S_3
SectionEnd
Function .onSelChange
StrCpy $0 0
StrCpy $1 ${S_1}
${DoWhile} $1 <= ${S_3}
${If} ${SectionIsSelected} $1
StrCpy $0 1
${ExitDo}
${EndIf}
IntOp $1 $1 + 1
${Loop}
GetDlgItem $1 $HwndParent 1
EnableWindow $1 $0
FunctionEnd

Resources