commit
c11eeb668c
@ -1,4 +1,9 @@
|
||||
FROM maven:3.8.1-adoptopenjdk-11
|
||||
MAINTAINER Goudham Suresh
|
||||
|
||||
RUN apt-get update && apt-get install -y gpg
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gpg \
|
||||
xvfb \
|
||||
libxrender1 libxtst6 libxi6 libxext6
|
||||
RUN /usr/bin/Xvfb :99 &
|
||||
RUN export DISPLAY=:99
|
@ -1,37 +1,68 @@
|
||||
[license]: https://img.shields.io/github/license/sgoudham/MyClipboard
|
||||
|
||||
[comment]: <> ([maven-central]: )
|
||||
|
||||
[comment]: <> ([build-status]:)
|
||||
|
||||
[comment]: <> ([codecov]:)
|
||||
[maven-central]: https://img.shields.io/maven-central/v/me.goudham/MyClipboard.svg?label=Maven%20Central
|
||||
[build-status]: https://goudham.me/jenkins/job/MyClipboard/job/release/badge/icon
|
||||
[codecov]: https://codecov.io/gh/sgoudham/MyClipboard/branch/main/graph/badge.svg?token=F4LKql7rIq
|
||||
[issues]: https://img.shields.io/github/issues/sgoudham/MyClipboard?label=issues
|
||||
[pull-requests]: https://img.shields.io/github/issues-pr/sgoudham/MyClipboard
|
||||
[fossa]: https://app.fossa.com/api/projects/git%2Bgithub.com%2Fsgoudham%2FMyClipboard.svg?type=shield
|
||||
|
||||
![fossa]
|
||||
![license]
|
||||
|
||||
[comment]: <> (![maven-central])
|
||||
|
||||
[comment]: <> (![build-status])
|
||||
|
||||
[comment]: <> (![codecov])
|
||||
![maven-central]
|
||||
![build-status]
|
||||
![codecov]
|
||||
![issues]
|
||||
![pull-requests]
|
||||
|
||||
# MyClipboard
|
||||
Utility Program to Access and Manipulate the Default System Clipboard
|
||||
Utility Library to Access and Manipulate the Default System Clipboard
|
||||
|
||||
# About
|
||||
The inspiration for this project came from my frustration of macOS not having clipboard history
|
||||
built-in unlike Windows. This library will allow you to access the system clipboard and manipulate it.
|
||||
|
||||
# Configuration
|
||||
TODO
|
||||
|
||||
# Windows / *Unix
|
||||
This approach differs from the macOS section below as Windows/*Unix properly notify the program with global clipboard events.
|
||||
This allows for a more event-driven approach as lostOwnership() is triggered whenever the clipboard has lost ownership - clipboard
|
||||
has new content within it - and the contents can be observed by multiple consumers.
|
||||
|
||||
# Mac OSX
|
||||
# macOS
|
||||
Unlike the aforementioned event-driven approach, macOS unfortunately is not very good at notifying the program if the
|
||||
system clipboard has changed. To query the system clipboard contents, we need to employ a polling schedule. I have chosen
|
||||
**200ms** to ensure that images and large files can be copied over as well as reducing the load on the CPU.
|
||||
|
||||
# Contributing
|
||||
TODO
|
||||
|
||||
# Installation
|
||||
|
||||
Latest Stable Version: ![maven-central]
|
||||
<p>Be sure to replace the <strong>VERSION</strong> key below with the one of the versions shown above!</p>
|
||||
|
||||
**Maven**
|
||||
```xml
|
||||
<!-- https://mvnrepository.com/artifact/me.goudham/MyClipboard -->
|
||||
<dependency>
|
||||
<groupId>me.goudham</groupId>
|
||||
<artifactId>MyClipboard</artifactId>
|
||||
<version>VERSION</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Gradle**
|
||||
```gradle
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/me.goudham/MyClipboard
|
||||
implementation group: 'me.goudham', name: 'MyClipboard', version: 'VERSION'
|
||||
}
|
||||
```
|
||||
|
||||
# License
|
||||
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fsgoudham%2FMyClipboard.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fsgoudham%2FMyClipboard?ref=badge_large)
|
@ -1,94 +0,0 @@
|
||||
package me.goudham;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import me.goudham.listener.ClipboardEventListener;
|
||||
|
||||
import static me.goudham.domain.Contents.IMAGE;
|
||||
import static me.goudham.domain.Contents.STRING;
|
||||
|
||||
abstract class ClipboardListener {
|
||||
final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
List<ClipboardEventListener> eventsListener = new ArrayList<>();
|
||||
private boolean imagesMonitored = true;
|
||||
private boolean textMonitored = true;
|
||||
|
||||
String getStringContent(Transferable clipboardContents) {
|
||||
String newContent = null;
|
||||
|
||||
try {
|
||||
newContent = (String) clipboardContents.getTransferData(STRING.getDataFlavor());
|
||||
} catch (UnsupportedFlavorException | IOException exp) {
|
||||
exp.printStackTrace();
|
||||
}
|
||||
|
||||
return newContent;
|
||||
}
|
||||
|
||||
BufferedImage getImageContent(Transferable clipboardContents) {
|
||||
BufferedImage bufferedImage = null;
|
||||
|
||||
try {
|
||||
bufferedImage = ClipboardUtils.convertToBufferedImage((Image) clipboardContents.getTransferData(IMAGE.getDataFlavor()));
|
||||
} catch (UnsupportedFlavorException | IOException exp) {
|
||||
exp.printStackTrace();
|
||||
}
|
||||
|
||||
return bufferedImage;
|
||||
}
|
||||
|
||||
void notifyStringEvent(String stringContent) {
|
||||
for (ClipboardEventListener clipboardEventListener : eventsListener) {
|
||||
clipboardEventListener.onCopyString(stringContent);
|
||||
}
|
||||
}
|
||||
|
||||
void notifyImageEvent(BufferedImage imageContent) {
|
||||
for (ClipboardEventListener clipboardEventListener : eventsListener) {
|
||||
clipboardEventListener.onCopyImage(imageContent);
|
||||
}
|
||||
}
|
||||
|
||||
void addEventListener(ClipboardEventListener clipboardEventListener) {
|
||||
if (!eventsListener.contains(clipboardEventListener)) {
|
||||
eventsListener.add(clipboardEventListener);
|
||||
}
|
||||
}
|
||||
|
||||
void removeEventListener(ClipboardEventListener clipboardEventListener) {
|
||||
eventsListener.remove(clipboardEventListener);
|
||||
}
|
||||
|
||||
void toggleTextMonitored() {
|
||||
this.textMonitored = !textMonitored;
|
||||
}
|
||||
|
||||
void toggleImagesMonitored() {
|
||||
this.imagesMonitored = !imagesMonitored;
|
||||
}
|
||||
|
||||
boolean isImagesMonitored() {
|
||||
return imagesMonitored;
|
||||
}
|
||||
|
||||
void setImagesMonitored(boolean imagesMonitored) {
|
||||
this.imagesMonitored = imagesMonitored;
|
||||
}
|
||||
|
||||
boolean isTextMonitored() {
|
||||
return textMonitored;
|
||||
}
|
||||
|
||||
void setTextMonitored(boolean textMonitored) {
|
||||
this.textMonitored = textMonitored;
|
||||
}
|
||||
|
||||
abstract void execute();
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package me.goudham;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import me.goudham.domain.ClipboardContent;
|
||||
import me.goudham.event.FileEvent;
|
||||
import me.goudham.event.ImageEvent;
|
||||
import me.goudham.event.TextEvent;
|
||||
|
||||
/**
|
||||
* Stores all eventListeners and produces notifications that are to be consumed by users using {@link MyClipboard}
|
||||
*/
|
||||
class EventManager {
|
||||
List<TextEvent> textEventListener = new ArrayList<>();
|
||||
List<ImageEvent> imageEventListener = new ArrayList<>();
|
||||
List<FileEvent> fileEventListener = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Adds a {@link TextEvent} to the {@code textEventListener}
|
||||
*
|
||||
* @param textEvent The {@link TextEvent} to be added
|
||||
*/
|
||||
void addEventListener(TextEvent textEvent) {
|
||||
textEventListener.add(textEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link ImageEvent} to the {@code imageEventListener}
|
||||
*
|
||||
* @param imageEvent The {@link ImageEvent} to be added
|
||||
*/
|
||||
void addEventListener(ImageEvent imageEvent) {
|
||||
imageEventListener.add(imageEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link FileEvent} to the {@code fileEventListener}
|
||||
*
|
||||
* @param fileEvent The {@link FileEvent} to be added
|
||||
*/
|
||||
void addEventListener(FileEvent fileEvent) {
|
||||
fileEventListener.add(fileEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link TextEvent} from the {@code textEventListener}
|
||||
*
|
||||
* @param textEvent The {@link TextEvent} to be removed
|
||||
*/
|
||||
void removeEventListener(TextEvent textEvent) {
|
||||
textEventListener.remove(textEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link ImageEvent} from the {@code imageEventListener}
|
||||
*
|
||||
* @param imageEvent The {@link ImageEvent} to be removed
|
||||
*/
|
||||
void removeEventListener(ImageEvent imageEvent) {
|
||||
imageEventListener.remove(imageEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link FileEvent} from the {@code fileEventListener}
|
||||
*
|
||||
* @param fileEvent The {@link FileEvent} to be removed
|
||||
*/
|
||||
void removeEventListener(FileEvent fileEvent) {
|
||||
fileEventListener.remove(fileEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces {@link String} change notifications to all consumers listening
|
||||
*
|
||||
* @param clipboardContent The previous clipboard contents
|
||||
* @param stringContent {@link String} to be consumed
|
||||
*/
|
||||
void notifyTextEvent(ClipboardContent clipboardContent, String stringContent) {
|
||||
for (TextEvent textEvent : textEventListener) {
|
||||
textEvent.onCopyText(clipboardContent, stringContent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces {@link BufferedImage} change notifications to all consumers listening
|
||||
*
|
||||
* @param clipboardContent The previous clipboard contents
|
||||
* @param imageContent {@link BufferedImage} to be consumed
|
||||
*/
|
||||
void notifyImageEvent(ClipboardContent clipboardContent, BufferedImage imageContent) {
|
||||
for (ImageEvent imageEvent : imageEventListener) {
|
||||
imageEvent.onCopyImage(clipboardContent, imageContent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces {@link List} of {@link File} change notifications to all consumers listening
|
||||
*
|
||||
* @param clipboardContent The previous clipboard contents
|
||||
* @param fileContent {@link List} of {@link File} to be consumed
|
||||
*/
|
||||
void notifyFilesEvent(ClipboardContent clipboardContent, List<File> fileContent) {
|
||||
for (FileEvent fileEvent : fileEventListener) {
|
||||
fileEvent.onCopyFiles(clipboardContent, fileContent);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
package me.goudham;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import me.goudham.domain.ClipboardContent;
|
||||
import me.goudham.domain.GenericClipboardContent;
|
||||
import me.goudham.domain.MyBufferedImage;
|
||||
import me.goudham.domain.TransferableFile;
|
||||
import me.goudham.domain.TransferableImage;
|
||||
|
||||
import static java.lang.Thread.sleep;
|
||||
import static me.goudham.Contents.FILE;
|
||||
import static me.goudham.Contents.IMAGE;
|
||||
import static me.goudham.Contents.TEXT;
|
||||
|
||||
/**
|
||||
* Clipboard for the macOS operating system
|
||||
*/
|
||||
class MacClipboard extends SystemClipboard implements Runnable {
|
||||
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
|
||||
GenericClipboardContent<?>[] genericClipboardContents;
|
||||
private boolean listening = false;
|
||||
|
||||
MacClipboard() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if {@link String} is within the clipboard and changed
|
||||
*
|
||||
* @param newClipboardContents {@link Transferable} containing new clipboard contents
|
||||
* @param genericClipboardContents {@link GenericClipboardContent[]} of Unknown {@link Class} containing previous contents
|
||||
*/
|
||||
void checkText(Transferable newClipboardContents, GenericClipboardContent<?>[] genericClipboardContents) {
|
||||
if (TEXT.isAvailable(clipboard) && !FILE.isAvailable(clipboard)) {
|
||||
String newStringContent = clipboardUtils.getStringContent(newClipboardContents);
|
||||
if (newStringContent == null) return;
|
||||
|
||||
if (isTextMonitored()) {
|
||||
Object oldContent = genericClipboardContents[0].getOldContent();
|
||||
if (!newStringContent.equals(oldContent)) {
|
||||
ClipboardContent clipboardContent = clipboardUtils.getClipboardContent(oldContent);
|
||||
eventManager.notifyTextEvent(clipboardContent, newStringContent);
|
||||
}
|
||||
}
|
||||
|
||||
genericClipboardContents[0].setOldContent(newStringContent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if {@link java.awt.Image} is within the clipboard and changed
|
||||
*
|
||||
* @param newClipboardContents {@link Transferable} containing new clipboard contents
|
||||
* @param genericClipboardContents {@link GenericClipboardContent[]} of Unknown {@link Class} containing previous contents
|
||||
*/
|
||||
void checkImages(Transferable newClipboardContents, GenericClipboardContent<?>[] genericClipboardContents) {
|
||||
if (IMAGE.isAvailable(clipboard)) {
|
||||
MyBufferedImage bufferedImageContent = clipboardUtils.getImageContent(newClipboardContents);
|
||||
if (bufferedImageContent.getBufferedImage() == null) return;
|
||||
|
||||
if (isImageMonitored()) {
|
||||
if (!bufferedImageContent.equals(genericClipboardContents[0].getOldContent())) {
|
||||
ClipboardContent clipboardContent = clipboardUtils.getClipboardContent(genericClipboardContents[0].getOldContent());
|
||||
eventManager.notifyImageEvent(clipboardContent, bufferedImageContent.getBufferedImage());
|
||||
}
|
||||
}
|
||||
|
||||
genericClipboardContents[0].setOldContent(bufferedImageContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if {@link java.util.List} of {@link java.io.File} is within the clipboard and changed
|
||||
*
|
||||
* @param newClipboardContents {@link Transferable} containing new clipboard contents
|
||||
* @param genericClipboardContents {@link GenericClipboardContent[]} of Unknown {@link Class} containing previous contents
|
||||
*/
|
||||
void checkFiles(Transferable newClipboardContents, GenericClipboardContent<?>[] genericClipboardContents) {
|
||||
if (FILE.isAvailable(clipboard)) {
|
||||
List<File> fileListContent = clipboardUtils.getFileContent(newClipboardContents);
|
||||
if (fileListContent == null) return;
|
||||
|
||||
if (isFileMonitored()) {
|
||||
if (!fileListContent.equals(genericClipboardContents[0].getOldContent())) {
|
||||
ClipboardContent clipboardContent = clipboardUtils.getClipboardContent(genericClipboardContents[0].getOldContent());
|
||||
eventManager.notifyFilesEvent(clipboardContent, fileListContent);
|
||||
}
|
||||
}
|
||||
|
||||
genericClipboardContents[0].setOldContent(fileListContent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void startListening() {
|
||||
if (!listening) {
|
||||
listening = true;
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void stopListening() {
|
||||
if (listening) {
|
||||
scheduledExecutorService.shutdown();
|
||||
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
try {
|
||||
sleep(200);
|
||||
} catch (InterruptedException ie) {
|
||||
logger.error("Exception Thrown As Thread Cannot Sleep", ie);
|
||||
}
|
||||
|
||||
listening = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void insert(String stringContent) {
|
||||
insertAndNotify(stringContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
void insert(Image imageContent) {
|
||||
insertAndNotify(imageContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
void insert(List<File> fileContent) {
|
||||
insertAndNotify(fileContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
void insertAndNotify(String stringContent) {
|
||||
try {
|
||||
sleep(200);
|
||||
} catch (InterruptedException ie) {
|
||||
logger.error("Exception Thrown As Thread Cannot Sleep", ie);
|
||||
}
|
||||
|
||||
clipboard.setContents(new StringSelection(stringContent), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
void insertAndNotify(Image imageContent) {
|
||||
try {
|
||||
sleep(200);
|
||||
} catch (InterruptedException ie) {
|
||||
logger.error("Exception Thrown As Thread Cannot Sleep", ie);
|
||||
}
|
||||
|
||||
clipboard.setContents(new TransferableImage(imageContent), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
void insertAndNotify(List<File> fileContent) {
|
||||
try {
|
||||
sleep(200);
|
||||
} catch (InterruptedException ie) {
|
||||
logger.error("Exception Thrown As Thread Cannot Sleep", ie);
|
||||
}
|
||||
|
||||
clipboard.setContents(new TransferableFile(fileContent), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Transferable newClipboardContents = clipboard.getContents(null);
|
||||
checkText(newClipboardContents, genericClipboardContents);
|
||||
checkImages(newClipboardContents, genericClipboardContents);
|
||||
checkFiles(newClipboardContents, genericClipboardContents);
|
||||
} catch (IllegalStateException ise) {
|
||||
logger.error("Exception Thrown As Clipboard Cannot Be Accessed", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point for {@link MacClipboard}
|
||||
* <p>Retrieves thread from {@link Executors#newSingleThreadScheduledExecutor()} and executes code on a fixed delay</p>
|
||||
*/
|
||||
@Override
|
||||
void execute() {
|
||||
Transferable oldClipboardContents = clipboard.getContents(null);
|
||||
genericClipboardContents = new GenericClipboardContent[] { clipboardUtils.getGenericClipboardContents(oldClipboardContents) };
|
||||
scheduledExecutorService.scheduleAtFixedRate(this, 0, 200, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package me.goudham;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import me.goudham.domain.MyClipboardContent;
|
||||
|
||||
import static me.goudham.domain.Contents.IMAGE;
|
||||
import static me.goudham.domain.Contents.STRING;
|
||||
|
||||
class MacClipboardListener extends ClipboardListener {
|
||||
|
||||
MacClipboardListener() { }
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
Transferable oldClipboardContents = clipboard.getContents(null);
|
||||
final MyClipboardContent<?>[] myClipboardContents = new MyClipboardContent[] { ClipboardUtils.getClipboardContents(oldClipboardContents, clipboard) };
|
||||
|
||||
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
|
||||
executor.scheduleAtFixedRate(() -> {
|
||||
Transferable newClipboardContents = clipboard.getContents(null);
|
||||
|
||||
if (isTextMonitored()) {
|
||||
if (STRING.isAvailable(clipboard)) {
|
||||
String newStringContent = getStringContent(newClipboardContents);
|
||||
if (!newStringContent.equals(myClipboardContents[0].getOldContent())) {
|
||||
notifyStringEvent(newStringContent);
|
||||
myClipboardContents[0].setOldContent(newStringContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isImagesMonitored()) {
|
||||
if (IMAGE.isAvailable(clipboard)) {
|
||||
BufferedImage bufferedImageContent = getImageContent(newClipboardContents);
|
||||
Dimension newDimensionContent = new Dimension(bufferedImageContent.getWidth(), bufferedImageContent.getHeight());
|
||||
if (!newDimensionContent.equals(myClipboardContents[0].getOldContent())) {
|
||||
notifyImageEvent(bufferedImageContent);
|
||||
myClipboardContents[0].setOldContent(newDimensionContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0, 350, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
@ -1,100 +1,286 @@
|
||||
package me.goudham;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import me.goudham.domain.ClipboardContent;
|
||||
import me.goudham.event.FileEvent;
|
||||
import me.goudham.event.ImageEvent;
|
||||
import me.goudham.event.TextEvent;
|
||||
import me.goudham.exception.UnsupportedSystemException;
|
||||
import me.goudham.listener.ClipboardEventListener;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Entry Class for User to interact with the System Clipboard
|
||||
*
|
||||
* The abstract class {@link ClipboardListener} is responsible for handling all operations
|
||||
* <p>
|
||||
* The abstract class {@link SystemClipboard} is responsible for handling all operations
|
||||
*/
|
||||
public class MyClipboard {
|
||||
private final @NotNull ClipboardListener clipboardListener;
|
||||
private @NotNull SystemClipboard systemClipboard;
|
||||
private static SystemUtils systemUtils = new SystemUtils();
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link MyClipboard}
|
||||
*
|
||||
* @param clipboardListener The underlying {@link ClipboardListener}
|
||||
* @param systemClipboard The underlying {@link SystemClipboard}
|
||||
*/
|
||||
private MyClipboard(@NotNull ClipboardListener clipboardListener) {
|
||||
this.clipboardListener = clipboardListener;
|
||||
this.clipboardListener.execute();
|
||||
private MyClipboard(@NotNull SystemClipboard systemClipboard) {
|
||||
this.systemClipboard = systemClipboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link MyClipboard} with an instance of {@link ClipboardListener} dependant on the OS
|
||||
* <p>A {@link WindowsOrUnixClipboardListener} or {@link MacClipboardListener} can be created</p>
|
||||
* Creates an instance of {@link MyClipboard} with an instance of {@link SystemClipboard} dependent on the OS
|
||||
* <p>A {@link WindowsOrUnixClipboard} or {@link MacClipboard} can be created</p>
|
||||
*
|
||||
* @return {@link MyClipboard}
|
||||
* @throws UnsupportedSystemException If {@link MyClipboard} detects an operating system which is not Mac or Windows/*Unix
|
||||
*/
|
||||
public static MyClipboard getSystemClipboard() throws UnsupportedSystemException {
|
||||
ClipboardListener clipboardListener;
|
||||
SystemClipboard systemClipboard;
|
||||
|
||||
if (isMac()) {
|
||||
clipboardListener = new MacClipboardListener();
|
||||
} else if (isWindows() || isUnix()) {
|
||||
clipboardListener = new WindowsOrUnixClipboardListener();
|
||||
if (systemUtils.isMac()) {
|
||||
systemClipboard = new MacClipboard();
|
||||
} else if (systemUtils.isWindows() || systemUtils.isUnix()) {
|
||||
systemClipboard = new WindowsOrUnixClipboard();
|
||||
} else {
|
||||
throw new UnsupportedSystemException("Your Operating System: " + System.getProperty("os.name") + "is not supported");
|
||||
throw new UnsupportedSystemException("Your Operating System: '" + System.getProperty("os.name") + "' is not supported");
|
||||
}
|
||||
|
||||
return new MyClipboard(clipboardListener);
|
||||
return new MyClipboard(systemClipboard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link ClipboardEventListener} to the underlying {@link ClipboardListener}
|
||||
* Allows the correct {@link SystemClipboard} to start listening for clipboard changes
|
||||
*
|
||||
* @param clipboardEventListener The {@link ClipboardEventListener} to be added
|
||||
* @see WindowsOrUnixClipboard#startListening()
|
||||
* @see MacClipboard#startListening()
|
||||
*/
|
||||
public void addEventListener(ClipboardEventListener clipboardEventListener) {
|
||||
clipboardListener.addEventListener(clipboardEventListener);
|
||||
public void startListening() {
|
||||
systemClipboard.startListening();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link ClipboardEventListener} from the underlying {@link ClipboardListener}
|
||||
* Stops the correct {@link SystemClipboard} to stop listening for clipboard changes
|
||||
*
|
||||
* @param clipboardEventListener The {@link ClipboardEventListener} to be removed
|
||||
* @see WindowsOrUnixClipboard#stopListening()
|
||||
* @see MacClipboard#stopListening()
|
||||
*/
|
||||
public void removeEventListener(ClipboardEventListener clipboardEventListener) {
|
||||
clipboardListener.removeEventListener(clipboardEventListener);
|
||||
public void stopListening() {
|
||||
systemClipboard.stopListening();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given {@link String} into the system clipboard
|
||||
* <p>
|
||||
* Due to the underlying {@link MacClipboard#insert(String)} implementation, inserting
|
||||
* clipboard contents will always result in event notifications being sent
|
||||
*
|
||||
* @param stringContent The given {@link String} to insert
|
||||
* @see WindowsOrUnixClipboard#insert(String)
|
||||
* @see MacClipboard#insert(String)
|
||||
*/
|
||||
public void insert(String stringContent) {
|
||||
systemClipboard.insert(stringContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given {@link Image} into the system clipboard
|
||||
* <p>
|
||||
* Due to the underlying {@link MacClipboard#insert(Image)} implementation, inserting
|
||||
* clipboard contents will always result in event notifications being sent
|
||||
*
|
||||
* @param imageContent The given {@link Image} to insert
|
||||
* @see WindowsOrUnixClipboard#insert(Image)
|
||||
* @see MacClipboard#insert(Image)
|
||||
*/
|
||||
public void insert(Image imageContent) {
|
||||
systemClipboard.insert(imageContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given {@link List} of {@link File} into the system clipboard
|
||||
* <p>
|
||||
* Due to the underlying {@link MacClipboard#insert(List)} implementation, inserting
|
||||
* clipboard contents will always result in event notifications being sent
|
||||
*
|
||||
* @param fileContent The given {@link List} of {@link File} to insert
|
||||
* @see WindowsOrUnixClipboard#insert(List)
|
||||
* @see MacClipboard#insert(List)
|
||||
*/
|
||||
public void insert(List<File> fileContent) {
|
||||
systemClipboard.insert(fileContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given {@link String} into the system clipboard
|
||||
* and notify the user about the new contents within the clipboard
|
||||
*
|
||||
* @param stringContent The given {@link String} to insert
|
||||
* @see WindowsOrUnixClipboard#insertAndNotify(String)
|
||||
* @see MacClipboard#insertAndNotify(String)
|
||||
*/
|
||||
public void insertAndNotify(String stringContent) {
|
||||
systemClipboard.insertAndNotify(stringContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given {@link Image} into the system clipboard
|
||||
* and notify the user about the new contents within the clipboard
|
||||
*
|
||||
* @param imageContent The given {@link Image} to insert
|
||||
* @see WindowsOrUnixClipboard#insertAndNotify(Image)
|
||||
* @see MacClipboard#insertAndNotify(Image)
|
||||
*/
|
||||
public void insertAndNotify(Image imageContent) {
|
||||
systemClipboard.insertAndNotify(imageContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given {@link List} of {@link File} into the system clipboard
|
||||
* and notify the user about the new contents within the clipboard
|
||||
*
|
||||
* @param fileContent The given {@link List} of {@link File} to insert
|
||||
* @see WindowsOrUnixClipboard#insertAndNotify(List)
|
||||
* @see MacClipboard#insertAndNotify(List)
|
||||
*/
|
||||
public void insertAndNotify(List<File> fileContent) {
|
||||
systemClipboard.insertAndNotify(fileContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current clipboard contents, {@code null} if clipboard has no contents
|
||||
*
|
||||
* @return {@link ClipboardContent} containing either {@code String}, {@code BufferedImage} or {@code List<File>}
|
||||
* @see SystemClipboard#getContents()
|
||||
*/
|
||||
public ClipboardContent getContents() {
|
||||
return systemClipboard.getContents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link TextEvent} to the underlying {@link SystemClipboard}
|
||||
*
|
||||
* @param textEvent The {@link TextEvent} to be added
|
||||
* @see EventManager#addEventListener(TextEvent)
|
||||
*/
|
||||
public void addEventListener(TextEvent textEvent) {
|
||||
systemClipboard.getEventManager().addEventListener(textEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link ImageEvent} to the underlying {@link SystemClipboard}
|
||||
*
|
||||
* @param imageEvent The {@link ImageEvent} to be added
|
||||
* @see EventManager#addEventListener(ImageEvent)
|
||||
*/
|
||||
public void addEventListener(ImageEvent imageEvent) {
|
||||
systemClipboard.getEventManager().addEventListener(imageEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link FileEvent} to the underlying {@link SystemClipboard}
|
||||
*
|
||||
* @param fileEvent The {@link FileEvent} to be added
|
||||
* @see EventManager#addEventListener(FileEvent)
|
||||
*/
|
||||
public void addEventListener(FileEvent fileEvent) {
|
||||
systemClipboard.getEventManager().addEventListener(fileEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link TextEvent} from the underlying {@link SystemClipboard}
|
||||
*
|
||||
* @param textEvent The {@link TextEvent} to be removed
|
||||
* @see EventManager#removeEventListener(TextEvent)
|
||||
*/
|
||||
public void removeEventListener(TextEvent textEvent) {
|
||||
systemClipboard.getEventManager().removeEventListener(textEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link ImageEvent} from the underlying {@link SystemClipboard}
|
||||
*
|
||||
* @param imageEvent The {@link ImageEvent} to be removed
|
||||
* @see EventManager#removeEventListener(ImageEvent)
|
||||
*/
|
||||
public void removeEventListener(ImageEvent imageEvent) {
|
||||
systemClipboard.getEventManager().removeEventListener(imageEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link FileEvent} from the underlying {@link SystemClipboard}
|
||||
*
|
||||
* @param fileEvent The {@link FileEvent} to be removed
|
||||
* @see EventManager#removeEventListener(FileEvent)
|
||||
*/
|
||||
public void removeEventListener(FileEvent fileEvent) {
|
||||
systemClipboard.getEventManager().removeEventListener(fileEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the current value of text monitoring, the default value is set to {@code True}
|
||||
*
|
||||
* @see SystemClipboard#toggleTextMonitored()
|
||||
*/
|
||||
public void toggleTextMonitored() {
|
||||
clipboardListener.toggleTextMonitored();
|
||||
systemClipboard.toggleTextMonitored();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the current value of image monitoring, the default value is set to {@code True}
|
||||
*
|
||||
* @see SystemClipboard#toggleImageMonitored()
|
||||
*/
|
||||
public void toggleImagesMonitored() {
|
||||
clipboardListener.toggleImagesMonitored();
|
||||
systemClipboard.toggleImageMonitored();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the current value of file monitoring, the default value is set to {@code True}
|
||||
*
|
||||
* @see SystemClipboard#toggleFileMonitored()
|
||||
*/
|
||||
public void toggleFilesMonitored() {
|
||||
systemClipboard.toggleFileMonitored();
|
||||
}
|
||||
|
||||
public boolean isImagesMonitored() {
|
||||
return clipboardListener.isImagesMonitored();
|
||||
public boolean isImageMonitored() {
|
||||
return systemClipboard.isImageMonitored();
|
||||
}
|
||||
|
||||
public void setImagesMonitored(boolean imagesMonitored) {
|
||||
clipboardListener.setImagesMonitored(imagesMonitored);
|
||||
public void setImageMonitored(boolean imagesMonitored) {
|
||||
systemClipboard.setImageMonitored(imagesMonitored);
|
||||
}
|
||||
|
||||
public boolean isTextMonitored() {
|
||||
return clipboardListener.isTextMonitored();
|
||||
return systemClipboard.isTextMonitored();
|
||||
}
|
||||
|
||||
public void setTextMonitored(boolean textMonitored) {
|
||||
clipboardListener.setTextMonitored(textMonitored);
|
||||
systemClipboard.setTextMonitored(textMonitored);
|
||||
}
|
||||
|
||||
public boolean isFileMonitored() {
|
||||
return systemClipboard.isFileMonitored();
|
||||
}
|
||||
|
||||
public void setFileMonitored(boolean fileMonitored) {
|
||||
systemClipboard.setFileMonitored(fileMonitored);
|
||||
}
|
||||
|
||||
public @NotNull SystemClipboard getClipboardListener() {
|
||||
return systemClipboard;
|
||||
}
|
||||
|
||||
private static boolean isMac() {
|
||||
return SystemUtils.IS_OS_MAC;
|
||||
public void setClipboardListener(@NotNull SystemClipboard systemClipboard) {
|
||||
this.systemClipboard = systemClipboard;
|
||||
}
|
||||
|
||||
private static boolean isUnix() {
|
||||
return SystemUtils.IS_OS_UNIX || SystemUtils.IS_OS_LINUX;
|
||||
static SystemUtils getSystemUtils() {
|
||||
return systemUtils;
|
||||
}
|
||||
|
||||
private static boolean isWindows() {
|
||||
return SystemUtils.IS_OS_WINDOWS;
|
||||
static void setSystemUtils(SystemUtils systemUtils) {
|
||||
MyClipboard.systemUtils = systemUtils;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,200 @@
|
||||
package me.goudham;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import me.goudham.domain.ClipboardContent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Abstract class containing common operations between {@link WindowsOrUnixClipboard} and
|
||||
* {@link MacClipboard}
|
||||
*/
|
||||
abstract class SystemClipboard {
|
||||
Clipboard clipboard;
|
||||
Logger logger;
|
||||
EventManager eventManager;
|
||||
ClipboardUtils clipboardUtils;
|
||||
private boolean imageMonitored = true;
|
||||
private boolean textMonitored = true;
|
||||
private boolean fileMonitored = true;
|
||||
|
||||
SystemClipboard() {
|
||||
clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
logger = LoggerFactory.getLogger(getClass());
|
||||
eventManager = new EventManager();
|
||||
clipboardUtils = new ClipboardUtils();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point of execution for the correct {@link SystemClipboard}
|
||||
*
|
||||
* @see WindowsOrUnixClipboard#execute()
|
||||
* @see MacClipboard#execute()
|
||||
*/
|
||||
abstract void execute();
|
||||
|
||||
/**
|
||||
* Allows the correct {@link SystemClipboard} to start listening for clipboard changes
|
||||
*
|
||||
* @see WindowsOrUnixClipboard#startListening()
|
||||
* @see MacClipboard#startListening()
|
||||
*/
|
||||
abstract void startListening();
|
||||
|
||||
/**
|
||||
* Stops the correct {@link SystemClipboard} to stop listening for clipboard changes
|
||||
*
|
||||
* @see WindowsOrUnixClipboard#stopListening()
|
||||
* @see MacClipboard#stopListening()
|
||||
*/
|
||||
abstract void stopListening();
|
||||
|
||||
/**
|
||||
* Insert the given {@link String} into the system clipboard
|
||||
*
|
||||
* @param stringContent The given {@link String} to insert
|
||||
* @see WindowsOrUnixClipboard#insert(String)
|
||||
* @see MacClipboard#insert(String)
|
||||
*/
|
||||
abstract void insert(String stringContent);
|
||||
|
||||
/**
|
||||
* Insert the given {@link Image} into the system clipboard
|
||||
*
|
||||
* @param imageContent The given {@link Image} to insert
|
||||
* @see WindowsOrUnixClipboard#insert(Image)
|
||||
* @see MacClipboard#insert(Image)
|
||||
*/
|
||||
abstract void insert(Image imageContent);
|
||||
|
||||
/**
|
||||
* Insert the given {@link List} of {@link File} into the system clipboard
|
||||
*
|
||||
* @param fileContent The given {@link List} of {@link File} to insert
|
||||
* @see WindowsOrUnixClipboard#insert(List)
|
||||
* @see MacClipboard#insert(List)
|
||||
*/
|
||||
abstract void insert(List<File> fileContent);
|
||||
|
||||
/**
|
||||
* Insert the given {@link String} into the system clipboard
|
||||
* and notify the user about the new contents within the clipboard
|
||||
*
|
||||
* @param stringContent The given {@link String} to insert
|
||||
* @see WindowsOrUnixClipboard#insertAndNotify(String)
|
||||
* @see MacClipboard#insertAndNotify(String)
|
||||
*/
|
||||
abstract void insertAndNotify(String stringContent);
|
||||
|
||||
/**
|
||||
* Insert the given {@link Image} into the system clipboard
|
||||
* and notify the user about the new contents within the clipboard
|
||||
*
|
||||
* @param imageContent The given {@link Image} to insert
|
||||
* @see WindowsOrUnixClipboard#insertAndNotify(Image)
|
||||
* @see MacClipboard#insertAndNotify(Image)
|
||||
*/
|
||||
abstract void insertAndNotify(Image imageContent);
|
||||
|
||||
/**
|
||||
* Insert the given {@link List} of {@link File} into the system clipboard
|
||||
* and notify the user about the new contents within the clipboard
|
||||
*
|
||||
* @param fileContent The given {@link List} of {@link File} to insert
|
||||
* @see WindowsOrUnixClipboard#insertAndNotify(List)
|
||||
* @see MacClipboard#insertAndNotify(List)
|
||||
*/
|
||||
abstract void insertAndNotify(List<File> fileContent);
|
||||
|
||||
/**
|
||||
* Returns the current clipboard contents, {@code null} if clipboard has no contents
|
||||
*
|
||||
* @return {@link ClipboardContent} containing either {@code String}, {@code BufferedImage} or {@code List<File>}
|
||||
*/
|
||||
ClipboardContent getContents() {
|
||||
Transferable clipboardContents = clipboard.getContents(null);
|
||||
return clipboardContents == null ? null : clipboardUtils.getClipboardContent(clipboardContents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the current value of text monitoring, the default value is set to {@code True}
|
||||
*/
|
||||
void toggleTextMonitored() {
|
||||
this.textMonitored = !textMonitored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the current value of image monitoring, the default value is set to {@code True}
|
||||
*/
|
||||
void toggleImageMonitored() {
|
||||
this.imageMonitored = !imageMonitored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the current value of file monitoring, the default value is set to {@code True}
|
||||
*/
|
||||
void toggleFileMonitored() {
|
||||
this.fileMonitored = !fileMonitored;
|
||||
}
|
||||
|
||||
Clipboard getClipboard() {
|
||||
return clipboard;
|
||||
}
|
||||
|
||||
void setClipboard(Clipboard clipboard) {
|
||||
this.clipboard = clipboard;
|
||||
}
|
||||
|
||||
Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
void setLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
EventManager getEventManager() {
|
||||
return eventManager;
|
||||
}
|
||||
|
||||
void setEventManager(EventManager eventManager) {
|
||||
this.eventManager = eventManager;
|
||||
}
|
||||
|
||||
ClipboardUtils getClipboardUtils() {
|
||||
return clipboardUtils;
|
||||
}
|
||||
|
||||
void setClipboardUtils(ClipboardUtils clipboardUtils) {
|
||||
this.clipboardUtils = clipboardUtils;
|
||||
}
|
||||
|
||||
boolean isImageMonitored() {
|
||||
return imageMonitored;
|
||||
}
|
||||
|
||||
void setImageMonitored(boolean imageMonitored) {
|
||||
this.imageMonitored = imageMonitored;
|
||||
}
|
||||
|
||||
boolean isTextMonitored() {
|
||||
return textMonitored;
|
||||
}
|
||||
|
||||
void setTextMonitored(boolean textMonitored) {
|
||||
this.textMonitored = textMonitored;
|
||||
}
|
||||
|
||||
boolean isFileMonitored() {
|
||||
return fileMonitored;
|
||||
}
|
||||
|
||||
void setFileMonitored(boolean fileMonitored) {
|
||||
this.fileMonitored = fileMonitored;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package me.goudham;
|
||||
|
||||
class SystemUtils {
|
||||
boolean isMac() {
|
||||
return org.apache.commons.lang3.SystemUtils.IS_OS_MAC;
|
||||
}
|
||||
|
||||
boolean isUnix() {
|
||||
return org.apache.commons.lang3.SystemUtils.IS_OS_UNIX || org.apache.commons.lang3.SystemUtils.IS_OS_LINUX;
|
||||
}
|
||||
|
||||
boolean isWindows() {
|
||||
return org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
package me.goudham;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.ClipboardOwner;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import me.goudham.domain.ClipboardContent;
|
||||
import me.goudham.domain.MyBufferedImage;
|
||||
import me.goudham.domain.TransferableFile;
|
||||
import me.goudham.domain.TransferableImage;
|
||||
|
||||
import static java.lang.Thread.sleep;
|
||||
import static me.goudham.Contents.FILE;
|
||||
import static me.goudham.Contents.IMAGE;
|
||||
import static me.goudham.Contents.TEXT;
|
||||
|
||||
/**
|
||||
* Clipboard for Windows and Unix operating systems
|
||||
*/
|
||||
class WindowsOrUnixClipboard extends SystemClipboard implements Runnable, ClipboardOwner {
|
||||
private ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
private boolean listening = false;
|
||||
|
||||
WindowsOrUnixClipboard() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lostOwnership(Clipboard oldClipboard, Transferable oldClipboardContents) {
|
||||
try {
|
||||
sleep(200);
|
||||
Transferable newClipboardContents = oldClipboard.getContents(null);
|
||||
processContents(oldClipboard, oldClipboardContents, newClipboardContents);
|
||||
regainOwnership(oldClipboard, newClipboardContents);
|
||||
} catch (IllegalStateException | InterruptedException exp) {
|
||||
logger.error("Exception Thrown When Processing Clipboard Changes", exp);
|
||||
executorService.submit(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect changes from the given {@link Clipboard} and send event notifications to all users listening
|
||||
*
|
||||
* @param oldClipboard The clipboard that is no longer owned
|
||||
* @param oldClipboardContents The old contents of the clipboard
|
||||
* @param newClipboardContents The new contents of the clipboard
|
||||
*/
|
||||
void processContents(Clipboard oldClipboard, Transferable oldClipboardContents, Transferable newClipboardContents) {
|
||||
ClipboardContent clipboardContent = clipboardUtils.getClipboardContent(oldClipboardContents);
|
||||
|
||||
if (isTextMonitored()) {
|
||||
if (TEXT.isAvailable(oldClipboard) && !FILE.isAvailable(oldClipboard)) {
|
||||
String stringContent = clipboardUtils.getStringContent(newClipboardContents);
|
||||
if (!stringContent.equals(clipboardContent.getText())) {
|
||||
eventManager.notifyTextEvent(clipboardContent, stringContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isImageMonitored()) {
|
||||
if (IMAGE.isAvailable(oldClipboard)) {
|
||||
MyBufferedImage bufferedImage = clipboardUtils.getImageContent(newClipboardContents);
|
||||
MyBufferedImage oldBufferedImage = new MyBufferedImage(clipboardContent.getImage());
|
||||
if (!bufferedImage.equals(oldBufferedImage)) {
|
||||
eventManager.notifyImageEvent(clipboardContent, bufferedImage.getBufferedImage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isFileMonitored()) {
|
||||
if (FILE.isAvailable(oldClipboard) && !IMAGE.isAvailable(oldClipboard)) {
|
||||
List<File> fileList = clipboardUtils.getFileContent(newClipboardContents);
|
||||
if (!fileList.equals(clipboardContent.getFiles())) {
|
||||
eventManager.notifyFilesEvent(clipboardContent, fileList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void regainOwnership(Clipboard clipboard, Transferable newClipboardContents) {
|
||||
clipboard.setContents(newClipboardContents, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
void startListening() {
|
||||
if (!listening) {
|
||||
listening = true;
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void stopListening() {
|
||||
if (listening) {
|
||||
executorService.shutdown();
|
||||
executorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
try {
|
||||
sleep(200);
|
||||
} catch (InterruptedException ie) {
|
||||
logger.error("Exception Thrown As Thread Cannot Sleep", ie);
|
||||
}
|
||||
|
||||
listening = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void insert(String stringContent) {
|
||||
setContents(new StringSelection(stringContent));
|
||||
}
|
||||
|
||||
@Override
|
||||
void insert(Image imageContent) {
|
||||
setContents(new TransferableImage(imageContent));
|
||||
}
|
||||
|
||||
@Override
|
||||
void insert(List<File> fileContent) {
|
||||
setContents(new TransferableFile(fileContent));
|
||||
}
|
||||
|
||||
@Override
|
||||
void insertAndNotify(String stringContent) {
|
||||
Transferable currentClipboardContents = clipboard.getContents(null);
|
||||
insert(stringContent);
|
||||
lostOwnership(clipboard, currentClipboardContents);
|
||||
}
|
||||
|
||||
@Override
|
||||
void insertAndNotify(Image imageContent) {
|
||||
Transferable currentClipboardContents = clipboard.getContents(null);
|
||||
insert(imageContent);
|
||||
lostOwnership(clipboard, currentClipboardContents);
|
||||
}
|
||||
|
||||
@Override
|
||||
void insertAndNotify(List<File> fileContent) {
|
||||
Transferable currentClipboardContents = clipboard.getContents(null);
|
||||
insert(fileContent);
|
||||
lostOwnership(clipboard, currentClipboardContents);
|
||||
}
|
||||
|
||||
void setContents(Transferable contents) {
|
||||
try {
|
||||
clipboard.setContents(contents, this);
|
||||
} catch (IllegalStateException ise) {
|
||||
logger.error("Exception Thrown As Clipboard Cannot Be Accessed", ise);
|
||||
executorService.submit(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Transferable currentClipboardContents = clipboard.getContents(null);
|
||||
regainOwnership(clipboard, currentClipboardContents);
|
||||
} catch (IllegalStateException ise) {
|
||||
logger.error("Exception Thrown When Retrieving Clipboard Contents", ise);
|
||||
executorService.submit(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for {@link WindowsOrUnixClipboard}
|
||||
* <p>Retrieves a thread from {@link Executors#newSingleThreadExecutor()} and executes code in the background</p>
|
||||
*/
|
||||
@Override
|
||||
public void execute() {
|
||||
executorService.submit(this);
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
package me.goudham;
|
||||
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.ClipboardOwner;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static java.lang.Thread.currentThread;
|
||||
import static java.lang.Thread.sleep;
|
||||
import static me.goudham.domain.Contents.IMAGE;
|
||||
import static me.goudham.domain.Contents.STRING;
|
||||
|
||||
class WindowsOrUnixClipboardListener extends ClipboardListener implements Runnable, ClipboardOwner {
|
||||
|
||||
WindowsOrUnixClipboardListener() { }
|
||||
|
||||
@Override
|
||||
public void lostOwnership(Clipboard oldClipboard, Transferable oldClipboardContents) {
|
||||
try {
|
||||
sleep(200);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
|
||||
Transferable newClipboardContents = oldClipboard.getContents(currentThread());
|
||||
processContents(oldClipboard, newClipboardContents);
|
||||
regainOwnership(oldClipboard, newClipboardContents);
|
||||
}
|
||||
|
||||
public void processContents(Clipboard oldClipboard, Transferable newClipboardContents) {
|
||||
if (isTextMonitored()) {
|
||||
if (STRING.isAvailable(oldClipboard)) {
|
||||
String stringContent = getStringContent(newClipboardContents);
|
||||
notifyStringEvent(stringContent);
|
||||
}
|
||||
}
|
||||
|
||||
if (isImagesMonitored()) {
|
||||
if (IMAGE.isAvailable(oldClipboard)) {
|
||||
BufferedImage bufferedImage = getImageContent(newClipboardContents);
|
||||
notifyImageEvent(bufferedImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void regainOwnership(Clipboard clipboard, Transferable newClipboardContents) {
|
||||
try {
|
||||
clipboard.setContents(newClipboardContents, this);
|
||||
} catch (IllegalStateException ise) {
|
||||
try {
|
||||
sleep(200);
|
||||
} catch (InterruptedException ie) {
|
||||
ie.printStackTrace();
|
||||
}
|
||||
regainOwnership(clipboard, newClipboardContents);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Transferable currentClipboardContents = clipboard.getContents(null);
|
||||
processContents(clipboard, currentClipboardContents);
|
||||
regainOwnership(clipboard, currentClipboardContents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
executorService.submit(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package me.goudham.domain;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Contains potential clipboard contents returned from the clipboard. Supported types are currently {@link String},
|
||||
* {@link BufferedImage} and {@link List} of {@link File}
|
||||
*/
|
||||
public class ClipboardContent {
|
||||
private String text;
|
||||
private BufferedImage image;
|
||||
private List<File> files;
|
||||
|
||||
public ClipboardContent(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public ClipboardContent(BufferedImage image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public ClipboardContent(List<File> files) {
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public List<File> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClipboardContent{" +
|
||||
"text='" + text + '\'' +
|
||||
", image=" + image +
|
||||
", files=" + files +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package me.goudham.domain;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Contains clipboard contents as generics
|
||||
* @param <T> Type of data given by the {@link java.awt.datatransfer.Clipboard}
|
||||
*/
|
||||
public class GenericClipboardContent<T> {
|
||||
private T oldContent;
|
||||
|
||||
public GenericClipboardContent(Object oldContent) {
|
||||
this.oldContent = (T) oldContent;
|
||||
}
|
||||
|
||||
public void setOldContent(Object oldContent) {
|
||||
this.oldContent = (T) oldContent;
|
||||
}
|
||||
|
||||
public T getOldContent() {
|
||||
return oldContent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
GenericClipboardContent<?> that = (GenericClipboardContent<?>) o;
|
||||
return Objects.equals(oldContent, that.oldContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(oldContent);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package me.goudham.domain;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Wrapper class surrounding {@link MyBufferedImage} to ensure image equality is properly evaluated
|
||||
*/
|
||||
public class MyBufferedImage {
|
||||
private BufferedImage bufferedImage;
|
||||
|
||||
public MyBufferedImage(BufferedImage bufferedImage) {
|
||||
this.bufferedImage = bufferedImage;
|
||||
}
|
||||
|
||||
public BufferedImage getBufferedImage() {
|
||||
return bufferedImage;
|
||||
}
|
||||
|
||||
public void setBufferedImage(BufferedImage bufferedImage) {
|
||||
this.bufferedImage = bufferedImage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
MyBufferedImage that = (MyBufferedImage) o;
|
||||
if (that.getBufferedImage() == null) return false;
|
||||
return equals(that.bufferedImage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(bufferedImage);
|
||||
}
|
||||
|
||||
private boolean equals(BufferedImage secondBufferedImage) {
|
||||
if (bufferedImage.getHeight() != secondBufferedImage.getHeight() && bufferedImage.getHeight() != secondBufferedImage.getHeight()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int xPixel = 0; xPixel < bufferedImage.getWidth(); xPixel++) {
|
||||
for (int yPixel = 0; yPixel < bufferedImage.getHeight(); yPixel++) {
|
||||
if (bufferedImage.getRGB(xPixel, yPixel) != secondBufferedImage.getRGB(xPixel, yPixel)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package me.goudham.domain;
|
||||
|
||||
public class MyClipboardContent<T> {
|
||||
private T oldContent;
|
||||
|
||||
public MyClipboardContent() {
|
||||
}
|
||||
|
||||
public void setOldContent(Object oldContent) {
|
||||
this.oldContent = (T) oldContent;
|
||||
}
|
||||
|
||||
public T getOldContent() {
|
||||
return oldContent;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package me.goudham.domain;
|
||||
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link Transferable} which implements the capability required to transfer a
|
||||
* {@link List} of {@link File}
|
||||
* <p>
|
||||
* This {@link Transferable} properly supports {@link DataFlavor#javaFileListFlavor}
|
||||
* @see DataFlavor#javaFileListFlavor
|
||||
*/
|
||||
public class TransferableFile implements Transferable {
|
||||
|
||||
private final List<File> files;
|
||||
|
||||
public TransferableFile(List<File> files) {
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
|
||||
if (flavor.equals(DataFlavor.javaFileListFlavor)) {
|
||||
return files;
|
||||
} else {
|
||||
throw new UnsupportedFlavorException(flavor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataFlavor[] getTransferDataFlavors() {
|
||||
return new DataFlavor[] { DataFlavor.javaFileListFlavor };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDataFlavorSupported(DataFlavor flavor) {
|
||||
return flavor == DataFlavor.javaFileListFlavor;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package me.goudham.event;
|
||||
|
||||
/**
|
||||
* Common interface for all events to extend (Not used yet)
|
||||
*/
|
||||
interface ClipboardEvent {
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package me.goudham.event;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import me.goudham.domain.ClipboardContent;
|
||||
|
||||
/**
|
||||
* Interface for notifying clipboard changes that happen to be {@link File}
|
||||
*/
|
||||
public interface FileEvent extends ClipboardEvent {
|
||||
void onCopyFiles(ClipboardContent oldContent, List<File> newContent);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package me.goudham.event;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import me.goudham.domain.ClipboardContent;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for notifying clipboard changes that happen to be {@link BufferedImage}
|
||||
*/
|
||||
public interface ImageEvent extends ClipboardEvent {
|
||||
void onCopyImage(ClipboardContent oldContent, BufferedImage newContent);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package me.goudham.event;
|
||||
|
||||
import me.goudham.domain.ClipboardContent;
|
||||
|
||||
/**
|
||||
* Interface for notifying clipboard changes that happen to be {@link String}
|
||||
*/
|
||||
public interface TextEvent extends ClipboardEvent {
|
||||
void onCopyText(ClipboardContent oldContent, String newContent);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package me.goudham.exception;
|
||||
|
||||
/**
|
||||
* Thrown when {@link java.awt.datatransfer.Clipboard} is unavailable to be accessed
|
||||
*/
|
||||
public class CannotAccessClipboardException extends Throwable {
|
||||
public CannotAccessClipboardException(String exceptionMessage) {
|
||||
super(exceptionMessage);
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package me.goudham.listener;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public interface ClipboardEventListener {
|
||||
void onCopyString(String stringContent);
|
||||
void onCopyImage(BufferedImage imageContent);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package me.goudham;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class BufferedImageMatcher implements Matcher<BufferedImage> {
|
||||
|
||||
private final BufferedImage expected;
|
||||
|
||||
public BufferedImageMatcher(BufferedImage expected) {
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object argument) {
|
||||
if (argument == null) return true;
|
||||
|
||||
BufferedImage actual = (BufferedImage) argument;
|
||||
|
||||
assertEquals(expected.getWidth(), actual.getWidth());
|
||||
assertEquals(expected.getHeight(), actual.getHeight());
|
||||
|
||||
for (int x = 0; x < actual.getWidth(); x++) {
|
||||
for (int y = 0; y < actual.getHeight(); y++) {
|
||||
assertEquals(expected.getRGB(x, y), actual.getRGB(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeMismatch(Object o, Description description) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void _dont_implement_Matcher___instead_extend_BaseMatcher_() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
package me.goudham;
|
||||
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import me.goudham.domain.ClipboardContent;
|
||||
import me.goudham.domain.GenericClipboardContent;
|
||||
import me.goudham.domain.MyBufferedImage;
|
||||
import me.goudham.domain.TransferableFile;
|
||||
import me.goudham.domain.TransferableImage;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static me.goudham.Contents.FILE;
|
||||
import static me.goudham.Contents.IMAGE;
|
||||
import static me.goudham.Contents.TEXT;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class ClipboardUtilsTest {
|
||||
|
||||
@Mock
|
||||
private Transferable transferableMock;
|
||||
|
||||
@Mock
|
||||
private Logger logger;
|
||||
|
||||
private ClipboardUtils sut;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
sut = new ClipboardUtils();
|
||||
ClipboardUtils.setLogger(logger);
|
||||
}
|
||||
|
||||
@Test
|
||||
void successfullyGetStringContent() {
|
||||
String expectedStringContent = "testString";
|
||||
Transferable expectedTransferable = new StringSelection(expectedStringContent);
|
||||
|
||||
String actualStringContent = sut.getStringContent(expectedTransferable);
|
||||
|
||||
assertThat(actualStringContent, is(expectedStringContent));
|
||||
verifyNoInteractions(logger);
|
||||
}
|
||||
|
||||
@Test
|
||||
void failToGetStringContent() throws IOException, UnsupportedFlavorException {
|
||||
String expectedStringContent = null;
|
||||
String expectedExceptionMessage = "Exception Thrown When Retrieving String Content";
|
||||
Throwable expectedException = new UnsupportedFlavorException(TEXT.getDataFlavor());
|
||||
|
||||
when(transferableMock.isDataFlavorSupported(TEXT.getDataFlavor())).thenReturn(true);
|
||||
when(transferableMock.getTransferData(TEXT.getDataFlavor())).thenThrow(expectedException);
|
||||
|
||||
String actualStringContent = sut.getStringContent(transferableMock);
|
||||
|
||||
verify(logger, times(1)).error(expectedExceptionMessage, expectedException);
|
||||
assertThat(actualStringContent, is(expectedStringContent));
|
||||
}
|
||||
|
||||
@Test
|
||||
void successfullyGetImageContent() {
|
||||
BufferedImage expectedBufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
||||
MyBufferedImage expectedImageContent = new MyBufferedImage(expectedBufferedImage);
|
||||
Transferable expectedTransferable = new TransferableImage(expectedBufferedImage);
|
||||
|
||||
MyBufferedImage actualImageContent = sut.getImageContent(expectedTransferable);
|
||||
|
||||
assertThat(actualImageContent, is(expectedImageContent));
|
||||
verifyNoInteractions(logger);
|
||||
}
|
||||
|
||||
@Test
|
||||
void failToGetImageContent() throws IOException, UnsupportedFlavorException {
|
||||
MyBufferedImage expectedImageContent = null;
|
||||
String expectedExceptionMessage = "Exception Thrown When Retrieving Image Content";
|
||||
Throwable expectedException = new UnsupportedFlavorException(IMAGE.getDataFlavor());
|
||||
|
||||
when(transferableMock.isDataFlavorSupported(IMAGE.getDataFlavor())).thenReturn(true);
|
||||
when(transferableMock.getTransferData(IMAGE.getDataFlavor())).thenThrow(expectedException);
|
||||
|
||||
MyBufferedImage actualImageContent = sut.getImageContent(transferableMock);
|
||||
|
||||
verify(logger, times(1)).error(expectedExceptionMessage, expectedException);
|
||||
assertThat(actualImageContent, is(expectedImageContent));
|
||||
}
|
||||
|
||||
@Test
|
||||
void successfullyGetFileContent() {
|
||||
List<File> expectedFileContent = List.of(new File("testFile"));
|
||||
Transferable expectedTransferable = new TransferableFile(expectedFileContent);
|
||||
|
||||
List<File> actualFileContent = sut.getFileContent(expectedTransferable);
|
||||
|
||||
assertThat(actualFileContent, is(expectedFileContent));
|
||||
verifyNoInteractions(logger);
|
||||
}
|
||||
|
||||
@Test
|
||||
void failToGetFileContent() throws IOException, UnsupportedFlavorException {
|
||||
List<File> expectedFileContent = null;
|
||||
String expectedExceptionMessage = "Exception Thrown When Retrieving File Content";
|
||||
Throwable expectedException = new UnsupportedFlavorException(FILE.getDataFlavor());
|
||||
|
||||
when(transferableMock.isDataFlavorSupported(FILE.getDataFlavor())).thenReturn(true);
|
||||
when(transferableMock.getTransferData(FILE.getDataFlavor())).thenThrow(expectedException);
|
||||
|
||||
List<File> actualFileContent = sut.getFileContent(transferableMock);
|
||||
|
||||
verify(logger, times(1)).error(expectedExceptionMessage, expectedException);
|
||||
assertThat(actualFileContent, is(expectedFileContent));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsForRetrievingGenericClipboardContents")
|
||||
void successfullyRetrieveOldClipboardContents(GenericClipboardContent<?> expectedGenericClipboardContent, Object expectedContent, DataFlavor dataFlavor) throws IOException, UnsupportedFlavorException {
|
||||
when(transferableMock.isDataFlavorSupported(dataFlavor)).thenReturn(true);
|
||||
when(transferableMock.getTransferData(dataFlavor)).thenReturn(expectedContent);
|
||||
|
||||
GenericClipboardContent<?> actualGenericClipboardContent = sut.getGenericClipboardContents(transferableMock);
|
||||
|
||||
assertThat(actualGenericClipboardContent.getOldContent(), is(expectedGenericClipboardContent.getOldContent()));
|
||||
verifyNoInteractions(logger);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsForOldClipboardContentsWhenContentIsTransferable")
|
||||
void successfullyMarshallClipboardContentsIntoOldClipboardContentWhenContentIsTransferable(Object expectedContent, DataFlavor dataFlavor, String expectedString, BufferedImage expectedImage, List<File> expectedFiles) throws IOException, UnsupportedFlavorException {
|
||||
when(transferableMock.isDataFlavorSupported(dataFlavor)).thenReturn(true);
|
||||
when(transferableMock.getTransferData(dataFlavor)).thenReturn(expectedContent);
|
||||
|
||||
ClipboardContent actualClipboardContent = sut.getClipboardContent(transferableMock);
|
||||
|
||||
assertThat(actualClipboardContent.getText(), is(expectedString));
|
||||
assertThat(actualClipboardContent.getFiles(), is(expectedFiles));
|
||||
assertThat(actualClipboardContent.getImage(), is(new BufferedImageMatcher(expectedImage)));
|
||||
verifyNoInteractions(logger);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsForOldClipboardContents")
|
||||
void successfullyMarshallClipboardContentsIntoOldClipboardContent(Object expectedOldContent, String expectedString, BufferedImage expectedImage, List<File> expectedFiles) {
|
||||
ClipboardContent actualClipboardContent = sut.getClipboardContent(expectedOldContent);
|
||||
|
||||
assertThat(actualClipboardContent.getText(), is(expectedString));
|
||||
assertThat(actualClipboardContent.getImage(), is(expectedImage));
|
||||
assertThat(actualClipboardContent.getFiles(), is(expectedFiles));
|
||||
verifyNoInteractions(logger);
|
||||
}
|
||||
|
||||
static Stream<Arguments> provideArgumentsForOldClipboardContentsWhenContentIsTransferable() {
|
||||
String string = "testString";
|
||||
BufferedImage bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
||||
List<File> files = List.of(new File("testFile"));
|
||||
|
||||
return Stream.of(
|
||||
Arguments.of(string, TEXT.getDataFlavor(), string, null, null),
|
||||
Arguments.of(bufferedImage, IMAGE.getDataFlavor(), null, bufferedImage, null),
|
||||
Arguments.of(files, FILE.getDataFlavor(), null, null, files)
|
||||
);
|
||||
}
|
||||
|
||||
static Stream<Arguments> provideArgumentsForOldClipboardContents() {
|
||||
String string = "testString";
|
||||
BufferedImage bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
||||
MyBufferedImage myBufferedImage = new MyBufferedImage(bufferedImage);
|
||||
List<File> files = List.of(new File("testFile"));
|
||||
|
||||
return Stream.of(
|
||||
Arguments.of(string, string, null, null),
|
||||
Arguments.of(myBufferedImage, null, bufferedImage, null),
|
||||
Arguments.of(files, null, null, files)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
static Stream<Arguments> provideArgumentsForRetrievingGenericClipboardContents() {
|
||||
String string = "testString";
|
||||
BufferedImage bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
||||
MyBufferedImage myBufferedImage = new MyBufferedImage(bufferedImage);
|
||||
List<File> files = List.of(new File("testFile"));
|
||||
|
||||
return Stream.of(
|
||||
Arguments.of(new GenericClipboardContent<>(string), string, TEXT.getDataFlavor()),
|
||||
Arguments.of(new GenericClipboardContent<>(myBufferedImage), bufferedImage, IMAGE.getDataFlavor()),
|
||||
Arguments.of(new GenericClipboardContent<>(files), files, FILE.getDataFlavor())
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package me.goudham;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
import me.goudham.exception.UnsupportedSystemException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class MyClipboardTest {
|
||||
|
||||
@Mock
|
||||
private SystemUtils systemUtilsMock;
|
||||
|
||||
private MyClipboard sut;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
MyClipboard.setSystemUtils(systemUtilsMock);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsForSuccessfullyRetrievingCorrectSystemClipboard")
|
||||
void successfullyRetrieveCorrectSystemClipboard(boolean isMac, boolean isWindows, boolean isUnix, Class<?> expectedListener) throws UnsupportedSystemException {
|
||||
when(systemUtilsMock.isMac()).thenReturn(isMac);
|
||||
when(systemUtilsMock.isWindows()).thenReturn(isWindows);
|
||||
when(systemUtilsMock.isUnix()).thenReturn(isUnix);
|
||||
|
||||
MyClipboard actualMyClipboard = MyClipboard.getSystemClipboard();
|
||||
|
||||
assertThat(actualMyClipboard.getClipboardListener(), instanceOf(expectedListener));
|
||||
}
|
||||
|
||||
@Test
|
||||
void failToRetrieveCorrectSystemClipboard() {
|
||||
String expectedOperatingSystem = "unknown";
|
||||
System.setProperty("os.name", expectedOperatingSystem);
|
||||
Throwable expectedException = new UnsupportedSystemException("Your Operating System: '" + System.getProperty("os.name") + "' is not supported");
|
||||
|
||||
when(systemUtilsMock.isMac()).thenReturn(false);
|
||||
when(systemUtilsMock.isWindows()).thenReturn(false);
|
||||
when(systemUtilsMock.isUnix()).thenReturn(false);
|
||||
|
||||
Throwable actualException = assertThrows(UnsupportedSystemException.class, MyClipboard::getSystemClipboard);
|
||||
|
||||
assertThat(actualException, instanceOf(expectedException.getClass()));
|
||||
assertThat(actualException.getMessage(), is(expectedException.getMessage()));
|
||||
}
|
||||
|
||||
static Stream<Arguments> provideArgumentsForSuccessfullyRetrievingCorrectSystemClipboard() {
|
||||
return Stream.of(
|
||||
Arguments.of(false, true, false, WindowsOrUnixClipboard.class),
|
||||
Arguments.of(false, false, true, WindowsOrUnixClipboard.class),
|
||||
Arguments.of(true, false, false, MacClipboard.class)
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue