Sign in to follow this  
Followers 0
orbs

detect fast user switch and session lock/unlock

15 posts in this topic

i guess i'm missing something dead simple. here's what i've tried:

from >this thread post #4 i took the script and modified it to compensate for constants already declared, includes missing, etc. the idea is to GUIRegisterMsg($WM_WTSSESSION_CHANGE), but no matter what i try, it does not work: the registered function is never called.

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <Date.au3>

;Global Const $WM_WTSSESSION_CHANGE = 0x2B1 ; commented out as it is already included in <GUIConstantsEx.au3> or <WindowsConstants.au3>
Global Const $WTS_CONSOLE_CONNECT = 1
Global Const $WTS_CONSOLE_DISCONNECT = 2
Global Const $WTS_REMOTE_CONNECT = 3
Global Const $WTS_REMOTE_DISCONNECT = 4
Global Const $WTS_SESSION_LOGON = 5
Global Const $WTS_SESSION_LOGOFF = 6
Global Const $WTS_SESSION_LOCK = 7
Global Const $WTS_SESSION_UNLOCK = 8
Global Const $WTS_SESSION_REMOTE_CONTROL = 9
;Global Const $FORMAT_MESSAGE_FROM_SYSTEM = 0x1000 ; commented out as it is already included in <GUIConstantsEx.au3> or <WindowsConstants.au3>
Global Const $LANG_NEUTRAL = 0x0
;Global Const $GWL_WNDPROC = (-4) ; commented out as it is already included in <GUIConstantsEx.au3> or <WindowsConstants.au3>
;Global Const $WM_DESTROY = 0x2 ; commented out as it is already included in <GUIConstantsEx.au3> or <WindowsConstants.au3>

GUIRegisterMsg($WM_WTSSESSION_CHANGE, "MY_WM_WTSSESSION_CHANGE")

; an easy way to quit the script
HotKeySet('{F3}', '_Quit')
Func _Quit()
    Exit
EndFunc   ;==>_Quit

; main part - do nothing except respond to WM
While True
WEnd

Func MY_WM_WTSSESSION_CHANGE($hWnd, $uMsg, $wParam, $lParam)
    Switch $uMsg
        Case $WM_WTSSESSION_CHANGE
            FileWriteLine(@ScriptDir & '\MY_WM_WTSSESSION_CHANGE.txt', _Now() & ' = ' & $wParam)
            #cs ; commented out as it is replaced by logging to a file
                Switch $wParam
                Case $WTS_CONSOLE_CONNECT
                MsgBox(0, "WM_WTSESSION_CHANGE", "A session was connected to the console session at " & _Now())

                Case $WTS_CONSOLE_DISCONNECT
                MsgBox(0, "WM_WTSESSION_CHANGE", "A session was disconnected from the console session at " & _Now())

                Case $WTS_REMOTE_CONNECT
                MsgBox(0, "WM_WTSESSION_CHANGE", "A session was connected to the remote session at " & _Now())

                Case $WTS_REMOTE_DISCONNECT
                MsgBox(0, "WM_WTSESSION_CHANGE", "A session was disconnected from the remote session at " & _Now())

                Case $WTS_SESSION_LOGON
                MsgBox(0, "WM_WTSESSION_CHANGE", "A user has logged on to the session at " & _Now())

                Case $WTS_SESSION_LOGOFF
                MsgBox(0, "WM_WTSESSION_CHANGE", "A user has logged off the session at " & _Now())

                Case $WTS_SESSION_LOCK
                MsgBox(0, "WM_WTSESSION_CHANGE", "A session has been locked at " & _Now())

                Case $WTS_SESSION_UNLOCK
                MsgBox(0, "WM_WTSESSION_CHANGE", "A session has been unlocked at " & _Now())

                Case $WTS_SESSION_REMOTE_CONTROL
                MsgBox(0, "WM_WTSESSION_CHANGE", "A session has changed its remote controlled status. To determine the status, call GetSystemMetrics and check the SM_REMOTECONTROL metric at " & _Now())

                EndSwitch
            #ce
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>MY_WM_WTSSESSION_CHANGE

the last post of that thread is a function that can detect if the session is locked or not locked:

Global Const $DESKTOP_SWITCHDESKTOP = 0x100

HotKeySet('{F3}', '_Quit')
Func _Quit()
    Exit
EndFunc   ;==>_Quit

While True
    FileWriteLine(@ScriptDir & '\IsLocked.txt', @HOUR & ':' & @MIN & ':' & @SEC & ' = ' & _CheckLocked())
    Sleep(5000)
WEnd

Func _CheckLocked()
    $hLockedDLL = DllOpen("user32.dll")
    $hDesktop = DllCall($hLockedDLL, "int", "OpenDesktop", "str", "Default", "int", 0, "int", 0, "int", $DESKTOP_SWITCHDESKTOP)
    $ret = DllCall($hLockedDLL, "int", "SwitchDesktop", "int", $hDesktop[0])
    DllCall($hLockedDLL, "int", "CloseDesktop", "int", $hDesktop[0])

    If $ret[0] = 0 Then
        $iLocked = 1
    ElseIf $ret[0] = 1 Then
        $iLocked = 0
    EndIf

    DllClose($hLockedDLL)

    If $iLocked Then
        Return 1
    Else
        Return 0
    EndIf
EndFunc   ;==>_CheckLocked

this works, but it does not detect user switch, and does not respond to event (so i have to keep calling it at regular intervals). it uses the user32.dll SwitchDesktop function, and i can't figure out if it can detect user switch.

there is also this: http://support.microsoft.com/kb/310153

but i'm coding in AutoIt and i'm having a hard time following their C++ code, although it very much resembles the first script i posted here.

any help is appreciated.

Share this post


Link to post
Share on other sites



thanks KaFu, but that code only works for lock/unlock, not user switch. on user switch, the event is detected, but the function returns 0 instead of 1  :(

Share this post


Link to post
Share on other sites

Share this post


Link to post
Share on other sites

So, what if you wrap the event detection in a separate function and add a check for @UserName against a buffer?

 

unfortunately, Google Translate list of languages does not have "Computerish"...  ;)

what is this "buffer" you refer to?

Share this post


Link to post
Share on other sites

Global $s_Buffer_Username = @UserName
 
...
 
Func _WinEventHook_Proc($h_Hook, $iEvent, $hWnd, $idObject, $idChild, $iEventThread, $iEventTime)
ConsoleWrite(TimerInit() & @TAB & Hex($iEvent, 4) & @CRLF)
If HasUsernameChanged() Then
ConsoleWrite("Username changed..." & @CRLF)
ElseIf IsDesktopLocked() Then
ConsoleWrite("Desktop locked..." & @CRLF)
Else
ConsoleWrite("Desktop unlocked..." & @CRLF)
EndIf
EndFunc   ;==>_WinEventHook_Proc
 
Func HasUsernameChanged()
If $s_Buffer_Username <> @UserName Then
$s_Buffer_Username = @UserName
Return 1; username changed
EndIf
Return 0
EndFunc   ;==>HasUsernameChanged

Share this post


Link to post
Share on other sites

ah, i see what you mean. but it doesn't work. and as afterthought, i think it can't work by design, because the script is running in a user session, and within the user session, @UserName never changes...

Share this post


Link to post
Share on other sites

Ah, you might be right. What's the output of GetLastError directly after the SwitchDesktop call? It might be possible to derive information from that.

 

$iRet = DllCall("User32.dll", "int", "SwitchDesktop", "int", $hDesktop[0])
ConsoleWrite(_WinAPI_GetLastError() & @tab & _WinAPI_GetLastErrorMessage() & @crlf)

Share this post


Link to post
Share on other sites

it's always error "127 The specified procedure could not be found." even for successful detection of lock/unlock.

this is the console output for lock & unlock events:

83067104214 0020
127 The specified procedure could not be found.
Desktop locked...
 
83073664091 0020
127 The specified procedure could not be found.
Desktop unlocked...
 
this is the console output for user switch:
 
83495907105 0020
127 The specified procedure could not be found.
Desktop unlocked...
 
83571037544 0020
127 The specified procedure could not be found.
Desktop unlocked...
 
it does not matter if i actually logon as another user; the events are fired when i leave the session to enter the logon screen, and when i return to the session. both events (leave & return) are detected as "Desktop unlocked..."
 
actually, it may be half way to solution: lock/unlock and leave/return are always in pairs. if i know the initial status, i can just toggle a boolean variable upon every such event, so reliably tell if the user session is active or not.
 
but, if i don't know the initial status (for example if my script starts when session is locked), or if these events somehow divert from the pairing (lock while switched), i'm in trouble... so better be able to properly detect the event.
 

Share this post


Link to post
Share on other sites

I'm still using 3.3.8.1 and WinApiEx, so these functions are based on that assumption. This function checks whether the process is attached to a WindowStation receiving user input... should (might?) return 0 after the user switch.

#include <WinAPIEx.au3>
 
Func _ProcessDesktop_Receives_UserInput()
Local Const $UOI_IO = 6
Local $aDesktops = _WinAPI_EnumDesktops(_WinAPI_GetProcessWindowStation())
Local $i_Input, $hDesktop
For $i = 1 To $aDesktops[0]
$hDesktop = _WinAPI_OpenDesktop($aDesktops[$i])
$i_Input += _WinAPI_GetUserObjectInformation($hDesktop, $UOI_IO) ; $UOI_IO = 1 if the object is a handle to the desktop that is receiving input from the user, 0 otherwise.
_WinAPI_CloseDesktop($hDesktop)
Next
Return $i_Input
EndFunc   ;==>_ProcessDesktop_Receives_UserInput
 
 
MsgBox(0, "", _ProcessDesktop_Receives_UserInput())

Share this post


Link to post
Share on other sites

#11 ·  Posted (edited)

you are correct, this function does return 0 for "lock" & "leave" events, and 1 for "unlock" & "return" events.

this is kind of making the query for _IsDesktopLocked() redundant, because although it can differentiate between "lock" and "leave" events, it can not differentiate between the "unlock" and "return" events.

 
...
 
but after some further checks, it seems that if i skip the _IsDesktopLocked() test, the function responds also to events for other users. i'm not quite sure how this works, i'm testing it...
 
EDIT: for example, if i go lock, then from the locked screen i click switch user", then log back on as the same user, i get 3 messages of 0 returned by _ProcessDesktop_Receives_UserInput(), and then 1 message of 1 returned by _ProcessDesktop_Receives_UserInput()
 
this is fine, cause i don't really care for successive messages of lock/leave. other than that, it works well. this is the code:
#include <WinAPISys.au3>

HotKeySet("{ESC}", "_Exit")
Func _Exit()
    Exit
EndFunc   ;==>_Exit

#region ; WinEventHook
; Event Constants
; <a href='http://msdn.microsoft.com/en-us/library/ms697187.aspx' class='bbc_url' title='External link' rel='nofollow external'>http://msdn.microsoft.com/en-us/library/ms697187.aspx</a>
; <a href='http://msdn.microsoft.com/en-us/library/dd318066(VS.85' class='bbc_url' title='External link' rel='nofollow external'>http://msdn.microsoft.com/en-us/library/dd318066(VS.85</a>).aspx
; <a href='http://mwinapi.sourceforge.net/doc/html/T_ManagedWinapi_Accessibility_AccessibleEventType.htm' class='bbc_url' title='External link' rel='nofollow external'>http://mwinapi.sourceforge.net/doc/html/T_ManagedWinapi_Accessibility_AccessibleEventType.htm</a>
; <a href='http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/accessibility/AccConst.html' class='bbc_url' title='External link' rel='nofollow external'>http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/accessibility/AccConst.html</a>
$h_DLL_User32 = DllOpen("User32.dll")
$h_WinEventHook_Proc = DllCallbackRegister("_WinEventHook_Proc", "none", "hwnd;int;hwnd;long;long;int;int")
If @error Then
    MsgBox(16 + 262144, "Error", "DllCallbackRegister(_WinEventHook_Proc) did not succeed. FFH will exit now.")
    Exit
EndIf
$h_Hook = _WinEventHook_Set(0x0020, 0x0020, $h_DLL_User32)
If @error Then
    MsgBox(16 + 262144, "Error", "_WinEventHook_Set() did not succeed. FFH will exit now.")
    Exit
EndIf
OnAutoItExitRegister("_WinEventHook_UnSet")
#endregion ; WinEventHook

#Region ; main - doing nothing in this example
while True
    sleep(10)
wend
#EndRegion

#region ; WinEventHook Functions
Func _WinEventHook_Proc($h_Hook, $iEvent, $hWnd, $idObject, $idChild, $iEventThread, $iEventTime)
    If _ProcessDesktop_Receives_UserInput() Then
        ConsoleWrite("Desktop unlock/return"&@CRLF)
    Else
        ConsoleWrite("Desktop lock/leave"&@CRLF)
    EndIf
EndFunc   ;==>_WinEventHook_Proc

Func _WinEventHook_Set($iEventMin, $iEventMax, $hDLLUser32)
    Local $aRet
    Local Const $WINEVENT_OUTOFCONTEXT = 0x0
    Local Const $WINEVENT_SKIPOWNPROCESS = 0x2
    If Not $hDLLUser32 Or $hDLLUser32 = -1 Then $hDLLUser32 = "User32.dll"
    $aRet = DllCall($hDLLUser32, "hwnd", "SetWinEventHook", _
            "uint", $iEventMin, _
            "uint", $iEventMax, _
            "hwnd", 0, _
            "ptr", DllCallbackGetPtr($h_WinEventHook_Proc), _
            "int", 0, _
            "int", 0, _
            "uint", $WINEVENT_OUTOFCONTEXT) ; BitOR($WINEVENT_OUTOFCONTEXT, $WINEVENT_SKIPOWNPROCESS)
    If @error Then Return SetError(@error, 0, 0)
    Return $aRet[0]
EndFunc   ;==>_WinEventHook_Set

Func _WinEventHook_UnSet()
    If $h_WinEventHook_Proc Then
        DllCallbackFree($h_WinEventHook_Proc)
    EndIf
    If $h_Hook Then DllCall($h_DLL_User32, "int", "UnhookWinEvent", "hwnd", $h_Hook)
    If $h_DLL_User32 Then DllClose($h_DLL_User32)
EndFunc   ;==>_WinEventHook_UnSet
#endregion ; WinEventHook Functions

Func _ProcessDesktop_Receives_UserInput()
    Local Const $UOI_IO = 6
    Local $aDesktops = _WinAPI_EnumDesktops(_WinAPI_GetProcessWindowStation())
    Local $i_Input, $hDesktop
    For $i = 1 To $aDesktops[0]
        $hDesktop = _WinAPI_OpenDesktop($aDesktops[$i])
        $i_Input += _WinAPI_GetUserObjectInformation($hDesktop, $UOI_IO) ; $UOI_IO = 1 if the object is a handle to the desktop that is receiving input from the user, 0 otherwise.
        _WinAPI_CloseDesktop($hDesktop)
    Next
    Return $i_Input
EndFunc   ;==>_ProcessDesktop_Receives_UserInput

; this function seems redundant
Func _IsDesktopLocked()
    Local $hDesktop
    Local $iRet
    Local $iRC
    Local $sMsg
    Local Const $DESKTOP_SWITCHDESKTOP = 0x100
    $hDesktop = DllCall("User32.dll", "int", "OpenDesktop", "str", "Default", "int", 0, "int", 0, "int", $DESKTOP_SWITCHDESKTOP)
    $iRet = DllCall("User32.dll", "int", "SwitchDesktop", "int", $hDesktop[0])
    If IsArray($iRet) Then
        If $iRet[0] = 0 Then

            $iRC = 1
        ElseIf $iRet[0] = 1 Then

            $iRC = 0
        EndIf
    Else

    EndIf
    DllCall("User32.dll", "int", "CloseDesktop", "int", $hDesktop[0]);<-- handle returned by "OpenDesktop"
    ;$iRet = DllCall("User32.dll", "int", "CloseDesktop", "int", $iRet[0])
    Return ($iRC)
EndFunc   ;==>IsDesktopLocked

EDIT: i'm conducting some more tests, but as it stands, i think the issue is solved - other than detecting explicitly the "leave/return" events, but the detection is implicit, so that's just as fine.

thanks, KaFu!

Edited by orbs

Share this post


Link to post
Share on other sites

OK, so i can detect lock/leave and unlock/return events during script operation.

the reason is that my script should behave slightly different on active and inactive sessions.

but the script needs to decide it's initial behavior, so i need to detect at script start if the session is active or not. this is a one-time check - it is not event-driven! how would i go about doing that?

Share this post


Link to post
Share on other sites

ok, if i couldn't figure that one out by myself, that's my cue to retire for today... ;)

thank KaFu it works!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0