/* * $RCSfile: KbdListener.cs,v $ * Copyright (C) 2006 Rob Loach (http://robloach.net) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ using System; using System.Windows.Forms; using System.Runtime.InteropServices; using System.ComponentModel; namespace ScreenCap { /// /// The KeyboardListener is a static class that allows registering a number /// of event handlers that you want to get called in case some keyboard key is pressed /// or released. The nice thing is that this KeyboardListener is also active in case /// the parent application is running in the back. /// [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name="SkipVerification")] public class KeyboardListener { #region Private declarations /// /// The Window that intercepts Keyboard messages /// private static ListeningWindow s_Listener; #endregion #region Private methods /// /// The function that will handle all keyboard activity signaled by the ListeningWindow. /// In this context handling means calling all registered subscribers for every key pressed / released. /// /// /// Inside this method the events could also be fired by calling /// s_KeyEventHandler(null,new KeyEventArgs(key,msg)) However, in case one of the registered /// subscribers throws an exception, execution of the non-executed subscribers is cancelled. /// /// /// private static void KeyHandler(ushort key, uint msg) { if(s_KeyEventHandler != null) { Delegate[] delegates = s_KeyEventHandler.GetInvocationList(); foreach(Delegate del in delegates) { EventHandler sink = (EventHandler)del; try { // This is a static class, therefore null is passed as the object reference sink(null,new UniversalKeyEventArgs(key,msg)); } // You can add some meaningful code to this catch block. catch{}; } } } #endregion #region Public declarations /// /// An instance of this class is passed when Keyboard events are fired by the KeyboardListener. /// public class UniversalKeyEventArgs : KeyEventArgs { public readonly uint m_Msg; public readonly ushort m_Key; public UniversalKeyEventArgs(ushort aKey, uint aMsg) : base((Keys)aKey) { m_Msg = aMsg; m_Key = aKey; } } /// /// For every application thread that is interested in keyboard events /// an EventHandler can be added to this variable /// public static event EventHandler s_KeyEventHandler; #endregion #region Public methods static KeyboardListener() { ListeningWindow.KeyDelegate aKeyDelegate = new ListeningWindow.KeyDelegate(KeyHandler); s_Listener = new ListeningWindow(aKeyDelegate); } #endregion #region Definition ListeningWindow class /// /// A ListeningWindow object is a Window that intercepts Keyboard events. /// private class ListeningWindow : NativeWindow { #region Declarations public delegate void KeyDelegate( ushort key, uint msg ); private const int WS_CLIPCHILDREN = 0x02000000, WM_INPUT = 0x00FF, RIDEV_INPUTSINK = 0x00000100, RID_INPUT = 0x10000003, RIM_TYPEKEYBOARD = 1; private uint m_PrevMessage = 0; private ushort m_PrevControlKey = 0; private KeyDelegate m_KeyHandler = null; #endregion #region Unsafe types internal unsafe struct RAWINPUTDEV { public ushort usUsagePage; public ushort usUsage; public uint dwFlags; public void* hwndTarget; }; internal unsafe struct RAWINPUTHEADER { public uint dwType; public uint dwSize; public void* hDevice; public void* wParam; }; internal unsafe struct RAWINPUTHKEYBOARD { public RAWINPUTHEADER header; public ushort MakeCode; public ushort Flags; public ushort Reserved; public ushort VKey; public uint Message; public uint ExtraInformation; }; #endregion public ListeningWindow(KeyDelegate keyHandlerFunction) { m_KeyHandler = keyHandlerFunction; CreateParams cp = new CreateParams(); // Fill in the CreateParams details. cp.Caption = "Hidden window"; cp.ClassName = null; cp.X = 0x7FFFFFFF; cp.Y = 0x7FFFFFFF; cp.Height = 0; cp.Width = 0; //cp.Parent = parent.Handle; cp.Style = WS_CLIPCHILDREN; // Create the actual invisible window this.CreateHandle(cp); // Register for Keyboard notification unsafe { try { RAWINPUTDEV myRawDevice = new RAWINPUTDEV(); myRawDevice.usUsagePage = 0x01; myRawDevice.usUsage = 0x06; myRawDevice.dwFlags = RIDEV_INPUTSINK; myRawDevice.hwndTarget = this.Handle.ToPointer(); if (RegisterRawInputDevices(&myRawDevice, 1, (uint)sizeof(RAWINPUTDEV)) == false) { int err = Marshal.GetLastWin32Error(); throw new Win32Exception(err,"ListeningWindow::RegisterRawInputDevices"); } } catch {throw;} } } #region Private methods protected override void WndProc(ref Message m) { switch (m.Msg) { case WM_INPUT: { try { unsafe { uint dwSize, receivedBytes; uint sizeof_RAWINPUTHEADER = (uint)(sizeof(RAWINPUTHEADER)); // Find out the size of the buffer we have to provide int res = GetRawInputData(m.LParam.ToPointer(), RID_INPUT, null, &dwSize, sizeof_RAWINPUTHEADER); if(res == 0) { // Allocate a buffer and ... byte* lpb = stackalloc byte[(int)dwSize]; // ... get the data receivedBytes = (uint)GetRawInputData((RAWINPUTHKEYBOARD*)(m.LParam.ToPointer()), RID_INPUT, lpb, &dwSize, sizeof_RAWINPUTHEADER); if ( receivedBytes == dwSize ) { RAWINPUTHKEYBOARD* keybData = (RAWINPUTHKEYBOARD*)lpb; // Finally, analyze the data if(keybData->header.dwType == RIM_TYPEKEYBOARD) { if((m_PrevControlKey != keybData->VKey) || (m_PrevMessage != keybData->Message)) { m_PrevControlKey = keybData->VKey; m_PrevMessage = keybData->Message; // Call the delegate in case data satisfies m_KeyHandler(keybData->VKey,keybData->Message); } } } else { string errMsg = string.Format("WndProc::GetRawInputData (2) received {0} bytes while expected {1} bytes", receivedBytes, dwSize); throw new Exception(errMsg); } } else { string errMsg = string.Format("WndProc::GetRawInputData (1) returned non zero value ({0})", res); throw new Exception(errMsg); } } } catch {throw;} } break; } // In case you forget this you will run into problems base.WndProc(ref m); } #endregion #region Private external methods // In case you want to have a comprehensive overview of calling conventions follow the next link: // http://www.codeproject.com/cpp/calling_conventions_demystified.asp [DllImport("User32.dll",CharSet = CharSet.Ansi,SetLastError=true)] [return : MarshalAs(UnmanagedType.Bool)] internal static extern unsafe bool RegisterRawInputDevices( RAWINPUTDEV* rawInputDevices, uint numDevices, uint size); [DllImport("User32.dll",CharSet = CharSet.Ansi,SetLastError=true)] [return : MarshalAs(UnmanagedType.I4)] internal static extern unsafe int GetRawInputData( void* hRawInput, uint uiCommand, byte* pData, uint* pcbSize, uint cbSizeHeader ); #endregion } #endregion } }