Files
SimpleRemoter/linux/ClipboardHandler.h
2026-04-19 22:55:21 +02:00

290 lines
8.5 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <string>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
// Linux 剪贴板操作封装
// 使用外部工具 xclip 或 xsel 实现,简单可靠
// 支持 X11 环境Wayland 需要 wl-clipboard
class ClipboardHandler
{
public:
// 检测可用的剪贴板工具
static const char* GetClipboardTool()
{
static const char* tool = nullptr;
static bool checked = false;
if (!checked) {
checked = true;
// 优先使用 xclip其次 xsel
if (system("which xclip > /dev/null 2>&1") == 0) {
tool = "xclip";
} else if (system("which xsel > /dev/null 2>&1") == 0) {
tool = "xsel";
}
}
return tool;
}
// 检查剪贴板功能是否可用
static bool IsAvailable()
{
// 需要有剪贴板工具且 DISPLAY 环境变量存在
return GetClipboardTool() != nullptr && getenv("DISPLAY") != nullptr;
}
// 获取剪贴板中的文件列表
// 返回文件的完整路径列表UTF-8失败返回空列表
// X11 使用 text/uri-list MIME 类型存储文件路径
static std::vector<std::string> GetFiles()
{
std::vector<std::string> files;
const char* tool = GetClipboardTool();
if (!tool || strcmp(tool, "xclip") != 0) {
// xsel 不支持指定目标类型,只能用 xclip
return files;
}
// 获取 text/uri-list 类型的剪贴板内容
std::string cmd = "xclip -selection clipboard -t text/uri-list -o 2>/dev/null";
FILE* pipe = popen(cmd.c_str(), "r");
if (!pipe) return files;
std::string result;
char buffer[4096];
while (fgets(buffer, sizeof(buffer), pipe)) {
result += buffer;
}
int status = pclose(pipe);
if (WEXITSTATUS(status) != 0 || result.empty()) {
return files;
}
// 解析 URI 列表,每行一个 file:// URI
// 格式: file:///path/to/file 或 file://hostname/path
size_t pos = 0;
while (pos < result.size()) {
size_t lineEnd = result.find('\n', pos);
if (lineEnd == std::string::npos) lineEnd = result.size();
std::string line = result.substr(pos, lineEnd - pos);
pos = lineEnd + 1;
// 去除回车
while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) {
line.pop_back();
}
// 跳过空行和注释
if (line.empty() || line[0] == '#') continue;
// 解析 file:// URI
if (line.compare(0, 7, "file://") == 0) {
std::string path;
if (line.compare(0, 8, "file:///") == 0) {
// file:///path/to/file -> /path/to/file
path = line.substr(7);
} else {
// file://hostname/path -> /path (忽略 hostname)
size_t slash = line.find('/', 7);
if (slash != std::string::npos) {
path = line.substr(slash);
}
}
// URL 解码 (处理 %20 等转义字符)
path = UrlDecode(path);
if (!path.empty() && path[0] == '/') {
files.push_back(path);
}
}
}
return files;
}
// 获取剪贴板文本
// 返回 UTF-8 编码的文本,失败返回空字符串
static std::string GetText()
{
const char* tool = GetClipboardTool();
if (!tool) return "";
std::string cmd;
if (strcmp(tool, "xclip") == 0) {
cmd = "xclip -selection clipboard -o 2>/dev/null";
} else {
cmd = "xsel --clipboard --output 2>/dev/null";
}
FILE* pipe = popen(cmd.c_str(), "r");
if (!pipe) return "";
std::string result;
char buffer[4096];
while (fgets(buffer, sizeof(buffer), pipe)) {
result += buffer;
}
int status = pclose(pipe);
if (WEXITSTATUS(status) != 0) {
return ""; // 命令执行失败
}
return result;
}
// 设置剪贴板文本
// text: UTF-8 编码的文本
// 返回是否成功
static bool SetText(const std::string& text)
{
if (text.empty()) return true;
const char* tool = GetClipboardTool();
if (!tool) return false;
std::string cmd;
if (strcmp(tool, "xclip") == 0) {
cmd = "xclip -selection clipboard 2>/dev/null";
} else {
cmd = "xsel --clipboard --input 2>/dev/null";
}
FILE* pipe = popen(cmd.c_str(), "w");
if (!pipe) return false;
size_t written = fwrite(text.c_str(), 1, text.size(), pipe);
int status = pclose(pipe);
return written == text.size() && WEXITSTATUS(status) == 0;
}
// 设置剪贴板文本(从原始字节)
// data: 文本数据(可能是 GBK 或 UTF-8
// len: 数据长度
static bool SetTextRaw(const char* data, size_t len)
{
if (!data || len == 0) return true;
// 服务端发来的文本可能是 GBK 编码,尝试转换为 UTF-8
// 如果已经是 UTF-8 则直接使用
std::string text = ConvertToUtf8(data, len);
return SetText(text);
}
private:
// URL 解码 (处理 %XX 转义字符)
static std::string UrlDecode(const std::string& str)
{
std::string result;
result.reserve(str.size());
for (size_t i = 0; i < str.size(); ++i) {
if (str[i] == '%' && i + 2 < str.size()) {
// 解析两位十六进制数
char hex[3] = { str[i + 1], str[i + 2], 0 };
char* end;
long val = strtol(hex, &end, 16);
if (end == hex + 2 && val >= 0 && val <= 255) {
result += (char)val;
i += 2;
continue;
}
}
result += str[i];
}
return result;
}
// 尝试将 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);
}
// 使用临时文件进行 iconv 转换
char tmpIn[] = "/tmp/clip_in_XXXXXX";
int fdIn = mkstemp(tmpIn);
if (fdIn < 0) {
return std::string(data, len);
}
// 写入输入数据
write(fdIn, data, len);
close(fdIn);
// 使用 iconv 转换
std::string cmd = "iconv -f GBK -t UTF-8 ";
cmd += tmpIn;
cmd += " 2>/dev/null";
FILE* pipe = popen(cmd.c_str(), "r");
if (!pipe) {
unlink(tmpIn);
return std::string(data, len);
}
std::string result;
char buffer[4096];
while (fgets(buffer, sizeof(buffer), pipe)) {
result += buffer;
}
int status = pclose(pipe);
unlink(tmpIn);
// 如果转换成功且结果非空,返回转换后的文本
if (WEXITSTATUS(status) == 0 && !result.empty()) {
return result;
}
// 转换失败,返回原始数据
return std::string(data, len);
}
// 检查是否是有效的 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;
}
};