From 97c72f14558ee8e53267491b89b024cb487e9228 Mon Sep 17 00:00:00 2001 From: Manuel Reimer Date: Mon, 18 Sep 2023 16:51:53 +0200 Subject: [PATCH] Created PLUGINS.html (markdown) --- PLUGINS.html.md | 2396 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2396 insertions(+) create mode 100644 PLUGINS.html.md diff --git a/PLUGINS.html.md b/PLUGINS.html.md new file mode 100644 index 0000000..d7b53b6 --- /dev/null +++ b/PLUGINS.html.md @@ -0,0 +1,2396 @@ +
+

The VDR Plugin System

+ +Version 2.6 +

+Copyright © 2021 Klaus Schmidinger
+vdr@tvdr.de
+www.tvdr.de +

+

+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). +

+This document is divided into two parts, the first one describing the +external interface +of the plugin system, and the second one describing the +internal interface. +The external interface handles everything necessary for a plugin to get hooked into the core +VDR program and present itself to the user. +The internal 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. + +


+

Table Of Contents

+ + +

Part I - The External Interface

+ +

Quick start

+ +
Can't wait, can't wait!

+ +Actually you should read this entire document before starting to work with VDR plugins, +but you probably want to see something happening right away ;-) +

+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: +

+If you enjoyed this brief glimpse into VDR plugin handling, read through the rest of +this document and eventually write your own VDR plugin. + +

The name of the plugin

+ +
Give me some I.D.!

+ +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. +

+The plugin's name should typically be as short as possible. Three letter +abbreviations like dvd (for a DVD player) or mp3 +(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. +

+A plugin can access its name through the (non virtual) member function + +

+const char *Name(void);
+

+ +The actual name is derived from the plugin's library file name, as defined in the +next chapter. + +


The plugin directory structure

+ +
Where is everybody?

+ +By default plugins are located in a directory named PLUGINS below the +VDR source directory. Inside this directory the following subdirectory structure +is used: + +

+VDR/PLUGINS/src
+VDR/PLUGINS/src/hello
+VDR/PLUGINS/lib
+VDR/PLUGINS/lib/libvdr-hello.so.1.1.0
+

+ +The src directory contains one subdirectory for each plugin, which carries +the name of that plugin (in the above example that would be hello). +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 Makefile that provides the targets all, install and +clean, and that a call to make all actually produces a dynamically +loadable library file for that plugin (we'll get to the details later). +The dynamically loadable library file for the plugin shall be located directly under +the plugin's source directory. +See the section Initializing a new plugin directory +for how to generate an example Makefile. +

+The lib directory contains the dynamically loadable libraries of all +available plugins. Note that the names of these files are created by concatenating +

+ + + +
libvdr-hello.so.1.1.0
VDR plugin
library prefix
name of
the plugin
shared object
indicator
API version number
this plugin was
compiled for
+

+The API version number refers to the plugin API version number of the VDR +version this plugin was compiled with. Compiled plugins can run with newer versions +of VDR as long as their plugin API version number is still the same as that of +the current VDR version. That way minor fixes to VDR, that don't require changes +to the VDR header files, can be made without requiring all plugins to be +recompiled. +

+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 +-L option. +

+The VDR Makefile contains the target plugins, which calls +make all in every directory found under VDR/PLUGINS/src, +plus the target clean-plugins, which calls make clean in +each of these directories. +

+If you download a plugin package +from the web, it will typically have a name like +

+vdr-hello-0.0.1.tgz +

+and will unpack into a directory named +

+hello-0.0.1 +

+To use the plugins and clean-plugins targets from the VDR Makefile +you need to unpack such an archive into the VDR/PLUGINS/src directory and +create a symbolic link with the basic plugin name, as in + +

+ln -s hello-0.0.1 hello
+

+ +Since the VDR Makefile 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 hello-0.0.1 and +hello-0.0.2) and define which one to actually use through the symbolic link. +

+If a plugin needs library files of its own, it can copy them to the lib +directory following the naming convention libname-library.so.0.0.1, +where name is the name of the plugin, and library identifies the +plugin's additional library. If the plugin hello would require the two +additional libraries foo and bar, the names would be +

+libhello-foo.so.0.0.1
+libhello-bar.so.0.0.1 +

+ +


Initializing a new plugin directory

+ +
A room with a view

+ +Call the Perl script newplugin from the VDR source directory to create +a new plugin directory with a Makefile and a main source file implementing +the basic derived plugin class. +You will also find a README file there with some initial text, where you +should fill in actual information about your project. +A HISTORY file is set up with an "Initial revision" entry. As your project +evolves, you should add the changes here with date and version number. +

+newplugin also creates a copy of the GPL license file COPYING, +assuming that you will release your work under that license. Change this if you +have other plans. +

+Add further files and maybe subdirectories to your plugin source directory as +necessary. Don't forget to adapt the Makefile appropriately. + +


The actual implementation

+ +
Use the source, Luke!

+ +A newly initialized plugin doesn't really do very much yet. +If you load it into VDR 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). +

+To implement actual functionality into your plugin you need to edit the source file +that was generated as PLUGINS/src/name.c. 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. +

+Depending on what your plugin shall do, you may or may not need all of the given +member functions. Except for the MainMenuEntry() 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 MainMenuEntry() function. +

+At the end of the plugin's source file you will find a line that looks like this: + +

+VDRPLUGINCREATOR(cPluginHello);
+

+ +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. +

+If your plugin requires additional source files, simply add them to your plugin's +source directory and adjust the Makefile accordingly. +

+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 + +

+#ifndef __I18N_H
+#define __I18N_H
+
+...
+
+#endif //__I18N_H
+

+ +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 precedes 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). +

+As long as you make sure 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 (i18n.h 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 'P', as in +P__I18N_H, or leave out the trailing _H, as in __I18N, +or use a completely different way to make sure a header file is included only once. +

+The 'hello' example that comes with VDR makes use of internationalization +and implements a file named i18n.h. To make sure it won't clash with VDR's +i18n.h it uses the macro _I18N__H (one underline at the beginning +and two replacing the dot). + +


Construction and Destruction

+ +
What goes up, must come down...

+ +The constructor and destructor of a plugin are defined as + +

+cPlugin(void);
+virtual ~cPlugin();
+

+ +The constructor shall initialize any member variables the plugin defines, but +must not access any global structures of VDR. +It also must not create any threads or other large data structures. These things +are done in the +Initialize() or +Start() +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. +

+The destructor has to clean up any data created by the plugin. +Any threads the plugin may have created shall be stopped in the +Stop() function. +

+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. + +


Version number

+ +
Which incarnation is this?

+ +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 + +

+virtual const char *Version(void) = 0;
+

+ +Since this is a "pure" virtual function, any derived plugin class must +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: + +

+static const char *VERSION = "0.0.1";
+
+const char *cPluginHello::Version(void)
+{
+  return VERSION;
+}
+

+ +Note that the definition of the version number is expected to be located in the +main source file, and must be written as +

+static const char *VERSION = ...
+
+

+just like shown in the above example. This is a convention that allows the Makefile +to extract the version number when generating the file name for the distribution archive. +

+A new plugin project should start with version number 0.0.1 and should reach +version 1.0.0 once it is completely operative and well tested. Following the +Linux kernel version numbering scheme, versions with even release numbers +(like 1.0.x, 1.2.x, 1.4.x...) should be stable releases, +while those with odd release numbers (like 1.1.x, 1.3.x, +1.5.x...) are usually considered "under development". The three parts of +a version number are not limited to single digits, so a version number of 1.2.15 +would be acceptable. + +


Description

+ +
What is it that you do?

+ +In order to tell the user what exactly a plugin does, it must implement the function + +

+virtual const char *Description(void) = 0;
+

+ +which returns a short, one line description of the plugin's purpose: + +

+static const char *DESCRIPTION = "A friendly greeting";
+
+virtual const char *Description(void)
+{
+  return tr(DESCRIPTION);
+}
+

+ +Note the tr() around the DESCRIPTION, which allows the description +to be internationalized. + +


Command line arguments

+ +
Taking orders

+ +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 + +

+virtual bool ProcessArgs(int argc, char *argv[]);
+

+ +The parameters argc and argv have exactly the same meaning +as in a normal C program's main() function. +argv[0] contains the name of the plugin (as given in the -P +option of the vdr call). +

+Each plugin has its own set of command line options, which are totally independent +from those of any other plugin or VDR itself. +

+You can use the getopt() or getopt_long() function to process +these arguments. As with any normal C program, the strings pointed to by argv +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: + +

+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;
+}
+

+ +The return value must be true if all options have been processed +correctly, or false in case of an error. The first plugin that returns +false from a call to its ProcessArgs() function will cause VDR +to exit. + +


Command line help

+ +
Tell me about it...

+ +If a plugin accepts command line options, it should implement the function + +

+virtual const char *CommandLineHelp(void);
+

+ +which will be called if the user enters the -h 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: + +

+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";
+}
+

+ +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. + +


Getting started

+ +
Let's get ready to rumble!

+ +If a plugin implements a function that runs in the background (presumably in a +thread of its own), or wants to make use of internationalization, +it needs to implement one of the functions + +

+virtual bool Initialize(void);
+virtual bool Start(void);
+

+ +which are called once for each plugin at program startup. +The difference between these two functions is that Initialize() is +called early at program startup, while Start() is called after the primary +device and user interface has been set up, but before the main program loop is entered. +Inside the Start() function of any plugin it is guaranteed that the Initialize() +functions of all plugins have already been called. For many plugins it probably +doesn't matter which of these functions they implement, but it may be of importance +for, e.g., plugins that implement devices. Such plugins should create their cDevice +derived objects in Initialize(), so that other plugins can use them in their +Start() functions. +

+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. +

+A return value of false 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 +false from its Initialize() or Start() function will cause +VDR to exit. +

+If the plugin doesn't implement any background functionality or internationalized +texts, it doesn't need to implement either of these functions. + +


Shutting down

+ +
Stop it, right there!

+ +If a plugin performs any background tasks, it shall implement the function + +

+virtual void Stop(void);
+

+ +in which it shall stop them. +

+The Stop() function will only be called if a previous call to the +Start() function of that plugin has +returned true. The Stop() functions are called in the reverse order +as the Start() functions were called. + +


Logging

+ +
Traces in the sand...

+ +

+If the plugin should print log messages, you can use dsyslog(), isyslog() or esyslog().
+

+The output of this log is the syslog of the system vdr is running on. +The log message can be formatted like printf(), as in + +

+esyslog("pluginname: error #%d has occurred", ErrorNumber);
+

+ +Note that the log messages will be given as provided, the plugin's name will not +automatically be added, so make sure your log messages are obvious enough. +

+Only use the above logging functions for occasional log messages. Do not use +them unconditionally for frequent messages that produce long sequences of lines +in the log file every few seconds. That might make it hard to work on other plugins +or the core VDR code, watching their log entries while they are permanently +interspersed with unrelated stuff.
+
+The recommended behavior for a plugin that does logging is to implement a command +line option that controls the level of log messages, preferably '-l N, --log=N', +where 'N' is in the range 0...3, with 0 meaning no logging whatsoever, 1 log only +errors, 2 log errors and informational messages, and 3 also log debug information.
+
+If a plugin can output extensive data for special debugging purposes (either to +the log file or stdout/stderr), this should be enabled by setting proper switches +in one of its source files (see for example how the communication between VDR and +CAMs can be monitored in VDR/ci.c).
+
+Under no circumstances must a plugin print anything to stdout or stderr during +normal operation! The only exceptions being special debug information as described +above, fatal error messages that will cause VDR to abort, or if it is the sole +purpose of the plugin to display something on stdout, like for instance the +skincurses plugin, which displays the OSD at the console.
+
+Please make any log messages in ENGLISH! Logs are usually sent to the developers +of a program, and they may not be able to read them if they are in an exotic language. + +


Main menu entry

+ +
Today's special is...

+ +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 + +

+virtual const char *MainMenuEntry(void);
+

+ +The default implementation returns a NULL 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: + +

+static const char *MAINMENUENTRY = "Hello";
+
+const char *cPluginHello::MainMenuEntry(void)
+{
+  return tr(MAINMENUENTRY);
+}
+

+ +The menu entries of all plugins will be inserted into VDR's main menu right +after the Recordings item, in the same sequence as they were given +in the call to VDR. + +


User interaction

+ +
It's showtime!

+ +If the user selects the main menu entry of a plugin, VDR calls the function + +

+virtual cOsdObject *MainMenuAction(void);
+

+ +which can do one of three things: +

+ +It is very important that a call to MainMenuAction() 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. + + +

Housekeeping

+ +
Chores, chores...

+ +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 + +

+virtual void Housekeeping(void);
+

+ +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 Housekeeping() +function of the same plugin. +

+ +It is very important that a call to Housekeeping() 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. + + +


Main thread hook

+ +
Pushing in...

+ +Normally a plugin only reacts on user input if directly called through its +main menu entry, or performs some background +activity in a separate thread. However, sometimes a plugin may need to do +something in the context of the main program thread, without being explicitly +called up by the user. In such a case it can implement the function + +

+virtual void MainThreadHook(void);
+

+ +in which it can do this. This function is called for every plugin once during +every cycle of VDR's main program loop, which typically happens once every +second. +Be very careful when using this function, and make sure you return from it +as soon as possible! If you spend too much time in this function, the user +interface performance will become sluggish! + +


Activity

+ +
Now is not a good time!

+ +If a plugin is running a background task that should be finished before shutting +down the system, it can implement the function + +

+virtual cString Active(void);
+

+ +which shall return an empty string if it is ok to shut down, and a proper message +if not: + +

+cString cDoSomethingPlugin::Active(void)
+{
+  if (busy)
+     return tr("Doing something");
+  return NULL;
+}
+

+ +The message should be short and should indicate what is currently going on. +It will be presented to the user as a confirmation message, followed by a +hyphen and a "shut down anyway?" prompt, as in +

+Doing something - shut down anyway? +

+All plugins will be queried, and the first one that returns a non empty +string will cause the confirmation message to be shown. If the user confirms +the prompt by pressing the "Ok" button, the rest of the plugins will also +be queried, and further prompts may show up. If all prompts have been confirmed, +the shutdown will take place. As soon as one prompt is not confirmed, no +further plugins will be queried and no shutdown will be done. + +


Wakeup

+ +
Wake me up before you go-go

+ +If a plugin wants to schedule activity for a later time, or wants to perform +periodic activity at a certain time at night, and if VDR shall wake up from +shutdown at that time, the plugin can implement the function + +

+virtual time_t WakeupTime(void);
+

+ +which shall return the time of the next custom wakeup time, or 0 if no wakeup +is planned. VDR will pass the most recent wakeup time of all plugins, or the next +timer time, whichever comes first, to the shutdown script. The following sample +will wake up VDR every night at 1:00: + +

+time_t MyPlugin::WakeupTime(void)
+{
+  time_t Now = time(NULL);
+  time_t Time = cTimer::SetTime(Now, cTimer::TimeToInt(100));
+  if (Time <= Now)
+     Time = cTimer::IncDay(Time, 1);
+  return Time;
+}
+

+ +After wakeup, the plugin shall continue to return the wakeup time and shall +return a string when Active() is called at that time, otherwise VDR may shut down +again instantly. If WakeupTime() returns a time that is not in +the future, the time will be ignored. + +


Setup parameters

+ +
Remember me...

+ +If a plugin requires its own setup parameters, it needs to implement the following +functions to handle these parameters: + +

+virtual cMenuSetupPage *SetupMenu(void);
+virtual bool SetupParse(const char *Name, const char *Value);
+

+ +The SetupMenu() function shall return the plugin's Setup menu +page, where the user can adjust all the parameters known to this plugin. +

+SetupParse() will be called for each parameter the plugin has +previously stored in the global setup data (see below). It shall return +true if the parameter was parsed correctly, false in case of +an error. If false is returned, an error message will be written to +the log file (and program execution will continue). +A possible implementation of SetupParse() could look like this: + +

+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;
+

+ +It is important to make sure that the parameter names are exactly the same as +used in the Setup menu's Store() function. +

+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 Name of each parameter will be preceded with the plugin's +name, as in +

+hello.GreetingTime = 3 +

+The prefix will be handled by the core VDR setup code, so the individual +plugins need not worry about this. +

+To store its values in the global setup, a plugin has to call the function + +

+void SetupStore(const char *Name, type Value);
+

+ +where Name is the name of the parameter ("GreetingTime" in the above +example, without the prefix "hello.") and Value is a simple data type (like +char *, int etc). +Note that this is not a function that the individual plugin class needs to implement! +SetupStore() is a non-virtual member function of the cPlugin class. +

+To remove a parameter from the setup data, call SetupStore() with the appropriate +name and without any value, as in +

+SetupStore("GreetingTime"); +

+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 SetupMenu() +function. +

+Finally, a plugin doesn't have to implement the SetupMenu() if it only +needs setup parameters that are not directly user adjustable. It can use +SetupStore() and SetupParse() without presenting these +parameters to the user. + +


The Setup menu

+ +
Have it your way!

+ +To implement a Setup menu, a plugin needs to derive a class from +cMenuSetupPage and implement its constructor and the pure virtual +Store() member function: + +

+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 cMenuEdittItem( 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);
+}
+

+ +In this example we have two global setup parameters (GreetingTime and UseAlternateGreeting). +The constructor initializes two private members with the values of these parameters, so +that the Setup 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 initialize a Setup +menu - the rest will be done by the core VDR code. +

+Once the user has pressed the "Ok" button to confirm the changes, the Store() 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 SetupStore() function for each of the parameters. +The Name string given here will be used to identify the parameter in VDR's +setup.conf file, and will be automatically prepended with the plugin's name. +

+Note that in this small example the new values of the parameters are copied into the +global variables within each SetupStore() call. This is not mandatory, however. +You can first assign the temporary values to the global variables and then do the +SetupStore() 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). + +


Additional files

+ +
I want my own stuff!

+ +There may be situations where a plugin requires files of its own. 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 such data already exists. +

+configuration files, maybe for data that can't be stored in the simple +setup parameters of VDR, or maybe because it needs to +launch other programs that simply need a separate configuration file. +

+cache files, to store data so that future requests for that data can be served faster. The data +that is stored within a cache might be values that have been computed earlier or duplicates of +original values that are stored elsewhere. +

+resource files, for providing additional files, like pictures, movie clips or channel logos. +

+Therefore VDR provides the functions + +

+const char *ConfigDirectory(const char *PluginName = NULL);
+const char *CacheDirectory(const char *PluginName = NULL);
+const char *ResourceDirectory(const char *PluginName = NULL);
+

+ +each of which returns a string containing the directory that VDR uses for its own +files (defined through the options in the call to VDR), extended by +"/plugins". So assuming the VDR configuration directory is /video +(the default if no -c or -v option is given), +a call to ConfigDirectory() will return /video/plugins. The first +call to ConfigDirectory() will automatically make sure that the plugins +subdirectory will exist. If, for some reason, this cannot be achieved, NULL +will be returned. +The behavior of CacheDirectory() and ResourceDirectory() is similar. +

+The additional plugins 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 file, it is suggested that this file be named name.*, +where name shall be the name of the plugin. +

+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 functions can be given an additional string that will be appended to the returned +directory name, as in + +

+const char *MyConfigDir = ConfigDirectory(Name());
+

+ +where Name() 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 NULL in case of an error). +

+ +The returned strings are statically allocated and will be overwritten by subsequent calls! + +

+The ConfigDirectory(), CacheDirectory() and ResourceDirectory() +functions are static member functions of the cPlugin class. This allows them to be +called even from outside any member function of the derived plugin class, by writing + +

+const char *MyConfigDir = cPlugin::ConfigDirectory();
+

+ +


Internationalization

+ +
Welcome to Babylon!

+ +If a plugin displays texts to the user, it should prepare for internationalization +of these texts. All that is necessary for this is to mark every text that is +presented to the user as translatable, as in + +

+const char *s = tr("Hello world!");
+

+ +The text given here must be the English version, and the returned pointer is either +a translated version (if available) or the original string. +Texts are searched for in the domain registered for this plugin. +If a plugin wants to make use of texts defined by the core VDR code, it can use +the special trVDR() macro to mark these texts without having them +appear in its own translation file. +

+Sometimes texts are stored in an array, in which case they need to be marked +differently, using the trNOOP() macro. The actual translation is then done +when such a text is used, as in + +

+const char *Texts = {
+  trNOOP("First text"),
+  trNOOP("Second text"),
+  trNOOP("Third one")
+  };
+
+for (int i = 0; i < 3; i++)
+    MyFunc(tr(Texts[i]));
+

+ +

+The system VDR is running on may use a character encoding where a single character +(or symbol) consists of more than one byte (UTF-8, as opposed to, for instance, +ISO8859-1, where every character is represented by a single byte in memory). +In order to make sure a plugin works regardless of the character encoding the current +system uses, the VDR core code provides several functions and macros that allow accessing +text strings transparently without knowing whether this is a single or multi byte +character set. The names of these functions and macros are all of the form Utf8...(), +and are defined in VDR/tools.h. +Most of the time a plugin doesn't need to care about this, but when it comes to +handling individual characters these functions may come in handy. + +


Custom services

+ +
What can I do for you?

+ +In some situations, two plugins may want to communicate directly, talking about things +that VDR doesn't handle itself. For example, a plugin may want to use features +that some other plugin offers, or it may want to inform other plugins about important +things it does. To receive requests or messages, a plugin can implement the +following function: + +

+virtual bool Service(const char *Id, void *Data = NULL);
+

+ +Id is a unique identification string that identifies the service protocol. +To avoid collisions, the string should contain a service name, the plugin name (unless +the service is not related to a single plugin) and a protocol version number. +Data points to a custom data structure. For each id string +there should be a specification that describes the format of the data +structure, and any change to the format should be reflected by a change +of the id string. +

+The function shall return true for any service id string it handles, and false +otherwise. The plugins have to agree in which situations the service +may be called, for example whether the service may be called from every thread, or +just from the main thread. A possible implementation could look like this: + +

+struct Hello_SetGreetingTime_v1_0 {
+  int NewGreetingTime;
+  };
+
+bool cPluginHello::Service(const char *Id, void *Data)
+{
+  if (strcmp(Id, "Hello-SetGreetingTime-v1.0") == 0) {
+     if (Data == NULL)
+        return true;
+     GreetingTime = ((Hello_SetGreetingTime_v1_0*)Data)->NewGreetingTime;
+     return true;
+     }
+  return false;
+}
+

+ +Plugins should expect to be called with Data set to NULL and may use +this as a 'service supported' check without performing any actions. +

+To send messages to, or request services from a specific plugin, one plugin can directly +call another plugin's service function: + +

+Hello_SetGreetingTime_v1_0 hellodata;
+hellodata.NewGreetingTime = 3;
+cPlugin *Plugin = cPluginManager::GetPlugin("hello");
+if (Plugin)
+   Plugin->Service("Hello-SetGreetingTime-v1.0", >hellodata);
+

+ +To send messages to, or request services from some plugin that offers the protocol, a +plugin can call the function cPluginManager::CallFirstService(). This function +will send the request to all plugins until one plugin handles it. +The function returns a pointer to the plugin that handled the request, or NULL +if no plugin handled it. +

+To send a message to all plugins, a plugin can call the function +cPluginManager::CallAllServices(). This function returns true if +any plugin handled the request, or false if no plugin handled the request. + +


SVDRP commands

+ +
Infinite Diversity in Infinite Combinations

+ +A plugin can implement its own SVDRP commands through the two functions + +

+virtual const char **SVDRPHelpPages(void);
+virtual cString SVDRPCommand(const char *Cmd, const char *Option, int &ReplyCode);
+

+ +The SVDRPHelpPages() function must return a pointer to a list of help +strings for all of the plugin's SVDRP commands, like this + +

+const char **cPluginSvdrpdemo::SVDRPHelpPages(void)
+{
+  static const char *HelpPages[] = {
+    "DATE\n"
+    "    Print the current date.",
+    "TIME [ raw ]\n"
+    "    Print the current time.\n"
+    "    If the optional keyword 'raw' is given, the result will be the\n"
+    "    raw time_t data.",
+    NULL
+    };
+  return HelpPages;
+}
+

+ +Note that the first line of each entry contains the actual command and its +parameters, while the following lines explain what the command does and what +the parameters (if any) mean. All lines of the explanation shall be indented +by exactly 4 blanks (no tabs), and none of them shall be longer than 79 characters +(to avoid messy output on 80 character wide terminals). The last entry in the +list must be NULL. +

+The command names HELP and MAIN are reserved and cannot +be used by a plugin. +

+The actual processing of SVDRP commands for a plugin is done in its +SVDRPCommand() function. +Here's an example of such a function, which implements the commands advertised in +the above help texts: + +

+cString cPluginSvdrpdemo::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
+{
+  if (strcasecmp(Command, "DATE") == 0) {
+     // we use the default reply code here
+     return DateString(time(NULL));
+     }
+  else if (strcasecmp(Command, "TIME") == 0) {
+     ReplyCode = 901;
+     if (*Option) {
+        if (strcasecmp(Option, "RAW") == 0)
+           return cString::sprintf("%ld\nThis is the number of seconds since the epoch\n"
+                                   "and a demo of a multi-line reply", time(NULL));
+        else {
+           ReplyCode = 504;
+           return cString::sprintf("Unknown option: \"%s\"", Option);
+           }
+        }
+     return TimeString(time(NULL));
+     }
+  return NULL;
+}
+

+ +The command is given to this function in the Command parameter, and any optional parameters +are given in the Option string. Command always points to an actual, non-empty string, while +Option may point to an empty string (it is never NULL, though). +

+If a plugin doesn't implement the given command, it shall return NULL, and VDR will +automatically issue a proper error message. If it encounters an unknown or invalid +option, it shall set the ReplyCode to one of the codes defined in VDR/svdrp.c +and return a proper error message. +

+The default ReplyCode is 900, and if the plugin doesn't care about reply +codes, it doesn't have to set it to anything else (unless there is an error, of +course). The codes in the range 901..999 are reserved for plugins that want +to use special reply codes. Any plugin can use any of these values and doesn't +have to coordinate this with any other plugin, since the caller knows which +plugin was called, and will therefore process the values according to the +particular plugin's definitions. +

+The returned string may consist of several lines, separated by the newline character +('\n'). Each of these lines will be preceded with the ReplyCode +when presenting them to the caller, and the continuation character ('-') +will be set for all but the last one. +

+The SVDRP functions are called from the separate "SVDRP server handler" thread. +Therefore the plugin needs to take care of proper locking if it accesses any +global data. + +


Locking

+ +
U can't touch this

+ +When accessing global data structures, proper locking is absolutely necessary. +

+There are several macros that allow for easy locking and unlocking. They all +follow the naming convention LOCK_*_READ|WRITE, where '*' is the name +of the global data structure, which can be one of TIMERS, CHANNELS, +RECORDINGS or SCHEDULES. To implicitly avoid deadlocks in case you +need to lock more than one structure, always make sure you use these macros in +this sequence! +

+Using one of these macros sets a lock on the structure, creates a local pointer to the +respective structure and makes sure the lock is released at the end of the current +block: + +

+if (some condition) {
+   LOCK_TIMERS_READ; // creates local const cTimers *Timers
+   for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
+       // do something with Timer
+       }
+   }
+

+ +Note the naming convention: TIMERS -> Timers etc. +

+The LOCK_*_READ macros create pointers that are 'const', while +the LOCK_*_WRITE macros allow modifications to the data structures. +Both wait indefinitely to obtain the lock. However, if LOCK_*_WRITE is +used twice in the same block, the second call will not obtain a lock and +immediately return NULL, which may lead to a crash. In such cases a +warning and backtrace is logged. +

+You may keep pointers to objects in such lists, even after releasing +the lock. However, you may only access such objects if you are +holding a proper lock again. If an object has been deleted from the list +while you did not hold a lock (for instance by an other thread), the +object will still be there, but no longer within this list (it is then +stored in the ListGarbageCollector for a few seconds). That way even if you +access the object after it has been deleted, you won't cause a segfault. +You can call the Contains() function to check whether an object you are +holding a pointer to is still in the list. Note that the garbage collector +is purged when the usual housekeeping is done. +

+See tools.h, class cListBase for more documentation and information on how +to use locking with timeouts, and thread.h, classes cStateLock and +cStateKey on how to easily react to changes in such lists. + +


Loading plugins into VDR

+ +
Saddling up!

+ +Plugins are loaded into VDR using the command line option -P, as in + +

+vdr -Phello
+

+ +If the plugin accepts command line options, they are given as part of the argument +to the -P option, which then has to be enclosed in quotes: + +

+vdr -P"hello -a abc -b"
+

+ +Any number of plugins can be loaded this way, each with its own -P option: + +

+vdr -P"hello -a abc -b" -Pdvd -Pmp3
+

+ +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 -L option: + +

+vdr -L/usr/lib/vdr -Phello
+

+ +There can be any number of -L options, and each of them will apply to the +-P options following it. +

+When started with the -h or -V option (for help +or version information, respectively), VDR will automatically load all plugins +in the default or given directory that match the VDR plugin +naming convention, +and display their help and/or version information in addition to its own output. + +


Building the distribution package

+ +
Let's get this show on the road!

+ +If you want to make your plugin available to other VDR users, you'll need to +make a package that can be easily distributed. +The Makefile that has been created by the call to +newplugin +provides the target dist, which does this for you. +

+Simply change into your source directory and execute make dist: + +

+cd VDR/PLUGINS/src/hello
+make dist
+

+ +After this you should find a file named like + +

+vdr-hello-0.0.1.tgz
+

+ +in your source directory, where hello will be replaced with your actual +plugin's name, and 0.0.1 will be your plugin's current version number. + +


Part II - The Internal Interface

+ +

Status monitor

+ +
A piece of the action

+ +If a plugin wants to get informed on various events in VDR, it can derive a class from +cStatus, as in + +

+#include <vdr/status.h>
+
+class cMyStatusMonitor : public cStatus {
+protected:
+  virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView);
+  };
+
+void cMyStatusMonitor::ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView)
+{
+  if (ChannelNumber)
+     dsyslog("channel switched to %d on DVB %d", ChannelNumber, Device->CardIndex());
+  else
+     dsyslog("about to switch channel on DVB %d", Device->CardIndex());
+}
+

+ +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: + +

+#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;
+}
+

+ +Note that the actual object is created in the Start() function, not in the +constructor! It is also important to delete the object in the destructor, in order to +avoid memory leaks. +

+A Plugin can implement any number of cStatus 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 cStatus +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. +

+See the file status.h for detailed information on which status monitor +member functions are available in cStatus. You only need to implement +the functions you actually want to use. + +


Players

+ +
Play it again, Sam!

+ +Implementing a player is a two step process. +First you need the actual player class, which is derived from the abstract cPlayer: + +

+#include <vdr/player.h>
+
+class cMyPlayer : public cPlayer {
+protected:
+  virtual void Activate(bool On);
+public:
+  cMyPlayer(void);
+  virtual ~cMyPlayer();
+  };
+

+ +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 +cThread and implement the necessary functionality: + +

+#include <vdr/player.h>
+
+class cMyPlayer : public cPlayer, cThread {
+protected:
+  virtual void Activate(bool On);
+  virtual void Action(void);
+public:
+  cMyPlayer(void);
+  virtual ~cMyPlayer();
+  };
+

+ +Take a look at the files player.h and dvbplayer.c to see how VDR implements +its own player for the VDR recordings. +

+To play the actual data, the player needs to call its member function + +

+int PlayPes(const uchar *Data, int Length, bool VideoOnly);
+

+ +where Data points to a block of Length bytes of a PES data +stream containing any combination of video, audio or Dolby tracks. Which audio +or Dolby track will actually be played is controlled by the device the player +is attached to. 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 data stream, and it must be delivered fast enough so that the +DVB device doesn't run out of data. +To avoid busy loops the player should call its member function + +

+bool DevicePoll(cPoller &Poller, int TimeoutMs = 0);
+

+ +to determine whether the device is ready for further data. +

+By default all audio track handling is done by the device a player is +attached to. +If the player can provide more than a single audio track, and has special +requirements in order to set a given track, it can implement the +following function to allow the device to set a specific track: + +

+virtual void SetAudioTrack(eTrackType Type, const tTrackId *TrackId)
+

+ +A player that has special requirements about audio tracks should announce its +available audio tracks by calling + +

+bool DeviceSetAvailableTrack(eTrackType Type, int Index, uint16_t Id, const char *Language = NULL, const char *Description = NULL)
+

+ +See device.h for details about the parameters for track handling. +

+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: + +

+#include <vdr/player.h>
+
+class cMyControl : public cControl {
+private:
+  cMyPlayer *player;
+public:
+  cMyControl(void);
+  virtual ~cMyControl();
+  virtual void Hide(void);
+  virtual cOsdObject *GetInfo(void);
+  virtual eOSState ProcessKey(eKeys Key);
+  };
+

+ +cMyControl shall create an object of type cMyPlayer and +hand over a pointer to it to the cControl base class, so that it +can be later attached to the primary DVB device: + +

+cMyControl::cMyControl(void)
+:cControl(player = new cMyPlayer)
+{
+}
+

+ +cMyControl will receive the user's key presses through the ProcessKey() +function. It will get all button presses, except for the volume control buttons +(kVolUp, kVolDn, kMute), the power button (kPower) +and the menu button (kMenu). If the user has not pressed a button for a while +(which is typically in the area of about one second), ProcessKey() will be called +with kNone, so that the cMyControl 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), ProcessKey() must return osEnd +to make the main program loop shut down the player control. +

+A derived cControl must implement the Hide() function, in which +it has to hide itself from the OSD, in case it uses it. Hide() may be called at +any time, and it may be called even if the cControl is not visible at the moment. +

+The GetInfo() function is called when the user presses the Info button, +and shall return a pointer to a cOsdObject that contains information +about the currently played programme. The caller takes ownership of the returned +pointer and will delete it when it is no longer used. If no information is available, +NULL shall be returned. +

+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 cControl::Launch() with the player control object, as in + +

+cControl::Launch(new cMyControl);
+

+ +Ownership of the MyControl 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). +

+The cPlayer class has a member function + +

+void DeviceStillPicture(const uchar *Data, int Length);
+

+ +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". +

+For detailed information on how to implement your own player, please take a look +at VDR's cDvbPlayer and cDvbPlayerControl classes. +

+User interface +

+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 +

+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... + +

Receivers

+ +
Tapping into the stream...

+ +In order to receive any kind of data from a cDevice, a plugin must set up an +object derived from the cReceiver class: + +

+#include <vdr/receiver.h>
+
+class cMyReceiver : public cReceiver, cThread {
+protected:
+  virtual void Activate(bool On);
+  virtual void Receive(uchar *Data, int Length);
+public:
+  cMyReceiver(int Pid);
+  };
+
+cMyReceiver::cMyReceiver(int Pid)
+:cReceiver(NULL, -1)
+{
+  AddPid(Pid);
+}
+
+cMyReceiver::~cMyReceiver()
+{
+  cReceiver::Detach();
+  ...
+}
+
+void cMyReceiver::Activate(bool On)
+{
+  // start your own thread for processing the received data
+}
+
+void cMyReceiver::Receive(uchar *Data, int Length)
+{
+  // buffer the data for processing in a separate thread
+}
+

+ +See the comments in VDR/receiver.h for details about the various +member functions of cReceiver. +

+The above example sets up a receiver that wants to receive data from only one +PID (for example the Teletext PID). In order to not interfere with other recording +operations, it sets its priority to -1 (any negative value will allow +a cReceiver to be detached from its cDevice at any time +in favor of a timer recording or live viewing). +

+Once a cReceiver has been created, it needs to be attached to +a cDevice: + +

+cMyReceiver *Receiver = new cMyReceiver(123);
+
+cDevice::ActualDevice()->AttachReceiver(Receiver);
+

+ +Note the use of cDevice::ActualDevice() here, which makes sure that +the receiver is attached to the device that actually receives the current live +video stream (this may be different from the primary device in case of Transfer +Mode). +

+The cReceiver must be detached from its device before it is deleted. + +


Filters

+ +
A Fistful of Data

+ +If you want to receive section data you have to implement a derived cFilter +class which at least implements the Process() function and a constructor +that sets the (initial) filter parameters: + +

+#include <vdr/filter.h>
+
+class cMyFilter : public cFilter {
+protected:
+  virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
+public:
+  cMyFilter(void);
+  ...
+  };
+
+cMyFilter::cMyFilter(void)
+{
+  Set(0x14, 0x70);        // TDT
+}
+
+void cMyFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
+{
+  // do something with the data here
+}
+

+ +An instance of such a filter needs to be attached to the device from +which it shall receive data, as in + +

+cMyFilter *Filter = new cMyFilter();
+
+cDevice::ActualDevice()->AttachFilter(Filter);
+

+ +If the cFilter isn't needed any more, it may simply be deleted +and will automatically detach itself from the cDevice. +

+See VDR/eit.c or VDR/pat.c to learn how to process filter data. + +


The On Screen Display

+ +
Window to the world

+ +If a plugin needs to have total control over the OSD, it can call the +static function + +

+#include <vdr/osd.h>
+
+cOsd *MyOsd = cOsdProvider::NewOsd(x, y);
+

+ +where x and y are the coordinates of the upper left corner +of the OSD area on the screen. Such an OSD doesn't display anything +yet, so you need to at least call the function + +

+tArea Area = { 0, 0, 100, 100, 4 };
+MyOsd->SetAreas(&Area, 1);
+

+ +to define an actual OSD drawing area (see VDR/osd.h for the declarations +of these functions, and VDR/skinsttng.c to see how VDR opens the OSD and sets up +its windows and color depths). +

+Theoretically the OSD supports a full screen drawing area, with 32 bit color +depth. However, the actual OSD device in use may not be able to provide the +full area or color depth, maybe because of lack of OSD memory or other restrictions. +A plugin that uses the OSD should therefore test whether the OSD is able to +provide the requested functionality, and should offer alternate color depths +to allow a less powerful OSD implementation to still work reasonably. +Since it is often not really necessary to have hundreds or thousands of colors +all over the OSD area, a plugin can divide the total drawing area into several +sub-areas with different color depths and separate color palettes, as in + +

+tArea Area = { 0, 0, 99, 99, 4 };
+if (osd->CanHandleAreas(Area, 1) == oeOk)
+   osd->SetAreas(&Area, 1);
+else {
+   tArea Areas[] = { { 0,  0, 99, 19, 2 },
+                     { 0, 20, 99, 79, 2 },
+                     { 0, 80, 99, 99, 4 }
+                   };
+   osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
+   }
+

+ +In this example an OSD with 100 by 100 pixel and 4 bit color depth shall +be opened, so at first a single area with the full required resolution +is set up and CanHandleAreas() is called with it. If the result indicates +that the OSD will be able to handle this drawing area, a call to SetAreas() +actually sets it. If a single area with that resolution can't be handled, +a second attempt is made in which the total drawing area is divided into +three horizontal stripes, two of which use only 2 bit color depth (because +the objects drawn in there can be displayed with 4 colors) while the third +one still requests 4 bit color depth. +

+Note that a plugin should always at first request a single drawing area +with the full required resolution. Only if this fails shall it use alternate +areas. Drawing areas are always rectangular and may not overlap (but do not need +to be adjacent). +

+Special consideration may have to be given to color usage if the OSD provides +8bpp (256 colors). In that case, fonts may be drawn using anti-aliasing, +which requires several blended color values between the foreground and background +color. In order to not use up the whole color palette for a single color +combination (and thus be unable to draw any other colors at all), it may be +useful to call + +

+osd->SetAntiAliasGranularity();
+

+ +which allows the system to evenly distribute the palette entries to the various +color combinations (see VDR/osd.h for details). +

+Directly accessing the OSD is only allowed from the foreground thread, which +restricts this to a cOsdObject returned from the plugin's MainMenuAction() +function, or any of the skin classes a plugin might implement. +

+If a plugin runs a separate thread and wants to issue a message directly from +within that thread, it can call + +

+int cSkins::QueueMessage(eMessageType Type, const char *s, int Seconds = 0, int Timeout = 0);
+

+ +to queue that message for display. See VDR/skins.h for details. + +


Skins

+ +
The emperor's new clothes

+ +The way VDR displays its menus to the user is implemented through skins. +A particular skin provides several functions that return objects to be used +for displaying a specific part of the OSD, like a menu, the channel display +or the volume bar. +

+By default VDR offers the Classic and the ST:TNG Panels skins, +which can be selected through Setup/OSD/Skin. A plugin can implement an +arbitrary skin of its own by doing something similar to what's done in +VDR/skinclassic.c. +

+The first step in implementing a new skin is to derive a class from cSkin +that provides the handling objects necessary to do the actual work: + +

+#include <vdr/skins.h>
+
+class cMySkin : public cSkin {
+public:
+  cMySkin(void);
+  virtual const char *Description(void);
+  virtual cSkinDisplayChannel *DisplayChannel(bool WithInfo);
+  virtual cSkinDisplayMenu *DisplayMenu(void);
+  virtual cSkinDisplayReplay *DisplayReplay(bool ModeOnly);
+  virtual cSkinDisplayVolume *DisplayVolume(void);
+  virtual cSkinDisplayTracks *DisplayTracks(const char *Title, int NumTracks, const char * const *Tracks);
+  virtual cSkinDisplayMessage *DisplayMessage(void);
+  };
+

+ +See the comments in VDR/skins.h for details. VDR/skinclassic.[hc] +can be used as an example for how to implement all the necessary classes and +functions to compose a complete skin. See also the chapter about themes +if you want to make the colors used by your skin configurable. +

+To add your new skin to the list of skins available to the user in Setup/OSD/Skin, +all you need to do is create a new object of your skin class, as in + +

+new cMySkin;
+

+ +in the Start() function of your plugin. +Do not delete this object, it will be automatically deleted when the program ends. +

+In order to be able to easily identify plugins that implement a skin it is recommended +that the name of such a plugin should be + +

+skinxyz
+

+ +where xyz is the actual name of the skin. + +


Themes

+ +
Eye of the beholder...

+ +A theme is a collection of colors that can be used by a skin. +Since every skin most likely has its own idea about what parts of it can be +themed, and different skins may have completely different numbers of +"themeable" parts, a particular theme can only be used with the skin it was designed +for. A particular skin, however, can have any number of themes. Which theme +will be actually used can be defined in Setup/OSD/Theme. +

+In order to make a skin "themeable" is shall create an object of type cTheme, as in + +

+static cTheme Theme;
+

+ +The next step is to define the colors that shall be provided by this theme, as in + +

+THEME_CLR(Theme, clrTitle,        0xFFBC8024);
+THEME_CLR(Theme, clrButtonRedFg,  clrWhite);
+THEME_CLR(Theme, clrButtonRedBg,  clrRed);
+

+ +THEME_CLR() is a helper macro that adds the given color name +and its default color value to the theme. +

+Any color names can be used, but they should always start with clr... and +if a given color has a foreground and a background value, the two names shall be +distinguished by appending ...Fg and ...Bg, respectively. +

+Color values can be either 32 bit hexadecimal numbers in the form 0xAARRGGBB +(where the individual bytes represent Alpha (transparency), Red, Green +and Blue component, respectively), or one of the predefined color names from +VDR/osd.h. +

+In the actual drawing code of a skin, the color names defined with the THEME_CLR() +macros can be used to fetch the actual color values from the theme, as in + +

+osd->DrawText(x, y, s, Theme.Color(clrButtonRedFg), Theme.Color(clrButtonRedBg), font);
+

+ +By default this will use the colors that have been defined in the respective +THEME_CLR() line, but may be overwritten through user supplied theme +files (see man vdr(5) for information about the format of a theme file). + +


Devices

+ +
Expanding the possibilities

+ +By default VDR is based on using DVB PCI cards that are supported by the +LinuxDVB driver. However, a plugin can implement additional devices that +can be used as sources of MPEG data for viewing or recording, and also +as output devices for replaying. Such a device can be a physical card +that is installed in the PC (like, for instance, an MPEG encoder card that +allows the analog signal of a proprietary set-top box to be integrated +into a VDR system; or an analog TV receiver card, which does the MPEG encoding +"on the fly" - assuming your machine is fast enough), or just a software program that takes an MPEG data +stream and displays it, for instance, on an existing graphics adapter. +

+To implement an additional device, a plugin must derive a class from cDevice: + +

+#include <vdr/device.h>
+
+class cMyDevice : public cDevice {
+  ...
+  };
+

+ +The derived class must implement several virtual functions, according to +the abilities this new class of devices can provide. See the comments in the +file VDR/device.h for more information on the various functions, +and also VDR/dvbdevice.[hc] for details on the implementation of +the cDvbDevice, which is used to access the DVB PCI cards. +

+Channel selection +

+If the new device can receive, it most likely needs to provide a way of +selecting which channel it shall tune to: + +

+virtual int NumProvidedSystems(void) const;
+virtual bool ProvidesSource(int Source) const;
+virtual bool ProvidesTransponder(const cChannel *Channel) const;
+virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL) const;
+virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
+

+ +These functions will be called with the desired source or channel and shall return whether +this device can provide the requested source or channel and whether tuning to it was successful, +respectively. +

+Audio selection +

+If the device can provide more than a single audio track, it can implement the +following function to make them available: + +

+virtual void SetAudioTrackDevice(eTrackType Type);
+virtual int GetAudioChannelDevice(void);
+virtual void SetAudioChannelDevice(int AudioChannel);
+

+ +

+Recording +

+A device that can be used for recording must implement the functions + +

+virtual bool SetPid(cPidHandle *Handle, int Type, bool On);
+virtual bool OpenDvr(void);
+virtual void CloseDvr(void);
+virtual bool GetTSPacket(uchar *&Data);
+

+ +which allow VDR to set the PIDs that shall be recorded, set up the device for +recording (and shut it down again), and receive the MPEG data stream. The data +must be delivered in the form of a Transport Stream (TS), which consists of +packets that are all 188 bytes in size. Each call to GetTSPacket() +must deliver exactly one such packet (if one is currently available). +

+Replaying +

+The functions to implement replaying capabilities are + +

+virtual bool HasDecoder(void) const;
+virtual bool CanReplay(void) const;
+virtual bool SetPlayMode(ePlayMode PlayMode);
+virtual int64_t GetSTC(void);
+virtual bool IsPlayingVideo(void) const;
+virtual bool HasIBPTrickSpeed(void);
+virtual void TrickSpeed(int Speed, bool Forward);
+virtual void Clear(void);
+virtual void Play(void);
+virtual void Freeze(void);
+virtual void Mute(void);
+virtual void StillPicture(const uchar *Data, int Length);
+virtual bool Poll(cPoller &Poller, int TimeoutMs = 0);
+virtual int PlayVideo(const uchar *Data, int Length);
+

+ +In addition, the following functions may be implemented to provide further +functionality: + +

+virtual bool GrabImage(const char *FileName, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1);
+virtual void SetVideoFormat(bool VideoFormat16_9);
+virtual void SetVolumeDevice(int Volume);
+

+ +

+Section Filtering +

+If your device provides section filtering capabilities it can implement +the functions + +

+virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask);
+virtual int ReadFilter(int Handle, void *Buffer, size_t Length);
+virtual void CloseFilter(int Handle);
+

+ +which must open and close a file handle that delivers section data for the given +filter parameters. +

+In order to actually start section handling, the +device also needs to call the function + +

+StartSectionHandler();
+

+ +from its constructor. +

+See Filters on how to set up actual filters that can +handle section data. + +

+On Screen Display +

+If your device provides On Screen Display (OSD) capabilities (which every device +that is supposed to be used as a primary device should do), it shall implement +an "OSD provider" class, derived from cOsdProvider, which, when its CreateOsd() +function is called, returns an object derived from cOsd, which can be used to +access the device's OSD: + +

+class cMyOsdProvider : public cOsdProvider {
+public:
+  cMyOsdProvider(void);
+  virtual cOsd *CreateOsd(int Left, int Top);
+  };
+

+ +In its MakePrimaryDevice() function the device shall create an object +of this class, as in + +

+void cMyDevice::MakePrimaryDevice(bool On)
+{
+  new cMyOsdProvider;
+}
+

+ +The OSD provider object is allocated on the heap and shall not be deleted +(it will be deleted automatically in case a different device sets up an OSD +provider, or when the program ends). +

+Note that an OSD implementation need not be physically linked to the device +in any way. All it needs to make sure is that the OSD will be visible to the +user - whether this goes through OSD facilities of the physical device (like +a "full featured" DVB card) or through a graphics adapter that overlays its +output with the video signal, doesn't matter. +

+In order to be able to determine the proper size of the OSD, the device +should implement the function + +

+virtual void GetOsdSize(int &Width, int &Height, double &Aspect);
+

+ +By default, an OSD size of 720x480 with an aspect ratio of 1.0 is assumed. + +

+Initializing new devices +

+A derived cDevice class shall implement a static function +in which it determines whether the necessary hardware to run this sort of +device is actually present in this machine (or whatever other prerequisites +might be important), and then creates as many device objects as necessary. +See VDR/dvbdevice.c for the implementation of the cDvbDevice +initialize function. +

+A plugin that adds devices to a VDR instance shall call this +function from its Initialize() function +to make sure other plugins that may need to have access to all available devices +will see them in their Start() function. +

+Nothing needs to be done to shut down the devices. VDR will automatically +shut down (delete) all devices when the program terminates. It is therefore +important that the devices are created on the heap, using the new +operator! +

+Device hooks +

+VDR has builtin facilities that select which device is able to provide a given +transponder, or, which device may provide EIT data. However, there may be +situations where the setup is so special that it requires considerations that +exceed the scope of the core VDR code. +This is where device hooks can be used. + +

+class cMyDeviceHook : public cDeviceHook {
+public:
+  cMyDeviceHook(void);
+  virtual bool DeviceProvidesTransponder(const cDevice *Device, const cChannel *Channel) const;
+  virtual bool DeviceProvidesEIT(const cDevice *Device) const;
+  };
+

+ +In its DeviceProvidesTransponder() function the device hook can take +whatever actions are necessary to determine whether the given Device can +provide the given Channel's transponder, as in + +

+bool cMyDeviceHook::DeviceProvidesTransponder(const cDevice *Device, const cChannel *Channel) const
+{
+  if (condition where Device can't provide Channel)
+     return false;
+  return true;
+}
+

+ +In its DeviceProvidesEIT() function the device hook can take +whatever actions are necessary to determine whether the given Device can +provide EIT data, as in + +

+bool cMyDeviceHook::DeviceProvidesEIT(const cDevice *Device) const
+{
+  if (condition where Device can't provide EIT data)
+     return false;
+  return true;
+}
+

+ +A plugin that creates a derived cDeviceHook shall do so in its Initialize() +function, as in + +

+new cMyDeviceHook;
+

+ +and shall not delete this object. It will be automatically deleted when the program ends. + +


Positioners

+ +
Now you see me - now you don't!

+ +If you are using a positioner (also known as "motor" or "rotor") to move your +satellite dish to receive various satellites, you will be using the 'P' command +in the diseqc.conf file. This command sends the necessary data to the +positioner to move the dish to the satellite's orbital position. By default VDR +uses its builtin DiSEqC positioner control. If your positioner requires a different +method of controlling (like maybe via a serial link), you can derive a class +from cPositioner, as in + +

+#include <vdr/positioner.h>
+
+class cMyPositioner : public cPositioner {
+public:
+  cMyPositioner(void);
+  virtual void Drive(ePositionerDirection Direction);
+  virtual void Step(ePositionerDirection Direction, uint Steps = 1);
+  virtual void Halt(void);
+  virtual void SetLimit(ePositionerDirection Direction);
+  virtual void DisableLimits(void);
+  virtual void EnableLimits(void);
+  virtual void StorePosition(uint Number);
+  virtual void RecalcPositions(uint Number);
+  virtual void GotoPosition(uint Number, int Longitude);
+  virtual void GotoAngle(int Longitude);
+  };
+

+ +See the implementation of cDiseqcPositioner in diseqc.c for details. +

+You should create your derived positioner object in the +Start() function of your plugin. +Note that the object has to be created on the heap (using new), +and you shall not delete it at any point (it will be deleted automatically +when the program ends). + +


Audio

+ +
"The stereo effect may only be experienced if stereo equipment is used!"

+ +There are many different ways to replay additional audio tracks, like Dolby Digital. +So VDR offers a plugin interface that allows for the implementation of any kind of +audio replay facility. +

+To implement a new audio output facility, simply derive a class from cAudio, +as in + +

+#include <vdr/audio.h>
+#include <vdr/thread.h>
+
+class cMyAudio : public cAudio, private cThread {
+private:
+  virtual void Action(void);
+public:
+  cMyAudio(void);
+  virtual void Play(const uchar *Data, int Length, uchar Id);
+  virtual void Mute(bool On);
+  virtual void Clear(void);
+  };
+

+ +You should create your derived audio object in the +Start() function of your plugin. +Note that the object has to be created on the heap (using new), +and you shall not delete it at any point (it will be deleted automatically +when the program ends). +

+The Play() function will be offered complete audio PES packets +and has to accept each packet immediately. It must return as soon as possible, +in order to not delay the overall replay process. Therefore you may want to +also derive your class from cThread and run the actual audio processing +as a separate thread. Note that the offered data is only valid within the call +to Play(), so if you can't process the entire block immediately, you +will need to copy it for later processing in your thread. +

+The Mute() and Clear() functions will be called whenever the audio shall +be muted, or any buffered data shall be cleared, respectively. + +


Remote Control

+ +
The joy of zapping!

+ +There are several ways to control the operation of VDR. The builtin methods +are using the PC keyboard, a homebuilt RCU unit or the LIRC interface. +Of course there may be many more ways you might think of to implement a +remote control, so a plugin can use the cRemote class to do that. +

+The simplest method for a plugin to issue commands to VDR is to call the +static function cRemote::Put(eKeys Key), as in + +

+cRemote::Put(kUp);
+

+ +In this case the plugin must do the mapping of whatever incoming signal or code +it processes to the eKeys values itself. This makes sense if the incoming +codes are well known and won't ever change. +

+In cases where the incoming codes are not known, or not all available keys may +be supported by the actual remote control in use, you may want to derive your +own remote control class from cRemote, as in + +

+#include <vdr/remote.h>
+#include <vdr/thread.h>
+
+class cMyRemote : public cRemote, private cThread {
+private:
+  virtual void Action(void);
+public:
+  cMyRemote(const char *Name);
+  virtual bool Initialize(void);
+  };
+

+ +Note that deriving from cThread is not required for a remote control +class to work, but typically you may want to have a separate thread running that +collects the input and delivers it to the cRemote base class. +

+You should create your derived remote control object in the +Start() function of your plugin. +Note that the object has to be created on the heap (using new), +and you shall not delete it at any point (it will be deleted automatically +when the program ends). +

+The constructor of your remote control class should look like this + +

+cMyRemote::cMyRemote(const char *Name)
+:cRemote(Name)
+{
+  Start();
+}
+

+ +The Name is important in order for the cRemote base class +to be able to distinguish the codes for the various remote controls. +When creating your cMyRemote object you should use the value returned +by the Name() member function of the plugin class, which returns the +plugin's name. Calling Start() will start the thread that collects +the incoming data (by calling your Action() function). +In case you need to do any other setup steps, like opening a file or initializing +member variables, you should do so before calling Start(). +

+If your remote control for some reason can't work (maybe because it was unable to +open some file handle it requires) it can implement the virtual function + +

+virtual bool Ready(void);
+

+ +and have it return false. In that case VDR will not try to learn keys from +that remote control. +VDR will handle everything necessary to learn the key mappings of your remote +control. In order to do so, it will first call the virtual function Initialize(), +in which you should take all necessary steps to make sure your remote control +can be accessed. This may, for instance, include trying various communications +protocols. Initialize(), if implemented, shall only return after it has +made sure data can be received from the remote control. Before calling this +function, VDR will prompt the user on the OSD to press any key on the remote control. +As soon as your derived cRemote class has detected useful incoming data, +Initialize() should return true. If any fatal error occurs, false +should be returned. +

+If your remote control class needs some setup data that shall be +readily available next time VDR starts (without having to go through the initialization +procedure again) it can use the cRemote member functions + +

+void PutSetup(const char *Setup);
+const char *GetSetup(void);
+

+ +to store and retrieve a character string containing whatever data is needed. +Note that the Initialize() function will only be called if there are +no key mappings known for this remote control. Once the key mappings have been +learned, Initialize() will never be called again. +

+The cRemote class assumes that any incoming remote control code can be +expressed as a character string. So whatever data your remote control provides +needs to be given to the base class by calling + +

+Put(const char *Code, bool Repeat = false, bool Release = false);
+

+ +where Code is the string representation of the remote control's +incoming data. Repeat and Release are boolean flags that +indicate whether this is a repeated keypress, or the key has been released. +Since a common case for remote control data is to be given as a numerical +value, there is another Put() function available for your convenience, +which takes a 64 bit unsigned integer value instead of a character string: + +

+Put(uint64 Code, bool Repeat = false, bool Release = false);
+

+ +The other parameters have the same meaning as in the first version of this function. +

+If your remote control has a repeat function that automatically repeats key events +if a key is held pressed down for a while, your derived class should use the global +parameters Setup.RcRepeatDelay and Setup.RcRepeatDelta to allow +users to configure the behavior of this function. + +


Conditional Access

+ +
Members only!

+ +Pay TV providers usually encrypt their broadcasts, so that only viewers who +have the proper smart card can watch them. Such a smart card needs to be inserted +into a CAM (Conditional Access Module), which in turn goes into a CI (Common +Interface) slot. +

+VDR's mechanisms for supporting Conditional Access are mainly the two classes +cCiAdapter and cCamSlot. A cCiAdapter handles exactly +one CI, and can provide several CAM slots, represented by the appropriate +number of cCamSlot objects. +

+In order to decrypt a particular channel, a cCiAdapter with a cCamSlot +that contains the necessary CAM will be assigned to a cDevice, and exactly +one of its CAM slots will be activated. Whether or not a cCiAdapter can +be assigned to a particular device depends on the hardware implementation. +Some devices (like the Siemens/Technotrend DVB cards) are hardwired with their +CI adapters, so the cCiAdapter for these can only be used with one device. +Other hardware implementations may allow CI adapters and devices to be connected +through some sort of matrix switch. When trying to decrypt an encrypted channel, +VDR will automatically select a useful combination of device and CAM slot. +

+If a plugin implements a derived cCiAdapter, it has to implement +several low level functions that handle the actual data transfer (see dvbci.c +for example). The decision whether the adapter can actually be assigned to different +devices is made in the function + +

+virtual bool Assign(cDevice *Device, bool Query = false);
+

+ +See the description of this function in ci.h for details. + +


Electronic Program Guide

+ +
The grass is always greener on the other side...

+ +In case the Electronic Program Guide (EPG) provided by the broadcaster +isn't sufficient for your taste, you can implement a cEpgHandler to +improve it from external sources. For instance, to replace the description +of the broadcaster's EPG with one from some external database, you could do: + +

+#include <vdr/epg.h>
+
+class cMyEpgHandler : public cEpgHandler {
+public:
+  virtual bool SetDescription(cEvent *Event, const char *Description);
+  };
+
+bool cMyEpgHandler::SetDescription(cEvent *Event, const char *Description)
+{
+  Event->SetDescription(DescriptionFromDatabase(Event));
+  return true;
+}
+

+ +where DescriptionFromDatabase() would derive the description of the + given event from some external source. Note that the function returns true +to signal VDR that no other EPG handlers shall be queried after this one. +

+See VDR/epg.h for details. + +


The video directory

+ +
Bits and pieces...

+ +By default VDR assumes that the video directory consists of one large +volume, on which it can store its recordings. If you want to distribute your +recordings over several physical drives, you can derive from cVideoDirectory, +as in + +

+#include <vdr/videodir.h>
+
+class cMyVideoDirectory : public cVideoDirectory {
+public:
+  cMyVideoDirectory(void);
+  virtual ~cMyVideoDirectory();
+  virtual int FreeMB(int *UsedMB = NULL);
+  virtual bool Register(const char *FileName);
+  virtual bool Rename(const char *OldName, const char *NewName);
+  virtual bool Move(const char *FromName, const char *ToName);
+  virtual bool Remove(const char *Name);
+  virtual void Cleanup(const char *IgnoreFiles[] = NULL);
+  virtual bool Contains(const char *Name);
+  };
+

+ +See the description in videodir.h for details. +

+You should create your derived video directory object in the +Start() function of your plugin. +Note that the object has to be created on the heap (using new), +and you shall not delete it at any point (it will be deleted automatically +when the program ends).