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:
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.
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.
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.
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 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 |
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.
Bernd Gehrmann [email protected]