<html> <head> <title>The VDR Plugin System</title> </head> <body bgcolor="white"> <center><h1>The VDR Plugin System</h1></center> VDR provides an easy to use plugin interface that allows additional functionality to be added to the program by implementing a dynamically loadable library file. This interface allows programmers to develop additional functionality for VDR completely separate from the core VDR source, without the need of patching the original VDR code (and all the problems of correlating various patches). <p> <!--X1.1.3--><table width=100%><tr><td bgcolor=#AA0000> </td><td width=100%> This document is divided into two parts, the first one describing the <a href="#Part I - The Outside Interface"><i>outside</i> interface</a> of the plugin system, and the second one describing the <a href="#Part II - The Inside Interface"><i>inside</i> interface</a>. The <i>outside</i> interface handles everything necessary for a plugin to get hooked into the core VDR program and present itself to the user. The <i>inside</i> interface provides the plugin code access to VDR's internal data structures and allows it to hook itself into specific areas to perform special actions. <!--X1.1.3--></td></tr></table> <p> <!--X1.1.1--><table width=100%><tr><td bgcolor=#0000AA> </td><td width=100%> Important modifications introduced in version 1.1.1 are marked like this. <!--X1.1.1--></td></tr></table> <!--X1.1.2--><table width=100%><tr><td bgcolor=#00AA00> </td><td width=100%> Important modifications introduced in version 1.1.2 are marked like this. <!--X1.1.2--></td></tr></table> <!--X1.1.3--><table width=100%><tr><td bgcolor=#AA0000> </td><td width=100%> Important modifications introduced in version 1.1.3 are marked like this. <!--X1.1.3--></td></tr></table> <!--X1.1.4--><table width=100%><tr><td bgcolor=#FF0000> </td><td width=100%> Important modifications introduced in version 1.1.4 are marked like this. <!--X1.1.4--></td></tr></table> <a name="Part I - The Outside Interface"><hr><center><h1>Part I - The Outside Interface</h1></center> <hr><h2>Quick start</h2> <center><i><b>Can't wait, can't wait!</b></i></center><p> Actually you should read this entire document before starting to work with VDR plugins, but you probably want to see something happening right away <tt>;-)</tt> <p> So, for a quick demonstration of the plugin system, there is a sample plugin called "hello" that comes with the VDR source. To test drive this one, do the following: <ul> <li>change into the VDR source directory <li><b><tt>make</tt></b> the VDR program with your usual <tt>REMOTE=...</tt> (and maybe other) options <li>do <b><tt>make plugins</tt></b> to build the plugin <li>run VDR with <b><tt>vdr -V</tt></b> to see the version information <li>run VDR with <b><tt>vdr -h</tt></b> to see the command line options <li>run VDR with <b><tt>vdr -Phello</tt></b> <li>open VDR's main menu and select the <i>Hello</i> item <li>open the <i>Setup</i> menu from VDR's main menu and select <i>Plugins</i> </ul> If you enjoyed this brief glimpse into VDR plugin handling, read through the rest of this document and eventually write your own VDR plugin. <hr><h2>The name of the plugin</h2> <center><i><b>Give me some I.D.!</b></i></center><p> One of the first things to consider when writing a VDR plugin is giving the thing a proper name. This name will be used in the VDR command line in order to load the plugin, and will also be the name of the plugin's source directory, as well as part of the final library name. <p> The plugin's name should typically be as short as possible. Three letter abbreviations like <b><tt>dvd</tt></b> (for a DVD player) or <b><tt>mp3</tt></b> (for an MP3 player) would be good choices. It is also recommended that the name consists of only lowercase letters and digits. No other characters should be used here. <p> A plugin can access its name through the (non virtual) member function <p><table><tr><td bgcolor=#F0F0F0><pre><br> const char *Name(void); </pre></td></tr></table><p> The actual name is derived from the plugin's library file name, as defined in the next chapter. <a name="The plugin directory structure"><hr><h2>The plugin directory structure</h2> <center><i><b>Where is everybody?</b></i></center><p> By default plugins are located in a directory named <tt>PLUGINS</tt> below the VDR source directory. Inside this directory the following subdirectory structure is used: <p><table><tr><td bgcolor=#F0F0F0><pre><br> VDR/PLUGINS/src VDR/PLUGINS/src/hello VDR/PLUGINS/lib VDR/PLUGINS/lib/libvdr-hello.so.1.1.0 </pre></td></tr></table><p> The <tt>src</tt> directory contains one subdirectory for each plugin, which carries the name of that plugin (in the above example that would be <tt>hello</tt>). What's inside the individual source directory of a plugin is entirely up to the author of that plugin. The only prerequisites are that there is a <tt>Makefile</tt> that provides the targets <tt>all</tt> and <tt>clean</tt>, and that a call to <tt>make all</tt> actually produces a dynamically loadable library file for that plugin (we'll get to the details later). <p> The <tt>lib</tt> directory contains the dynamically loadable libraries of all available plugins. Note that the names of these files are created by concatenating <p> <table border=2> <tr><td align=center><b><tt>libvdr-</tt></b></td><td align=center><b><tt>hello</tt></b></td><td align=center><b><tt>.so.</tt></b></td><td align=center><b><tt>1.1.0</tt></b></td></tr> <tr><td align=center><font size=-1>VDR plugin<br>library prefix</font></td><td align=center><font size=-1>name of<br>the plugin</font></td><td align=center><font size=-1>shared object<br>indicator</font></td><td align=center><font size=-1>VDR version number<br>this plugin was<br>compiled for</font></td></tr> </table> <p> The plugin library files can be stored in any directory. If the default organization is not used, the path to the plugin directory has be be given to VDR through the <b><tt>-L</tt></b> option. <p> The VDR <tt>Makefile</tt> contains the target <tt>plugins</tt>, which calls <tt>make all</tt> in every directory found under <tt>VDR/PLUGINS/src</tt>, plus the target <tt>plugins-clean</tt>, which calls <tt>make clean</tt> in each of these directories. <p> If you download a plugin <a href="#Building the distribution package">package</a> from the web, it will typically have a name like <p> <tt>vdr-hello-0.0.1.tgz</tt> <p> and will unpack into a directory named <p> <!--X1.1.2--><table width=100%><tr><td bgcolor=#00AA00> </td><td width=100%> <tt>hello-0.0.1</tt> <!--X1.1.2--></td></tr></table> <p> To use the <tt>plugins</tt> and <tt>plugins-clean</tt> targets from the VDR <tt>Makefile</tt> you need to unpack such an archive into the <tt>VDR/PLUGINS/src</tt> directory and create a symbolic link with the basic plugin name, as in <!--X1.1.2--><table width=100%><tr><td bgcolor=#00AA00> </td><td width=100%> <p><table><tr><td bgcolor=#F0F0F0><pre><br> ln -s hello-0.0.1 hello </pre></td></tr></table><p> Since the VDR <tt>Makefile</tt> only searches for directories with names consisting of only lowercase characters and digits, it will only follow the symbolic links, which should lead to the current version of the plugin you want to use. This way you can have several different versions of a plugin source (like <tt>hello-0.0.1</tt> and <tt>hello-0.0.2</tt>) and define which one to actually use through the symbolic link. <!--X1.1.2--></td></tr></table> <a name="Initializing a new plugin directory"><hr><h2>Initializing a new plugin directory</h2> <center><i><b>A room with a view</b></i></center><p> Call the Perl script <tt>newplugin</tt> from the VDR source directory to create a new plugin directory with a <tt>Makefile</tt> and a main source file implementing the basic derived plugin class. You will also find a <tt>README</tt> file there with some inital text, where you should fill in actual information about your project. A <tt>HISTORY</tt> file is set up with an "Initial revision" entry. As your project evolves, you should add the changes here with date and version number. <p> <tt>newplugin</tt> also creates a copy of the GPL license file <tt>COPYING</tt>, assuming that you will release your work under that license. Change this if you have other plans. <p> Add further files and maybe subdirectories to your plugin source directory as necessary. Don't forget to adapt the <tt>Makefile</tt> appropriately. <hr><h2>The actual implementation</h2> <center><i><b>Use the source, Luke!</b></i></center><p> A newly initialized plugin doesn't really do very much yet. If you <a href="#Loading plugins into VDR">load it into VDR</a> you will find a new entry in the main menu, with the same name as your plugin (where the first character has been converted to uppercase). There will also be a new entry named "Plugins" in the "Setup" menu, which will bring up a list of all loaded plugins, through which you can access each plugin's own setup parameters (if it provides any). <p> To implement actual functionality into your plugin you need to edit the source file that was generated as <tt>PLUGINS/src/name.c</tt>. Read the comments in that file to see where you can bring in your own code. The following sections of this document will walk you through the individual member functions of the plugin class. <p> Depending on what your plugin shall do, you may or may not need all of the given member functions. Except for the <tt>MainMenuEntry()</tt> function they all by default return values that will result in no actual functionality. You can either completely delete unused functions from your source file, or just leave them as they are. If your plugin shall not be accessible through VDR's main menu, simply remove (or comment out) the line implementing the <tt>MainMenuEntry()</tt> function. <p> At the end of the plugin's source file you will find a line that looks like this: <p><table><tr><td bgcolor=#F0F0F0><pre><br> VDRPLUGINCREATOR(cPluginHello); </pre></td></tr></table><p> This is the "magic" hook that allows VDR to actually load the plugin into its memory. You don't need to worry about the details behind all this. <p> If your plugin requires additional source files, simply add them to your plugin's source directory and adjust the <tt>Makefile</tt> accordingly. <p> <!--X1.1.1--><table width=100%><tr><td bgcolor=#0000AA> </td><td width=100%> Header files usually contain preprocessor statements that prevent the same file (or rather its contents, to be precise) from being included more than once, like <p><table><tr><td bgcolor=#F0F0F0><pre><br> #ifndef __I18N_H #define __I18N_H ... #endif //__I18N_H </pre></td></tr></table><p> The example shown here is the way VDR does this in its core source files. It takes the header file's name, converts it to all uppercase, replaces the dot with an underline and preceedes the whole thing with two underlines. The GNU library header files do this pretty much the same way, except that they usually precede the name with only one underline (there are exceptions, though). <p> As long as you make shure that none of your plugin's header files will be named like one of VDR's header files, you can use the same method as VDR. However, if you want to name a header file like one that is already existing in VDR's source (<tt>i18n.h</tt> would be a possible candidate for this), you may want to make sure that the macros used here don't clash. How you do this is completely up to you. You could, for instance, prepend the macro with a <tt>'P'</tt>, as in <tt>P__I18N_H</tt>, or leave out the trailing <tt>_H</tt>, as in <tt>__I18N</tt>, or use a completely different way to make sure a header file is included only once. <p> The 'hello' example that comes with VDR makes use of <a href="#Internationalization">internationalization</a> and implements a file named <tt>i18n.h</tt>. To make sure it won't clash with VDR's <tt>i18n.h</tt> it uses the macro <tt>_I18N__H</tt> (one underline at the beginning and two replacing the dot). <!--X1.1.1--></td></tr></table> <hr><h2>Construction and Destruction</h2> <center><i><b>What goes up, must come down...</b></i></center><p> The constructor and destructor of a plugin are defined as <p><table><tr><td bgcolor=#F0F0F0><pre><br> cPlugin(void); virtual ~cPlugin(); </pre></td></tr></table><p> The <b>constructor</b> shall initialize any member variables the plugin defines, but <b>must not access any global structures of VDR</b>. It also must not create any threads or other large data structures. These things are done in the <a href="#Getting started"><tt>Start()</tt></a> function later. Constructing a plugin object shall not have any side effects or produce any output, since VDR, for instance, has to create the plugin objects in order to get their command line help - and after that immediately destroys them again. <p> The <b>destructor</b> has to clean up any data created by the plugin, and has to take care that any threads the plugin may have created will be stopped. <p> Of course, if your plugin doesn't define any member variables that need to be initialized (and deleted), you don't need to implement either of these functions. <hr><h2>Version number</h2> <center><i><b>Which incarnation is this?</b></i></center><p> Every plugin must have a version number of its own, which does not necessarily have to be in any way related to the VDR version number. VDR requests a plugin's version number through a call to the function <p><table><tr><td bgcolor=#F0F0F0><pre><br> virtual const char *Version(void) = 0; </pre></td></tr></table><p> Since this is a "pure" virtual function, any derived plugin class <b>must</b> implement it. The returned string should identify this version of the plugin. Typically this would be something like "0.0.1", but it may also contain other information, like for instance "0.0.1pre2" or the like. The string should only be as long as really necessary, and shall not contain the plugin's name itself. Here's an example: <p><table><tr><td bgcolor=#F0F0F0><pre><br> static const char *VERSION = "0.0.1"; const char *cPluginHello::Version(void) { return VERSION; } </pre></td></tr></table><p> Note that the definition of the version number is expected to be located in the main source file, and must be written as <pre> static const char *VERSION = ... </pre> just like shown in the above example. This is a convention that allows the <tt>Makefile</tt> to extract the version number when generating the file name for the distribution archive. <p> A new plugin project should start with version number <tt>0.0.1</tt> and should reach version <tt>1.0.0</tt> once it is completely operative and well tested. Following the Linux kernel version numbering scheme, versions with <i>even</i> release numbers (like <tt>1.0.x</tt>, <tt>1.2.x</tt>, <tt>1.4.x</tt>...) should be stable releases, while those with <i>odd</i> release numbers (like <tt>1.1.x</tt>, <tt>1.3.x</tt>, <tt>1.5.x</tt>...) are usually considered "under development". The three parts of a version number are not limited to single digits, so a version number of <tt>1.2.15</tt> would be acceptable. <hr><h2>Description</h2> <center><i><b>What is it that you do?</b></i></center><p> In order to tell the user what exactly a plugin does, it must implement the function <p><table><tr><td bgcolor=#F0F0F0><pre><br> virtual const char *Description(void) = 0; </pre></td></tr></table><p> which returns a short, one line description of the plugin's purpose: <p><table><tr><td bgcolor=#F0F0F0><pre><br> static const char *DESCRIPTION = "A friendly greeting"; virtual const char *Description(void) { return tr(DESCRIPTION); } </pre></td></tr></table><p> Note the <tt>tr()</tt> around the <tt>DESCRIPTION</tt>, which allows the description to be <a href="#Internationalization">internationalized</a>. <hr><h2>Command line arguments</h2> <center><i><b>Taking orders</b></i></center><p> A VDR plugin can have command line arguments just like any normal program. If a plugin wants to react on command line arguments, it needs to implement the function <p><table><tr><td bgcolor=#F0F0F0><pre><br> virtual bool ProcessArgs(int argc, char *argv[]); </pre></td></tr></table><p> The parameters <tt>argc</tt> and <tt>argv</tt> have exactly the same meaning as in a normal C program's <tt>main()</tt> function. <tt>argv[0]</tt> contains the name of the plugin (as given in the <b><tt>-P</tt></b> option of the <tt>vdr</tt> call). <p> Each plugin has its own set of command line options, which are totally independent from those of any other plugin or VDR itself. <p> You can use the <tt>getopt()</tt> or <tt>getopt_long()</tt> function to process these arguments. As with any normal C program, the strings pointed to by <tt>argv</tt> will survive the entire lifetime of the plugin, so it is safe to store pointers to these values inside the plugin. Here's an example: <p><table><tr><td bgcolor=#F0F0F0><pre><br> bool cPluginHello::ProcessArgs(int argc, char *argv[]) { // Implement command line argument processing here if applicable. static struct option long_options[] = { { "aaa", required_argument, NULL, 'a' }, { "bbb", no_argument, NULL, 'b' }, { NULL } }; int c; while ((c = getopt_long(argc, argv, "a:b", long_options, NULL)) != -1) { switch (c) { case 'a': option_a = optarg; break; case 'b': option_b = true; break; default: return false; } } return true; } </pre></td></tr></table><p> The return value must be <i>true</i> if all options have been processed correctly, or <i>false</i> in case of an error. The first plugin that returns <i>false</i> from a call to its <tt>ProcessArgs()</tt> function will cause VDR to exit. <hr><h2>Command line help</h2> <center><i><b>Tell me about it...</b></i></center><p> If a plugin accepts command line options, it should implement the function <p><table><tr><td bgcolor=#F0F0F0><pre><br> virtual const char *CommandLineHelp(void); </pre></td></tr></table><p> which will be called if the user enters the <b><tt>-h</tt></b> option when starting VDR. The returned string should contain the command line help for this plugin, formatted in the same way as done by VDR itself: <p><table><tr><td bgcolor=#F0F0F0><pre><br> const char *cPluginHello::CommandLineHelp(void) { // Return a string that describes all known command line options. return " -a ABC, --aaa=ABC do something nice with ABC\n" " -b, --bbb activate 'plan B'\n"; } </pre></td></tr></table><p> This command line help will be printed directly below VDR's help texts (separated by a line indicating the plugin's name, version and description), so if you use the same formatting as shown here it will line up nicely. Note that all lines should be terminated with a newline character, and should be shorter than 80 characters. <a name="Getting started"><hr><h2>Getting started</h2> <center><i><b>Let's get ready to rumble!</b></i></center><p> If a plugin implements a function that runs in the background (presumably in a thread of its own), or wants to make use of <a href="#Internationalization">internationalization</a>, it needs to implement the function <!--X1.1.2--><table width=100%><tr><td bgcolor=#00AA00> </td><td width=100%> <p><table><tr><td bgcolor=#F0F0F0><pre><br> virtual bool Start(void); </pre></td></tr></table><p> <!--X1.1.2--></td></tr></table> which is called once for each plugin at program startup. Inside this function the plugin must set up everything necessary to perform its task. This may, for instance, be a thread that collects data from the DVB stream, which is later presented to the user via a function that is available from the main menu. <p> <!--X1.1.2--><table width=100%><tr><td bgcolor=#00AA00> </td><td width=100%> A return value of <i>false</i> indicates that something has gone wrong and the plugin will not be able to perform its task. In that case, the plugin should write a proper error message to the log file. The first plugin that returns <i>false</i> from its <tt>Start()</tt> function will cause VDR to exit. <!--X1.1.2--></td></tr></table> <p> If the plugin doesn't implement any background functionality or internationalized texts, it doesn't need to implement this function. <hr><h2>Main menu entry</h2> <center><i><b>Today's special is...</b></i></center><p> If the plugin implements a feature that the user shall be able to access from VDR's main menu, it needs to implement the function <p><table><tr><td bgcolor=#F0F0F0><pre><br> virtual const char *MainMenuEntry(void); </pre></td></tr></table><p> The default implementation returns a <tt>NULL</tt> pointer, which means that this plugin will not have an item in the main menu. Here's an example of a plugin that will have a main menu item: <p><table><tr><td bgcolor=#F0F0F0><pre><br> static const char *MAINMENUENTRY = "Hello"; const char *cPluginHello::MainMenuEntry(void) { return tr(MAINMENUENTRY); } </pre></td></tr></table><p> The menu entries of all plugins will be inserted into VDR's main menu right after the <i>Recordings</i> item, in the same sequence as they were given in the call to VDR. <hr><h2>User interaction</h2> <center><i><b>It's showtime!</b></i></center><p> If the user selects the main menu entry of a plugin, VDR calls the function <p><table><tr><td bgcolor=#F0F0F0><pre><br> virtual cOsdMenu *MainMenuAction(void); </pre></td></tr></table><p> which can do one of two things: <ul> <li>Return a pointer to a <tt>cOsdMenu</tt> object which will be displayed as a submenu of the main menu (just like the <i>Recordings</i> menu, for instance). That menu can then implement further functionality and, for instance, could eventually start a custom player to replay a file other than a VDR recording. <li>Perform a specific action and return <tt>NULL</tt>. In that case the main menu will be closed after calling <tt>MainMenuAction()</tt>. </ul> <b> It is very important that a call to <tt>MainMenuAction()</tt> returns as soon as possible! As long as the program stays inside this function, no other user interaction is possible. If a specific action takes longer than a few seconds, the plugin should launch a separate thread to do this. </b> <!--X1.1.2--><table width=100%><tr><td bgcolor=#00AA00> </td><td width=100%> <hr><h2>Housekeeping</h2> <center><i><b>Chores, chores...</b></i></center><p> From time to time a plugin may want to do some regular tasks, like cleaning up some files or other things. In order to do this it can implement the function <p><table><tr><td bgcolor=#F0F0F0><pre><br> virtual void Housekeeping(void); </pre></td></tr></table><p> which gets called when VDR is otherwise idle. The intervals between subsequent calls to this function are not defined. There may be several hours between two calls (if, for instance, there are recordings or replays going on) or they may be as close as ten seconds. The only thing that is guaranteed is that there are at least ten seconds between two subsequent calls to the <tt>Housekeeping()</tt> function of the same plugin. <p> <b> It is very important that a call to <tt>Housekeeping()</tt> returns as soon as possible! As long as the program stays inside this function, no other user interaction is possible. If a specific action takes longer than a few seconds, the plugin should launch a separate thread to do this. </b> <!--X1.1.2--></td></tr></table> <a name="Setup parameters"><hr><h2>Setup parameters</h2> <center><i><b>Remember me...</b></i></center><p> If a plugin requires its own setup parameters, it needs to implement the following functions to handle these parameters: <p><table><tr><td bgcolor=#F0F0F0><pre><br> virtual cMenuSetupPage *SetupMenu(void); virtual bool SetupParse(const char *Name, const char *Value); </pre></td></tr></table><p> The <tt>SetupMenu()</tt> function shall return the plugin's <a href="#The Setup menu"><i>Setup</i> menu</a> page, where the user can adjust all the parameters known to this plugin. <p> <tt>SetupParse()</tt> will be called for each parameter the plugin has previously stored in the global setup data (see below). It shall return <i>true</i> if the parameter was parsed correctly, <i>false</i> in case of an error. If <i>false</i> is returned, an error message will be written to the log file (and program execution will continue). <!--X1.1.1--><table width=100%><tr><td bgcolor=#0000AA> </td><td width=100%> A possible implementation of <tt>SetupParse()</tt> could look like this: <p><table><tr><td bgcolor=#F0F0F0><pre><br> bool cPluginHello::SetupParse(const char *Name, const char *Value) { // Parse your own setup parameters and store their values. if (!strcasecmp(Name, "GreetingTime")) GreetingTime = atoi(Value); else if (!strcasecmp(Name, "UseAlternateGreeting")) UseAlternateGreeting = atoi(Value); else return false; return true; </pre></td></tr></table><p> It is important to make sure that the parameter names are exactly the same as used in the <a href="#The Setup menu"><i>Setup</i> menu</a>'s <tt>Store()</tt> function. <!--X1.1.1--></td></tr></table> <p> The plugin's setup parameters are stored in the same file as VDR's parameters. In order to allow each plugin (and VDR itself) to have its own set of parameters, the <tt>Name</tt> of each parameter will be preceeded with the plugin's name, as in <p> <tt>hello.GreetingTime = 3</tt> <p> The prefix will be handled by the core VDR setup code, so the individual plugins need not worry about this. <p> To store its values in the global setup, a plugin has to call the function <p><table><tr><td bgcolor=#F0F0F0><pre><br> void SetupStore(const char *Name, <i>type</i> Value); </pre></td></tr></table><p> where <tt>Name</tt> is the name of the parameter (<tt>"GreetingTime"</tt> in the above example, without the prefix <tt>"hello."</tt>) and <tt>Value</tt> is a simple data type (like <tt>char *</tt>, <tt>int</tt> etc). Note that this is not a function that the individual plugin class needs to implement! <tt>SetupStore()</tt> is a non-virtual member function of the <tt>cPlugin</tt> class. <p> To remove a parameter from the setup data, call <tt>SetupStore()</tt> with the appropriate name and without any value, as in <p> <tt>SetupStore("GreetingTime");</tt> <p> The VDR menu "Setup/Plugins" will list all loaded plugins with their name, version number and description. Selecting an item in this list will bring up the plugin's "Setup" menu if that plugin has implemented the <tt>SetupMenu()</tt> function. <p> Finally, a plugin doesn't have to implement the <tt>SetupMenu()</tt> if it only needs setup parameters that are not directly user adjustable. It can use <tt>SetupStore()</tt> and <tt>SetupParse()</tt> without presenting these parameters to the user. <!--X1.1.1--><table width=100%><tr><td bgcolor=#0000AA> </td><td width=100%> <a name="The Setup menu"><hr><h2>The Setup menu</h2> <center><i><b>Have it your way!</b></i></center><p> To implement a <i>Setup</i> menu, a plugin needs to derive a class from <tt>cMenuSetupPage</tt> and implement its constructor and the pure virtual <tt>Store()</tt> member function: <p><table><tr><td bgcolor=#F0F0F0><pre><br> int GreetingTime = 3; int UseAlternateGreeting = false; class cMenuSetupHello : public cMenuSetupPage { private: int newGreetingTime; int newUseAlternateGreeting; protected: virtual void Store(void); public: cMenuSetupHello(void); }; cMenuSetupHello::cMenuSetupHello(void) { newGreetingTime = GreetingTime; newUseAlternateGreeting = UseAlternateGreeting; Add(new cMenuEditIntItem( tr("Greeting time (s)"), &newGreetingTime)); Add(new cMenuEditBoolItem(tr("Use alternate greeting"), &newUseAlternateGreeting)); } void cMenuSetupHello::Store(void) { SetupStore("GreetingTime", GreetingTime = newGreetingTime); SetupStore("UseAlternateGreeting", UseAlternateGreeting = newUseAlternateGreeting); } </pre></td></tr></table><p> In this example we have two global setup parameters (<tt>GreetingTime</tt> and <tt>UseAlternateGreeting</tt>). The constructor initializes two private members with the values of these parameters, so that the <i>Setup</i> menu can work with temporary copies (in order to discard any changes if the user doesn't confirm them by pressing the "Ok" button). After this the constructor adds the appropriate menu items, using internationalized texts and the addresses of the temporary variables. That's all there is to inizialize a <i>Setup</i> menu - the rest will be done by the core VDR code. <p> Once the user has pressed the "Ok" button to confirm the changes, the <tt>Store()</tt> function will be called, in which all setup parameters must be actually stored in VDR's global setup data. This is done by calling the <tt>SetupStore()</tt> function for each of the parameters. The <i>Name</i> string given here will be used to identify the parameter in VDR's <tt>setup.conf</tt> file, and will be automatically prepended with the plugin's name. <p> Note that in this small example the new values of the parameters are copied into the global variables within each <tt>SetupStore()</tt> call. This is not mandatory, however. You can first assign the temporary values to the global variables and then do the <tt>SetupStore()</tt> calls, or you can define a class or struct that contains all your setup parameters and use that one to copy all parameters with one single statement (like VDR does with its cSetup class). <!--X1.1.1--></td></tr></table> <!--X1.1.2--><table width=100%><tr><td bgcolor=#00AA00> </td><td width=100%> <hr><h2>Configuration files</h2> <center><i><b>I want my own stuff!</b></i></center><p> There may be situations where a plugin requires configuration files of its own, maybe for data that can't be stored in the simple <a href="#Setup parameters">setup parameters</a> of VDR, or maybe because it needs to launch other programs that simply need a separate configuration file. While the plugin is free to store such files anywhere it sees fit, it might be a good idea to put them in a common place, preferably where other configuration data already exists. VDR provides the function <p><table><tr><td bgcolor=#F0F0F0><pre><br> const char *ConfigDirectory(const char *PluginName = NULL); </pre></td></tr></table><p> which returns a string containing the directory that VDR uses for its own configuration files (defined through the <tt><b>-c</b></tt> option in the call to VDR), extended by <tt>"/plugins"</tt>. So assuming the VDR configuration directory is <tt>/video</tt> (the default if no <tt><b>-c</b></tt> or <tt><b>-v</b></tt> option is given), a call to <tt>ConfigDirectory()</tt> will return <tt>/video/plugins</tt>. The first call to <tt>ConfigDirectory()</tt> will automatically make sure that the <tt>plugins</tt> subdirectory will exist. If, for some reason, this cannot be achieved, <tt>NULL</tt> will be returned. <p> The additional <tt>plugins</tt> directory is used to keep files from plugins apart from those of VDR itself, making sure there will be no name clashes. If a plugin needs only one extra configuration file, it is suggested that this file be named <tt>name.conf</tt>, where <i>name</i> shall be the name of the plugin. <p> If a plugin needs more than one such file, it is suggested that the plugin stores these in a subdirectory of its own, named after the plugin. To easily get such a name the <tt>ConfigDirectory()</tt> function can be given an additional string that will be appended to the returned directory name, as in <p><table><tr><td bgcolor=#F0F0F0><pre><br> const char *MyConfigDir = ConfigDirectory(Name()); </pre></td></tr></table><p> where <tt>Name()</tt> is the member function of the plugin class that returns the plugin's name. Again, VDR will make sure that the requested directory will exist (or return <tt>NULL</tt> in case of an error). <p> <b> The returned string is statically allocated and will be overwritten by subsequent calls to ConfigDirectory()! </b> <p> The <tt>ConfigDirectory()</tt> function is a static member function of the <tt>cPlugin</tt> class. This allows it to be called even from outside any member function of the derived plugin class, by writing <p><table><tr><td bgcolor=#F0F0F0><pre><br> const char *MyConfigDir = cPlugin::ConfigDirectory(); </pre></td></tr></table><p> <!--X1.1.2--></td></tr></table> <a name="Internationalization"><hr><h2>Internationalization</h2> <center><i><b>Welcome to Babylon!</b></i></center><p> If a plugin displays texts to the user, it should implement internationalized versions of these texts and call the function <p><table><tr><td bgcolor=#F0F0F0><pre><br> void RegisterI18n(const tI18nPhrase * const Phrases); </pre></td></tr></table><p> to register them with VDR's internationalization mechanism. <p> The call to this function must be done in the <a href="#Getting started"><tt>Start()</tt></a> function of the plugin: <p><table><tr><td bgcolor=#F0F0F0><pre><br> const tI18nPhrase Phrases[] = { { "Hello world!", "Hallo Welt!", "",// TODO "",// TODO "",// TODO "",// TODO "",// TODO "",// TODO "",// TODO "",// TODO "",// TODO "",// TODO "",// TODO "",// TODO "",// TODO }, { NULL } }; void cPluginHello::Start(void) { RegisterI18n(Phrases); } </pre></td></tr></table><p> Each entry of type <tt>tI18nPhrase</tt> must have exactly as many members as defined by the constant <tt>I18nNumLanguages</tt> in the file <tt>VDR/i18n.h</tt>, and the sequence of the various languages must be the same as defined in <tt>VDR/i18n.c</tt>.<br> <b>It is very important that the array is terminated with a <tt>{ NULL }</tt> entry!</b>. <p> Usually you won't be able to fill in all the different translations by yourself, so you may want to contact the maintainers of these languages (listed in the file <tt>VDR/i18n.c</tt>) and ask them to provide the additional translations. <p> The actual runtime selection of the texts corresponding to the selected language is done by wrapping each internationalized text with the <tt>tr()</tt> macro: <p><table><tr><td bgcolor=#F0F0F0><pre><br> const char *s = tr("Hello world!"); </pre></td></tr></table><p> The text given here must be the first one defined in the related <i>Phrases</i> entry (which is the English version), and the returned pointer is either a translated version (if available) or the original string. In the latter case a message will be written to the log file, indicating that a translation is missing. Texts are first searched for in the <i>Phrases</i> registered for this plugin (if any) and then in the global VDR texts. So a plugin can make use of texts defined by the core VDR code. <a name="Loading plugins into VDR"><hr><h2>Loading plugins into VDR</h2> <center><i><b>Saddling up!</b></i></center><p> Plugins are loaded into VDR using the command line option <b><tt>-P</tt></b>, as in <p><table><tr><td bgcolor=#F0F0F0><pre><br> vdr -Phello </pre></td></tr></table><p> If the plugin accepts command line options, they are given as part of the argument to the <b><tt>-P</tt></b> option, which then has to be enclosed in quotes: <p><table><tr><td bgcolor=#F0F0F0><pre><br> vdr -P"hello -a abc -b" </pre></td></tr></table><p> Any number of plugins can be loaded this way, each with its own <b><tt>-P</tt></b> option: <p><table><tr><td bgcolor=#F0F0F0><pre><br> vdr -P"hello -a abc -b" -Pdvd -Pmp3 </pre></td></tr></table><p> If you are not starting VDR from the VDR source directory (and thus your plugins cannot be found at their default location) you need to tell VDR the location of the plugins through the <b><tt>-L</tt></b> option: <p><table><tr><td bgcolor=#F0F0F0><pre><br> vdr -L/usr/lib/vdr -Phello </pre></td></tr></table><p> There can be any number of <b><tt>-L</tt></b> options, and each of them will apply to the <b><tt>-P</tt></b> options following it. <p> When started with the <b><tt>-h</tt></b> or <b><tt>-V</tt></b> option (for <i>help</i> or <i>version</i> information, respectively), VDR will automatically load all plugins in the default or given directory that match the VDR plugin <a href="#The plugin directory structure">naming convention</a>, and display their help and/or version information in addition to its own output. <a name="Building the distribution package"><hr><h2>Building the distribution package</h2> <center><i><b>Let's get this show on the road!</b></i></center><p> If you want to make your plugin available to other VDR users, you'll need to make a package that can be easily distributed. <!--X1.1.3--><table width=100%><tr><td bgcolor=#AA0000> </td><td width=100%> The <tt>Makefile</tt> that has been created by the call to <a href="#Initializing a new plugin directory"><tt>newplugin</tt></a> provides the target <tt>dist</tt>, which does this for you. <p> Simply change into your source directory and execute <tt>make dist</tt>: <p><table><tr><td bgcolor=#F0F0F0><pre><br> cd VDR/PLUGINS/src/hello make dist </pre></td></tr></table><p> <!--X1.1.3--></td></tr></table> After this you should find a file named like <p><table><tr><td bgcolor=#F0F0F0><pre><br> vdr-hello-0.0.1.tgz </pre></td></tr></table><p> in your source directory, where <tt>hello</tt> will be replaced with your actual plugin's name, and <tt>0.0.1</tt> will be your plugin's current version number. <!--X1.1.3--><table width=100%><tr><td bgcolor=#AA0000> </td><td width=100%> <a name="Part II - The Inside Interface"><hr><center><h1>Part II - The Inside Interface</h1></center> <hr><h2>Status monitor</h2> <center><i><b>A piece of the action</b></i></center><p> If a plugin wants to get informed on various events in VDR, it can derive a class from <tt>cStatus</tt>, as in <p><table><tr><td bgcolor=#F0F0F0><pre><br> #include <vdr/status.h> class cMyStatusMonitor : public cStatus { protected: virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber); }; void cMyStatusMonitor::ChannelSwitch(const cDevice *Device, int ChannelNumber) { if (ChannelNumber) dsyslog("channel switched to %d on DVB %d", ChannelNumber, Device->CardIndex()); else dsyslog("about to switch channel on DVB %d", Device->CardIndex()); } </pre></td></tr></table><p> An object of this class will be informed whenever the channel is switched on one of the DVB devices. It could be used in a plugin like this: <p><table><tr><td bgcolor=#F0F0F0><pre><br> #include <vdr/plugin.h> class cPluginStatus : public cPlugin { private: cMyStatusMonitor *statusMonitor; public: cPluginStatus(void); virtual ~cPluginStatus(); ... virtual bool Start(void); ... }; cPluginStatus::cPluginStatus(void) { statusMonitor = NULL; } cPluginStatus::~cPluginStatus() { delete statusMonitor; } bool cPluginStatus::Start(void) { statusMonitor = new cMyStatusMonitor; return true; } </pre></td></tr></table><p> Note that the actual object is created in the <tt>Start()</tt> function, not in the constructor! It is also important to delete the object in the destructor, in order to avoid memory leaks. <p> A Plugin can implement any number of <tt>cStatus</tt> derived objects, and once the plugin has been started it may create and delete them as necessary. No further action apart from creating an object derived from <tt>cStatus</tt> is necessary. VDR will automatically hook it into a list of status monitors, with their individual virtual member functions being called in the same sequence as the objects were created. <p> See the file <tt>status.h</tt> for detailed information on which status monitor member functions are available in <tt>cStatus</tt>. You only need to implement the functions you actually want to use. <!--X1.1.3--></td></tr></table> <!--X1.1.4--><table width=100%><tr><td bgcolor=#FF0000> </td><td width=100%> <hr><h2>Players</h2> <center><i><b>Play it again, Sam!</b></i></center><p> Implementing a player is a two step process. First you need the actual player class, which is derived from the abstract <tt>cPlayer</tt>: <p><table><tr><td bgcolor=#F0F0F0><pre><br> #include <vdr/player.h> class cMyPlayer : public cPlayer { protected: virtual void Activate(bool On); public: cMyPlayer(void); virtual ~cMyPlayer(); }; </pre></td></tr></table><p> What exactly you do in this class is entirely up to you. If you want to run a separate thread which, e.g., reads data from a file, you can additionally derive your class from <tt>cThread</tt> and implement the necessary functionality: <p><table><tr><td bgcolor=#F0F0F0><pre><br> #include <vdr/player.h> class cMyPlayer : public cPlayer, cThread { protected: virtual void Activate(bool On); virtual void Action(void); public: cMyPlayer(void); virtual ~cMyPlayer(); }; </pre></td></tr></table><p> Take a look at the files <tt>player.h</tt> and <tt>dvbplayer.c</tt> to see how VDR implements its own player for the VDR recordings. <p> To play the video data, the player needs to call its member function <p><table><tr><td bgcolor=#F0F0F0><pre><br> int PlayVideo(const uchar *Data, int Length); </pre></td></tr></table><p> where <tt>Data</tt> point to a block of <tt>Length</tt> bytes of a PES data stream. There are no prerequisites regarding the length or alignment of an individual block of data. The sum of all blocks must simply result in the desired video data stream, and it must be delivered fast enough so that the DVB device doesn't run out of data. <p> TODO: PlayAudio()??? <p> The second part needed here is a control object that receives user input from the main program loop and reacts on this by telling the player what to do: <p><table><tr><td bgcolor=#F0F0F0><pre><br> #include <vdr/player.h> class cMyControl : public cControl { private: cMyPlayer *player; public: cMyControl(void); virtual ~cMyControl(); virtual void Hide(void); virtual eOSState ProcessKey(eKeys Key); }; </pre></td></tr></table><p> <tt>cMyControl</tt> shall create an object of type <tt>cMyPlayer</tt> and hand over a pointer to it to the <tt>cControl</tt> base class, so that it can be later attached to the primary DVB device: <p><table><tr><td bgcolor=#F0F0F0><pre><br> cMyControl::cMyControl(void) :cControl(player = new cMyPlayer) { } </pre></td></tr></table><p> <tt>cMyControl</tt> will receive the user's key presses through the <tt>ProcessKey()</tt> function. It will get all button presses, except for the volume control buttons (<tt>kVolUp</tt>, <tt>kVolDn</tt>, <tt>kMute</tt>), the power button (<tt>kPower</tt>) and the menu button (<tt>kMenu</tt>). If the user has not pressed a button for a while (which is typically in the area of about one second), <tt>ProcessKey()</tt> will be called with <tt>kNone</tt>, so that the <tt>cMyControl</tt> gets a chance to check whether its player is still active. Once the player has become inactive (because the user has decided to stop it or the DVB device has detached it), <tt>ProcessKey()</tt> must return <tt>osEnd</tt> to make the main program loop shut down the player control. <p> A derived <tt>cControl</tt> <b>must</b> implement the <tt>Hide()</tt> function, in which it has to hide itself from the OSD, in case it uses it. <tt>Hide()</tt> may be called at any time, and it may be called even if the <tt>cControl</tt> is not visible at the moment. The reason for this is that the <tt>Menu</tt> button shall always bring up the main VDR menu, so any active <tt>cControl</tt> needs to be hidden when that button is pressed. <p> Finally, to get things going, a plugin that implements a player (and the surrounding infrastructure like displaying a list of playable stuff etc) simply has to call the static function <tt>cControl::Launch()</tt> with the player control object, as in <p><table><tr><td bgcolor=#F0F0F0><pre><br> cControl::Launch(new cMyControl); </pre></td></tr></table><p> Ownership of the <tt>MyControl</tt> object is handed over to the VDR core code, so the plugin should not keep a pointer to it, because VDR will destroy the object whenever it sees fit (for instance because a recording shall start that needs to use the primary DVB device, or the user decides to start a different replay). <p> The <tt>cPlayer</tt> class has a member function <p><table><tr><td bgcolor=#F0F0F0><pre><br> void DeviceStillPicture(const uchar *Data, int Length); </pre></td></tr></table><p> which can be called to display a still picture. VDR uses this function when handling its editing marks. A special case of a "player" might use this function to implement a "picture viewer". <p> For detailed information on how to implement your own player, please take a look at VDR's <tt>cDvbPlayer</tt> and <tt>cDvbPlayerControl</tt> classes. <p> <b>User interface</b> <p> In order for a new player to nicely "blend in" to the overall VDR appearance it is recommended that it implements the same functionality with the same keys as the VDR player does (as far as this is possible and makes sense). The main points to consider here are <ul> <li>The <i>Ok</i> button shall bring up some display that indicates what is currently being played, and what the status of this replay session is. As an alternative (for instance with a DVD player) it may display a player specific menu, from which the user can select certain options. <li>The <i>Up</i>, <i>Down</i>, <i>Left</i> and <i>Right</i> buttons shall control <i>Play</i>, <i>Pause</i>, <i>Fast Rewind</i> and <i>Fast Forward</i>, respectively (provided that this particular player can implement these functions) if the player is not currently showing any menu. If there is a menu, they shall allow the user to navigate in the menu. <li>The <i>Green</i> and <i>Yellow</i> buttons shall skip back- and forward by an amount of time suitable for this player (provided that this particular player can implement these functions). <li>The <i>Blue</i> button shall immediately stop the replay session. </ul> Of course, these are only suggestions which should make it easier for VDR users to enjoy additional players, since they will be able to control them with actions that they already know. If you absolutely want to do things differently, just go ahead - it's your show... <!--X1.1.4--></td></tr></table> </body> </html>