Menu

#1163 Cannot minimize installer programatically

3.0 Series
open
nobody
None
5
2016-11-21
2016-11-09
Marshall
No

I have a requirement to make an installer that is not silent but starts minimized. The best I can do is to minimize after the instfiles page shows briefly, which looks bad.

It is not possible to minimize the installer window until after either:
the user is able to interact with a page, or
the first section begins.


Function .onGUIInit
ShowWindow $HWNDPARENT ${SW_MINIMIZE}
FunctionEnd
The above does not work!


Page instfiles preInstfiles showInstfiles leaveInstfiles
Function showInstfiles
ShowWindow $HWNDPARENT ${SW_MINIMIZE}
FunctionEnd
The above does not work!


Page instfiles preInstfiles showInstfiles leaveInstfiles
Section dummy
ShowWindow $HWNDPARENT ${SW_MINIMIZE}
SectionEnd
The above does work, but you see the instfiles page briefly before it minimizes


Page license preLicense showLicense leaveLicense
Page instfiles preInstfiles showInstfiles leaveInstfiles
Function showInstfiles
ShowWindow $HWNDPARENT ${SW_MINIMIZE}
FunctionEnd
The above works - the installer minimizes when you click Install


Page license preLicense showLicense leaveLicense
Page instfiles preInstfiles showInstfiles leaveInstfiles
Function preLicense
Abort
FunctionEnd
Function showInstfiles
ShowWindow $HWNDPARENT ${SW_MINIMIZE}
FunctionEnd
The above does not work!

It seems like NSIS is forcing the window to show unminimized after all GUI initialization is finished. Can't it check to see if the window state is not minimized/maximized/etc before overriding?

Discussion

  • Anders

    Anders - 2016-11-13

    The first time ShowWindow is called in a process Windows ignores the SW parameter so you are fighting both NSIS and Microsoft here!

    This seems to work OK for me:

    !include WinMessages.nsh
    Function minhack_leave
    ShowWindow $HWNDPARENT 7
    ShowWindow $HWNDPARENT 2
    FunctionEnd
    Function minhack_pre
    ShowWindow $HWNDPARENT 7
    ShowWindow $HWNDPARENT 0
    System::Call 'USER32::PostMessage(i$HWNDPARENT,i${WM_COMMAND},i1,i0)'
    FunctionEnd
    
    Page Components minhack_pre "" minhack_leave
    #Page Directory
    Page InstFiles
    

    If you want to be really crazy you can spawn a new instance of yourself that sets the initial show command:

    !include WinMessages.nsh
    !include LogicLib.nsh
    
    Function .onGUIInit
    System::Alloc 68
    Pop $4
    System::Call "*$4(i 68)"
    System::Call 'KERNEL32::GetStartupInfo(ir4)'
    System::Call "*$4(i,i,i,i,i,i,i,i,i,i,i,i.r1,&i2.r2)"
    ${If} $2 != ${SW_SHOWMINIMIZED}
        System::Call '*(i,i,i,i)i.r5'
        System::Call "*$4(i,i,i,i,i,i,i,i,i,i,i,i 1,&i2 ${SW_SHOWMINIMIZED})"
        StrCpy $0 $CMDLINE
        System::Call 'KERNEL32::CreateProcess(t "$EXEPATH",tr0,i0,i0,i0,i0,i0,i0,ir4,ir5)i.r0'
        ${If} $0 <> 0
            System::Call "*$5(i.r1,i.r2)"
            System::Call 'KERNEL32::CloseHandle(i $1)'
            System::Call 'KERNEL32::CloseHandle(i $2)'
            Quit
        ${EndIf}
        System::Free $5
    ${EndIf}
    System::Free $4
    FunctionEnd
    
     
  • Marshall

    Marshall - 2016-11-14

    That looks better but the window still shows briefly before minimizing, rather thans starting minimized, which the higher ups say is a no go.

    The CreateProcess method seems to solve the problem but is a very inconvenient way of achieving the desired result (my .onInit code is huge, can't simply be skipped on relaunch, and can't be skipped before the decision to minimize).

    It would be far better if the first call to ShowWindow from my NSIS script would tell NSIS not to call ShowWindow itself after GUI initialization is done.

    It should be a really simple change - I'd offer to make a patch myself but so far have had zero success in building NSIS (and C/C++ is really not my forte)

     

    Last edit: Marshall 2016-11-14
  • Anders

    Anders - 2016-11-16

    Have you tried LockWindow or playing with the window styles?

    !include WinMessages.nsh
    Function .onGuiInit
    LockWindow On
    FunctionEnd
    Function minhack_leave
    ShowWindow $HWNDPARENT 7
    ShowWindow $HWNDPARENT 2
    LockWindow Off
    FunctionEnd
    Function minhack_pre
    ShowWindow $HWNDPARENT 7
    ShowWindow $HWNDPARENT 0
    System::Call 'USER32::PostMessage(i$HWNDPARENT,i${WM_COMMAND},i1,i0)'
    FunctionEnd
    

    or maybe

    !include WinMessages.nsh
    !include nsDialogs.nsh
    !define /IfNDef WS_EX_LAYERED 0x00080000
    Function .onGuiInit
    System::Call 'USER32::GetWindowLong(i $hwndparent, i ${GWL_EXSTYLE})i.r0'
    IntOp $0 $0 | ${WS_EX_LAYERED}
    System::Call 'USER32::SetWindowLong(i $hwndparent, i ${GWL_EXSTYLE},i $0)'
    FunctionEnd
    Function minhack_leave
    ShowWindow $HWNDPARENT 7
    ShowWindow $HWNDPARENT 2
    System::Call 'USER32::GetWindowLong(i $hwndparent, i ${GWL_EXSTYLE})i.r0'
    IntOp $1 ${WS_EX_LAYERED} ~
    IntOp $0 $0 & $1
    System::Call 'USER32::SetWindowLong(i $hwndparent, i ${GWL_EXSTYLE},i $0)'
    FunctionEnd
    Function minhack_pre
    ShowWindow $HWNDPARENT 7
    ShowWindow $HWNDPARENT 0
    System::Call 'USER32::PostMessage(i$HWNDPARENT,i${WM_COMMAND},i1,i0)'
    FunctionEnd
    

    How noticable any issues are probably depends on your Windows version, Visual Style and DWM settings (which you have not told us anything about).

     
  • Marshall

    Marshall - 2016-11-21

    I've tried a bunch of things and still no joy. Windows 7 and 10, default settings all around.
    Ultimately the problem is that NSIS calls ShowWindow when all UI init is complete, thus overriding any value I set.

    This has become an even more annoying problem now, as the NotifyIcon plugin calls ShowWindow as well, which means that even relaunching the process to set the initial show state no longer works

     
  • Marshall

    Marshall - 2016-11-21

    It looks to me as if NSIS is calling ShowWindow on every page. Is this necessary?
    I get that we don't want to show the window until the first page has loaded but can't we add a check so that we only call ShowWindow once and only if the user's script hasn't called the NSIS ShowWindow instruction?

     
  • Marshall

    Marshall - 2016-11-21

    Just re-read my previous posts and realised that it might have seemed I was ranting a bit. Apologies for that :)

    The use case for what we’re trying to do here is start an install in an elevated state and in the system tray. This allows for the install (which takes quite some time for our software) to continue in the background without bothering the user. The complication comes because we relaunch our installer in an elevated state after the user has agreed to start the install, but not when we first run our installer (similar to other application installers such as Opera).

    We also want the installer to look as good as possible. The problem with all the work arounds we've tried is that there is always some flicker or screen artifacts. I can provide screenshots or video if needed but suffice to say you either see - briefly - an incomplete dialog, a minimize animation or a taskbar icon.

    The problem, ultimately, seems to be how ShowWindow works and how Windows expects you to use it. It's kind of a catch 22 situation. The first call to ShowWindow when a program starts up needs to be the one and only call prior to user interaction so that the initial show state (e.g. from CreateProcess or a shortcut) is respected. After the first page has been shown we can do anything we want because it will be user-triggered.

    But because NSIS calls ShowWindow under-the-hood (which it has to), it has to assume that SW_SHOW is the right choice - the user's NSIS script cannot make the decision about what the default initial show state should be. This makes sense from the perspective of your average user - NSIS simplifies a lot of installer creation by automating and abstracting away the UI - and SW_SHOW makes sense for probably 99% of installers.

    Plugins like NotifyIcon require ShowWindow to already have been called so that they don't swallow the startup state. That's fine except for when they are called before first page show is complete.

    So there are 2 issues that can occur right now:
    1. User script cannot set their own default initial show state via ShowWindow.
    2. Startup state set by CreateProcess/shortcut gets discarded by plugins (or by #1)

    The work arounds to these issues are either very unwieldy (relaunch) and/or result in screen flicker which reduces the quality of the user experience in these cases.

    I think my proposed solution (only have NSIS call ShowWindow if the user's script hasn't already called it) would solve both issues and allow us to achieve our objective. In the case of NotifyIcon combined with respecting startup mode, the user's script would have to call ShowWindow itself before the plugin, which is not ideal but at least it would be possible and could be added to the plugin's documentation.

     
    • Anders

      Anders - 2016-11-22

      You lose me a bit when you start talking about hidden taskbar buttons and tray icons because I personally don't believe a installer should use these features.

      The NotifyIcon plugin might be able to override NSIS if this behavior is really desired. If it subclasses the main window it can catch WM_WINDOWPOSCHANGING and prevent some changes. ITaskbarList::DeleteTab can be used to remove buttons from the taskbar.

      Worst case scenario, you create a plugin that uses IAT hooking to change ShowWindow.

       

Log in to post a comment.