New plug-in API

From Scribus Wiki
Jump to navigation Jump to search

This content is obsolete. The interface has since been implemented; see scplugin.h and the Scribus documentation.


Plugin API ideas

C++ style plugin interface

Plugin management might be a lot neater, especially as we add functions, if we move to a more C++ style plugin API. My proposal is:

  • Provide a simple Plugin class in the core code. This is used to carry most info about the plugin. It should be an abstract class with virtual methods and virtual dtor.
    • An instance of a Plugin subclass is returned by the plugin when asked.
    • The Plugin instance has methods to replace most of what is now done by growing numbers of 'extern C' functions that we dlopen().
    • Plugin provides the interface between the main app and the plugin. It does not implment the functionality of the plugin; that's done by classes that the main app never needs to even know about. Plugin instances are a way of getting and passing around info about a plugin, not an implementation of the plugin its self.
    • Plugin can be extended to provide new interfaces later, and existing plugins will only need to be recompiled (maybe not even that) so long as a sensible stub implementation of the interface can be provided by the base Plugin class.
    • Doing things this way makes it easier to make different plugin types (eg subclass ScPersistentPlugin or ScInputOutputPlugin) with different and well defined interfaces.
  • Each plugin provides a very small number of 'extern C' functions that are accessed via QLibrary or dlopen(). All functions are prefixed with the plugin name (the same as the .so basename) so we can statically link them into the main binary without symbol conflicts if we need to.
    • int plugname_pluginAPIVersion(); returns the plugin API version. This is checked by the plugin loader before trying to get a Plugin instance and plugins with a non-matching API version are rejected. We might want to do some sort of minor/major thing here so that we can increment the minor number to indicate backward compatible addition of features, and the major for non-backward-compatible changes. Don't know if it's really that important, though, since the Scribus core will generally be the limitation when it comes to compatibilty anyway.
    • Plugin* plugname_plugin() returns a pointer to a Plugin object (actually a subclass defined in the plugin's code).
    • plugname_freePlugin(Plugin*) frees the passed plugin object. Needed to ensure we always free memory in the same .so/.dll as we allocate it and so that the right dtor gets run.

The plugin manager is responsible for loading and unloading plugins, etc, just as it is now. It's also responsible for keeping a registry of plugins loaded and loadable, again unchanged from the present.

In the case of plugins that are loaded only on demand, PluginManager will need to copy information out of the returned Plugin instances, or copy-construct a Plugin subclass instance to store it. We can't very well unload the implementation of an object while still using that object, after all...

The other API functions of the plug-in are accessed via the Plugin object for the plugin, which can be obtained pretty easily via a plugin manager method like:

Plugin* getPlugin(QString& pluginName);

where pluginName is the unique part of the shared object name (no suffix or soname info). So, calling Run should be as simple as:

ScInputOutputPlugin* plug = dynamic_cast<ScInputOutputPlugin*>(getPlugin("gettext"));
if (plug)
    plug.run(ScApp, ScApp);
else
   qDebug("Couldn't get plugin");

or even

ScInputOutputPlugin* plug = getIOPlugin("gettext");
if (plug)
    plug.run(ScApp, ScApp);
// getIOPlugin will log if it can't find the plugin or it's the wrong type

Still to settle:

  • Best way to store info on plugins that are loaded on demand. Copy construct a Plugin? Copy important values out into the existing struct?

current header file draft

Plugins calling other plugins

I'd like to investigate adding some functionality to the plugin manager to let it enable plugins to access each others' functions. This would probably be best built on the plugin API proposal above. I'm not sure exactly how it'd need to work, but the general idea is that the client plugin:

  • #includes the header of the plugin it wants to use
  • Asks the plugin manager for the Plugin instance of the desired plugin
  • dynamic_casts<>s the returned Plugin instance to the subclass defined in the header of the plugin it wishes to access
  • Uses the now-availible methods in the Plugin subclass to ask for an instance of the plugin's "real" worker class, call additional methods defined on the Plugin subclass, etc. Each plugin can choose to present the most appropriate interface.

There would also need to be some mechanism in the plugin manager to ensure that plugins can ask that other plugins be loaded/unloaded. Reference counting would probably need to be involved here - maybe we could use QLibrary's existing smarts. Circular loading and load order also need to be considered here. A confirmed circular dependency should cause PluginManager to return NULL (probably by keeping an internal count of the "depth" of in-progress load requests).

Practical applications would include enabling access to other plugins from Python and ... er.... that's all I can think of right now ;-)

Prefs UI API for Plug-ins

It should be fairly easy to build an API to permit plugins to provide panels for the preferences dialog. This permits all plug-ins to be configured in the same place, along with the rest of the app.

The general idea is to let a plugin export members to:

  • report if it wants to have prefs UI
  • return an icon for the prefs panel
  • return a name for the prefs panel
  • return a widget pointer for the prefs panel
  • destroy their widget when it's finished with. We do this so there's no need to worry about fun with destroying memory in a different library to it was allocated in, since IIRC some compilers/platforms don't like that.

eg:

bool Plugin::hasPrefsUI() const;
QPixmap Plugin::prefsPanelIcon();
QString& Plugin::prefsPanelName();
QWidget* Plugin::newPrefsPanelWidget();
void Plugin::destroyPrefsPanelWidget(QWidget*);

The plug-in manager can then, at start-up, check if each plug-in wants to display prefs UI in the prefs dialog. When the prefs dialog is displayed, the prefs dialog asks the plug-in manager to add the plug-ins' UI or requests the icon, name, and widget panels to do so its self from the plugin manager. The prefs dialog connects a few signals to each panel so they can save their settings when the dialog is closed, but otherwise doesn't interact with the supplied widget.

The plugin is asked to destroy its prefs widget its self to avoid fun with platforms that are fussy about creating and destroying memory over library boundaries and similar issues.