UOS环境C#/Avalonia将文件剪切到剪切(粘贴)板实现

问题描述:

Avalonia的剪切板IClipboard实现目前(Ver11.3.10)在UOS国产系统下不能成功将文件写入剪切板,在桌面文件夹粘贴时会报错:

尝试了Avalonia的DataObject旧方法和直接用C#操纵X11,也试了xclip-copyfile命令行,均未能成功,最后曲线救国,采用自编译可执行文件,在软件里调用成功,实现在软件内选择文件复制,然后在桌面文件夹粘贴OK。

以下是需要的代码:

复制代码
一、实现思路
使用X11库与系统剪切板交互
将文件路径转换为URI格式
设置剪切板数据为x-special/gnome-copied-files格式

二、完整代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>

// 将文件路径转换为文件URI
char* file_to_uri(const char* filepath) {
    // 简单的实现,实际应该进行URL编码
    int length = strlen(filepath) + 8; // "file://" + 原路径 + '\0'
    char* uri = malloc(length);
    if (uri == NULL) return NULL;
    
    snprintf(uri, length, "file://%s", filepath);
    return uri;
}

// 设置文件到剪切板
int set_file_to_clipboard(const char* filepath) {
    Display* display = XOpenDisplay(NULL);
    if (!display) {
        fprintf(stderr, "无法打开X显示\n");
        return 1;
    }
    
    Window window = XCreateSimpleWindow(display, 
                                       DefaultRootWindow(display),
                                       0, 0, 1, 1, 0, 0, 0);
    
    // 获取剪切板原子
    Atom clipboard = XInternAtom(display, "CLIPBOARD", False);
    Atom targets_atom = XInternAtom(display, "TARGETS", False);
    Atom text_atom = XInternAtom(display, "TEXT", False);
    Atom utf8_atom = XInternAtom(display, "UTF8_STRING", False);
    Atom x_special_atom = XInternAtom(display, "x-special/gnome-copied-files", False);
    Atom uri_list_atom = XInternAtom(display, "text/uri-list", False);
    
    // 转换为URI格式
    char* uri = file_to_uri(filepath);
    if (!uri) {
        fprintf(stderr, "无法创建URI\n");
        XCloseDisplay(display);
        return 1;
    }
    
    // 创建剪切板数据
    // GNOME格式: copy\nfile://path
    int copy_cmd_len = strlen("copy\n") + strlen(uri) + 1;
    char* copy_cmd = malloc(copy_cmd_len);
    if (!copy_cmd) {
        free(uri);
        XCloseDisplay(display);
        return 1;
    }
    snprintf(copy_cmd, copy_cmd_len, "copy\n%s", uri);
    
    // URI列表格式: file://path
    char* uri_list = malloc(strlen(uri) + 2); // uri + \n + \0
    if (!uri_list) {
        free(uri);
        free(copy_cmd);
        XCloseDisplay(display);
        return 1;
    }
    snprintf(uri_list, strlen(uri) + 2, "%s\n", uri);
    
    // 设置剪切板所有权
    XSetSelectionOwner(display, clipboard, window, CurrentTime);
    
    if (XGetSelectionOwner(display, clipboard) != window) {
        fprintf(stderr, "无法获取剪切板所有权\n");
        free(uri);
        free(copy_cmd);
        free(uri_list);
        XCloseDisplay(display);
        return 1;
    }
    
    // 等待剪切板请求
    XEvent event;
    while (1) {
        XNextEvent(display, &event);
        
        if (event.type == SelectionRequest) {
            XSelectionRequestEvent* req = &event.xselectionrequest;
            XSelectionEvent notify = {0};
            
            notify.type = SelectionNotify;
            notify.display = req->display;
            notify.requestor = req->requestor;
            notify.selection = req->selection;
            notify.target = req->target;
            notify.property = req->property;
            notify.time = req->time;
            
            if (req->target == x_special_atom) {
                // 设置x-special/gnome-copied-files格式
                XChangeProperty(req->display, req->requestor, req->property,
                              utf8_atom, 8, PropModeReplace,
                              (unsigned char*)copy_cmd, strlen(copy_cmd));
                notify.property = req->property;
                XSendEvent(req->display, req->requestor, False, 0, (XEvent*)&notify);
            }
            else if (req->target == uri_list_atom) {
                // 设置text/uri-list格式
                XChangeProperty(req->display, req->requestor, req->property,
                              utf8_atom, 8, PropModeReplace,
                              (unsigned char*)uri_list, strlen(uri_list));
                notify.property = req->property;
                XSendEvent(req->display, req->requestor, False, 0, (XEvent*)&notify);
            }
            else if (req->target == targets_atom) {
                // 返回支持的格式
                Atom targets[] = { x_special_atom, uri_list_atom, text_atom, utf8_atom };
                XChangeProperty(req->display, req->requestor, req->property,
                              XA_ATOM, 32, PropModeReplace,
                              (unsigned char*)targets, sizeof(targets)/sizeof(targets[0]));
                notify.property = req->property;
                XSendEvent(req->display, req->requestor, False, 0, (XEvent*)&notify);
            }
            else if (req->target == text_atom || req->target == utf8_atom) {
                // 纯文本格式(文件路径)
                XChangeProperty(req->display, req->requestor, req->property,
                              utf8_atom, 8, PropModeReplace,
                              (unsigned char*)filepath, strlen(filepath));
                notify.property = req->property;
                XSendEvent(req->display, req->requestor, False, 0, (XEvent*)&notify);
            }
            else {
                // 不支持的目标
                notify.property = None;
                XSendEvent(req->display, req->requestor, False, 0, (XEvent*)&notify);
            }
        }
        else if (event.type == SelectionClear) {
            break;
        }
    }
    
    // 清理
    free(uri);
    free(copy_cmd);
    free(uri_list);
    XDestroyWindow(display, window);
    XCloseDisplay(display);
    
    printf("文件 '%s' 已复制到剪切板\n", filepath);
    printf("现在可以在文件管理器中右键粘贴或使用Ctrl+V粘贴\n");
    
    return 0;
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        fprintf(stderr, "用法: %s <文件路径>\n", argv[0]);
        fprintf(stderr, "示例: %s /home/user/example.txt\n", argv[0]);
        return 1;
    }
    
    // 检查文件是否存在
    FILE* file = fopen(argv[1], "r");
    if (!file) {
        fprintf(stderr, "错误: 文件 '%s' 不存在或无法读取\n", argv[1]);
        return 1;
    }
    fclose(file);
    
    return set_file_to_clipboard(argv[1]);
}

三、编译方法
# 安装必要的开发库
sudo apt-get install libx11-dev
# 编译程序
gcc -o set-file-clipboard set-file-clipboard.c -lX11

四、使用方法

# 设置文件到剪切板
./set-file-clipboard /path/to/your/file.txt

View Code

复制代码
#region UOS(dde-file-manager)剪切文件

public static async Task SetFilesToClipboard(List<string> filePaths)
{
    if (filePaths == null || filePaths.Count == 0)
        return;
    
    // 每个带空格的文件路径都需要用引号包围
    var arguments = string.Join(" ", filePaths.Select(fp => 
        $"\"{fp.Replace("\"", "\\\"")}\""
    ));

    const string exePath = "set-files-to-clipboard";
    if (!File.Exists(exePath))
        throw new FileNotFoundException($"可执行文件不存在: {exePath}");
    
    if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
    {
        try
        {
            var mode = File.GetUnixFileMode(exePath);
            if ((mode & UnixFileMode.UserExecute) == 0)
            {
                File.SetUnixFileMode(exePath, 
                    mode | UnixFileMode.UserExecute | 
                    UnixFileMode.GroupExecute | 
                    UnixFileMode.OtherExecute);
                LogHelper.Info($"已为 {exePath} 设置执行权限");
            }
        }
        catch (Exception ex)
        {
            LogHelper.Error($"设置权限失败: {ex.Message}");
        }
    }
    
    var process = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = exePath,
            Arguments = arguments,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true
        }
    };
    
    LogHelper.Info($"执行命令: {process.StartInfo.FileName} {arguments}");
    
    process.Start();
    var output = await process.StandardOutput.ReadToEndAsync();
    var error = await process.StandardError.ReadToEndAsync();
    await process.WaitForExitAsync();
    
    if (process.ExitCode != 0)
    {
        throw new Exception($"设置剪切板失败: {error}");
    }
    
    LogHelper.Info(output);
}
#endregion

View Code

关键词:复制,剪切,粘贴文件,文件管理器,Avalonia,Clipboard,dde-file-manager,UOS,Linux

相关推荐
秋雨雁南飞8 小时前
Avalonia 示例
avalonia
fengfuyao9859 小时前
基于C#实现的支持五笔和拼音输入的输入法
开发语言·c#
xiaowu08010 小时前
C# 多返回值写法
java·前端·c#
前端慢慢其修远10 小时前
利用signalR实现简单通信(Vue2+C#)
c#·vue
微小冷11 小时前
C#异步编程详解
开发语言·c#·async·await·异步编程
qq_3161652911 小时前
C#委托和事件的区别
开发语言·c#
刘975311 小时前
【第24】天24c#今日小结
开发语言·c#
wangnaisheng12 小时前
【C#】性能优化
c#
CreasyChan13 小时前
Unity DOTS技术栈详解
unity·c#·游戏引擎
时光追逐者14 小时前
一个基于 .NET 8 开源免费、高性能、低占用的博客系统
c#·.net·博客系统