7

I want to be notified if a string is copied to the system clipboard. When a new string is copied from the same source application, the FlavorListener won't get an event. To get informed when another string is copied, i read the string from the clipboard, convert it to a SrtingSelection, which is able to take the ownership, and put it back to the clipboard. Now I got informed twice, once the StringSelection lost ownership and once it takes it back. Is there a way to check for the ownership directly, instead of storing the string and check it equals the new one? Here is my code so far:

 import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.FlavorEvent;
import java.awt.datatransfer.FlavorListener;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws Exception {
        // The clipboard
        final Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
        // read clipboard and take ownership to get the FlavorListener notified
        // when the content has changed but the owner has not
        processClipboard(cb);
        cb.addFlavorListener(new FlavorListener() {
            @Override
            public void flavorsChanged(FlavorEvent e) {
                processClipboard(cb);
            }
        });
        // keep thread for testing
        Thread.sleep(100000L);
    }

    public static void processClipboard(Clipboard cb) {
        // gets the content of clipboard
        Transferable trans = cb.getContents(null);
        if (trans.isDataFlavorSupported(DataFlavor.stringFlavor)) {
            try {
                // cast to string
                String s = (String) trans
                        .getTransferData(DataFlavor.stringFlavor);
                System.out.println(s);
                // only StringSelection can take ownership, i think
                StringSelection ss = new StringSelection(s);
                // set content, take ownership
                cb.setContents(ss, ss);
            } catch (UnsupportedFlavorException e2) {
                e2.printStackTrace();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
        }
    }
}

I hope you understand my bad english :-(

skaffman
  • 398,947
  • 96
  • 818
  • 769
anonymous001
  • 93
  • 2
  • 2
  • 5
  • 1
    Your approach will fail if there are two applications trying the same - they both will grab each others clipboard contents away. – Paŭlo Ebermann Mar 30 '11 at 12:00
  • Yes I know, but I didn't know of any other Method in pure Java, do you? – anonymous001 Mar 30 '11 at 15:25
  • Seems there is none ... I think X11 does not support listening for owner changes without being the owner yourself. (But in reality we don't want to listen for owner changes, but for content changes.) – Paŭlo Ebermann Mar 30 '11 at 17:57
  • There is no reliable way to do it. I resorted to periodic polling. :( – Boann Dec 16 '13 at 13:07

3 Answers3

5

The previous answer is close to be working.

The real cure is to instead just monitor the event of ownership change. By the monitor's occupying the clipboard as owner when monitoring the clipboard, so when any application changes the clipboard, the ownership would change, so this would reliably indicate the clipboard content change. However, this approach must have sufficient wait to work, (200 ms was found to be working) after an ownership change event before accessing the clipboard and re-occupying the clipboard.

This solution was provided and proved to be working by marc weber at http://www.coderanch.com/t/377833/java/java/listen-clipboard

I have verified for my purpose. If needed, I can post the solution here.

Yu

Yu Shen
  • 2,770
  • 3
  • 33
  • 48
  • This answer is correct. Use marc weber's solution, including his Thread.sleep() fix for exceptions, plus add a loop that catches said exceptions and tries again until it succeeds. Following those steps my listener is now working for all cases. I'm also using this to get the clipboard content: `String contents = (String)sysClip.getData(DataFlavor.stringFlavor);` – Tony R Feb 15 '14 at 17:41
  • @Yu Hi, Can you please share a complete solution that works? Thank you – Dimitris Feb 05 '15 at 12:25
  • 1
    Wow, this is really confusing. After hours of trying to figure out, finally, I've got no idea what should be added or removed to fix something in someone's code somewhere. – Yster Dec 01 '15 at 08:52
1

To avoid double notification remove the flavor listener before setting the new clipboard content and add the listener again after setting clipboard content.

public class NewClass implements FlavorListener, ClipboardOwner{
    private Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();  
    public NewClass() {
        System.out.println("NewClass constructor");
        clip.setContents(clip.getContents(null), this);
        clip.addFlavorListener(this);
        try {
            Thread.sleep(100000L);
        }
        catch (InterruptedException e) {

        }
    }

    @Override
    public void flavorsChanged(FlavorEvent e) {
        System.out.println("ClipBoard Changed!!!");
        clip.removeFlavorListener(this);
        clip.setContents(clip.getContents(null), this);
        clip.addFlavorListener(this);

    }

    @Override
    public void lostOwnership(Clipboard arg0, Transferable arg1) {
        System.out.println("ownership losted");
    }
}
AdrZ
  • 31
  • 3
0

I think this would work :)

import java.awt.AWTEvent;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.io.IOException;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JFrame;

public final class ClipboardMonitor extends Observable implements ClipboardOwner {
  private static ClipboardMonitor monitor = null;

  public ClipboardMonitor() {
        gainOwnership();
  }

  private void gainOwnership() {
        Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
        try {
              Transferable content = clip.getContents(null);
              DataFlavor[] f = content.getTransferDataFlavors();
              boolean imageDetected = false;
              for (int i = 0; i < f.length; i++) {
                    //                        System.out.println("Name: " + f[i].getHumanPresentableName());
                    //                        System.out.println("MimeType: " + f[i].getMimeType());
                    //                        System.out.println("PrimaryType: " + f[i].getPrimaryType());
                    //                        System.out.println("SubType: " + f[i].getSubType());
                    if (f[i].equals(DataFlavor.imageFlavor)) {
                          imageDetected = true;
                          break;
                    }
              }
              if (imageDetected) {
                    System.out.println("Image content detected");
                    Transferable t = new Transferable() {
                          public DataFlavor[] getTransferDataFlavors() {
                                return new DataFlavor[] { DataFlavor.stringFlavor };
                          }
                          public boolean isDataFlavorSupported(DataFlavor flavor) {
                                return false;
                          }
                          public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
                                return "dummy text instead of snapshot image";
                          }
                    };
                    clip.setContents(t, this);
              } else {
                    clip.setContents(content, this);
              }
              setChanged();
              notifyObservers(content);
        } catch (IllegalArgumentException istateexception) {
              istateexception.printStackTrace();
        } catch (Exception ioexception) {
              ioexception.printStackTrace();
        }
  }

  private int getCurrentEventModifiers() {
        int modifiers = 0;
        AWTEvent currentEvent = EventQueue.getCurrentEvent();
        if (currentEvent instanceof InputEvent) {
              modifiers = ((InputEvent) currentEvent).getModifiers();
        } else
              if (currentEvent instanceof ActionEvent) {
                    modifiers = ((ActionEvent) currentEvent).getModifiers();
              }
        return modifiers;
  }

  public void lostOwnership(Clipboard clipboard, Transferable contents) {
        System.out.println("Ownership lost ...");
        new Thread(new Runnable() {
              public void run() {
                    try {
                          Thread.sleep(200);
                          gainOwnership();
                    } catch (Exception e) {
                          e.printStackTrace();
                    }
              }
        }).start();
  }

  public void flushClipboard() {
        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(""), null);
  }

  public static final ClipboardMonitor getMonitor() {
        if (monitor == null)
              monitor = new ClipboardMonitor();
        return (monitor);
  }

  public static void main(String[] args) {
        JFrame f = new JFrame();
        ClipboardMonitor monitor = ClipboardMonitor.getMonitor();
        monitor.addObserver(new Observer() {
              public void update(Observable o, Object arg) {
                    System.out.println("Clipboard has been regained!");
              }
        });

        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(500, 100);
        f.setVisible(true);
  }

}

Jananath Banuka
  • 2,951
  • 8
  • 57
  • 105