Archive: Howto, custom MUI page with progressively added text?


Howto, custom MUI page with progressively added text?
Question for the NSIS gurus.

I wish to generate a custom MUI page which contains a single user locked text area.

I wish the text area to be populated by my installer from the result of command line program. The command line program takes a while to generate all of its output. I wish that after every 2 or so seconds to grab more output from the command line program and splat it into the text area of my custom MUI screen.

I want the behaviour to be similar to the MUI InstFiles default page when the "show details" button is expanded. That default screen will progressively show files as they are being installed. This screen also prevents user editing of the text area and disables the "next" button until it has completed.

That is exactly what I want to do in my custom MUI page. But I can't figure out how to mimic it.

I know some of the building blocks. Usage of 'nsExec::ExecToStack' with a timeout should get me the content I'm interested in. Also, creation of the custom page layout is also easy, as is displaying my page when I want.

What I can't figure is the howto to progressively fill in the text area of my page. Where in my NSIS script do I place my code?

The three standard page callbacks: "pre", "show" and "leave" appear to kick in either before my page exists, or after it finishes, not actually during the lifetime of my page. Note, my custom page needs to run after InstFiles has been run and completed.

I have a single Section named "Install". The instructions of this section only appear to kick in during the runtime of the InstFiles default MUI page.

What I want is to have either a callback or section associated with the runtime of my custom page. Hence, I can display the content I want and disable the next button till I've finished (hopefully disabling the next button is also easy).

Long story short, I can't figure out how to do this task. If the gurus can give some hints that would be most appreciated by this newbie.

Dennis.


For custom pages there aren't those 3 callbacks pre, show, and leave.
There are the create callback and the leave callback, as for the show callback, this is the period between the call InstallOptions::InitDialog and the call InstallOptions::show.


Again, I'd like to emphasize I'm interested in finding how/where do I place my "progressive output to text area" code such that it displays in the text area of my custom page whilst that page is visible.

When I put my code in a Section it appears the section code gets invoked when InstFiles is in effect (not my page). Too early.

When I put my code in a callback function it gets invoked before my page gets created or after ..... but not during.

How do I code things up in NSIS such that my code gets invoked during the visible lifetime of my custom MUI page (which is after InstFiles Page)?

Dennis.


The custom page (and the nsis exe) only has a single thread running. While the custom page is displayed, you don't have any way to capture the other program's output and update the text box.

You will need either a plugin that can create a new thread, or you can create a completely separate program that will capture the second program's output and update the text box with wm_settext. Either way, you would start the new thread/exe first in the custom page pre function and then show the custom page.

Don


Excellent information. Just what I needed to know.

I knew I was going round in circles, not getting anywhere. Now I know why, it can't be done.

Very much appreciated.

Thanks for the extra tidbits for me to follow. Cool. NSIS community rules :)

Dennis.


Figuring out the way page callbacks act, would help you understand that during the visible lifetime of the page there's not action unless a control is triggered.
So, end user or another running process, should trigger e.g. the next button in order to activate the leave callback.
Once the leave callback is activated, you can change e.g. the contents of a text field and add abort to stay on the page.


Originally posted by Red Wine
Figuring out the way page callbacks act, would help you understand that during the visible lifetime of the page there's not action unless a control is triggered.
So, end user or another running process, should trigger e.g. the next button in order to activate the leave callback.
Once the leave callback is activated, you can change e.g. the contents of a text field and add abort to stay on the page.
The single-threaded nature of the NSIS generated installer had not entered my mind. Now it all makes sense why I can't do what I wanted to do.

Like Wile E. Coyote it's back to the drawing board for me :)

demiller9 provided some hooks for me to explore.

Thanks to all.

Dennis

I've added new switches called /TOFUNC and /ENDFUNC to the ExecDos plug-in. Was about to do it for nsExec but it doesn't already have asynchronous support. I also added an ::isdone function to check if a thread has completed but we don't need to use that here.

Here's what you need to do:

!include LogicLib.nsh

Page Custom CustomPageShow

Function ExecDosCaptureOutput
Pop $R0
; Do something with line of output.
FunctionEnd

Function ExecDosCaptureOutputDone
; Re-enable buttons.
GetDlgItem $R0 $HWNDPARENT 1
EnableWindow $R0 1
GetDlgItem $R0 $HWNDPARENT 2
EnableWindow $R0 1
GetDlgItem $R0 $HWNDPARENT 3
EnableWindow $R0 1
FunctionEnd

Function CustomPageShow

!insertmacro MUI_INSTALLOPTIONS_EXTRACT myFile.ini
!insertmacro MUI_INSTALLOPTIONS_INITDIALOG myFile.ini

; Disable all buttons.
GetDlgItem $R0 $HWNDPARENT 1
EnableWindow $R0 0
GetDlgItem $R0 $HWNDPARENT 2
EnableWindow $R0 0
GetDlgItem $R0 $HWNDPARENT 3
EnableWindow $R0 0

GetFunctionAddress $R0 ExecDosCaptureOutput
GetFunctionAddress $R1 ExecDosCaptureOutputDone
ExecDos::exec /NOUNLOAD /ASYNC /TOFUNC=$R0 /ENDFUNC=$R1 "cmd /?"
Pop $R9

!insertmacro MUI_INSTALLOPTIONS_DISPLAY

; Security feature.
ExecDos::wait $R9

FunctionEnd


I've got it to disable the buttons until it has finished because things can get messy if you leave the thread running while you change to another dialog.

How will you display the output on your custom page? If it is just a single static text label or text box then you are limited to NSIS_MAX_STRLEN characters (1024 in the normal NSIS build). If you use a ListBox you can use SendMessage with LB_ADDSTRING to add a line of output to your list box.

Stu

Wow, big-time thanks Afrow UK, your logic looks like it could do the job.

I will definitely try this when I get to work next week. If it does what I want you will have saved me "A LOT OF WORK" :)

Can't wait, I will let you know how things go.

Thanks.

Dennis.


!insertmacro MUI_INSTALLOPTIONS_DISPLAY require ini file parameter, so you can use !insertmacro MUI_INSTALLOPTIONS_SHOW in this place (no param).
And we test now new ExecDos release combining Afrow UK and my code. It probably will be uploaded in Monday.


Stu and Takhir,

Ok, I've experimented with your new ExecDos (with new /TOFUNC and /ENDFUNC functionality) suggestion.

Really close ..... but not quite.

Let me explain.

My test command line program does the equivalent of

cout << "hello\r\n"
cout.flush()
sleep 1
cout << "hello 2\r\n"
cout.flush()
sleep 2
..... etc etc

The /TOFUNC function does get called multiple times for each line of output in my command line script. That's great!!

The problem. The /TOFUNC function appears to get called (multiple times) at the end of the invocation of the command line script (not during). The sleeps will all complete "before" the first invocation of the /TOFUNC function (usage of MessageBox inside the /TOFUNC function confirms this).

My ideal would be, when a line of output is read (inside ExecDoc::exec) then the /TOFUNC function would get invoked at that point. That way I can get my custom list box getting updated progressively, rather than just one big splat at the end.

Note, the "SendMessage via ${LB_ADDSTRING}" worked great, thanks.

I hope I'm not asking too much, but if the /TOFUNC function could get invoked immediately once a line of output has been encountered then my problem would be completely solved.

As it stands now, I'm close but not there from a behavior perspective.

Any feedback from the ExecDos gurus would be appreciated.

Dennis.


This was also a problem with nsExec::Exec. Output would only be displayed when the process had actually completed.

It would be much easier if you converted your executable into a NSIS plug-in that calls ExecuteCodeSegment itself.

Stu


IMHO this should work in ExecDos, with /DETAILED option plug-in could send items to detailed window (both sync and async modes), why script function cannot?


Stu, Takhir and others,

My current thinking (and experimentation) is to follow Don's (demiller9) advice.

That being to create my own C-plugin function (say called 'customExec') that gets given a command line program + args string. The plugin creates a thread, which then does a popen. When the thread reads a line of output it would do a

SendMessage(handle-to-list-box, LB_ADDSTRING, 0, (LPARAM)popen-read-line);

to populate the list-box in my custom page immediately. Note, after the C-plugin creates the thread it immediately returns control back to the NSIS script (whilst the thread is still running in the background, hopefully the thread survives a return from plugin event).

The custom page pre function initiates this 'thread+popen' functionality. I hope to disable the custom page 'back/next/cancel' buttons as well.

The custom page leave function calls my own C-plugin function (say called 'customWait') that waits until the thread naturally completes. Once that is done the 'back/next/cancel' get re-enabled and the thread gets cleaned up.

Guys, does this sound feasible?

Note, I can already get a C-plugin to populate an NSIS custom page list-box (via FindWindowEx(..., "#32770") magic).

Anyway, it would be good to know whether I'm heading down the right/wrong path. Please speak up if I'm being silly.

I do appreciate the advice I've be given here.

Dennis.


Did you read last ExecDos description http://nsis.sourceforge.net/ExecDos ? Last weekend I added /TOWINDOW option (with samples), edit, listbox and listview target windows supported. I also attaching to this post corrected Afrow UK' /TOFUNCTION sample - time stamps show script function call times, and IMHO this works as expected (with !ifdef'ed output to Detailed window or message box).


Takhir,

Looks interesting. I'll give the new ExecDos a go, the /TOWINDOW does look interesting.

Side note, my plugin idea is not working that well. My plugin spawned a thread (easy), the thread then tries to fill in the listbox via a SendMessage call. Boom-crash :( :(

Hmmm, it seems you can't use SendMessage in normal threads due to GUI message queue funniness. One must derive from CWinThread etc etc (must involve MFC). I don't have MFC since I'm using MSVC 2005 Express Edition.

Sigh!!!

Why must it be soo hard.

Hence, I'm back to square one. However, new ExecDos may be the answer. Thanks Takhir, time for me to play.

Dennis.


:D :D :D :D :D

It works!!!!

/TOWINDOW works!!

I'm soooo happy, like the of the end of "Return of the Jedi", that's how I feel.

I really appreciate everyone's assistance, especially Takhir (and co) for having a killer plugin.

ExecDos::exec with /TOWINDOW is "the solution". Great.

Now I can finish writing my killer installer (obviously based around NSIS and ExecDos) :) :)

Dennis.


Hello I have been following this reply. I am knew to NSIS and windows installers. Old mainframe guy. However I have managed to use the installer and are left with oe issue. I am using /TOWINDOW to give status of a EXECDOS command for a FTP job I am using. It works well.

However, I would also like to log it to a file. How can I get the data back from the listbox.ini (I am using the sample for EXECDOS /TOWINDOW) so that I can write it to a file?

I tried the TOFUNC and I could then write it to log file, but could not work out a way of displaying it to the status window.

BTW the reason why I want it to a log is so that I can interrogate it to work out if the EXECDOS command (e.g FTP) has been succsssful. The window status is so that the end user can see what we are doing in real time.


Code snippents below (when I tried the TOFUNC. You can see the TOWINDOW commented out):

Function batchStatusWindow

!insertmacro MUI_HEADER_TEXT "zSQLMON Install Command Status Bar" "$task"
!insertmacro MUI_INSTALLOPTIONS_EXTRACT "${statusWindow}"
!insertmacro MUI_INSTALLOPTIONS_INITDIALOG "${statusWindow}"
Pop $0


; first disable
StrCpy $R0 0
Call enableNext
; then leave for enable when ENDFUNC calls enableNext
StrCpy $R0 1
GetFunctionAddress $R2 enableNext
GetFunctionAddress $R3 outputHandler
; !insertmacro MUI_INSTALLOPTIONS_READ $R8 "${statusWindow}" "Field 1" HWND
; ExecDos::exec /NOUNLOAD /ASYNC /TOWINDOW /ENDFUNC=$R2 '"$InstallPath\$cmdname"' "" $R8
ExecDos::exec /NOUNLOAD /ASYNC /TOFUNC /ENDFUNC=$R2 '"$InstallPath\$cmdname"' "" $R3
Pop $R9
!insertmacro MUI_INSTALLOPTIONS_SHOW


; Security feature.
ExecDos::wait $R9
Pop $R4


FunctionEnd

; This guy tries to write to log + window at same time
Function outputHandler

Pop $2
StrCpy $panelData $2
${LogText} $panelData
SendMessage $0, LB_ADDSTRING, 0 "STR:$panelData"

FunctionEnd
Function enableNext

GetDlgItem $0 $HWNDPARENT 1
EnableWindow $0 $R0
GetDlgItem $0 $HWNDPARENT 2
EnableWindow $0 $R0
GetDlgItem $0 $HWNDPARENT 3
EnableWindow $0 $R0


endenableNext:

FunctionEnd


No need to bother with this posting. I figured it out I had missed a window/field handler. Once I set this correctly it worked.

Now it is looking gooooood!