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

本文介绍如何编写一个跨平台的 Node.js 可调用 C++ 模块,用于监听鼠标中键按下事件。该模块在 Electron 环境下运行,并提供 addMiddleMouseClickListenerremoveMiddleMouseClickListener 函数用于注册和移除事件监听器。

目标:

  • 在 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 应用的响应性。

此代码示例展示了一个简单的鼠标中键事件监听器的实现,您可以根据自己的需求进行扩展和修改。

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

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

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