p

Python Bindings for KDE (PyKDE-3.15)
Prev |  | Next

# DCOP and Extensions

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

## What Extensions?

There are three basic extensions added to PyKDE that are not part of KDE itself:

Packing/Unpacking QByteArrays
  DCOP passes data between applications using QByteArrays. QByteArrays can be difficult to pack or unpack using PyQt or PyKDE, so PyKDE has additional methods (dcop_add and dcop_next) to make these operations simpler in Python
Client Extensions
  PyKDE's DCOP client extensions make it easy and natural to call DCOP methods in other DCOP-enabled applications - the application or DCOP object being referenced look like Python classes, and the method being called looks to the programmer like a Python method.
DCOP Enabling (Export) Extensions
  Another set of extensions makes it trivial to expose an application's methods via DCOP to other applications. All that is required is to subclass a pre-written Python class and provide a list of the methods to expose, along with a method signature listing the name of the method, it's return type, and the the types of its arguments.

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.

## Calling DCOP Methods

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.

### Collection the Information

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.

### Application/Object/Method Information

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.

### Writing the Code

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.

### More on Application Names

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).

### Data Types

The DCOP extensions will support any of the following C++ data types:

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.

### Other Extension Features

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:

- DCOPApp.objects - returns a list of the applications DCOP objects. example: d.objects
- DCOPApp.object(objname) - returns a DCOPObj for the DCOPObject. example: d.object ("Panel")
- DCOPObj.methods - returns a list of the methods and object has. example: o.methods
- DCOPObj.method (methname) - returns an DCOPMeth instance corresponding to the method, which can be called. example: o.method("panelPosition")
- DCOPMeth.valid - returns whether the method is valid or not (True/False). example: d.Panel.panelPosition.valid
- DCOPMeth.rtype - a method's return type. example d.Panel.panelPosition.rtype
- DCOPMeth.argtypes - a list of the method's argument types. example d.Panel.panelPosition.argtypes
- DCOPMeth.argnames - a list of the method's argument names. example d.Panel.panelPosition.argnames

If a method isn't valid, it's rtype, argtypes and argnames values will all be None.

## DCOP Enabling a Python Application

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.

## Packing and Unpacking 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 kdecore 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 kdecore 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.

## Thanks

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.

Prev | Home | Next
Templates and Example Programs |  | General Limitations