Receiving Session Lock and Unlock Notifications


Some programs, such as MSN Messenger, change their behavior when the current session is locked and unlocked.  Messenger, for instance, will change your status to Away while your machine is locked, and then back to Online when your machine is unlocked.


In order to pull this off, you’ll need Windows to notify your application when the locked status of the current session is changing.  On Windows XP and higher, you can get this notification via a WM_WTSSESSION_CHANGE message.  You notify Windows that you want to receive that message via a call to WTSRegisterSessionNotification  (which requires that you make a matching call to WTSUnRegisterSessionNotification when you no longer need notification).  Both of these APIs are declared in the WtsApi32.h header in the platform SDK.


When the WM_WTSESSION_CHANGE message arrives, there is a status code in the wParam indicating what type of change is happening.  The two changes we care about are WTS_SESSION_LOCK and WTS_SESSION_UNLOCK to represent locking and unlocking the machine.


All of the APIs involved are relatively simple, and creating P/Invoke signatures for them is easy.  Once that work is done, creating a simple base class that is capable of receiving lock and unlock notifications is very straightforward.



using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

/// <summary>
/// Base class for a form that wants to be notified of Windows
/// session lock / unlock events
/// </summary>
public abstract class LockNotificationForm : Form
{
    // from wtsapi32.h
    private const int NotifyForThisSession = 0;

    // from winuser.h
    private const int SessionChangeMessage = 0x02B1;
    private const int SessionLockParam = 0x7;
    private const int SessionUnlockParam = 0x8;

    [DllImport(“wtsapi32.dll”)]
    private static extern bool WTSRegisterSessionNotification(IntPtr hWnd, int dwFlags);

    [DllImport(“wtsapi32.dll”)]
    private static extern bool WTSUnRegisterSessionNotification(IntPtr hWnd);
    
    // flag to indicate if we’ve registered for notifications or not
    private bool registered = false;
    
    /// <summary>
    /// Is this form receiving lock / unlock notifications
    /// </summary>
    protected bool ReceivingLockNotifications
    {
        get { return registered; }
    }
            
    /// <summary>
    /// Unregister for event notifications
    /// </summary>
    protected override void Dispose(bool disposing)
    {
        if(registered)
        {
            WTSUnRegisterSessionNotification(Handle);
            registered = false;
        }
        
        base.Dispose(disposing);
        return;
    }
    
    /// <summary>
    /// Register for event notifications
    /// </summary>
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);

        // WtsRegisterSessionNotification requires Windows XP or higher
        bool haveXp =   Environment.OSVersion.Platform == PlatformID.Win32NT &&
                            (Environment.OSVersion.Version.Major > 5 || 
                                (Environment.OSVersion.Version.Major == 5 &&
                                 Environment.OSVersion.Version.Minor >= 1));

        if(haveXp)
            registered = WTSRegisterSessionNotification(Handle, NotifyForThisSession);

        return;
    }

    /// <summary>
    /// The windows session has been locked
    /// </summary>
    protected virtual void OnSessionLock()
    {
        return;
    }

    /// <summary>
    /// The windows session has been unlocked
    /// </summary>
    protected virtual void OnSessionUnlock()
    {
        return;
    }
    
    /// <summary>
    /// Process windows messages
    /// </summary>
    protected override void WndProc(ref Message m)
    {
        // check for session change notifications
        if(m.Msg == SessionChangeMessage)
        {
            if(m.WParam.ToInt32() == SessionLockParam)
                OnSessionLock();
            else if(m.WParam.ToInt32() == SessionUnlockParam)
                OnSessionUnlock();
        }

        base.WndProc(ref m);
        return;
    }    
}


This class exposes three interesting protected members to its derived classes.  The ReceivingLockNotifications property is a flag that indicates that the form is receiving lock and unlock messages.  The most likely reasons this would be set to false is if the form is not running on Windows XP or higher, or the form is disposed.  OnSessionLock and OnSessionUnlock are methods called when the user locks and unlocks the current Windows session.


Control flow through the form is pretty basic.  In the OnHandleCreated method, we check to see if the user is running on XP or higher, and if so, call out to WTSRegisterSessionNotification, requesting notifications only for the current session.  This call needs to be done in OnHandleCreated so that there is an HWND associated with the form, since that HWND is required to register for notifications.


The WndProc is overridden so that we can check to see if we’ve gotten any WM_WTSESSION_CHANGE messages.  If we have, and their wParam is either lock or unlock, we call the appropriate virtual function.  The rest of the messages just get processed by the default WndProc implementation.


Finally, in the form’s Dispose method, we check to see if we ever registered for notifications, and if so unregister.


Using this class is very easy.  Here’s a tiny sample that just keeps track of the lock and unlock times in a text box:



using System;
using System.Windows.Forms;

public class NotifyForm : LockNotificationForm
{
    private TextBox textBox = new TextBox();
    
    public static void Main()
    {
        using(NotifyForm notifyForm = new NotifyForm())
            Application.Run(notifyForm);
    }

    public NotifyForm()
    {
        textBox.Top = ClientRectangle.Top;
        textBox.Left = ClientRectangle.Left;
        textBox.Width = ClientRectangle.Width;
        textBox.Height = ClientRectangle.Height;
        textBox.Multiline = true;

        Controls.Add(textBox);
    }
    
    protected override void OnSessionLock()
    {
        textBox.Text = textBox.Text + “Locked at ” + DateTime.Now + Environment.NewLine;
    }

    protected override void OnSessionUnlock()
    {
        textBox.Text = textBox.Text + “Unlocked at ” + DateTime.Now + Environment.NewLine;
    }
}


Running this code yields results similar to:


Lock/Unlock notification application


Comments (16)

  1. Ohad Israeli says:

    There seems to be a problem with the solution while working under terminal server… if you run multiple sessions of the application only one will catch the notification.

    Another area is the message pump…

    I had implemented a sense events listener which works great also under terminal server env. and gets notify for events so you don’t have to listen to the message pump.

    I’ll clean up my code and publish it on my blog soon.

  2. shawnfa says:

    Hi Ohad,

    I’ve tested this code running multiple instances across multiple terminal server sessions. Each instance is notified of each lock / unlock action that occurs on its own session. Using a Windows Service and SENS will require administrator privileges, which this code does not require.

    -Shawn

  3. SeanGep.NET says:

    Great stuff, I was just looking into doing something like this…

  4. Stupid says:

    Why doesnt C# BCLs ship with a Const library for the windows message constants? Why must we always have to go hunt through the .h files and reimplement the wheel?

  5. Some links found today – just a good way of making sure I don’t lose them:

    Receiving Session Lock…

  6. Great sample, Shawn!

  7. If you are writing rich-client applications for the Windows desktop there are lots of little touches…

  8. Daniel says:

    Thanks, this was just what I was looking for.

  9. Richard says:

    This article also explains how to get SENS Events for the same Logon/Logoff events:

    http://dotnet.sys-con.com/read/105651.htm

  10. Ron says:

    I was looking for somthing to modify for a program I am writing so I can tell if a user had logged back in. This class makes things much easier for me. Thanks so much for this information.

  11. Alan says:

    Just thanks, it was exactly what I was searching for. Thanks again for posting !