p Examples

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 TQByteArrays
DCOP passes data between applications using TQByteArrays. TQByteArrays can be difficult to pack or unpack using PyTQt 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 TQByteArrays 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:
charshortint
longunsigned charunsigned short
unsigned intunsigned longuchar
ushsortuintulong
Q_INT32pid_tfloat
doubleTQStringTQStringList
TQCStringKURLKURL::List
TQSizeTQRectTQRegion
TQFontTQCursorTQPixmap
TQColorTQColorGroupTQPalette
TQBrushTQWidget::FocusPolicyDCOPRef
TQVariantTQDateTQTime
TQDateTimeTQImageTQKeySequence
TQPenTQPictureTQPointArray
TQValueList<DCOPRef>TQValueList<TQCString>TQMap<TQCString,DCOPRef>
TQMap<TQCString,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 TQString or TQCString (return types will always be the TQt object type). The TQValueList types take or return a Python list of the indicated object. The TQMap 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 TQt (for instance, TQPoint or TQStringList).

It's possible to add support for more types in the future. To be added, a type requires a pair of overloaded TQDataStream operators ("<<" and ">>"). Types must also exist in the libs that PyTQt 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:

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

Packing and Unpacking TQByteArrays

NOTE: It isn't necessary to use the dcop_add and dcop_next functions or worry about TQByteArrays 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 TQDataStream to operate on a TQByteArray. The TQByteArray 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 TQByteArray, TQDataStream, IO_ReadOnly, IO_WriteOnly, TQString,\
    TQCString, TQValueList<TQCString>
from dcopext import numericTypes, stringTypes    
    
b = TQByteArray ()
s = TQDataStream (b, IO_WriteOnly)

i = 6
d = 3.14
t = TQString ("Hello, World")
x = TQCString ("One")
y = TQCString ("Two")
z = TQCString ("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, "TQValueList<TQCString>")

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 TQValueList (and TQMap - not shown) type needs a qualifier - a Python list type doesn't know (or care) what the type of its elements is.

Other types (TQString, TQCString) 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 TQByteArray

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 ("TQMap") or atype.startswith ("TQValueList"):
	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 TQt's TQString or TQCString types; the third if clause handles TQValueList and TQMap based types; the else clause handles everything else.

Unpacking a TQByteArray is simpler - dcop_next always takes a TQDataStream instance and a type name string. The code below assumes the same set of imports as above:


# b is a TQByteArray to be unpacked
s = TQDataStream (b, IO_ReadOnly)

i1 = dcop_next (s, "long")
d1 = dcop_next (s, "double")
t1 = dcop_next (s, "TQString")
x1 = dcop_next (s, "TQCString")
l1 = dcop_next (s, "TQValueList<TQCString>")

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 PyTQt or PyKDE.