返回介绍

读取剪切板文件路径

发布于 2024-09-11 01:11:56 字数 4568 浏览 0 评论 0 收藏 0

Electron 公开的剪切板 API 是无法读取剪切板内多个文件的路径的,开发一个原生模块来补全 Electron 在这方面的不足。

创建原生模块的入口文件:

src/native/export.cc

#include <napi.h>
#include <tuple>
#include "clipboard.h"

Napi::Array ReadFilePathsJs(const Napi::CallbackInfo &info)
{
    auto env = info.Env();
    const auto file_paths = ReadFilePaths();
    auto result = Napi::Array::New(env, file_paths.size());
    for (size_t i = 0; i != file_paths.size(); ++i)
    {
        result.Set(i, file_paths[i]);
    }
    return result;
}

Napi::Object Init(Napi::Env env, Napi::Object exports)
{
    exports.Set("readFilePaths", Napi::Function::New(env, ReadFilePathsJs));
    return exports;
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init);
  • NODE_API_MODULE 定义此原生模块的入口函数,一旦 Node.js 加载该模块时,将执行 Init 方法,NODE_GYP_MODULE_NAME 宏展开后为编译配置文件 binding.gyp 中的 target_name。
  • Init 方法是这个模块的入口函数,这个函数包含两个参数,第一个是 JavaScript 运行时环境对象,第二个是模块的导出对象(也就是 module.exports),给这个对象设置属性,以导出想要暴露给外部的内容,此处导出了 ReadFilePathsJs 方法,当外部调用此方法时,将执行 ReadFilePathsJs 函数。入口函数退出时应把 exports 对象返回给调用方。
  • ReadFilePathsJs 方法执行时调用方会传入一个 CallbackInfo 类型的参数,它是一个由 Node.js 传入的对象,该对象包含 JavaScript 调用此方法时的输入参数,可以通过这个对象的 Env 方法获取 JavaScript 运行时环境对象。
  • ReadFilePathsJs 方法内,调用了 ReadFilePaths 方法,这个方法在 clipboard.h 中定义,它返回一个字符串容器(std::vector 类型),这个容器中的内容就是剪切板内的文件路径。
  • 把这个容器中的内容逐一复制到一个数组中(Napi::Array 类型,这个类型可以直接被 JavaScript 访问),最后把这个数组返回给调用者。

定义 clipboard.h 头文件内容:

src/native/clipboard.h

#ifndef CLIPBOARD_H
#define CLIPBOARD_H

#include <vector>
#include <string>

std::vector<std::string> ReadFilePaths();

#endif

定义方法:ReadFilePaths,在不同平台下读取剪切板的实现逻辑不同,所以要为这个头文件完成两个实现逻辑。clipboard.cc 是 Windows 平台上的实现。clipboard.mm 是 Mac 平台上的实现逻辑。

Windows 平台上的实现逻辑:

src/native/clipboard.cc

#include <Windows.h>
#include <ShlObj.h>
#include <memory>
#include "clipboard.h"

// 宽字符串转 UTF8 字符串
std::string Utf16CStringToUtf8String(LPCWSTR input, UINT len)
{
    int target_len = WideCharToMultiByte(CP_UTF8, 0, input, len, NULL, 0, NULL, NULL);
    std::string result(target_len, '\0');
    WideCharToMultiByte(CP_UTF8, 0, input, len, result.data(), target_len, NULL, NULL);
    return result;
}

//  RAII(Resource Acquisition Is Initialization)类
class ClipboardScope
{

    bool valid;

public:
    ClipboardScope()
    {
        valid = static_cast<bool>(OpenClipboard(NULL));
    }
    ~ClipboardScope()
    {
        CloseClipboard();
    }

    bool IsValid()
    {
        return valid;
    }
};

//读取剪切板内的文件路径
std::vector<std::string> ReadFilePaths()
{
    auto result = std::vector<std::string>();
    ClipboardScope clipboard_scope;
    if (!clipboard_scope.IsValid())
    {
        return result;
    }
    HDROP drop_files_handle = (HDROP)GetClipboardData(CF_HDROP);
    if (!drop_files_handle)
    {
        return result;
    }
    UINT file_count = DragQueryFileW(drop_files_handle, 0xFFFFFFFF, NULL, 0);
    result.reserve(file_count);
    for (UINT i = 0; i < file_count; ++i)
    {
        UINT path_len = DragQueryFileW(drop_files_handle, i, NULL, 0);
        UINT buffer_len = path_len + 1;
        std::unique_ptr<WCHAR[]> buffer(new WCHAR[buffer_len]);
        path_len = DragQueryFileW(drop_files_handle, i, buffer.get(), buffer_len);
        result.emplace_back(Utf16CStringToUtf8String(buffer.get(), path_len));
    }
    return result;
}
  • OpenClipboard 之后要对应的关闭操作 CloseClipboard,把这两个操作封装到了一个对象中:clipboard_scope,这个对象初始化时,执行 OpenClipboard 操作,对象释放时,执行 CloseClipboard 操作,这是使用 C++ 开发常见的 RAII(Resource Acquisition Is Initialization)开发技巧。
  • 通过 Windows API 获取到的文件路径是宽字节字符串,需要把这个字符串转化成 UTF8 格式的字符串才能被 JavaScript 使用,上述代码中 Utf16CStringToUtf8String 方法就是完成这个任务的。
  • 如果剪切板内没有文件路径,就返回一个空容器,如果有文件路径,就把所有的文件路径都放置到容器中返回给调用者。

Mac 平台上的实现逻辑:

src/native/clipboard.mm

#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#include "clipboard.h"

std::vector<std::string> ReadFilePaths() {
    NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
    NSArray<NSURL *> *urls = [pasteboard readObjectsForClasses:@[NSURL.class] options:@{
            NSPasteboardURLReadingFileURLsOnlyKey: @YES,
    }];
    if (!urls) {
        return {};
    }
    auto result = std::vector<std::string>();
    result.reserve(urls.count);
    for (NSURL *url in urls) {
        result.emplace_back([url.path UTF8String]);
    }
    return result;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文