I'm wanting to detect when the user has pasted something in ANY application, so I can follow it up with copying a new item into the clipboard (Use case: I have a list of items I'm copying from a database one-by-one into a web-page, and would like to automatically put the next one in the clipboard once I've finished pasting.)
Currently I have a button using Tkinter that copies a field when pressed using the following code.
self.root.clipboard_clear()
self.root.clipboard_append(text)
What I need then would be some way to detect when a paste has been performed in another application, so I can then load in the next item into the clipboard. I would like it to work on Win/Mac/Linux as I work across all three. Any ideas?
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, 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_()
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:
Peek at/read the clipboard contents
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.
Well,I used to use this to make a global hotkeys for my tools.(In pynput official document,it also support linux/mac OS.)
A minimal example:
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:It could used in Chrome,Edge(Most of application).But it couldn't used in games.
Related
I using the following action to generate ambient occlusion maps for models in maya:
Create and assign aiAmbientOcclusion to my model (the one I want to generate oa maps for).
Then, I go Arnold>Utilities>Render Selection To Texture.
Since this process is always the same I want to write a python script to automate it unfortunately I haven't found many useful examples about writing scripts for Arnold.
To add this functionality I must:
import mtoa.renderToTexture
that script is located in
the_way_to_my_install_folder/solidangle/mtoa/2017/scripts/mtoa
I saw that the script defines the class MtoARenderToTexture and I should pass an object to it. Now.
What kind of object I mush use and is there some sort of documentation for MtoARenderToTexture class?
I was able to do what I wanted using ether this tutorial and extending MtoARenderToTexture class.
I do not iclude all my scripts that load scene and manage scenes files as they very specific to my needs, but still think it's a good idea to share some very basic and fundamental elements that may be useful for some new entries as myself.
This is how my extended class looks like
import mtoa.renderToTexture as renderToTexture
import maya.cmds as cmds
class rkMtoaRtoT(renderToTexture.MtoARenderToTexture):
def __init__(self):
renderToTexture.MtoARenderToTexture.__init__(self)
self.dFolder = '~'
self.dResolution = 1024
self.dCameraSamples = 5
def doAutomaticExport(self):
renderToTexture.MtoARenderToTexture.create(self)
cmds.textFieldButtonGrp('outputFolder', e=True, tx=self.dFolder)
cmds.intFieldGrp('resolution', e=True, v1=self.dResolution)
cmds.intFieldGrp('aa_samples', e=True, v1=self.dCameraSamples)
renderToTexture.MtoARenderToTexture.doExport(self)
So because I have the unity-gtk-module installed, all gtk-applications export their menu over the dbus SessionBus. My goal is to extract a list of all available menu entries. I've already implemented this with the help of pydbus, but for some reason, this solution is highly unstable and some applications just flat out crash. The unity-gtk-module uses Gio's g_dbus_connection_export_menu_model () to export its GMenuModel modeled menu over dbus, so I thought it would make sense to try to use Gio to process the exported menu. Gio uses the GDBusMenuModel class to retrieve a menu from the bus. Python uses PyGObject for wrapping Gio:
from gi.repository import Gio
connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
menuModel = Gio.DBusMenuModel.get(connection, [bus-name e.g. ":1.5"], [object-path e.g. "/com/canonical/unity/gtk/window/0"])
Now menuModel should be wrapping the GMenuModel from my application. At this point I'm honestly a bit confused about how exactly the GMenuModel works (the Description is not really helping) but it seems I have to use a GMenuAttributeIter object to iterate through the entries. But when I try this:
iter = Gio.MenuModel.iterate_item_attributes(menuModel, 0) #0 is the index of the root node
this happens:
GLib-GIO-CRITICAL **: g_dbus_menu_model_get_item_attributes: assertion 'proxy->items' failed
GLib-GIO-CRITICAL **: GMenuModel implementation 'GDBusMenuModel' doesn't override iterate_item_attributes() and fails to return sane calues from get_item_attributes()
This probably happens because GDBusMenuModel inherits GMenuModel which provides these methods, but is abstract, so GDBusMenuModel should override them, which it doesn't (see link above, it provides just g_dbus_menu_model_get ()). If this is the case, how am I supposed to actually use this class as a proxy? And if it's not, what am I doing wrong?
I justed logged in to SO the first time after a few years and remembered that I've actually found a solution to this question (I think). Honestly, I can't remember what half of these words even mean, but at the time I wrote a script to accomplish the task posed in the title, and as far as I remember, in the end, it worked out: https://gist.github.com/encomiastical/caa0ee955300bc2a40ef55d123b06212
tl;dr in bold below
I'm currently developing a text-based adventure game, and I've implemented a basic saving system.
The process takes advantage of the 'pickle' module. It generates or appends to a file with a custom extension (when it is, in reality, a text file).
The engine pickles the player's location, their inventory, and, well, the last part is where it gets a little weird.
The game loads dialog from a specially formatted script (Here I mean as in an actor's script). Some dialog changes based on certain conditions (already spoken to them before, new event, etc.). So, for that third object the engine saves, it saves ALL dialog trees in their current positions. As in, it saves the literal script in its current state.
Here is the saving routine:
with open('save.devl','wb') as file:
pickle.dump((current_pos,player_inv,dia_dict),file)
for room in save_map:
pickle.dump(room,file)
file.close()
My problem is, this process makes a very ugly, very verbose, super large text file. Now I know that text files are basically the smallest files I can generate, but I was wondering if there was any way to compress or otherwise make more efficient the process of recording the state of everything in the game. Or, less preferably but better in the long run, just a smarter way to save the player's data.
The format of dialog was requested. Here is a sample:
[Isaac]
a: Hello.|1. I'm Evan.|b|
b: Nice to meet you.|1. Where are you going?\2.Goodbye.|c,closer|
c: My cousin's wedding.|1. Interesting. Where are you from?\2. What do you know about the ship?\3. Goodbye.|e,closer|
closer: See you later.||break|
e: It's the WPT Magnus. Cruise-class zeppelin. Been in service for about three years, I believe.||c|
standing: Hello, again.|1. What do you know about the ship?\2.Goodbye.|e,closer|
The name in brackets is how the program identifies which tree to call. Each letter is a separate branch in the tree. The bars separate the branch into three parts: 1. What the character says 2. The responses you are allowed 3. Where each response goes, or if the player doesn't respond, where the player is directed afterwards.
In this example, after the player has talked to Isaac, the 'a' branch is erased from the copy of the tree that the game stores in memory. It then permanently uses the 'standing' branch.
Pickle itself has other protocols that are all more compact than the default protocol (protocol 0) - which is the only one "text based" - the others are binary protocols.
But them, you hardly would get more than 50% of the file size - to be able to enhance the answer, we need to know better what you are saving, and if there are smarter ways to save your data - for example, by avoiding repeating the same sub-data structure if it is present in several of your rooms. (Although if you are using object identity inside your game, Pickle should take care of that).
That said, just change your pickle.dump calls to include the protocol parameter - the -1 value is equivalent to "HIGHEST_PROTOCOL", which is usually the most efficient:
pickle.dump(room,file, protocol=-1)
(loading the pickles do not require that the protocol is passed at all)
Aditionally, you might want to use Python's zlib interface to compress pickle data. That could give you another 20-30% file size reduction - you have to chain the calls to file.write, zlib.compress and pickle.dumps, so you will be easier with a little helper code - also you need to control file offsets, as zlib is not like pickle which advances the file pointer:
import pickle, zlib
def store_obj(file_, obj):
compressed = zlib.compress(pickle.dumps(obj, protocol=-1), level=9)
file_.write(len(compressed).to_bytes(4, "little"))
file_.write(compressed)
def get_obj(file_):
obj_size = int.from_bytes(file_.read(4), "little")
if obj_size == 0:
return None
data = zlib.decompress(self.file_.read(obj_size))
return pickle.loads(data)
Was wondering if it is possible to retrieve the name of the last object deleted.
I have looked into listHistory, but that seems to list the history of a selected or named object. I have also looked into undoHistory printqueue, which prints out the undo history into the script editor, but i can't retrieve that information from the console.
Any ideas? I've looked around and can't find any info on this. Thanks in advance.
You can get the list with:
undoInfo -q -pq;
There are a few really really good use cases for scalping Maya undo. Such as determining selection order after the fact. In any case it may be difficult to know what it actually was form the queue so you may need to undo and redo to get what the deleted object was.
So this may or may not work, mileage may vary.
As a side note since your restoring stuff why not save the object list at time of save. The order is going to be the same (ensured), so you can see the changes in the end and deletions as missing objects. See the objects in in a plain ls are in creation order. You can use this for rudimentary diff from import to import for example. Same works for deletions.
Catching any individual deletion after the fact is not possible. However you can stick an attributeDeleted scriptJob on objects you want to monitor - it will fire when they are deleted. If you really want to catch every object, a scriptJob listening for the event DagObjectCreated will let you hook the other scriptJob to each new object - however that's not a good idea most of the time, since it will create a ton of scriptJobs in your scene (plus you'd have to also loop through the scene on load and attach the same deletion callback to existing objects as well...)
import maya.cmds as cmds
from functools import partial
def objectDeleted(obj):
print "%s was deleted" % obj
def catch_deletion(obj):
cmds.scriptJob ( attributeDeleted = ( (obj + ".tx"), partial(objectDeleted, obj) ) )
catch_deletion('pCube1')
On Mac OS X the trackpad has support for several gestures, one is the two fingered swipe to scroll up, down, left, or right on a page. wxPython has a panel to help create scrolled widgets wx.lib.scrolledpanel. However it does not have support for gestures which is a real pain.
I have tried to modify the NSView, as it is done if it were a normal Objective C application, however the NSEvents use methods (touchesBeganWithEvent:, etc) that are subclassed to be used as a notification and handling of an event. This is unlike the Bind calls in wxPython. This would be fine however if Objective C allowed monkey patching... eg
def handleTouchBegin(event):
print "Hey a touch event has begun!"
view.touchBeganWithEvent_ = handleTouchBegin
but as you can guess PyObj C errors (because Objective C doesn't support monkey patching or not in any clean and nice fashion) and I get the following error
TypeError: cannot change a method
Ok well I could do what apple says and subclass it, but the object is already created, so how can I still capture the events. Of course there is also
NSEvent addGlobalMonitorForEventsMatchingMask:
and
NSEvent addLocalMonitorForEventsMatchingMask:
but those also disappoint in that they either don't even deal with the application (global deals with all the others) or doesn't deal with a single NSView's events (or does it and I am misinformed).
So how should I do this? Am I missing another option, I know I read something about NSResponder but from what I gathered that is what NSView is, an event responder and you don't add one to a NSView.
Are there observers like in QTKit such as for monitoring the load state changing ( https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/QTKitApplicationProgrammingGuide/AnatomyoftheQTKFramework/AnatomyoftheQTKFramework.html#//apple_ref/doc/uid/TP40008156-CH109-SW11 )?
ObjC doesn't support Python-style monkeypatching… but it does support some similar features like method swizzling, class posing, category interposition, etc. And PyObjC can do all of those things.
Off the top of my head, swizzling from PyObjC should look something like this:
def swizzle(cls, sel, func):
oldimp = cls.instanceMethodForSelector_(sel)
def wrapper(self, *args, **kwargs):
return func(self, oldimp, *args, **kwargs)
newmethod = objc.selector(wrapper,
selector=oldimp.selector, signature=oldimp.signature)
objc.classAddMethod(cls, sel, newmethod)
return wrapper