[C/C++] 윈도우 마우스 후킹으로 제스처 인식 프로그램 만들기 (마무리)
마우스 후킹을 이용한 매크로 프로그램 제작
저번 포스팅에 이어 이번에도 마우스 후킹에 관련된 내용입니다.
아직 못 보셨다면 기본 마우스 후킹에 대한 내용을 보시고 보셔도 좋습니다.
https://wendys.tistory.com/110
드디어 마무리 시간입니다.
이제 마우스의 이동 거리의 계산을 통한 방향 확인이 가능해진 시점에서 해야 할 일은 이동 방향의 목록을 만들어서 특정 기능을 할 수 있도록 하는 것만 남았습니다. 저번 시간에 80%까지 마무리 후 직접 해보는 걸 권장해드렸지만 이렇게 찾아주셨는데 마무리까지 책임져 드리는 게 좋을 것 같아서 진행하겠습니다.
마지막으로 제시해드렸던 enum 은 보기가 조금 더 편한 거지 선호하시는 방법으로 하시길 추천드립니다.
enum mouse_gesture : long {
unknown = 0,
left,
right,
up,
down
};
목록을 사용해야 하니 가장 간편한 std::list
를 사용할 예정입니다. std::vector
를 사용해도 되고, 어떤 걸 사용하던지 상관없습니다. 다만 List를 접근할 때는 전 보통 Lock Object
를 사용합니다. 이유는 멀티 스레드 환경에서 1번 스레드가 List에 접근하여 값을 변경하고있는데, 이 때 2번 쓰레드가 그 값을 확인하려거나 동시에 변경을 시도하는 경우 원하지 않은 결과가 나오기 때문입니다. 그렇기 때문에 개발 시 List를 사용할 때엔 습관적으로 Lock Object를 사용하는 것이 좋습니다.
#include <mutex>
#include <list>
class hook_mouse_callback {
...
private:
...
std::list<mouse_gesture> _gesture_list;
std::mutex _gesture_list_lock;
};
List를 사용하기 위해서 멤버 변수로 _gesture_list를 추가하고, List접근 시 동기화를 위하여 Lock Object를 선언합니다. Lock Object는 CriticalSection
을 사용해도 되고 std::mutex
를 사용해도 되고 동기화 객체는 무엇이든 상관없습니다. 편한 걸로 사용하시되 주의하실 점은 동기화 오브젝트 사용 중 데드락이 걸리지 않도록 꼭 관리해주셔야 합니다.
동기화 오브젝트 사용 시 RAII
라는 디자인 패턴을 주로 사용하며, Auto Lock
형태로써 지역 스코프가 완료되는 시점에 자동으로 해제
를 하도록 하여 해제를 누락시키지 않는 기법이 있으니 참고하시면 좋습니다.
위와 같이 추가했으면 준비는 다 되었으니 본론으로 돌아와서 바로 코드로 보겠습니다.
if (wparam == WM_RBUTTONDOWN) {
_is_right_button_down = true;
//
// mouse gesture clear.
//
_gesture_list.clear();
_latest_mouse_point = mouse_param->pt;
}
else if (wparam == WM_MOUSEMOVE) {
if (_is_right_button_down) {
POINT current_point = mouse_param->pt;
double distance = get_distance(_latest_mouse_point, current_point);
if (distance < __min_distance) {
break;
}
//
// 방향 결정 ← ↑ ↓ → 목록 추가
//
double dx = _latest_mouse_point.x - current_point.x;
double dy = _latest_mouse_point.y - current_point.y;
double radian = atan2(dy, dx);
double degree = (radian * 180) / __pi;
if (degree < 0.0) {
degree = degree + 360.0;
}
mouse_gesture gesture = mouse_gesture::unknown;
if (degree < 35 || degree > 315) {
gesture = mouse_gesture::left;
}
else if (degree > 135) {
if (degree > 225) {
gesture = mouse_gesture::down;
}
else {
gesture = mouse_gesture::right;
}
}
else {
gesture = mouse_gesture::up;
}
//
// 해당 방향을 list에 저장 (같은 방향인경우 추가하지 않고 진행)
//
if (_gesture_list.empty()) {
_gesture_list.push_back(gesture);
}
else {
if (_gesture_list.back() != gesture) {
_gesture_list.push_back(gesture);
}
}
//
// 기준 포인트를 현재 포인트로 변환
//
_latest_mouse_point = current_point;
}
}
WM_RBUTTONDOWN
에서 _gesture_list.clear()를 호출하고 있습니다. 마우스 우클릭 이벤트가 발생할때마다 새로운 방향 및 커맨드로 판단을 해야하기때문에 위의 타이밍에 처리되도록 되었으며, WM_RBUTTONUP
시점에 clear() 처리를 해도 문제 없습니다만 최초 실행시점에도 clear를 하기위해 WM_RBUTTONDOWN
에서 처리되었습니다.
목록이 잘 생성되었을까요?
그렇다면 목록이 잘 생성되었는지는 어떻게 확인하면 될까요?? WM_RBUTTONUP
시점에서 판단하여 처리를 해줘야 합니다.
else if (wparam == WM_RBUTTONUP) {
_is_right_button_down = false;
{
//
// command handling
//
std::wstring command;
std::lock_guard<std::mutex> lock(_gesture_list_lock);
if (false == _gesture_list.empty()) {
for (const auto& gesture : _gesture_list) {
if (mouse_gesture::left == gesture) {
command.push_back('L');
}
else if (mouse_gesture::up == gesture) {
command.push_back('U');
}
else if (mouse_gesture::down == gesture) {
command.push_back('D');
}
else if (mouse_gesture::right == gesture) {
command.push_back('R');
}
}
command.push_back('\n');
OutputDebugString(command.c_str());
}
}
}
WM_MOUSEMOVE
시 추가되었던 _gesture_list를 확인하여 각각 방향에 대하여 LUDR 등 커맨드를 생성할 수 있습니다.
이렇게 생성된 커맨드를 이용하면 처음에 보여드렸던 마우스 매크로 프로그램을 만들 수 있습니다.
간단하게 커맨드 처리하는 내용을 보여드리자면 다음 코드처럼 처리하실 수 있습니다.
완성된 전체 코드
inline double get_distance(
__in const POINT p1,
__in const POINT p2
) {
double distance = 0.0;
distance = sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
return distance;
}
inline void keyboard_input(
__in WORD virtual_key,
__in bool extended,
__in bool key_down
) {
INPUT input = {};
input.type = INPUT_KEYBOARD;
input.ki.wVk = virtual_key;
input.ki.dwFlags = key_down ? 0 : KEYEVENTF_KEYUP;
if (extended) {
input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
}
SendInput(1, &input, sizeof(INPUT));
}
inline void keyboard_input_down(
__in WORD virtual_key,
__in bool extended
) {
keyboard_input(virtual_key, extended, true);
}
inline void keyboard_input_up(
__in WORD virtual_key,
__in bool extended
) {
keyboard_input(virtual_key, extended, false);
}
bool hook_mouse_callback::execute_command(
__in std::string command
) {
bool is_execute_command = false;
if (0 == command.compare("U")) {
keyboard_input_down(VK_CONTROL, true);
keyboard_input_down(VK_HOME, true);
keyboard_input_up(VK_HOME, true);
keyboard_input_up(VK_CONTROL, true);
is_execute_command = true;
}
else if(0 == command.compare("D")) {
keyboard_input_down(VK_CONTROL, true);
keyboard_input_down(VK_END, true);
keyboard_input_up(VK_END, true);
keyboard_input_up(VK_CONTROL, true);
is_execute_command = true;
}
return is_execute_command;
}
LRESULT hook_mouse_callback::new_function(
__in int code,
__in WPARAM wparam,
__in LPARAM lparam
) {
do {
if (code < HC_ACTION) {
break;
}
MOUSEHOOKSTRUCT* mouse_param = (MOUSEHOOKSTRUCT*)lparam;
if (wparam == WM_RBUTTONDOWN) {
_is_right_button_down = true;
//
// mouse gesture clear.
//
_gesture_list.clear();
_latest_mouse_point = mouse_param->pt;
}
else if (wparam == WM_MOUSEMOVE) {
if (_is_right_button_down) {
POINT current_point = mouse_param->pt;
double distance = get_distance(_latest_mouse_point, current_point);
if (distance < __min_distance) {
break;
}
//
// 방향 결정 ← ↑ ↓ → 목록 추가
//
double dx = _latest_mouse_point.x - current_point.x;
double dy = _latest_mouse_point.y - current_point.y;
double radian = atan2(dy, dx);
double degree = (radian * 180) / __pi;
if (degree < 0.0) {
degree = degree + 360.0;
}
mouse_gesture gesture = mouse_gesture::unknown;
if (degree < 35 || degree > 315) {
gesture = mouse_gesture::left;
}
else if (degree > 135) {
if (degree > 225) {
gesture = mouse_gesture::down;
}
else {
gesture = mouse_gesture::right;
}
}
else {
gesture = mouse_gesture::up;
}
//
// 해당 방향을 list에 저장 (같은 방향인경우 추가하지 않고 진행)
//
if (_gesture_list.empty()) {
_gesture_list.push_back(gesture);
}
else {
if (_gesture_list.back() != gesture) {
_gesture_list.push_back(gesture);
}
}
//
// 기준 포인트를 현재 포인트로 변환
//
_latest_mouse_point = current_point;
}
}
else if (wparam == WM_RBUTTONUP) {
_is_right_button_down = false;
{
//
// command handling
//
std::string command;
std::lock_guard<std::mutex> lock(_gesture_list_lock);
if (false == _gesture_list.empty()) {
for (const auto& gesture : _gesture_list) {
if (mouse_gesture::left == gesture) {
command.push_back('L');
}
else if (mouse_gesture::up == gesture) {
command.push_back('U');
}
else if (mouse_gesture::down == gesture) {
command.push_back('D');
}
else if (mouse_gesture::right == gesture) {
command.push_back('R');
}
}
OutputDebugStringA(command.c_str());
if (execute_command(command)) {
//
// execute command.
//
return 1;
}
}
}
}
else {
break;
}
} while (false);
return ::CallNextHookEx(_hook, code, wparam, lparam);
}
keyboard_input
은 특정 기능을 위해서 사용하시면 될 것 같습니다. 예를 들어 위에서 처럼 ↑ command 처리 시 키보드 HOME 버튼을 시스템에 전달하거나, ↓ command 처리시 키보드 END 버튼을 전달하는 등 처리가 가능하며, 그 외에 ShellExecute, 모든 동작이 가능하기 때문에 command에 따라 원하는 기능을 구현하시면 됩니다.
여기서 핵심은 execute_command 호출 후 성공 시 return 1
처리를 하는 것에 있습니다.
이는 마우스 오른쪽 마우스 클릭이 콘텍스트 메뉴가 생성되게 되는데, 마우스 커맨드가 일어나는 시점에는 컨텍스트 메뉴가 뜨지 않게 하기 위함이기때문에 만약 마우스 로그 등의 기록을 위한경우에는 return 1 코드를 제거하여 컨텍스트 메뉴가 정상적으로 동작하도록 해야 합니다.
현재 만들어서 사용중인 프로그램은 개인적으로 만들어서 사용하다보니 UI 관련 부분이 완료가 안되어있어서 배포는 추후 할 예정입니다. 상관 없이 원하시면 비밀댓글로 요청해주세요 :) 해당 포스팅이 많은 분들에게 도움이 되었으면 좋겠습니다.
Sample Download
'⌨ DEVELOPMENT > WIndows Hooking' 카테고리의 다른 글
[C++] 최고의 API Hooking Library MS Detours. (1) | 2019.12.07 |
---|---|
[Hooking] dll injection을 이용하여 다른 프로세스에 dll 로드하기 (0) | 2019.11.16 |
[C/C++] 키보드 메시지 후킹을 이용한 키로거 만들기 (6) | 2019.07.27 |
[C/C++] 윈도우 마우스 후킹으로 제스처 인식 프로그램 만들기 (마우스 이동 방향 및 각도 구하기) (0) | 2019.07.23 |
[C/C++] 윈도우 마우스 후킹으로 제스처 인식 프로그램 만들기 (마우스 이동거리 계산하기) (2) | 2019.07.23 |
[C/C++] 윈도우 메시지 후킹 마우스를 지배하는자 (windows mouse message hooking) (1) | 2019.07.21 |
[C/C++] 윈도우 후킹을 하기 전에 알아야 할 DLL Main에서 하면 안되는 5가지 작업 (2) | 2019.07.16 |
[C/C++] 윈도우 메시지 가로채기 기법 (windows message hooking) (0) | 2019.07.14 |
댓글
이 글 공유하기
다른 글
-
[Hooking] dll injection을 이용하여 다른 프로세스에 dll 로드하기
[Hooking] dll injection을 이용하여 다른 프로세스에 dll 로드하기
2019.11.16 -
[C/C++] 키보드 메시지 후킹을 이용한 키로거 만들기
[C/C++] 키보드 메시지 후킹을 이용한 키로거 만들기
2019.07.27 -
[C/C++] 윈도우 마우스 후킹으로 제스처 인식 프로그램 만들기 (마우스 이동 방향 및 각도 구하기)
[C/C++] 윈도우 마우스 후킹으로 제스처 인식 프로그램 만들기 (마우스 이동 방향 및 각도 구하기)
2019.07.23 -
[C/C++] 윈도우 마우스 후킹으로 제스처 인식 프로그램 만들기 (마우스 이동거리 계산하기)
[C/C++] 윈도우 마우스 후킹으로 제스처 인식 프로그램 만들기 (마우스 이동거리 계산하기)
2019.07.23