Archives

Archives / 2006 / June
  • [.NET 2.0] Detecting Idle Time with Mouse and Keyboard Hooks

    I thought I could share some piece of code I wrote a couple of years ago to detect if a user is idle or not. I used it on a chat client I developed because we were not allowed to use Messenger at the site. So the way to do it is to set up a coupld of low-level Windows hooks to detect mouse and keyboard activity. Obviously this involves a couple of DLL Import statements and you have to be careful to UnHook everything when the application exits as we're dealing with unmanaged code here. That's why I implemented the IDispose pattern for it.

    This code is actually a piece of cake to implement if you use the ClientIdleHandler class (code below). First a look at the sample Windows Form which is made up of a lable indicating if the user is idle or active, and a timer set to 1 second. Each time the timer fires, it checks the bActive value in the ClientIdleHandler to see if the user has been active since the last check. I also added a progress bar on it (0 to 10) to give a visual indication on the idle seconds count.

    Note: This code has not been tested in production systems and if you decide to use it, do that at your own risk. This code has been running pretty fine for the last 2 years, but on .NET 1.1. I don't know how well it behaves on .NET 2.0 even though it seems to work well. Also note that this code is not optimized in anyway and you may want to implement the static bActive variable different, perhaps raise events from the ClientIdleHandler class instead of using a timer like I do. Anyway, you have been warned :)

    Form1.cs

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;

    namespace MouseAndKbdHookSample
    {
        
    public partial class Form1 : Form
        {
            
    private ClientIdleHandler clientIdleHandler = null;
            
    private int iIdleCount = 0; //keeps track of how many seconds the user has been idle
            private const int IDLE_THRESHOLD = 10; //how many seconds should pass before the app goes into idle mode

            public Form1()
            {
                InitializeComponent();

                
    //start client idle hook
                clientIdleHandler = new ClientIdleHandler();
                clientIdleHandler.Start();
            }

            
    private void timer1_Tick(object sender, EventArgs e)
            {
                
    if (ClientIdleHandler.bActive)    //indicates user is active
                {
                    
    //zero the idle counters
                    ClientIdleHandler.bActive = false;
                    iIdleCount = 0;

                    
    //change user status
                    if (IdleLabel.Text == "IDLE")
                        IdleLabel.Text =
    "ACTIVE";

                }
                
    else    //user was idle the last second
                {
                    
    //check if threshold was reached
                    if (iIdleCount >= IDLE_THRESHOLD)
                    {
                        
    //change to IDLE and stop counting seconds
                        if (IdleLabel.Text == "ACTIVE")
                            IdleLabel.Text =
    "IDLE";
                    }
                    
    else //increase secs counter
                        iIdleCount++;
                }

                
    //some visual goo added
                progressBar1.Value = iIdleCount;
            }
        }
    }

    I added some code into the Dispose() method of the form, to help close the hooks, like this:

              //Dispose of our client idle handler
              if (clientIdleHandler != null)
                  clientIdleHandler.Close();

    Then there is this ClientIdleHandler class which does it all. I added enough comments in there I hope to explain what it does. If you want to read more on these hooks and how to use them from .NET there is always Google out there and a few KB articles on MSDN which helped me out. Like this one for example. Some people may recognize parts of the code in this class from a bunch of code sample that can be found on the Internet, still this kind of functionality is still asked for by people in forums.

    ClientIdleHandler.cs

    using System;
    using System.Runtime.InteropServices;

    namespace MouseAndKbdHookSample
    {
        
    /// <summary>
        /// Write something here sometime...
        /// </summary>
        public class ClientIdleHandler : IDisposable
        {
            
    //idle counter
            public static bool bActive = false;
            
    // hook active or not
            static int hHookKbd = 0;
            
    static int hHookMouse = 0;

            
    // the Hook delegate
            public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
            
    //Declare MouseHookProcedure as HookProc type.
            public event HookProc MouseHookProcedure;
            
    //Declare KbdHookProcedure as HookProc type.
            public event HookProc KbdHookProcedure;

            
    //Import for SetWindowsHookEx function.
            //Use this function to install thread-specific hook.
            [DllImport("user32.dll", CharSet = CharSet.Auto,
                 CallingConvention =
    CallingConvention.StdCall)]
            
    public static extern int SetWindowsHookEx(int idHook, HookProc lpfn,
                
    IntPtr hInstance, int threadId);

            
    //Import for UnhookWindowsHookEx.
            //Call this function to uninstall the hook.
            [DllImport("user32.dll", CharSet = CharSet.Auto,
                 CallingConvention =
    CallingConvention.StdCall)]
            
    public static extern bool UnhookWindowsHookEx(int idHook);

            
    //Import for CallNextHookEx
            //Use this function to pass the hook information to next hook procedure in chain.
            [DllImport("user32.dll", CharSet = CharSet.Auto,
                 CallingConvention =
    CallingConvention.StdCall)]
            
    public static extern int CallNextHookEx(int idHook, int nCode,
                
    IntPtr wParam, IntPtr lParam);

            //Added all the hook types here just for reference
            
    public enum HookType : int
            {
                WH_JOURNALRECORD = 0,
                WH_JOURNALPLAYBACK = 1,
                WH_KEYBOARD = 2,
                WH_GETMESSAGE = 3,
                WH_CALLWNDPROC = 4,
                WH_CBT = 5,
                WH_SYSMSGFILTER = 6,
                WH_MOUSE = 7,
                WH_HARDWARE = 8,
                WH_DEBUG = 9,
                WH_SHELL = 10,
                WH_FOREGROUNDIDLE = 11,
                WH_CALLWNDPROCRET = 12,
                WH_KEYBOARD_LL = 13,
                WH_MOUSE_LL = 14
            }

            
    static public int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
            {
                
    //user is active, at least with the mouse
                bActive = true;
                
                
    //just return the next hook
                return CallNextHookEx(hHookMouse, nCode, wParam, lParam);
            }


            
    static public int KbdHookProc(int nCode, IntPtr wParam, IntPtr lParam)
            {
                
    //user is active, at least with the mouse
                bActive = true;

                
    //just return the next hook
                return CallNextHookEx(hHookKbd, nCode, wParam, lParam);
            }

            
    public void Start()
            {
                
    if (hHookMouse == 0)
                {
                    
    // Create an instance of HookProc.
                    MouseHookProcedure = new HookProc(MouseHookProc);
                    
    // Create an instance of HookProc.
                    KbdHookProcedure = new HookProc(KbdHookProc);

                    
    //register a global hook
                    hHookMouse = SetWindowsHookEx((int)HookType.WH_MOUSE_LL,
                        MouseHookProcedure,
                        (System.
    IntPtr)Marshal.GetHINSTANCE(
                        System.Reflection.
    Assembly.GetExecutingAssembly().GetModules()[0]),
                        0);
                    
    //If SetWindowsHookEx fails.
                    if (hHookMouse == 0)
                    {
                        Close();
                        
    throw new ApplicationException("SetWindowsHookEx() failed");
                    }
                }

                
    if (hHookKbd == 0)
                {
                    
    //register a global hook
                    hHookKbd = SetWindowsHookEx((int)HookType.WH_KEYBOARD_LL,
                        KbdHookProcedure,
                        (System.
    IntPtr)Marshal.GetHINSTANCE(
                        System.Reflection.
    Assembly.GetExecutingAssembly().GetModules()[0]),
                        0);
                    
    //If SetWindowsHookEx fails.
                    if (hHookKbd == 0)
                    {
                        Close();
                        
    throw new ApplicationException("SetWindowsHookEx() failed");
                    }
                }
            }

            
    public void Close()
            {
                
    if (hHookMouse != 0)
                {
                    
    bool ret = UnhookWindowsHookEx(hHookMouse);
                    
    //If UnhookWindowsHookEx fails.
                    if (ret == false)
                    {
                        
    throw new ApplicationException("UnhookWindowsHookEx() failed");
                    }
                    hHookMouse = 0;
                }

                
    if (hHookKbd != 0)
                {
                    
    bool ret = UnhookWindowsHookEx(hHookKbd);
                    
    //If UnhookWindowsHookEx fails.
                    if (ret == false)
                    {
                        
    throw new ApplicationException("UnhookWindowsHookEx() failed");
                    }
                    hHookKbd = 0;
                }
            }

            
    public ClientIdleHandler()
            {
                
    //
                // TODO: Add constructor logic here
                //
            }
            #region IDisposable Members

            
    public void Dispose()
            {
                
    if (hHookMouse != 0 || hHookKbd != 0)
                    Close();
            }

            #endregion
        }
    }

    That's it folks! I hope it helps someone.

  • [Refactoring] Tools for Refactoring VB.NET

    I've not been coding any VB.NET for a while now, but when looking into a pretty good sample of how to develop an Outlook style smart client (which is in VB.NET), the walkthrough recommended to use Refactor! for Visual Basic 2005. It's a free plugin from Developer Express inc, in partnership with Microsoft and it supports a bunch of the most common refactorings you need.

    The last 2 years I've used c# all of the time for my .NET projects, but VB.NET is pretty neat actually. I don't mind using VB for coding up presentation layer stuff, and keep using C# for the rest. VB can be somewhat faster to use, as you don't need to think of cases, semi colons and stuff, and you got these new namespaces which gives you fast track access to common functionality which is almost like cheating ;)

    If the link to the sample app is not working for you, try searching on MSDN for "RSS Reader Outlook look and feel" and you should find it. Also available on the VB Developer Center.

  • WinFX Renamed to .NET Framework 3.0

    Picked this up from Soma's blog:

    ...we have decided to rename WinFX to the .NET Framework 3.0.  .NET Framework 3.0 aptly identifies the technology for exactly what it is – the next version of our developer framework.

    What a pack of good stuff developers will get in that release indeed. The CLR will still be version 2.0 though. Jazon Sander posted some details around questions that followed directly after that announcement, like these:

    1.  What version of the compilers are being used?  .NET FX 3.0 is built on .NET FX 2.0 including the CLR and BCL.  This means you will be using the 2.0 C# and VB compilers from the redist when using .NET FX 3.0. 

    2.  Will .NET FX 3.0 contain LINQ support?  No.  LINQ support is in the Orcas product which is shipping after .NET FX 3.0 (which ships in Vista).

    I think I'll have to figure out a new tag taxonomy for my blog posts :)

  • [Vista] Getting Daemon Tools to Work in Vista Beta 2

    Try this at own risk (see below)

    If you like me use Daemon tools alot and want to get it to work in Vista Beta 2 - Try downloading an older version 3.02 from the D-tools archives. It worked for me.

    You may have to disable UAP to install the drivers though, which is done via the System Configuration tools (the Tools tab).

    One strange thing which happened just now though is that Vista tried to re-active again it seems, and it failed. I looked at "Welcome Center" and yep - my Vista is no longer activated and when I try to activate it fails with error code 0x8007000D Not sure if it was disabling of UAP or installing D-tools that made this thing happen...

  • [Vista] ATI Drivers for Vista Beta 2

    Found this news article (and downloading as I write):

    ATI today released a major update to its first beta generation Windows Vista graphics driver. The Catalyst Beta package fully supports the Vista Display Driver Model (VDDM) and should deliver greater stability than the previous version and allow a better look at Microsoft's AeroGlass user interface.

    The driver published is available in 32-bit and 64-bit versions and supports DirectX9-compatible graphics processors, including the desktop Radeon processors 9500 and higher, the Mobility Radeon 9550 and higher series, the chipsets Crossfire 3200 and Xpress 200 and 200m as well as the workstation cards FireGL V3100 and higher and FireMV 2200 PCIE and higher.

    EDIT: For some reason that I don't know, I cannot get any higher resolution than 1280 x 1024 on my 19' flat screen, which is no improvement over the drivers which were installed from start.

  • [Vista] Some Things Work - Others Don't

    I decided to be brave and installed Vista Beta 2 on my home computer. Because you have to format the C-drive, I made a huge, fresh backup of most of my files and went ahead with the install. Vista installed fine and started up OK with a few issues:

    My ATI Radeon X850 XT gets a maximum of 1280 x 1024 - I will look for new drivers and see if that helps.

    My Sound Blaster Audigy 2 zs refuse works...almost. I can use the front panel in- and output, but the output from the card on the back of the computer doesn't work at all. I'm currently running my sound via earphones from the front output... I have to investigate this further.

    None of the Daemon Tools versions I've tried so far seems to work properly, which is a disaster because I got most software and backups saved as Images. IsoBuster got me going, but it's not as good as Daemon Tools (which I just luuuuuv).

    I had some problems getting DNS lookups to work for some reasons. I could get to almost all webbsites except a few really important ones (like ms.com, google etc), which were pretty weird. I'm connected to the Internet via a router/firewall and have automatic IP configuration set up. I thought maybe it had something to do with IP6 or something so I just added my ISP DNS servers straight into the network settings and I've had no problems since then.

    When the computer goes into Sleep mode and I want to start from it, it seems to hang in startup and I have to power off  fully and press the power button again. When it starts up, it revovers from the hibernate mode, which seems to work well. Not sure why it cannot get going from Sleep mode but I'll have to investigate.

    Other than that - performance is great, file copying seems to be faster than in XP (!), but maybe that was because I used a really crappy AV software. Someone blogged about some AV which worked with Vista... Ah here it is, blogged by Kristan Kenney on DigitalFile.

    (All of a sudden the Media Player 11 stopped playing in a middle of a song... maybe it doesn't like David Bowie :o) I like the way Media Player 11 minimize to the system tray... I think. It takes up quite some space down there actually...

    What else is there to say... it will take some getting used to the new File Explorer though, but I think it will work out pretty well. The Windows Sidebar is something I will explore some more - I'm thinking of developing a couple of (hopefully) useful Gadgets just for fun if nothing else. The CPU/Mem meter is just beautiful, and the RSS feed viewer is pretty cool.

    Oh, and all these security allowance elevation dialog thingys which keeps poping up all the time you want to start/remove/change stuff will have to go. If you've decided to be local admin on your box you don't want to keep saying yes, Yes, YES, YAHHH DARNIT! all the time. Once should be enough :)