Copyright © 2009 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.
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:
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.
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
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
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.
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).
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.
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.
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.
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.
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.
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.
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.
If the plugin should print log messages, you can use dsyslog(), isyslog() or esyslog().
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.
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.
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:
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.
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!
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.
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.
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.
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 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); } |
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).
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
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
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).
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
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
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.
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:
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:
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:
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.
A plugin can implement its own SVDRP commands through the two functions
The SVDRPHelpPages() function must return a pointer to a list of help
strings for all of the plugin's SVDRP commands, like this
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:
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.
Plugins are loaded into VDR using the command line option -P, as in
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:
Any number of plugins can be loaded this way, each with its own -P option:
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:
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.
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:
After this you should find a file named like
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.
If a plugin wants to get informed on various events in VDR, it can derive a class from
cStatus, as in
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:
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.
Implementing a player is a two step process.
First you need the actual player class, which is derived from the abstract cPlayer:
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:
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
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
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:
A player that has special requirements about audio tracks should announce its
available audio tracks by calling
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:
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 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
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
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
In order to receive any kind of data from a cDevice, a plugin must set up an
object derived from the cReceiver class:
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
Once a cReceiver has been created, it needs to be attached to
a cDevice:
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).
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:
An instance of such a filter needs to be attached to the device from
which it shall receive data, as in
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.
If a plugin needs to have total control over the OSD, it can call the
static function
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
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
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
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 tread, it can call
to queue that message for display. See VDR/skins.h for details.
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:
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
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
where xyz is the actual name of the skin.
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
The next step is to define the colors that shall be provided by this theme, as in
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
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).
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:
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:
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:
Recording
A device that can be used for recording must implement the functions
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
In addition, the following functions may be implemented to provide further
functionality:
Section Filtering
If your device provides section filtering capabilities it can implement
the functions
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
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:
In its MakePrimaryDevice() function the device shall create an object
of this class, as in
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.
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. 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.
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
A plugin that creates a derived cDeviceHook shall do so in its Initialize()
function, as in
and shall not delete this object. It will be automatically deleted when the program ends.
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
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.
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
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
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
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
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
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
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:
The other parameters have the same meaning as in the first version of this function.
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
See the description of this function in ci.h for details.
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:
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.
const char *MyConfigDir = ConfigDirectory(Name());
const char *MyConfigDir = cPlugin::ConfigDirectory();
Internationalization
const char *s = tr("Hello world!");
const char *Texts = {
trNOOP("First text"),
trNOOP("Second text"),
trNOOP("Third one")
};
for (int i = 0; i < 3; i++)
MyFunc(tr(Texts[i]));
Custom services
virtual bool Service(const char *Id, void *Data = NULL);
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;
}
Hello_SetGreetingTime_v1_0 hellodata;
hellodata.NewGreetingTime = 3;
cPlugin *Plugin = cPluginManager::GetPlugin("hello");
if (Plugin)
Plugin->Service("Hello-SetGreetingTime-v1.0", >hellodata);
SVDRP commands
virtual const char **SVDRPHelpPages(void);
virtual cString SVDRPCommand(const char *Cmd, const char *Option, int &ReplyCode);
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;
}
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;
}
Loading plugins into VDR
vdr -Phello
vdr -P"hello -a abc -b"
vdr -P"hello -a abc -b" -Pdvd -Pmp3
vdr -L/usr/lib/vdr -Phello
Building the distribution package
cd VDR/PLUGINS/src/hello
make dist
vdr-hello-0.0.1.tgz
Part II - The Internal Interface
Status monitor
#include <vdr/status.h>
class cMyStatusMonitor : public cStatus {
protected:
virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber
#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;
}
Players
#include <vdr/player.h>
class cMyPlayer : public cPlayer {
protected:
virtual void Activate(bool On);
public:
cMyPlayer(void);
virtual ~cMyPlayer();
};
#include <vdr/player.h>
class cMyPlayer : public cPlayer, cThread {
protected:
virtual void Activate(bool On);
virtual void Action(void);
public:
cMyPlayer(void);
virtual ~cMyPlayer();
};
int PlayPes(const uchar *Data, int Length, bool VideoOnly);
bool DevicePoll(cPoller &Poller, int TimeoutMs = 0);
virtual void SetAudioTrack(eTrackType Type, const tTrackId *TrackId)
bool DeviceSetAvailableTrack(eTrackType Type, int Index, uint16_t Id, const char *Language = NULL, const char *Description = NULL)
#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::cMyControl(void)
:cControl(player = new cMyPlayer)
{
}
cControl::Launch(new cMyControl);
void DeviceStillPicture(const uchar *Data, int Length);
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
#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 *Receiver = new cMyReceiver(123);
cDevice::ActualDevice()->AttachReceiver(Receiver);
Filters
#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
}
cMyFilter *Filter = new cMyFilter();
cDevice::ActualDevice()->AttachFilter(Filter);
The On Screen Display
#include <vdr/osd.h>
cOsd *MyOsd = cOsdProvider::NewOsd(x, y);
tArea Area = { 0, 0, 100, 100, 4 };
MyOsd->SetAreas(&Area, 1);
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));
}
osd->SetAntiAliasGranularity();
int cSkins::QueueMessage(eMessageType Type, const char *s, int Seconds = 0, int Timeout = 0);
Skins
#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);
};
new cMySkin;
skinxyz
Themes
static cTheme Theme;
THEME_CLR(Theme, clrTitle, 0xFFBC8024);
THEME_CLR(Theme, clrButtonRedFg, clrWhite);
THEME_CLR(Theme, clrButtonRedBg, clrRed);
osd->DrawText(x, y, s, Theme.Color(clrButtonRedFg), Theme.Color(clrButtonRedBg), font);
Devices
#include <vdr/device.h>
class cMyDevice : public cDevice {
...
};
virtual void SetAudioTrackDevice(eTrackType Type);
virtual int GetAudioChannelDevice(void);
virtual void SetAudioChannelDevice(int AudioChannel);
virtual bool SetPid(cPidHandle *Handle, int Type, bool On);
virtual bool OpenDvr(void);
virtual void CloseDvr(void);
virtual bool GetTSPacket(uchar *&Data);
virtual bool HasDecoder(void) const;
virtual bool CanReplay(void) const;
virtual bool SetPlayMode(ePlayMode PlayMode);
virtual int64_t GetSTC(void);
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);
virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask);
StartSectionHandler();
class cMyOsdProvider : public cOsdProvider {
public:
cMyOsdProvider(void);
virtual cOsd *CreateOsd(int Left, int Top);
};
void cMyDevice::MakePrimaryDevice(bool On)
{
new cMyOsdProvider;
}
virtual void GetOsdSize(int &Width, int &Height, double &Aspect);
class cMyDeviceHook : public cDeviceHook {
public:
cMyDeviceHook(void);
virtual bool DeviceProvidesTransponder(const cDevice *Device, const cChannel *Channel) const;
};
bool cMyDeviceHook::DeviceProvidesTransponder(const cDevice *Device, const cChannel *Channel) const
{
if (condition where Device can't provide Channel)
return false;
return true;
}
new cMyDeviceHook;
Audio
#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);
};
Remote Control
cRemote::Put(kUp);
#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);
};
cMyRemote::cMyRemote(const char *Name)
:cRemote(Name)
{
Start();
}
virtual bool Ready(void);
void PutSetup(const char *Setup);
const char *GetSetup(void);
Put(const char *Code, bool Repeat = false, bool Release = false);
Put(uint64 Code, bool Repeat = false, bool Release = false);
Conditional Access
virtual bool Assign(cDevice *Device, bool Query = false);
Electronic Program Guide
#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;
}