1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN
"http://www.w3.org/TR/html4/loose.dtd"">
<html>p
<head>
<title>Examples</title>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<meta name="GENERATOR" content="Quanta Plus">
</head>
<p>
<DIV
CLASS="NAVHEADER"
><TABLE SUMMARY="Header navigation table" WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0">
<TR><TH COLSPAN="3" ALIGN="center">Python Bindings for KDE (PyKDE-3.16.0)</TH></TR>
<TR><TD WIDTH="10%" ALIGN="left" VALIGN="bottom"><A HREF="examples.html" ACCESSKEY="P">Prev</A></TD>
<TD WIDTH="80%" ALIGN="center" VALIGN="bottom"></TD>
<TD WIDTH="10%" ALIGN="right" VALIGN="bottom"><A HREF="limits.html" ACCESSKEY="N">Next</A></TD>
</TR>
</TABLE><HR ALIGN="LEFT" WIDTH="100%"></DIV>
<h1>DCOP and Extensions</h1>
<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.
</p>
<p>
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
</p>
<h2>What Extensions?</h2>
There are three basic extensions added to PyKDE that are not part of KDE itself:
<dl>
<dt>Packing/Unpacking QByteArrays</dt>
<dd>
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
</dd>
<dt>Client Extensions</dt>
<dd>
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.
</dd>
<dt>DCOP Enabling (Export) Extensions</dt>
<dd>
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.
</dd>
</dl>
<p>
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.
</p>
<h2>Calling DCOP Methods</h2>
<p>
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.
</p>
<h3>Collection the Information</h3>
<p>
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.
</p>
<IMG src="images/kdcop1.png" align="middle" border="0">
<h3>Application/Object/Method Information</h3>
<p>
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.
</p>
<h3>Writing the Code</h3>
<p>
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).
</p>
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
<TR><TD><PRE CLASS="PROGRAMLISTING">
import dcopext
# ! other imports not shown !
app = KApplication ()
dcop = app.dcopClient ()
d = dcopext.DCOPApp ("kicker", dcop)
ok, panelPos = d.Panel.panelPosition ()
</PRE></TD></TR></TABLE>
<p>
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).
</p>
<p>
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).
</p>
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
<TR><TD><PRE CLASS="PROGRAMLISTING">
import dcopext
# ! other imports not shown !
o = dcopext.DCOPObj ("kicker", dcop, "Panel")
ok, panelPos = o.panelPosition ()
</PRE></TD></TR></TABLE>
<p>
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.
</p>
<h3>More on Application Names</h3>
<p>
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.
</p>
<p>
Many applications (konqueror, for example) can have several instances running at the same
time. kdcop would display multiple instances like this:
</p>
<IMG src="images/kdcop2.png" border="0">
<p>
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.
</p>
<p>
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).
</p>
<h3>Data Types</h3>
The DCOP extensions will support any of the following C++ data types:
<table><TR><TD>char</TD><TD>short</TD><TD>int</TD></TR>
<TR><TD>long</TD><TD>unsigned char</TD><TD>unsigned short</TD></TR>
<TR><TD>unsigned int</TD><TD>unsigned long</TD><TD>uchar</TD></TR>
<TR><TD>ushsort</TD><TD>uint</TD><TD>ulong</TD></TR>
<TR><TD>Q_INT32</TD><TD>pid_t</TD><TD>float</TD></TR>
<TR><TD>double</TD><TD>QString</TD><TD>QStringList</TD></TR>
<TR><TD>QCString</TD><TD>KURL</TD><TD>KURL::List</TD></TR>
<TR><TD>QSize</TD><TD>QRect</TD><TD>QRegion</TD></TR>
<TR><TD>QFont</TD><TD>QCursor</TD><TD>QPixmap</TD></TR>
<TR><TD>QColor</TD><TD>QColorGroup</TD><TD>QPalette</TD></TR>
<TR><TD>QBrush</TD><TD>QWidget::FocusPolicy</TD><TD>DCOPRef</TD></TR>
<TR><TD>QVariant</TD><TD>QDate</TD><TD>QTime</TD></TR>
<TR><TD>QDateTime</TD><TD>QImage</TD><TD>QKeySequence</TD></TR>
<TR><TD>QPen</TD><TD>QPicture</TD><TD>QPointArray</TD></TR>
<TR><TD>QValueList<DCOPRef></TD><TD>QValueList<QCString></TD><TD>QMap<QCString,DCOPRef></TD></TR>
<TR><TD>QMap<QCString,DCOPRef></TD><TD></TD><TD></TD></TR>
</table>
<p>
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).
</p>
<p>
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.
</p>
<h3>Other Extension Features</h3>
<p>
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:
<ul>
<li> DCOPApp.objects - returns a list of the applications DCOP objects. example: d.objects</li>
<li> DCOPApp.object(objname) - returns a DCOPObj for the DCOPObject. example: d.object ("Panel")</li>
<li> DCOPObj.methods - returns a list of the methods and object has. example: o.methods</li>
<li> DCOPObj.method (methname) - returns an DCOPMeth instance corresponding to the method, which
can be called. example: o.method("panelPosition")</li>
<li> DCOPMeth.valid - returns whether the method is valid or not (True/False). example:
d.Panel.panelPosition.valid</li>
<li>DCOPMeth.rtype - a method's return type. example d.Panel.panelPosition.rtype</li>
<li>DCOPMeth.argtypes - a list of the method's argument types. example d.Panel.panelPosition.argtypes</li>
<li>DCOPMeth.argnames - a list of the method's argument names. example d.Panel.panelPosition.argnames</li>
</ul>
<p>
If a method isn't valid, it's rtype, argtypes and argnames values will all be None.
</p>
</p>
<h2>DCOP Enabling a Python Application</h2>
<p>
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:
</p>
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
<TR><TD><PRE CLASS="PROGRAMLISTING">
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
</PRE></TD></TR></TABLE>
<p>
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.
</p>
<h2>Packing and Unpacking QByteArrays</h2>
<p>
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.
</p>
<p>
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.
</p>
<p>
The dcop_add function is actually a group of overloaded functions, some of which take
different argument counts. Here are some examples:
</p>
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
<TR><TD><PRE CLASS="PROGRAMLISTING">
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>")
</PRE></TD></TR></TABLE>
<p>
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.
</p>
<p>
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.
</p>
<p>
Other types (QString, QCString) are uniquely typed, so no modifier is needed.
</p>
<p>
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:
</p>
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
<TR><TD><PRE CLASS="PROGRAMLISTING">
# 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)
</PRE></TD></TR></TABLE>
<p>
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.
</p>
<p>
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:
</p>
<TABLE BORDER="0" BGCOLOR="#E0E0E0" WIDTH="100%">
<TR><TD><PRE CLASS="PROGRAMLISTING">
# 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>")
</PRE></TD></TR></TABLE>
<p>
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.
</p>
<p>
The types that dcop_add/dcop_next can handle are the same types listed in the dcopext
section above.
</p>
<h2>Thanks</h2>
<p>
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.
</p>
<DIV CLASS="NAVFOOTER">
<HR ALIGN="LEFT" WIDTH="100%">
<TABLE SUMMARY="Footer navigation table" WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0" >
<TR>
<TD WIDTH="33%" ALIGN="left" VALIGN="top"><A HREF="examples.html" ACCESSKEY="P">Prev</A></TD>
<TD WIDTH="34%" ALIGN="center" VALIGN="top"><A HREF="index.html" ACCESSKEY="H">Home</A></TD>
<TD WIDTH="33%" ALIGN="right" VALIGN="top"><A HREF="limits.html" ACCESSKEY="N">Next</A></TD>
</TR>
<TR>
<TD WIDTH="33%" ALIGN="left" VALIGN="top">Templates and Example Programs</TD>
<TD WIDTH="34%" ALIGN="center" VALIGN="top"> </TD>
<TD WIDTH="33%" ALIGN="right" VALIGN="top">General Limitations</TD>
</TR>
</TABLE>
</DIV>
</body>
</html>
|