RawInput을 이용한 키보드/마우스 입력 모니터링
지난 글에서,
Windows - Raw Input의 Top level collection 의미
; https://www.sysnet.pe.kr/2/0/11612
Raw Input에 대한 이야기를 했는데요, 키에 대한 블록킹 기능은 없지만 모니터링을 하는 용도로는 잘 맞는 것 같습니다. 실제로 전역 후킹으로 인한 DLL 잠금 등의 문제가 없어 개발도 편리하고 C#에서도 구현하는 것이 매우 쉽습니다.
검색해 보면, 이미 대부분의 코드를 구현한 분이 있습니다. ^^
C# Get Mouse handle (GetRawInputDeviceInfo)
; https://stackoverflow.com/questions/14584280/c-sharp-get-mouse-handle-getrawinputdeviceinfo
실제로 다음과 같이 코딩해 봤습니다.
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
// https://stackoverflow.com/questions/14584280/c-sharp-get-mouse-handle-getrawinputdeviceinfo
public partial class Form1 : Form, IMessageFilter
{
public Form1()
{
InitializeComponent();
Application.AddMessageFilter(this);
}
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == Win32Interop.WM_INPUT)
{
RawInput raw = Win32Interop.GetDeviceID(m);
string source = (m.WParam.ToInt32() == Win32Interop.RIM_INPUT) ? "this" : "others";
switch (raw.Header.Type)
{
case RawInputType.Keyboard:
System.Diagnostics.Debug.WriteLine(
$"[{source}] [keyboard] Device ID : {raw.Header.Device}, VKey : {raw.Keyboard.VKey}, Code : {raw.Keyboard.MakeCode} Msg: {raw.Keyboard.Message}");
break;
case RawInputType.Mouse:
System.Diagnostics.Debug.WriteLine(
$"[{source}] [mouse] Device ID : {raw.Header.Device}, X : {raw.Mouse.LastX}, Y : {raw.Mouse.LastY}");
break;
}
}
return false;
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
Application.RemoveMessageFilter(this);
base.OnFormClosing(e);
}
private unsafe void Form1_Load(object sender, EventArgs e)
{
RAWINPUTDEVICE [] Rid = new RAWINPUTDEVICE[2];
Rid[0].UsagePage = HIDUsagePage.Generic;
Rid[0].Usage = HIDUsage.Mouse;
Rid[0].Flags = RawInputDeviceFlags.InputSink;
Rid[0].WindowHandle = this.Handle;
Rid[1].UsagePage = HIDUsagePage.Generic;
Rid[1].Usage = HIDUsage.Keyboard;
Rid[1].Flags = RawInputDeviceFlags.InputSink;
Rid[1].WindowHandle = this.Handle;
if (Win32Interop.RegisterRawInputDevices(Rid, 2, sizeof(RAWINPUTDEVICE)) == false)
{
MessageBox.Show("Failed to register");
}
}
}
}
등록 부분을 보면,
Rid[0].UsagePage = HIDUsagePage.Generic;
Rid[0].Usage = HIDUsage.Mouse;
Rid[0].Flags = RawInputDeviceFlags.InputSink;
Rid[0].WindowHandle = this.Handle;
Rid[1].UsagePage = HIDUsagePage.Generic;
Rid[1].Usage = HIDUsage.Keyboard;
Rid[1].Flags = RawInputDeviceFlags.InputSink;
Rid[1].WindowHandle = this.Handle;
키보드와 마우스에 대한 입력을 모니터링하는 걸로 했고, Flags에 InputSink 옵션을 줬는데 이 옵션이 없으면 모니터링하는 프로그램이 전경(foreground) 윈도우로 선택되지 않으면 이벤트를 받지 못합니다. 즉, 다른 프로그램에 입력 포커스가 간 경우에도 모니터링을 원한다면 InputSink 옵션을 주는 것인데, 이 경우 시스템에게 어떤 윈도우에게 메시지를 전송할지 WindowHandle 속성에 Message Loop를 실행하고 있는 윈도우의 핸들을 설정해 주는 것으로 알려줄 수 있습니다.
등록이 정상적으로 되면, Window의 메시지 루프로 WM_INPUT 메시지를 통해 전달이 됩니다.
if (m.Msg == Win32Interop.WM_INPUT)
{
RawInput raw = Win32Interop.GetDeviceID(m);
string source = (m.WParam.ToInt32() == Win32Interop.RIM_INPUT) ? "this" : "others";
switch (raw.Header.Type)
{
case RawInputType.Keyboard:
System.Diagnostics.Debug.WriteLine(
$"[{source}] [keyboard] Device ID : {raw.Header.Device}, VKey : {raw.Keyboard.VKey}, Code : {raw.Keyboard.MakeCode} Msg: {raw.Keyboard.Message}");
break;
case RawInputType.Mouse:
System.Diagnostics.Debug.WriteLine(
$"[{source}] [mouse] Device ID : {raw.Header.Device}, X : {raw.Mouse.LastX}, Y : {raw.Mouse.LastY}");
break;
}
}
따라서 WM_INPUT 이벤트가 들어온 경우, 위와 같이 키보드/마우스 유형에 따라 적절하게 정보를 구하면 됩니다. 해보면, keydown/keyup, mouse down/move/up의 모든 입력을 받아오는 것을 확인할 수 있습니다.
참고로, UAC 권한에 의해 일반 사용자 권한으로 위의 프로그램을 실행한 경우 관리자 권한으로 실행한 프로그램의 입력은 감지할 수 없습니다. 당연히, 관리자 권한의 프로그램에 대한 입력도 받고 싶다면 자신의 프로그램도 관리자 권한으로 실행해야 합니다.
(
첨부 파일은 이 글의 프로젝트를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]