25
sie/09
0

[C#] Niskopoziomowe przechwytywanie klawiszy klawiatury

Aby przechwycić naciśnięcie klawiszy na niskim poziomie (np. przed wszystkimi innymi aplikacjami) należy skorzystać z InteropSys aby „wpiąć” się do API systemu operacyjnego. Poniższy przykład z bloga Stephena Touba pokazuje jak to szybko i prosto zrobić:


using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class InterceptKeys
{
 private const int WH_KEYBOARD_LL = 13;
 private const int WM_KEYDOWN = 0x0100;
 private const int WM_KEYUP = 0x0101;
 private static LowLevelKeyboardProc _proc = HookCallback;
 private static IntPtr _hookID = IntPtr.Zero;

 public static void Main()
 {
 _hookID = SetHook(_proc);
 Application.Run();
 UnhookWindowsHookEx(_hookID);
 }

 private static IntPtr SetHook(LowLevelKeyboardProc proc)
 {
 using (Process curProcess = Process.GetCurrentProcess())
 using (ProcessModule curModule = curProcess.MainModule)
 {
 return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
 GetModuleHandle(curModule.ModuleName), 0);
 }
 }

 private delegate IntPtr LowLevelKeyboardProc(
 int nCode, IntPtr wParam, IntPtr lParam);

 private static IntPtr HookCallback(
 int nCode, IntPtr wParam, IntPtr lParam)
 {
 if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
 {
 int vkCode = Marshal.ReadInt32(lParam);
 Console.WriteLine((Keys)vkCode);
 }
 return CallNextHookEx(_hookID, nCode, wParam, lParam);
 }

 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
 private static extern IntPtr SetWindowsHookEx(int idHook,
 LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
 [return: MarshalAs(UnmanagedType.Bool)]
 private static extern bool UnhookWindowsHookEx(IntPtr hhk);

 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
 private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
 IntPtr wParam, IntPtr lParam);

 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
 private static extern IntPtr GetModuleHandle(string lpModuleName);
}

„Problem” zaczyna się w przypadku gdy chcemy obsłużyć naciśnięcie kombinacji klawiszy, a w szczególnym przypadku ALT i jakiegoś innego klawisza. ALT nie jest „przechwytywany” przez hook, przez co funkcję private static IntPtr HookCallback należy nieco przerobić, korzystając ze wcześniej zdefiniowanej struktury KeyboardHookStruct w której to możemy sprawdzić również flagi naciśniętych przycisków (w tym przypadku ALT+ESC):


public struct KeyboardHookStruct
 {
 public int vkCode;
 public int scanCode;
 public int flags;
 public int time;
 public int dwExtraInfo;

 };

// ...

KeyboardHookStruct hookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));

 if (hookStruct.vkCode == ESCAPE && (hookStruct.flags & LLKHF_ALTDOWN) == hookStruct.flags)
     MessageBox.Show("I pressed ALT + ESC!");

Kilka uwag:

  1. Należy zauważć że w tym przypadku najpierw sprawdzamy drugi klawisz z kombinacji, a potem fakt czy jest wciśnięty ALT.
  2. Druga istotna rzecz to fakt zastosowania porównania wartości klawiszy do znanych z WinAPI nazw klawiszy wirtualnych.
  3. Listę kodów klawiszy wirtualnych można znaleźć na tej stronie Microsoft MSDN
  4. Łatwo napisać aplikację która przechwytuje klawisze, ale nie przekazuje ich dalej do kolejnych aplikacji zarejestrowanych w systemie w ramach łańcucha uchwytów. Wystarczy usunąć wywołania do funkcji:  CallNextHookEx
  5. Jeżeli wciśniemy klawisz i przytrzymamy, informacje o jego naciśnięciu (KEYDOWN) będą w kółko (w odpowiednim odstępie czasowym) przesyłane. Należy to odpowiednio obsłużyć.
  6. Przykład z klawiszem ALT będzie wykonywał się dwa razy, ze względu na fakt że w nacisnięcie klawisza to w rzeczywistości dwie akcje. Naciśnięcie (KEY_DOWN) oraz podniesienie (KEY_UP) klawisza. Można to naprawić poprzez rozpoznanie jaka aktualnie akcja (DOWN/UP) jest wykonywana (poprzez porównanie z wParam) lub poprzez dodanie prostego semaforu
13
lip/09
0

[.NET] Klikanie myszą w danej lokalizacji

Klikanie myszą w danej lokalizacji (W punkcie p) zrealizowane w C#. Niby proste, jednak nie oczywiste. Poniższy kod umożliwia wykonanie kliknięcia lewym przyciskiem myszy. Należy zauważyć że składa się on z dwóch eventów które można przetłumaczyć na „LeftMouseButtonDown” oraz „LeftMouseButtonUp”:

using System.Runtime.InteropServices;
//...
[DllImport("user32.dll")]
public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);

public void DoMouseClick(Point p)
{
mouse_event(2, p.X, p.Y, 0, 0);
mouse_event(4, p.X, p.Y, 0, 0);
}

Więcej tutaj: http://msdn.microsoft.com/en-us/library/ms646260(VS.85).aspx