Search code examples
nsis

Set value of InstallDir in a function, or set auto populate value somehow?


I'm creating an installer using NSIS. This installer actually installs two programs in two different directories in the same installer. I am doing this using the modern user interface (MUI) pages and simply calling MUI_PAGE_DIRECTORY twice specifying different starting parameteres, and capturing the directory in the LEAVE macro. What I'm wondering is, can I somehow call InstallDir in a function, or set the auto directory populate value in a function? Or possibly even call a function after the browse button has been returned from?

The reason I want to do this is so when the user clicks the browse button in either of the two directory pages, after they select a directory, the name of the finnal directory specifed in InstallDir will be appended.

For example: InstallDir value for program 1: c:\client InstallDir value for program 2: c:\program files\server

user clicks browse on program 1 and chooses c:\temp the resulting path is c:\temp\client

user clicks browse on program 2 and chooses c:\whatever the resulting path is c:\whatever\server

For reference here are the code snipits of what I have that works, but does not deal with the auto append browse button behaviour:

!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ClientDirectoryLeave
!insertmacro MUI_PAGE_DIRECTORY

!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ServerDirectoryLeave
!insertmacro MUI_PAGE_DIRECTORY

; Setup the page display for the client install page
Function ShowPageClient
  !insertmacro MUI_HEADER_TEXT "Client" "Client"
  !insertmacro MUI_INNERDIALOG_TEXT 1006 "Client"

  ; setup intal directory
  Push $0
  StrCpy $0 $PROGRAMFILES 2 #
  ; CLIENT_FOLDER_NAME is defined as a folder, but this would basicaly
  ; result in C:\Client as the first 2 characters of $PROGRAMFILES
  ; is the hard drive with program files installed on it
  StrCpy $INSTDIR "$0\${CLIENT_FOLDER_NAME}"
  Pop $0

    ; set the inital value of the directory text box  
    !insertmacro MUI_INNERDIALOG_TEXT 1019 $INSTDIR

    ; find and disable the directory selection box 
    ; We do not want users to type in this box
    FindWindow $R0 "#32770" "" $HWNDPARENT
    GetDlgItem $R1 $R0 1019 ;Text Box
    EnableWindow $R1 0
FunctionEnd


; Setup the page display for the server install location page
Function ShowPageServer
  !insertmacro MUI_HEADER_TEXT "Server" "Server"
  !insertmacro MUI_INNERDIALOG_TEXT 1006 "Server"

  ; setup intal directory
  ; SERVER_FOLDER_NAME is defined as a folder, but this would basicaly
  ; result in C:\Program Files\Server 
  StrCpy $INSTDIR "$PROGRAMFILES\${SERVER_FOLDER_NAME}"

  ; set the inital value of the directory text box  
  !insertmacro MUI_INNERDIALOG_TEXT 1019 $INSTDIR

  ; find and disable the directory selection box 
  ; We do not want users to type in this box
  FindWindow $R0 "#32770" "" $HWNDPARENT
  GetDlgItem $R1 $R0 1019 ;Text Box
  EnableWindow $R1 0

FunctionEnd

Note: I can make the browse button work for one of the directory pages, but then when I'm on the second page, the auto populate actual auto populates incorrectly


Solution

  • Okay I finally figured it out. Basically there is a function that is called to "verify" the path after browse button is clicked. I tied into this function an appended the directory manual if needed. To do this I created a new variable and set it in a function called when the page is displays as follows:

    ; Client Directory
    !define MUI_PAGE_CUSTOMFUNCTION_SHOW ShowPageClient
    !define MUI_PAGE_CUSTOMFUNCTION_LEAVE ClientDirectoryLeave
    !insertmacro MUI_PAGE_DIRECTORY
    
    ; Setup the page display for the client install page
    Function ShowPageClient
      ; setup intal directory
      Push $0
      StrCpy $0 $PROGRAMFILES 2 #
      StrCpy $INSTDIR "$0\${CLIENT_FOLDER_NAME}"
      Pop $0
    
      !insertmacro MUI_INNERDIALOG_TEXT 1019 $INSTDIR
    
      FindWindow $R0 "#32770" "" $HWNDPARENT
      GetDlgItem $R1 $R0 1019 ;Text Box
      EnableWindow $R1 0
    
      ; Setup the client or server variable to indicate that
      ; we're in the client install part to signal the .onVerifyInstDir function
      ; to put the correct directory
      StrCpy $CLIENT_OR_SERVER "client"
    FunctionEnd
    

    The function that is called after browse is .onVerifyInstDir so that is where I check the CLIENT_OR_SERVER variable and set the path appropriately

    ; This function is special and is called any time a
    ; path is validated on returning from the browse button
    ; need to append the correct directory if it does not already exists
    ; in the install directory path
    Function .onVerifyInstDir
      ; save the current $0 register, as it is used in this function
      Push $0
    
      ${If} $CLIENT_OR_SERVER == "client"
        ; in the client stage so directory must contain CLIENT_FOLDER_NAME
        ${StrContains} $0 "${CLIENT_FOLDER_NAME}" "$INSTDIR"
        ${If} $0 == ""
          ; the install dir does not contain the folder so append it
          StrCpy $INSTDIR "$INSTDIR\${CLIENT_FOLDER_NAME}"
        ${EndIf}
      ${Else}
        ; in the server stage so directory must contain SERVER_FOLDER_NAME
        ${StrContains} $0 "${SERVER_FOLDER_NAME}" "$INSTDIR"
        ${If} $0 == ""
          ; the install dir does not contain the folder so append it
          StrCpy $INSTDIR "$INSTDIR\${SERVER_FOLDER_NAME}"
        ${EndIf}
      ${EndIf}
    
      ; pop the saved register value
      Pop $0
    FunctionEnd
    

    Couple notes: the StrContains function I used was found here: http://nsis.sourceforge.net/StrContains

    Further reference to the .onVerifyInstDir function can be found here: http://nsis.sourceforge.net/Docs/Chapter4.html#4.7.2.1.10