feat(macos): Add clipboard support to match Linux implementation
This commit is contained in:
186
macos/ClipboardHandler.h
Normal file
186
macos/ClipboardHandler.h
Normal file
@@ -0,0 +1,186 @@
|
||||
#pragma once
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// macOS 剪贴板操作封装
|
||||
// 使用 NSPasteboard API 实现
|
||||
|
||||
class ClipboardHandler
|
||||
{
|
||||
public:
|
||||
// 检查剪贴板功能是否可用 (macOS 总是可用)
|
||||
static bool IsAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取剪贴板中的文件列表
|
||||
// 返回文件的完整路径列表(UTF-8),失败返回空列表
|
||||
static std::vector<std::string> GetFiles()
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
|
||||
@autoreleasepool {
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
|
||||
// 方法1: 尝试获取文件 URL 列表 (macOS 10.13+)
|
||||
NSArray<NSURL*>* urls = [pasteboard readObjectsForClasses:@[[NSURL class]]
|
||||
options:@{NSPasteboardURLReadingFileURLsOnlyKey: @YES}];
|
||||
if (urls && urls.count > 0) {
|
||||
for (NSURL* url in urls) {
|
||||
if (url.isFileURL) {
|
||||
NSString* path = url.path;
|
||||
if (path) {
|
||||
files.push_back([path UTF8String]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
// 方法2: 兼容旧版 API (NSFilenamesPboardType)
|
||||
NSArray* filenames = [pasteboard propertyListForType:NSFilenamesPboardType];
|
||||
if (filenames && [filenames isKindOfClass:[NSArray class]]) {
|
||||
for (NSString* path in filenames) {
|
||||
if ([path isKindOfClass:[NSString class]]) {
|
||||
files.push_back([path UTF8String]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
// 获取剪贴板文本
|
||||
// 返回 UTF-8 编码的文本,失败返回空字符串
|
||||
static std::string GetText()
|
||||
{
|
||||
@autoreleasepool {
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
NSString* text = [pasteboard stringForType:NSPasteboardTypeString];
|
||||
if (text) {
|
||||
return [text UTF8String];
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// 设置剪贴板文本
|
||||
// text: UTF-8 编码的文本
|
||||
// 返回是否成功
|
||||
static bool SetText(const std::string& text)
|
||||
{
|
||||
if (text.empty()) return true;
|
||||
|
||||
@autoreleasepool {
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
[pasteboard clearContents];
|
||||
|
||||
NSString* nsText = [NSString stringWithUTF8String:text.c_str()];
|
||||
if (nsText) {
|
||||
return [pasteboard setString:nsText forType:NSPasteboardTypeString];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置剪贴板文本(从原始字节)
|
||||
// data: 文本数据(可能是 GBK 或 UTF-8)
|
||||
// len: 数据长度
|
||||
static bool SetTextRaw(const char* data, size_t len)
|
||||
{
|
||||
if (!data || len == 0) return true;
|
||||
|
||||
// 服务端发来的文本可能是 GBK 编码,尝试转换为 UTF-8
|
||||
std::string text = ConvertToUtf8(data, len);
|
||||
return SetText(text);
|
||||
}
|
||||
|
||||
// 设置剪贴板文件列表
|
||||
// files: UTF-8 编码的文件路径列表
|
||||
// 返回是否成功
|
||||
static bool SetFiles(const std::vector<std::string>& files)
|
||||
{
|
||||
if (files.empty()) return true;
|
||||
|
||||
@autoreleasepool {
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
[pasteboard clearContents];
|
||||
|
||||
NSMutableArray<NSURL*>* urls = [NSMutableArray arrayWithCapacity:files.size()];
|
||||
for (const auto& path : files) {
|
||||
NSString* nsPath = [NSString stringWithUTF8String:path.c_str()];
|
||||
if (nsPath) {
|
||||
NSURL* url = [NSURL fileURLWithPath:nsPath];
|
||||
if (url) {
|
||||
[urls addObject:url];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (urls.count > 0) {
|
||||
return [pasteboard writeObjects:urls];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
// 检查是否是有效的 UTF-8 序列
|
||||
static bool IsValidUtf8(const char* data, size_t len)
|
||||
{
|
||||
const unsigned char* bytes = (const unsigned char*)data;
|
||||
size_t i = 0;
|
||||
|
||||
while (i < len) {
|
||||
if (bytes[i] <= 0x7F) {
|
||||
// ASCII
|
||||
i++;
|
||||
} else if ((bytes[i] & 0xE0) == 0xC0) {
|
||||
// 2-byte sequence
|
||||
if (i + 1 >= len || (bytes[i + 1] & 0xC0) != 0x80) return false;
|
||||
i += 2;
|
||||
} else if ((bytes[i] & 0xF0) == 0xE0) {
|
||||
// 3-byte sequence
|
||||
if (i + 2 >= len || (bytes[i + 1] & 0xC0) != 0x80 || (bytes[i + 2] & 0xC0) != 0x80) return false;
|
||||
i += 3;
|
||||
} else if ((bytes[i] & 0xF8) == 0xF0) {
|
||||
// 4-byte sequence
|
||||
if (i + 3 >= len || (bytes[i + 1] & 0xC0) != 0x80 ||
|
||||
(bytes[i + 2] & 0xC0) != 0x80 || (bytes[i + 3] & 0xC0) != 0x80) return false;
|
||||
i += 4;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 尝试将 GBK 转换为 UTF-8
|
||||
// 如果已经是 UTF-8,直接返回
|
||||
static std::string ConvertToUtf8(const char* data, size_t len)
|
||||
{
|
||||
// 检查是否已经是有效的 UTF-8
|
||||
if (IsValidUtf8(data, len)) {
|
||||
return std::string(data, len);
|
||||
}
|
||||
|
||||
// 使用 NSString 进行编码转换 (GBK = CFStringEncodingGB_18030_2000)
|
||||
@autoreleasepool {
|
||||
// 尝试 GBK (GB18030) 编码
|
||||
NSStringEncoding gbkEncoding = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
|
||||
NSString* str = [[NSString alloc] initWithBytes:data length:len encoding:gbkEncoding];
|
||||
if (str) {
|
||||
const char* utf8 = [str UTF8String];
|
||||
if (utf8) {
|
||||
return std::string(utf8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转换失败,返回原始数据
|
||||
return std::string(data, len);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user