Detecting paste in python Detecting paste in python tkinter tkinter

Detecting paste in python


As Leon's answer points out, under standard conditions, it is unlikely that you will be able to detect any use of copied objects once you've released them into the wild. However, most modern OSes support something called "delayed rendering". Not only can the format of the selection be negotiated between host and destination, but it is not advisable to copy large pieces of memory without first knowing where they are going. Both Windows and X provide a way of doing exactly what you want through this mechanism.

Rather than go into the details of how each OS implements their clipboard API, let's look at a fairly standard cross-plarform package: PyQt5. Clipboard access is implemented through the QtGui.QClipBoard class. You can trigger delayed rendering by avoiding the convenience methods and using setMimeData. In particular, you would create a custom QtCore.QMimeData subclass that implements retreiveData to fetch upon request rather than just storing the data in the clipboard. You will also have to set up your own implementations of hasFormat and formats, which should not be a problem. This will allow you to dynamically negotiate the available formats upon request, which is how delayed rendering is normally implemented.

So now let's take a look at a small application. You mentioned in the question that you have a list of items that you would like to copy successively once the first one has been copied. Let's do exactly that. Our custom retrieveData implementation will convert the current selection to a string, encode it in UTF-8 or whatever, move the selection forward, and copy that into the clipboard:

from PyQt5.QtCore import Qt, QMimeData, QStringListModel, QTimer, QVariantfrom PyQt5.QtGui import QClipboardfrom PyQt5.QtWidgets import QAbstractItemView, QApplication, QListViewclass MyMimeData(QMimeData):    FORMATS = {'text/plain'}    def __init__(self, item, hook=None):        super().__init__()        self.item = item        self.hook = hook    def hasFormat(self, fmt):        return fmt in self.FORMATS    def formats(self):        # Ensure copy        return list(self.FORMATS)    def retrieveData(self, mime, type):        if self.hasFormat(mime):            if self.hook:                self.hook()            return self.item        return QVariant()class MyListView(QListView):    def keyPressEvent(self, event):        if event.key() == Qt.Key_C and event.modifiers() & Qt.ControlModifier:            self.copy()        else:            super().keyPressEvent(event)    def nextRow(self):        current = self.selectedIndexes()[0]        row = None        if current:            row = self.model().index(current.row() + 1, current.column())        if row is None or row.row() == -1:            row = self.model().index(0, current.column())        self.setCurrentIndex(row)        QTimer.singleShot(1, self.copy)    def copy(self, row=None):        if row is None:            row = self.selectedIndexes()[0]        data = MyMimeData(row.data(), self.nextRow)        QApplication.clipboard().setMimeData(data, QClipboard.Clipboard)model = QStringListModel([    "First", "Second", "Third", "Fourth", "Fifth",    "Sixth", "Seventh", "Eighth", "Ninth", "Tenth",])app = QApplication([])view = MyListView()view.setSelectionMode(QAbstractItemView.SingleSelection)view.setModel(model)view.show()app.exec_()

The QTimer object here is just a hack to quickly get a separate thread to run the copy. Attempting to copy outside Qt space triggers some thread-related problems.

The key here is that you can not simply copy text to the clipboard, but rather create a placeholder object. You will not be able to use a simple interface like pyperclip, and likely tkinter.

On the bright side, the above example hopefully shows you that PyQt5 is not too complex for a simple application (and definitely a good choice for the non-simple kind). It is also nice that most operating systems support some form of delayed rendering in some form that Qt can latch on to.

Keep in mind that delayed rendering only lets you know when an object is read from the clipboard by some application, not necessarily the one you want. In fact it doesn't have to be a "paste": the application could just be peeking into the clipboard. For the simple operation described in the question, this will likely be fine. If you want better control over the communication, use something more advanced, like OS-specific monitoring of who reads the copied data, or a more robust solution like shared memory.


Disclaimer: I am not an expert in clipboards. This answer is my understanding how they work. It can be totally wrong.

Hardly there is a platform specific way to solve this, let alone do it in a cross-platform way. The act of pasting from a clipboard consists of two disconnected steps:

  1. Peek at/read the clipboard contents
  2. Use that data in an application specific way

During the second step the application may check the type of the data read from the clipboard and may ignore it if doesn't match the type of the data that can be pasted in the active context (e.g. an image cannot be pasted in a plain text editor). If pasting happens, it happens in the user space and each application may do it differently. Detecting all possible implementations under all platforms simply doesn't makes any sense.

At best you can monitor the acts of peeking at the clipboard contents, yet any application (consider a third party clipboard manager) can examine the clipboard eagerly without any explicit actions from the user, hence those events are not necessarily followed by any observable utilization of that data.

The following loose analogy from the real world will probably convince you to abandon looking for a solution. Suppose that you apply for and are granted a patent to some recipe. The patent is published and can be read by anyone. Could you ask for an effective way to detect any instances of a dish being cooked according to the patented recipe?


On ms-windows, it seems you should be able to use a global hook (using SetWindowsHookExA) to intercept the relevant WM_PASTE message.