diff --git a/src/main/java/me/goudham/ClipboardListener.java b/src/main/java/me/goudham/ClipboardListener.java new file mode 100644 index 0000000..70260b0 --- /dev/null +++ b/src/main/java/me/goudham/ClipboardListener.java @@ -0,0 +1,42 @@ +package me.goudham; + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.util.ArrayList; +import java.util.List; +import me.goudham.listener.ClipboardEventListener; + +abstract class ClipboardListener { + final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + List eventsListener = new ArrayList<>(); + private boolean imagesMonitored = true; + private boolean textMonitored = true; + + void addEventListener(ClipboardEventListener clipboardEventListener) { + if (!eventsListener.contains(clipboardEventListener)) { + eventsListener.add(clipboardEventListener); + } + } + + void removeEventListener(ClipboardEventListener clipboardEventListener) { + eventsListener.remove(clipboardEventListener); + } + + 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(); +} diff --git a/src/main/java/me/goudham/ClipboardUtils.java b/src/main/java/me/goudham/ClipboardUtils.java new file mode 100644 index 0000000..be9b6da --- /dev/null +++ b/src/main/java/me/goudham/ClipboardUtils.java @@ -0,0 +1,44 @@ +package me.goudham; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Image; +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 me.goudham.domain.MyClipboardContent; + +import static me.goudham.domain.Contents.IMAGE; +import static me.goudham.domain.Contents.STRING; + +class ClipboardUtils { + + static MyClipboardContent getClipboardContents(Transferable contents, Clipboard clipboard) { + MyClipboardContent myClipboardContent = new MyClipboardContent<>(); + + try { + if (STRING.isAvailable(clipboard)) { + myClipboardContent.setOldContent(contents.getTransferData(STRING.getDataFlavor())); + } else if (IMAGE.isAvailable(clipboard)) { + BufferedImage bufferedImage = convertToBufferedImage((Image) contents.getTransferData(IMAGE.getDataFlavor())); + myClipboardContent.setOldContent(new Dimension(bufferedImage.getWidth(), bufferedImage.getHeight())); + } + } catch (UnsupportedFlavorException | IOException exp) { + exp.printStackTrace(); + } + + return myClipboardContent; + } + + static BufferedImage convertToBufferedImage(Image image) { + BufferedImage newImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB); + + Graphics2D graphics = newImage.createGraphics(); + graphics.drawImage(image, 0, 0, null); + graphics.dispose(); + + return newImage; + } +} diff --git a/src/main/java/me/goudham/MacClipboardListener.java b/src/main/java/me/goudham/MacClipboardListener.java new file mode 100644 index 0000000..5045a6f --- /dev/null +++ b/src/main/java/me/goudham/MacClipboardListener.java @@ -0,0 +1,61 @@ +package me.goudham; + +import java.awt.Dimension; +import java.awt.Image; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import me.goudham.listener.ClipboardEventListener; +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); + + try { + if (isTextMonitored()) { + if (STRING.isAvailable(clipboard)) { + String newContent = (String) newClipboardContents.getTransferData(STRING.getDataFlavor()); + if (!newContent.equals(myClipboardContents[0].getOldContent())) { + for (ClipboardEventListener clipboardEventListener : eventsListener) { + clipboardEventListener.onCopyString(newContent); + } + myClipboardContents[0].setOldContent(newContent); + } + } + } + + if (isImagesMonitored()) { + if (IMAGE.isAvailable(clipboard)) { + BufferedImage bufferedImage = ClipboardUtils.convertToBufferedImage((Image) newClipboardContents.getTransferData(IMAGE.getDataFlavor())); + Dimension newContent = new Dimension(bufferedImage.getWidth(), bufferedImage.getHeight()); + if (!newContent.equals(myClipboardContents[0].getOldContent())) { + for (ClipboardEventListener clipboardEventListener : eventsListener) { + clipboardEventListener.onCopyImage(bufferedImage); + } + myClipboardContents[0].setOldContent(newContent); + } + } + } + } catch (UnsupportedFlavorException | IOException exp) { + exp.printStackTrace(); + } + }, 0, 350, TimeUnit.MILLISECONDS); + } +} diff --git a/src/main/java/me/goudham/Main.java b/src/main/java/me/goudham/Main.java new file mode 100644 index 0000000..fc03fee --- /dev/null +++ b/src/main/java/me/goudham/Main.java @@ -0,0 +1,34 @@ +package me.goudham; + +import java.awt.image.BufferedImage; +import me.goudham.exception.UnsupportedSystemException; +import me.goudham.listener.ClipboardEventListener; + +public class Main { + public static void main(String[] args) throws UnsupportedSystemException { + MyClipboard myClipboard = MyClipboard.getSystemClipboard(); + myClipboard.addEventListener(new ClipboardEventListener() { + @Override + public void onCopyString(String stringContent) { + System.out.println(stringContent); + } + + @Override + public void onCopyImage(BufferedImage imageContent) { + System.out.println(imageContent); + } + }); +// +// while (true) { +// +// } +// JClipboard jClipboard = JClipboard.getDefault(); +// jClipboard.addEventListener(new JClipboardEventListener() { +// @Override +// public void onClipboardTextChange(JClipboardEvent event) { +// System.out.println("Was: " + event.getOldValue()); // The old value +// System.out.println("Is: " + event.getNewValue()); // The new value +// } +// }); + } +} diff --git a/src/main/java/me/goudham/MyClipboard.java b/src/main/java/me/goudham/MyClipboard.java new file mode 100644 index 0000000..baae546 --- /dev/null +++ b/src/main/java/me/goudham/MyClipboard.java @@ -0,0 +1,67 @@ +package me.goudham; + +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 + */ +public class MyClipboard { + private final @NotNull ClipboardListener clipboardListener; + + /** + * Creates an instance of {@link MyClipboard} + * + * @param clipboardListener The underlying {@link ClipboardListener} + */ + private MyClipboard(@NotNull ClipboardListener clipboardListener) { + this.clipboardListener = clipboardListener; + this.clipboardListener.execute(); + } + + /** + * Creates an instance of {@link MyClipboard} with an instance of {@link ClipboardListener} dependant on the OS + *

A {@link WindowsOrUnixClipboardListener} or {@link MacClipboardListener} can be created

+ * + * @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; + + if (isMac()) { + clipboardListener = new MacClipboardListener(); + } else if (isWindows() || isUnix()) { + clipboardListener = new WindowsOrUnixClipboardListener(); + } else { + throw new UnsupportedSystemException("Your Operating System: " + System.getProperty("os.name") + "is not supported"); + } + + return new MyClipboard(clipboardListener); + } + + /** + * Adds an {@link ClipboardEventListener} to the underlying {@link ClipboardListener} + * + * @param clipboardEventListener The {@link ClipboardEventListener} to be added + */ + public void addEventListener(ClipboardEventListener clipboardEventListener) { + clipboardListener.addEventListener(clipboardEventListener); + } + + private static boolean isMac() { + return SystemUtils.IS_OS_MAC; + } + + private static boolean isUnix() { + return SystemUtils.IS_OS_UNIX || SystemUtils.IS_OS_LINUX; + } + + private static boolean isWindows() { + return SystemUtils.IS_OS_WINDOWS; + } +} diff --git a/src/main/java/me/goudham/WindowsOrUnixClipboardListener.java b/src/main/java/me/goudham/WindowsOrUnixClipboardListener.java new file mode 100644 index 0000000..b838545 --- /dev/null +++ b/src/main/java/me/goudham/WindowsOrUnixClipboardListener.java @@ -0,0 +1,78 @@ +package me.goudham; + +import java.awt.Image; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.ClipboardOwner; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import me.goudham.listener.ClipboardEventListener; + +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) { + try { + if (STRING.isAvailable(oldClipboard)) { + String stringContent = (String) newClipboardContents.getTransferData(DataFlavor.stringFlavor); + for (ClipboardEventListener clipboardEventListener : eventsListener) { + clipboardEventListener.onCopyString(stringContent); + } + } else if (IMAGE.isAvailable(oldClipboard)) { + BufferedImage imageContent = ClipboardUtils.convertToBufferedImage((Image) newClipboardContents.getTransferData(DataFlavor.imageFlavor)); + for (ClipboardEventListener clipboardEventListener : eventsListener) { + clipboardEventListener.onCopyImage(imageContent); + } + } + } catch (UnsupportedFlavorException | IOException ignored) { + } + } + + 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); + } +} \ No newline at end of file diff --git a/src/main/java/me/goudham/domain/Contents.java b/src/main/java/me/goudham/domain/Contents.java new file mode 100644 index 0000000..556a0ff --- /dev/null +++ b/src/main/java/me/goudham/domain/Contents.java @@ -0,0 +1,37 @@ +package me.goudham.domain; + +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; + +public enum Contents { + STRING(DataFlavor.stringFlavor) { + @Override + public boolean isAvailable(Clipboard clipboard) { + return clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor); + } + }, + IMAGE(DataFlavor.imageFlavor) { + @Override + public boolean isAvailable(Clipboard clipboard) { + return clipboard.isDataFlavorAvailable(DataFlavor.imageFlavor); + } + }, + FILELIST(DataFlavor.javaFileListFlavor) { + @Override + public boolean isAvailable(Clipboard clipboard) { + return clipboard.isDataFlavorAvailable(DataFlavor.javaFileListFlavor); + } + }; + + private final DataFlavor dataFlavor; + + Contents(DataFlavor dataFlavor) { + this.dataFlavor = dataFlavor; + } + + public DataFlavor getDataFlavor() { + return dataFlavor; + } + + public abstract boolean isAvailable(Clipboard clipboard); +} diff --git a/src/main/java/me/goudham/domain/MyClipboardContent.java b/src/main/java/me/goudham/domain/MyClipboardContent.java new file mode 100644 index 0000000..032a9ad --- /dev/null +++ b/src/main/java/me/goudham/domain/MyClipboardContent.java @@ -0,0 +1,16 @@ +package me.goudham.domain; + +public class MyClipboardContent { + private T oldContent; + + public MyClipboardContent() { + } + + public void setOldContent(Object oldContent) { + this.oldContent = (T) oldContent; + } + + public T getOldContent() { + return oldContent; + } +} diff --git a/src/main/java/me/goudham/domain/TransferableImage.java b/src/main/java/me/goudham/domain/TransferableImage.java new file mode 100644 index 0000000..a5f73d9 --- /dev/null +++ b/src/main/java/me/goudham/domain/TransferableImage.java @@ -0,0 +1,42 @@ +package me.goudham.domain; + +import java.awt.Image; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import org.jetbrains.annotations.NotNull; + +public class TransferableImage implements Transferable { + + private final Image image; + + public TransferableImage(@NotNull Image image) { + this.image = image; + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { + if (flavor.equals(DataFlavor.imageFlavor)) { + return image; + } else { + throw new UnsupportedFlavorException(flavor); + } + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { DataFlavor.imageFlavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + DataFlavor[] flavors = getTransferDataFlavors(); + for (DataFlavor dataFlavor : flavors) { + if (flavor.equals(dataFlavor)) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/me/goudham/exception/UnsupportedSystemException.java b/src/main/java/me/goudham/exception/UnsupportedSystemException.java new file mode 100644 index 0000000..6cdc64d --- /dev/null +++ b/src/main/java/me/goudham/exception/UnsupportedSystemException.java @@ -0,0 +1,10 @@ +package me.goudham.exception; + +/** + * Thrown when {@link me.goudham.MyClipboard} detects an operating system which is not {@code Mac} or {@code Windows/*Unix} + */ +public class UnsupportedSystemException extends Throwable { + public UnsupportedSystemException(String exceptionMessage) { + super(exceptionMessage); + } +} diff --git a/src/main/java/me/goudham/listener/ClipboardEventListener.java b/src/main/java/me/goudham/listener/ClipboardEventListener.java new file mode 100644 index 0000000..2851ad7 --- /dev/null +++ b/src/main/java/me/goudham/listener/ClipboardEventListener.java @@ -0,0 +1,8 @@ +package me.goudham.listener; + +import java.awt.image.BufferedImage; + +public interface ClipboardEventListener { + void onCopyString(String stringContent); + void onCopyImage(BufferedImage imageContent); +}