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 模块。主要步骤如下:

  1. 定义 C++ 函数: 该函数将被 Node.js 调用,接收一个回调函数作为参数,用于在鼠标中间按钮按下时调用。
  2. 注册事件监听器: 使用操作系统特定的 API 注册鼠标中间按钮按下事件监听器。
    • Windows:使用 SetWindowsHookEx 函数
    • Linux:使用 X11Quartz API
    • Mac:使用 CoreGraphics API
  3. 调用回调函数: 使用 node-addon-api 库的 Function 类在鼠标中间按钮按下时调用回调函数。
  4. 导出 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)

使用方法

  1. 编译 C++ 代码生成 Node.js 模块。
  2. 在 Node.js 代码中引入模块。
  3. 使用 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 应用中。

Node.js 可调用 C++ 模块:跨平台鼠标中间按钮事件监听

原文地址: https://www.cveoy.top/t/topic/okli 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录