Node.js 可调用 C++ 模块:跨平台鼠标中键事件监听
Node.js 可调用 C++ 模块:跨平台鼠标中键事件监听
本文介绍如何编写一个跨平台的 Node.js 可调用 C++ 模块,用于监听鼠标中键按下事件。该模块在 Electron 环境下运行,并提供 addMiddleMouseClickListener 和 removeMiddleMouseClickListener 函数用于注册和移除事件监听器。
目标:
- 在 Electron 应用中监听鼠标中键按下事件。
- 使用 C++ 编写跨平台兼容的事件监听模块。
- 通过 Node.js 注册事件回调 JavaScript 代码。
模块功能:
addMiddleMouseClickListener:注册鼠标中键按下事件监听器。removeMiddleMouseClickListener:移除某个注册的事件监听器。- 支持多次注册事件监听器,鼠标中键按下时会调用所有注册的回调函数。
- 回调函数的参数为当前鼠标的 X 坐标。
- 如果未注册任何事件监听器,则不会响应鼠标中键按下事件。
1. 编写 C++ 模块
#include <node.h>
#include <nan.h>
#ifdef _WIN32
#include <windows.h>
#elif __linux__
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XTest.h>
#elif __APPLE__
#include <ApplicationServices/ApplicationServices.h>
#endif
namespace mouse_event {
#ifdef _WIN32
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode < 0) {
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
if (wParam == WM_MBUTTONDOWN) {
POINT pt;
GetCursorPos(&pt);
Nan::Callback* cb = reinterpret_cast<Nan::Callback*>(lParam);
Nan::TryCatch trycatch;
cb->Call(Nan::GetCurrentContext()->Global(), 1, &Nan::New(pt.x));
if (trycatch.HasCaught()) {
Nan::FatalException(trycatch);
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
HHOOK hook = NULL;
NAN_METHOD(AddMiddleMouseClickListener) {
Nan::Callback* cb = new Nan::Callback(info[0].As<v8::Function>());
hook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, NULL, 0);
SetProp(NULL, 'MiddleMouseClickCallback', cb);
}
NAN_METHOD(RemoveMiddleMouseClickListener) {
Nan::Callback* cb = reinterpret_cast<Nan::Callback*>(GetProp(NULL, 'MiddleMouseClickCallback'));
if (cb != NULL) {
UnhookWindowsHookEx(hook);
SetProp(NULL, 'MiddleMouseClickCallback', NULL);
delete cb;
}
}
#elif __linux__
Display* display;
void MouseProc() {
XEvent event;
while (XPending(display) > 0) {
XNextEvent(display, &event);
if (event.type == ButtonPress && event.xbutton.button == Button2) {
Nan::Callback* cb = reinterpret_cast<Nan::Callback*>(XFetchBytes(display, 1));
if (cb != NULL) {
XFree(cb);
XTestFakeButtonEvent(display, Button2, True, CurrentTime);
XTestFakeButtonEvent(display, Button2, False, CurrentTime);
XFlush(display);
Nan::TryCatch trycatch;
cb->Call(Nan::GetCurrentContext()->Global(), 1, &Nan::New(event.xbutton.x));
if (trycatch.HasCaught()) {
Nan::FatalException(trycatch);
}
}
}
}
}
Nan::Callback* cb = NULL;
NAN_METHOD(AddMiddleMouseClickListener) {
cb = new Nan::Callback(info[0].As<v8::Function>());
display = XOpenDisplay(NULL);
XGrabButton(display, Button2, AnyModifier, DefaultRootWindow(display), False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
XSaveContext(display, None, reinterpret_cast<XContext>(cb), reinterpret_cast<const char*>(cb), sizeof(Nan::Callback*));
XEvent event;
event.type = ButtonPress;
event.xbutton.button = Button2;
XSendEvent(display, DefaultRootWindow(display), True, ButtonPressMask, &event);
XFlush(display);
}
NAN_METHOD(RemoveMiddleMouseClickListener) {
if (cb != NULL) {
XUngrabButton(display, Button2, AnyModifier, DefaultRootWindow(display));
XDeleteContext(display, None, reinterpret_cast<XContext>(cb));
delete cb;
}
}
#elif __APPLE__
CGEventRef MouseProc(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon) {
if (type == kCGEventOtherMouseDown && CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) == 2) {
CGPoint pt = CGEventGetLocation(event);
Nan::Callback* cb = reinterpret_cast<Nan::Callback*>(refcon);
Nan::TryCatch trycatch;
cb->Call(Nan::GetCurrentContext()->Global(), 1, &Nan::New(pt.x));
if (trycatch.HasCaught()) {
Nan::FatalException(trycatch);
}
}
return event;
}
CFMachPortRef port;
CFRunLoopSourceRef source;
CFRunLoopRef loop;
NAN_METHOD(AddMiddleMouseClickListener) {
Nan::Callback* cb = new Nan::Callback(info[0].As<v8::Function>());
CGEventMask mask = CGEventMaskBit(kCGEventOtherMouseDown);
port = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, mask, MouseProc, cb);
source = CFMachPortCreateRunLoopSource(NULL, port, 0);
loop = CFRunLoopGetCurrent();
CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes);
CGEventTapEnable(port, true);
CFRunLoopRun();
}
NAN_METHOD(RemoveMiddleMouseClickListener) {
if (loop != NULL) {
CFRunLoopStop(loop);
CFRunLoopRemoveSource(loop, source, kCFRunLoopCommonModes);
CFRelease(source);
CFRelease(port);
}
}
#endif
NAN_MODULE_INIT(Init) {
Nan::Export(target, 'addMiddleMouseClickListener', AddMiddleMouseClickListener);
Nan::Export(target, 'removeMiddleMouseClickListener', RemoveMiddleMouseClickListener);
}
NODE_MODULE(mouse_event, Init)
}
2. 编写 demo
const mouse_event = require('./build/Release/mouse_event');
mouse_event.addMiddleMouseClickListener((x) => {
console.log('Middle mouse clicked at x=' + x);
});
setTimeout(() => {
mouse_event.removeMiddleMouseClickListener();
}, 5000);
注意:
- 确保在代码中包含必要的头文件。
- 使用
node-gyp编译 C++ 模块,并将其链接到您的 Electron 项目中。 - 在编写 C++ 事件监听代码时,避免阻塞方式,以确保 Electron 应用的响应性。
此代码示例展示了一个简单的鼠标中键事件监听器的实现,您可以根据自己的需求进行扩展和修改。
原文地址: https://www.cveoy.top/t/topic/opdL 著作权归作者所有。请勿转载和采集!