Clipboard Access in JVM

The idea originated from my desire to write a Kotlin script that reads the content of the clipboard and saves it to a file. However, after searching online, the methods given by Java and Kotlin both access the system clipboard through the awt package in the JDK. Since Kotlin scripts still run on the JVM target, the methods are universal.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

public class Test {

    public static void main(String[] args) throws IOException, UnsupportedFlavorException {
        var clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        var transferable = clipboard.getContents(null);
        System.out.println(transferable.getTransferData(DataFlavor.stringFlavor));
    }
}
1
2
3
val toolkit = Toolkit.getDefaultToolkit()
val clipboard = toolkit.systemClipboard
val result = clipboard.getData(DataFlavor.stringFlavor) as String

This method is usable and supports a wide range of data types, not only text but also images. However, there is a drawback: the headless option must be turned off when using it. Kotlin is turned off by default, and it needs to be explicitly configured with kotlin -Djava.awt.headless=false to disable it.

Headless mode is a system configuration in which the display device, keyboard, or mouse is lacking. Sounds unexpected, but actually you can perform different operations in this mode, even with graphic data.

After disabling the headless mode, the code is usable. However, running it will pull up a JRE program. Although this pulled-up program has no interface, it will still force a window switch, which feels bad. So, I had to find another way and chose to use Node.js. Finally, I found clipboardy, a simple and easy-to-use npm library, which met my needs first. After carefully reading its implementation code, it accesses the system clipboard by calling the command after. However, if you call the system command, the commands in different system environments are different. How to take into account portability and run in different system environments? The solution is also simple and crude: manually write an implementation for each system, and if some systems do not have this operating system clipboard command program, include a binary as a fallback in the npm package. Simple, but usable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import process from 'node:process';
import isWSL from 'is-wsl';
import termux from './lib/termux.js';
import linux from './lib/linux.js';
import macos from './lib/macos.js';
import windows from './lib/windows.js';

const platformLib = (() => {
	switch (process.platform) {
		case 'darwin':
			return macos;
		case 'win32':
			return windows;
		case 'android':
			if (process.env.PREFIX !== '/data/data/com.termux/files/usr') {
				throw new Error('You need to install Termux for this module to work on Android: https://termux.com');
			}

			return termux;
		default:
			// `process.platform === 'linux'` for WSL.
			if (isWSL) {
				return windows;
			}

			return linux;
	}
})();

So, naturally, I borrowed this idea and implemented a version with Kotlin. The code implementation can be seen at clipboard-jvm. The idea is to call different shell commands for different operating systems to access and operate the system clipboard. For executing external commands, it's also very simple to use.

1
2
3
4
5
import org.yeungyeah.clipboard.Clipboard
val clipboard = Clipboard.getClipboard()
println(clipboard.get())
clipboard.set("Hello World")
println(clipboard.get())

I originally wanted to publish the dependency library to the Maven Central Repository so that the script could be used directly in one line. However, Maven publishing is really troublesome. After studying for a few hours, I was still blocked by some related checks, so I could only install it locally for my own use first. I will update it after uploading it to Maven Central, and also update an article on how to deploy to Maven Central.

After deploying to the Maven Central Repository, you can import the dependency directly through configuration. For example, in a Java project, you can use

1
2
3
4
5
<dependency>
  <groupId>io.github.yeung66</groupId>
  <artifactId>clipboard-jvm</artifactId>
  <version>1.0.0</version>
</dependency>

Or in a Kotlin script, directly

1
2
3
4
5
6
7
@file:DependsOn("io.github.yeung66:clipboard-jvm:1.0.0")

import org.yeungyeah.clipboard.Clipboard
val clipboard = Clipboard.getClipboard()
println(clipboard.get())
clipboard.set("Hello World")
println(clipboard.get())

Lift off🛫️. However, I have only tested it on macOS. In theory, it should work on other systems too. If you're interested, you can give it a try.