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.

Reproducing Automatic Presence with the UCC API – Part 1

Most popular IM clients, including Communicator 2007, automatically modify the user’s status when they are inactive or away.  While piloting Office Communications Server we created a simple application that had the main features of Communicator (chat, A/V, presence, etc.) to show that we could incorporate that functionality into our in-house applications so that our agents only have to work with one piece of software.  The presence functionality is important to us, as we don’t want to route inbound calls from Speech Server to an agent who isn’t available.  As part of the pilot, I created a simple presence manager to monitor user activity and update availability when necessary.  After five minutes of inactivity, a state value of Inactive is published.  After 15 minutes, a state value of Away is published.  Finally, if the user locks the machine, a state value of Away is published.

Basically, there are two different things the manager needs to detect: the last time there was input (keyboard/mouse) to the machine, and when Windows is locked.  After a little digging, I found the easiest way to achieve our first goal was to P/Invoke the GetLastInputInfo function, which returns the time of the last keyboard/mouse activity.  Using a timer, we can call this method as often as necessary to check if our thresholds (five and 15 minutes) have been reached.  For the sake of simplicity, I removed the code that publishes the state to OCS.  We could also easily add logic to check if the user is on the phone, so the “In a Call” state isn’t overwritten. 

NOTE: I’m using the System.Threading.Timer class.

    internal sealed class PresenceManager : IDisposable

    {

        [DllImport(“user32.dll”)]

        private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

 

        private Timer myLastInputTimer;

 

        public PresenceManager()

        {

            // initialize the timer to call back every two seconds

            myLastInputTimer = new Timer(CheckIdleTime, null, 2000, 2000);

        }

 

        /// <summary>

        /// Gets the current availability of the signed-in user.

        /// </summary>

        public Availability CurrentAvailability { get; private set; }

 

        /// <summary>

        /// Callback method for <see cref=”myLastInputTimer”/> to check the idle time

        /// of the computer and update the status if needed.

        /// </summary>

        /// <param name=”state”>The state, unused by this method.</param>

        private void CheckIdleTime(object state)

        {

            LASTINPUTINFO lastInputInfo = LASTINPUTINFO.Create();

            if (GetLastInputInfo(ref lastInputInfo))

            {

                // get the idle time by subtracting the tick count of the last input

                // event from the total tick count of the system

                int idleTicks = Environment.TickCount – lastInputInfo.dwTime;

                TimeSpan idleTime = TimeSpan.FromSeconds(idleTicks / 1000.0);

 

                Availability currentAvailability = this.CurrentAvailability;

                if (idleTime < TimeSpan.FromMinutes(5))

                {

                    if (currentAvailability != Availability.Available)

                    {

                        // publish available state to OCS here…

 

                        this.CurrentAvailability = Availability.Available;

                    }

                }

                else if (idleTime < TimeSpan.FromMinutes(15))

                {

                    if (currentAvailability != Availability.AvailableIdle

                        && currentAvailability != Availability.BusyIdle)

                    {

                        if (currentAvailability == Availability.Busy)

                        {

                            // publish busy-idle state to OCS here…

 

                            this.CurrentAvailability = Availability.BusyIdle;

                        }

                        else

                        {

                            // publish inactive state to OCS here…

 

                            this.CurrentAvailability = Availability.AvailableIdle;

                        }

                    }

                }

                else if (currentAvailability != Availability.Away)

                {

                    // publish away state to OCS here…

 

                    this.CurrentAvailability = Availability.Away;

                }

            }

        }

    }

 

Now we just need to define the LASTINPUTINFO structure that we pass in GetLastInputInfo.  I nested it inside the manager:

 

        /// <summary>

        /// Structure used by the <see cref=”GetLastInputInfo”/> method.

        /// </summary>

        [StructLayout(LayoutKind.Sequential)]

        private struct LASTINPUTINFO

        {

            private static readonly int size = Marshal.SizeOf(typeof(LASTINPUTINFO));

            /// <summary>

            /// Creates a new <see cref=”LASTINPUTINFO”/> initialized and ready to

            /// be used by the <see cref=”GetLastInputInfo”/> method.

            /// </summary>

            /// <returns></returns>

            public static LASTINPUTINFO Create()

            {

                LASTINPUTINFO lii = new LASTINPUTINFO();

                lii.cbSize = size;

                return lii;

            }

 

            /// <summary>

            /// The size of this instance, required by the

            /// <see cref=”GetLastInputInfo”/> method.

            /// </summary>

            [MarshalAs(UnmanagedType.U4)]

            public int cbSize;

            /// <summary>

            /// The system tick-count at the last recorded user input, set

            /// by the <see cref=”GetLastInputInfo”/> method.

            /// </summary>

            [MarshalAs(UnmanagedType.U4)]

            public int dwTime;

        }

 

GetLastInputInfo requires the cbSize field to be set before the method is called, so I added a Create method to return an instance ready to be uses.  The timer calls back on a thread retrieved from the thread pool, but we can publish to OCS from any thread.  I found that two seconds was a good interval that updated the state to Available in a reasonable time.  In the second post I’ll cover detecting when the machine is locked/unlocked using another P/Invoke operation.