Compiz Introduction and Demo Part 5/5: How to write a Compiz Plugin

Step by step instructions on how to write a Compiz plugin:

Let’s assume that the compiz source code has been downloaded in the ~/ubuntu/compiz directory we created in Part 4 of this tutorial and we have already configure compiz using CMake.

ubuntu/
├── compiz
│   └── build
├── install
├── nux
├── nux_install
└── unity

If you enter the directory compiz and run “ls plugins/” you will see that there’s a directory for every plugin. Each of these plugin directories contains a CMakeLists.txt file, an src/ directory and an xml.in file. The CMakeLists.txt is the file that contains the settings for cmake to find the plugin and other information like which libraries should be linked for the plugin to work. The src/ directory contains the source code and the xml.in file contains some plugin info in XML format as well as its options in ccsm (http://wiki.compiz.org/CCSM).

Step 1: CMakeLists.txt

As a first step we’ll create our plugin directory:

$ cd ~/ubuntu/compiz
$ mkdir ~/ubuntu/compiz/plugins/helloworld

and we’ll also write a basic CMakeLists.txt file that we might need to extend later.

Our CMakeLists.txt file will contain the name of the plugin and the compiz plugins (or libraries) from which it depends. For the moment we’ll only need the composite plugin (for the compositor) and the opengl plugin (for the cool graphics stuff in OpenGL). So, our CMakeLists.txt will look like that:

find_package (Compiz REQUIRED)
include (CompizPlugin)
compiz_plugin (helloworld PLUGINDEPS composite opengl)

the 1st line finds compiz with pkgconfig, the 2nd imports the plugin buildsystem and the 3rd sets up a new compiz plugin project that depends on the listed plugins (in our case opengl and composite)

To test the CMakeLists.txt just run make (Compiz will be re-configured automatically after that).

Step 2: Write the xml.in file

The xml.in file contains the plugin information (name, description etc) and the plugin options that can be set in the compizconfig-settings-manager (CCSM). A simple XML file that can be extended later could be the following:

<?xml version="1.0" encoding="UTF-8"?>

 <plugin name="helloworld" useBcop="true">
   Hello World
   Example plugin for the NUDT presentation
   Effects
   
     
       composite
       opengl
     
     <relation type="after">
       composite
       opengl
     
     
       General
       <option name="toggle" type="key">
         Toggle
         Toggle helloworld banner effect.
         &lt;Shift&gt;0
       
     
   
 

In the first block we have a short and long plugin description that will appear in CCSM (compiz-config-settings-manager) in the category Effects:

ccsm #1

The deps block shows the plugin dependencies. Our helloworld will depend from the composite plugin like every other plugin that uses the compiz compositor (see part 1 of this tutorial) and from the opengl plugin (because we’d like to render OpenGL stuff on the screen). It will be loaded after those 2 plugins.

The options group is extremely useful when someone needs to set configuration options (for the appearence, color, size, time or other plugin functionalities) for the plugin through ccsm. We ‘ll see at the next steps how someone can read the option values from the plugin and take them into account. In this example plugin we only have one option that “toggles” the plugin (shows and hides the plugin screen aka the surface where the plugin renders stuff).

ccsm #2

In the screenshpt above you can see how this option will look in CCSM.

Step 3: Directory src/ and some C++ code 🙂

All compiz plugins have a directory named src/ with the plugin source code. Let us create one:

$ mkdir ~/ubuntu/compiz/plugins/helloworld/src

Our directory structure at this point must be:

ubuntu/
├── compiz
│   ├── build
│   └── plugins
│           └── helloworld
│                   ├── CMakeLists.txt
│                   ├── helloworld.xml.in
│                   └── src
├── install
├── nux
├── nux_install
└── unity

We enter the src/ directory and we create two files: a helloworld.h and a helloworld.cpp.

Let’s start with the helloworld.h:

We will need to define at least 2 classes for our plugin to function. The first is the “screen” class and the other is the “VTable” class. Some plugins also define a “window” class, if they store per-window settings.

The way these classes work is that they derive from a special template class called PluginClassHandler, which is used to “attach” a new plugin class to a core structure that supports this. And then, for every instance of that class created, another copy of this plugin’s class will be created and “attached” to the core structure. This becomes important when someone wants to store data for every window – instead of creating lots of lists of window ID’s, he can just store it in his window class and retrieve it using the ::get (BaseClass *) function provided by PluginClassHandler. (A great tutorial on writing compiz plugins by Sam Spilsbury can be found here: http://smspillaz.ucc.asn.au/unity/compiz_arch/writing.odt.)

Let’s write and explain a helloworld.h line by line:

#ifndef HELLOWORLD_H_
#define HELLOWORLD_H_
#include 
#include 
#include 
#include 

First we include the header files of the opengl and composite plugins as well as 2 core header files  needed for the vtable (core.h and pluginclasshandler.h).

#include "helloworld_options.h"

The helloworld_options.h is a generated header file that was created when we edited the helloworld.xml.in (see step 2). We need it to read the helloworld options later.

class HelloworldScreen :
  public PluginClassHandler ,
  public HelloworldOptions,
  public ScreenInterface,
  public CompositeScreenInterface,
  public GLScreenInterface
 {
   public:
   HelloworldScreen (CompScreen *);
   ~HelloworldScreen();

Every plugin that renders something on the screen must have its own “PluginNameScreen” class. You can see that PluginNameScreen class as a class that provides functions to draw things on a surface handled by the plugin. If the plugin has to deal with windows it must have it’s own window class as well. Our Helloworld plugin will only draw a helloworld message, therefore we will only implement a HelloworldScreen class.

The HelloworldScreen class will inherit the CompositeScreenInterface and the GLScreenInterface in order to use the compositor and the opengl plugin functions. If you open the file: ~/ubuntu/compiz/plugins/opengl/include/opengl/opengl.h you will see the GLScreenInterface functions that can be implemented by the plugins that draw with OpenGL on the screen.

   CompositeScreen *cScreen;
   GLScreen *gScreen;

The class must also contain a pointer to its base composite and opengl plugins’ screens because we’ll need to call some of their functions. For example we might need to tell the compositor to clear the screen or damage it and re-draw.

   bool glPaintOutput (const GLScreenPaintAttrib&,
                       const GLMatrix&,
                       const CompRegion&,
                       CompOutput*,
                       unsigned int);

The glPaintOutput function is the GLScreenInterface function that should be used by plugins to draw to an output. We can see that in the documentation of the GLScreenInterface class functions inside the file opengl.h:

/**
* Hookable function used for plugins to use openGL to draw on an output
*
* @param attrib Describes some basic drawing attribs for the screen
* including translation, rotation and scale
* @param matrix Describes a 4×4 3D modelview matrix for which this
* screen should be drawn in
* @param region Describes the region of the screen being redrawn
* @param output Describes the output being redrawn
* @param mask Bitmask which describes how the screen is being redrawn’
*/

   bool toggle();
   bool active;
 };

The toggle function will be filled by us and will contain the actions that should be performed when the user “toggles” the plugin using the shortcut we set in the ccsm options (Shift+0 see xml.in @Step 2).

#define HELLOWORLD_SCREEN(s) HelloworldScreen *hs = HelloworldScreen::get (s)

This is just a useful macro for less typing, you could call get instead :p

class HelloworldPluginVTable :
 public CompPlugin::VTableForScreen 
{
  public:
  bool init ();
};

The VTable class provides information to core about how to construct plugin classes for certain structures. Most plugins need the same things from core, therefore there are template classes CompPlugin::VTableForScreenAndWindow and CompPlugin::VTableForScreen . This will handle setting up constructors for you and everything.

All plugins must have a bool init () function defined in their VTable. It is used by compiz to initialize the plugins.

COMPIZ_PLUGIN_20090315(helloworld, HelloworldPluginVTable);

This macro is used to create the symbols core will read from a plugin when it loads.

#endif // HELLOWORLD_H_

One of the most important parts of the compiz plugin architecture is that plugins are able to hook and unhook functions that core and other plugins provide so long as they link to them at build time. For example, when opengl gets its function called to paint the screen, glPaintOutput, this function is interfacable so every plugin that has interfaced this function also gets its function called, and usually paints some other stuff on the screen at the same time. (More information on interfaces you can find again on Sam Spilbury’s text: http://smspillaz.ucc.asn.au/unity/compiz_arch/writing.odt).

So, these are pretty much all the classes and functions we’ll need for our helloworld plugin.

Here it’s how it looks:

helloworld.h
helloworld.h : helloworld plugin header file

Let’s fill the functions 🙂

For start, our plugin will only paint the screen red and draw a blue quad on the screen. Then we’ll replace that part with something more cool.

#include "helloworld.h"

This line just includes our header file (helloworld.h).

void setFunctions(bool enabled)
{
  HELLOWORLD_SCREEN(screen);
  hs->gScreen->glPaintOutputSetEnabled(hs, enabled);
}

From the GLScreenInterface functions (see opengl plugin: opengl.h) we'll only need to implement (fill) the glPaintOutput function which is the function one must fill to draw cool stuff on the screen. Before we use it we have to enable it calling the SetEnabled in our Screen class constructor:

HelloworldScreen::HelloworldScreen (CompScreen *screen) :
  PluginClassHandler  (screen),
  cScreen (CompositeScreen::get (screen)),
  gScreen (GLScreen::get (screen)),
  active (false)
{
  ScreenInterface::setHandler (screen, false);
  CompositeScreenInterface::setHandler (cScreen, false);
  GLScreenInterface::setHandler (gScreen, false);
  optionSetToggleInitiate (boost::bind (&HelloworldScreen::toggle, this));
}

Constructor… We set the Handlers for the different Screen interfaces we inherit and the callback that will be called when the Toggle option is set through ccsm.

HelloworldScreen::~HelloworldScreen()
{
}

The destructor is left empty for the moment.

bool HelloworldScreen::glPaintOutput(const GLScreenPaintAttrib &attrib,
  const GLMatrix &transform,
  const CompRegion &region,
  CompOutput *output,
  unsigned int mask)
{
  bool status = gScreen->glPaintOutput(attrib, transform, region, output, mask);
  glClearColor(0, 0, 1, 1);
  return status;
}

The glPaintOutput performs a call to the gl screen glPaintOutput and then paints something on the screen… In this case we paint the screen blue using the glClearColor OpenGL call. When the user enables the plugin from ccsm and presses the key combination that is set in the xml metadata file the screen will become blue.

bool HelloworldScreen::toggle ()
{
  active = !active;
  if (active)
    setFunctions(true);
  else
    setFunctions(false);
 
  cScreen->damageScreen();
  return true;
}

This is the callback that enables and disables the helloworld plugin. It is called everytime the user presses the shortcuts that toggle the plugin (shift+0 in our case). The first time it’s called it enables the glPaintOutput so that the plugin can draw the blue screen, the second time it disables it and so on.

bool HelloworldPluginVTable::init()
{
  if (CompPlugin::checkPluginABI ("core", CORE_ABIVERSION) &&
    CompPlugin::checkPluginABI ("composite", COMPIZ_COMPOSITE_ABI) &&
    CompPlugin::checkPluginABI ("opengl", COMPIZ_OPENGL_ABI))
    return true;
  return false;
}

This function is necessary for compiz to find and start the plugin. It also looks for the plugin dependencies.

If we want our plugin to do something more spectacular, we can write a full program and call its drawing function inside the glPaintOutput function. In the following example I wrote for a Compiz presentation in Changsha, the helloworld plugin calls the fx_draw(); to draw some cool particles, the fx_init(); in the constructor to initialize the particle system and the fx_cleanup(); in the destructor to cleanup the remaining particles. You can compile it and run it (following the build instructions of Part 4/5) after you get the code from: https://code.launchpad.net/~hikiko/compiz/compiz.helloworld

Here’s a video of what the helloworld plugin does when I press shift+0 to toggle it:

You can try it by yourselves and replace the glPaintOutput code with your own cool graphics stuff! If you have any questions don’t hesitate to email me at: or ping me on IRC/Freenode (nick: hikiko).

Advertisements
Compiz Introduction and Demo Part 5/5: How to write a Compiz Plugin

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s