在python中检测粘贴

时间:2018-01-29 14:17:32

标签: python tkinter

我想要检测用户何时在任何应用程序中粘贴了某些内容,因此我可以将新项目复制到剪贴板中进行跟进(使用案例:我有一个我正在从数据库中复制的项目列表逐个进入网页,并希望在完成粘贴后自动将下一个放入剪贴板。)

目前我有一个使用Tkinter的按钮,当使用以下代码按下时会复制一个字段。

self.root.clipboard_clear()
self.root.clipboard_append(text)

然后我需要一些方法来检测何时在另一个应用程序中执行了粘贴,这样我就可以将下一个项目加载到剪贴板中。我想在Win / Mac / Linux上工作,因为我在这三个方面工作。有什么想法吗?

4 个答案:

答案 0 :(得分:2)

正如Leon's answer所指出的,在标准条件下,一旦将它们释放到野外,就不太可能检测到对复制对象的任何使用。但是,大多数现代OS支持称为“延迟渲染”的东西。不仅可以在主机和目标之间协商选择的格式,而且建议不要在不知道大容量存储器去向的情况下复制大容量存储器。 Windows和X都提供了一种通过这种机制完全实现所需功能的方法。

让我们看看一个相当标准的跨平台软件包:PyQt5,而不是详细介绍每个操作系统如何实现其剪贴板API。剪贴板访问是通过QtGui.QClipBoard类实现的。您可以通过避免使用便捷方法并使用setMimeData来触发延迟渲染。特别是,您将创建一个自定义QtCore.QMimeData子类,该子类实现retreiveData以根据请求进行获取,而不仅仅是将数据存储在剪贴板中。您还必须设置自己的hasFormatformats的实现,这应该没问题。这将使您能够根据请求动态协商可用格式,这通常是延迟渲染的实现方式。

因此,现在让我们看一个小的应用程序。您在问题中提到,您拥有要复制的第一个项目后要连续复制的项目的列表。让我们做到这一点。我们的自定义retrieveData实现将当前选择内容转换为字符串,使用UTF-8或其他格式对其进行编码,将选择内容向前移动,然后将其复制到剪贴板中:

from PyQt5.QtCore import Qt, QMimeData, QStringListModel, QTimer, QVariant
from PyQt5.QtGui import QClipboard
from PyQt5.QtWidgets import QAbstractItemView, QApplication, QListView

class 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_()

这里的QTimer对象只是一种黑客,可以快速获取单独的线程来运行副本。尝试在Qt空间之外进行复制会触发一些与线程相关的问题。

这里的关键是您不能简单地将文本复制到剪贴板,而是可以创建一个占位符对象。您将无法使用简单的界面,例如pyperclip,甚至可能使用tkinter

从好的方面来说,上面的示例希望向您显示PyQt5对于简单的应用程序来说并不太复杂(对于非简单类型的应用程序绝对是一个不错的选择)。很好,大多数操作系统都支持Qt可以锁定到的某种形式的延迟渲染。

请记住,延迟渲染只会让您知道何时 some 应用程序从剪贴板读取对象,而不一定是您想要的对象。实际上,它不必一定是“粘贴”的:该应用程序可能只是窥视剪贴板。对于问题中描述的简单操作,这可能会很好。如果您想更好地控制通信,请使用更高级的功能,例如特定于操作系统的监视谁可以读取复制的数据,或者使用更强大的解决方案,例如共享内存。

答案 1 :(得分:1)

免责声明:我不是剪贴板专家。这个答案是我对它们如何工作的理解。这可能是完全错误的。

几乎没有特定于平台的方法可以解决此问题,更不用说以跨平台的方式了。从剪贴板粘贴的操作包括两个未连接的步骤:

  1. 浏览/阅读剪贴板中的内容
  2. 以特定于应用程序的方式使用该数据

在第二步中,应用程序可能会检查从剪贴板读取的数据类型,如果与活动上下文中可以粘贴的数据类型不匹配(例如,无法粘贴图像),则可以忽略它纯文本编辑器)。如果发生粘贴,它将在用户空间中发生,并且每个应用程序可能会做不同的操作。在所有平台下检测所有可能的实现根本没有任何意义。

充其量您可以监视偷看剪贴板内容的行为,但是任何应用程序(考虑到第三方剪贴板管理器)都可以在没有用户任何明确动作的情况下急切地检查剪贴板,因此这些事件并不一定要紧随其后。可观察到的数据利用率。

以下来自现实世界的松散比喻可能会说服您放弃寻找解决方案。假设您已申请某种配方并获得了专利。该专利已发布,任何人都可以阅读。您能否要求一种有效的方法来检测根据专利食谱烹饪的任何菜肴?

答案 2 :(得分:0)

在ms-windows上,看来您应该可以使用global hook(使用SetWindowsHookExA)来拦截相关的WM_PASTE消息。

答案 3 :(得分:0)

好吧,我以前经常用它来为我的工具创建全局热键。(在pynput official document中,它也支持linux / mac OS。

一个最小的例子:

from pynput.keyboard import GlobalHotKeys
import platform

# platform.system() can detect the device.(macOS/Windows/Linux)

def yourfunction():
    print("detect paste...") # each time when you pressed "Ctrl+V",it will call the function

if platform.system() == 'Windows':
    with GlobalHotKeys({"<ctrl>+v":yourfunction}) as listener:
        listener.join()

PS:它可以用在Chrome,Edge(大多数应用程序)中。但是不能用在游戏中。