

Table of Contents
The notion of a service is a central concept in KDE's modular architecture. There is no strict technical implementation connected with this term - services can be plugins in the form of shared libraries, or they can be programs controlled via DCOP. By claiming to be of a certain service type, a service promises to implement certain APIs or features. In C++ terms, one can think of a service type as an abstract class, and a service as an implementation of that interface.
The advantage of this separation is clear: An application utilizing a service type does not have to know about possible implementations of it. It just uses the APIs associated with the service type. In this way, the used service can be changed without affecting the application. Also, the user can configure which services he prefers for certain features.
Some examples:
The HTML rendering engine used in Konqueror is an
embedable component that implements the service types
KParts/ReadOnlyPart and Browser/View.
In KDevelop HEAD, most functionality is packaged in
plugins with the service type KDevelop/Part. At startup,
all services with this type are loaded, such that you can extend the IDE in a
very flexible way.
In the icon view, Konqueror displays - if enabled -
thumbnail pictures of images, HTML pages, PDF and text files. This ability can
be extended. If you want it to display preview pictures of your own data files
with some MIME type, you can implement a service with service type
ThumbCreator.
Obviously, a service is not only characterized by the service types it
implements, but also by some properties. For example, a
ThumbCreator does not only claim to implement the C++ class with the type
ThumbCreator, it also has a list of MIME types it is
responsible for. Similarly, KDevelop parts have the programming language they
support as a property. When an application requests a service type, it can
also list constraints on the properties of the service. In the above example,
when KDevelop loads the plugins for a Java project, it asks only for the
plugins which have Java as the programming language property. For this
purpose, KDE contains a full-blown CORBA-like trader with
a complex query language.
New service types are added by installing a description of them into the
directory KDEDIR/share/servicetypes. In an automake
framework, this can be done with this Makefile.am
snippet:
kde_servicetypesdir_DATA = kdeveloppart.desktop EXTRA_DIST = $(kde_servicetypesdir_DATA)
The definition kdeveloppart.desktop of a
KDevelop part looks as follows:
[Desktop Entry] Type=ServiceType X-KDE-ServiceType=KDevelop/Part Name=KDevelop Part [PropertyDef::X-KDevelop-Scope] Type=QString [PropertyDef::X-KDevelop-ProgrammingLanguages] Type=QStringList [PropertyDef::X-KDevelop-Args] Type=QString
In addition to the usual entries, this example demonstrates how you declare
that a service has some properties. Each property definition corresponds
to a group [PropertyDef::name] in the configuration file. In
this group, the Type entry declares the type of the property.
Possible types are everything that can be stored in a
QVariant.
Service definitions are stored in the directory
KDEDIR/share/services:
kde_servicesdir_DATA = kdevdoxygen.desktop EXTRA_DIST = $(kde_servicesdir_DATA)
The content of the following example file
kdevdoxygen.desktop defines the
KDevDoxygen plugin with the service type
KDevelop/Part:
[Desktop Entry] Type=Service Comment=Doxygen Name=KDevDoxygen ServiceTypes=KDevelop/Part X-KDE-Library=libkdevdoxygen X-KDevelop-ProgrammingLanguages=C,C++,Java X-KDevelop-Scope=Project
In addition to the usual declarations, an important entry is
X-KDE-Library. This contains the name of the libtool
library (without the .la extension). It also fixes
(with the prefix init_ prepended) the name of the exported
symbol in the library which returns an object factory. For the above example,
the library must contain the following function:
extern "C" {
void *init_libkdevdoxygen()
{
return new DoxygenFactory;
}
};
The type of the factory class DoxygenFactory depends on
the specific service type the service implements. In our example of a KDevelop
plugin, the factory must be a KDevFactory (which
inherits KLibFactory). More common examples are
KParts::Factory
which is supposed to produce
KParts::ReadOnlyPart
objects or in most cases the generic
KLibFactory.
In order to use a shared library service in an application, you need to obtain a KService object representing it. This is discussed in the section about MIME types (and in a section about the trader to be written :-)
With the KService object at hand, you can very simply
load the library and get a pointer to its factory object:
KService *service = ...
QString libName = QFile::encodeName(service->library());
KLibFactory *factory = KLibLoader::self()->factory(libName);
if (!factory) {
QString name = service->name();
QString errorMessage = KLibLoader::self()->lastErrorMessage();
KMessageBox::error(0, i18n("There was an error loading service %1.\n"
"The diagnostics from libtool is:\n%2")
.arg(name).arg(errorMessage);
}
From this point, the further proceeding depends again on the service type. For generic plugins, you create objects with the method KLibFactory::create(). For KParts, you must cast the factory pointer to the more specific KParts::Factory and use its create() method:
if (factory->inherits("KParts::Factory")) {
KParts::Factory *partFactory = static_cast<KParts::Factory*>(factory);
QObject *obj = partFactory->createPart(parentWidget, widgetName,
parent, name, "KParts::ReadOnlyPart");
...
} else {
cout << "Service does not implement the right factory" << endl;
}
A DCOP service is usually implemented as a program that is started up when it is
needed. It then goes into a loop and listens for DCOP connections. The program
may be an interactive one, but it may also run completely or for a part of its
lifetime as a daemon in the background without the user noticing it. An example
for such a daemon is kio_uiserver, which implements user interaction
such as progress dialog for the KIO library. The advantage of such a centralized
daemon in this context is that e.g. the download progress for several different
files can be shown in one window, even if those downloads were initiated from
different applications.
A DCOP service is defined differently from a shared library service. Of course, it doesn't specify a library, but instead an executable. Also, DCOP services do not specify a ServiceType line, because usually they are started by their name. As additional properties, it contains two lines:
X-DCOP-ServiceType specifies the way the service is
started. The value Unique says that the service must not be
started more than once. This means, if you try to start this service (e.g. via
KApplication::startServiceByName(), KDE looks whether it is already
registered with DCOP and uses the running service. If it is not registered yet,
KDE will start it up and wait until is registered. Thus, you can immediately
send DCOP calls to the service. In such a case, the service should be implemented
as a
KUniqueApplication.
The value Multi for X-DCOP-ServiceType says that multiple
instances of the service can coexist, so every attempt to start the service
will create another process. As a last possibility the value None
can be used. In this case, a start of the service will not wait until it
is registered with DCOP.
X-KDE-StartupNotify should normally be set to false. Otherwise, when
the program is started, the task bar will show a startup notification, or, depending
on the user's settings, the cursor will be changed.
Here is the definition of kio_uiserver:
[Desktop Entry] Type=Service Name=kio_uiserver Exec=kio_uiserver X-DCOP-ServiceType=Unique X-KDE-StartupNotify=false
A DCOP service is started with one of several methods in the KApplication class:
DCOPClient *client = kapp->dcopClient();
client->attach();
if (!client->isApplicationRegistered("kio_uiserver")) {
QString error;
if (KApplication::startServiceByName("kio_uiserver", QStringList(), &error))
cout << "Starting kioserver failed with message " << error << endl;
}
...
QByteArray data, replyData;
QCString replyType;
QDataStream arg(data, IO_WriteOnly);
arg << true;
if (!client->call("kio_uiserver", "UIServer", "setListMode(bool)",
data, replyType, replyData))
cout << "Call to kio_uiserver failed" << endl;
...
Note that the example of a DCOP call given here uses explicit marshalling of arguments. Often you will want to use a stub generated by dcopidl2cpp instead, because it is much simpler and less error prone.
In the example given here, the service was started "by name", i.e. the
first argument to KApplication::startServiceByName() is
the name is appearing in the Name line of the desktop
file. An alternative is to use
KApplication::startServiceByDesktopName(), which takes
the file name of its desktop file as argument, i.e. in this case
"kio_uiserver.desktop".
All these calls take a list of URLs as a second argument, which is given
to the service on the command line. The third argument is a pointer to a
QString. If starting the service fails, this argument
is set to a translated error message.