From 3ef7a5cce4ff8fb94ce6385dce3e3426f95969ba Mon Sep 17 00:00:00 2001 From: Manuel Reimer Date: Tue, 19 Sep 2023 10:13:32 +0200 Subject: [PATCH] Updated The VDR Plugin System (markdown) --- The-VDR-Plugin-System.md | 300 ++++++++++++++++++++------------------- 1 file changed, 152 insertions(+), 148 deletions(-) diff --git a/The-VDR-Plugin-System.md b/The-VDR-Plugin-System.md index d7b53b6..4834be8 100644 --- a/The-VDR-Plugin-System.md +++ b/The-VDR-Plugin-System.md @@ -1,4 +1,6 @@ -
+ + +

The VDR Plugin System

Version 2.6 @@ -76,11 +78,11 @@ structures and allows it to hook itself into specific areas to perform special a -

Part I - The External Interface

+

Part I - The External Interface


Quick start

-
Can't wait, can't wait!

+

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 ;-) @@ -102,7 +104,7 @@ this document and eventually write your own VDR plugin.


The name of the plugin

-
Give me some I.D.!

+

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 @@ -117,7 +119,7 @@ No other characters should be used here.

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

+

 const char *Name(void);
 

@@ -126,13 +128,13 @@ next chapter.


The plugin directory structure

-
Where is everybody?

+

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
@@ -188,7 +190,7 @@ To use the plugins and clean-plugins targets from the VDR 
 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
 

@@ -210,7 +212,7 @@ additional libraries foo and bar, the names would be


Initializing a new plugin directory

-
A room with a view

+

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 @@ -229,7 +231,7 @@ necessary. Don't forget to adapt the Makefile appropriately.


The actual implementation

-
Use the source, Luke!

+

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 @@ -252,7 +254,7 @@ If your plugin shall not be accessible through VDR's main menu, simply remove

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

+

 VDRPLUGINCREATOR(cPluginHello);
 

@@ -265,7 +267,7 @@ 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
 
@@ -296,11 +298,11 @@ and two replacing the dot).
 
 

Construction and Destruction

-
What goes up, must come down...

+

What goes up, must come down...

The constructor and destructor of a plugin are defined as -

+

 cPlugin(void);
 virtual ~cPlugin();
 

@@ -325,13 +327,13 @@ initialized (and deleted), you don't need to implement either of these functions


Version number

-
Which incarnation is this?

+

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;
 

@@ -342,7 +344,7 @@ 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)
@@ -371,17 +373,17 @@ would be acceptable.
 
 

Description

-
What is it that you do?

+

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)
@@ -395,13 +397,13 @@ to be internationalized.
 
 

Command line arguments

-
Taking orders

+

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[]);
 

@@ -418,7 +420,7 @@ these arguments. As with any normal C program, the strings pointed to by arg 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.
@@ -449,11 +451,11 @@ to exit.
 
 

Command line help

-
Tell me about it...

+

Tell me about it...

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

+

 virtual const char *CommandLineHelp(void);
 

@@ -461,7 +463,7 @@ which will be called if the user enters the -h option when start 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.
@@ -478,13 +480,13 @@ be shorter than 80 characters.
 
 

Getting started

-
Let's get ready to rumble!

+

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

@@ -516,11 +518,11 @@ texts, it doesn't need to implement either of these functions.


Shutting down

-
Stop it, right there!

+

Stop it, right there!

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

+

 virtual void Stop(void);
 

@@ -533,7 +535,7 @@ as the Start() functions were called.


Logging

-
Traces in the sand...

+

Traces in the sand...

If the plugin should print log messages, you can use dsyslog(), isyslog() or esyslog().
@@ -545,7 +547,7 @@ If the plugin should print log messages, you can use dsyslog(), isy 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);
 

@@ -579,12 +581,12 @@ of a program, and they may not be able to read them if they are in an exotic lan


Main menu entry

-
Today's special is...

+

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

@@ -592,7 +594,7 @@ 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)
@@ -607,11 +609,11 @@ in the call to VDR.
 
 

User interaction

-
It's showtime!

+

It's showtime!

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

+

 virtual cOsdObject *MainMenuAction(void);
 

@@ -640,12 +642,12 @@ the plugin should launch a separate thread to do this.


Housekeeping

-
Chores, chores...

+

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

@@ -665,7 +667,7 @@ the plugin should launch a separate thread to do this.


Main thread hook

-
Pushing in...

+

Pushing in...

Normally a plugin only reacts on user input if directly called through its main menu entry, or performs some background @@ -673,7 +675,7 @@ 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);
 

@@ -686,19 +688,19 @@ interface performance will become sluggish!


Activity

-
Now is not a good time!

+

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)
@@ -722,13 +724,13 @@ further plugins will be queried and no shutdown will be done.
 
 

Wakeup

-
Wake me up before you go-go

+

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

@@ -737,7 +739,7 @@ is planned. VDR will pass the most recent wakeup time of all plugins, or the nex 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);
@@ -755,12 +757,12 @@ the future, the time will be ignored.
 
 

Setup parameters

-
Remember me...

+

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

@@ -775,7 +777,7 @@ 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.
@@ -801,7 +803,7 @@ 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);
 

@@ -828,13 +830,13 @@ parameters to the user.


The Setup menu

-
Have it your way!

+

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;
 
@@ -852,7 +854,7 @@ cMenuSetupHello::cMenuSetupHello(void)
 {
   newGreetingTime = GreetingTime;
   newUseAlternateGreeting = UseAlternateGreeting;
-  Add(new cMenuEdittItem( tr("Greeting time (s)"),      &newGreetingTime));
+  Add(new cMenuEditIntItem( tr("Greeting time (s)"),      &newGreetingTime));
   Add(new cMenuEditBoolItem(tr("Use alternate greeting"), &newUseAlternateGreeting));
 }
 
@@ -886,7 +888,7 @@ your setup parameters and use that one to copy all parameters with one single st
 
 

Additional files

-
I want my own stuff!

+

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 @@ -904,7 +906,7 @@ original values that are stored elsewhere.

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);
@@ -930,7 +932,7 @@ these in a subdirectory of its own, named after the plugin. To easily get such a
 the functions can be given an additional string that will be appended to the returned
 directory name, as in
 
-

+

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

@@ -946,19 +948,19 @@ The ConfigDirectory(), CacheDirectory() and ResourceDirect 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!

+

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!");
 

@@ -973,7 +975,7 @@ 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"),
@@ -998,7 +1000,7 @@ handling individual characters these functions may come in handy.
 
 

Custom services

-
What can I do for you?

+

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 @@ -1006,7 +1008,7 @@ that some other plugin offers, or it may want to inform other plugins about impo things it does. To receive requests or messages, a plugin can implement the following function: -

+

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

@@ -1023,7 +1025,7 @@ 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;
   };
@@ -1046,7 +1048,7 @@ 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");
@@ -1066,11 +1068,11 @@ any plugin handled the request, or false if no plugin handled the reque
 
 

SVDRP commands

-
Infinite Diversity in Infinite Combinations

+

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

@@ -1078,7 +1080,7 @@ virtual cString SVDRPCommand(const char *Cmd, const char *Option, int &Reply 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[] = {
@@ -1109,7 +1111,7 @@ The actual processing of SVDRP commands for a plugin is done in its
 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) {
@@ -1161,7 +1163,7 @@ global data.
 
 

Locking

-
U can't touch this

+

U can't touch this

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

@@ -1176,7 +1178,7 @@ Using one of these macros sets a lock on the structure, creates a local pointer 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)) {
@@ -1211,24 +1213,24 @@ to use locking with timeouts, and thread.h, classes cStateLock and
 
 

Loading plugins into VDR

-
Saddling up!

+

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
 

@@ -1236,7 +1238,7 @@ 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
 

@@ -1251,7 +1253,7 @@ 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!

+

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. @@ -1261,30 +1263,30 @@ 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

+

Part II - The Internal Interface


Status monitor

-
A piece of the action

+

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 {
@@ -1304,7 +1306,7 @@ void cMyStatusMonitor::ChannelSwitch(const cDevice *Device, int ChannelNumber, b
 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 {
@@ -1352,12 +1354,12 @@ the functions you actually want to use.
 
 

Players

-
Play it again, Sam!

+

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 {
@@ -1373,7 +1375,7 @@ What exactly you do in this class is entirely up to you. If you want to run a se
 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 {
@@ -1391,7 +1393,7 @@ 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);
 

@@ -1404,7 +1406,7 @@ 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);
 

@@ -1416,14 +1418,14 @@ 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)
 

@@ -1432,7 +1434,7 @@ 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 {
@@ -1451,7 +1453,7 @@ public:
 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)
 {
@@ -1482,7 +1484,7 @@ Finally, to get things going, a plugin that implements a player (and the surroun
 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);
 

@@ -1493,7 +1495,7 @@ 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);
 

@@ -1533,12 +1535,12 @@ ahead - it's your show...


Receivers

-
Tapping into the stream...

+

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 {
@@ -1584,7 +1586,7 @@ 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);
@@ -1599,13 +1601,13 @@ The cReceiver must be detached from its device before it is deleted.
 
 

Filters

-
A Fistful of Data

+

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 {
@@ -1630,7 +1632,7 @@ void cMyFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
 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);
@@ -1643,12 +1645,12 @@ See VDR/eit.c or VDR/pat.c to learn how to process filter data.
 
 

The On Screen Display

-
Window to the world

+

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);
@@ -1658,7 +1660,7 @@ 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);
 

@@ -1677,7 +1679,7 @@ 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);
@@ -1712,7 +1714,7 @@ 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();
 

@@ -1726,7 +1728,7 @@ 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);
 

@@ -1734,7 +1736,7 @@ to queue that message for display. See VDR/skins.h for details.


Skins

-
The emperor's new clothes

+

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 @@ -1749,7 +1751,7 @@ arbitrary skin of its own by doing something similar to what's done in 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 {
@@ -1773,7 +1775,7 @@ 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;
 

@@ -1783,7 +1785,7 @@ Do not delete this object, it will be automatically deleted when the program end 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
 

@@ -1791,7 +1793,7 @@ where xyz is the actual name of the skin.


Themes

-
Eye of the beholder...

+

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 @@ -1802,13 +1804,13 @@ 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);
@@ -1829,7 +1831,7 @@ and Blue component, respectively), or one of the predefined color names from
 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);
 

@@ -1839,7 +1841,7 @@ files (see man vdr(5) for information about the format of a theme file)


Devices

-
Expanding the possibilities

+

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 @@ -1853,7 +1855,7 @@ 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 {
@@ -1872,7 +1874,7 @@ the cDvbDevice, which is used to access the DVB PCI cards.
 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;
@@ -1889,7 +1891,7 @@ respectively.
 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);
@@ -1900,7 +1902,7 @@ virtual void SetAudioChannelDevice(int AudioChannel);
 

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);
@@ -1917,7 +1919,7 @@ must deliver exactly one such packet (if one is currently available).
 

The functions to implement replaying capabilities are -

+

 virtual bool HasDecoder(void) const;
 virtual bool CanReplay(void) const;
 virtual bool SetPlayMode(ePlayMode PlayMode);
@@ -1937,7 +1939,7 @@ 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);
@@ -1949,7 +1951,7 @@ virtual void SetVolumeDevice(int Volume);
 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);
@@ -1961,7 +1963,7 @@ filter parameters.
 In order to actually start section handling, the
 device also needs to call the function
 
-

+

 StartSectionHandler();
 

@@ -1979,7 +1981,7 @@ an "OSD provider" class, derived from cOsdProvider, which, when its cOsd, which can be used to access the device's OSD: -

+

 class cMyOsdProvider : public cOsdProvider {
 public:
   cMyOsdProvider(void);
@@ -1990,7 +1992,7 @@ public:
 In its MakePrimaryDevice() function the device shall create an object
 of this class, as in
 
-

+

 void cMyDevice::MakePrimaryDevice(bool On)
 {
   new cMyOsdProvider;
@@ -2010,7 +2012,7 @@ 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);
 

@@ -2044,7 +2046,7 @@ 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);
@@ -2057,7 +2059,7 @@ 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)
@@ -2070,7 +2072,7 @@ 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)
@@ -2082,7 +2084,7 @@ bool cMyDeviceHook::DeviceProvidesEIT(const cDevice *Device) const
 A plugin that creates a derived cDeviceHook shall do so in its Initialize()
 function, as in
 
-

+

 new cMyDeviceHook;
 

@@ -2090,7 +2092,7 @@ and shall not delete this object. It will be automatically deleted when the prog


Positioners

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

+

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 @@ -2100,7 +2102,7 @@ uses its builtin DiSEqC positioner control. If your positioner requires a differ 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 {
@@ -2129,7 +2131,7 @@ when the program ends).
 
 

Audio

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

+

"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 @@ -2138,7 +2140,7 @@ 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>
 
@@ -2172,7 +2174,7 @@ be muted, or any buffered data shall be cleared, respectively.
 
 

Remote Control

-
The joy of zapping!

+

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. @@ -2182,7 +2184,7 @@ 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);
 

@@ -2194,7 +2196,7 @@ 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>
 
@@ -2219,7 +2221,7 @@ when the program ends).
 

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

+

 cMyRemote::cMyRemote(const char *Name)
 :cRemote(Name)
 {
@@ -2239,7 +2241,7 @@ 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);
 

@@ -2260,7 +2262,7 @@ 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);
 

@@ -2274,7 +2276,7 @@ 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);
 

@@ -2285,7 +2287,7 @@ 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);
 

@@ -2298,7 +2300,7 @@ users to configure the behavior of this function.


Conditional Access

-
Members only!

+

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 @@ -2325,7 +2327,7 @@ several low level functions that handle the actual data transfer (see dvbci. 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);
 

@@ -2333,14 +2335,14 @@ See the description of this function in ci.h for details.


Electronic Program Guide

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

+

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 {
@@ -2363,14 +2365,14 @@ See VDR/epg.h for details.
 
 

The video directory

-
Bits and pieces...

+

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 {
@@ -2394,3 +2396,5 @@ You should create your derived video directory object in the
 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).
+
+