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
Related
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.
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
I have a wxPython GUI with a very large grid. I am using similar code to the GridHugeTable.py example from the wxPython demo -- i.e., using PyGridTableBase to make a virtual grid.
I am running into trouble when I try to interactively add columns to this grid, however.
Calling AppendCols(1) results in this error:
wx._core.PyAssertionError: C++ assertion "Assert failure" failed at /Users/vagrant/pisi-64bit/tmp/wxPython-3.0.2.0-3/work/wxPython-src-3.0.2.0/src/generic/grid.cpp(1129) in AppendCols():
Called grid table class function AppendCols but your derived table class does not override this function
But if I try to overwrite AppendCols in my table class, the application just hangs indefinitely and never resolves. It hangs even if there is actually nothing in my custom AppendCols method at all...
class HugeTable(gridlib.PyGridTableBase):
"""
Table class for virtual grid
"""
def __init__(self, log, num_rows, num_cols):
gridlib.PyGridTableBase.__init__(self)
def AppendCols(self, *args):
pass
I've been able to overwrite other methods successfully, (setValue, getValue, getColLabelValue, etc.), so I'm not sure what is different here.
Update:
I returned to this problem after a while away. I no longer get the wx.__core.PyAssertionError. However, I still can't get my custom AppendCols method to work. I can't figure out what to put in AppendCols to make a new column actually show up.
I'm not sure how to look in the source code -- none of the Python documentation seems to have what I'm looking for, so maybe I need to go digging in wxWidgets? The documentation hasn't helped: https://wiki.wxpython.org/wxPyGridTableBase.
The clue I needed is in the the demo code for custom table.
So, this code works (leaving out my custom logic and just including the bare bones):
def AppendCols(self, *args):
msg = gridlib.GridTableMessage(self, # The table
gridlib.GRIDTABLE_NOTIFY_COLS_APPENDED, # what we did to it
1) # how many
self.GetView().ProcessTableMessage(msg)
return True
From my other question here on SO, I asked how to retrieve the current playing song from Windows Media Player and Zune, I got an answer from a c++ dev who gave me an explanation of how I would do this for WMP.
However, I am no C++ dev, nor am I very experienced with the pywin32 library. And on-top of all that, the documentation on all this (especially concerning WMP) is horrible.
Therefor, I need your help understanding how I would do the following in Python.
Source
I have working code in C++ to print the name of media currently
playing in WMP. It's a simple console application (78 lines of code).
Steps:
1) implements a basic COM object implementing IUnknown, IOleClientSite, IServiceProvider and IWMPRemoteMediaServices. This is
straightforward (sort of, your mileage may vary) using the ATL
template CComObjectRootEx. The only methods needing (simple) code are
IServiceProvider::QueryService and
IWMPRemoteMediaServices::GetServiceType. All other methods may return
E_NOTIMPL
2) Instantiate the "WMPlayer.OCX" COM object (in my case, via CoCreateInstance)
3) Retrieve from the object an IOleObject interface pointer via QueryInterface
4) Instanciate an object from the class seen in 1) (I use the CComObject<>::CreateInstance template)
5) Use the SetClientSite method from the interface you got at 3), passing a pointer to your OleClientSite implementation.
6) During the SetClientSite call, WMP will callback you: fisrt asking for an IServiceProvider interface pointer, second calling the
QueryService method, asking for an IWMPRemoteMediaServices interface
pointer. Return your implementation of IWMPRemoteMediaServices and,
third, you will be called again via GetServiceType. You must then
return "Remote". You are now connected to the WMP running instance
7) Query the COM object for an IWMPMedia interface pointer
8) If 7) didn't gave NULL, read the the IWMPMedia::name property.
9) DONE
All the above was tested with VS2010 / Windows Seven, and with WMP
running (if there is no Media Player process running, just do
nothing).
I don't know if yoy can/want to implement COM interface and object in
Python. If you are interested by my C++ code, let me know. You could
use that code in a C++ DLL, and then call it from python.
I know a little bit about the win32api.
At the first step, I really don't know what to do, googling IOleClientSite results in the msdn documentation, it's an interface. However, that's where I get stuck already. I can't find anything (might just be my horrendous googling skills) on working with these things in Python.
The second step:
WMP = win32com.client.Dispatch("WMPlayer.OCX")
Alright, that's doable.
On to the third step. QueryInterface -
"regardless of the object you have, you can always call its QueryInterface() method to obtain a new interface, such as IStream."
source
However, not for me. As I understand his explanation, I think it means that every com object sort of "inherits" three methods from IUnknown, one of which is QueryInterface, however this does not seem the case since calling QueryInterface on my WMP object fails miserably. (Object has no attribute 'QueryInterface')
I could ramble on, but I believe you got the point, I have no idea how to work with this. Can anyone help me out with this one? Preferably with code examples, but resources/documentation is welcome too.
Almost final answser but CAN'T finish.
I seems that pythoncom can't be used to implement custom Interface without the help of a C++ module.
Here is an answser from Mark Hammon (Mon, 13 Jan 2003): How to create COM Servers with IID_IDTExtensibility2 interface
Sorry - you are SOL. To support arbitary interfaces, you need C++
support, in the form of an extension module. There is a new "Univgw"
that may help you out, but I dont know much about this
I am not able to find anything about that "Univgw" thing...
The comtypes python module is intended to resolve the problem, and I found links saying it does, but I can't make it works with my fresh new Python 3.3. It's Python 2.x code. comtypes seems outdated and unmaintained.
Step 1 OK for IOleClientSite and IServiceProvider, KO for IWMPRemoteMediaServices
Step 2, 3, 4 and 5 OK
Step 6, 7 and 8 can't be implemented without IWMPRemoteMediaServices :-(
disclaimer: complete newbie in Python, please don't yell
import pythoncom
import win32com.client as wc
from win32com.axcontrol import axcontrol
import win32com.server as ws
from win32com.server import util
from win32com.server.exception import COMException
import winerror
import pywintypes
# Windows Media Player Custom Interface IWMPRemoteMediaServices
IWMPRemoteMediaServices = pywintypes.IID("{CBB92747-741F-44FE-AB5B-F1A48F3B2A59}")
class OleClientSite:
_public_methods_ = [ 'SaveObject', 'GetMoniker', 'GetContainer', 'ShowObject', 'OnShowWindow', 'RequestNewObjectLayout', 'QueryService' ]
_com_interfaces_ = [ axcontrol.IID_IOleClientSite, pythoncom.IID_IServiceProvider ]
def SaveObject(self):
print("SaveObject")
raise COMException(hresult=winerror.E_NOTIMPL)
def GetMoniker(self, dwAssign, dwWhichMoniker):
print("GetMoniker ")
raise COMException(hresult=winerror.E_NOTIMPL)
def GetContainer(self):
print("GetContainer")
raise COMException(hresult=winerror.E_NOTIMPL)
def ShowObject(self):
print("ShowObject")
raise COMException(hresult=winerror.E_NOTIMPL)
def OnShowWindow(self, fShow):
print("ShowObject" + str(fShow))
raise COMException(hresult=winerror.E_NOTIMPL)
def RequestNewObjectLayout(self):
print("RequestNewObjectLayout")
raise COMException(hresult=winerror.E_NOTIMPL)
def QueryService(self, guidService, riid):
print("QueryService",guidService,riid)
if riid == IWMPRemoteMediaServices:
print("Known Requested IID, but can't implement!")
raise COMException(hresult=winerror.E_NOINTERFACE)
else:
print("Requested IID is not IWMPRemoteMediaServices" )
raise COMException(hresult=winerror.E_NOINTERFACE)
if __name__=='__main__':
wmp = wc.Dispatch("WMPlayer.OCX")
IOO = wmp._oleobj_.QueryInterface(axcontrol.IID_IOleObject)
pyOCS = OleClientSite()
comOCS = ws.util.wrap(pyOCS, axcontrol.IID_IOleClientSite)
IOO.SetClientSite(comOCS)
I'm having a little bit of trouble assigning values to a QLineEdit. I've read the documentation and feel that the QLineEdit.SetText() command will be used at some point.
I've used Qt Designer to design a GUI for my software. On the main window (MainWindow.py, with an accompanying ui_MainWindow.py setup file), I have a LineEdit (lineEditScanBarcode) which has strong focus. I've managed to pull input from that LineEdit pretty well. What I'd like to do is this:
If the input in LineEditScanBarcode = x, then assign the name 'John Smith' to a secondary QLineEdit (lineEditUser) which has a zero focus policy. This is what I have so far:
def ScanBarcode(self):
barcode = self.lineEditScanBarcode.text()
self.lineEditScanBarcode.clear()
if barcode == '12345':
print("Welcome John")
self.lineEditUser.setText() = 'John'
else: print("Sorry, user not recognised.")
Upon running this, I get the following error:
Syntax Error: can't assign to function call
I've had a look at the above error, but I'm still unsure as to what's going on here. I still have no idea to open one window on top of another (this software package will have about 10 windows), but that's another story!
Is my logic here on track? I've never used Qt before, so my understanding of the intricacies involved is lacking to say the least.
Any input would be great!
As the comment states, the error is on this line:
self.lineEditUser.setText() = 'John'
You are attempting to assign the value 'John' to that functioncall (as the error states). If you review the documentation for QLineEdit in PyQT, you'll see that QLineEdit.setText() requires a string to be passed to it.
So, what you need to do instead is pass the value 'John' to the function like so:
self.lineEditUser.setText('John')
On another note your idea that your
software package will have about 10 windows
is definitely something that you want to reexamine. More windows, especially when undocked and floating independently will no doubt cause usability issues. I'd strongly recommend sharing your ideas over at UserExperience.SE.