You are here

NSIS Header WindowFunc

12 posts / 0 new
Last post
wraithdu
Offline
Last seen: 10 years 10 months ago
Developer
Joined: 2007-06-27 20:22
NSIS Header WindowFunc

WindowFunc header file. Window (not MS Windows) related functions.

Download WindowFunc.nsh

${WinWait}
Waits for a window to exist by title or PID (title optional, partial title accepted; PID required) and optional class (full classname required if used). Can specify a timeout value. Returns -1 if timed out, or the window handle if successful.

${WinWaitVisible}
Waits for a window to exist and be visible by title or PID (title optional, partial title accepted; PID required) and optional class (full classname required if used). Can specify a timeout value. Returns -1 if timed out, or the window handle if successful.

${EnumWindows}
Enumerates all windows or visible windows only, and passes the window's handle, title, class, and visibility to a user defined function.

NOTES:
It is always best to specify both a title and class. Some apps have multiple windows with the same title and different class, or different titles but the same class.

A child window will return as visible only if all of its parent or parent's parent (etc.) windows are visible.

Not all apps use the standard windows titlebar, some use a custom titlebar. In this case the window title cannot be read and this function will likely fail (timeout or get stuck in a loop). Be wary of this situation and use the window classname where appropriate.

wraithdu
Offline
Last seen: 10 years 10 months ago
Developer
Joined: 2007-06-27 20:22
WinWait prelim

Here's a POC for some of the missing parts of what we were talking about. Notice the absence of EnumWindows...wish I'd have known I could do it that way before, much faster.

OutFile test2.exe

!include LogicLib.nsh
!include WordFunc.nsh
!insertmacro WordFind

Section
	Call WindowFind
SectionEnd

Function WindowFind
	${Do}
		FindWindow $0 "" "" 0 $0
		${IfThen} $0 == 0 ${|} ${Break} ${|}
		System::Call /NOUNLOAD 'user32::GetWindowText(i r0, t .r1, i ${NSIS_MAX_STRLEN})'
		ClearErrors
		${WordFind} "$1" "notepad" "E*" $2
		${Unless} ${Errors}
			System::Call /NOUNLOAD 'user32::IsWindowVisible(i r0)i .r2'
			DetailPrint "Window Handle: $0"
			DetailPrint "Window Title: $1"
			DetailPrint "Visibility: $2"
		${EndUnless}
	${Loop}
	System::Free 0
FunctionEnd
digitxp
digitxp's picture
Offline
Last seen: 12 years 7 months ago
Joined: 2007-11-03 18:33
That reminds me...

I have an idea: Along with the splash, we can call system dlls to change the window title to (AppName)Portable. Too bad I have no idea how... I know you use user32.dll...
I'll go check out MSDN.

Insert original signature here with Greasemonkey Script.

wraithdu
Offline
Last seen: 10 years 10 months ago
Developer
Joined: 2007-06-27 20:22
First shot ;)

Ok, let's give this a try.

@haustin
I tried to exclude WordFunc/WordFind when not needed, like we did with ProcFunc. However in this library it is not known whether it is needed until the function is called from within the Section (mode specified as title). At this point it is too late to insert WordFunc/WordFind (must be done outside of the section). Can you think of a solution?

@digit
Why would you want to change an app's window title? I mean it could be done, but for what purpose? Also, not all apps use a "standard" windows title bar, and therefore the title cannot be changed (or even read in some cases).

digitxp
digitxp's picture
Offline
Last seen: 12 years 7 months ago
Joined: 2007-11-03 18:33
It was an idea.

John wants the portable apps to be distinctive, if you were to change the title from 'OpenOffice.org Impress' to 'OpenOffice.org Impress Portable Provided by PortableApps.com' or something.
Better than the splash, less intrusive and longer lasting.
Somebody know how to work MSDN?

Insert original signature here with Greasemonkey Script.

wraithdu
Offline
Last seen: 10 years 10 months ago
Developer
Joined: 2007-06-27 20:22
It's built into NSIS.

EDIT - n/m, that's probably the dumbest thing I've ever said.

EDIT2 - here

SilentInstall silent
AutoCloseWindow true
OutFile test2.exe

!include WinMessages.nsh

Section
	FindWindow $0 "Notepad" "Untitled - Notepad"
	SendMessage $0 ${WM_SETTEXT} 0 "STR:New Title"
SectionEnd
wraithdu
Offline
Last seen: 10 years 10 months ago
Developer
Joined: 2007-06-27 20:22
Update

Added ${EnumWindows}

haustin
Offline
Last seen: 12 years 7 months ago
Joined: 2007-09-19 17:59
Very nice!

And the "Very Clever Use of FindWindow" Award goes to... wraithdu!

I originally thought of copying your ProcessClose (a la EnumWindows()) and then discovered that's exactly how AutoHotKey does it. I never even noticed that FindWindow lent itself to looping with the childafter argument. Duh!

And of course it should be faster... it's built-in!

I tried to exclude WordFunc/WordFind when not needed, like we did with ProcFunc.

The only way I can think of is to create separate macros for WinWaitTitle and WinWaitPID. An additional benefit would be that it would reduce the number of user-visible arguments and remove the visible overloading of the title-or-pid argument.

Finally (and one reason I originally thought of it being in ProcFunc), WinWaitPID really should check to see if the target process still exists and return 0 if not (instead of continuing to wait for it to present a window).

Amazing stuff, yet again. -hea

P.S. You just want two NSIS Wiki pages, don't you. Blum

<EDIT>
A child window will return as visible if any one of its parent or parent's parent (etc.) is visible.

Actually, MSDN says it will return as visible only if the window and all of its ancestors are visible. But it shouldn't matter in this context because the only ancestor of a child of the desktop is the always-visible desktop. Smile
</EDIT>

wraithdu
Offline
Last seen: 10 years 10 months ago
Developer
Joined: 2007-06-27 20:22
Ahh, guess I read MSDN

Ahh, guess I read MSDN wrong...it's a little unclear actually.

I'll add the check for PID existence to the function here, no need to call the whole thing from ProcFunc.

I thought about 4 separate macros also, but thought it was a little cumbersome, assuming you wanted to keep the "Visible" macro separate as well. I just couldn't think of a way to distinguish between a windows title and PID otherwise (title could be just numbers).

Re: ProcFunc - I know we did a crap load of work tweaking the inclusion of WordFind...so do you think I should ditch "SearchProcessPaths" anyway to simplify the library? It's the same reasoning why I didn't include any kind of search in this library for EnumWindows.

haustin
Offline
Last seen: 12 years 7 months ago
Joined: 2007-09-19 17:59
Re:

Actually, come to think of it, a user might want to restrict matches by PID and title. So it should probably be something like:

${WinWait} [pid] "[class]" [visible:0/1] [timeout] $var
${WinWaitTitle} "[title]" [pid] "[class]" [visible:0/1] [timeout] $var

where [pid] and [class] may be "" if not used for matching

Your call on whether to include ${WinWaitVisible} and ${WinWaitTitleVisible} macros.

Re: ProcFunc...so do you think I should ditch "SearchProcessPaths" anyway to simplify the library?

Well... I'd probably lean that direction. But it would be nice to include a user_func example using WordFind so users won't have to figure it out.

Lest you think the exercise was pointless ;-), I have hacked your ProcessExists code to allow returning multiple PIDs (in a space-separated list) and to allow an exclusion list of PIDs (also space-separated) that won't get matched. The exclude functionality was also added to ProcessWait. You guessed it, ${ProcessExistsExclude} and ${ProcessWaitExclude} require WordFind! Smile   And as it's still in the "hacked" state, it doesn't yet account for users who don't need WordFunc.

If you're wondering about ProcFunc's mode numering, I renumbered it to 0=Exists, 1=GetPath, 2=GetName; +4 to match by PID; +8 to return multiple PIDs; +16 to use PID exclude list. Obviously, not all combinations make sense, so only these are implemented: 0, 1, 4, 5, 6, 8, 16, 24.

Also, I changed the process name-or-PID test to:

    ; passed process name or pid
    StrCpy $2 $0 1 -4
    ${If} $2 == "."
        ; get process pid
        ${ProcessExists} $0 $0
    ${EndIf}

...so that it works for any 3-letter extensions (including EXE and BIN).

Using all of the aforementioned, I have successfully gotten OpenOfficePortable to display the splash until right before OOo's own splash shows up, by changing the splash timeout to 999999 and modifying the last part of OpenOfficePortable.nsi to look like this:

    LaunchNow:
        StrCmp $SECONDARYLAUNCH "true" LaunchAndClose
            ${ProcessExistsMulti} "soffice.BIN" $1 
                ; returns a space-delimited list of matching PIDs;
                ; this is the list of previously launched matching processes
                ; (not needed here; shown for the sake of completeness)

            Exec $EXECSTRING
            ${ProcessWaitExclude} "soffice.BIN" 15000 $1 $0
                ; waits for a matching process, not in list of previous PIDs

            ${WinWait} PID $0 "" -1 $1
                ;  N.B.  assumes WinWait will return if process dies

            Sleep 1000
                ; necessary because OOo splash is created a while before displayed;
                ; WinWaitVisible doesn't return until first OOo non-splash opens,
                ; leaving OOo splash covered the entire time

            newadvsplash::stop

        StrCmp $WAITFORPROGRAM "true" 0 TheEnd
            ${ProcessWaitClose} $0 -1 $1
            StrCmp $RUNDATALOCALLY "true" 0 TheEnd
                RMDir /r "$DOCUMENTSDIRECTORY\${NAME}Temp\"
        Goto TheEnd
		
    LaunchAndClose:
        Exec $EXECSTRING

    TheEnd:
SectionEnd

-hea

wraithdu
Offline
Last seen: 10 years 10 months ago
Developer
Joined: 2007-06-27 20:22
I think I'm going to get rid

I think I'm going to get rid of SearchProcessPaths (but I'm keeping a copy!!).

I'm still pondering the WinWait funcs.

I think your Multi and Exclude ideas are interesting. But for your example, you could use the ${Execute} macro to return the exact PID you're concerned with, allowing you to skip the ProcessWaitExclude.

Either way, when you get those functions hammered out, you should publish them. You'd probably get more of a response from the NSIS forum as far as usage goes. Could lead to a wiki of your own Wink

haustin
Offline
Last seen: 12 years 7 months ago
Joined: 2007-09-19 17:59
Re:

I think I'm going to get rid of SearchProcessPaths (but I'm keeping a copy!!).

Yeah, it would probably be the handiest example implementation of a user_func -- that's why I suggested including it as an example. The other two (if you wanted to include that many) could be:

Page instfiles                  ; show the log screen
ShowInstDetails show            ; show details on the log screen
OutFile "testproc.exe"

!include ProcFunc.nsh
!insertmacro EnumProcessPaths
!include FileFunc.nsh
!insertmacro GetRoot

Var SEARCHSTRING
Var SEARCHLEN
Var SEARCHRESULT

; Show first process found with a particular name (e.g., "foo.exe")
;
Function user_func1 ; [processpath] [PID]
    Exch $0 ; process path, return value
    Exch
    Exch $1 ; PID
    Push $2
    StrCpy $2 $0 "" -$SEARCHLEN ; $2 = right($0,len("\foo.exe")
    StrCmp $2 "\$SEARCHSTRING" 0 NoMatch
        DetailPrint `    Found a process matching "$SEARCHSTRING":`
        DetailPrint `    [pid=$1] $0`
        StrCpy $SEARCHRESULT 1  ; tell script we found one
        StrCpy $0 0             ; return = stop looking
        Goto TheEnd
    NoMatch:
        StrCpy $0 1             ; return = continue looking
    TheEnd:
        Pop $2
        Pop $1
        Exch $0                 ; Push return value
FunctionEnd

; Show all processes with executables on a particular filesystem.
;
Function user_func2 ; [processpath] [PID]
    Exch $0 ; process path
    Exch
    Exch $1 ; PID
    Push $2
    StrCpy $2 $0 $SEARCHLEN     ; $2 = left($0,len("X:\")
    StrCmp $2 $SEARCHSTRING 0 NoMatch
        DetailPrint `    [pid=$1] $0`
        Goto TheEnd
    NoMatch:
        ; do nothing
    TheEnd:
        Pop $2
        Pop $1
        Pop $0
        Push 1                  ; return = continue looking
FunctionEnd

Section Main
;   Test1:
        ; pre-compute $SEARCHSTRING and $SEARCHLEN for user_func
        StrCpy $SEARCHSTRING "calc.exe"
        StrLen $SEARCHLEN "\$SEARCHSTRING"
                                    ; i.e., len("\calc.exe") = 9
        DetailPrint `Looking for first process called "$SEARCHSTRING"...`
        ${EnumProcessPaths} "user_func1" $0
        StrCmp $SEARCHRESULT 1 0 NotFound
            DetailPrint `Success!`
            Goto Test2
        NotFound:
            DetailPrint `Couldn't find one; try running Calculator.`
    Test2:
        DetailPrint ` `
        ; pre-compute $SEARCHSTRING and $SEARCHLEN for user_func
        ${GetRoot} $EXEPATH $0      ; $0 = root directory of filesystem this
                                    ; ...script was run from
        StrCpy $SEARCHSTRING "$0\"  ; add trailing slash
        StrLen $SEARCHLEN "$SEARCHSTRING"
                                    ; e.g., len("X:\") = 3
        DetailPrint `Listing all processes from same volume as this Test ($0)...`
        ${EnumProcessPaths} "user_func2" $0
        DetailPrint `Done.`
SectionEnd

And, yes, I actually tested this one. Smile   Note how Test2 lends itself readily to an eject script...

I think your Multi and Exclude ideas are interesting. But for your example, you could use the ${Execute} macro to return the exact PID you're concerned with, allowing you to skip the ProcessWaitExclude.

Actually, ${Execute} execs soffice.EXE which in turn loads soffice.BIN. The EXE doesn't create any windows, only the BIN does. I would imagine that other programs spawn multiple processes when launched as well. That's why my test code (OOo) first looks for running processes that match the target, then launches the parent process that spawns the target, then looks for a matching target that wasn't there in the first place. At that point, we have a useful PID (even if AllowMultipleInstances=true). For the majority of programs, the ${Execute} macro I requested would work beautifully. Wink

Could lead to a wiki of your own

I was kinda hoping you'd consider merging them with your ProcFunc.nsh and correcting any stupid coding errors (remember, I fired my code reviewer :P). I don't mind "{based on|influenced by} {idea|code|crap} contributed by [Real Name]" or similar, but otherwise, I prefer to influence the world behind the scenes. :cool:

-hea

P.S. My POS XPSP2 machine BSOD'd when I was mostly done with this post. Grrr. Sad

Fortunately I was using Firefox with browser.sessionstore.enabled=true (which by default writes to disk only every 10 sec, not every page load). Unfortunately, since posting requires logging in, Session Restore didn't bring back my unfinished post. So, I had to manually edit my sessionstore.bak to retrieve the contents of my unposted comments and unencode all of the %nn nonsense. Minor pain, but thank god I was using Firefox!

Log in or register to post comments