mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
- Fixed handling second audio and Dolby Digital PIDs for encrypted channels (was broken in version 1.3.37). - Improved TS/PES conversion to better handle lost TS packets (thanks to Reinhard Nissl). - Limited the frequency of log messages from the cRepackers. - Now using the gettid() syscall to get a thread's pid, so that we get a useful value on NPTL systems (suggested by Johannes Stezenbach). - Fixed the RCU remote control handling to avoid problems with NPTL (thanks to Andreas Share for reporting a lockup with the RCU on NPTL systems). - When displaying the amount of free disk space, the space consumed by recordings that have been "deleted" but not yet actually "removed" is now taken into account (suggested by Christian Vogt). - Now avoiding unnecessary disk access when checking if there are deleted recordings that need to be removed (reported by Carsten Koch). - Fixed handling the DELETEDLIFETIME when removing deleted recordings. Now a deleted recording is retained at least DELETEDLIFETIME seconds before actually removing it. The value of DELETEDLIFETIME has been changed to 300. So after (possibly inadvertently) deleting a recording, there will be at least 5 minutes in which it can be recovered (unless a new recording immediately requires the disk space). The count starts again at 0 every time VDR is started. - Fixed a possible crash when displaying the "Low disk space!" message from a background thread (thanks to Christof Steininger). - Fixed handling OSD areas that have invalid sizes (thanks to Marco Schlüßler). - Added a mutex to AssertFreeDiskSpace() to make sure calls from foreground and background threads won't interfere. - The main menu now dynamically updates its contents in case an instant recording or replay stops, etc. - The version number of EPG events is now also stored in the epg.data file (thanks to Kendy Kutzner). - EPG events that are no longer in the currently broadcasted data stream are now automatically deleted. - Removed an invalid access to Event->schedule in cSchedule::DelEvent(). - Modified cSchedule::Cleanup() (events are always sorted by time). - Schedules are now cleaned up once every hour (not only at 05:00). - The "Schedule" and "What's on now/next?" menus are now updated if a timer is set or modified. - cTimer no longer has its own 'schedule' member, it rather uses that of the event it has been set to. - The "Red" button in the "Schedule", "What's on now/next?" and "Event" menus now immediately creates a timer for the selected event and marks it with 'T'. If the event is already marked with 'T', the "Red" button opens the "Edit timer" menu for that timer. - Removing deleted recordings is now done in a separate thread. - Dropped the unused "stop recording on primary interface" stuff. - Converting a grabbed image to JPEG is now done with the new function RgbToJpeg() (see tools.h). - The SVDRP command GRAB now determines the image type (JPEG or PNM) from the extension (".jpg", ".jpeg" or ".pnm") of the given file name. The explicit 'jpeg' or 'pnm' parameter is still accepted for backward compatibility, but has no meaning any more. - The function cDevice::GrabImage() no longer writes the grabbed image to a file, but rather returns a pointer to the image in memory. The wrapper function cDevice::GrabImageFile() can be used to write the grabbed image directly to a file. Plugins that used the old version of cDevice::GrabImage() need to be adapted to the new interface. - The new class cBase64Encoder (see tools.h) can be used to encode data in base64 (thanks to Bob Withers for publishing his Base64 class). - The SVDRP command GRAB now writes the image data to the SVDRP connection (encoded in base64) if the given file name consists of only the file extension (".jpg", ".jpeg" or ".pnm"), or if only "-" is given as file name (based on a suggestion from Darren Salt). A simple way of viewing a grabbed image on a remote host is: svdrpsend.pl -d <hostname> 'grab -' | sed -n -e 's/^216-//p' -e '1ibegin-base64 644 -' -e '$a====' | uudecode | display - - The new command line option '-g' must be given if the SVDRP command GRAB shall be allowed to write image files to disk. The parameter to this option must be the full path name of an existing directory, without any "..", double '/' or symlinks. By default, or if "-g- is given, grabbing to files is not allowed any more because of potential security risks. - Modified the way the SVDRP command GRAB writes the grabbed image to a file to avoid a security hole (CAN-2005-0071, reported by Javier Fernández-Sanguino Peña): + The file handle is now opened in a way that it won't follow symbolic links (suggested by Darren Salt). + The given file name is now canonicalized, so that it won't contain any ".." or symlinks (suggested by Darren Salt). + Grabbing to files is limited to the directory given in the the command line option '-g'. By default grabbing to files is not allowed any more. - Updated the Greek OSD texts (thanks to Dimitrios Dimitrakos). - Changed all "illegal" to "invalid" in error messages (there's nothing "illegal" in VDR ;-). - When started as user 'root' VDR now switches to a lesser privileged user id, keeping the capability to set the system time (based on a patch from Ludwig Nussel). By default the user id 'vdr' is used, which can be changed through the new command line option '-u'. Note that for security reasons VDR will no longer run as user 'root' (unless you explicitly start it with '-u root', but this is not recommended!). The 'runvdr' script has been changed to use the '-u' option. - Changed the API of the functions cStatus::Recording() and cStatus::Replaying(), so that they can provide the full file name of the recording. Plugins that use these (or the related cStatus::Msg...() functions) need to be adapted (suggested by Andreas Brugger). - The DVB devices now retune (and, if applicable, resend the DiSEqC data) if the lock is lost (based on a patch from Reinhard Nissl). - Fixed handling multi byte key sequences in cKbdRemote (based on a patch from Jürgen Schneider). - Removed unused variables in skinclassic.c and skinsttng.c (thanks to Marco Schlüßler). - Made the static cControl functions thread safe (thanks to Patrick Fischer). - Fixed initializing pthread_mutexattr_t and pthread_rwlockattr_t to avoid warnings with g++ 4.1.0 (thanks to Ville Skyttä). - Fixed incrementing the 'state' variables in the repacker classes in remux.c to avoid warnings with g++ 4.1.0 (reported by Ville Skyttä). - The Makefile now reports a summary of failed plugins (thanks to Udo Richter). - The cTimer constructor can now take an optional cChannel (suggested by Patrick Fischer). - Fixed setting the main thread id if VDR is running as a daemon. - Fixed handling TS packets in cTS2PES (thanks to Reinhard Nissl). - Added cTimer::SetPriority() to set a timer's priority (suggested by Kendy Kutzner). - Added cMenuEditStrItem::InEditMode() (suggested by Christian Wieninger). - Now using FE_READ_STATUS to read the current frontend status (suggested by Holger Wächtler). - The "Menu" key now behaves consistently. If there is anything on the OSD, it is closed when the "Menu" key is pressed, and if there is nothing on the OSD, the "Menu" key opens the main menu (suggested by Luca Olivetti). - The new option "Setup/OSD/Timeout requested channel info" can be used to turn off the automatic timeout of the channel display in case it was invoked by a press of the "Ok" key (suggested by Thiemo Gehrke). - A message is now given when an instant recording is started (suggested by Helmut Auer). Actually the code was already there, just commented out - don't remember why it wasn't active... - Removed an obsolete "Summary" text from i18n.c and preceded all key definition texts with "Key$" to avoid duplicates (reported by Lucian Muresan). - Preceded all button texts with "Button$". - Removed obsolete "Eject", "Language" and "scanning recordings..." texts. - Added missing #include "thread.h" to dvbspu.c (reported by Gavin Hamill). - Disabled the use of "fadvise" in cUnbufferedFile because there have been several reports that it causes more problems than it solves (suggested by Petri Hintukainen). If you want to use "fadvise", you can activate the line //#define USE_FADVISE in tools.c. - Removed unused 'offset' member from cOsdItem. - In the "Channels" menu the numeric keys now position the cursor to the channel with the given number (see MANUAL, section "Remote Control Keys", note (3) for details). - The "Mark/Move" function in the "Channels" menu now also works in the non-numeric sort modes. - The default cOsdObject::Show() now automatically calls cOsdMenu::Display() if this is a menu. - The new "Info" key brings up information on the currently viewed programme or recording. For a live programme this is the same as "Schedule/Ok", i.e. the description of the current EPG event. For a recording this is the same as shown by the "Info" button in the "Recordings" menu. Plugins that implement players can overwrite their cControl::GetInfo() function to show their own info (see PLUGINS.html for details). Pressing the "Info" key again while the info is displayed will close the OSD. In order to assign this new key to an existing remote control setup, the remote.conf file needs to be deleted and VDR has to be restarted to go through the process of learning the remote control keys. - Any cReceivers still attached to a cDevice when that device switches to a different transponder are now automatically detached (suggested by Patrick Fischer). - The "flags" of a timer are now handled as an unsigned integer value. In order to do this, the interface of cMenuEditBitItem also had to be changed. - In string entry fields (like, e.g., the file name of a recording) the characters can now be entered by pressing the numeric keys, the same way as on a telephone keypad (based on the "Easy Input" patch from Marcel Schaeben). - Fixed the "Day" field of the "Edit timer" menu when pressing '0' to switch from "single shot" to "weekly", followed by the "Right" key (reported by Andreas Böttger). - The file 'ca.conf' is obsolete and has been removed. - Revised all descriptions regarding CICAM. - Adapted c(Dvb)Device::ProvidesCa() to the dynamic CA handling. - Added a mutex to synchronize cDevice::PlayPesPacket() and SetCurrentAudioTrack() (thanks to Reinhard Nissl). - Added a SleepMs() in cRecorder::Action() to avoid a busy loop (thanks to Ingo Schneider). - Cleaned up some trailing white space.
1996 lines
82 KiB
HTML
1996 lines
82 KiB
HTML
<html>
|
|
<head>
|
|
<title>The VDR Plugin System</title>
|
|
</head>
|
|
<body bgcolor="white">
|
|
|
|
<center><h1>The VDR Plugin System</h1></center>
|
|
|
|
<center><b>Version 1.3</b></center>
|
|
<p>
|
|
<center>
|
|
Copyright © 2006 Klaus Schmidinger<br>
|
|
<a href="mailto:kls@cadsoft.de">kls@cadsoft.de</a><br>
|
|
<a href="http://www.cadsoft.de/vdr">www.cadsoft.de/vdr</a>
|
|
</center>
|
|
<p>
|
|
<!--X1.3.30--><table width=100%><tr><td bgcolor=#0000AA> </td><td width=100%>
|
|
Important modifications introduced in version 1.3.30 are marked like this.
|
|
<!--X1.3.30--></td></tr></table>
|
|
<!--X1.3.31--><table width=100%><tr><td bgcolor=#00AA00> </td><td width=100%>
|
|
Important modifications introduced in version 1.3.31 are marked like this.
|
|
<!--X1.3.31--></td></tr></table>
|
|
<!--X1.3.37--><table width=100%><tr><td bgcolor=#AA0000> </td><td width=100%>
|
|
Important modifications introduced in version 1.3.37 are marked like this.
|
|
<!--X1.3.37--></td></tr></table>
|
|
<!--X1.3.38--><table width=100%><tr><td bgcolor=#FF0000> </td><td width=100%>
|
|
Important modifications introduced in version 1.3.38 are marked like this.
|
|
<!--X1.3.38--></td></tr></table>
|
|
<p>
|
|
VDR provides an easy to use plugin interface that allows additional functionality
|
|
to be added to the program by implementing a dynamically loadable library file.
|
|
This interface allows programmers to develop additional functionality for VDR completely
|
|
separate from the core VDR source, without the need of patching the original
|
|
VDR code (and all the problems of correlating various patches).
|
|
<p>
|
|
This document is divided into two parts, the first one describing the
|
|
<a href="#Part I - The External Interface"><i>external</i> interface</a>
|
|
of the plugin system, and the second one describing the
|
|
<a href="#Part II - The Internal Interface"><i>internal</i> interface</a>.
|
|
The <i>external</i> interface handles everything necessary for a plugin to get hooked into the core
|
|
VDR program and present itself to the user.
|
|
The <i>internal</i> interface provides the plugin code access to VDR's internal data
|
|
structures and allows it to hook itself into specific areas to perform special actions.
|
|
|
|
<hr>
|
|
<h1>Table Of Contents</h1>
|
|
<ul>
|
|
<li><a href="#Part I - The External Interface">Part I - The External Interface</a>
|
|
<ul>
|
|
<li><a href="#Quick start">Quick start</a>
|
|
<li><a href="#The name of the plugin">The name of the plugin</a>
|
|
<li><a href="#The plugin directory structure">The plugin directory structure</a>
|
|
<li><a href="#Initializing a new plugin directory">Initializing a new plugin directory</a>
|
|
<li><a href="#The actual implementation">The actual implementation</a>
|
|
<li><a href="#Construction and Destruction">Construction and Destruction</a>
|
|
<li><a href="#Version number">Version number</a>
|
|
<li><a href="#Description">Description</a>
|
|
<li><a href="#Command line arguments">Command line arguments</a>
|
|
<li><a href="#Command line help">Command line help</a>
|
|
<li><a href="#Getting started">Getting started</a>
|
|
<li><a href="#Shutting down">Shutting down</a>
|
|
<li><a href="#Main menu entry">Main menu entry</a>
|
|
<li><a href="#User interaction">User interaction</a>
|
|
<li><a href="#Housekeeping">Housekeeping</a>
|
|
<li><a href="#Setup parameters">Setup parameters</a>
|
|
<li><a href="#The Setup menu">The Setup menu</a>
|
|
<li><a href="#Configuration files">Configuration files</a>
|
|
<li><a href="#Internationalization">Internationalization</a>
|
|
<!--X1.3.30--><table width=100%><tr><td bgcolor=#0000AA> </td><td width=100%>
|
|
<li><a href="#Custom services">Custom services</a>
|
|
<!--X1.3.30--></td></tr></table>
|
|
<!--X1.3.31--><table width=100%><tr><td bgcolor=#00AA00> </td><td width=100%>
|
|
<li><a href="#SVDRP commands">SVDRP commands</a>
|
|
<!--X1.3.31--></td></tr></table>
|
|
<li><a href="#Loading plugins into VDR">Loading plugins into VDR</a>
|
|
<li><a href="#Building the distribution package">Building the distribution package</a>
|
|
</ul>
|
|
<li><a href="#Part II - The Internal Interface">Part II - The Internal Interface</a>
|
|
<ul>
|
|
<li><a href="#Status monitor">Status monitor</a>
|
|
<li><a href="#Players">Players</a>
|
|
<li><a href="#Receivers">Receivers</a>
|
|
<li><a href="#Filters">Filters</a>
|
|
<li><a href="#The On Screen Display">The On Screen Display</a>
|
|
<li><a href="#Skins">Skins</a>
|
|
<li><a href="#Themes">Themes</a>
|
|
<li><a href="#Devices">Devices</a>
|
|
<li><a href="#Audio">Audio</a>
|
|
<li><a href="#Remote Control">Remote Control</a>
|
|
</ul>
|
|
</ul>
|
|
|
|
<a name="Part I - The External Interface"><hr><center><h1>Part I - The External Interface</h1></center>
|
|
|
|
<a name="Quick start"><hr><h2>Quick start</h2>
|
|
|
|
<center><i><b>Can't wait, can't wait!</b></i></center><p>
|
|
|
|
Actually you should read this entire document before starting to work with VDR plugins,
|
|
but you probably want to see something happening right away <tt>;-)</tt>
|
|
<p>
|
|
So, for a quick demonstration of the plugin system, there is a sample plugin called
|
|
"hello" that comes with the VDR source. To test drive this one, do the following:
|
|
<ul>
|
|
<li>change into the VDR source directory
|
|
<li><b><tt>make</tt></b> the VDR program with your usual <tt>REMOTE=...</tt> (and maybe other) options
|
|
<li>do <b><tt>make plugins</tt></b> to build the plugin
|
|
<li>run VDR with <b><tt>vdr -V</tt></b> to see the version information
|
|
<li>run VDR with <b><tt>vdr -h</tt></b> to see the command line options
|
|
<li>run VDR with <b><tt>vdr -Phello</tt></b>
|
|
<li>open VDR's main menu and select the <i>Hello</i> item
|
|
<li>open the <i>Setup</i> menu from VDR's main menu and select <i>Plugins</i>
|
|
</ul>
|
|
If you enjoyed this brief glimpse into VDR plugin handling, read through the rest of
|
|
this document and eventually write your own VDR plugin.
|
|
|
|
<a name="The name of the plugin"><hr><h2>The name of the plugin</h2>
|
|
|
|
<center><i><b>Give me some I.D.!</b></i></center><p>
|
|
|
|
One of the first things to consider when writing a VDR plugin is giving the thing
|
|
a proper name. This name will be used in the VDR command line in order to load
|
|
the plugin, and will also be the name of the plugin's source directory, as well
|
|
as part of the final library name.
|
|
<p>
|
|
The plugin's name should typically be as short as possible. Three letter
|
|
abbreviations like <b><tt>dvd</tt></b> (for a DVD player) or <b><tt>mp3</tt></b>
|
|
(for an MP3 player) would be good choices. It is also recommended that the name
|
|
consists of only lowercase letters and digits.
|
|
No other characters should be used here.
|
|
<p>
|
|
A plugin can access its name through the (non virtual) member function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
const char *Name(void);
|
|
</pre></td></tr></table><p>
|
|
|
|
The actual name is derived from the plugin's library file name, as defined in the
|
|
next chapter.
|
|
|
|
<a name="The plugin directory structure"><hr><h2>The plugin directory structure</h2>
|
|
|
|
<center><i><b>Where is everybody?</b></i></center><p>
|
|
|
|
By default plugins are located in a directory named <tt>PLUGINS</tt> below the
|
|
VDR source directory. Inside this directory the following subdirectory structure
|
|
is used:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
VDR/PLUGINS/src
|
|
VDR/PLUGINS/src/hello
|
|
VDR/PLUGINS/lib
|
|
VDR/PLUGINS/lib/libvdr-hello.so.1.1.0
|
|
</pre></td></tr></table><p>
|
|
|
|
The <tt>src</tt> directory contains one subdirectory for each plugin, which carries
|
|
the name of that plugin (in the above example that would be <tt>hello</tt>).
|
|
What's inside the individual source directory of a
|
|
plugin is entirely up to the author of that plugin. The only prerequisites are
|
|
that there is a <tt>Makefile</tt> that provides the targets <tt>all</tt> and
|
|
<tt>clean</tt>, and that a call to <tt>make all</tt> actually produces a dynamically
|
|
loadable library file for that plugin (we'll get to the details later).
|
|
<p>
|
|
The <tt>lib</tt> directory contains the dynamically loadable libraries of all
|
|
available plugins. Note that the names of these files are created by concatenating
|
|
<p>
|
|
<table border=2>
|
|
<tr><td align=center><b><tt>libvdr-</tt></b></td><td align=center><b><tt>hello</tt></b></td><td align=center><b><tt>.so.</tt></b></td><td align=center><b><tt>1.1.0</tt></b></td></tr>
|
|
<tr><td align=center><font size=-1>VDR plugin<br>library prefix</font></td><td align=center><font size=-1>name of<br>the plugin</font></td><td align=center><font size=-1>shared object<br>indicator</font></td><td align=center><font size=-1>VDR version number<br>this plugin was<br>compiled for</font></td></tr>
|
|
</table>
|
|
<p>
|
|
The plugin library files can be stored in any directory. If the default organization
|
|
is not used, the path to the plugin directory has be be given to VDR through the
|
|
<b><tt>-L</tt></b> option.
|
|
<p>
|
|
The VDR <tt>Makefile</tt> contains the target <tt>plugins</tt>, which calls
|
|
<tt>make all</tt> in every directory found under <tt>VDR/PLUGINS/src</tt>,
|
|
plus the target <tt>plugins-clean</tt>, which calls <tt>make clean</tt> in
|
|
each of these directories.
|
|
<p>
|
|
If you download a plugin <a href="#Building the distribution package">package</a>
|
|
from the web, it will typically have a name like
|
|
<p>
|
|
<tt>vdr-hello-0.0.1.tgz</tt>
|
|
<p>
|
|
and will unpack into a directory named
|
|
<p>
|
|
<tt>hello-0.0.1</tt>
|
|
<p>
|
|
To use the <tt>plugins</tt> and <tt>plugins-clean</tt> targets from the VDR <tt>Makefile</tt>
|
|
you need to unpack such an archive into the <tt>VDR/PLUGINS/src</tt> directory and
|
|
create a symbolic link with the basic plugin name, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
ln -s hello-0.0.1 hello
|
|
</pre></td></tr></table><p>
|
|
|
|
Since the VDR <tt>Makefile</tt> only searches for directories with names consisting
|
|
of only lowercase characters and digits, it will only follow the symbolic links, which
|
|
should lead to the current version of the plugin you want to use. This way you can
|
|
have several different versions of a plugin source (like <tt>hello-0.0.1</tt> and
|
|
<tt>hello-0.0.2</tt>) and define which one to actually use through the symbolic link.
|
|
|
|
<a name="Initializing a new plugin directory"><hr><h2>Initializing a new plugin directory</h2>
|
|
|
|
<center><i><b>A room with a view</b></i></center><p>
|
|
|
|
Call the Perl script <tt>newplugin</tt> from the VDR source directory to create
|
|
a new plugin directory with a <tt>Makefile</tt> and a main source file implementing
|
|
the basic derived plugin class.
|
|
You will also find a <tt>README</tt> file there with some inital text, where you
|
|
should fill in actual information about your project.
|
|
A <tt>HISTORY</tt> file is set up with an "Initial revision" entry. As your project
|
|
evolves, you should add the changes here with date and version number.
|
|
<p>
|
|
<tt>newplugin</tt> also creates a copy of the GPL license file <tt>COPYING</tt>,
|
|
assuming that you will release your work under that license. Change this if you
|
|
have other plans.
|
|
<p>
|
|
Add further files and maybe subdirectories to your plugin source directory as
|
|
necessary. Don't forget to adapt the <tt>Makefile</tt> appropriately.
|
|
|
|
<a name="The actual implementation"><hr><h2>The actual implementation</h2>
|
|
|
|
<center><i><b>Use the source, Luke!</b></i></center><p>
|
|
|
|
A newly initialized plugin doesn't really do very much yet.
|
|
If you <a href="#Loading plugins into VDR">load it into VDR</a> you will find a new
|
|
entry in the main menu, with the same name as your plugin (where the first character
|
|
has been converted to uppercase). There will also be a new entry named "Plugins" in
|
|
the "Setup" menu, which will bring up a list of all loaded plugins, through which you
|
|
can access each plugin's own setup parameters (if it provides any).
|
|
<p>
|
|
To implement actual functionality into your plugin you need to edit the source file
|
|
that was generated as <tt>PLUGINS/src/name.c</tt>. Read the comments in that file
|
|
to see where you can bring in your own code. The following sections of this document
|
|
will walk you through the individual member functions of the plugin class.
|
|
<p>
|
|
Depending on what your plugin shall do, you may or may not need all of the given
|
|
member functions. Except for the <tt>MainMenuEntry()</tt> function they all by default
|
|
return values that will result in no actual functionality. You can either completely
|
|
delete unused functions from your source file, or just leave them as they are.
|
|
If your plugin shall not be accessible through VDR's main menu, simply remove
|
|
(or comment out) the line implementing the <tt>MainMenuEntry()</tt> function.
|
|
<p>
|
|
At the end of the plugin's source file you will find a line that looks like this:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
VDRPLUGINCREATOR(cPluginHello);
|
|
</pre></td></tr></table><p>
|
|
|
|
This is the "magic" hook that allows VDR to actually load the plugin into
|
|
its memory. You don't need to worry about the details behind all this.
|
|
<p>
|
|
If your plugin requires additional source files, simply add them to your plugin's
|
|
source directory and adjust the <tt>Makefile</tt> accordingly.
|
|
<p>
|
|
Header files usually contain preprocessor statements that prevent the same
|
|
file (or rather its contents, to be precise) from being included more than once, like
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#ifndef __I18N_H
|
|
#define __I18N_H
|
|
|
|
...
|
|
|
|
#endif //__I18N_H
|
|
</pre></td></tr></table><p>
|
|
|
|
The example shown here is the way VDR does this in its core source files.
|
|
It takes the header file's name, converts it to all uppercase, replaces the
|
|
dot with an underline and 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).
|
|
<p>
|
|
As long as you make shure that none of your plugin's header files will be named
|
|
like one of VDR's header files, you can use the same method as VDR. However,
|
|
if you want to name a header file like one that is already existing in VDR's
|
|
source (<tt>i18n.h</tt> would be a possible candidate for this), you may want
|
|
to make sure that the macros used here don't clash. How you do this is completely
|
|
up to you. You could, for instance, prepend the macro with a <tt>'P'</tt>, as in
|
|
<tt>P__I18N_H</tt>, or leave out the trailing <tt>_H</tt>, as in <tt>__I18N</tt>,
|
|
or use a completely different way to make sure a header file is included only once.
|
|
<p>
|
|
The 'hello' example that comes with VDR makes use of <a href="#Internationalization">internationalization</a>
|
|
and implements a file named <tt>i18n.h</tt>. To make sure it won't clash with VDR's
|
|
<tt>i18n.h</tt> it uses the macro <tt>_I18N__H</tt> (one underline at the beginning
|
|
and two replacing the dot).
|
|
|
|
<a name="Construction and Destruction"><hr><h2>Construction and Destruction</h2>
|
|
|
|
<center><i><b>What goes up, must come down...</b></i></center><p>
|
|
|
|
The constructor and destructor of a plugin are defined as
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
cPlugin(void);
|
|
virtual ~cPlugin();
|
|
</pre></td></tr></table><p>
|
|
|
|
The <b>constructor</b> shall initialize any member variables the plugin defines, but
|
|
<b>must not access any global structures of VDR</b>.
|
|
It also must not create any threads or other large data structures. These things
|
|
are done in the
|
|
<a href="#Getting started"><tt>Initialize()</tt></a> or
|
|
<a href="#Getting started"><tt>Start()</tt></a>
|
|
function later.
|
|
Constructing a plugin object shall not have any side effects or produce any output,
|
|
since VDR, for instance, has to create the plugin objects in order to get their
|
|
command line help - and after that immediately destroys them again.
|
|
<p>
|
|
The <b>destructor</b> has to clean up any data created by the plugin.
|
|
Any threads the plugin may have created shall be stopped in the
|
|
<a href="#Shutting down"><tt>Stop()</tt></a> function.
|
|
<p>
|
|
Of course, if your plugin doesn't define any member variables that need to be
|
|
initialized (and deleted), you don't need to implement either of these functions.
|
|
|
|
<a name="Version number"><hr><h2>Version number</h2>
|
|
|
|
<center><i><b>Which incarnation is this?</b></i></center><p>
|
|
|
|
Every plugin must have a version number of its own, which does not necessarily
|
|
have to be in any way related to the VDR version number.
|
|
VDR requests a plugin's version number through a call to the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual const char *Version(void) = 0;
|
|
</pre></td></tr></table><p>
|
|
|
|
Since this is a "pure" virtual function, any derived plugin class <b>must</b>
|
|
implement it. The returned string should identify this version of the plugin.
|
|
Typically this would be something like "0.0.1", but it may also contain other
|
|
information, like for instance "0.0.1pre2" or the like. The string should only
|
|
be as long as really necessary, and shall not contain the plugin's name itself.
|
|
Here's an example:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
static const char *VERSION = "0.0.1";
|
|
|
|
const char *cPluginHello::Version(void)
|
|
{
|
|
return VERSION;
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
Note that the definition of the version number is expected to be located in the
|
|
main source file, and must be written as
|
|
<pre>
|
|
static const char *VERSION = ...
|
|
</pre>
|
|
just like shown in the above example. This is a convention that allows the <tt>Makefile</tt>
|
|
to extract the version number when generating the file name for the distribution archive.
|
|
<p>
|
|
A new plugin project should start with version number <tt>0.0.1</tt> and should reach
|
|
version <tt>1.0.0</tt> once it is completely operative and well tested. Following the
|
|
Linux kernel version numbering scheme, versions with <i>even</i> release numbers
|
|
(like <tt>1.0.x</tt>, <tt>1.2.x</tt>, <tt>1.4.x</tt>...) should be stable releases,
|
|
while those with <i>odd</i> release numbers (like <tt>1.1.x</tt>, <tt>1.3.x</tt>,
|
|
<tt>1.5.x</tt>...) are usually considered "under development". The three parts of
|
|
a version number are not limited to single digits, so a version number of <tt>1.2.15</tt>
|
|
would be acceptable.
|
|
|
|
<a name="Description"><hr><h2>Description</h2>
|
|
|
|
<center><i><b>What is it that you do?</b></i></center><p>
|
|
|
|
In order to tell the user what exactly a plugin does, it must implement the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual const char *Description(void) = 0;
|
|
</pre></td></tr></table><p>
|
|
|
|
which returns a short, one line description of the plugin's purpose:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
static const char *DESCRIPTION = "A friendly greeting";
|
|
|
|
virtual const char *Description(void)
|
|
{
|
|
return tr(DESCRIPTION);
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
Note the <tt>tr()</tt> around the <tt>DESCRIPTION</tt>, which allows the description
|
|
to be <a href="#Internationalization">internationalized</a>.
|
|
|
|
<a name="Command line arguments"><hr><h2>Command line arguments</h2>
|
|
|
|
<center><i><b>Taking orders</b></i></center><p>
|
|
|
|
A VDR plugin can have command line arguments just like any normal program.
|
|
If a plugin wants to react on command line arguments, it needs to implement
|
|
the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual bool ProcessArgs(int argc, char *argv[]);
|
|
</pre></td></tr></table><p>
|
|
|
|
The parameters <tt>argc</tt> and <tt>argv</tt> have exactly the same meaning
|
|
as in a normal C program's <tt>main()</tt> function.
|
|
<tt>argv[0]</tt> contains the name of the plugin (as given in the <b><tt>-P</tt></b>
|
|
option of the <tt>vdr</tt> call).
|
|
<p>
|
|
Each plugin has its own set of command line options, which are totally independent
|
|
from those of any other plugin or VDR itself.
|
|
<p>
|
|
You can use the <tt>getopt()</tt> or <tt>getopt_long()</tt> function to process
|
|
these arguments. As with any normal C program, the strings pointed to by <tt>argv</tt>
|
|
will survive the entire lifetime of the plugin, so it is safe to store pointers to
|
|
these values inside the plugin. Here's an example:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
bool cPluginHello::ProcessArgs(int argc, char *argv[])
|
|
{
|
|
// Implement command line argument processing here if applicable.
|
|
static struct option long_options[] = {
|
|
{ "aaa", required_argument, NULL, 'a' },
|
|
{ "bbb", no_argument, NULL, 'b' },
|
|
{ NULL }
|
|
};
|
|
|
|
int c;
|
|
while ((c = getopt_long(argc, argv, "a:b", long_options, NULL)) != -1) {
|
|
switch (c) {
|
|
case 'a': option_a = optarg;
|
|
break;
|
|
case 'b': option_b = true;
|
|
break;
|
|
default: return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
The return value must be <i>true</i> if all options have been processed
|
|
correctly, or <i>false</i> in case of an error. The first plugin that returns
|
|
<i>false</i> from a call to its <tt>ProcessArgs()</tt> function will cause VDR
|
|
to exit.
|
|
|
|
<a name="Command line help"><hr><h2>Command line help</h2>
|
|
|
|
<center><i><b>Tell me about it...</b></i></center><p>
|
|
|
|
If a plugin accepts command line options, it should implement the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual const char *CommandLineHelp(void);
|
|
</pre></td></tr></table><p>
|
|
|
|
which will be called if the user enters the <b><tt>-h</tt></b> option when starting VDR.
|
|
The returned string should contain the command line help for this plugin, formatted
|
|
in the same way as done by VDR itself:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
const char *cPluginHello::CommandLineHelp(void)
|
|
{
|
|
// Return a string that describes all known command line options.
|
|
return " -a ABC, --aaa=ABC do something nice with ABC\n"
|
|
" -b, --bbb activate 'plan B'\n";
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
This command line help will be printed directly below VDR's help texts (separated
|
|
by a line indicating the plugin's name, version and description), so if you use the
|
|
same formatting as shown here it will line up nicely.
|
|
Note that all lines should be terminated with a newline character, and should
|
|
be shorter than 80 characters.
|
|
|
|
<a name="Getting started"><hr><h2>Getting started</h2>
|
|
|
|
<center><i><b>Let's get ready to rumble!</b></i></center><p>
|
|
|
|
If a plugin implements a function that runs in the background (presumably in a
|
|
thread of its own), or wants to make use of <a href="#Internationalization">internationalization</a>,
|
|
it needs to implement one of the functions
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual bool Initialize(void);
|
|
virtual bool Start(void);
|
|
</pre></td></tr></table><p>
|
|
|
|
which are called once for each plugin at program startup.
|
|
The difference between these two functions is that <tt>Initialize()</tt> is
|
|
called early at program startup, while <tt>Start()</tt> is called after the primary
|
|
device and user interface has been set up, but before the main program loop is entered.
|
|
Inside the <tt>Start()</tt> function of any plugin it is guaranteed that the <tt>Initialize()</tt>
|
|
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 <tt>Initialize()</tt>, so that other plugins can use them in their
|
|
<tt>Start()</tt> functions.
|
|
<p>
|
|
Inside this function the plugin must set up everything necessary to perform
|
|
its task. This may, for instance, be a thread that collects data from the DVB
|
|
stream, which is later presented to the user via a function that is available
|
|
from the main menu.
|
|
<p>
|
|
A return value of <i>false</i> indicates that something has gone wrong and the
|
|
plugin will not be able to perform its task. In that case, the plugin should
|
|
write a proper error message to the log file. The first plugin that returns
|
|
<i>false</i> from its <tt>Initialize()</tt> or <tt>Start()</tt> function will cause
|
|
VDR to exit.
|
|
<p>
|
|
If the plugin doesn't implement any background functionality or internationalized
|
|
texts, it doesn't need to implement either of these functions.
|
|
|
|
<a name="Shutting down"><hr><h2>Shutting down</h2>
|
|
|
|
<center><i><b>Stop it, right there!</b></i></center><p>
|
|
|
|
If a plugin performs any background tasks, it shall implement the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual void Stop(void);
|
|
</pre></td></tr></table><p>
|
|
|
|
in which it shall stop them.
|
|
<p>
|
|
The <tt>Stop()</tt> function will only be called if a previous call to the
|
|
<a href="#Getting started"><tt>Start()</tt></a> function of that plugin has
|
|
returned <i>true</i>. The <tt>Stop()</tt> functions are called in the reverse order
|
|
as the <a href="#Getting started"><tt>Start()</tt></a> functions were called.
|
|
|
|
<a name="Main menu entry"><hr><h2>Main menu entry</h2>
|
|
|
|
<center><i><b>Today's special is...</b></i></center><p>
|
|
|
|
If the plugin implements a feature that the user shall be able to access
|
|
from VDR's main menu, it needs to implement the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual const char *MainMenuEntry(void);
|
|
</pre></td></tr></table><p>
|
|
|
|
The default implementation returns a <tt>NULL</tt> pointer, which means that
|
|
this plugin will not have an item in the main menu. Here's an example of a
|
|
plugin that will have a main menu item:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
static const char *MAINMENUENTRY = "Hello";
|
|
|
|
const char *cPluginHello::MainMenuEntry(void)
|
|
{
|
|
return tr(MAINMENUENTRY);
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
The menu entries of all plugins will be inserted into VDR's main menu right
|
|
after the <i>Recordings</i> item, in the same sequence as they were given
|
|
in the call to VDR.
|
|
|
|
<a name="User interaction"><hr><h2>User interaction</h2>
|
|
|
|
<center><i><b>It's showtime!</b></i></center><p>
|
|
|
|
If the user selects the main menu entry of a plugin, VDR calls the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual cOsdObject *MainMenuAction(void);
|
|
</pre></td></tr></table><p>
|
|
|
|
which can do one of three things:
|
|
<ul>
|
|
<li>Return a pointer to a <tt>cOsdMenu</tt> object which will be displayed
|
|
as a submenu of the main menu (just like the <i>Recordings</i> menu, for instance).
|
|
That menu can then implement further functionality and, for instance, could
|
|
eventually start a custom player to replay a file other than a VDR recording.
|
|
<li>Return a pointer to a <tt>cOsdObject</tt> object which will be displayed
|
|
instead of the normal menu. The derived <tt>cOsdObject</tt> can open a
|
|
<a href="#The On Screen Display">raw OSD</a> from within its <tt>Show()</tt>
|
|
function (it should not attempt to do so from within its constructor, since
|
|
at that time the OSD is still in use by the main menu).
|
|
See the 'osddemo' example that comes with VDR for a demonstration of how this
|
|
is done.
|
|
<li>Perform a specific action and return <tt>NULL</tt>. In that case the main menu
|
|
will be closed after calling <tt>MainMenuAction()</tt>.
|
|
</ul>
|
|
<b>
|
|
It is very important that a call to <tt>MainMenuAction()</tt> returns as soon
|
|
as possible! As long as the program stays inside this function, no other user
|
|
interaction is possible. If a specific action takes longer than a few seconds,
|
|
the plugin should launch a separate thread to do this.
|
|
</b>
|
|
|
|
<a name="Housekeeping"><hr><h2>Housekeeping</h2>
|
|
|
|
<center><i><b>Chores, chores...</b></i></center><p>
|
|
|
|
From time to time a plugin may want to do some regular tasks, like cleaning
|
|
up some files or other things. In order to do this it can implement the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual void Housekeeping(void);
|
|
</pre></td></tr></table><p>
|
|
|
|
which gets called when VDR is otherwise idle. The intervals between subsequent
|
|
calls to this function are not defined. There may be several hours between two
|
|
calls (if, for instance, there are recordings or replays going on) or they may
|
|
be as close as ten seconds. The only thing that is guaranteed is that there are
|
|
at least ten seconds between two subsequent calls to the <tt>Housekeeping()</tt>
|
|
function of the same plugin.
|
|
<p>
|
|
<b>
|
|
It is very important that a call to <tt>Housekeeping()</tt> returns as soon
|
|
as possible! As long as the program stays inside this function, no other user
|
|
interaction is possible. If a specific action takes longer than a few seconds,
|
|
the plugin should launch a separate thread to do this.
|
|
</b>
|
|
|
|
<a name="Setup parameters"><hr><h2>Setup parameters</h2>
|
|
|
|
<center><i><b>Remember me...</b></i></center><p>
|
|
|
|
If a plugin requires its own setup parameters, it needs to implement the following
|
|
functions to handle these parameters:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual cMenuSetupPage *SetupMenu(void);
|
|
virtual bool SetupParse(const char *Name, const char *Value);
|
|
</pre></td></tr></table><p>
|
|
|
|
The <tt>SetupMenu()</tt> function shall return the plugin's <a href="#The Setup menu"><i>Setup</i> menu</a>
|
|
page, where the user can adjust all the parameters known to this plugin.
|
|
<p>
|
|
<tt>SetupParse()</tt> will be called for each parameter the plugin has
|
|
previously stored in the global setup data (see below). It shall return
|
|
<i>true</i> if the parameter was parsed correctly, <i>false</i> in case of
|
|
an error. If <i>false</i> is returned, an error message will be written to
|
|
the log file (and program execution will continue).
|
|
A possible implementation of <tt>SetupParse()</tt> could look like this:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
bool cPluginHello::SetupParse(const char *Name, const char *Value)
|
|
{
|
|
// Parse your own setup parameters and store their values.
|
|
if (!strcasecmp(Name, "GreetingTime")) GreetingTime = atoi(Value);
|
|
else if (!strcasecmp(Name, "UseAlternateGreeting")) UseAlternateGreeting = atoi(Value);
|
|
else
|
|
return false;
|
|
return true;
|
|
</pre></td></tr></table><p>
|
|
|
|
It is important to make sure that the parameter names are exactly the same as
|
|
used in the <a href="#The Setup menu"><i>Setup</i> menu</a>'s <tt>Store()</tt> function.
|
|
<p>
|
|
The plugin's setup parameters are stored in the same file as VDR's parameters.
|
|
In order to allow each plugin (and VDR itself) to have its own set of parameters,
|
|
the <tt>Name</tt> of each parameter will be preceded with the plugin's
|
|
name, as in
|
|
<p>
|
|
<tt>hello.GreetingTime = 3</tt>
|
|
<p>
|
|
The prefix will be handled by the core VDR setup code, so the individual
|
|
plugins need not worry about this.
|
|
<p>
|
|
To store its values in the global setup, a plugin has to call the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
void SetupStore(const char *Name, <i>type</i> Value);
|
|
</pre></td></tr></table><p>
|
|
|
|
where <tt>Name</tt> is the name of the parameter (<tt>"GreetingTime"</tt> in the above
|
|
example, without the prefix <tt>"hello."</tt>) and <tt>Value</tt> is a simple data type (like
|
|
<tt>char *</tt>, <tt>int</tt> etc).
|
|
Note that this is not a function that the individual plugin class needs to implement!
|
|
<tt>SetupStore()</tt> is a non-virtual member function of the <tt>cPlugin</tt> class.
|
|
<p>
|
|
To remove a parameter from the setup data, call <tt>SetupStore()</tt> with the appropriate
|
|
name and without any value, as in
|
|
<p>
|
|
<tt>SetupStore("GreetingTime");</tt>
|
|
<p>
|
|
The VDR menu "Setup/Plugins" will list all loaded plugins with their name,
|
|
version number and description. Selecting an item in this list will bring up
|
|
the plugin's "Setup" menu if that plugin has implemented the <tt>SetupMenu()</tt>
|
|
function.
|
|
<p>
|
|
Finally, a plugin doesn't have to implement the <tt>SetupMenu()</tt> if it only
|
|
needs setup parameters that are not directly user adjustable. It can use
|
|
<tt>SetupStore()</tt> and <tt>SetupParse()</tt> without presenting these
|
|
parameters to the user.
|
|
|
|
<a name="The Setup menu"><hr><h2>The Setup menu</h2>
|
|
|
|
<center><i><b>Have it your way!</b></i></center><p>
|
|
|
|
To implement a <i>Setup</i> menu, a plugin needs to derive a class from
|
|
<tt>cMenuSetupPage</tt> and implement its constructor and the pure virtual
|
|
<tt>Store()</tt> member function:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
int GreetingTime = 3;
|
|
int UseAlternateGreeting = false;
|
|
|
|
class cMenuSetupHello : public cMenuSetupPage {
|
|
private:
|
|
int newGreetingTime;
|
|
int newUseAlternateGreeting;
|
|
protected:
|
|
virtual void Store(void);
|
|
public:
|
|
cMenuSetupHello(void);
|
|
};
|
|
|
|
cMenuSetupHello::cMenuSetupHello(void)
|
|
{
|
|
newGreetingTime = GreetingTime;
|
|
newUseAlternateGreeting = UseAlternateGreeting;
|
|
Add(new cMenuEditIntItem( tr("Greeting time (s)"), &newGreetingTime));
|
|
Add(new cMenuEditBoolItem(tr("Use alternate greeting"), &newUseAlternateGreeting));
|
|
}
|
|
|
|
void cMenuSetupHello::Store(void)
|
|
{
|
|
SetupStore("GreetingTime", GreetingTime = newGreetingTime);
|
|
SetupStore("UseAlternateGreeting", UseAlternateGreeting = newUseAlternateGreeting);
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
In this example we have two global setup parameters (<tt>GreetingTime</tt> and <tt>UseAlternateGreeting</tt>).
|
|
The constructor initializes two private members with the values of these parameters, so
|
|
that the <i>Setup</i> menu can work with temporary copies (in order to discard any changes
|
|
if the user doesn't confirm them by pressing the "Ok" button).
|
|
After this the constructor adds the appropriate menu items, using internationalized texts
|
|
and the addresses of the temporary variables. That's all there is to inizialize a <i>Setup</i>
|
|
menu - the rest will be done by the core VDR code.
|
|
<p>
|
|
Once the user has pressed the "Ok" button to confirm the changes, the <tt>Store()</tt> function will
|
|
be called, in which all setup parameters must be actually stored in VDR's global setup data.
|
|
This is done by calling the <tt>SetupStore()</tt> function for each of the parameters.
|
|
The <i>Name</i> string given here will be used to identify the parameter in VDR's
|
|
<tt>setup.conf</tt> file, and will be automatically prepended with the plugin's name.
|
|
<p>
|
|
Note that in this small example the new values of the parameters are copied into the
|
|
global variables within each <tt>SetupStore()</tt> call. This is not mandatory, however.
|
|
You can first assign the temporary values to the global variables and then do the
|
|
<tt>SetupStore()</tt> calls, or you can define a class or struct that contains all
|
|
your setup parameters and use that one to copy all parameters with one single statement
|
|
(like VDR does with its cSetup class).
|
|
|
|
<a name="Configuration files"><hr><h2>Configuration files</h2>
|
|
|
|
<center><i><b>I want my own stuff!</b></i></center><p>
|
|
|
|
There may be situations where a plugin requires configuration files of its own, maybe
|
|
for data that can't be stored in the simple <a href="#Setup parameters">setup parameters</a>
|
|
of VDR, or maybe because it needs to launch other programs that simply need a separate
|
|
configuration file. While the plugin is free to store such files anywhere it
|
|
sees fit, it might be a good idea to put them in a common place, preferably
|
|
where other configuration data already exists. VDR provides the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
const char *ConfigDirectory(const char *PluginName = NULL);
|
|
</pre></td></tr></table><p>
|
|
|
|
which returns a string containing the directory that VDR uses for its own configuration
|
|
files (defined through the <tt><b>-c</b></tt> option in the call to VDR), extended by
|
|
<tt>"/plugins"</tt>. So assuming the VDR configuration directory is <tt>/video</tt>
|
|
(the default if no <tt><b>-c</b></tt> or <tt><b>-v</b></tt> option is given),
|
|
a call to <tt>ConfigDirectory()</tt> will return <tt>/video/plugins</tt>. The first
|
|
call to <tt>ConfigDirectory()</tt> will automatically make sure that the <tt>plugins</tt>
|
|
subdirectory will exist. If, for some reason, this cannot be achieved, <tt>NULL</tt>
|
|
will be returned.
|
|
<p>
|
|
The additional <tt>plugins</tt> directory is used to keep files from plugins apart
|
|
from those of VDR itself, making sure there will be no name clashes. If a plugin
|
|
needs only one extra configuration file, it is suggested that this file be named
|
|
<tt>name.conf</tt>, where <i>name</i> shall be the name of the plugin.
|
|
<p>
|
|
If a plugin needs more than one such file, it is suggested that the plugin stores
|
|
these in a subdirectory of its own, named after the plugin. To easily get such a name
|
|
the <tt>ConfigDirectory()</tt> function can be given an additional string that will
|
|
be appended to the returned directory name, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
const char *MyConfigDir = ConfigDirectory(Name());
|
|
</pre></td></tr></table><p>
|
|
|
|
where <tt>Name()</tt> is the member function of the plugin class that returns the
|
|
plugin's name. Again, VDR will make sure that the requested directory will exist
|
|
(or return <tt>NULL</tt> in case of an error).
|
|
<p>
|
|
<b>
|
|
The returned string is statically allocated and will be overwritten by subsequent
|
|
calls to ConfigDirectory()!
|
|
</b>
|
|
<p>
|
|
The <tt>ConfigDirectory()</tt> function is a static member function of the <tt>cPlugin</tt>
|
|
class. This allows it to be called even from outside any member function of the derived
|
|
plugin class, by writing
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
const char *MyConfigDir = cPlugin::ConfigDirectory();
|
|
</pre></td></tr></table><p>
|
|
|
|
<a name="Internationalization"><hr><h2>Internationalization</h2>
|
|
|
|
<center><i><b>Welcome to Babylon!</b></i></center><p>
|
|
|
|
If a plugin displays texts to the user, it should implement internationalized
|
|
versions of these texts and call the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
void RegisterI18n(const tI18nPhrase * const Phrases);
|
|
</pre></td></tr></table><p>
|
|
|
|
to register them with VDR's internationalization mechanism.
|
|
<p>
|
|
The call to this function must be done in the <a href="#Getting started"><tt>Initialize()</tt></a>
|
|
or <a href="#Getting started"><tt>Start()</tt></a> function of the plugin:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
const tI18nPhrase Phrases[] = {
|
|
{ "Hello world!",
|
|
"Hallo Welt!",
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
"",// TODO
|
|
},
|
|
{ NULL }
|
|
};
|
|
|
|
void cPluginHello::Start(void)
|
|
{
|
|
RegisterI18n(Phrases);
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
Each entry of type <tt>tI18nPhrase</tt> must have exactly as many members as defined
|
|
by the constant <tt>I18nNumLanguages</tt> in the file <tt>VDR/i18n.h</tt>, and the
|
|
sequence of the various languages must be the same as defined in <tt>VDR/i18n.c</tt>.<br>
|
|
<b>It is very important that the array is terminated with a <tt>{ NULL }</tt>
|
|
entry!</b>.
|
|
<p>
|
|
Usually you won't be able to fill in all the different translations by yourself, so
|
|
you may want to contact the maintainers of these languages (listed in the file
|
|
<tt>VDR/i18n.c</tt>) and ask them to provide the additional translations.
|
|
<p>
|
|
The actual runtime selection of the texts corresponding to the selected language
|
|
is done by wrapping each internationalized text with the <tt>tr()</tt> macro:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
const char *s = tr("Hello world!");
|
|
</pre></td></tr></table><p>
|
|
|
|
The text given here must be the first one defined in the related <i>Phrases</i>
|
|
entry (which is the English version), and the returned pointer is either a translated
|
|
version (if available) or the original string. In the latter case a message will be
|
|
written to the log file, indicating that a translation is missing.
|
|
Texts are first searched for in the <i>Phrases</i> registered for this plugin (if any)
|
|
and then in the global VDR texts. So a plugin can make use of texts defined by the
|
|
core VDR code.
|
|
|
|
<!--X1.3.30--><table width=100%><tr><td bgcolor=#0000AA> </td><td width=100%>
|
|
<a name="Custom services"><hr><h2>Custom services</h2>
|
|
|
|
<center><i><b>What can I do for you?</b></i></center><p>
|
|
|
|
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:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual bool Service(const char *Id, void *Data = NULL);
|
|
</pre></td></tr></table><p>
|
|
|
|
<tt>Id</tt> 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.
|
|
<tt>Data</tt> 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.
|
|
<p>
|
|
The function shall return <i>true</i> for any service id string it handles, and <i>false</i>
|
|
otherwise. The plugins have to agreee 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:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
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;
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
Plugins should expect to be called with <tt>Data</tt> set to <tt>NULL</tt> and may use
|
|
this as a 'service supported' check without performing any actions.
|
|
<p>
|
|
To send messages to, or request services from a specific plugin, one plugin can directly
|
|
call another plugin's service function:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
Hello_SetGreetingTime_v1_0 hellodata;
|
|
hellodata.NewGreetingTime = 3;
|
|
cPlugin *Plugin = cPluginManager::GetPlugin("hello");
|
|
if (Plugin)
|
|
Plugin->Service("Hello-SetGreetingTime-v1.0", >hellodata);
|
|
</pre></td></tr></table><p>
|
|
|
|
To send messages to, or request services from some plugin that offers the protocol, a
|
|
plugin can call the function <tt>cPluginManager::CallFirstService()</tt>. 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 <tt>NULL</tt>
|
|
if no plugin handled it.
|
|
<p>
|
|
To send a message to all plugins, a plugin can call the function
|
|
<tt>cPluginManager::CallAllServices()</tt>. This function returns <tt>true</tt> if
|
|
any plugin handled the request, or <tt>false</tt> if no plugin handled the request.
|
|
|
|
<!--X1.3.30--></td></tr></table>
|
|
|
|
<!--X1.3.31--><table width=100%><tr><td bgcolor=#00AA00> </td><td width=100%>
|
|
<a name="SVDRP commands"><hr><h2>SVDRP commands</h2>
|
|
|
|
<center><i><b>Infinite Diversity in Infinite Combinations</b></i></center><p>
|
|
|
|
A plugin can implement its own SVDRP commands through the two functions
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual const char **SVDRPHelpPages(void);
|
|
virtual cString SVDRPCommand(const char *Cmd, const char *Option, int &ReplyCode);
|
|
</pre></td></tr></table><p>
|
|
|
|
The <tt>SVDRPHelpPages()</tt> function must return a pointer to a list of help
|
|
strings for all of the plugin's SVDRP commands, like this
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
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;
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
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.
|
|
<p>
|
|
The command names <tt>HELP</tt> and <tt>MAIN</tt> are reserverd and cannot
|
|
be used by a plugin.
|
|
<p>
|
|
The actual processing of SVDRP commands for a plugin is done in its
|
|
<tt>SVDRPCommand()</tt> function.
|
|
Here's an example of such a function, which implements the commands advertised in
|
|
the above help texts:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
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;
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
The command is given to this function in the <tt>Command</tt> parameter, and any optional parameters
|
|
are given in the <tt>Option</tt> string. <tt>Command</tt> always points to an actual, non-empty string, while
|
|
<tt>Option</tt> may point to an empty string (it is never NULL, though).
|
|
<p>
|
|
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 <tt>ReplyCode</tt> to one of the codes defined in <tt>VDR/svdrp.c</tt>
|
|
and return a proper error message.
|
|
<p>
|
|
The default <tt>ReplyCode</tt> 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.
|
|
<p>
|
|
The returned string may consist of several lines, separated by the newline character
|
|
('<tt>\n</tt>'). Each of these lines will be preceded with the <tt>ReplyCode</tt>
|
|
when presenting them to the caller, and the continuation character ('<tt>-</tt>')
|
|
will be set for all but the last one.
|
|
|
|
<!--X1.3.31--></td></tr></table>
|
|
|
|
<a name="Loading plugins into VDR"><hr><h2>Loading plugins into VDR</h2>
|
|
|
|
<center><i><b>Saddling up!</b></i></center><p>
|
|
|
|
Plugins are loaded into VDR using the command line option <b><tt>-P</tt></b>, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
vdr -Phello
|
|
</pre></td></tr></table><p>
|
|
|
|
If the plugin accepts command line options, they are given as part of the argument
|
|
to the <b><tt>-P</tt></b> option, which then has to be enclosed in quotes:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
vdr -P"hello -a abc -b"
|
|
</pre></td></tr></table><p>
|
|
|
|
Any number of plugins can be loaded this way, each with its own <b><tt>-P</tt></b> option:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
vdr -P"hello -a abc -b" -Pdvd -Pmp3
|
|
</pre></td></tr></table><p>
|
|
|
|
If you are not starting VDR from the VDR source directory (and thus your plugins
|
|
cannot be found at their default location) you need to tell VDR the location of
|
|
the plugins through the <b><tt>-L</tt></b> option:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
vdr -L/usr/lib/vdr -Phello
|
|
</pre></td></tr></table><p>
|
|
|
|
There can be any number of <b><tt>-L</tt></b> options, and each of them will apply to the
|
|
<b><tt>-P</tt></b> options following it.
|
|
<p>
|
|
When started with the <b><tt>-h</tt></b> or <b><tt>-V</tt></b> option (for <i>help</i>
|
|
or <i>version</i> information, respectively), VDR will automatically load all plugins
|
|
in the default or given directory that match the VDR plugin
|
|
<a href="#The plugin directory structure">naming convention</a>,
|
|
and display their help and/or version information in addition to its own output.
|
|
|
|
<a name="Building the distribution package"><hr><h2>Building the distribution package</h2>
|
|
|
|
<center><i><b>Let's get this show on the road!</b></i></center><p>
|
|
|
|
If you want to make your plugin available to other VDR users, you'll need to
|
|
make a package that can be easily distributed.
|
|
The <tt>Makefile</tt> that has been created by the call to
|
|
<a href="#Initializing a new plugin directory"><tt>newplugin</tt></a>
|
|
provides the target <tt>dist</tt>, which does this for you.
|
|
<p>
|
|
Simply change into your source directory and execute <tt>make dist</tt>:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
cd VDR/PLUGINS/src/hello
|
|
make dist
|
|
</pre></td></tr></table><p>
|
|
|
|
After this you should find a file named like
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
vdr-hello-0.0.1.tgz
|
|
</pre></td></tr></table><p>
|
|
|
|
in your source directory, where <tt>hello</tt> will be replaced with your actual
|
|
plugin's name, and <tt>0.0.1</tt> will be your plugin's current version number.
|
|
|
|
<a name="Part II - The Internal Interface"><hr><center><h1>Part II - The Internal Interface</h1></center>
|
|
|
|
<a name="Status monitor"><hr><h2>Status monitor</h2>
|
|
|
|
<center><i><b>A piece of the action</b></i></center><p>
|
|
|
|
If a plugin wants to get informed on various events in VDR, it can derive a class from
|
|
<tt>cStatus</tt>, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#include <vdr/status.h>
|
|
|
|
class cMyStatusMonitor : public cStatus {
|
|
protected:
|
|
virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber);
|
|
};
|
|
|
|
void cMyStatusMonitor::ChannelSwitch(const cDevice *Device, int ChannelNumber)
|
|
{
|
|
if (ChannelNumber)
|
|
dsyslog("channel switched to %d on DVB %d", ChannelNumber, Device->CardIndex());
|
|
else
|
|
dsyslog("about to switch channel on DVB %d", Device->CardIndex());
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
An object of this class will be informed whenever the channel is switched on one of
|
|
the DVB devices. It could be used in a plugin like this:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#include <vdr/plugin.h>
|
|
|
|
class cPluginStatus : public cPlugin {
|
|
private:
|
|
cMyStatusMonitor *statusMonitor;
|
|
public:
|
|
cPluginStatus(void);
|
|
virtual ~cPluginStatus();
|
|
...
|
|
virtual bool Start(void);
|
|
...
|
|
};
|
|
|
|
cPluginStatus::cPluginStatus(void)
|
|
{
|
|
statusMonitor = NULL;
|
|
}
|
|
|
|
cPluginStatus::~cPluginStatus()
|
|
{
|
|
delete statusMonitor;
|
|
}
|
|
|
|
bool cPluginStatus::Start(void)
|
|
{
|
|
statusMonitor = new cMyStatusMonitor;
|
|
return true;
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
Note that the actual object is created in the <tt>Start()</tt> function, not in the
|
|
constructor! It is also important to delete the object in the destructor, in order to
|
|
avoid memory leaks.
|
|
<p>
|
|
A Plugin can implement any number of <tt>cStatus</tt> derived objects, and once
|
|
the plugin has been started it may create and delete them as necessary.
|
|
No further action apart from creating an object derived from <tt>cStatus</tt>
|
|
is necessary. VDR will automatically hook it into a list of status monitors, with
|
|
their individual virtual member functions being called in the same sequence as the
|
|
objects were created.
|
|
<p>
|
|
See the file <tt>status.h</tt> for detailed information on which status monitor
|
|
member functions are available in <tt>cStatus</tt>. You only need to implement
|
|
the functions you actually want to use.
|
|
|
|
<a name="Players"><hr><h2>Players</h2>
|
|
|
|
<center><i><b>Play it again, Sam!</b></i></center><p>
|
|
|
|
Implementing a player is a two step process.
|
|
First you need the actual player class, which is derived from the abstract <tt>cPlayer</tt>:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#include <vdr/player.h>
|
|
|
|
class cMyPlayer : public cPlayer {
|
|
protected:
|
|
virtual void Activate(bool On);
|
|
public:
|
|
cMyPlayer(void);
|
|
virtual ~cMyPlayer();
|
|
};
|
|
</pre></td></tr></table><p>
|
|
|
|
What exactly you do in this class is entirely up to you. If you want to run a separate
|
|
thread which, e.g., reads data from a file, you can additionally derive your class from
|
|
<tt>cThread</tt> and implement the necessary functionality:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#include <vdr/player.h>
|
|
|
|
class cMyPlayer : public cPlayer, cThread {
|
|
protected:
|
|
virtual void Activate(bool On);
|
|
virtual void Action(void);
|
|
public:
|
|
cMyPlayer(void);
|
|
virtual ~cMyPlayer();
|
|
};
|
|
</pre></td></tr></table><p>
|
|
|
|
Take a look at the files <tt>player.h</tt> and <tt>dvbplayer.c</tt> to see how VDR implements
|
|
its own player for the VDR recordings.
|
|
<p>
|
|
To play the actual data, the player needs to call its member function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
int PlayPes(const uchar *Data, int Length, bool VideoOnly);
|
|
</pre></td></tr></table><p>
|
|
|
|
where <tt>Data</tt> points to a block of <tt>Length</tt> 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
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
bool DevicePoll(cPoller &Poller, int TimeoutMs = 0);
|
|
</pre></td></tr></table><p>
|
|
|
|
to determine whether the device is ready for further data.
|
|
<p>
|
|
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:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual void SetAudioTrack(eTrackType Type, const tTrackId *TrackId)
|
|
</pre></td></tr></table><p>
|
|
|
|
A player that has special requirements about audio tracks should announce its
|
|
available audio tracks by calling
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
bool DeviceSetAvailableTrack(eTrackType Type, int Index, uint16_t Id, const char *Language = NULL, uint32_t Flags = 0)
|
|
</pre></td></tr></table><p>
|
|
|
|
See <tt>device.h</tt> for details about the parameters for track handling.
|
|
<p>
|
|
The second part needed here is a control object that receives user input from the main
|
|
program loop and reacts on this by telling the player what to do:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#include <vdr/player.h>
|
|
|
|
class cMyControl : public cControl {
|
|
private:
|
|
cMyPlayer *player;
|
|
public:
|
|
cMyControl(void);
|
|
virtual ~cMyControl();
|
|
virtual void Hide(void);
|
|
<!--X1.3.38--><table width=100%><tr><td bgcolor=#FF0000> </td><td width=100%>
|
|
virtual cOsdObject *GetInfo(void);
|
|
<!--X1.3.38--></td></tr></table>
|
|
virtual eOSState ProcessKey(eKeys Key);
|
|
};
|
|
</pre></td></tr></table><p>
|
|
|
|
<tt>cMyControl</tt> shall create an object of type <tt>cMyPlayer</tt> and
|
|
hand over a pointer to it to the <tt>cControl</tt> base class, so that it
|
|
can be later attached to the primary DVB device:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
cMyControl::cMyControl(void)
|
|
:cControl(player = new cMyPlayer)
|
|
{
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
<tt>cMyControl</tt> will receive the user's key presses through the <tt>ProcessKey()</tt>
|
|
function. It will get all button presses, except for the volume control buttons
|
|
(<tt>kVolUp</tt>, <tt>kVolDn</tt>, <tt>kMute</tt>), the power button (<tt>kPower</tt>)
|
|
and the menu button (<tt>kMenu</tt>). If the user has not pressed a button for a while
|
|
(which is typically in the area of about one second), <tt>ProcessKey()</tt> will be called
|
|
with <tt>kNone</tt>, so that the <tt>cMyControl</tt> gets a chance to check whether its
|
|
player is still active. Once the player has become inactive (because the user has decided
|
|
to stop it or the DVB device has detached it), <tt>ProcessKey()</tt> must return <tt>osEnd</tt>
|
|
to make the main program loop shut down the player control.
|
|
<p>
|
|
A derived <tt>cControl</tt> <b>must</b> implement the <tt>Hide()</tt> function, in which
|
|
it has to hide itself from the OSD, in case it uses it. <tt>Hide()</tt> may be called at
|
|
any time, and it may be called even if the <tt>cControl</tt> is not visible at the moment.
|
|
<p>
|
|
<!--X1.3.38--><table width=100%><tr><td bgcolor=#FF0000> </td><td width=100%>
|
|
The <tt>GetInfo()</tt> function is called when the user presses the <tt>Info</tt> button,
|
|
and shall return a pointer to a <tt>cOsdObject</tt> 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,
|
|
<tt>NULL</tt> shall be returned.
|
|
<!--X1.3.38--></td></tr></table>
|
|
<p>
|
|
Finally, to get things going, a plugin that implements a player (and the surrounding
|
|
infrastructure like displaying a list of playable stuff etc) simply has to call the
|
|
static function <tt>cControl::Launch()</tt> with the player control object, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
cControl::Launch(new cMyControl);
|
|
</pre></td></tr></table><p>
|
|
|
|
Ownership of the <tt>MyControl</tt> object is handed over to the VDR core code,
|
|
so the plugin should not keep a pointer to it, because VDR will destroy the object
|
|
whenever it sees fit (for instance because a recording shall start that needs to
|
|
use the primary DVB device, or the user decides to start a different replay).
|
|
<p>
|
|
The <tt>cPlayer</tt> class has a member function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
void DeviceStillPicture(const uchar *Data, int Length);
|
|
</pre></td></tr></table><p>
|
|
|
|
which can be called to display a still picture. VDR uses this function when handling
|
|
its editing marks. A special case of a "player" might use this function to implement
|
|
a "picture viewer".
|
|
<p>
|
|
For detailed information on how to implement your own player, please take a look
|
|
at VDR's <tt>cDvbPlayer</tt> and <tt>cDvbPlayerControl</tt> classes.
|
|
<p>
|
|
<b>User interface</b>
|
|
<p>
|
|
In order for a new player to nicely "blend in" to the overall VDR appearance it
|
|
is recommended that it implements the same functionality with the same keys as the
|
|
VDR player does (as far as this is possible and makes sense). The main points to
|
|
consider here are
|
|
<ul>
|
|
<li>The <i>Ok</i> button shall bring up some display that indicates what is currently
|
|
being played, and what the status of this replay session is. As an alternative (for
|
|
instance with a DVD player) it may display a player specific menu, from which the
|
|
user can select certain options.
|
|
<li>The <i>Up</i>, <i>Down</i>, <i>Left</i> and <i>Right</i> buttons shall control
|
|
<i>Play</i>, <i>Pause</i>, <i>Fast Rewind</i> and <i>Fast Forward</i>, respectively
|
|
(provided that this particular player can implement these functions) if the player
|
|
is not currently showing any menu. If there is a menu, they shall allow the user
|
|
to navigate in the menu. The dedicated <i>Play</i>, <i>Pause</i>, <i>FastRew</i>
|
|
and <i>FastFwd</i> keys shall always result in their specific functionality.
|
|
<li>The <i>Green</i> and <i>Yellow</i> buttons shall skip back- and forward by an
|
|
amount of time suitable for this player (provided that this particular player can
|
|
implement these functions).
|
|
<li>The <i>Blue</i> and <i>Stop</i> button shall immediately stop the replay session.
|
|
</ul>
|
|
Of course, these are only suggestions which should make it easier for VDR users to
|
|
enjoy additional players, since they will be able to control them with actions
|
|
that they already know. If you absolutely want to do things differently, just go
|
|
ahead - it's your show...
|
|
|
|
<a name="Receivers"><hr><h2>Receivers</h2>
|
|
|
|
<center><i><b>Tapping into the stream...</b></i></center><p>
|
|
|
|
In order to receive any kind of data from a <tt>cDevice</tt>, a plugin must set up an
|
|
object derived from the <tt>cReceiver</tt> class:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#include <vdr/receiver.h>
|
|
|
|
class cMyReceiver : public cReceiver, cThread {
|
|
protected:
|
|
virtual void Activate(bool On);
|
|
virtual void Receive(uchar *Data, int Length);
|
|
public:
|
|
cMyReceiver(int Pid);
|
|
};
|
|
|
|
cMyReceiver::cMyReceiver(int Pid)
|
|
:cReceiver(0, -1, Pid)
|
|
{
|
|
}
|
|
|
|
void cMyReceiver::Activate(bool On)
|
|
{
|
|
// start your own thread for processing the received data
|
|
}
|
|
|
|
void cMyReceiver::Receive(uchar *Data, int Length)
|
|
{
|
|
// buffer the data for processing in a separate thread
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
See the comments in <tt>VDR/receiver.h</tt> for details about the various
|
|
member functions of <tt>cReceiver</tt>.
|
|
<p>
|
|
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 <tt>-1</tt> (any negative value will allow
|
|
a <tt>cReceiver</tt> to be detached from its <tt>cDevice</tt> at any time.
|
|
<p>
|
|
Once a <tt>cReceiver</tt> has been created, it needs to be <i>attached</i> to
|
|
a <tt>cDevice</tt>:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
cMyReceiver *Receiver = new cMyReceiver(123);
|
|
|
|
cDevice::ActualDevice()->AttachReceiver(Receiver);
|
|
</pre></td></tr></table><p>
|
|
|
|
Noteh the use of <tt>cDevice::ActualDevice()</tt> 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 <i>Transfer
|
|
Mode</i>).
|
|
<p>
|
|
If the <tt>cReceiver</tt> isn't needed any more, it may simply be <i>deleted</i>
|
|
and will automatically detach itself from the <tt>cDevice</tt>.
|
|
|
|
<a name="Filters"><hr><h2>Filters</h2>
|
|
|
|
<center><i><b>A Fistful of Datas</b></i></center><p>
|
|
|
|
If you want to receive section data you have to implement a derived <tt>cFilter</tt>
|
|
class which at least implements the <tt>Process()</tt> function and a constructor
|
|
that sets the (initial) filter parameters:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#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
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
An instance of such a filter needs to be attached to the device from
|
|
which it shall receive data, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
cMyFilter *Filter = new cMyFilter();
|
|
|
|
cDevice::ActualDevice()->AttachFilter(Filter);
|
|
</pre></td></tr></table><p>
|
|
|
|
If the <tt>cFilter</tt> isn't needed any more, it may simply be <i>deleted</i>
|
|
and will automatically detach itself from the <tt>cDevice</tt>.
|
|
<p>
|
|
See VDR/eit.c or VDR/pat.c to learn how to process filter data.
|
|
|
|
<a name="The On Screen Display"><hr><h2>The On Screen Display</h2>
|
|
|
|
<center><i><b>Window to the world</b></i></center><p>
|
|
|
|
If a plugin needs to have total control over the OSD, it can call the
|
|
static function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#include <vdr/osd.h>
|
|
|
|
cOsd *MyOsd = cOsdProvider::NewOsd(x, y);
|
|
</pre></td></tr></table><p>
|
|
|
|
where <tt>x</tt> and <tt>y</tt> 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
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
tArea Area = { 0, 0, 100, 100, 4 };
|
|
MyOsd->SetAreas(&Area, 1);
|
|
</pre></td></tr></table><p>
|
|
|
|
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).
|
|
<p>
|
|
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 powerfull 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
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
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));
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
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 <tt>CanHandleAreas()</tt> is called with it. If the result indicates
|
|
that the OSD will be able to handle this drawing area, a call to <tt>SetAreas()</tt>
|
|
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.
|
|
<p>
|
|
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).
|
|
|
|
<!--X1.3.37--><table width=100%><tr><td bgcolor=#AA0000> </td><td width=100%>
|
|
<p>
|
|
Directly accessing the OSD is only allowed from the foreground thread, which
|
|
restricts this to a <tt>cOsdObject</tt> returned from the plugin's <tt>MainMenuAction()</tt>
|
|
function, or any of the skin classes a plugin might implement.
|
|
<p>
|
|
If a plugin runs a separate thread and wants to issue a message directly from
|
|
within that tread, it can call
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
int cSkins::QueueMessage(eMessageType Type, const char *s, int Seconds = 0, int Timeout = 0);
|
|
</pre></td></tr></table><p>
|
|
|
|
to queue that message for display. See <tt>VDR/skins.h</tt> for details.
|
|
<!--X1.3.37--></td></tr></table>
|
|
|
|
<a name="Skins"><hr><h2>Skins</h2>
|
|
|
|
<center><i><b>The emperor's new clothes</b></i></center><p>
|
|
|
|
The way VDR displays its menus to the user is implemented through <i>skins</i>.
|
|
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.
|
|
<p>
|
|
By default VDR offers the <i>Classic</i> and the <i>ST:TNG Panels</i> 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
|
|
<tt>VDR/skinclassic.c</tt>.
|
|
<p>
|
|
The first step in implementing a new skin is to derive a class from <tt>cSkin</tt>
|
|
that provides the handling objects necessary to do the actual work:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#include "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 cSkinDisplayMessage *DisplayTrack(int NumTracks, const char * const *Tracks);
|
|
virtual cSkinDisplayMessage *DisplayMessage(void);
|
|
};
|
|
</pre></td></tr></table><p>
|
|
|
|
See the comments in <tt>VDR/skins.h</tt> for details. <tt>VDR/skinclassic.[hc]</tt>
|
|
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 <a href="#Themes">themes</a>
|
|
if you want to make the colors used by your skin configurable.
|
|
<p>
|
|
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
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
new cMySkin;
|
|
</pre></td></tr></table><p>
|
|
|
|
in the <a href="#Getting started"><tt>Start()</tt></a> function of your plugin.
|
|
Do not delete this object, it will be automatically deleted when the program ends.
|
|
<p>
|
|
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
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
skinxyz
|
|
</pre></td></tr></table><p>
|
|
|
|
where <tt>xyz</tt> is the actual name of the skin.
|
|
|
|
<a name="Themes"><hr><h2>Themes</h2>
|
|
|
|
<center><i><b>Eye of the beholder...</b></i></center><p>
|
|
|
|
A <i>theme</i> is a collection of colors that can be used by a <a href="#Skins">skin</a>.
|
|
Since every skin most likely has its own idea about what parts of it can be
|
|
<i>themed</i>, 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.
|
|
<p>
|
|
In order to make a skin "themeable" is shall create an object of type cTheme, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
static cTheme Theme;
|
|
</pre></td></tr></table><p>
|
|
|
|
The next step is to define the colors that shall be provided by this theme, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
THEME_CLR(Theme, clrTitle, 0xFFBC8024);
|
|
THEME_CLR(Theme, clrButtonRedFg, clrWhite);
|
|
THEME_CLR(Theme, clrButtonRedBg, clrRed);
|
|
</pre></td></tr></table><p>
|
|
|
|
<tt>THEME_CLR()</tt> is a helper macro that adds the given color name
|
|
and its default color value to the theme.
|
|
<p>
|
|
Any color names can be used, but they should always start with <tt>clr...</tt> and
|
|
if a given color has a foreground and a background value, the two names shall be
|
|
distinguished by appending <tt>...Fg</tt> and <tt>...Bg</tt>, respectively.
|
|
<p>
|
|
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
|
|
<tt>VDR/osd.h</tt>.
|
|
<p>
|
|
In the actual drawing code of a skin, the color names defined with the <tt>THEME_CLR()</tt>
|
|
macros can be used to fetch the actual color values from the theme, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
osd->DrawText(x, y, s, Theme.Color(clrButtonRedFg), Theme.Color(clrButtonRedBg), font);
|
|
</pre></td></tr></table><p>
|
|
|
|
By default this will use the colors that have been defined in the respective
|
|
<tt>THEME_CLR()</tt> line, but may be overwritten through user supplied theme
|
|
files (see <tt>man vdr(5)</tt> for information about the format of a theme file).
|
|
|
|
<a name="Devices"><hr><h2>Devices</h2>
|
|
|
|
<center><i><b>Expanding the possibilities</b></i></center><p>
|
|
|
|
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.
|
|
<p>
|
|
To implement an additional device, a plugin must derive a class from <tt>cDevice</tt>:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#include <vdr/device.h>
|
|
|
|
class cMyDevice : public cDevice {
|
|
...
|
|
};
|
|
</pre></td></tr></table><p>
|
|
|
|
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 <tt>VDR/device.h</tt> for more information on the various functions,
|
|
and also <tt>VDR/dvbdevice.[hc]</tt> for details on the implementation of
|
|
the <tt>cDvbDevice</tt>, which is used to access the DVB PCI cards.
|
|
<p>
|
|
<b>Channel selection</b>
|
|
<p>
|
|
If the new device can receive, it most likely needs to provide a way of
|
|
selecting which channel it shall tune to:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual bool ProvidesSource(int Source) const;
|
|
virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL);
|
|
virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
|
|
</pre></td></tr></table><p>
|
|
|
|
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,
|
|
repectively.
|
|
<p>
|
|
<b>Audio selection</b>
|
|
<p>
|
|
If the device can provide more than a single audio track, it can implement the
|
|
following function to make them available:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual void SetAudioTrackDevice(eTrackType Type);
|
|
virtual int GetAudioChannelDevice(void);
|
|
virtual void SetAudioChannelDevice(int AudioChannel);
|
|
</pre></td></tr></table><p>
|
|
|
|
<p>
|
|
<b>Recording</b>
|
|
<p>
|
|
A device that can be used for recording must implement the functions
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual bool SetPid(cPidHandle *Handle, int Type, bool On);
|
|
virtual bool OpenDvr(void);
|
|
virtual void CloseDvr(void);
|
|
virtual bool GetTSPacket(uchar *&Data);
|
|
</pre></td></tr></table><p>
|
|
|
|
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 <tt>GetTSPacket()</tt>
|
|
must deliver exactly one such packet (if one is currently available).
|
|
<p>
|
|
<b>Replaying</b>
|
|
<p>
|
|
The functions to implement replaying capabilites are
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual bool HasDecoder(void) const;
|
|
virtual bool CanReplay(void) const;
|
|
virtual bool SetPlayMode(ePlayMode PlayMode);
|
|
virtual int64_t GetSTC(void);
|
|
virtual void TrickSpeed(int Speed);
|
|
virtual void Clear(void);
|
|
virtual void Play(void);
|
|
virtual void Freeze(void);
|
|
virtual void Mute(void);
|
|
virtual void StillPicture(const uchar *Data, int Length);
|
|
virtual bool Poll(cPoller &Poller, int TimeoutMs = 0);
|
|
virtual int PlayVideo(const uchar *Data, int Length);
|
|
</pre></td></tr></table><p>
|
|
|
|
In addition, the following functions may be implemented to provide further
|
|
functionality:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual bool GrabImage(const char *FileName, bool Jpeg = true, int Quality = -1, int Si
|
|
virtual void SetVideoFormat(bool VideoFormat16_9);
|
|
virtual void SetVolumeDevice(int Volume);
|
|
</pre></td></tr></table><p>
|
|
|
|
<p>
|
|
<b>Section Filtering</b>
|
|
<p>
|
|
If your device provides section filtering capabilities it can implement
|
|
the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask);
|
|
</pre></td></tr></table><p>
|
|
|
|
which must open a file handle that delivers section data for the given
|
|
filter parameters.
|
|
<p>
|
|
In order to actually start section handling, the
|
|
device also needs to call the function
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
StartSectionHandler();
|
|
</pre></td></tr></table><p>
|
|
|
|
from its constructor.
|
|
<p>
|
|
See <a href="#Filters">Filters</a> on how to set up actual filters that can
|
|
handle section data.
|
|
|
|
<p>
|
|
<b>On Screen Display</b>
|
|
<p>
|
|
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 <tt>cOsdProvider</tt>, which, when its <tt>CreateOsd()</tt>
|
|
function is called, returns an object derived from <tt>cOsd</tt>, which can be used to
|
|
access the device's OSD:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
class cMyOsdProvider : public cOsdProvider {
|
|
public:
|
|
cMyOsdProvider(void);
|
|
virtual cOsd *CreateOsd(int Left, int Top);
|
|
};
|
|
</pre></td></tr></table><p>
|
|
|
|
In its <tt>MakePrimaryDevice()</tt> function the device shall create an object
|
|
of this class, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
void cMyDevice::MakePrimaryDevice(bool On)
|
|
{
|
|
new cMyOsdProvider;
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
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.
|
|
|
|
<p>
|
|
<b>Initializing new devices</b>
|
|
<p>
|
|
A derived <tt>cDevice</tt> 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 <tt>VDR/dvbdevice.c</tt> for the implementation of the <tt>cDvbDevice</tt>
|
|
initialize function.
|
|
<p>
|
|
A plugin that adds devices to a VDR instance shall call this
|
|
function from its <a href="#Getting started"><tt>Initialize()</tt></a> function
|
|
to make sure other plugins that may need to have access to all available devices
|
|
will see them in their <a href="#Getting started"><tt>Start()</tt></a> function.
|
|
<p>
|
|
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 <tt>new</tt>
|
|
operator!
|
|
|
|
<a name="Audio"><hr><h2>Audio</h2>
|
|
|
|
<center><i><b>"The stereo effect may only be experienced if stereo equipment is used!"</b></i></center><p>
|
|
|
|
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.
|
|
<p>
|
|
To implement a new audio output facility, simply derive a class from <tt>cAudio</tt>,
|
|
as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#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);
|
|
};
|
|
</pre></td></tr></table><p>
|
|
|
|
You should create your derived audio object in the
|
|
<a href="#Getting started"><tt>Start()</tt></a> function of your plugin.
|
|
Note that the object has to be created on the heap (using <tt>new</tt>),
|
|
and you shall not delete it at any point (it will be deleted automatically
|
|
when the program ends).
|
|
<p>
|
|
The <tt>Play()</tt> 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 <tt>cThread</tt> and run the actual audio processing
|
|
as a separate thread. Note that the offered data is only valid within the call
|
|
to <tt>Play()</tt>, so if you can't process the entire block immediately, you
|
|
will need to copy it for later processing in your thread.
|
|
<p>
|
|
The <tt>Mute()</tt> and <tt>Clear()</tt> functions will be called whenever the audio shall
|
|
be muted, or any buffered data shall be cleared, respectively.
|
|
|
|
<a name="Remote Control"><hr><h2>Remote Control</h2>
|
|
|
|
<center><i><b>The joy of zapping!</b></i></center><p>
|
|
|
|
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 <tt>cRemote</tt> class to do that.
|
|
<p>
|
|
The simplest method for a plugin to issue commands to VDR is to call the
|
|
static function <tt>cRemote::Put(eKeys Key)</tt>, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
cRemote::Put(kUp);
|
|
</pre></td></tr></table><p>
|
|
|
|
In this case the plugin must do the mapping of whatever incoming signal or code
|
|
it processes to the <tt>eKeys</tt> values itself. This makes sense if the incoming
|
|
codes are well known and won't ever change.
|
|
<p>
|
|
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 <tt>cRemote</tt>, as in
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
#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);
|
|
};
|
|
</pre></td></tr></table><p>
|
|
|
|
Note that deriving from <tt>cThread</tt> 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 <tt>cRemote</tt> base class.
|
|
<p>
|
|
You should create your derived remote control object in the
|
|
<a href="#Getting started"><tt>Start()</tt></a> function of your plugin.
|
|
Note that the object has to be created on the heap (using <tt>new</tt>),
|
|
and you shall not delete it at any point (it will be deleted automatically
|
|
when the program ends).
|
|
<p>
|
|
The constructor of your remote control class should look like this
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
cMyRemote::cMyRemote(const char *Name)
|
|
:cRemote(Name)
|
|
{
|
|
Start();
|
|
}
|
|
</pre></td></tr></table><p>
|
|
|
|
The <tt>Name</tt> is important in order for the <tt>cRemote</tt> base class
|
|
to be able to distinguish the codes for the various remote controls.
|
|
When creating your <tt>cMyRemote</tt> object you should use the value returned
|
|
by the <tt>Name()</tt> member function of the plugin class, which returns the
|
|
plugin's name. Calling <tt>Start()</tt> will start the thread that collects
|
|
the incoming data (by calling your <tt>Action()</tt> 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 <tt>Start()</tt>.
|
|
<p>
|
|
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
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
virtual bool Ready(void);
|
|
</pre></td></tr></table><p>
|
|
|
|
and have it return <i>false</i>. 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 <tt>Initialize()</tt>,
|
|
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. <tt>Initialize()</tt>, 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 <tt>cRemote</tt> class has detected useful incoming data,
|
|
<tt>Initialize()</tt> should return <i>true</i>. If any fatal error occurs, <i>false</i>
|
|
should be returned.
|
|
<p>
|
|
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 <tt>cRemote</tt> member functions
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
void PutSetup(const char *Setup);
|
|
const char *GetSetup(void);
|
|
</pre></td></tr></table><p>
|
|
|
|
to store and retrieve a character string containing whatever data is needed.
|
|
Note that the <tt>Initialize()</tt> function will only be called if there are
|
|
no key mappings known for this remote control. Once the key mappings have been
|
|
learned, <tt>Initialize()</tt> will never be called again.
|
|
<p>
|
|
The <tt>cRemote</tt> 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
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
Put(const char *Code, bool Repeat = false, bool Release = false);
|
|
</pre></td></tr></table><p>
|
|
|
|
where <tt>Code</tt> is the string representation of the remote control's
|
|
incoming data. <tt>Repeat</tt> and <tt>Release</tt> 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 <tt>Put()</tt> function available for your convenience,
|
|
which takes a 64 bit unsigned integer value instead of a character string:
|
|
|
|
<p><table><tr><td bgcolor=#F0F0F0><pre>
|
|
Put(uint64 Code, bool Repeat = false, bool Release = false);
|
|
</pre></td></tr></table><p>
|
|
|
|
The other parameters have the same meaning as in the first version of this function.
|
|
|
|
</body>
|
|
</html>
|