Development/Architecture/KDE3/Services

From KDE TechBase

KDE's Services and Service Types

What are KDE services?

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

Defining service types

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.

Defining shared library services

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.

Using shared library services

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("Error while 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;
}

Defining DCOP services

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

Using DCOP services

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


Initial Author: Bernd Gehrmann