Skip to content
⌘ NSIS Forum Archive

Displaying dynamic data (on runtime) in installer dialog

18 posts

Pawel#

Displaying dynamic data (on runtime) in installer dialog

Hi,
I want to display some dynamic data during installation process. Lets say it is time of installation (but the goal is to display any data, like for example RAM usage). Data should be displayed on the bottom of each installer page (new text control in own ui [!define MUI_UI "my_ui.exe"]). It should work like this:



1. User run installer. Time is set to 00:00
2. User sets options on different installer pages (built-in or created via nsDialogs). Our text control displays time (each second time is refreshed)
3. Installer installs files... Time is displayed
4. Installer finishes its work. On Finish page time is stopped. For example: 03:24

Now, is that possibly to do in simple way? Or is there any plugin that can do this?
Have you got any idea?
Thanks,
-Pawel
aerDNA#
It would be pretty easy with ${NSD_CreateTimer} but unfortunately that only works on Welcome/Finish and custom.
I think you should be able to do something with WndSubclass plugin. Compile the example, you'll see what I mean.
stass#
Pawel
You can use ThreadTimer_plug-in and function CreateWindowEx

something like this:

;  http://nsis.sourceforge.net/ThreadTimer_plug-in
!include MUI2.nsh
!include "FileFunc.nsh"
!define MUI_CUSTOMFUNCTION_GUIINIT onGUIInit
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY 
!insertmacro MUI_PAGE_INSTFILES
!define MUI_PAGE_CUSTOMFUNCTION_SHOW StopTimer
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_LANGUAGE "English"
!define WS_BORDER 0x00800000
InstallDir "$TEMP"
OutFile "test_ThreadTimer.exe"
Var Stime
!macro CreateWindow 
System::Call `*(i30,i201,i50,i12)i.R0`
System::Call `user32::MapDialogRect(i$HWNDPARENT,iR0)`
System::Call `*$R0(i.R1,i.R2,i.R3,i.R4)`
System::Free $R0
System::Call 'user32::CreateWindowEx(i0,t "EDIT",t "$4 : $5 : $6",i${DEFAULT_STYLES}|${WS_BORDER},iR1,iR2,iR3,iR4,i$HWNDPARENT,i222,i0,i0) i.R2'
!macroend
Function .onInit
System::Call 'kernel32::GetTickCount(v)i.r0'
StrCpy $Stime $0 
GetFunctionAddress $1 ChekTime
ThreadTimer::Start 100 -1 $1
FunctionEnd
Function onGUIInit
!insertmacro CreateWindow
FunctionEnd
Function StopTimer
ThreadTimer::Stop Function
FunctionEnd
Function ChekTime
System::Call 'kernel32::GetTickCount(v)i.r1'
IntOp $0 $1 - $Stime
    IntOp $1 $0 / 1000
    IntOp $0 $0 % 1000
    IntOp $2 $1 / 60
    IntOp $1 $1 % 60
    IntOp $3 $2 / 60
    IntOp $2 $2 % 60
${NSD_SetText} $R2 "$3 : $2 : $1"     
FunctionEnd
Section
Sleep 2000
SectionEnd 
But sometimes I get an error ... Why - it is not clear ...
JasonFriday13#
It's because you are creating the thread in .onInit, and because there is no visible window yet the handle in ChekTime is null.

You could check for a null handle and skip the 'settext' line, or you could move the code in .onInit into onGUIInit after creating the window.

[edit] I just realized that last month I have clocked up ten years on this forum, man time flies 😛.
Pawel#
Is it possible to use user variable instead of predefined variable $0-9 in System plugin call?

System::Call 'kernel32::GetTickCount(v)i.r0' 
var /Global MyVar
System::Call 'kernel32::GetTickCount(v)i.$MyVar' 
Pawel#
Thanks.
I was asking, as there is something wrong with the ThreadTimer_plug-in and system.dll calls...
Code by @stass works almost fine. I can see a time displayed on NSIS dialog on every NSIS built-in pages or nsdialog pages.
The problem is when installer starts to install files.
Take a look (images are a bit changes, I removed some installer captions):



It seems that the ThreadTimer_plug-in in some way interferes with NSIS main thread. Could you please test this with installing files using File command? For example:

Section Test
    SetOutPath "$INSTDIR\TestDir"
        File /r "FILES\*.*"    
    
SectionEnd 
I wonder if you can reproduce it... I get completely random errors.
-Pawel
LoRd_MuldeR#
Some thoughts:
  1. First of all, ThreadTimer plug-in calls ExecuteCodeSegment() from a background thread. But I'm not quite sure it is "safe" to call this function from any other thread than the NSIS "main" thread. If you call a function that is not thread-safe – and you have to assume that a function is not thread-safe, unless the developer explicitly states that it is thread-safe – from multiple threads and without the required synchronization, then weird things can happen! Any "it works for me (most of the time)" really isn't an argument for "it's safe to do" here. Quite often the bugs that result from "threading" issues are difficult to pin down.
  2. Even if we assume that ExecuteCodeSegment() is thread-safe, there still is the problem that you are doing GUI stuff in your custom function which is called by ThreadTimer. And you need to keep in mind that your custom function will be running in the context some "background" thread, not the "main" NSIS thread. I'm not 100% sure how things are in NSIS here, but every GUI framework I have ever used – including Qt, WxWidgets, Microsoft.NET/WPF, Delphi/VCL and Java/Swing – strictly forbids doing any direct GUI access from any thread, except for the "main" GUI (dispatcher) thread. And I've read a quite interesting article on why it's practically impossible to write a multi-threaded GUI framework that scales well - though I cannot find the link to that article right now.
  3. If you use the system plug-in, you should triple-check your code, as it's quite easy to create memory corruptions and/or stack corruptions by using the system plug-in in an incorrect way. This can lead to "undefined" behavior later on!
Anders#
ExecuteCodeSegment itself should be thread safe (pass a NULL HWND in the 2nd parameter) but anything that changes the NSIS variables, stack or internal state (Set*) is not. You can probably get away with a lot of things, the most important part is not calling functions that use push/pop from multiple threads at the same time. "System::Call foo::bar()" actually uses the stack so if you are using System::Call on the background thread you must make sure that it or any other stack usage only happens on that thread and not in any sections!

Doing UI stuff from other threads is safe enough, SendMessage knows how to handle cross-thread messages.
LoRd_MuldeR#
Is there a particular reason why ThreadTimer plug-in uses a secondary thread rather than the SetTimout function, which would be executing the callback function in the "main" thread? Well, I guess there are situations where you want the behavior ThreadTimer as it is implemented. But I think using SetTimout could avoid a lot of headache regarding synchronization here...
LoRd_MuldeR#edited
For what it's worth, I have implemented a ThreadTimer alternative, which is based on SetTimer rather than creating a secondary thread. This comes at the advantage that your callback function will be executed in the context of the NSIS "main" GUI thread, so there will be no synchronization issues. The drawback is that you have to call TimerCreate from the "main" GUI thread, so the .onGuiInit function probably is the place to do it! Trying to do it in the sections won't work, as expected, because a message loop is required.

New version of plugin is here http://forums.winamp.com/showpost.ph...1&postcount=17
Pawel#
OK, end of test.
Unfortunatelly, I see the same errors. Maybe it is plugin, maybe my code (my installer is a big installer wih plenty of code). Also, I see that time is increased each 2 seconds, not 1 ( I set:
${StdUtils.TimerCreate} $TimerId MyCallbackFunction 1000)...
LoRd_MuldeR#
Originally Posted by Pawel View Post
OK, end of test.
Unfortunatelly, I see the same errors. Maybe it is plugin, maybe my code (my installer is a big installer wih plenty of code).
You will probably need to strip down your installer to the shortest possible script that can reproduce the problem.

Originally Posted by Pawel View Post
Also, I see that time is increased each 2 seconds, not 1 ( I set:
${StdUtils.TimerCreate} $TimerId MyCallbackFunction 1000)...
If you set a timeout of 1000 milliseconds, it means that there will be a delay of at least 1000 milliseconds, but the actual delay can be somewhat higher! That's because the computer's internal timer precision is usually ~16 milliseconds (though it can be forced to a higher precision using timeBeginPeriod, but this may be bad for laptop's battery); because multiple processes/threads have to share the CPU, so your process/thread can not occupy the CPU exclusively (instead it will get a short time slice to run on the CPU every now and then); and because the GUI thread has to handle a lot of messages, so the WM_TIMER message may not be processed immediately, even if it was appended to the thread's message queue after exactly 1000 milliseconds and if the thread was actually executing at that moment.
Pawel#
Yes, you are right. I will try to investigate where the problem is. It will be very hard to do.
Thanks for explanations and code. I will try to post here my example installer code (I will remove most of the lines... will see if problem still exists).
Will let you know.
LoRd_MuldeR#
Here is an updated version, which also has proper documentation of the new functions included:


Regards.

(BTW: Is there a way to edit my old posts, in order to strip obsolete attachments?)
Pawel#edited
I made some new tests. I compiled my installer as ANSI (NSIS 3.0 b1) with LoRd_MuldeR's StdUtils plugin. There was no problems!

EDIT: No, I checked log and NSIS (ANSI) just skip broken paths (with system.dll in path). Not showing messageboxes... strange.

I am now removing all external plugins and test adding each one after test...

@Anders
Have you got any idea what could be a problem? Maybe NSIS Unicode has some bug? But how to know what is the problem?

Edit2: I use in my code the following system.dll calls:

; 1. Get System Tick
System::Call 'kernel32::GetTickCount(v)i.r9' 
    
; 2. Example of creating image control (in nsDialogs Page)
nsDialogs::CreateControl STATIC ${WS_VISIBLE}|${WS_CHILD}|${WS_CLIPSIBLINGS}|${SS_ICON} 0 10u 95u 100% 100% ""
Pop $R1
StrCpy $0 $PLUGINSDIR\Images\IT_Auto_Install.ico
System::Call "user32::LoadImage(i 0, t r0, i ${IMAGE_ICON}, i 0, i 0, i ${LR_LOADFROMFILE}) i.s"
Pop $IT_TYPE_ICONS
SendMessage $R1 ${STM_SETIMAGE} ${IMAGE_ICON} $IT_TYPE_ICONS
        
; Deleting object
System::Call "gdi32:DeleteObject(i $IT_TYPE_ICONS)"
; 3. Refreshing Shell Icons
System::Call "Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)"
; 4. Creating Mutex    
System::Call "kernel32::CreateMutexW(i 0, i 0, t 'My_Mutex') i .r1 ?e"
; 5. Getting System resolution
System::Call "user32::GetSystemMetrics(i 0) i .r0"
System::Call "user32::GetSystemMetrics(i 1) i .r1"
; 6. Free Unused Library (when registering dll with regsvr32)
System::Call 'Ole32::CoFreeUnusedLibraries()'
System::Call 'Ole32::CoFreeUnusedLibraries()' 
Am I using it correctly for Unicode NSIS?
Should I call it in other way in ANSI NSIS?