Node.js 可调用 C++ 模块:跨平台鼠标中间按钮事件监听
Node.js 可调用 C++ 模块:跨平台鼠标中间按钮事件监听
本文将介绍如何编写一个 Node.js 可调用的 C++ 模块,用于接收 PC 端鼠标中间按钮按下的事件,并实现跨平台兼容(Windows、Linux、Mac)。该模块通过 Node.js 注册事件回调 JavaScript 代码,实现功能调用。
模块功能概述
- 接收 PC 端鼠标中间按钮按下事件
- 跨平台兼容:Windows、Linux、Mac
- 通过 Node.js 注册事件回调 JavaScript 代码
- 注册函数:
addMiddleMouseClickListener - 支持多次注册事件,鼠标中间按钮按下时一次调用所有注册的回调函数
- 未注册事件时不响应
实现步骤
在 C++ 中,我们需要使用 node-addon-api 库来编写可调用的 Node.js 模块。主要步骤如下:
- 定义 C++ 函数: 该函数将被 Node.js 调用,接收一个回调函数作为参数,用于在鼠标中间按钮按下时调用。
- 注册事件监听器: 使用操作系统特定的 API 注册鼠标中间按钮按下事件监听器。
- Windows:使用
SetWindowsHookEx函数 - Linux:使用
X11或QuartzAPI - Mac:使用
CoreGraphicsAPI
- Windows:使用
- 调用回调函数: 使用
node-addon-api库的Function类在鼠标中间按钮按下时调用回调函数。 - 导出 C++ 函数: 使用
node-addon-api库的Napi模块将 C++ 函数导出为 Node.js 模块。
示例代码
#include <napi.h>
#include <iostream>
// Windows 头文件
#ifdef _WIN32
#include <windows.h>
HHOOK mouseHook;
#endif
// Linux 头文件
#ifdef __linux__
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XInput2.h>
Display* display;
int xi_opcode;
XIEventMask eventmask;
#endif
// Mac 头文件
#ifdef __APPLE__
#include <ApplicationServices/ApplicationServices.h>
CGEventRef eventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon);
CFMachPortRef eventTap;
#endif
class MouseListener : public Napi::ObjectWrap<MouseListener> {
public:
static Napi::Object Init(Napi::Env env, Napi::Object exports);
MouseListener(const Napi::CallbackInfo& info);
private:
static Napi::FunctionReference constructor;
void AddMiddleMouseListener(const Napi::CallbackInfo& info);
Napi::FunctionReference callback_;
};
Napi::FunctionReference MouseListener::constructor;
Napi::Object MouseListener::Init(Napi::Env env, Napi::Object exports) {
Napi::Function func = DefineClass(env, "MouseListener", {
InstanceMethod("addMiddleMouseListener", &MouseListener::AddMiddleMouseListener),
});
constructor = Napi::Persistent(func);
constructor.SuppressDestruct();
exports.Set("MouseListener", func);
return exports;
}
MouseListener::MouseListener(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<MouseListener>(info) {
callback_ = Napi::Persistent(info[0].As<Napi::Function>());
}
void MouseListener::AddMiddleMouseListener(const Napi::CallbackInfo& info) {
callback_ = Napi::Persistent(info[0].As<Napi::Function>());
// Windows 实现
#ifdef _WIN32
mouseHook = SetWindowsHookEx(WH_MOUSE_LL, [](int nCode, WPARAM wParam, LPARAM lParam) -> LRESULT {
if (nCode == HC_ACTION) {
MSLLHOOKSTRUCT* p = (MSLLHOOKSTRUCT*)lParam;
if (p->flags & LLMHF_INJECTED) {
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
if (wParam == WM_MBUTTONDOWN) {
Napi::Env env = callback_.Env();
callback_.Call({Napi::String::New(env, "middle")});
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}, NULL, 0);
#endif
// Linux 实现
#ifdef __linux__
display = XOpenDisplay(NULL);
if (display == NULL) {
std::cerr << "Failed to open X display" << std::endl;
return;
}
int xi_major_version = 2, xi_minor_version = 0;
if (!XQueryExtension(display, "XInputExtension", &xi_opcode, &xi_opcode, &xi_opcode)) {
std::cerr << "X Input extension not available" << std::endl;
return;
}
XIEventMask eventmask;
unsigned char mask[XIMaskLen(XI_LASTEVENT)] = {0};
XISetMask(mask, XI_ButtonPress);
XISetMask(mask, XI_ButtonRelease);
eventmask.deviceid = XIAllMasterDevices;
eventmask.mask_len = sizeof(mask);
eventmask.mask = mask;
XISelectEvents(display, DefaultRootWindow(display), &eventmask, 1);
XEvent event;
while (true) {
XNextEvent(display, &event);
if (event.type == GenericEvent && event.xcookie.extension == xi_opcode) {
XIDeviceEvent* xiEvent = (XIDeviceEvent*)event.xcookie.data;
if (xiEvent->evtype == XI_ButtonPress && xiEvent->detail == 2) {
Napi::Env env = callback_.Env();
callback_.Call({Napi::String::New(env, "middle")});
}
}
}
#endif
// Mac 实现
#ifdef __APPLE__
CGEventMask eventMask = CGEventMaskBit(kCGEventOtherMouseUp);
eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, 0, eventMask, eventTapCallback, NULL);
CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(eventTap, true);
#endif
}
#ifdef __APPLE__
CGEventRef eventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon) {
if (type == kCGEventOtherMouseDown && CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) == 2) {
Napi::Env env = MouseListener::callback_.Env();
MouseListener::callback_.Call({Napi::String::New(env, "middle")});
}
return event;
}
#endif
Napi::Object Init(Napi::Env env, Napi::Object exports) {
MouseListener::Init(env, exports);
return exports;
}
NODE_API_MODULE(addon, Init)
使用方法
- 编译 C++ 代码生成 Node.js 模块。
- 在 Node.js 代码中引入模块。
- 使用
addMiddleMouseClickListener函数注册回调函数。
const addon = require('path/to/your/addon');
const listener = new addon.MouseListener((message) => {
console.log('鼠标中间按钮按下:', message);
});
listener.addMiddleMouseClickListener();
注意事项
- Linux 和 Mac 下的示例代码使用了
while(true)循环来等待事件,这会阻塞主线程。在实际应用中,应该使用异步方法来处理事件。 - 该模块仅实现了基本功能,实际应用中可能需要根据需求进行调整。
- 该模块使用
node-addon-api库实现,请确保已经安装并配置好。
总结
本文介绍了如何编写一个 Node.js 可调用的 C++ 模块,用于跨平台监听鼠标中间按钮按下事件。通过该模块,我们可以轻松地将鼠标事件监听功能集成到 Node.js 应用中。
原文地址: https://www.cveoy.top/t/topic/okli 著作权归作者所有。请勿转载和采集!