文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
读取剪切板文件路径
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论