mirror of
https://github.com/robotn/gohook.git
synced 2024-11-23 20:56:52 +08:00
234 lines
6.3 KiB
C
234 lines
6.3 KiB
C
/* Copyright (C) 2006-2017 Alexander Barker.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <ApplicationServices/ApplicationServices.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "../iohook.h"
|
|
|
|
// #include "../logger_c.h"
|
|
#include "input.h"
|
|
|
|
// TODO Possibly relocate to input helper.
|
|
static inline CGEventFlags get_key_event_mask(iohook_event * const event) {
|
|
CGEventFlags native_mask = 0x00;
|
|
|
|
if (event->mask & (MASK_SHIFT)) { native_mask |= kCGEventFlagMaskShift; }
|
|
if (event->mask & (MASK_CTRL)) { native_mask |= kCGEventFlagMaskControl; }
|
|
if (event->mask & (MASK_META)) { native_mask |= kCGEventFlagMaskControl; }
|
|
if (event->mask & (MASK_ALT)) { native_mask |= kCGEventFlagMaskAlternate; }
|
|
|
|
if (event->type == EVENT_KEY_PRESSED || event->type == EVENT_KEY_RELEASED || event->type == EVENT_KEY_TYPED) {
|
|
switch (event->data.keyboard.keycode) {
|
|
case VC_KP_0:
|
|
case VC_KP_1:
|
|
case VC_KP_2:
|
|
case VC_KP_3:
|
|
case VC_KP_4:
|
|
case VC_KP_5:
|
|
case VC_KP_6:
|
|
case VC_KP_7:
|
|
case VC_KP_8:
|
|
case VC_KP_9:
|
|
|
|
case VC_NUM_LOCK:
|
|
case VC_KP_ENTER:
|
|
case VC_KP_MULTIPLY:
|
|
case VC_KP_ADD:
|
|
case VC_KP_SEPARATOR:
|
|
case VC_KP_SUBTRACT:
|
|
case VC_KP_DIVIDE:
|
|
case VC_KP_COMMA:
|
|
native_mask |= kCGEventFlagMaskNumericPad;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return native_mask;
|
|
}
|
|
|
|
static inline void post_key_event(iohook_event * const event) {
|
|
bool is_pressed = event->type == EVENT_KEY_PRESSED;
|
|
|
|
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
|
|
CGEventRef cg_event = CGEventCreateKeyboardEvent(src,
|
|
(CGKeyCode) scancode_to_keycode(event->data.keyboard.keycode),
|
|
is_pressed);
|
|
|
|
CGEventSetFlags(cg_event, get_key_event_mask(event));
|
|
CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works.
|
|
CFRelease(cg_event);
|
|
CFRelease(src);
|
|
}
|
|
|
|
static inline void post_mouse_button_event(iohook_event * const event, bool is_pressed) {
|
|
CGMouseButton mouse_button;
|
|
CGEventType mouse_type;
|
|
if (event->data.mouse.button == MOUSE_BUTTON1) {
|
|
if (is_pressed) {
|
|
mouse_type = kCGEventLeftMouseDown;
|
|
} else {
|
|
mouse_type = kCGEventLeftMouseUp;
|
|
}
|
|
mouse_button = kCGMouseButtonLeft;
|
|
} else if (event->data.mouse.button == MOUSE_BUTTON2) {
|
|
if (is_pressed) {
|
|
mouse_type = kCGEventRightMouseDown;
|
|
} else {
|
|
mouse_type = kCGEventRightMouseUp;
|
|
}
|
|
mouse_button = kCGMouseButtonRight;
|
|
} else {
|
|
if (is_pressed) {
|
|
mouse_type = kCGEventOtherMouseDown;
|
|
} else {
|
|
mouse_type = kCGEventOtherMouseUp;
|
|
}
|
|
mouse_button = event->data.mouse.button - 1;
|
|
}
|
|
|
|
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
|
|
CGEventRef cg_event = CGEventCreateMouseEvent(src,
|
|
mouse_type,
|
|
CGPointMake(
|
|
(CGFloat) event->data.mouse.x,
|
|
(CGFloat) event->data.mouse.y
|
|
),
|
|
mouse_button
|
|
);
|
|
CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works.
|
|
CFRelease(cg_event);
|
|
CFRelease(src);
|
|
}
|
|
|
|
static inline void post_mouse_wheel_event(iohook_event * const event) {
|
|
// FIXME Should I create a source event with the coords?
|
|
// It seems to automagically use the current location of the cursor.
|
|
// Two options: Query the mouse, move it to x/y, scroll, then move back
|
|
// OR disable x/y for scroll events on Windows & X11.
|
|
CGScrollEventUnit scroll_unit;
|
|
if (event->data.wheel.type == WHEEL_BLOCK_SCROLL) {
|
|
// Scrolling data is line-based.
|
|
scroll_unit = kCGScrollEventUnitLine;
|
|
} else {
|
|
// Scrolling data is pixel-based.
|
|
scroll_unit = kCGScrollEventUnitPixel;
|
|
}
|
|
|
|
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
|
|
CGEventRef cg_event = CGEventCreateScrollWheelEvent(src,
|
|
kCGScrollEventUnitLine,
|
|
// TODO Currently only support 1 wheel axis.
|
|
(CGWheelCount) 1, // 1 for Y-only, 2 for Y-X, 3 for Y-X-Z
|
|
event->data.wheel.amount * event->data.wheel.rotation);
|
|
|
|
CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works.
|
|
CFRelease(cg_event);
|
|
CFRelease(src);
|
|
}
|
|
|
|
static inline void post_mouse_motion_event(iohook_event * const event) {
|
|
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
|
|
CGEventRef cg_event;
|
|
if (event->mask >> 8 == 0x00) {
|
|
// No mouse flags.
|
|
cg_event = CGEventCreateMouseEvent(src,
|
|
kCGEventMouseMoved,
|
|
CGPointMake(
|
|
(CGFloat) event->data.mouse.x,
|
|
(CGFloat) event->data.mouse.y
|
|
),
|
|
0
|
|
);
|
|
} else if (event->mask & MASK_BUTTON1) {
|
|
cg_event = CGEventCreateMouseEvent(src,
|
|
kCGEventLeftMouseDragged,
|
|
CGPointMake(
|
|
(CGFloat) event->data.mouse.x,
|
|
(CGFloat) event->data.mouse.y
|
|
),
|
|
kCGMouseButtonLeft
|
|
);
|
|
} else if (event->mask & MASK_BUTTON2) {
|
|
cg_event = CGEventCreateMouseEvent(src,
|
|
kCGEventRightMouseDragged,
|
|
CGPointMake(
|
|
(CGFloat) event->data.mouse.x,
|
|
(CGFloat) event->data.mouse.y
|
|
),
|
|
kCGMouseButtonRight
|
|
);
|
|
} else {
|
|
cg_event = CGEventCreateMouseEvent(src,
|
|
kCGEventOtherMouseDragged,
|
|
CGPointMake(
|
|
(CGFloat) event->data.mouse.x,
|
|
(CGFloat) event->data.mouse.y
|
|
),
|
|
(event->mask >> 8) - 1
|
|
);
|
|
}
|
|
|
|
// kCGSessionEventTap also works.
|
|
CGEventPost(kCGHIDEventTap, cg_event);
|
|
CFRelease(cg_event);
|
|
CFRelease(src);
|
|
}
|
|
|
|
IOHOOK_API void hook_post_event(iohook_event * const event) {
|
|
switch (event->type) {
|
|
case EVENT_KEY_PRESSED:
|
|
case EVENT_KEY_RELEASED:
|
|
post_key_event(event);
|
|
break;
|
|
|
|
|
|
case EVENT_MOUSE_PRESSED:
|
|
post_mouse_button_event(event, true);
|
|
break;
|
|
|
|
case EVENT_MOUSE_RELEASED:
|
|
post_mouse_button_event(event, false);
|
|
break;
|
|
|
|
case EVENT_MOUSE_CLICKED:
|
|
post_mouse_button_event(event, true);
|
|
post_mouse_button_event(event, false);
|
|
break;
|
|
|
|
case EVENT_MOUSE_WHEEL:
|
|
post_mouse_wheel_event(event);
|
|
break;
|
|
|
|
|
|
case EVENT_MOUSE_MOVED:
|
|
case EVENT_MOUSE_DRAGGED:
|
|
post_mouse_motion_event(event);
|
|
break;
|
|
|
|
|
|
case EVENT_KEY_TYPED:
|
|
// FIXME Ignoreing EVENT_KEY_TYPED events.
|
|
|
|
case EVENT_HOOK_ENABLED:
|
|
case EVENT_HOOK_DISABLED:
|
|
// Ignore hook enabled / disabled events.
|
|
|
|
default:
|
|
// Ignore any other garbage.
|
|
logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n",
|
|
__FUNCTION__, __LINE__, event->type);
|
|
break;
|
|
}
|
|
}
|