|
Puedes bajarte el código desde este link KeyInterceptor.cs
Introducción
Muchas veces podemos estar interesados en escuchas las
teclas que se presionan independientemente de la
aplicación activa. Esto es especialmente útil
cuando queremos que una aplicación que corre en segundo
plano reaccione ante una cierta combinación de teclas.
Ejemplo de este tipo de aplicaciones son los capturadores de
pantalla. Los key loggers y los bloqueadores (o filtros de
teclas) son otros tipos de aplicaciones de esta familia. Ahora
vamos a ver como se hacen este tipo de aplicaciones que
necesitan hechar mano a las APIs de Windows en .Net, en esta
oportunidad, con C#.
Emulando el esquema de manejo de teclado de .Net
El Framework .Net nos permite manejar las teclas mediante
dos EventHandlers (delegados):
Lo que haremos será crear una clase
KeyboardInterceptor que brinde estos delegados para responder
ante los tres eventos KeyDown, KeyPress y KeyUp.
Declarando las funciones del API
Lo primero que hacemos es declarar una clase interna llamada
WinApi que contiene todas las definiciones de funciones
importadas, estructuras y constantes que necesitaremos para
manejar el teclado a bajo nivel. Estas definiciones se
encuentran en www.pinvoke.net.
También es bueno recomendar aquí el plugin de
pinvoke para Visual Studio el cual nos permite buscar e
insertar las firmas de estas funciones.
#region Internal Structures and delegates definition Region
/// <summary>
/// The KBDLLHOOKSTRUCT structure contains information about a low-level
/// keyboard input event.
/// See: http://msdn.microsoft.com/en-us/library/ms644967(VS.85).aspx
/// </summary>
internal struct KBDLLHOOKSTRUCT
{
public int vkCode; // Specifies a virtual-key code. The code must
// be avalue in the range 1 to 254.
public int scanCode; // Specifies a hardware scan code for the key.
public int flags;
public int time;
public int dwExtraInfo;
}
/// <summary>
/// The LowLevelKeyboardProc hook procedure is an application-defined
/// or library-defined callback function used with the SetWindowsHookEx
/// function. The system calls this function every time a new keyboard
/// input event is about to be posted into a thread input queue.
/// See: http://msdn.microsoft.com/en-us/library/ms644985(VS.85).aspx
/// </summary>
internal delegate IntPtr LowLevelKeyboardProc(
int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);
#endregion
#region Internal WinApi class (Win32 api functions)
/// <summary>
/// This class define the WinApi signatures.
/// See: PInvoke web site
/// </summary>
[ComVisibleAttribute(false),
System.Security.SuppressUnmanagedCodeSecurity()]
internal class WinApi
{
#region Constants
// Constants
// Windows NT/2000/XP: Installs a hook procedure that monitors low-level
// keyboard input events. For more information, see the
// LowLevelKeyboardProc hook procedure.
internal const int WH_KEYBOARD_LL = 13;
internal const int WM_KEYDOWN = 0x0100;
internal const int WM_SYSKEYDOWN = 0x104;
internal const int WM_KEYUP = 0x101;
internal const int WM_SYSKEYUP = 0x105;
//Modifier key constants
internal const int VK_SHIFT = 0x010;
internal const int VK_CONTROL = 0x011;
internal const int VK_MENU = 0x012;
internal const int VK_CAPITAL = 0x014;
#endregion
#region DLL Imports Region
/// <summary>
/// The SetWindowsHookEx function installs an application-defined hook
/// procedure into a hook chain. You would install a hook procedure to
/// monitor the system for certain types of events. These events are
/// associated either with a specific thread or with all threads in the
/// same desktop as the calling thread.
/// See: http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx
/// </summary>
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
/// <summary>
/// The UnhookWindowsHookEx function removes a hook procedure installed
/// in a hook chain by the SetWindowsHookEx function.
/// See: http://msdn.microsoft.com/en-us/library/ms644993(VS.85).aspx
/// </summary>
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool UnhookWindowsHookEx(IntPtr hhk);
/// <summary>
/// The CallNextHookEx function passes the hook information to the next
/// hook procedure in the current hook chain. A hook procedure can call
/// this function either before or after processing the hook information.
/// See: http://msdn.microsoft.com/en-us/library/ms644974(VS.85).aspx
/// </summary>
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);
/// <summary>
/// Retrieves a module handle for the specified module. The module must
/// have been loaded by the calling process.
/// See: http://msdn.microsoft.com/en-us/library/ms683199(VS.85).aspx
/// </summary>
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern IntPtr GetModuleHandle(string lpModuleName);
/// <summary>
/// The GetKeyboardState function copies the status of the 256 virtual
/// keys to the specified buffer.
/// </summary>
[DllImport("user32")]
internal static extern int GetKeyboardState(byte[] pbKeyState);
/// <summary>
/// The ToAscii function translates the specified virtual-key code and
/// keyboard state to the corresponding character or characters. The
/// function translates the code using the input language and physical
/// keyboard layout identified by the keyboard layout handle.
/// </summary>
[DllImport("user32")]
internal static extern int ToAscii(int uVirtKey, int uScanCode,
byte[] lpbKeyState, byte[] lpwTransKey, int fuState);
#endregion
}
#endregion
Introduciendo nuestro Handler en la cadena de handler de
Windows
Lo que hay que hacer es indicarle a Windows que cuando se
presione una tecla invoque a un método en nuestro
código. Esto es lo que estamos haciendo en el
constructor de la clase KeyboardInterceptor cunado invocamos a
la función WinApi.SetWindowsHookEx() en la que indicamos
que cuando se presione una tecla invoque a nuestro
método HookCallback.
Luego en este método, en el HookCallback, tratamos
cada evento según el comando del que se trate
(WM_KEYDOWN, WM_SYSKEYDOWN, WM_KEYUP, WM_SYSKEYUP) y una vez
hecho esto pasamos el mensaje al próximo handler en la
cadena de handlers subscriptos al evento del teclado a bajo
nivel(WH_KEYBOARD_LL) mediante la llamada a la función
WinApi.CallNextHookEx(). Para hacer esto, previamente debimos
salvar el identificador del handler en la variable hookId.
/// <summary>
/// </summary>
class KeyboardInterceptor : IDisposable
{
#region Private Fields
private LowLevelKeyboardProc proc;
private IntPtr hookId = IntPtr.Zero;
#endregion
#region Public Constructor
public KeyboardInterceptor()
{
proc = new LowLevelKeyboardProc( HookCallback );
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
hookId = WinApi.SetWindowsHookEx(WinApi.WH_KEYBOARD_LL, proc,
WinApi.GetModuleHandle(curModule.ModuleName), 0);
}
}
#endregion
#region The callback method invoked when keys are pressed
private IntPtr HookCallback(
int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam)
{
int command = (int)wParam;
bool handled = false;
if (nCode >= 0)
{
if (KeyDown != null && (command == WinApi.WM_KEYDOWN || command == WinApi.WM_SYSKEYDOWN))
{
}
if (KeyPress != null && command == WinApi.WM_KEYDOWN)
{
}
if (KeyUp != null && (command == WinApi.WM_KEYUP || command == WinApi.WM_SYSKEYUP))
{
}
}
return WinApi.CallNextHookEx(hookId, nCode, wParam, ref lParam);
}
#endregion
#region IDisposable Members
/// <summary>
/// Releases the keyboard hook.
/// </summary>
public void Dispose()
{
WinApi.UnhookWindowsHookEx(hookId);
}
#endregion
}
Posibilitando engancharse y manejar los eventos
Como ya dijimos en la introducción,
implementaremos los tres eventos KeyDown, KeyPress y KeyUp para
que una aplicación pueda subscribirse a estos y
manejarlos. Lo primero es crear los eventos como sigue:
#region Public Properties
public event KeyPressEventHandler KeyPress;
public event KeyEventHandler KeyUp;
public event KeyEventHandler KeyDown;
#endregion
y luego, en el método HookCallback, lanzarlos
según corresponda.
Dado que el KeyPressEventArg recibe un caracter como
parámetro en su constructor, lo que debemos hacer es
convertir, o mejor dicho pedirle a windows que partiendo de la
virtual key presionada, nos diga que caracter ascii corresponde
a la tecla presionada.
if (KeyDown != null && (command == WinApi.WM_KEYDOWN || command == WinApi.WM_SYSKEYDOWN))
{
KeyDown(this, new KeyEventArgs((Keys)lParam.vkCode));
}
if (KeyPress != null && command == WinApi.WM_KEYDOWN)
{
shift = ((Keys.Shift & Control.ModifierKeys) == Keys.Shift);
caps = ((Keys.Capital & Control.ModifierKeys) == Keys.Capital);
byte[] keyState = new byte[256];
WinApi.GetKeyboardState(keyState);
byte[] inBuffer = new byte[2];
if (WinApi.ToAscii(lParam.vkCode, lParam.scanCode, keyState, inBuffer, lParam.flags) == 1)
{
char key = (char)inBuffer[0];
if ((caps ^ shift) && Char.IsLetter(key)) key = Char.ToUpper(key);
KeyPressEventArgs e = new KeyPressEventArgs(key);
KeyPress(this, e);
}
}
if (KeyUp != null && (command == WinApi.WM_KEYUP || command == WinApi.WM_SYSKEYUP))
{
KeyUp(this, new KeyEventArgs((Keys)lParam.vkCode));
}
Utilizando esta clase
Primero hay que crear una instancia de la clase que hemos
llamado KeyInterceptor. Esto lo hacemos utilizando un bloque
using para que cuando se sale de allí se libere el hook
gracias a que hemos hecho a nuestra clase Disposable y en
justamente en el método Dispose que liberamos el hook.
Esto es algo muy importante.
En este caso declaramos la instancia como static y
pública para poder referenciarla desde un formulario que
llamamos KeyViwerForm.
static class MainApp
{
public static KeyboardInterceptor ki;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (ki = new KeyboardInterceptor())
{
Application.Run(new KeyViewerForm());
}
}
}
En el formulario KeyViewerForm, más precisamente en
su constructor, nos subscribimos a los tres eventos y
codificamos los manejadores.
#region Key Events Management
private void Form1_Load(object sender, EventArgs e)
{
MainApp.ki.KeyPress += new KeyPressEventHandler(kh_KeyPress);
MainApp.ki.KeyDown += new KeyEventHandler(ki_KeyDown);
MainApp.ki.KeyUp += new KeyEventHandler(ki_KeyUp);
}
private void kh_KeyPress(object sender, KeyPressEventArgs e)
{
// put here code for key press
}
private void ki_KeyDown(object sender, KeyEventArgs e)
{
// put here code for key down
}
private void ki_KeyUp(object sender, KeyEventArgs e)
{
// put here code for key up
}
#endregion
Bien, eso es todo.
|