Reproducing Automatic Presence with the UCC API – Part 2

My last post detailed how to monitor user activity and update the user’s state when they become inactive.  With that code, we can switch between Available, Inactive, and Away states based on user input via the keyboard or mouse.  The next step is to notify OCS when the user locks their machine, by changing the user’s availability to Away.  To do this, we’re going to need to use P/Invoke again, this time calling the WTSRegisterSessionNotification function, part of WtsApi32.dll.  This will only work on a Windows XP or newer OS.  This function takes a handle to a window we own, which is then notified of session changes by the WM_WTSSESSION_CHANGE message.  To receive that notification we need a class that can hook into the windows message loop, something derived from System.Windows.Forms.Control, which I added as a private nested class in the PresenceManager:

        /// <summary>

        /// Registers for session changes for this session by calling

        /// WTSRegisterSessionNotification.  Provides events that

        /// notify when the machine has been locked/unlocked.

        /// </summary>

        private sealed class SessionChangeHandler : Control

        {

            [DllImport(“WtsApi32.dll”, SetLastError = true)]

            [return: MarshalAs(UnmanagedType.Bool)]

            private static extern bool WTSRegisterSessionNotification(

                IntPtr hWnd, [MarshalAs(UnmanagedType.U4)]int dwFlags);

            [DllImport(“WtsApi32.dll”, SetLastError = true)]

            [return: MarshalAs(UnmanagedType.Bool)]

            private static extern bool WTSUnRegisterSessionNotification(IntPtr hWnd);

 

            private const int NOTIFY_FOR_THIS_SESSION = 0;

            private const int WM_WTSSESSION_CHANGE = 0x2b1;

            private const int WTS_SESSION_LOCK = 0x7;

            private const int WTS_SESSION_UNLOCK = 0x8;

 

            /// <summary>

            /// Raised when the machine has been locked.

            /// </summary>

            public event EventHandler MachineLocked;

            /// <summary>

            /// Raised when the machine has been unlocked.

            /// </summary>

            public event EventHandler MachineUnlocked;

 

            public SessionChangeHandler()

            {

                if (!WTSRegisterSessionNotification(this.Handle, NOTIFY_FOR_THIS_SESSION))

                {

                    Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());

                }

            }

 

            protected override void WndProc(ref Message m)

            {

                if (m.Msg == WM_WTSSESSION_CHANGE)

                {

                    int value = m.WParam.ToInt32();

                    if (value == WTS_SESSION_LOCK)

                    {

                        OnMachineLocked(EventArgs.Empty);

                    }

                    else if (value == WTS_SESSION_UNLOCK)

                    {

                        OnMachineUnlocked(EventArgs.Empty);

                    }

                }

                base.WndProc(ref m);

            }

 

            protected override void OnHandleDestroyed(EventArgs e)

            {

                // unregister this instance before it’s destroyed

                if (!WTSUnRegisterSessionNotification(this.Handle))

                {

                    Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());

                }

 

                base.OnHandleDestroyed(e);

            }

 

            private void OnMachineLocked(EventArgs e)

            {

                EventHandler temp = MachineLocked;

                if (temp != null)

                {

                    temp(this, e);

                }

            }

            private void OnMachineUnlocked(EventArgs e)

            {

                EventHandler temp = MachineUnlocked;

                if (temp != null)

                {

                    temp(this, e);

                }

            }

        }

 

Surprisingly, WTSRegisterSessionNotification was not yet documented at pinvoke.net, so I added a page for it.  The SessionChangeHandler registers itself when it is constructing, and makes a corresponding call to WTSUnRegisterSessionNotification just before the handle is destroyed.  Any time the machine is locked or unlocked, the appropiate event is fired, which the PresenceManager can easily handle:

 

        private void MachineLocked(object sender, EventArgs e)

        {

            // disable the timer and notify OCS that the user is away

            myLastInputTimer.Change(Timeout.Infinite, Timeout.Infinite);

            this.CurrentAvailability = Availability.Away;

        }

        private void MachineUnlocked(object sender, EventArgs e)

        {

            // change the machine’s state back to available and re-enable the timer

            this.CurrentAvailability = Availability.Away;

            myLastInputTimer.Change(2000, 2000);

        }

We can use the same code to publish the presence state to OCS as we did in the timer callback, which I’ll cover in part three.  If you’re not sure it worked, adding a few calls to Debug.WriteLine can help.  At this point, the PresenceManager handles availability the same way we are used to seeing from other IM clients.  It just needs to be instantiated and kept somewhere, and it will handle automatic presence until disposed.  In the last part of this series I’ll cover publishing presence state to OCS, and the issues I encountered with the UCC API.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s