Menu

Porting_GUI_plugins_to_0.6

waker

Introduction

There are several GUI plugins, that simply get the "GtkWidget *mainwin" pointer, then inject new GUI components in there.

This is a hack, so deadbeef 0.6 brings the solution, called "design mode".

To support this solution, there's the new API, that allows plugin devs to create widgets, and register them with GTKUI, so that deadbeef is able to create and place them in the GUI layout in arbitrary way.

It is now the official API for developing GUI plugins, which add new panels to the main window.

This page is about how to learn about the new API, and how to port the existing plugins to benefit from it.

GTKUI id has been changed to block the broken plugins

The API has changed, the GTKUI behavior changed, and the plugins can't use it correctly - so they crash or hang.

To avoid that situation, the "gtkui" plugin id has been changed to "gtkui_1".

Here's what happened, and why the plugins don't work.

First thing: widget layout has changed. That means, the old plugins won't be able to put their widget in the places they expect to exist.

2nd, and more important thing. To make deadbeef run on OSX, I had to move GTK main loop to the main thread.

The initialization now became this:

  1. load all plugins
  2. call "start" for all plugins except current GUI plugin
  3. call "connect" for all plugins (including the GUI plugin)
  4. call "start" for the GUI plugin - this is a blocking call, and will run gtk_main on the thread from which it was called.

That means that plugins that were initializing GTK widgets in their connect functions, don't work anymore, because at the time of connect GTK is not initialized yet.

Get familiar with the design mode

Build deadbeef 0.6 (possibly beta), and get familiar with the design mode. Click the "View -> Design mode", then right click on GUI elements, and try to build your own custom layout.

Try adding the Playlist Browser into GUI. Learn how to place it in different places in the window.

The playlist browser is implemented as plugin, and you can learn how to develop your own GUI plugins from its source code.

The gtkui_api.h contains the declaration of the new ddb_gtkui_widget_t, and there's lots of documentation in there.

Compatibility with both 0.5 and 0.6

Plugin developers can decide which versions of deadbeef they want to support.

It is possible to support deadbeef 0.5 and 0.6 at the same time, in the same binary build of plugin.

We are talking about GTKUI here, so supporting deadbeef 0.5 and 0.6 means supporting GTKUI version 1 and GTKUI version 2.

Remember: never make your plugin compatible with GTKUI versions which don't exist yet.

The examples below demonstrate how to do that. They explicitly request GTKUI version 1 or 2, or both, but won't run on anything else, because they don't know what it is (no one knows - the API may be completely different).

So, the rule is: make "connect" return -1 if GTKUI API version is unknown to you.

Example 1: deadbeef 0.5 only

// i assume the plugin can work with 0.5.0+
// so we need to tell deadbeef, that plugin works with API version starting from 1.0 -- which corresponds to deadbeef 0.5.0
static DB_misc_t plugin = {
 ...
 .plugin.api_vmajor = 1,
 .plugin.api_vminor = 0,
 .plugin.connect = my_connect,
 ...
};

// now the connect method:
int my_connect (void) {
#if !GTK_CHECK_VERSION(3,0,0)
    gtkui_plugin = (ddb_gtkui_t *) deadbeef->plug_get_for_id ("gtkui");
#else
    gtkui_plugin = (ddb_gtkui_t *) deadbeef->plug_get_for_id ("gtkui3");
#endif
    if (gtkui_plugin && gtkui_plugin->gui.plugin.version_major == 1) { // gtkui version 1
          // success
          ... do initialization ...
          return 0;
    }
    // failure
    return -1;
}

Example2: deadbeef 0.6 only

// we don't need to support 0.5, and the API version corresponding to deadbeef 0.6 is 1.5
static DB_misc_t plugin = {
 ...
 .plugin.api_vmajor = 1,
 .plugin.api_vminor = 5,
 .plugin.connect = my_connect,
 ...
};

// now the connect method:
int my_connect (void) {
    // note: DDB_GTKUI_PLUGIN_ID will get you "gtkui_1", so it won't work on deadbeef 0.5
    gtkui_plugin = (ddb_gtkui_t *) deadbeef->plug_get_for_id (DDB_GTKUI_PLUGIN_ID);
    if (gtkui_plugin && gtkui_plugin->gui.plugin.version_major == 2) { // gtkui version 2
          // success
          gtkui_plugin->w_reg_widget (_("My widget"), 0, w_mywidget_create, "mywidget", NULL);
          ... do initialization ...
          return 0;
    }
    // failure
    return -1;
}

Example 3: 0.5 and 0.6 together in 1 plugin, fully backwards compatible

// we use the API version 1.0 again -- because we want deadbeef 0.5+ (including 0.6)
static DB_misc_t plugin = {
 ...
 .plugin.api_vmajor = 1,
 .plugin.api_vminor = 0,
 .plugin.connect = my_connect,
 ...
};

// now the connect method:
int my_connect (void) {
    // first, try to find "gtkui" plugin
#if !GTK_CHECK_VERSION(3,0,0)
    gtkui_plugin = (ddb_gtkui_t *) deadbeef->plug_get_for_id ("gtkui");
#else
    gtkui_plugin = (ddb_gtkui_t *) deadbeef->plug_get_for_id ("gtkui3");
#endif
    // note the gtkui version 1 here
    if (gtkui_plugin && gtkui_plugin->gui.plugin.version_major == 1) {
          // success
          plugin_mode = OLD; // this will let your plugin know, that it should work in legacy mode, and not try to use the new GTKUI API
          ... do initialization ...
          return 0;
    }

    // failed to load "gtkui", let's check if there's "gtkui_1"
    gtkui_plugin = (ddb_gtkui_t *) deadbeef->plug_get_for_id (DDB_GTKUI_PLUGIN_ID);
    // note the gtkui version 2 here
    if (gtkui_plugin && gtkui_plugin->gui.plugin.version_major == 2) {
          // success
          plugin_mode = NEW;
          gtkui_plugin->w_reg_widget (_("My widget"), 0, w_mywidget_create, "mywidget", NULL);
          ... do initialization ...
          return 0;
    }
    // failure
    return -1;
}

Single or multiple instances

Normally, the widgets should support multiple instances. But this is not always possible, especially during development, or in beta versions.

So the DDB_WF_SINGLE_INSTANCE flag was introduced, and it solves just this problem.

If your plugin can only handle 1 widget instance at a time, this is the example how to do it:

gtkui_plugin->w_reg_widget (_("My widget"), DDB_WF_SINGLE_INSTANCE, w_mywidget_create, "mywidget", NULL);

Testing

Builds of all Deadbeef releases are provided on the website. It's very simple to download them, and have both 0.5.6 (and 0.5.0 too!) and 0.6.0 at the same time, and be able to check that your plugin works in all of them, or at least don't crash any of them. Please do it.


Related

Wiki: Main_Page