백그라운드에서 키보드 입력감지를 위해서는 몇가지 방법이 있고, event-driven 구조의 windows를 이용하여 아래 그림의 구조로 Windows API에 접근해 키보드 디바이스로부터 키입력에 대한 메시지를 전달받을 수 있다.
현재 내가 아는 백그라운드에서 키보드 후킹 방법은 요렇게 있다.
- 정확하게 모르는 low-level에서의 DLL injection
- [user32.dll] SetWindowsHookEx 함수를 이용한 DLL injection
- [user32.dll] SetWindowsHookEx 함수
- [user32.dll] RegisterHotKey 함수
- [runtime library] presentationcore.dll 어플리케이션이 직접 루프를 돌면서 해당 키보드의 상태값을 확인하는 방법
위에서부터 아래로, 로우에서 하이레벨로 접근하는 방식인데 이중에 RegisterHotKey와 SetWindowsHookEx를 이용해볼 예정이고 먼저 RegisterHotKey로 이벤트로 처리 가능한 클래스가 인터넷에 있어 해당 소스를 수정해보았다.
HotKey Hooking
참고한 소스: How to create keyboard shortcut in windows that call function in my app
만든 소스: HotKey Hooking
// HotKeyManager.cs
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace KeyHookingApp
{
[Flags]
public enum KeyModifier
{
None = 0x0000,
Alt = 0x0001,
Ctrl = 0x0002,
Shift = 0x0004,
Win = 0x0008,
NoRepeat = 0x4000
}
public class HotKeyEventArgs : EventArgs
{
public HotKeyEventArgs(KeyModifier keyModifier, Keys key)
{
KeyModifier = keyModifier;
Key = key;
}
public HotKeyEventArgs(IntPtr hotKeyParam)
{
uint param = (uint)hotKeyParam.ToInt64();
Key = (Keys)((param & 0xffff0000) >> 16);
KeyModifier = (KeyModifier)(param & 0x0000ffff);
}
public KeyModifier KeyModifier { get; private set; }
public Keys Key { get; private set; }
}
public static class HotKeyManager
{
[DllImport("user32.dll", SetLastError = true)]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private class MessageWindow : Form
{
private const int WM_HOTKEY = 0x312;
public MessageWindow()
{
_wnd = this;
_hwnd = this.Handle;
_windowReadyEvent.Set();
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_HOTKEY)
{
HotKeyEventArgs e = new HotKeyEventArgs(m.LParam);
HotKeyManager.OnHotKeyPressed(this, e);
}
base.WndProc(ref m);
}
protected override void SetVisibleCore(bool value)
{
base.SetVisibleCore(false);
}
}
private static int _id;
private static volatile MessageWindow _wnd;
private static volatile IntPtr _hwnd;
private static ManualResetEvent _windowReadyEvent = new ManualResetEvent(false);
static HotKeyManager()
{
new Thread(() =>
{
Application.Run(new MessageWindow());
})
{
Name = "MessageLoopThread",
IsBackground = true
}.Start();
}
public static async Task<(bool, int)> RegisterHotKey(Keys key, KeyModifier keyModifier)
{
await Task.Run(() => { _windowReadyEvent.WaitOne(); });
int id = Interlocked.Increment(ref _id);
bool ret = (bool)_wnd.Invoke(new Func(RegisterHotKey), _hwnd, id, (uint)keyModifier, (uint)key);
return (ret, id);
}
public static bool UnregisterHotKey(int id)
{
return (bool)_wnd.Invoke(new Func(UnregisterHotKey), _hwnd, id);
}
public static event EventHandler HotKeyPressed;
private static void OnHotKeyPressed(object sender, HotKeyEventArgs eventData)
{
HotKeyPressed?.Invoke(sender, eventData);
}
}
}
아래는 WPF앱의 메인 윈도우.
// MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Threading;
namespace KeyHookingApp
{
public partial class MainWindow : Window
{
private int lastHotKeyId = 0;
public MainWindow()
{
InitializeComponent();
HotKeyManager.HotKeyPressed += HotKey_Pushed;
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
var (ret, id) = await HotKeyManager.RegisterHotKey(Keys.A, KeyModifier.Alt);
(ret, id) = await HotKeyManager.RegisterHotKey(Keys.S, KeyModifier.Alt);
lastHotKeyId = id;
}
private void Window_Closed(object sender, EventArgs e)
{
while (0 < lastHotKeyId)
{
HotKeyManager.UnregisterHotKey(lastHotKeyId--);
}
}
public void HotKey_Pushed(object sender, HotKeyEventArgs e)
{
textBlockPressedKey.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
textBlockPressedKey.Text = e.KeyModifier.ToString() + "+" + e.Key.ToString();
}));
}
}
}
'C#' 카테고리의 다른 글
[WPF] 소스로 트레이 아이콘 - 2 (Forms 대신 Windows.Controls로) (0) | 2021.02.04 |
---|---|
[WPF] 소스로 트레이 아이콘 - 1 (Windows.Forms) (0) | 2021.02.04 |
Nuget 패키지 오프라인으로 설치 (System.Windows.Form) (0) | 2021.01.27 |
[WPF] 모니터 밝기 및 감마 조절 (0) | 2021.01.27 |
C# Event, Event Handler, Delegate(정리하는중) (0) | 2020.10.01 |