diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java
index 19c1e822903..a125c34ad3d 100644
--- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java
@@ -43,6 +43,7 @@
Test_org_eclipse_swt_accessibility_AccessibleControlEvent.class, //
Test_org_eclipse_swt_accessibility_AccessibleEvent.class, //
Test_org_eclipse_swt_accessibility_AccessibleTextEvent.class, //
+ Test_org_eclipse_swt_dnd_Clipboard.class,
Test_org_eclipse_swt_events_ArmEvent.class, //
Test_org_eclipse_swt_events_ControlEvent.class, //
Test_org_eclipse_swt_events_DisposeEvent.class, //
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/SwtTestUtil.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/SwtTestUtil.java
index cce810b32bc..beb7d4af66e 100644
--- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/SwtTestUtil.java
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/SwtTestUtil.java
@@ -105,7 +105,8 @@ public class SwtTestUtil {
public final static boolean isX11 = isGTK
&& "x11".equals(System.getProperty("org.eclipse.swt.internal.gdk.backend"));
-
+ public final static boolean isGTK4 = isGTK
+ && System.getProperty("org.eclipse.swt.internal.gtk.version", "").startsWith("4");
/**
* The palette used by images. See {@link #getAllPixels(Image)} and {@link #createImage}
@@ -400,13 +401,16 @@ public static void processEvents(int timeoutMs, BooleanSupplier breakCondition)
long targetTimestamp = System.currentTimeMillis() + timeoutMs;
Display display = Display.getCurrent();
while (!breakCondition.getAsBoolean()) {
- if (!display.readAndDispatch()) {
- if (System.currentTimeMillis() < targetTimestamp) {
- Thread.sleep(50);
- } else {
+ while (display.readAndDispatch()) {
+ if (System.currentTimeMillis() >= targetTimestamp) {
return;
}
}
+ if (System.currentTimeMillis() < targetTimestamp) {
+ Thread.sleep(50);
+ } else {
+ return;
+ }
}
}
@@ -583,18 +587,24 @@ public static boolean hasPixelNotMatching(Image image, Color nonMatchingColor, R
}
public static Path getPath(String fileName, TemporaryFolder tempFolder) {
- Path filePath = tempFolder.getRoot().toPath().resolve("image-resources").resolve(Path.of(fileName));
- if (!Files.isRegularFile(filePath)) {
+ Path path = tempFolder.getRoot().toPath();
+ Path filePath = path.resolve("image-resources").resolve(Path.of(fileName));
+ return getPath(fileName, filePath);
+}
+
+public static Path getPath(String sourceFilename, Path destinationPath) {
+ if (!Files.isRegularFile(destinationPath)) {
// Extract resource on the classpath to a temporary file to ensure it's
// available as plain file, even if this bundle is packed as jar
- try (InputStream inStream = SwtTestUtil.class.getResourceAsStream(fileName)) {
- assertNotNull(inStream, "InputStream == null for file " + fileName);
- Files.createDirectories(filePath.getParent());
- Files.copy(inStream, filePath);
+ try (InputStream inStream = SwtTestUtil.class.getResourceAsStream(sourceFilename)) {
+ assertNotNull(inStream, "InputStream == null for file " + sourceFilename);
+ Files.createDirectories(destinationPath.getParent());
+ Files.copy(inStream, destinationPath);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
- return filePath;
+ return destinationPath;
}
+
}
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_Clipboard.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_Clipboard.java
new file mode 100644
index 00000000000..bc6ef9778d0
--- /dev/null
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_Clipboard.java
@@ -0,0 +1,358 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Kichwa Coders Canada, Inc.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.swt.tests.junit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.ProcessBuilder.Redirect;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.rmi.NotBoundException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
+
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.RTFTransfer;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import clipboard.ClipboardCommands;
+
+/**
+ * Automated Test Suite for class org.eclipse.swt.dnd.Clipboard
+ *
+ * @see org.eclipse.swt.dnd.Clipboard
+ * @see Test_org_eclipse_swt_custom_StyledText StyledText tests as it also does
+ * some clipboard tests
+ */
+public class Test_org_eclipse_swt_dnd_Clipboard {
+
+ @TempDir
+ static Path tempFolder;
+ static int uniqueId = 1;
+ private Display display;
+ private Shell shell;
+ private Clipboard clipboard;
+ private TextTransfer textTransfer;
+ private RTFTransfer rtfTransfer;
+ private ClipboardCommands remote;
+ private Process remoteClipboardProcess;
+
+ @BeforeEach
+ public void setUp() {
+ display = Display.getCurrent();
+ if (display == null) {
+ display = Display.getDefault();
+ }
+
+ clipboard = new Clipboard(display);
+ textTransfer = TextTransfer.getInstance();
+ rtfTransfer = RTFTransfer.getInstance();
+ }
+
+ private void sleep() throws InterruptedException {
+ if (SwtTestUtil.isGTK4) {
+ /**
+ * TODO remove all uses of sleep and change them to processEvents with the
+ * suitable conditional, or entirely remove them
+ */
+ SwtTestUtil.processEvents(100, null);
+ } else {
+ SwtTestUtil.processEvents();
+ }
+ }
+
+ /**
+ * Note: Wayland backend does not allow access to system clipboard from
+ * non-focussed windows. So we have to create/open and focus a window here so
+ * that clipboard operations work.
+ */
+ private void openAndFocusShell() throws InterruptedException {
+ shell = new Shell(display);
+ shell.open();
+ shell.setFocus();
+ sleep();
+ }
+
+ /**
+ * Note: Wayland backend does not allow access to system clipboard from
+ * non-focussed windows. So we have to open and focus remote here so that
+ * clipboard operations work.
+ */
+ private void openAndFocusRemote() throws Exception {
+ startRemoteClipboardCommands();
+ remote.setFocus();
+ remote.waitUntilReady();
+ sleep();
+ }
+
+ @AfterEach
+ public void tearDown() throws Exception {
+ sleep();
+ try {
+ stopRemoteClipboardCommands();
+ } finally {
+ if (clipboard != null) {
+ clipboard.dispose();
+ }
+ if (shell != null) {
+ shell.dispose();
+ }
+ SwtTestUtil.processEvents();
+ }
+ }
+
+ private void startRemoteClipboardCommands() throws Exception {
+ /*
+ * The below copy using getPath may be redundant (i.e. it may be possible to run
+ * the class files from where they currently reside in the bin folder or the
+ * jar), but this method of setting up the class files is very simple and is
+ * done the same way that other files are extracted for tests.
+ *
+ * If the ClipboardTest starts to get more complicated, or other tests want to
+ * replicate this design element, then refactoring this is an option.
+ */
+ List.of( //
+ "ClipboardTest", //
+ "ClipboardCommands", //
+ "ClipboardCommandsImpl", //
+ "ClipboardTest$LocalHostOnlySocketFactory" //
+ ).forEach((f) -> {
+ // extract the files and put them in the temp directory
+ SwtTestUtil.getPath("/clipboard/" + f + ".class", tempFolder.resolve("clipboard/" + f + ".class"));
+ });
+
+ String javaHome = System.getProperty("java.home");
+ String javaExe = javaHome + "/bin/java" + (SwtTestUtil.isWindowsOS ? ".exe" : "");
+ assertTrue(Files.exists(Path.of(javaExe)));
+
+ ProcessBuilder pb = new ProcessBuilder(javaExe, "clipboard.ClipboardTest").directory(tempFolder.toFile());
+ pb.inheritIO();
+ pb.redirectOutput(Redirect.PIPE);
+ remoteClipboardProcess = pb.start();
+
+ // Read server output to find the port
+ int port = runOperationInThread(() -> {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(remoteClipboardProcess.getInputStream()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith(ClipboardCommands.PORT_MESSAGE)) {
+ String[] parts = line.split(":");
+ return Integer.parseInt(parts[1].trim());
+ }
+ }
+ throw new RuntimeException("Failed to get port");
+ });
+ assertNotEquals(0, port);
+ Registry reg = LocateRegistry.getRegistry("127.0.0.1", port);
+ long stopTime = System.currentTimeMillis() + 1000;
+ do {
+ try {
+ remote = (ClipboardCommands) reg.lookup(ClipboardCommands.ID);
+ break;
+ } catch (NotBoundException e) {
+ // try again because the remote app probably hasn't bound yet
+ }
+ } while (System.currentTimeMillis() < stopTime);
+
+ // Run a no-op on the Swing event loop so that we know it is idle
+ // and we can continue startup
+ remote.waitUntilReady();
+ }
+
+ private void stopRemoteClipboardCommands() throws Exception {
+ try {
+ if (remote != null) {
+ remote.stop();
+ remote = null;
+ }
+ } finally {
+ if (remoteClipboardProcess != null) {
+ try {
+ remoteClipboardProcess.waitFor(1, TimeUnit.SECONDS);
+ } finally {
+ remoteClipboardProcess.destroyForcibly();
+ remoteClipboardProcess = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Make sure to always copy/paste unique strings - this ensures that tests run
+ * under {@link RepeatedTest}s don't false pass because of clipboard value on
+ * previous iteration.
+ */
+ private String getUniqueTestString() {
+ return "Hello World " + uniqueId++;
+ }
+
+ /**
+ * Test that the remote application clipboard works
+ */
+ @Test
+ public void test_Remote() throws Exception {
+ openAndFocusRemote();
+ String helloWorld = getUniqueTestString();
+ remote.setContents(helloWorld);
+ assertEquals(helloWorld, remote.getStringContents());
+ }
+
+ /**
+ * This tests set + get on local clipboard. Remote clipboard can have different
+ * behaviours and has additional tests.
+ */
+ @Test
+ public void test_LocalClipboard() throws Exception {
+ openAndFocusShell();
+
+ String helloWorld = getUniqueTestString();
+ clipboard.setContents(new Object[] { helloWorld }, new Transfer[] { textTransfer });
+ assertEquals(helloWorld, clipboard.getContents(textTransfer));
+ assertNull(clipboard.getContents(rtfTransfer));
+
+ helloWorld = getUniqueTestString();
+ String helloWorldRtf = "{\\rtf1\\b\\i " + helloWorld + "}";
+ clipboard.setContents(new Object[] { helloWorld, helloWorldRtf }, new Transfer[] { textTransfer, rtfTransfer });
+ assertEquals(helloWorld, clipboard.getContents(textTransfer));
+ assertEquals(helloWorldRtf, clipboard.getContents(rtfTransfer));
+
+ helloWorld = getUniqueTestString();
+ helloWorldRtf = "{\\rtf1\\b\\i " + helloWorld + "}";
+ clipboard.setContents(new Object[] { helloWorldRtf }, new Transfer[] { rtfTransfer });
+ if (SwtTestUtil.isCocoa) {
+ /*
+ * macOS's pasteboard has some extra functionality that even if you don't
+ * provide a plain text version, the pasteboard will convert the rtf to plain
+ * text. This isn't in SWT's API contract so if this test fails in the future it
+ * can be removed.
+ *
+ * From the apple docs
+ *
+ * For example, if you provided a requestor object for the NSPasteboardTypeRTF
+ * type, write data to the pasteboard in the RTF format. You don’t need to write
+ * multiple data formats to the pasteboard to ensure interoperability with other
+ * apps.
+ */
+ assertEquals(helloWorld, clipboard.getContents(textTransfer));
+ } else {
+ assertNull(clipboard.getContents(textTransfer));
+ }
+ assertEquals(helloWorldRtf, clipboard.getContents(rtfTransfer));
+ }
+
+ @Test
+ public void test_setContents() throws Exception {
+ try {
+ openAndFocusShell();
+ String helloWorld = getUniqueTestString();
+
+ clipboard.setContents(new Object[] { helloWorld }, new Transfer[] { textTransfer });
+ sleep();
+
+ openAndFocusRemote();
+ SwtTestUtil.processEvents(1000, () -> helloWorld.equals(runOperationInThread(remote::getStringContents)));
+ String result = runOperationInThread(remote::getStringContents);
+ assertEquals(helloWorld, result);
+ } catch (Exception | AssertionError e) {
+ if (SwtTestUtil.isGTK4 && !SwtTestUtil.isX11) {
+ // TODO make the code + test stable
+ throw new RuntimeException(
+ "This test is really unstable on wayland backend, at least with Ubuntu 25.04", e);
+ }
+ throw e;
+ }
+ }
+
+ @Test
+ public void test_getContents() throws Exception {
+ openAndFocusRemote();
+ String helloWorld = getUniqueTestString();
+ remote.setContents(helloWorld);
+
+ openAndFocusShell();
+ SwtTestUtil.processEvents(1000, () -> {
+ return helloWorld.equals(clipboard.getContents(textTransfer));
+ });
+ assertEquals(helloWorld, clipboard.getContents(textTransfer));
+ }
+
+ @FunctionalInterface
+ public interface ExceptionalSupplier {
+ T get() throws Exception;
+ }
+
+ /**
+ * When running some operations, such as requesting remote process read the
+ * clipboard, we need to have the event queue processing otherwise the remote
+ * won't be able to read our clipboard contribution.
+ *
+ * This method starts the supplier in a new thread and runs the event loop until
+ * the thread completes, or until a timeout is reached.
+ */
+ private T runOperationInThread(ExceptionalSupplier supplier) throws RuntimeException {
+ return runOperationInThread(2000, supplier);
+ }
+
+ /**
+ * When running some operations, such as requesting remote process read the
+ * clipboard, we need to have the event queue processing otherwise the remote
+ * won't be able to read our clipboard contribution.
+ *
+ * This method starts the supplier in a new thread and runs the event loop until
+ * the thread completes, or until a timeout is reached.
+ */
+ private T runOperationInThread(int timeoutMs, ExceptionalSupplier supplier) throws RuntimeException {
+ Object[] supplierValue = new Object[1];
+ Exception[] supplierException = new Exception[1];
+ Runnable task = () -> {
+ try {
+ supplierValue[0] = supplier.get();
+ } catch (Exception e) {
+ supplierValue[0] = null;
+ supplierException[0] = e;
+ }
+ };
+ Thread thread = new Thread(task, this.getClass().getName() + ".runOperationInThread");
+ thread.setDaemon(true);
+ thread.start();
+ BooleanSupplier done = () -> !thread.isAlive();
+ try {
+ SwtTestUtil.processEvents(timeoutMs, done);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed while running thread", e);
+ }
+ assertTrue(done.getAsBoolean());
+ if (supplierException[0] != null) {
+ throw new RuntimeException("Failed while running thread", supplierException[0]);
+ }
+ @SuppressWarnings("unchecked")
+ T result = (T) supplierValue[0];
+ return result;
+ }
+}
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java
index 6257137905c..dccc2589885 100644
--- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Shell.java
@@ -458,7 +458,7 @@ public void test_getImeInputMode() {
@Test
public void test_getLocation() {
//Setting location for Windows is not supported in GTK4
- if (isGTK4()) {
+ if (SwtTestUtil.isGTK4) {
return;
}
shell.setLocation(10,15);
@@ -1007,7 +1007,7 @@ public void test_Issue450_NoShellActivateOnSetFocus() {
@Override
public void test_setLocationLorg_eclipse_swt_graphics_Point() {
//Setting location for Windows is not supported in GTK4
- if (isGTK4()) {
+ if (SwtTestUtil.isGTK4) {
return;
}
super.test_setLocationLorg_eclipse_swt_graphics_Point();
@@ -1016,14 +1016,9 @@ public void test_setLocationLorg_eclipse_swt_graphics_Point() {
@Override
public void test_setLocationII() {
//Setting location for Windows is not supported in GTK4
- if (isGTK4()) {
+ if (SwtTestUtil.isGTK4) {
return;
}
super.test_setLocationII();
}
-
-public static boolean isGTK4() {
- String gtkVersion = System.getProperty("org.eclipse.swt.internal.gtk.version", "");
- return SwtTestUtil.isGTK && gtkVersion.startsWith("4");
-}
}
diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java
new file mode 100644
index 00000000000..adf595f842a
--- /dev/null
+++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Kichwa Coders Canada, Inc.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package clipboard;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+public interface ClipboardCommands extends Remote {
+ String PORT_MESSAGE = "ClipboardCommands Registry Port: ";
+ String ID = "ClipboardCommands";
+
+ void stop() throws RemoteException;
+
+ void setContents(String string) throws RemoteException;
+
+ void setFocus() throws RemoteException;
+
+ String getStringContents() throws RemoteException;
+
+ void waitUntilReady() throws RemoteException;
+}
diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommandsImpl.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommandsImpl.java
new file mode 100644
index 00000000000..3ada14d8622
--- /dev/null
+++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommandsImpl.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Kichwa Coders Canada, Inc.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package clipboard;
+
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.lang.reflect.InvocationTargetException;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Arrays;
+
+import javax.swing.SwingUtilities;
+
+public class ClipboardCommandsImpl extends UnicastRemoteObject implements ClipboardCommands {
+ private static final long serialVersionUID = 330098269086266134L;
+ private ClipboardTest clipboardTest;
+
+ protected ClipboardCommandsImpl(ClipboardTest clipboardTest) throws RemoteException {
+ super();
+ this.clipboardTest = clipboardTest;
+ }
+
+ @Override
+ public void waitUntilReady() throws RemoteException {
+ invokeAndWait(() -> {
+ clipboardTest.log("waitUntilReady()");
+ });
+ }
+
+ @Override
+ public void stop() throws RemoteException {
+ invokeAndWait(() -> {
+ clipboardTest.log("stop()");
+ clipboardTest.dispose();
+ });
+ }
+
+ @Override
+ public void setContents(String text) throws RemoteException {
+ invokeAndWait(() -> {
+ clipboardTest.log("setContents(\"" + text + "\")");
+ StringSelection selection = new StringSelection(text);
+ Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null);
+
+ });
+ }
+
+ @Override
+ public String getStringContents() throws RemoteException {
+ String[] data = new String[] { null };
+ invokeAndWait(() -> {
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ try {
+ data[0] = (String) clipboard.getData(DataFlavor.stringFlavor);
+ clipboardTest.log("getStringContents() returned " + data[0]);
+ } catch (Exception e) {
+ data[0] = null;
+ DataFlavor[] availableDataFlavors = clipboard.getAvailableDataFlavors();
+ clipboardTest.log("getStringContents() threw " + e.toString()
+ + " and returned null. The clipboard had availableDataFlavors = "
+ + Arrays.asList(availableDataFlavors));
+ }
+ });
+ return data[0];
+ }
+
+ @Override
+ public void setFocus() throws RemoteException {
+ invokeAndWait(() -> {
+ clipboardTest.log("setFocus()");
+ clipboardTest.requestFocus();
+ });
+ }
+
+ private void invokeAndWait(Runnable run) throws RemoteException {
+ try {
+ SwingUtilities.invokeAndWait(run);
+ } catch (InvocationTargetException | InterruptedException e) {
+ throw new RemoteException("Failed to run in Swing", e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java
new file mode 100644
index 00000000000..07279cbf111
--- /dev/null
+++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Kichwa Coders Canada, Inc.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package clipboard;
+
+import java.awt.BorderLayout;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.RMISocketFactory;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+
+import org.eclipse.swt.tests.junit.Test_org_eclipse_swt_dnd_Clipboard;
+
+/**
+ * Test program used by {@link Test_org_eclipse_swt_dnd_Clipboard}.
+ *
+ *
+ */
+@SuppressWarnings("serial")
+public class ClipboardTest extends JFrame {
+ private static final class LocalHostOnlySocketFactory extends RMISocketFactory {
+ @Override
+ public ServerSocket createServerSocket(int port) throws IOException {
+ return new ServerSocket(port, 50, InetAddress.getLoopbackAddress());
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ return new Socket(InetAddress.getLoopbackAddress(), port);
+ }
+ }
+
+ private static Registry rmiRegistry;
+ private JTextArea textArea;
+ private ClipboardCommands commands;
+
+ public ClipboardTest() throws RemoteException {
+ super("ClipboardTest");
+ commands = new ClipboardCommandsImpl(this);
+ rmiRegistry.rebind(ClipboardCommands.ID, commands);
+
+
+
+ textArea = new JTextArea(10, 40);
+ JScrollPane scrollPane = new JScrollPane(textArea);
+
+ JButton copyButton = new JButton("Copy");
+ JButton pasteButton = new JButton("Paste");
+
+ copyButton.addActionListener(e -> {
+ String text = textArea.getSelectedText();
+ if (text != null) {
+ StringSelection selection = new StringSelection(text);
+ Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null);
+ }
+ });
+
+ pasteButton.addActionListener(e -> {
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ try {
+ String data = (String) clipboard.getData(DataFlavor.stringFlavor);
+ textArea.insert(data, textArea.getCaretPosition());
+ } catch (Exception ex) {
+ JOptionPane.showMessageDialog(ClipboardTest.this, "Could not paste from clipboard", "Error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ });
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.add(copyButton);
+ buttonPanel.add(pasteButton);
+
+ add(scrollPane, BorderLayout.CENTER);
+ add(buttonPanel, BorderLayout.SOUTH);
+
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ pack();
+ setLocationRelativeTo(null); // Center on screen
+ setVisible(true);
+ }
+
+ public void log(String log) {
+ textArea.insert(log, textArea.getCaretPosition());
+ if (!log.endsWith("\n")) {
+ textArea.insert("\n", textArea.getCaretPosition());
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ System.setProperty("java.rmi.server.hostname", "127.0.0.1");
+
+ // Make sure RMI is localhost only
+ RMISocketFactory.setSocketFactory(new LocalHostOnlySocketFactory());
+ int chosenPort = getAvailablePort();
+ rmiRegistry = LocateRegistry.createRegistry(chosenPort);
+ System.out.println(ClipboardCommands.PORT_MESSAGE + chosenPort);
+
+
+
+ SwingUtilities.invokeLater(() -> {
+ try {
+ new ClipboardTest();
+ } catch (RemoteException e) {
+ System.err.println("Failed to start ClipboardTest");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ });
+ }
+
+ /**
+ * Because LocateRegistry requires reflection and/or using sun.* packages to get
+ * the running port, use ServerSocket to get a free port.
+ */
+ private static int getAvailablePort() throws IOException {
+ try (var ss = new java.net.ServerSocket(0)) {
+ return ss.getLocalPort();
+ }
+ }
+}