commit
c11eeb668c
@ -1,4 +1,9 @@
|
|||||||
FROM maven:3.8.1-adoptopenjdk-11
|
FROM maven:3.8.1-adoptopenjdk-11
|
||||||
MAINTAINER Goudham Suresh
|
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
|
[license]: https://img.shields.io/github/license/sgoudham/MyClipboard
|
||||||
|
[maven-central]: https://img.shields.io/maven-central/v/me.goudham/MyClipboard.svg?label=Maven%20Central
|
||||||
[comment]: <> ([maven-central]: )
|
[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
|
||||||
[comment]: <> ([build-status]:)
|
|
||||||
|
|
||||||
[comment]: <> ([codecov]:)
|
|
||||||
[issues]: https://img.shields.io/github/issues/sgoudham/MyClipboard?label=issues
|
[issues]: https://img.shields.io/github/issues/sgoudham/MyClipboard?label=issues
|
||||||
[pull-requests]: https://img.shields.io/github/issues-pr/sgoudham/MyClipboard
|
[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]: https://app.fossa.com/api/projects/git%2Bgithub.com%2Fsgoudham%2FMyClipboard.svg?type=shield
|
||||||
|
|
||||||
![fossa]
|
![fossa]
|
||||||
![license]
|
![license]
|
||||||
|
![maven-central]
|
||||||
[comment]: <> (![maven-central])
|
![build-status]
|
||||||
|
![codecov]
|
||||||
[comment]: <> (![build-status])
|
|
||||||
|
|
||||||
[comment]: <> (![codecov])
|
|
||||||
![issues]
|
![issues]
|
||||||
![pull-requests]
|
![pull-requests]
|
||||||
|
|
||||||
# MyClipboard
|
# MyClipboard
|
||||||
Utility Program to Access and Manipulate the Default System Clipboard
|
Utility Library to Access and Manipulate the Default System Clipboard
|
||||||
|
|
||||||
# About
|
# 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
|
# Configuration
|
||||||
|
TODO
|
||||||
|
|
||||||
# Windows / *Unix
|
# 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
|
# 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
|
# 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)
|
[![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;
|
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.exception.UnsupportedSystemException;
|
||||||
import me.goudham.listener.ClipboardEventListener;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry Class for User to interact with the System Clipboard
|
* Entry Class for User to interact with the System Clipboard
|
||||||
*
|
* <p>
|
||||||
* The abstract class {@link ClipboardListener} is responsible for handling all operations
|
* The abstract class {@link SystemClipboard} is responsible for handling all operations
|
||||||
*/
|
*/
|
||||||
public class MyClipboard {
|
public class MyClipboard {
|
||||||
private final @NotNull ClipboardListener clipboardListener;
|
private @NotNull SystemClipboard systemClipboard;
|
||||||
|
private static SystemUtils systemUtils = new SystemUtils();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of {@link MyClipboard}
|
* Creates an instance of {@link MyClipboard}
|
||||||
*
|
*
|
||||||
* @param clipboardListener The underlying {@link ClipboardListener}
|
* @param systemClipboard The underlying {@link SystemClipboard}
|
||||||
*/
|
*/
|
||||||
private MyClipboard(@NotNull ClipboardListener clipboardListener) {
|
private MyClipboard(@NotNull SystemClipboard systemClipboard) {
|
||||||
this.clipboardListener = clipboardListener;
|
this.systemClipboard = systemClipboard;
|
||||||
this.clipboardListener.execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of {@link MyClipboard} with an instance of {@link ClipboardListener} dependant on the OS
|
* Creates an instance of {@link MyClipboard} with an instance of {@link SystemClipboard} dependent on the OS
|
||||||
* <p>A {@link WindowsOrUnixClipboardListener} or {@link MacClipboardListener} can be created</p>
|
* <p>A {@link WindowsOrUnixClipboard} or {@link MacClipboard} can be created</p>
|
||||||
*
|
*
|
||||||
* @return {@link MyClipboard}
|
* @return {@link MyClipboard}
|
||||||
* @throws UnsupportedSystemException If {@link MyClipboard} detects an operating system which is not Mac or Windows/*Unix
|
* @throws UnsupportedSystemException If {@link MyClipboard} detects an operating system which is not Mac or Windows/*Unix
|
||||||
*/
|
*/
|
||||||
public static MyClipboard getSystemClipboard() throws UnsupportedSystemException {
|
public static MyClipboard getSystemClipboard() throws UnsupportedSystemException {
|
||||||
ClipboardListener clipboardListener;
|
SystemClipboard systemClipboard;
|
||||||
|
|
||||||
if (isMac()) {
|
if (systemUtils.isMac()) {
|
||||||
clipboardListener = new MacClipboardListener();
|
systemClipboard = new MacClipboard();
|
||||||
} else if (isWindows() || isUnix()) {
|
} else if (systemUtils.isWindows() || systemUtils.isUnix()) {
|
||||||
clipboardListener = new WindowsOrUnixClipboardListener();
|
systemClipboard = new WindowsOrUnixClipboard();
|
||||||
} else {
|
} 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) {
|
public void startListening() {
|
||||||
clipboardListener.addEventListener(clipboardEventListener);
|
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) {
|
public void stopListening() {
|
||||||
clipboardListener.removeEventListener(clipboardEventListener);
|
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() {
|
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() {
|
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() {
|
public boolean isImageMonitored() {
|
||||||
return clipboardListener.isImagesMonitored();
|
return systemClipboard.isImageMonitored();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setImagesMonitored(boolean imagesMonitored) {
|
public void setImageMonitored(boolean imagesMonitored) {
|
||||||
clipboardListener.setImagesMonitored(imagesMonitored);
|
systemClipboard.setImageMonitored(imagesMonitored);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTextMonitored() {
|
public boolean isTextMonitored() {
|
||||||
return clipboardListener.isTextMonitored();
|
return systemClipboard.isTextMonitored();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTextMonitored(boolean textMonitored) {
|
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() {
|
public void setClipboardListener(@NotNull SystemClipboard systemClipboard) {
|
||||||
return SystemUtils.IS_OS_MAC;
|
this.systemClipboard = systemClipboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isUnix() {
|
static SystemUtils getSystemUtils() {
|
||||||
return SystemUtils.IS_OS_UNIX || SystemUtils.IS_OS_LINUX;
|
return systemUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isWindows() {
|
static void setSystemUtils(SystemUtils systemUtils) {
|
||||||
return SystemUtils.IS_OS_WINDOWS;
|
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