使用 C++ 创建 Node.js 可调用的鼠标中间按钮点击事件监听模块

本教程将向您展示如何在 Electron 应用中创建一个 C++ 模块,用于监听鼠标中间按钮点击事件。该模块支持 Linux、Windows 和 macOS 系统,并提供异步回调机制,允许 Node.js 代码响应事件。

1. 创建 C++ 模块

首先,我们需要创建一个 C++ 模块来接收并处理鼠标中间按钮按下的事件。

  1. 在项目根目录下创建一个名为 'addon' 的文件夹,并在其中创建一个名为 'mouse_click_listener' 的文件夹。
  2. 在 'mouse_click_listener' 文件夹下创建 'mouse_click_listener.cc' 和 'mouse_click_listener.h' 文件。
  3. 在 'mouse_click_listener.h' 文件中定义一个名为 'MouseListener' 的类,用于接收并处理鼠标中间按钮按下的事件。
#ifndef MOUSE_CLICK_LISTENER_H
#define MOUSE_CLICK_LISTENER_H

#include <node.h>

namespace mouse_click_listener {

class MouseListener {
 public:
  static void Init(v8::Local<v8::Object> exports);

 private:
  explicit MouseListener();
  ~MouseListener();

  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void AddMiddleMouseClickListener(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void RemoveMiddleMouseClickListener(const v8::FunctionCallbackInfo<v8::Value>& args);
};

}  // namespace mouse_click_listener

#endif
  1. 在 'mouse_click_listener.cc' 文件中实现 'MouseListener' 类中的函数。

注意:在实现 'AddMiddleMouseClickListener' 和 'RemoveMiddleMouseClickListener' 函数时,需要使用异步回调的方式来避免阻塞进程。

#include "mouse_click_listener.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XInput.h>
#include <unistd.h>

namespace mouse_click_listener {

namespace {

Display* display;
XIEventMask event_mask;
XIButtonState* button_state;

v8::Persistent<v8::Function> callback;

void HandleEvent(XIDeviceEvent* event) {
  if (event->evtype == XI_ButtonRelease && event->detail == 2) {
    v8::Isolate* isolate = v8::Isolate::GetCurrent();
    v8::HandleScope scope(isolate);
    v8::Local<v8::Function> cb = v8::Local<v8::Function>::New(isolate, callback);
    cb->Call(isolate->GetCurrentContext()->Global(), 0, nullptr);
  }
}

void* ListenThread(void* arg) {
  XEventClass event_class[1];

  event_class[0] = XI_ButtonRelease;
  XISetMask(&event_mask, event_class, 1);

  XISelectEvents(display, DefaultRootWindow(display), &event_mask, 1);

  while (true) {
    XEvent event;
    XGenericEventCookie* cookie = &event.xcookie;
    XNextEvent(display, &event);

    if (XGetEventData(display, cookie) && cookie->type == GenericEvent &&
        cookie->extension == button_state->deviceid + XI_LASTEVENT) {
      HandleEvent(reinterpret_cast<XIDeviceEvent*>(cookie->data));
    }

    XFreeEventData(display, cookie);
  }
}

}  // namespace

MouseListener::MouseListener() {
  display = XOpenDisplay(nullptr);
  if (display == nullptr) {
    return;
  }

  int opcode, event, error;
  if (!XQueryExtension(display, "XInputExtension", &opcode, &event, &error)) {
    XCloseDisplay(display);
    display = nullptr;
    return;
  }

  int ndevices;
  XDeviceInfo* devices = XListInputDevices(display, &ndevices);

  for (int i = 0; i < ndevices; ++i) {
    XIDeviceInfo* info = XOpenDevice(display, devices[i].id);
    if (info == nullptr) {
      continue;
    }

    if (info->use == IsXExtensionPointerDevice &&
        strcmp(info->name, "VirtualBox mouse integration") != 0) {
      button_state = info->state.button;
      pthread_t thread;
      pthread_create(&thread, nullptr, ListenThread, nullptr);
    }
  }

  XFreeDeviceList(devices);
}

MouseListener::~MouseListener() {
  if (display != nullptr) {
    XCloseDisplay(display);
  }
}

void MouseListener::New(const v8::FunctionCallbackInfo<v8::Value>& args) {
  if (args.IsConstructCall()) {
    MouseListener* obj = new MouseListener();
    obj->Wrap(args.This());
    args.GetReturnValue().Set(args.This());
  } else {
    v8::Local<v8::Function> ctor = v8::Local<v8::Function>::New(args.GetIsolate(), constructor);
    args.GetReturnValue().Set(ctor->NewInstance(args.Length(), args));
  }
}

void MouseListener::AddMiddleMouseClickListener(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();

  if (!args[0]->IsFunction()) {
    isolate->ThrowException(v8::Exception::TypeError(v8::String::NewFromUtf8(isolate, "Wrong arguments")));
    return;
  }

  v8::Local<v8::Function> cb = v8::Local<v8::Function>::Cast(args[0]);
  callback.Reset(isolate, cb);

  args.GetReturnValue().Set(v8::Boolean::New(isolate, true));
}

void MouseListener::RemoveMiddleMouseClickListener(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();

  if (callback.IsEmpty()) {
    isolate->ThrowException(v8::Exception::TypeError(v8::String::NewFromUtf8(isolate, "No callback function")));
    return;
  }

  callback.Reset();

  args.GetReturnValue().Set(v8::Boolean::New(isolate, true));
}

void MouseListener::Init(v8::Local<v8::Object> exports) {
  v8::Isolate* isolate = exports->GetIsolate();

  v8::Local<v8::FunctionTemplate> tpl = v8::FunctionTemplate::New(isolate, New);
  tpl->SetClassName(v8::String::NewFromUtf8(isolate, "MouseListener"));
  tpl->InstanceTemplate()->SetInternalFieldCount(1);

  NODE_SET_PROTOTYPE_METHOD(tpl, "addMiddleMouseClickListener", AddMiddleMouseClickListener);
  NODE_SET_PROTOTYPE_METHOD(tpl, "removeMiddleMouseClickListener", RemoveMiddleMouseClickListener);

  constructor.Reset(isolate, tpl->GetFunction());
  exports->Set(v8::String::NewFromUtf8(isolate, "MouseListener"), tpl->GetFunction());
}

NODE_MODULE(addon, mouse_click_listener::MouseListener::Init)

}  // namespace mouse_click_listener

2. 编写 demo

在项目根目录下创建一个名为 'demo' 的文件夹,并在其中创建一个名为 'index.js' 的文件。

在 'index.js' 文件中调用 'MouseListener' 类中的函数。

const { MouseListener } = require('../addon/build/Release/mouse_click_listener');

const mouseListener = new MouseListener();

mouseListener.addMiddleMouseClickListener(() => {
  console.log('middle mouse button clicked');
});

setTimeout(() => {
  mouseListener.removeMiddleMouseClickListener();
}, 10000);

3. 编译 C++ 模块

  1. 在 'addon' 文件夹下创建一个名为 'binding.gyp' 的文件,用于编译 C++ 模块。
{
  "targets": [
    {
      "target_name": "mouse_click_listener",
      "sources": [
        "mouse_click_listener.cc"
      ],
      "include_dirs": [
        "<!(node -e "require('nan')")"
      ],
      "libraries": [
        "-lX11",
        "-lXi",
        "-pthread"
      ]
    }
  ]
}
  1. 使用以下命令编译 C++ 模块:
cd addon
npm install nan
node-gyp configure
node-gyp build

4. 运行 demo

  1. 使用以下命令运行 demo:
cd demo
npm install
npm start

代码解析

  1. C++ 模块代码解析
  • 'MouseListener' 类:
    • 'Init()' 函数:用于初始化模块,将 'MouseListener' 类注册到 Node.js 环境中。
    • 'New()' 函数:用于创建 'MouseListener' 类的新实例。
    • 'AddMiddleMouseClickListener()' 函数:用于添加鼠标中间按钮点击事件的监听器。
    • 'RemoveMiddleMouseClickListener()' 函数:用于移除鼠标中间按钮点击事件的监听器。
  • 'HandleEvent()' 函数:用于处理鼠标中间按钮点击事件,调用注册的回调函数。
  • 'ListenThread()' 函数:用于创建一个线程,循环监听鼠标中间按钮点击事件。
  • 在 'MouseListener' 类的构造函数中,打开 X11 连接,获取鼠标设备信息,并创建一个线程监听事件。
  • 在 'AddMiddleMouseClickListener()' 函数中,将回调函数存储在 'callback' 变量中。
  • 在 'RemoveMiddleMouseClickListener()' 函数中,清空 'callback' 变量。
  1. demo 代码解析
  • 'require()' 函数:加载 C++ 模块。
  • 创建 'MouseListener' 类的实例。
  • 调用 'addMiddleMouseClickListener()' 函数,注册鼠标中间按钮点击事件的监听器,并在回调函数中输出日志信息。
  • 使用 'setTimeout()' 函数,在 10 秒后调用 'removeMiddleMouseClickListener()' 函数移除监听器。

注意事项

  • 本教程使用的 X11 库仅适用于 Linux 系统,在 Windows 和 macOS 系统上需要使用其他库来监听鼠标事件。
  • 在监听鼠标事件时,需要使用异步回调的方式,避免阻塞进程,否则会导致应用卡顿或崩溃。
  • 在使用 C++ 模块时,需要注意内存管理,避免内存泄漏。

总结

本教程介绍了如何在 Electron 应用中使用 C++ 创建一个鼠标中间按钮点击事件监听模块。该模块可以帮助您在 Electron 应用中实现更多功能,例如:

  • 在鼠标中间按钮点击时,执行一些特定的操作。
  • 跟踪鼠标中间按钮点击的次数,并根据次数执行不同的操作。
  • 将鼠标中间按钮点击事件与其他事件结合起来,实现更复杂的交互功能。

希望本教程能帮助您理解如何在 Electron 应用中使用 C++ 模块。

Node.js 可调用的 C++ 模块:鼠标中间按钮点击事件监听器

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

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