몇달전에 Raw input을 사용해야하는 일이 있었다
단순한 문제라고 생각했던 나는 관련 라이브러리를 찾아보았으나, 내 목적에 부합하는 건 존재하지 않았다
그래서 그냥 열심히 구글링해가며 새로 만들었다
당시 나에게 필요했던 요구사항은 두가지이다
Python의 경우 라이브러리가 존재하기에 손쉽게 설치할 수 있었다
하지만 C/C++의 경우 각 운영체제별 코드는 존재하였지만, 멀티 플랫폼을 위한 코드는 존재하지 않았다
따라서 각 운영체제별 코드를 작성하고, 매크로로 한쪽을 비활성화시키는 방향으로 진행하였다
윈도우의 경우, 기본적으로 hook과 message 객체를 사용한다
방식은 간단하다
https://learn.microsoft.com/ko-kr/windows/win32/winmsg/about-hooks
https://learn.microsoft.com/ko-kr/windows/win32/learnwin32/window-messages
위 방식을 코드로 구현하기 위해서는 loop과 callback, 두가지 코드가 필요하다
우선 loop의 경우, 아래와 같은 순서로 동작한다
void start_loop() {
hook = SetWindowsHookEx(WH_KEYBOARD_LL, &callback_win32, NULL, 0);
if (!hook) {
std::cout << "Failed to create hook : " << GetLastError();
return;
}
while (!GetMessage(&message, NULL, 0, 0) && running) {
TranslateMessage(&message);
DispatchMessage(&message);
}
UnhookWindowsHookEx(hook);
}
callback 함수는 다음과 같다
LRESULT CALLBACK callback_win32(int c, WPARAM w, LPARAM l) {
if (c != HC_ACTION) return CallNextHookEx(NULL, c, w, l);
auto key = ((PKBDLLHOOKSTRUCT)l)->vkCode;
if (w == WM_KEYDOWN) {
// 키 눌림...
}
else if (w == WM_KEYUP) {
// 키 올라감...
}
return CallNextHookEx(NULL, c, w, l);
}
macOS는 Xcode 프로젝트에 'ApplicationService.framework'을 추가해줘야 한다
여기에서 EventTap이라는 객체를 사용할 수 있게되고, 이로써 event 발생시 callback을 받을 수 있다
https://www.kuniga.me/blog/2021/11/16/a-simple-key-logger.html
macOS도 Windows와 동일하게 loop과 callback 두가지 함수가 필요하다
우선 loop의 경우 다음과 같다
void start_loop() {
CFMachPortRef event = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp),
&callback_darwin,
NULL);
if (!event) {
fprintf(stderr, "Failed to create event tap\n");
exit(1);
}
loop = CFRunLoopGetCurrent();
CFRunLoopSourceRef loop_src = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, event, 0);
CFRunLoopAddSource(loop, loop_src, kCFRunLoopCommonModes);
CGEventTapEnable(event, true);
CFRunLoopRun();
}
이때 윈도우와 차이점이라면, CFRunLoopRun 함수의 경우 자체적으로 loop을 실행한다는 것이다
즉, 윈도우는 while문을 통해 message를 받아와야 했다면, macOS는 위 함수만 실행함으로써 loop을 돌게 된다
callback은 다음과 같다
CGEventRef callback_darwin(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void*) {
if ((type != kCGEventKeyDown) && (type != kCGEventKeyUp)) return event;
CGKeyCode key_code = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
if (type == kCGEventKeyDown) {
// 키 눌림...
}
else if (type == kCGEventKeyUp) {
// 키 올라감...
}
return event;
}
만들어 놓은게 아까워서 좀더 다듬고 Github에도 올려놓았다
https://github.com/ross1573/key_logger