Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

calling a method when content of clipboard is changed

Tags:

java

clipboard

I'm trying to make a little desktop app that should show the contents of the clipboard (if it is a string). I have done a constructor that does that and it works well, now I just want to make a call to a similar method whenever a text is copied into the clipboard in the OS. I'm quite new to this so any help would be appreciated! Something tells me I should use interrupts in some way...

package pasty;

import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

public class PastyFrame implements KeyListener {

    String currentClipboardString;
    JLabel clipboardLabel = new JLabel();

    public PastyFrame() {
        JFrame frame = new JFrame();
        frame.setVisible(true);

        try {
            currentClipboardString = (String) Toolkit.getDefaultToolkit().getSystemClipboard().getData(DataFlavor.stringFlavor);
        } catch (UnsupportedFlavorException | IOException ex) {
            Logger.getLogger(PastyFrame.class.getName()).log(Level.SEVERE, null, ex);

            currentClipboardString = "";
        }
        if (currentClipboardString.isEmpty()) {
            currentClipboardString = "The clipboard is empty";
        }
        frame.setSize(400, 100);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setLayout(new FlowLayout());


        clipboardLabel.setText(currentClipboardString);
        frame.add(clipboardLabel);
}
like image 541
user1907859 Avatar asked Jan 08 '13 23:01

user1907859


3 Answers

I came up with another solution to this problem: The following listener continuously reads the clipboard content using a loop. If a text is detected, it will be compared to the previous content of the clipboard, which is cached. When the clipboard contains a new text that was not cached previously, it may perform some action like 'notify observers', as in this example, which may prompt a GUI to update itself.

In this example, content changes, where the clipboard contains something other than a string, are ignored.

Other than just detecting content type changes (i.e. by using the FlavorListerner), this solution detects changes by continuous string comparison. By just read-accessing the clipboard, I'd expect this code to cause less interference with other applications than e.g. by taking ownership of the clipboard.

Suggestions are welcome.

package gui;

import java.awt.HeadlessException;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Observable;

/**
 * @author Matthias Hinz
 */
class ClipboardTextListener extends Observable implements Runnable {

    Clipboard sysClip = Toolkit.getDefaultToolkit().getSystemClipboard();

    private volatile boolean running = true;

    public void terminate() {
        running = false;
    }

    public void run() {
        System.out.println("Listening to clipboard...");
        // the first output will be when a non-empty text is detected
        String recentContent = "";
        // continuously perform read from clipboard
        while (running) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                // request what kind of data-flavor is supported
                List<DataFlavor> flavors = Arrays.asList(sysClip.getAvailableDataFlavors());
                // this implementation only supports string-flavor
                if (flavors.contains(DataFlavor.stringFlavor)) {
                    String data = (String) sysClip.getData(DataFlavor.stringFlavor);
                    if (!data.equals(recentContent)) {
                        recentContent = data;
                        // Do whatever you want to do when a clipboard change was detected, e.g.:
                        System.out.println("New clipboard text detected: " + data);
                        setChanged();
                        notifyObservers(data);
                    }
                }

            } catch (HeadlessException e1) {
                e1.printStackTrace();
            } catch (UnsupportedFlavorException e1) {
                e1.printStackTrace();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ClipboardTextListener b = new ClipboardTextListener();
        Thread thread = new Thread(b);
        thread.start();
    }
}
like image 107
Matthias Hinz Avatar answered Oct 26 '22 02:10

Matthias Hinz


You can call Clipboard.addFlavorListener to listen for clipboard updates from the OS:

Toolkit.getDefaultToolkit().getSystemClipboard().addFlavorListener(new FlavorListener() { 
   @Override 
   public void flavorsChanged(FlavorEvent e) {

      System.out.println("ClipBoard UPDATED: " + e.getSource() + " " + e.toString());
   } 
}); 

Some Side Notes:

  • For launching your application, consider using initial threads.
  • Call JFrame.pack to set the frame size.
  • Key Bindings are preferred over KeyListeners for mapping KeyEvents in Swing.
like image 30
Reimeus Avatar answered Oct 26 '22 04:10

Reimeus


I use this. The whole class.

public class ClipBoardListener extends Thread implements ClipboardOwner{
Clipboard sysClip = Toolkit.getDefaultToolkit().getSystemClipboard();  


    @Override
  public void run() {
    Transferable trans = sysClip.getContents(this);  
    TakeOwnership(trans);       

  }  

    @Override
  public void lostOwnership(Clipboard c, Transferable t) {  

  try {  
    ClipBoardListener.sleep(250);  //waiting e.g for loading huge elements like word's etc.
  } catch(Exception e) {  
    System.out.println("Exception: " + e);  
  }  
  Transferable contents = sysClip.getContents(this);  
    try {
        process_clipboard(contents, c);
    } catch (Exception ex) {
        Logger.getLogger(ClipBoardListener.class.getName()).log(Level.SEVERE, null, ex);
    }
  TakeOwnership(contents);


}  

  void TakeOwnership(Transferable t) {  
    sysClip.setContents(t, this);  
  }  

public void process_clipboard(Transferable t, Clipboard c) { //your implementation
    String tempText;
    Transferable trans = t;

    try {
        if (trans != null?trans.isDataFlavorSupported(DataFlavor.stringFlavor):false) {
            tempText = (String) trans.getTransferData(DataFlavor.stringFlavor);
            System.out.println(tempText);  
        }

    } catch (Exception e) {
    }
}

}

When other program takes ownership of the clipboard it waits 250 ms and takes back clipboard's ownership with updated content.

like image 41
daredesm Avatar answered Oct 26 '22 02:10

daredesm