C# WPF user32.dll 을 이용하여 전역 키 후킹하기, 핫키 등록하기 / 키 보내기 (2) API를 이용한 예제보기
C - C++ - C# - Form - WPF 2019. 3. 21. 15:03이번에는 API를 응용한 함수를 만들어보겠습니다.
[목표]
- 후킹을 시작하고
- 입력 키를 확인한다.
- 조합키 입력을 확인한다.
- 내가 원하는 키를 다른 프로그램에 보낸다.
1. 후킹 시작 / 후킹 해제 선언하기
선언한 User32.dll API 를 그대로 써도 되겠지만, 본 API를 그대로 쓰기 보단
래핑하여 사용하는 것이 제 취향입니다.
public static void SetHook()
{
IntPtr hInstance = LoadLibrary("User32");
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _proc, hInstance, 0);
}
public static void UnHook()
{
UnhookWindowsHookEx(hhook);
}
UnHook은 WPF 에서 프로그램이 종료될 때 호출하면 좋겠죠?
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
UnHook();
}
2. 내가 후킹할 키가 맞는지 확인
public static IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam)
{
// 후킹할 내용만 정의
if (code <= 0 && wParam != (IntPtr) WM_KEYDOWN)
{
// 원래 입력되어야 할 프로그램에 키를 넘긴다면 아래와 같이 보냅니다.
return CallNextHookEx(hhook, code, (int)wParam, lParam);
}
int vkCode = Marshal.ReadInt32(lParam);
VKeys curKey = (VKeys)vkCode;
/*
여기에 원하는 동작을 할 소스코드를 적으면 되겠죠?
*/
// 원래 입력되어야 할 프로그램에 키를 넘기기 싫다면 (IntPtr)1; 을 보냅니다.
return (IntPtr)1;
}
3. 조합키를 확인
GetAsyncKeyState는 같이 눌린 키가 있는지 확인하는 API입니다.
Return 값에 따라서, 누른적이 있는지, 지금도 누르고 있는지를 확인할 수 있습니다.
Return 값은 어설프게나마 Enum으로 만들어 봤습니다.
/**
* 0x0000 이전에 누른 적이 없고 호출 시점에도 눌려있지 않은 상태
* 0x0001 이전에 누른 적이 있고 호출 시점에는 눌려있지 않은 상태
* 0x8000 이전에 누른 적이 없고 호출 시점에는 눌려있는 상태
* 0x8001 이전에 누른 적이 있고 호출 시점에도 눌려있는 상태
*/
enum RETURN_GetAsyncKeyState : ushort
{
NN = 0x0000, // 0
YN = 0x0001, // 1
NY = 0x8000, // 32768
YY = 0x8001 // 32769
}
private static bool IsKeyPress(VKeys key)
{
RETURN_GetAsyncKeyState rtnVal = (RETURN_GetAsyncKeyState)GetAsyncKeyState((int)key);
return RETURN_GetAsyncKeyState.YN.Equals(rtnVal) || RETURN_GetAsyncKeyState.YY.Equals(rtnVal);
}
// 위에 예제에 더해서 체크해봅시다.
public static IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam)
{
// 후킹할 내용만 정의
if (code <= 0 && wParam != (IntPtr) WM_KEYDOWN)
{
// 원래 입력되어야 할 프로그램에 키를 넘긴다면 아래와 같이 보냅니다.
return CallNextHookEx(hhook, code, (int)wParam, lParam);
}
int vkCode = Marshal.ReadInt32(lParam);
VKeys curKey = (VKeys)vkCode;
// Left Ctrl + S 인지 확인해봅시다.
if(VKeys.S.Equal(curKey) && IsKeyPress(VKeys.VK_LCONTROL))
{
// Left Ctrl + S 일 때 처리할 내용 쓰기
}
// 원래 입력되어야 할 프로그램에 키를 넘기기 싫다면 (IntPtr)1; 을 보냅니다.
return (IntPtr)1;
}
3. 프로그램에 내가 원하는 값을 보내기
함수명만 봐도 이해가 될거 같아서 함수설명은 패스하겠습니다.
public static void SetFocus(IntPtr handle)
{
SetForegroundWindow(handle);
}
public static void SendLeftButtonDown(IntPtr handle, int x, int y)
{
PostMessage(handle, WM_LBUTTONDOWN, 0, new IntPtr(y * 0x10000 + x));
}
public static void SendLeftButtonUp(IntPtr handle, int x, int y)
{
PostMessage(handle, WM_LBUTTONUP, 0, new IntPtr(y * 0x10000 + x));
}
public static void SendLeftButtondblclick(IntPtr handle, int x, int y)
{
PostMessage(handle, WM_LBUTTONDBLCLK, 0, new IntPtr(y * 0x10000 + x));
}
public static void SendRightButtonDown(IntPtr handle, int x, int y)
{
PostMessage(handle, WM_RBUTTONDOWN, 0, new IntPtr(y * 0x10000 + x));
}
public static void SendRightButtonUp(IntPtr handle, int x, int y)
{
PostMessage(handle, WM_RBUTTONUP, 0, new IntPtr(y * 0x10000 + x));
}
public static void SendRightButtondblclick(IntPtr handle, int x, int y)
{
PostMessage(handle, WM_RBUTTONDBLCLK, 0, new IntPtr(y * 0x10000 + x));
}
public static void SendMouseMove(IntPtr handle, int x, int y)
{
PostMessage(handle, WM_MOUSEMOVE, 0, new IntPtr(y * 0x10000 + x));
}
public static void SendKeyDown(IntPtr handle, int key)
{
PostMessage(handle, WM_KEYDOWN, key, IntPtr.Zero);
}
public static void SendKeyUp(IntPtr handle, int key)
{
PostMessage(handle, WM_KEYUP, key, new IntPtr(1));
}
public static void SendChar(IntPtr handle, char c)
{
SendMessage(handle, WM_CHAR, c, IntPtr.Zero);
}
public static void SendString(IntPtr handle, string s)
{
foreach (char c in s) SendChar(handle, c);
}
다만, 보내는 순서는 아래와 같이 하셔야 합니다.
...
// 보낼 프로그램의 Process 를 가져옵니다.
Process sendTargetProcess = 보낼 프로그램 가져오는 로직 정의;
// 보낼 프로그램을 활성화시킵니다.
SetFocus(sendTargetProcess.MainWindowHandle);
// 키를 보냅니다.
SendKeyDown(sendTargetProcess.MainWindowHandle, (int) VKey.PageUp);
User32의 FindWindow를 통해 프로세스의 Handle을 찾아도 되지만,
Process 를 통해 Handle을 얻어도 됩니다.
화면에 리스트 형태로 MainWindowTitle이 있는 프로세스를 찾아(작업줄에 표시되는 프로그램) 리스트뷰에 뿌리고, 뿌려진 아이템을 클릭한 뒤 보내면 편하겠죠?
아래는 리스트뷰를 선언하고, 리스트 뷰에 실행중인 프로세스를 아이템소스로 하여 선택을 유도하는 예제입니다.
- UI 선언
<Button Content="보내기" HorizontalAlignment="Left" Margin="263,104,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click" Height="22"/>
<ComboBox x:Name="cbxTest" HorizontalAlignment="Left" Margin="138,104,0,0" VerticalAlignment="Top" Width="120"/>
<ListView x:Name="livwPrcsList" Margin="0,131,394,53" BorderBrush="{x:Null}">
<ListView.View>
<GridView>
<GridViewColumn Header="프로세스명" DisplayMemberBinding="{Binding MainWindowTitle}"/>
</GridView>
</ListView.View>
</ListView>
<Button x:Name="btnGetProcess" Content="가져오기" HorizontalAlignment="Left" Margin="10,104,0,0" VerticalAlignment="Top" Width="76" Click="BtnGetProcess_Click"/>
- 소스코드(cs)에서 데이터 설정과 핸들러 정의
// MainWindow 생성자를 통해 콤보박스 초기화
public MainWindow()
{
// TEST
cbxTest.ItemsSource = Enum.GetValues(typeof(VKeys)).Cast<VKeys>();
}
// 선택한 프로세스에 선택한 키 보내기 버튼
private void Button_Click(object sender, RoutedEventArgs e)
{
if (livwPrcsList.SelectedIndex < 0 || cbxTest.SelectedIndex < 0) { MessageBox.Show("없어"); return; }
Process p = livwPrcsList.SelectedItem as Process;
VKeys a = (VKeys) cbxTest.SelectedItem;
HookingUtils.SetFocus(p.MainWindowHandle);
HookingUtils.SendKeyDown(p.MainWindowHandle, (int) a);
}
// 실행중인 프로세스 에서 작업줄에 표시되는 프로그램만 가져오기
private void BtnGetProcess_Click(object sender, RoutedEventArgs e)
{
Process[] prcs = ProcessUtils.GetProcessNames();
List<Process> prcsList = new List<Process>();
foreach (Process p in prcs)
{
if (p.MainWindowTitle.Equals(""))
{
continue;
}
prcsList.Add(p);
}
livwPrcsList.ItemsSource = prcsList;
}
원하시는 분이 있으면 WPF로 간단하게 만들어 올려드리겠습니다.
'C - C++ - C# - Form - WPF' 카테고리의 다른 글
[빠르게 보는] C# WPF 화면 캡쳐 / 스크린 캡쳐 (0) | 2019.07.09 |
---|---|
[빠르게 보는] WPF 폴더 선택 다이얼로그 (0) | 2019.07.05 |
C# WPF user32.dll 을 이용하여 전역 키 후킹하기, 핫키 등록하기 / 키 보내기 (1) API와 상수보기 (0) | 2019.03.21 |
C# WPF 콤보박스 ENUM을 데이터 바인딩하여 사용하기 (0) | 2019.03.08 |
C# WPF 마우스 이벤트 발생시키기 mouse event fire / raise (0) | 2019.03.07 |
WRITTEN BY