Recently, I had to customize NSIS installers for work.
It was my first time, and I had to get to know this tool first. While this system already offers many functionalities, if you want to change anything in its built-in pages, you must rely on Win32 APIs.
The welcome and finish pages are an exception, up to a certain sense, because they are made with nsDialogs, NSIS’s plugin for creating completely custom pages.
As a result, it is possible to pass to add new widgets using the same API in a function passed MUI_PAGE_CUSTOMFUNCTION_SHOW
. However, I encountered a major problem: the default widgets fill the available space. Therefore, any new widgets will be hidden, and it will not be possible to interact with them with the mouse. I looked for a solution online - NSIS is more than 20 years old, so you can find a lot of documentation - but I did not find an answer that worked for me.
Eventually, I found that you can resize widgets on Win32 with MoveWindow
.
However, this function wants the position of the widget to move in addition to its size, and Win32 does not have a function to get the data we need directly.
GetClientRect
would be enough to get the widget size, but, as its documentation says, it always returns (0, 0)
as upper-left coordinates.
The most similar function I could find is GetWindowRect
, whose returned coordinates are relative to the upper-left corner of the screen. So, we can call GetWindowRect
on the widget to resize and on its parent dialog to find the coordinates in the dialog frame.
We can use NSIS’s System plugin, whose documentation offers an example of how to call GetClientRect
and access a C struct
.
We still miss a piece: all these Win32 functions accept a window handle as their first argument.
Luckily, NSIS sets them to variables in the scripts where the built-in pages are created. I wonder why they do not advertise them more in their documentation. The variables we are looking for are $mui.WelcomePage
(for the dialog handle) and $mui.WelcomePage.Text
. You can find all the variables in MUI2’s built-in page implementation in Contrib/Modern UI 2/Pages/
in NSIS’s installation directory.
The code I wrote to customize my welcome page looks like this:
!define MUI_PAGE_CUSTOMFUNCTION_SHOW WelcomeShow ; [...] Function WelcomeShow System::Call '*(i,i,i,i)p.r1' StrCpy $0 $mui.WelcomePage System::Call "user32::GetWindowRect(p $0, @ r1)" System::Call "*$1(i.r2,i.r3,i,i)" StrCpy $0 $mui.WelcomePage.Text System::Call "user32::GetWindowRect(p $0, @ r1)" System::Call "*$1(i.r4,i.r5,i.r6,i)" System::Free $1 IntOp $R0 $4 - $2 ; Text X IntOp $R1 $5 - $3 ; Text Y IntOp $R2 $6 - $4 ; Text width System::Call 'user32::MoveWindow(i$0,i$R0,i$R1,i$R2,i120,i0)' ; Add your widgets after this ; Possibly useful: IntOp $R3 $R1 + 120 ; Minimum Y for your first new widget FunctionEnd
I tried only to customize the welcome page, but I expect the finish page to be somehow similar.
Maybe doing the customization in this way is a bit convoluted. When I implemented the first version of this code, I did not know these pages were implemented with nsDialogs. But while writing this, I realized that copying, pasting, and adapting the original code might be another possibility (maybe, I have not tried, yet).
All the other pages are not implemented with nsDialogs but with traditional resource files. Using nsDialog macros resulted in a crash for me. It is still possible to customize those pages, but re-compiling NSIS or tools like Resource Hacker might be easier and more effective alternatives. Otherwise, this Stack Overflow answer can be used as a starting point to do it with scripts. It also contains some code similar to the one above, but I found it only after implementing mine 😅️.