1

1 概述

本文主要讲述了如何利用xselLinux环境下对系统剪贴板的访问。

2 起因

在搜索引擎直接搜索“Java访问剪贴板”,大部分都是直接使用AWT API进行访问的例子:

Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
StringSelection selection = new StringSelection("test");
clipboard.setContents(selection, selection);

但是,一个最大的问题是,需要该程序一直运行,才能访问到剪贴板,因此,如果没有其他处理逻辑,需要加上线程休眠代码:

Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
StringSelection selection = new StringSelection("test");
clipboard.setContents(selection, selection);
TimeUnit.HOURS.sleep(1);

换句话说,这样只是临时复制到剪贴板,并没有永久复制,那么,有没有其他办法可以在运行程序结束之后也能访问到剪贴板呢?

3 xsel

xselLinux下访问剪贴板的命令行工具,类似的还有xclip,没有安装的可以使用包管理器安装。其中写入到剪贴板命令如下:

echo "test clipboard" | xsel -ib

由此想到了可以尝试使用Runtime

public static void main(String[] args) throws Exception {
    Runtime runtime = Runtime.getRuntime();
    // 直接执行命令
    Process process = runtime.exec("echo \"111\" | xsel -ib");
    // 等待执行结束
    process.waitFor();
    StringBuilder builder = new StringBuilder();
    // 获取输出
    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
    for (String s; ((s = reader.readLine()) != null); ) {
        builder.append(s);
    }
    System.out.println(builder);
    builder = new StringBuilder();
    // 获取错误输出
    reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    for (String s; ((s = reader.readLine()) != null); ) {
        builder.append(s);
    }
    System.out.println(builder);
    // 获取返回值
    int exitValue = process.exitValue();
    System.out.println("exitValue is " + exitValue);
    if (exitValue != 0) {
        System.out.println("error");
    }
    process.destroy();
}

运行之后输出如下:

"111" | xsel -ib
"111" | xsel -ib
exitValue is 0

可以看到输出结果是不正常的,这样就相当于变成了执行

echo "\"111\" | xsel -ib"

也就是输出的字符串都是echo的参数。

4 创建脚本文件

出现上面结果的原因是Process并不能直接支持使用管道运算符,因此,采用直接创建脚本运行命令的方法。

步骤:

  • 创建临时脚本文件:使用Files.createFile创建
  • 授权:700权限,也就是所有者读、写、执行权限,使用Files.setPosixFilePermissions
  • 写入脚本文件:向脚本文件写入echo str | xsel -ib,使用Files.writeString
  • 执行:利用Process.exec执行脚本文件
  • 删除:利用Files.delete删除临时文件

代码如下:

public static void main(String[] args) throws Exception {
    String fileName = "1.sh";
    Path executeFile = Files.createFile(Path.of(fileName));
    Files.setPosixFilePermissions(executeFile, Set.of(PosixFilePermission.OWNER_WRITE,
            PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ));
    String clipboardContent = "111";
    Files.writeString(executeFile, "echo " + clipboardContent + " | xsel -ib");
    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec("./" + fileName);
    process.waitFor();
    StringBuilder builder = new StringBuilder();
    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
    for (String s; ((s = reader.readLine()) != null); ) {
        builder.append(s);
    }
    System.out.println(builder);
    builder = new StringBuilder();
    reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    for (String s; ((s = reader.readLine()) != null); ) {
        builder.append(s);
    }
    System.out.println(builder);
    int exitValue = process.exitValue();
    System.out.println("exitValue is " + exitValue);
    if (exitValue != 0) {
        System.out.println("error");
    }
    Files.delete(executeFile);
    process.destroy();
}

输出:



exitValue is 0

有两行空行是System.out.println()的换行输出,说明ProcessinputStream以及errorStream都没有内容。

测试结果也是正常,能够剪贴出111字符串。

5 从剪贴板读取

从剪贴板读取的原理类似,就是xsel的参数不一样,这里不展开了,放上完整代码:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;

public class Main {
    private static final String FILENAME = "1.sh";

    public static void main(String[] args) throws Exception {
        writeToClipboard("111111");
        System.out.println(readFromClipboard());
        writeToClipboard("22222");
        System.out.println(readFromClipboard());
    }

    //写入到剪贴板
    private static void writeToClipboard(String content) throws Exception {
        Path executeFile = createFile("echo " + content + " | xsel -ib");
        exec(executeFile);
    }

    //从剪贴板读取
    private static String readFromClipboard() throws Exception {
        Path executeFile = createFile("xsel -ob");
        return exec(executeFile);
    }

    private static Path createFile(String fileContent) throws Exception {
        Path executeFile = Files.createFile(Path.of(FILENAME));
        Files.setPosixFilePermissions(executeFile, Set.of(PosixFilePermission.OWNER_WRITE,
                PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ));
        Files.writeString(executeFile, fileContent);
        return executeFile;
    }

    private static String exec(Path executeFile) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        Process process = runtime.exec("./" + FILENAME);
        process.waitFor();
        StringBuilder builder = new StringBuilder();
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        for (String s; ((s = reader.readLine()) != null); ) {
            builder.append(s);
        }
        String res = "";
        if (builder.length() != 0) {
            res = builder.toString();
        }
        reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        builder = new StringBuilder();
        for (String s; ((s = reader.readLine()) != null); ) {
            builder.append(s);
        }
        if (builder.length() != 0) {
            System.out.println(builder);
        }
        int exitValue = process.exitValue();
        System.out.println("exitValue is " + exitValue);
        if (exitValue != 0) {
            System.out.println("error");
        }
        Files.delete(executeFile);
        process.destroy();
        return res;
    }
}

氷泠
420 声望647 粉丝