From 641cc4c634d8a417f5387fcd4f11cda257b6c46f Mon Sep 17 00:00:00 2001 From: Manuel Reimer Date: Tue, 19 Sep 2023 10:27:25 +0200 Subject: [PATCH] Updated The VDR Plugin System (markdown) --- The-VDR-Plugin-System.md | 404 +++++++++++++++++++-------------------- 1 file changed, 202 insertions(+), 202 deletions(-) diff --git a/The-VDR-Plugin-System.md b/The-VDR-Plugin-System.md index 876feec..43e9019 100644 --- a/The-VDR-Plugin-System.md +++ b/The-VDR-Plugin-System.md @@ -119,9 +119,9 @@ 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. @@ -134,12 +134,12 @@ 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). @@ -190,9 +190,9 @@ 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
-

+

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 @@ -254,9 +254,9 @@ 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);
-

+

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. @@ -267,14 +267,14 @@ 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 @@ -302,10 +302,10 @@ 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. @@ -333,9 +333,9 @@ 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. @@ -344,14 +344,14 @@ 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 @@ -377,20 +377,20 @@ 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. @@ -403,9 +403,9 @@ 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. @@ -455,22 +455,22 @@ 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 @@ -486,10 +486,10 @@ 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 @@ -522,9 +522,9 @@ 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.

@@ -547,9 +547,9 @@ 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);
-

+

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. @@ -586,22 +586,22 @@ of a program, and they may not be able to read them if they are in an exotic lan 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 @@ -613,9 +613,9 @@ 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:
    @@ -647,9 +647,9 @@ the plugin should launch a separate thread to do this. 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 @@ -675,9 +675,9 @@ 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 @@ -693,21 +693,21 @@ 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 @@ -730,16 +730,16 @@ 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);
    @@ -748,7 +748,7 @@ time_t MyPlugin::WakeupTime(void)
          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 @@ -762,10 +762,10 @@ 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. @@ -777,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.
    @@ -786,7 +786,7 @@ bool cPluginHello::SetupParse(const char *Name, const char *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. @@ -803,9 +803,9 @@ 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 @@ -836,7 +836,7 @@ 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;
     
    @@ -863,7 +863,7 @@ 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 @@ -906,11 +906,11 @@ 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);
    -

    +

    each of which returns a string containing the directory that VDR uses for its own files (defined through the options in the call to VDR), extended by @@ -932,9 +932,9 @@ 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());
    -

    +

    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 @@ -948,9 +948,9 @@ 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

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

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

    +

    The text given here must be the English version, and the returned pointer is either a translated version (if available) or the original string. @@ -975,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"),
    @@ -984,7 +984,7 @@ const char *Texts = {
     
     for (int i = 0; i < 3; i++)
         MyFunc(tr(Texts[i]));
    -

    +

    The system VDR is running on may use a character encoding where a single character @@ -1008,9 +1008,9 @@ 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);
    -

    +

    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 @@ -1025,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;
       };
    @@ -1040,7 +1040,7 @@ bool cPluginHello::Service(const char *Id, void *Data)
          }
       return false;
     }
    -

    +

    Plugins should expect to be called with Data set to NULL and may use this as a 'service supported' check without performing any actions. @@ -1048,13 +1048,13 @@ this as a 'service supported' check without performing any actions. To send messages to, or request services from a specific plugin, one plugin can directly call another plugin's service function: -

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

    +

    To send messages to, or request services from some plugin that offers the protocol, a plugin can call the function cPluginManager::CallFirstService(). This function @@ -1072,15 +1072,15 @@ any plugin handled the request, or false if no plugin handled the reque A plugin can implement its own SVDRP commands through the two functions -

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

    +

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

    +
     const char **cPluginSvdrpdemo::SVDRPHelpPages(void)
     {
       static const char *HelpPages[] = {
    @@ -1094,7 +1094,7 @@ const char **cPluginSvdrpdemo::SVDRPHelpPages(void)
         };
       return HelpPages;
     }
    -

    +

    Note that the first line of each entry contains the actual command and its parameters, while the following lines explain what the command does and what @@ -1111,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) {
    @@ -1133,7 +1133,7 @@ cString cPluginSvdrpdemo::SVDRPCommand(const char *Command, const char *Option,
          }
       return NULL;
     }
    -

    +

    The command is given to this function in the Command parameter, and any optional parameters are given in the Option string. Command always points to an actual, non-empty string, while @@ -1178,14 +1178,14 @@ 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)) {
            // do something with Timer
            }
        }
    -

    +

    Note the naming convention: TIMERS -> Timers etc.

    @@ -1217,30 +1217,30 @@ to use locking with timeouts, and thread.h, classes cStateLock and Plugins are loaded into VDR using the command line option -P, as in -

    +
     vdr -Phello
    -

    +

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

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

    +

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

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

    +

    If you are not starting VDR from the VDR source directory (and thus your plugins cannot be found at their default location) you need to tell VDR the location of the plugins through the -L option: -

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

    +

    There can be any number of -L options, and each of them will apply to the -P options following it. @@ -1263,16 +1263,16 @@ 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. @@ -1286,7 +1286,7 @@ 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 -

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

    +

    An object of this class will be informed whenever the channel is switched on one of the DVB devices. It could be used in a plugin like this: -

    +
     #include <vdr/plugin.h>
     
     class cPluginStatus : public cPlugin {
    @@ -1335,7 +1335,7 @@ bool cPluginStatus::Start(void)
       statusMonitor = new cMyStatusMonitor;
       return true;
     }
    -

    +

    Note that the actual object is created in the Start() function, not in the constructor! It is also important to delete the object in the destructor, in order to @@ -1359,7 +1359,7 @@ 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: -

    +
     #include <vdr/player.h>
     
     class cMyPlayer : public cPlayer {
    @@ -1369,13 +1369,13 @@ public:
       cMyPlayer(void);
       virtual ~cMyPlayer();
       };
    -

    +

    What exactly you do in this class is entirely up to you. If you want to run a separate thread which, e.g., reads data from a file, you can additionally derive your class from cThread and implement the necessary functionality: -

    +
     #include <vdr/player.h>
     
     class cMyPlayer : public cPlayer, cThread {
    @@ -1386,16 +1386,16 @@ public:
       cMyPlayer(void);
       virtual ~cMyPlayer();
       };
    -

    +

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

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

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

    +

    where Data points to a block of Length bytes of a PES data stream containing any combination of video, audio or Dolby tracks. Which audio @@ -1406,9 +1406,9 @@ desired data stream, and it must be delivered fast enough so that the DVB device doesn't run out of data. To avoid busy loops the player should call its member function -

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

    +

    to determine whether the device is ready for further data.

    @@ -1418,23 +1418,23 @@ If the player can provide more than a single audio track, and has special requirements in order to set a given track, it can implement the following function to allow the device to set a specific track: -

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

    +

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

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

    +

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

    The second part needed here is a control object that receives user input from the main program loop and reacts on this by telling the player what to do: -

    +
     #include <vdr/player.h>
     
     class cMyControl : public cControl {
    @@ -1447,18 +1447,18 @@ public:
       virtual cOsdObject *GetInfo(void);
       virtual eOSState ProcessKey(eKeys Key);
       };
    -

    +

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

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

    +

    cMyControl will receive the user's key presses through the ProcessKey() function. It will get all button presses, except for the volume control buttons @@ -1484,9 +1484,9 @@ 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);
    -

    +

    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 @@ -1495,9 +1495,9 @@ use the primary DVB device, or the user decides to start a different replay).

    The cPlayer class has a member function -

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

    +

    which can be called to display a still picture. VDR uses this function when handling its editing marks. A special case of a "player" might use this function to implement @@ -1540,7 +1540,7 @@ ahead - it's your show... 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 {
    @@ -1572,7 +1572,7 @@ void cMyReceiver::Receive(uchar *Data, int Length)
     {
       // buffer the data for processing in a separate thread
     }
    -

    +

    See the comments in VDR/receiver.h for details about the various member functions of cReceiver. @@ -1586,11 +1586,11 @@ in favor of a timer recording or live viewing). Once a cReceiver has been created, it needs to be attached to a cDevice: -

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

    +

    Note the use of cDevice::ActualDevice() here, which makes sure that the receiver is attached to the device that actually receives the current live @@ -1607,7 +1607,7 @@ 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 {
    @@ -1627,16 +1627,16 @@ void cMyFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
     {
       // do something with the data here
     }
    -

    +

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

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

    +

    If the cFilter isn't needed any more, it may simply be deleted and will automatically detach itself from the cDevice. @@ -1650,20 +1650,20 @@ 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 -

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

    +

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

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

    +

    to define an actual OSD drawing area (see VDR/osd.h for the declarations of these functions, and VDR/skinsttng.c to see how VDR opens the OSD and sets up @@ -1679,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);
    @@ -1690,7 +1690,7 @@ else {
                        };
        osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
        }
    -

    +

    In this example an OSD with 100 by 100 pixel and 4 bit color depth shall be opened, so at first a single area with the full required resolution @@ -1714,9 +1714,9 @@ color. In order to not use up the whole color palette for a single color combination (and thus be unable to draw any other colors at all), it may be useful to call -

    +
     osd->SetAntiAliasGranularity();
    -

    +

    which allows the system to evenly distribute the palette entries to the various color combinations (see VDR/osd.h for details). @@ -1728,9 +1728,9 @@ function, or any of the skin classes a plugin might implement. If a plugin runs a separate thread and wants to issue a message directly from within that thread, it can call -

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

    +

    to queue that message for display. See VDR/skins.h for details. @@ -1751,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 {
    @@ -1765,7 +1765,7 @@ public:
       virtual cSkinDisplayTracks *DisplayTracks(const char *Title, int NumTracks, const char * const *Tracks);
       virtual cSkinDisplayMessage *DisplayMessage(void);
       };
    -

    +

    See the comments in VDR/skins.h for details. VDR/skinclassic.[hc] can be used as an example for how to implement all the necessary classes and @@ -1775,9 +1775,9 @@ if you want to make the colors used by your skin configurable. To add your new skin to the list of skins available to the user in Setup/OSD/Skin, all you need to do is create a new object of your skin class, as in -

    +
     new cMySkin;
    -

    +

    in the Start() function of your plugin. Do not delete this object, it will be automatically deleted when the program ends. @@ -1785,9 +1785,9 @@ 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
    -

    +

    where xyz is the actual name of the skin. @@ -1804,17 +1804,17 @@ will be actually used can be defined in Setup/OSD/Theme.

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

    +
     static cTheme Theme;
    -

    +

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

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

    +

    THEME_CLR() is a helper macro that adds the given color name and its default color value to the theme. @@ -1831,9 +1831,9 @@ 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);
    -

    +

    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 @@ -1855,13 +1855,13 @@ stream and displays it, for instance, on an existing graphics adapter.

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

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

    +

    The derived class must implement several virtual functions, according to the abilities this new class of devices can provide. See the comments in the @@ -1874,13 +1874,13 @@ 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;
     virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL) const;
     virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
    -

    +

    These functions will be called with the desired source or channel and shall return whether this device can provide the requested source or channel and whether tuning to it was successful, @@ -1891,23 +1891,23 @@ 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);
    -

    +

    Recording

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

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

    +

    which allow VDR to set the PIDs that shall be recorded, set up the device for recording (and shut it down again), and receive the MPEG data stream. The data @@ -1919,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);
    @@ -1934,16 +1934,16 @@ virtual void Mute(void);
     virtual void StillPicture(const uchar *Data, int Length);
     virtual bool Poll(cPoller &Poller, int TimeoutMs = 0);
     virtual int PlayVideo(const uchar *Data, int Length);
    -

    +

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

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

    +

    Section Filtering @@ -1951,11 +1951,11 @@ 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);
    -

    +

    which must open and close a file handle that delivers section data for the given filter parameters. @@ -1963,9 +1963,9 @@ filter parameters. In order to actually start section handling, the device also needs to call the function -

    +
     StartSectionHandler();
    -

    +

    from its constructor.

    @@ -1981,23 +1981,23 @@ 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);
       virtual cOsd *CreateOsd(int Left, int Top);
       };
    -

    +

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

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

    +

    The OSD provider object is allocated on the heap and shall not be deleted (it will be deleted automatically in case a different device sets up an OSD @@ -2012,9 +2012,9 @@ output with the video signal, doesn't matter. In order to be able to determine the proper size of the OSD, the device should implement the function -

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

    +

    By default, an OSD size of 720x480 with an aspect ratio of 1.0 is assumed. @@ -2046,47 +2046,47 @@ situations where the setup is so special that it requires considerations that exceed the scope of the core VDR code. This is where device hooks can be used. -

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

    +

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

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

    +

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

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

    +

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

    +
     new cMyDeviceHook;
    -

    +

    and shall not delete this object. It will be automatically deleted when the program ends. @@ -2102,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 {
    @@ -2119,7 +2119,7 @@ public:
       virtual void GotoPosition(uint Number, int Longitude);
       virtual void GotoAngle(int Longitude);
       };
    -

    +

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

    @@ -2140,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>
     
    @@ -2153,7 +2153,7 @@ public:
       virtual void Mute(bool On);
       virtual void Clear(void);
       };
    -

    +

    You should create your derived audio object in the Start() function of your plugin. @@ -2184,9 +2184,9 @@ remote control, so a plugin can use the cRemote class to do that. The simplest method for a plugin to issue commands to VDR is to call the static function cRemote::Put(eKeys Key), as in -

    +
     cRemote::Put(kUp);
    -

    +

    In this case the plugin must do the mapping of whatever incoming signal or code it processes to the eKeys values itself. This makes sense if the incoming @@ -2196,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>
     
    @@ -2207,7 +2207,7 @@ public:
       cMyRemote(const char *Name);
       virtual bool Initialize(void);
       };
    -

    +

    Note that deriving from cThread is not required for a remote control class to work, but typically you may want to have a separate thread running that @@ -2221,13 +2221,13 @@ when the program ends).

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

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

    +

    The Name is important in order for the cRemote base class to be able to distinguish the codes for the various remote controls. @@ -2241,9 +2241,9 @@ member variables, you should do so before calling Start(). If your remote control for some reason can't work (maybe because it was unable to open some file handle it requires) it can implement the virtual function -

    +
     virtual bool Ready(void);
    -

    +

    and have it return false. In that case VDR will not try to learn keys from that remote control. @@ -2262,10 +2262,10 @@ If your remote control class needs some setup data that shall be readily available next time VDR starts (without having to go through the initialization procedure again) it can use the cRemote member functions -

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

    +

    to store and retrieve a character string containing whatever data is needed. Note that the Initialize() function will only be called if there are @@ -2276,9 +2276,9 @@ The cRemote class assumes that any incoming remote control code can be expressed as a character string. So whatever data your remote control provides needs to be given to the base class by calling -

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

    +

    where Code is the string representation of the remote control's incoming data. Repeat and Release are boolean flags that @@ -2287,9 +2287,9 @@ Since a common case for remote control data is to be given as a numerical value, there is another Put() function available for your convenience, which takes a 64 bit unsigned integer value instead of a character string: -

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

    +

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

    @@ -2327,9 +2327,9 @@ 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);
    -

    +

    See the description of this function in ci.h for details. @@ -2342,7 +2342,7 @@ 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 {
    @@ -2355,7 +2355,7 @@ bool cMyEpgHandler::SetDescription(cEvent *Event, const char *Description)
       Event->SetDescription(DescriptionFromDatabase(Event));
       return true;
     }
    -

    +

    where DescriptionFromDatabase() would derive the description of the given event from some external source. Note that the function returns true @@ -2372,7 +2372,7 @@ 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 {
    @@ -2387,7 +2387,7 @@ public:
       virtual void Cleanup(const char *IgnoreFiles[] = NULL);
       virtual bool Contains(const char *Name);
       };
    -

    +

    See the description in videodir.h for details.