p
DCOP is KDE's acronym for it's "Desktop Communications-Oriented Protocol" - basically a lightweight and simple mechanism for inter-process communications (IPC). DCOP allows two running applications to exchange messages or other information or exercise control over each other.
While the DCOP implementation is convenient for C++ programmers, it presents some difficulties for Python programmers. The DCOP extensions that have been added to PyKDE should make most DCOP applications (either DCOP-client or DCOP-enabled applications) simple to write and reliable to run
The methods for packing/unpacking QByteArrays are available to the programmer, but are primarily used transparently by the other PyKDE DCOP extensions. The client and export extensions are two Python modules that are included and installed as part of PyKDE.
Accessing a DCOP method in another application requires 3 pieces of information: the name of the application to be accessed, the name of the DCOP object which holds the method to be called, and the name of the method itself.
The easiest way to collect the required information is to use the kdcop application that comes with PyKDE. kdcop is graphical application that looks like the image shown.
Look at the entry for kicker, which has been expanded in the image. Underneath kicker (the application name - kicker is the panel on the standard KDE screen) is a list of DCOP objects, for example, Panel. Under each object is a list of methods the application/object exposes, for example, "int panelPosition ()". This indicates the method panelPosition takes no arguments and returns an integer.
There are two ways to use the DCOP extensions to call the panelPosition method. The first is from the application level, the second is from the object level. We can use the "application level" in this case, because the object name "Panel" can be valid Python identifier (not all object names have this property).
import dcopext # ! other imports not shown ! app = KApplication () dcop = app.dcopClient () d = dcopext.DCOPApp ("kicker", dcop) ok, panelPos = d.Panel.panelPosition () |
That's all there's to it in this case. We import dcopext, which contains the client extension classes; from the KApplication instance, we "borrow" the DCOPClient instance (dcop); we create a DCOPApp instance, passing it the name of the app ("kicker") and the DCOPClient instance; we call kicker's Panel object's panelPosition method (d.Panel.panelPosition); lastly, the integer value is returned to our application (panelPos) as the second item in a tuple - the first element of the tuple (ok) is a boolean value indicating whether the call succeeded (True) or failed (False).
Many of the DCOP object names can't be used as Python identifiers (for example,"0x8239ae0" or KIO::Scheduler in kicker, or EditInterface#1, which kwrite exports). In that case, it's necessary to write the code at the object level, constructing a DCOPObj instead of a DCOPApp (DCOPApp actually constructs a DCOPObj behind the scenese in the example above).
import dcopext # ! other imports not shown ! o = dcopext.DCOPObj ("kicker", dcop, "Panel") ok, panelPos = o.panelPosition () |
In this example, 'o' is a DCOPObj. In constructing 'o', we add a string representation of the name of the object ("Panel") to the application name and DCOPClient object. We then use the DCOPObj 'o' to call the the method (panelPosition) that the object supports.
In the example above, kicker was the name of the application and the id we used to reference the application as well. kicker is an example of a unique application - only one instance of kicker can be running at any time.
Many applications (konqueror, for example) can have several instances running at the same time. kdcop would display multiple instances like this:
kdcop shows 3 instances of konqueror running in the example above. To perform a DCOP call in this case, we'd need to know which instance of konqueror we want to send the call to. The suffix on each instance of konqueror is the PID of the instance running. We simply pass the full id (app name + pid - eg konqueror-14409) when constructing DCOPApp or DCOPObj.
If you instantiate the application you want to communicate with from your own application (that will be making the DCOP calls), methods like KApplication.startServiceByDesktopName will let you start the app and also return both the PID of the started app and the complete identifier string needed to initiate DCOP communications. The identifier's name portion may or may not be the same as the name of the application (see the example_dcopexport.py example program, whose ID is "petshop-####" (#### is the PID of the application instance).
char | short | int |
long | unsigned char | unsigned short |
unsigned int | unsigned long | uchar |
ushsort | uint | ulong |
Q_INT32 | pid_t | float |
double | QString | QStringList |
QCString | KURL | KURL::List |
QSize | QRect | QRegion |
QFont | QCursor | QPixmap |
QColor | QColorGroup | QPalette |
QBrush | QWidget::FocusPolicy | DCOPRef |
QVariant | QDate | QTime |
QDateTime | QImage | QKeySequence |
QPen | QPicture | QPointArray |
QValueList<DCOPRef> | QValueList<QCString> | QMap<QCString,DCOPRef> |
QMap<QCString,DCOPRef> |
Data conversion between C++ and Python types is done transparently. The integer types map to Python int or Python long, the decimal types to Python double. A Python string can be used for any argument that requires a QString or QCString (return types will always be the Qt object type). The QValueList types take or return a Python list of the indicated object. The QMap types take or return a Python dict with the first type as the key and the second type as data. All other types use the same object type in Python and Qt (for instance, QPoint or QStringList).
It's possible to add support for more types in the future. To be added, a type requires a pair of overloaded QDataStream operators ("<<" and ">>"). Types must also exist in the libs that PyQt and PyKDE support - types specific to applications (like konqueror) cannot be supported at this time.
The dcopext module consists of 3 classes (DCOPApp, DCOPObj and DCOPMeth) corresponding to applications, objects and methods respectively. These classes have additional variables and methods:
If a method isn't valid, it's rtype, argtypes and argnames values will all be None.
Enabling a Python application to handle DCOP calls is even simpler than making calls as a DCOP client. Suppose a Python application has two methods we want to appear as int getValue() and void setValue(int). The corresponding Python methods are get_value() set_value(i). We want to export these methods under the object "Value". Here's the code:
from dcopexport import DCOPExObj # ! other imports not shown ! class ValueObject (DCOPExObj): def __init__ (self, id="Value"): DCOPExObj.__init__ (self, id) self.value = 0 self.addMethod ("int getValue()", self.get_value) self.addMethod ("void setValue(int)", self.set_value) def get_value(self): return self.value def set_value (self, i): self.value = i |
Note that the module for the DCOPExObj class is "dcopexport". The Python methods may be part of the DCOPExObj subclass, part of another class, or global Python functions. They must be callable from the DCOPExObj subclass being created. The dcopexport extension takes care of everything else, including the "functions()" method which applications (yours or kdcop, for example) can call to find out which methods are available and their return and argument types. You can have multiple instances of DCOPExObj in a program. All of the data types listed above are supported transparently - you don't have to pack or unpack QByteArrays.
NOTE: It isn't necessary to use the dcop_add and dcop_next functions or worry about QByteArrays at all when using dcopext or dcopexport as shown above. Those modules handle the packing and unpacking details automatically behind the scenes.
The dcop_add and dcop_next functions are available in the PyKDE tdecore module (they may be relocated to a different module in the future). They use a QDataStream to operate on a QByteArray. The QByteArray can be thought of as a stack (a FIFO stack though) - dcop_add pushes objects onto the stack, dcop_next pops objects off the stack. The first object popped off will be the first object pushed on, etc.
The dcop_add function is actually a group of overloaded functions, some of which take different argument counts. Here are some examples:
from tdecore import dcop_add, dcop_next from qt import QByteArray, QDataStream, IO_ReadOnly, IO_WriteOnly, QString,\ QCString, QValueList<QCString> from dcopext import numericTypes, stringTypes b = QByteArray () s = QDataStream (b, IO_WriteOnly) i = 6 d = 3.14 t = QString ("Hello, World") x = QCString ("One") y = QCString ("Two") z = QCString ("Three") l = [x, y, z] dcop_add (s, i, "long") dcop_add (s, d, "double") dcop_add (s, t) dcop_add (s, x) dcop_add (s, l, "QValueList<QCString>") |
Notice that for numeric types (integer or decimal) an additional string is needed to specify the C++ type of the object - that's because Python has only 3 basic numeric types, while C++ has at least 10 basic numeric types plus variations via typedefs.
Also, the QValueList (and QMap - not shown) type needs a qualifier - a Python list type doesn't know (or care) what the type of its elements is.
Other types (QString, QCString) are uniquely typed, so no modifier is needed.
While it may change in the future, dcop_add right now retains the variable argument lists. You can handle this in your own code easily if you import "numericTypes" and "stringTypes" from dcopext as shown above. The following code will sort things out:
# atype is the type of the argument being processed (as a string) # value is the object being packed into the QByteArray if atype in numericTypes: dcop_add (s, value, atype) elif atype in stringTypes and isinstance (value, str): dcop_add (s, eval ("%s('%s')" % (atype, value))) elif atype.startswith ("QMap") or atype.startswith ("QValueList"): dcop_add (params, value, atype) else: dcop_add (s, value) |
At least in DCOP related applications, all of the necessary type information is always easily available. The first if clause above processes numeric types; the second if clause allows you to use Python strings in place of Qt's QString or QCString types; the third if clause handles QValueList and QMap based types; the else clause handles everything else.
Unpacking a QByteArray is simpler - dcop_next always takes a QDataStream instance and a type name string. The code below assumes the same set of imports as above:
# b is a QByteArray to be unpacked s = QDataStream (b, IO_ReadOnly) i1 = dcop_next (s, "long") d1 = dcop_next (s, "double") t1 = dcop_next (s, "QString") x1 = dcop_next (s, "QCString") l1 = dcop_next (s, "QValueList<QCString>") |
Of course the type specified in dcop_next to unpack the object must match the type of the object originally packed, and must happen in the same order (you can't use this to cast or convert types). i1, d1, etc should contain the same values as i, d, etc above.
The types that dcop_add/dcop_next can handle are the same types listed in the dcopext section above.
The code for dcopext and dcopexport is based on pydcop.py and pcop.cpp written by Torben Weis and Julian Rockey. It's available in the dcoppython/ section of the kde-bindings source code, and can be used to implement DCOP communication without using PyQt or PyKDE.