Fast User Switching and Session Changes in WPF

While I haven’t been working on C#/WPF in awhile, I figured I’d go ahead and complete a draft that’s been sitting in my queue for awhile.

Let's switch. Your pink background makes my eyes bleed.

Let's switch. Your pink background makes my eyes bleed.

Fast user switching is a concept Microsoft first included with Windows XP that allows the OS to quickly switch to another user and back by keeping the first user’s applications running while the second uses the computer.

This sounds like a great idea, and for the most part, it is; however, it places a new responsibility on application developers, because the context in which an application is running could change at any time.

That is, user1 could be using app.exe, switch to user2, and user2 could use app.exe. Then you have two users using the same executable at the same time, and user1 expects that app.exe will retain his place in the app for when he returns.

Now, before you start shouting, “Anarchy!”, note that there are a variety of things you can do to address this responsibility.

You can just tell user2 that he can’t use the application until user1 closes it. This is a poor approach, but you can feel free to annoy your users with how lazy you are if you like.

Alternatively, you can set your application to begin listening for window events that signal a change in the computer’s session.

Just as we have before, we’ll be using our friend, WndProc, to capture messages that WPF doesn’t fire for us. The one we’re looking for this time is WM_WTSSESSION_CHANGE, which will notify the application that the Windows session has changed. In order to receive notifications for this event, we’ll have to register using the function WTSRegisterSessionNotification.

Let’s kick things off with some imports for the Win32 APIs we’ll be using and the constants associated with them (see the MSDN links in the previous paragraph).

    [DllImport("WtsApi32.dll")]
    private static extern bool WTSRegisterSessionNotification(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)]int dwFlags);
    [DllImport("WtsApi32.dll")]
    private static extern bool WTSUnRegisterSessionNotification(IntPtr hWnd);
    [DllImport("kernel32.dll")]
    public static extern int WTSGetActiveConsoleSessionId();
 
    // dwFlags options for WTSRegisterSessionNotification
    const int NOTIFY_FOR_THIS_SESSION = 0;     // Only session notifications involving the session attached to by the window identified by the hWnd parameter value are to be received.
    const int NOTIFY_FOR_ALL_SESSIONS = 1;     // All session notifications are to be received.
 
    // session change message ID
    const int WM_WTSSESSION_CHANGE = 0x2b1;
 
    public enum WTSMessage
    {
        // WParam values that can be received:
        WTS_CONSOLE_CONNECT = 0x1, // A session was connected to the console terminal.
        WTS_CONSOLE_DISCONNECT = 0x2, // A session was disconnected from the console terminal.
        WTS_REMOTE_CONNECT = 0x3, // A session was connected to the remote terminal.
        WTS_REMOTE_DISCONNECT = 0x4, // A session was disconnected from the remote terminal.
        WTS_SESSION_LOGON = 0x5, // A user has logged on to the session.
        WTS_SESSION_LOGOFF = 0x6, // A user has logged off the session.
        WTS_SESSION_LOCK = 0x7, // A session has been locked.
        WTS_SESSION_UNLOCK = 0x8, // A session has been unlocked.
        WTS_SESSION_REMOTE_CONTROL = 0x9 // A session has changed its remote controlled status.
    }

The first thing we’ll need to do is register for notifications. You’ll probably want this near the start of your application, in case your user is fast and/or part of your QA. After registration is successful, we want to capture the initial session ID to use for comparisons later on.

    private int initialSessionId;
        if (!WTSRegisterSessionNotification((new WindowInteropHelper(this)).Handle, NOTIFY_FOR_THIS_SESSION))
        {
            // throw exception - registration has failed!
        }
        initialSessionId = Process.GetCurrentProcess().SessionId;

When our WndProc is executed, we can check the message to see if it corresponds to the WM_WTSSESSION_CHANGE we’ve defined (confused? should’ve clicked that link earlier…).

We once again get the active session ID for the event to allow us to compare against our initial value. After all, we may not want to do anything if the session ID hasn’t changed.

It’s also useful to check the wParamValue in order to understand the type of session change that has occurred. If a user logs out, we can perform auto-save functions or some clean up to get rid of unneeded resources as a preparation for the new user to log in. Alternatively, we can expand the use of this feature and show low-res images to make repaints faster if it’s a remote connection.

    protected override void WndProc(ref Message m)
    {
        int evtSessionID = WTSGetActiveConsoleSessionId();
 
        switch (m.Msg)
        {
            case WM_WTSSESSION_CHANGE:
                {
                    WTSMessage wParamValue = (WTSMessage)m.WParam.ToInt32();
                    Console.WriteLine("Session message " + wParamValue + " Active Session ID:" + evtSessionID + " Current Process Session ID:" + initialSessionId);
                    // do something useful
                }
                break;
        }
        base.WndProc(ref m);
    }

Lastly, let’s be a good neighbor and unregister our subscription for notifications using WTSUnRegisterSessionNotification. Be sure to do this before your window handle is destroyed, such as in your Window_Closing event.

    WTSUnRegisterSessionNotification((new WindowInteropHelper(this)).Handle);

All done! Now, let me know how you use it.

Attaching to WndProc in WPF

WPF, like any other UI program, has an inner loop that continually runs in order to update the state of the application and render the UI.  One part of this loop is a call to the function WndProc, which is the function through which Windows communicates the messages your window is receiving (be it input or system notifications).

WPF hides this function from you (presumably to make things easier) and instead just fires events off for anything [it thinks] you’ll ever need.  Sometimes, however, it is useful to attach to this loop in order to address messages that don’t have a related WPF event, such as messages sent by other applications.

Here’s how you do it.

In your window’s SourceInitialized event, create an HwndSource object from your window’s handle. Use the AddHook method to attach an event handler to all of your window’s events using the supplied function.

private void Window_SourceInitialized(object sender, EventArgs e)
{
    IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
    HwndSource src = HwndSource.FromHwnd(windowHandle);
    src.AddHook(new HwndSourceHook(WndProc));
}

As its always good practice to define the methods you reference, be sure to define WndProc and, hopefully, do something useful with it. Its parameters describe the message by giving you its ID, as well as the parameters sent along with it. For some more on windows messages, check out my earlier post regarding emulating those messages. If your desired message has been captured and handled, be sure to set handled to true.

private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    // address the messages you are receiving using msg, wParam, lParam
    if (msg == WM_LOOK_FOR_DROIDS)
    {
        if (wParam == DROIDS_IM_LOOKING_FOR)
        {
            CaptureDroids(lParam);
            handled = true;
        }
        else
        {
            AskToMoveAlong(lParam);
        }
    }
    return IntPtr.Zero;
}

Lastly, be sure to remove the hook when your window is closing.

private void Window_Closing(object sender, EventArgs e)
{
    IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
    HwndSource src = HwndSource.FromHwnd(windowHandle);
    src.RemoveHook(new HwndSourceHook(this.WndProc));
}

Easy as pie. If you’re feeling nostalgic and don’t want to use events, you can implement your entire application in the WndProc method (other than the rendering, which WPF also hides from you). I wouldn’t recommend it, though…