Panel Applets - Introduction

Beginning with PyKDE-3.8, support is provided for writing panel applets (based on KPanelApplet) completely in Python. Normally, panel applets require compiling each applet to a .so lib along with a .la file to allow kicker to load the applet.. PyKDE uses a single .so lib to load all panel applets written in Python. A .la file is sitll required, along with a .desktop file, but these are easily created by the user, or by using the panel applet installer supplied with PyKDE.

Sections:

What Are Panel Applets?

Panel applets are subclasses of KPanelApplet, which in turn is a thin subclass of the Qt QFrame class. They are usually applications with specific, limited funtionality. Part of the interface is installed in the KDE panel ("kicker"), usually by the user selecting the applet from the kicker applet menu. They differ from system tray applications in that panel applets usually don't have their own event loop or KApplication instance - they borrow those from somewhere else.

All panel applets require two things:

Using PyKDE, these requirements are met by deriving the panel applet from the PyKDE class PyKPanelApplet (a KPanelApplet subclass that basically allows access to the applet's destructor for shutting down the Python interpreter). The factory function for Python/PyKDE applets is "createApplet". The createApplet function does nothing more than create an instance of the applet class and return the instance to the caller.

Loading Applets

The user can select an applet by right clicking on the kicker panel:

kicker menus

The list of available applets is in the rightmost menu. kicker determines the applets avaiable by scanning it's applets/ directory (normally $KDEDIR/share/apps/kicker/applets/) for .desktop files. The .desktop files provide the text for the menu entry, the name of the library which "contains" the applet, and a true/false indication of whether the applet can have multiple instances or must be unique.

A .desktop file looks like this:

 
[Desktop Entry]
Name=Demo
Comment=Demo - Python
X-KDE-Library=demo_py_applet
X-KDE-UniqueApplet=false

The "Name" field is the entry that appears on the menu. "X=KDE-Library" is the library that kicker will load (demo_py_applet.so in this case) to instantiate and execute the applet. The .desktop file can have any name, as long as it ends with the ".desktop" extension.

kicker loads the specified .so lib by first locating the associated .la file. .la files are created when linking using libtool; qmake 1.0.6 can also create .la file. The .la file is just a text file. kicker next loads.the .so file and looks for a global function named "init". It then calls "init" with two arguments: a pointer to the applet's QWidget parent (usually NULL) and a QString reference containing the applet's config file name. This name always begins with the string value of X-KDE-Library (demo_py_applet again).

Calling 'init' causes the KPanelApplet subclass contained the .so lib to be instantiated, and "init" then returns a pointer to the subclass to kicker. kicker makes some SIGNAL/SLOT connections to the applet (for orienting and resizing it if the panel orientation or size changes) and the applet begins receiving events as appropriate.

Loading a Python Applet

Since it's not possible to create a .so lib from Python, and since any Python app requires the Python imntepreter to run, the Python applet loading process is slightly different.

All Python applets load via a single .so file (libpykpanelapplet.so) which is created as part of PyKDE. In order to have kicker load this lib, but still be able to have multiple different Python applets, the libname specified in the .desktop file (demo_py_applet.so above) is symlinked to libpykpanelapplet.so. In addition, a dummy .la file is created to satisfy kicker's need for an .la file.

libpykpanelapplet.so contains an 'init' global function, but it behaves differently than the factory function for a C++ applet. "init" in this case performs a number of steps:

  1. It finds the Python script for the applet, which has the same base name as the lib (demo_py_applet.py) The name of the script is determined from the configFile name passed in by kicker. Scripts are stored in the same path as .desktop files
  2. It loads the Python interpreter
  3. It adds the path to the script to Python's sys.path
  4. It converts the C++ args passed in (parent and configFile) to PyObjects
  5. It invokes the factory function in the Python script and receives an instance of the applet on return
  6. It converts the applet instance to a C++ pointer and returns it to kicker