Docking as Windows 7 in Vista and XP using Windows Hooks

I was one of those lucky guys who got the Windows 7 virtual machine at the PDC for the first time and I already liked. When the new beta was released I decided to install it on my old laptop and I love it, I really mean it. I think it's great, there are a lot of small features that you immediately like with almost not noticing them. One of them is the cool docking feature that allows you drag a window over the limits of the screen to change it's size and position.

When I saw I immediately wondered how this could be done using C#. The idea would be to detect when a window is being dragged to the limits of our screen, when it reaches them display a frame and finally change the size and position of the window being dragged. 

At a first view, using some Windows Hook and capturing a couple of messages can do the trick. The problem is that we can only install global hooks for WH_KEYBOARD_LL and WH_MOUSE_LL, the other global hooks require a native DLL export to inject itself in another process. This is already a problem because discards the possibility to monitor messages before and after they are processed by a window,  in addition we cannot use either the MouseProc that gives us information about the mouse and more important the value of the HitTest, which is necessary to know what part of the window the user is clicking. What we will do is to try to use the WH_MOUSE_LL with some work around. I already wrote about hooks more than one year ago, you can read it here if you are not familiarized with them.

With that hook we will obtain information about mouse events, we will focus on the messages WM_LBUTTONDOWN, WM_MOUSEMOVE and WM_LBUTTONUP. In the attached code I created a hook helper that captures the hook callbacks and generates .NET events to facilitate its use. You can see below the event handler:

   1:  void hookHelper_HookMouseEvent(object sender,
   2:      HookMouseEventArgs e)
   3:  {
   4:      switch(e.Message)
   5:      {
   6:          case NativeConstants.WM_LBUTTONDOWN:
   7:              CheckWindow(e.Point);
   8:              break;
   9:    
  10:          case NativeConstants.WM_MOUSEMOVE:
  11:              CheckPosition(e.Point);
  12:              break;
  13:    
  14:          case NativeConstants.WM_LBUTTONUP:
  15:              ResizeWindow(e.Point);
  16:              break;
  17:      }
  18:  }

In the method CheckWindow we decide if we Window where the user has clicked is a window we are interested to monitor, to do it we retrieve the window handle using the API WindowFromPoint and doing a Hit test over the window. The Hit test can be done by sending the message WM_NCHITTEST specifying the handle of the window and the coordinates of the point, this will return us a value indicating what part of the window has been clicked, in our case we are interested only if the user clicks the Title of the window, which value is HTCAPTION.

   1:  private void CheckWindow(Point point)
   2:  {
   3:      Win32.NativeStructs.Point p =
   4:          new Win32.NativeStructs.Point();
   5:      p.x = point.X;
   6:      p.y = point.Y;
   7:    
   8:      windowHandle = NativeMethods.WindowFromPoint(p);
   9:      if (windowHandle != IntPtr.Zero)
  10:      {
  11:          IntPtr lParam = new IntPtr(65536 * p.y + p.x);
  12:          IntPtr hitTestPtr = NativeMethods.SendMessage(windowHandle,
  13:              (uint)NativeConstants.WM_NCHITTEST, (uint)0, lParam);
  14:          if ((int)hitTestPtr == NativeConstants.HTCAPTION)
  15:          {
  16:              dragging = true;
  17:          }
  18:      }
  19:  }

The messages, return values and functions are declared in the header Winuser.h, the easier to have all the information about the windows API is to go to MSDN, of course, but also to download the Windows SDK.

Next step is to give to the user the visual clue that something will happen with the window, to do it Windows 7 shows an animation below the pointer and a frame simulating where the window will be placed. To do it we will not code any fancy thing, we can simulate it by just opening a standard .NET form setting its properties ControlBox, ShowInTaskBar and TransparencyKey to our desired values. To know the size we need to give to our frame we just need to check the property WorkingArea from the .NET class System.Windows.Forms.SystemInformation. The only trick we will do is to display the form with the function SetWindowPos, because it allow us controlling the Z-Order of the window and to open the form without activate it, this is handy to avoid our fake frame is placed on top of the window we are actually moving. To do it we use the function SetWindowPos including the handle of the window to insert after and the parameter SWP_NOACTIVATE.

   1:  NativeMethods.SetWindowPos(frame.Handle,
   2:      windowHandle,
   3:      0, 0,
   4:      SystemInformation.WorkingArea.Width / 2,
   5:      SystemInformation.WorkingArea.Height,
   6:      NativeConstants.SWP_SHOWWINDOW |
   7:      NativeConstants.SWP_NOACTIVATE);

We are near to the end, we just need to complete the last step, change the size and position of the window to the desired the values using again the function SetWindowPos for the left and right placement. To maximize we just use the function ShowWindow passing as parameter SW_SHOWMAXIMIZED.

Windows 7 also restores the original size of the window after you drag away from the docked position, this could be done by capturing first the size and position of the window using the functions GetWindowPlacement and SetWindowPlacement, if you use them do not forget to set the length of your WindowPlacement structure using Marshal.SizeOf().

If you are not familiarized with P/Invoke in .NET you can make use of tools like PInvoke Interop Assistant from Microsoft CLR Interop Team or the P/Invoke wiki.

Now that all seems to be under control, if you download the code you will see that we have a big problem. When we release the button the size doesn't seem to change, but it actually changes to the size and position we have given.  The problem is that the window is in the moving state and when we release the button after set our size, it comes back to the last size the window had before releasing the mouse button. If we make use of Microsoft Spy++ we can see that after set the position the window still receives the messages like WM_WINDOWPOSCHANGED or WM_EXITSIZEMOVE.

I've tried with some of the messages captured previously using the function SendMessage to see if I could exit the moving/sizing state but without success. So, at the end I couldn't resolve it using C#, if you have ideas on how this can be solved they will be welcome. As I was joking with a friend a couple of days ago I feel I've lost my "mojo".

It is curious to see how our mind works, when I completely stopped thinking about what message send in order to make it work, I thought I could try the last trick by setting the size after the resize was completed. The way to do that is pretty simply, we just need to enable a timer that starts after we capture the WM_LBUTTONUP.

MadAboutNet.WindowsDocker.zip (404.63 kb)

Hook Keyboard and Mouse

In the forums of Baleares on .NET (Spanish) a member asked a general question about hooking functions, avoiding the easy joke about what hooking functions are, we could define them basically as a technique which allows putting your own code between the caller and the destination.

One of the easiest and common hooks is the one to capture keyboard and mouse events, something that you can easily implement in .NET making use of InteropServices.

We will see with a sample how to build a class that intercepts the mouse and keyboard messages and raises a .net event, which users can handle (see below to download the full source code).

The first thing is to obtain the functions to install and remove the hooks, which we can find in the user32.dll.

   1: [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
   2: public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
   3:  
   4: [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
   5: public static extern bool UnhookWindowsHookEx(int idHook);
   6:  
   7: [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
   8: public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);

You can find detailed information about what every parameter is in the MSDN, but just as a fast review: idHook is the type of hook to install, lpfn is a pointer to our hook method, hMod is a handle to the DLL that will contain the method pointed by lpfn and finally dwThreadId, which specifies the identifer of the thread with which the hook method is to be associated. The type we have set to lpfn is HookProc, as you will see next, this is just a delegate with the next signature: public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

Another important thing to stand up is the third DllImport, what we are going to do is to install and remove hooks from a hook chain, because of that it will be necessary to call the next installed hooks, otherwise we will prevent they are executed.

To install the hook the only thing we need to do is to call SetWiwnodwsHookEx with the rigth parameters.

   1: HookProc mouseHookProc = new HookProc(MouseHookProc);
   2: ...
   3: public void InstallMouseHook()
   4: {
   5:      idHookMouse = NativeMethods.SetWindowsHookEx(NativeConstants.WH_MOUSE_LL, mouseHookProc, Marshal.GetHINSTANCE(this.GetType().Module), 0);
   6:     if (idHookMouse == 0)
   7:         throw new ApplicationException("MouseHook cannot be set");
   8: }
   9: ...
  10: private int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
  11: {
  12:     if (nCode < 0)
  13:     {
  14:         return NativeMethods.CallNextHookEx(NativeConstants.WH_MOUSE_LL, nCode, wParam, lParam);
  15:     }
  16:     else
  17:     {
  18:         MSLLHOOKSTRUCT mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
  19:  
  20:         HookMouseEventArgs args = new HookMouseEventArgs();
  21:         args.Point = new Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y);
  22:         args.Message = wParam.ToInt32();
  23:  
  24:         OnHookMouse(args);
  25:  
  26:         return NativeMethods.CallNextHookEx(NativeConstants.WH_MOUSE_LL, nCode, wParam, lParam);
  27:     }
  28: }
  29: ...
  30: [StructLayout(LayoutKind.Sequential)]
  31: public class MSLLHOOKSTRUCT
  32: {
  33:     public POINT pt;
  34:     public int mouseData;
  35:     public int flags;
  36:     public int time;
  37:     public int dwExtraInfo;
  38: }

In the excerpt of the above code we set a global hook to intercept all the messages of the mouse. When there is a message the method MouseHookProc is called and we will fire an event with the HookMouseEventArgs class as parameters, after that we take care of call the next hook in the chain. In the line 18 you can see how we convert the pointer lParam received as parameter to an struct of type MSLLHOOKSTRUCT. This class contains some valuable information like "mouseData" that represents the message type (left button down, mouse wheel ...), or "pt" that represents the point in which is the mouse.

The last thing you need to know is that you must remove the hook as soon as possible, since it's resource consuming. This can be accomplished as follows:

   1: public void RemoveMouseHook()
   2: {
   3:     if (idHookMouse > 0)
   4:         NativeMethods.UnhookWindowsHookEx(idHookMouse);
   5:  
   6:     mouseHookProc = null;
   7:     idHookMouse = 0;
   8: }

These are the basis about how to build your own class to handle Mouse Hooks.

I hope you find interesting and remember that you can complete all the information in our bible: the MSDN.

Registry Handling

These days I’ve been bothered by a virus that decided to live on my laptop.

I’m not going to explain how it arrived there, because I get really mad, I’m just going to give a common sense suggestion: never, never let your laptop to other people, doesn’t matter how friends you are ….

In any case there was an interesting part on all this, since the virus had nice things to learn which I’m going to try to reproduce using .NET:

The virus introduced lot of keys in the registry; one of the most interesting was added to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify. Maybe in another post I will explain why this one is interesting, but first let’s try to add or modify registry keys from C#.

The .NET Framework, once more, has done most of the work for us. We only need to take a look to the Microsoft.Win32 namespace and there we will find the classes "Registry" and "RegistryKey", which makes extremely easy to work with the Windows Registry.

Take a look to the next code:

   1: string keyName = @"Software\Microsoft\Windows NT\CurrentVersion\WinLogon\Notify";
   2:  
   3: RegistryKey notifyKey = Registry.LocalMachine.OpenSubKey(keyName, true); 
   4: RegistryKey testKey = notifyKey.CreateSubKey("TestKey"); 
   5:  
   6: testKey.SetValue("Asynchronous", 0x00000001, RegistryValueKind.DWord); 
   7: testKey.SetValue("DllName", "FakeVirus.dll", RegistryValueKind.String); 
   8: testKey.SetValue("Impersonate", 0x00000000, RegistryValueKind.DWord); 
   9: testKey.SetValue("Logoff", "Logoff", RegistryValueKind.String); 
  10: testKey.SetValue("Logon", "Logon", RegistryValueKind.String);

As you already deducted we have created a new sub key called TestKey and introduced in it 5 different values, Asynchronous, DllName, Impersonate … very easy, isn't it?

With the virus I wasn’t able to delete the registry entries added because they were regenerated immediately after any kind of change. This was done monitoring the entries, something that cannot be done directly with the managed classes but Windows supports through its API.

Reproduce this mechanism is not a big problem since even if .NET doesn’t have an equivalent method we can use p/invoke (Platform Invoke) to use the unmanaged functions of the Win32 API. For more information about it you can check the next link: http://msdn2.microsoft.com/en-us/library/ms724892.aspx

I add here the most interesting part of the code in order you can build your own monitor:

   1: // P/Invoke methods and constants 
   2:  
   3: internal class NativeMethods
   4: {
   5:    private NativeMethods()
   6:    {
   7:    } 
   8:  
   9:    [DllImport("advapi32.dll", SetLastError = true)]
  10:    internal static extern int RegNotifyChangeKeyValue(
  11:       IntPtr hKey,
  12:       [MarshalAs(UnmanagedType.Bool)] 
  13:       bool watchSubtree,
  14:       int dwNotifyFilter, 
  15:       IntPtr hEvent, 
  16:       [MarshalAs(UnmanagedType.Bool)] 
  17:       bool fAsynchronous 
  18:    ); 
  19:  
  20:    [DllImport("advapi32.dll", SetLastError = true)]
  21:    internal static extern int RegOpenKeyEx(
  22:       IntPtr hKey,
  23:       string subKey,
  24:       uint options,
  25:       int sam,
  26:       out IntPtr phkResult 
  27:    ); 
  28:  
  29:    [DllImport("advapi32.dll", SetLastError = true)]
  30:    internal static extern int RegCloseKey(
  31:       IntPtr hKey 
  32:    ); 
  33:  
  34:    internal const int HKEY_CLASSES_ROOT = unchecked((int)0x80000000);
  35:    internal const int HKEY_CURRENT_USER = unchecked((int)0x80000001);
  36:    internal const int HKEY_LOCAL_MACHINE = unchecked((int)0x80000002);
  37:    internal const int HKEY_USERS = unchecked((int)0x80000003);
  38:    internal const int HKEY_PERFORMANCE_DATA= unchecked((int)0x80000004);
  39:    internal const int HKEY_CURRENT_CONFIG = unchecked((int)0x80000005);
  40:    internal const int HKEY_DYN_DATA = unchecked((int)0x80000006); 
  41:  
  42:    internal const int KEY_NOTIFY = 0x0010; 
  43:  
  44:    internal const int REG_NOTIFY_CHANGE_NAME = 0x00000001;
  45:    internal const int REG_NOTIFY_CHANGE_ATTRIBUTES = 0x00000002;
  46:    internal const int REG_NOTIFY_CHANGE_LAST_SET = 0x00000004;
  47:    internal const int REG_NOTIFY_CHANGE_SECURITY = 0x00000008;
  48: } 
  49:  
  50: .................................
  51:  
  52: IntPtr parentKey = new IntPtr(NativeMethods.HKEY_LOCAL_MACHINE); 
  53:  
  54: string subKey = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify\TestKey" 
  55:  
  56: IntPtr monitoredKey; 
  57:  
  58: try 
  59: { 
  60:    int ret = NativeMethods.RegOpenKeyEx(parentKey, subKey, 0, NativeMethods.KEY_NOTIFY, out monitoredKey);
  61:    if (ret != 0)
  62:    {
  63:       throw new Win32Exception(ret);
  64:    } 
  65:  
  66:    using (AutoResetEvent monitorEvent = new AutoResetEvent(false))
  67:    {
  68:       ret = NativeMethods.RegNotifyChangeKeyValue(parentKey, true, 
  69:                         NativeMethods.REG_NOTIFY_CHANGE_NAME | NativeMethods.REG_NOTIFY_CHANGE_ATTRIBUTES | 
  70:                         NativeMethods.REG_NOTIFY_CHANGE_LAST_SET | NativeMethods.REG_NOTIFY_CHANGE_SECURITY, 
  71:                         monitorEvent.Handle, true); 
  72:  
  73:       if (ret != 0)
  74:       {
  75:          throw new Win32Exception(ret);
  76:       }  
  77:  
  78:       monitorEvent.WaitOne();
  79:       // Do something after the registry has changed 
  80:    } 
  81: } 
  82: finally
  83: {
  84:    parentKey = IntPtr.Zero;
  85:    if (monitoredKey != IntPtr.Zero)
  86:    {
  87:       NativeMethods.RegCloseKey(monitoredKey);
  88:       monitoredKey = IntPtr.Zero;
  89:    } 
  90: }

Nice, don't you think?