summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Schmidinger <vdr@tvdr.de>2002-05-09 16:26:56 +0200
committerKlaus Schmidinger <vdr@tvdr.de>2002-05-09 16:26:56 +0200
commitb527b2770868bccde05ad47393951fde5d51f79a (patch)
treec939c1b42f0be0840d7cdbfc61af4c182a955cbf
parentbf4a5a678d56f3fca45110f1536ce2c3c8f3b816 (diff)
downloadvdr-b527b2770868bccde05ad47393951fde5d51f79a.tar.gz
vdr-b527b2770868bccde05ad47393951fde5d51f79a.tar.bz2
Implemented plugin interface1.1.0
-rw-r--r--HISTORY14
-rw-r--r--Makefile28
-rw-r--r--PLUGINS.html620
-rw-r--r--PLUGINS/src/hello/COPYING340
-rw-r--r--PLUGINS/src/hello/HISTORY6
-rw-r--r--PLUGINS/src/hello/Makefile78
-rw-r--r--PLUGINS/src/hello/README11
-rw-r--r--PLUGINS/src/hello/hello.c105
-rw-r--r--PLUGINS/src/hello/i18n.c52
-rw-r--r--PLUGINS/src/hello/i18n.h11
-rw-r--r--config.c375
-rw-r--r--config.h42
-rw-r--r--i18n.c143
-rw-r--r--i18n.h20
-rw-r--r--menu.c690
-rw-r--r--menuitems.c476
-rw-r--r--menuitems.h110
-rwxr-xr-xnewplugin250
-rw-r--r--osd.c11
-rw-r--r--osd.h4
-rw-r--r--plugin.c319
-rw-r--r--plugin.h81
-rw-r--r--remote.h4
-rw-r--r--vdr.124
-rw-r--r--vdr.c131
25 files changed, 3136 insertions, 809 deletions
diff --git a/HISTORY b/HISTORY
index c6af5343..ff8e834f 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,7 +1,9 @@
Video Disk Recorder Revision History
------------------------------------
-2000-02-19: Version 0.01 (Initial revision).
+2000-02-19: Version 0.01
+
+- Initial revision.
2000-03-11: Version 0.02
@@ -1242,3 +1244,13 @@ Video Disk Recorder Revision History
'channels.conf.cable' whenever a new version is sent to me.
- Fixed skipping forward in time shift mode near the end of the recording (thanks
to Andreas Böttger for reporting this one).
+
+2002-05-09: Version 1.1.0
+
+- Begin of the 1.1 development branch. THIS IS NOT A STABLE VERSION!
+ The current stable version for every day use is still the 1.0 branch.
+- First step towards a universal plugin interface. See the file PLUGINS.html
+ for a detailed description. The man page vdr(1) describes the new options '-L'
+ and '-P' used to load plugins. This first step implements the complete "outer"
+ shell for plugins. The "inner" access to VDR data structures will follow.
+- The VDR version number is now displayed in the title line of the "Setup" menu.
diff --git a/Makefile b/Makefile
index 62fd8973..4759ec24 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
# See the main source file 'vdr.c' for copyright information and
# how to reach the author.
#
-# $Id: Makefile 1.33 2002/04/01 12:50:48 kls Exp $
+# $Id: Makefile 1.34 2002/05/09 09:35:05 kls Exp $
.DELETE_ON_ERROR:
@@ -13,15 +13,17 @@ DTVDIR = ./libdtv
MANDIR = /usr/local/man
BINDIR = /usr/local/bin
+PLUGINDIR= ./PLUGINS
+
VIDEODIR = /video
INCLUDES = -I$(DVBDIR)/ost/include
DTVLIB = $(DTVDIR)/libdtv.a
-OBJS = config.o dvbapi.o dvbosd.o eit.o font.o i18n.o interface.o menu.o osd.o\
- recording.o remote.o remux.o ringbuffer.o svdrp.o thread.o tools.o vdr.o\
- videodir.o
+OBJS = config.o dvbapi.o dvbosd.o eit.o font.o i18n.o interface.o menu.o\
+ menuitems.o osd.o plugin.o recording.o remote.o remux.o ringbuffer.o\
+ svdrp.o thread.o tools.o vdr.o videodir.o
OSDFONT = -adobe-helvetica-medium-r-normal--23-*-100-100-p-*-iso8859-1
FIXFONT = -adobe-courier-bold-r-normal--25-*-100-100-m-*-iso8859-1
@@ -69,7 +71,7 @@ include $(DEPFILE)
# The main program:
vdr: $(OBJS) $(DTVLIB)
- g++ -g -O2 $(OBJS) $(NCURSESLIB) -ljpeg -lpthread $(LIBDIRS) $(DTVLIB) -o vdr
+ g++ -g -O2 -rdynamic $(OBJS) $(NCURSESLIB) -ljpeg -lpthread -ldl $(LIBDIRS) $(DTVLIB) -o vdr
# The font files:
@@ -88,6 +90,21 @@ genfontfile: genfontfile.c
$(DTVLIB) $(DTVDIR)/libdtv.h:
make -C $(DTVDIR) all
+# The 'include' directory (for plugins):
+
+include-dir:
+ @mkdir -p include/vdr
+ @(cd include/vdr; for i in ../../*.h; do ln -fs $$i .; done)
+
+# Plugins:
+
+plugins: include-dir
+ @for i in `ls $(PLUGINDIR)/src | grep -v '[^a-z0-9]'`; do make -C "$(PLUGINDIR)/src/$$i" all; done
+
+plugins-clean:
+ @for i in `ls $(PLUGINDIR)/src | grep -v '[^a-z0-9]'`; do make -C "$(PLUGINDIR)/src/$$i" clean; done
+ @-rm -f $(PLUGINDIR)/lib/*
+
# Install the files:
install:
@@ -104,6 +121,7 @@ install:
clean:
make -C $(DTVDIR) clean
-rm -f $(OBJS) $(DEPFILE) vdr genfontfile genfontfile.o core* *~
+ -rm -rf include
fontclean:
-rm -f fontfix.c fontosd.c
CLEAN: clean fontclean
diff --git a/PLUGINS.html b/PLUGINS.html
new file mode 100644
index 00000000..7481bfaf
--- /dev/null
+++ b/PLUGINS.html
@@ -0,0 +1,620 @@
+<html>
+<head>
+<title>The VDR Plugin System</title>
+</head>
+<body bgcolor="white">
+
+<h1>The VDR Plugin System</h1>
+
+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 describes the "outside" interface of the plugin system.
+It handles everything necessary for a plugin to get hooked into the core
+VDR program and present itself to the user.
+
+<!--<p>TODO: Link to the document about VDR base classes to use when implementing actual functionality (yet to be written).-->
+
+<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 demo 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 demo 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.
+
+<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><br>
+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><br>
+VDR/PLUGINS/src
+VDR/PLUGINS/src/demo
+VDR/PLUGINS/src/hello
+VDR/PLUGINS/lib
+VDR/PLUGINS/lib/libvdr-demo.so.1.1.0
+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>demo</tt> and
+<tt>hello</tt>, respectively). 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>demo</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-demo-0.0.1.tgz</tt>
+<p>
+and will unpack into a directory named
+<p>
+<tt>vdr-demo-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><br>
+ln -s vdr-demo-0.0.1 demo
+</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>vdr-demo-0.0.1</tt> and
+<tt>vdr-demo-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.
+
+<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><br>
+VDRPLUGINCREATOR(cPluginDemo);
+</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.
+
+<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><br>
+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>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, and has to
+take care that any threads the plugin may have created will be stopped.
+<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.
+
+<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><br>
+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><br>
+static const char *VERSION = "0.0.1";
+
+...
+
+const char *cPluginDemo::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.
+
+<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><br>
+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><br>
+virtual const char *Description(void)
+{
+ return "A simple demo plugin";
+}
+</pre></td></tr></table><p>
+
+<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><br>
+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><br>
+bool cPluginDemo::ProcessArgs(int argc, char *argv[])
+{
+ 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': fprintf(stderr, "option -a = %s\n", optarg);
+ break;
+ case 'b': fprintf(stderr, "option -b\n");
+ 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.
+
+<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><br>
+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><br>
+const char *cPluginDemo::CommandLineHelp(void)
+{
+ 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 the function
+
+<p><table><tr><td bgcolor=#F0F0F0><pre><br>
+virtual void Start(void);
+</pre></td></tr></table><p>
+
+which is called once for each plugin at program startup.
+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>
+If the plugin doesn't implement any background functionality or internationalized
+texts, it doesn't need to implement this function.
+
+<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><br>
+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><br>
+const char *cPluginDemo::MainMenuEntry(void)
+{
+ return "Demo";
+}
+</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.
+
+<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><br>
+virtual cOsdMenu *MainMenuAction(void);
+</pre></td></tr></table><p>
+
+which can do one of two 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>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>
+
+<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><br>
+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 "Setup" menu
+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).
+<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 preceeded with the plugin's
+name, as in
+<p>
+<tt>demo.SomeParameter = 123</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><br>
+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>"SomeParameter"</tt> in the above
+example, without the prefix <tt>"demo."</tt>) and <tt>Value</tt> is a simple data type (like
+<tt>char&nbsp;*</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("SomeParameter");</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="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><br>
+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>Start()</tt></a> function of the plugin:
+
+<p><table><tr><td bgcolor=#F0F0F0><pre><br>
+const tI18nPhrase Phrases[] = {
+ { "Hello world!",
+ "Hallo Welt!",
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ },
+ { NULL }
+ };
+
+void cPluginDemo::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>{&nbsp;NULL&nbsp;}</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><br>
+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.
+
+<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><br>
+vdr -Pdemo
+</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><br>
+vdr -P"demo -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><br>
+vdr -P"demo -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><br>
+vdr -L/usr/lib/vdr -Pdemo
+</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>package</tt>, which does this for you.
+<p>
+Simply change into your source directory and execute <tt>make package</tt>:
+
+<p><table><tr><td bgcolor=#F0F0F0><pre><br>
+cd VDR/PLUGINS/src/demo
+make package
+</pre></td></tr></table><p>
+
+After this you should find a file named like
+
+<p><table><tr><td bgcolor=#F0F0F0><pre><br>
+vdr-demo-0.0.1.tgz
+</pre></td></tr></table><p>
+
+in your source directory, where <tt>demo</tt> will be replaced with your actual
+plugin's name, and <tt>0.0.1</tt> will be your plugin's current version number.
+
+</body>
+</html>
diff --git a/PLUGINS/src/hello/COPYING b/PLUGINS/src/hello/COPYING
new file mode 100644
index 00000000..5b6e7c66
--- /dev/null
+++ b/PLUGINS/src/hello/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/PLUGINS/src/hello/HISTORY b/PLUGINS/src/hello/HISTORY
new file mode 100644
index 00000000..b35760ca
--- /dev/null
+++ b/PLUGINS/src/hello/HISTORY
@@ -0,0 +1,6 @@
+VDR Plugin 'hello' Revision History
+-----------------------------------
+
+2002-05-09: Version 0.0.1
+
+- Initial revision.
diff --git a/PLUGINS/src/hello/Makefile b/PLUGINS/src/hello/Makefile
new file mode 100644
index 00000000..fb764cdc
--- /dev/null
+++ b/PLUGINS/src/hello/Makefile
@@ -0,0 +1,78 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id: Makefile 1.1 2002/05/09 15:17:44 kls Exp $
+
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+#
+PLUGIN = hello
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = `grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g'`
+
+### The directory environment:
+
+DVBDIR = ../../../../DVB/ost/include
+VDRDIR = ../../..
+VDRINC = $(VDRDIR)/include
+LIBDIR = ../../lib
+TMPDIR = /tmp
+
+### The version number of VDR (taken from VDR's "config.h"):
+
+VDRVERSION = `grep 'define VDRVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g'`
+
+### The name of the distribution archive:
+
+ARCHIVE = vdr-$(PLUGIN)-$(VERSION)
+
+### Includes and Defines (add further entries here):
+
+INCLUDES = -I$(VDRINC) -I$(DVBDIR)
+
+DEFINES = -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o i18n.o
+
+### The C++ compiler and options:
+
+CXX = g++
+CXXFLAGS = -O2 -Wall -Woverloaded-virtual -m486
+
+### Implicit rules:
+
+%.o: %.c
+ $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $<
+
+# Dependencies:
+
+MAKEDEP = g++ -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+ @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+
+include $(DEPFILE)
+
+### Targets:
+
+all: libvdr-$(PLUGIN).so
+
+libvdr-$(PLUGIN).so: $(OBJS)
+ $(CXX) $(CXXFLAGS) -shared $(OBJS) -o $@
+ @cp $@ $(LIBDIR)/$@.$(VDRVERSION)
+
+package: clean
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @mkdir $(TMPDIR)/$(ARCHIVE)
+ @cp -a * $(TMPDIR)/$(ARCHIVE)
+ @tar czf $(ARCHIVE).tgz -C $(TMPDIR) $(ARCHIVE)
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @echo Distribution archive created as $(ARCHIVE).tgz
+
+clean:
+ @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~
diff --git a/PLUGINS/src/hello/README b/PLUGINS/src/hello/README
new file mode 100644
index 00000000..fd844ca6
--- /dev/null
+++ b/PLUGINS/src/hello/README
@@ -0,0 +1,11 @@
+This is a "plugin" for the Video Disk Recorder (VDR).
+
+Written by: Klaus Schmidinger <kls@cadsoft.de>
+
+Project's homepage: www.cadsoft.de/people/kls/vdr
+
+Latest version available at: www.cadsoft.de/people/kls/vdr/software.htm
+
+See the file COPYING for license information.
+
+Description: This is a small demo of the VDR plugin interface.
diff --git a/PLUGINS/src/hello/hello.c b/PLUGINS/src/hello/hello.c
new file mode 100644
index 00000000..5db4f2a2
--- /dev/null
+++ b/PLUGINS/src/hello/hello.c
@@ -0,0 +1,105 @@
+/*
+ * hello.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id: hello.c 1.1 2002/05/09 15:28:51 kls Exp $
+ */
+
+#include <getopt.h>
+#include <vdr/interface.h>
+#include <vdr/plugin.h>
+#include "i18n.h"
+
+static const char *VERSION = "0.0.1";
+static const char *DESCRIPTION = "A friendly greeting";
+static const char *MAINMENUENTRY = "Hello";
+
+class cPluginHello : public cPlugin {
+private:
+ // Add any member variables or functions you may need here.
+ const char *option_a;
+ bool option_b;
+public:
+ cPluginHello(void);
+ virtual ~cPluginHello();
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return tr(DESCRIPTION); }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual void Start(void);
+ virtual const char *MainMenuEntry(void) { return tr(MAINMENUENTRY); }
+ virtual cOsdMenu *MainMenuAction(void);
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *Name, const char *Value);
+ };
+
+cPluginHello::cPluginHello(void)
+{
+ // Initialize any member varaiables here.
+ // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
+ // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
+ option_a = NULL;
+ option_b = false;
+}
+
+cPluginHello::~cPluginHello()
+{
+ // Clean up after yourself!
+}
+
+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";
+}
+
+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;
+}
+
+void cPluginHello::Start(void)
+{
+ // Start any background activities the plugin shall perform.
+ RegisterI18n(Phrases);
+}
+
+cOsdMenu *cPluginHello::MainMenuAction(void)
+{
+ // Perform the action when selected from the main VDR menu.
+ Interface->Info(tr("Hello world!"));
+ return NULL;
+}
+
+cMenuSetupPage *cPluginHello::SetupMenu(void)
+{
+ // Return a setup menu in case the plugin supports one.
+ return NULL;
+}
+
+bool cPluginHello::SetupParse(const char *Name, const char *Value)
+{
+ // Parse your own setup parameters and store their values.
+ return false;
+}
+
+VDRPLUGINCREATOR(cPluginHello); // Don't touch this!
diff --git a/PLUGINS/src/hello/i18n.c b/PLUGINS/src/hello/i18n.c
new file mode 100644
index 00000000..70617421
--- /dev/null
+++ b/PLUGINS/src/hello/i18n.c
@@ -0,0 +1,52 @@
+/*
+ * i18n.c: Internationalization
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id: i18n.c 1.1 2002/05/09 15:13:31 kls Exp $
+ */
+
+#include "i18n.h"
+
+const tI18nPhrase Phrases[] = {
+ { "Hello",
+ "Hallo",
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ },
+ { "Hello world!",
+ "Hallo Welt!",
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ },
+ { "A friendly greeting",
+ "Ein freundlicher Gruß",
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ },
+ { NULL }
+ };
diff --git a/PLUGINS/src/hello/i18n.h b/PLUGINS/src/hello/i18n.h
new file mode 100644
index 00000000..2976fe88
--- /dev/null
+++ b/PLUGINS/src/hello/i18n.h
@@ -0,0 +1,11 @@
+/*
+ * i18n.h: Internationalization
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id: i18n.h 1.1 2002/05/09 15:15:49 kls Exp $
+ */
+
+#include <vdr/i18n.h>
+
+extern const tI18nPhrase Phrases[];
diff --git a/config.c b/config.c
index be0defe2..6068d909 100644
--- a/config.c
+++ b/config.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: config.c 1.98 2002/04/26 12:30:00 kls Exp $
+ * $Id: config.c 1.99 2002/05/05 12:00:00 kls Exp $
*/
#include "config.h"
@@ -13,6 +13,7 @@
#include "dvbapi.h"
#include "i18n.h"
#include "interface.h"
+#include "plugin.h"
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
// format characters in order to allow any number of blanks after a numeric
@@ -387,11 +388,12 @@ cTimer& cTimer::operator= (const cTimer &Timer)
return *this;
}
-bool cTimer::operator< (const cTimer &Timer)
+bool cTimer::operator< (const cListObject &ListObject)
{
+ cTimer *ti = (cTimer *)&ListObject;
time_t t1 = StartTime();
- time_t t2 = (*(cTimer *)&Timer).StartTime();
- return t1 < t2 || (t1 == t2 && priority > Timer.priority);
+ time_t t2 = ti->StartTime();
+ return t1 < t2 || (t1 == t2 && priority > ti->priority);
}
const char *cTimer::ToText(cTimer *Timer)
@@ -917,12 +919,76 @@ const cCaDefinition *cCaDefinitions::Get(int Number)
return NULL;
}
+// -- cSetupLine -------------------------------------------------------------
+
+cSetupLine::cSetupLine(void)
+{
+ plugin = name = value = NULL;
+}
+
+cSetupLine::cSetupLine(const char *Name, const char *Value, const char *Plugin)
+{
+ name = strdup(Name);
+ value = strdup(Value);
+ plugin = Plugin ? strdup(Plugin) : NULL;
+}
+
+cSetupLine::~cSetupLine()
+{
+ delete plugin;
+ delete name;
+ delete value;
+}
+
+bool cSetupLine::operator< (const cListObject &ListObject)
+{
+ const cSetupLine *sl = (cSetupLine *)&ListObject;
+ if (!plugin && !sl->plugin)
+ return strcasecmp(name, sl->name) < 0;
+ if (!plugin)
+ return true;
+ if (!sl->plugin)
+ return false;
+ int result = strcasecmp(plugin, sl->plugin);
+ if (result == 0)
+ result = strcasecmp(name, sl->name);
+ return result < 0;
+}
+
+bool cSetupLine::Parse(char *s)
+{
+ char *p = strchr(s, '=');
+ if (p) {
+ *p = 0;
+ char *Name = compactspace(s);
+ char *Value = compactspace(p + 1);
+ if (*Name && *Value) {
+ p = strchr(Name, '.');
+ if (p) {
+ *p = 0;
+ char *Plugin = compactspace(Name);
+ Name = compactspace(p + 1);
+ if (!(*Plugin && *Name))
+ return false;
+ plugin = strdup(Plugin);
+ }
+ name = strdup(Name);
+ value = strdup(Value);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool cSetupLine::Save(FILE *f)
+{
+ return fprintf(f, "%s%s%s = %s\n", plugin ? plugin : "", plugin ? "." : "", name, value) > 0;
+}
+
// -- cSetup -----------------------------------------------------------------
cSetup Setup;
-char *cSetup::fileName = NULL;
-
cSetup::cSetup(void)
{
OSDLanguage = 0;
@@ -966,19 +1032,77 @@ cSetup::cSetup(void)
CurrentVolume = MAXVOLUME;
}
-void cSetup::PrintCaCaps(FILE *f, const char *Name)
+cSetup& cSetup::operator= (const cSetup &s)
+{
+ memcpy(&__BeginData__, &s.__BeginData__, (char *)&s.__EndData__ - (char *)&s.__BeginData__);
+ return *this;
+}
+
+cSetupLine *cSetup::Get(const char *Name, const char *Plugin)
+{
+ for (cSetupLine *l = First(); l; l = Next(l)) {
+ if ((l->Plugin() == NULL) == (Plugin == NULL)) {
+ if ((!Plugin || strcasecmp(l->Plugin(), Plugin) == 0) && strcasecmp(l->Name(), Name) == 0)
+ return l;
+ }
+ }
+ return NULL;
+}
+
+void cSetup::Store(const char *Name, const char *Value, const char *Plugin)
+{
+ if (Name && *Name) {
+ cSetupLine *l = Get(Name, Plugin);
+ if (l)
+ Del(l);
+ if (Value)
+ Add(new cSetupLine(Name, Value, Plugin));
+ }
+}
+
+void cSetup::Store(const char *Name, int Value, const char *Plugin)
+{
+ char *buffer = NULL;
+ asprintf(&buffer, "%d", Value);
+ Store(Name, buffer, Plugin);
+ delete buffer;
+}
+
+bool cSetup::Load(const char *FileName)
+{
+ if (cConfig<cSetupLine>::Load(FileName, true)) {
+ bool result = true;
+ for (cSetupLine *l = First(); l; l = Next(l)) {
+ if (l->Plugin()) {
+ cPlugin *p = cPluginManager::GetPlugin(l->Plugin());
+ if (p && !p->SetupParse(l->Name(), l->Value()))
+ result = false;
+ }
+ else {
+ if (!Parse(l->Name(), l->Value()))
+ result = false;
+ }
+ }
+ return result;
+ }
+ return false;
+}
+
+void cSetup::StoreCaCaps(const char *Name)
{
for (int d = 0; d < MAXDVBAPI; d++) {
- int written = 0;
+ char buffer[MAXPARSEBUFFER];
+ char *q = buffer;
+ *buffer = 0;
for (int i = 0; i < MAXCACAPS; i++) {
if (CaCaps[d][i]) {
- if (!written++)
- fprintf(f, "CaCaps = %d", d + 1);
- fprintf(f, " %d", CaCaps[d][i]);
+ if (!*buffer)
+ q += snprintf(buffer, sizeof(buffer), "%d", d + 1);
+ q += snprintf(q, sizeof(buffer) - (q - buffer), " %d", CaCaps[d][i]);
}
}
- if (written)
- fprintf(f, "\n");
+ if (*buffer)
+ Store(Name, buffer);
}
}
@@ -1005,144 +1129,99 @@ bool cSetup::ParseCaCaps(const char *Value)
return false;
}
-bool cSetup::Parse(char *s)
-{
- char *p = strchr(s, '=');
- if (p) {
- *p = 0;
- char *Name = compactspace(s);
- char *Value = compactspace(p + 1);
- if (*Name && *Value) {
- if (!strcasecmp(Name, "OSDLanguage")) OSDLanguage = atoi(Value);
- else if (!strcasecmp(Name, "PrimaryDVB")) PrimaryDVB = atoi(Value);
- else if (!strcasecmp(Name, "ShowInfoOnChSwitch")) ShowInfoOnChSwitch = atoi(Value);
- else if (!strcasecmp(Name, "MenuScrollPage")) MenuScrollPage = atoi(Value);
- else if (!strcasecmp(Name, "MarkInstantRecord")) MarkInstantRecord = atoi(Value);
- else if (!strcasecmp(Name, "NameInstantRecord")) strn0cpy(NameInstantRecord, Value, MaxFileName);
- else if (!strcasecmp(Name, "InstantRecordTime")) InstantRecordTime = atoi(Value);
- else if (!strcasecmp(Name, "LnbSLOF")) LnbSLOF = atoi(Value);
- else if (!strcasecmp(Name, "LnbFrequLo")) LnbFrequLo = atoi(Value);
- else if (!strcasecmp(Name, "LnbFrequHi")) LnbFrequHi = atoi(Value);
- else if (!strcasecmp(Name, "DiSEqC")) DiSEqC = atoi(Value);
- else if (!strcasecmp(Name, "SetSystemTime")) SetSystemTime = atoi(Value);
- else if (!strcasecmp(Name, "TimeTransponder")) TimeTransponder = atoi(Value);
- else if (!strcasecmp(Name, "MarginStart")) MarginStart = atoi(Value);
- else if (!strcasecmp(Name, "MarginStop")) MarginStop = atoi(Value);
- else if (!strcasecmp(Name, "EPGScanTimeout")) EPGScanTimeout = atoi(Value);
- else if (!strcasecmp(Name, "EPGBugfixLevel")) EPGBugfixLevel = atoi(Value);
- else if (!strcasecmp(Name, "SVDRPTimeout")) SVDRPTimeout = atoi(Value);
- else if (!strcasecmp(Name, "SortTimers")) SortTimers = atoi(Value);
- else if (!strcasecmp(Name, "PrimaryLimit")) PrimaryLimit = atoi(Value);
- else if (!strcasecmp(Name, "DefaultPriority")) DefaultPriority = atoi(Value);
- else if (!strcasecmp(Name, "DefaultLifetime")) DefaultLifetime = atoi(Value);
- else if (!strcasecmp(Name, "UseSubtitle")) UseSubtitle = atoi(Value);
- else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value);
- else if (!strcasecmp(Name, "VideoFormat")) VideoFormat = atoi(Value);
- else if (!strcasecmp(Name, "RecordDolbyDigital")) RecordDolbyDigital = atoi(Value);
- else if (!strcasecmp(Name, "ChannelInfoPos")) ChannelInfoPos = atoi(Value);
- else if (!strcasecmp(Name, "OSDwidth")) OSDwidth = atoi(Value);
- else if (!strcasecmp(Name, "OSDheight")) OSDheight = atoi(Value);
- else if (!strcasecmp(Name, "OSDMessageTime")) OSDMessageTime = atoi(Value);
- else if (!strcasecmp(Name, "MaxVideoFileSize")) MaxVideoFileSize = atoi(Value);
- else if (!strcasecmp(Name, "SplitEditedFiles")) SplitEditedFiles = atoi(Value);
- else if (!strcasecmp(Name, "MinEventTimeout")) MinEventTimeout = atoi(Value);
- else if (!strcasecmp(Name, "MinUserInactivity")) MinUserInactivity = atoi(Value);
- else if (!strcasecmp(Name, "MultiSpeedMode")) MultiSpeedMode = atoi(Value);
- else if (!strcasecmp(Name, "ShowReplayMode")) ShowReplayMode = atoi(Value);
- else if (!strcasecmp(Name, "CaCaps")) return ParseCaCaps(Value);
- else if (!strcasecmp(Name, "CurrentChannel")) CurrentChannel = atoi(Value);
- else if (!strcasecmp(Name, "CurrentVolume")) CurrentVolume = atoi(Value);
- else
- return false;
- return true;
- }
- }
- return false;
-}
-
-bool cSetup::Load(const char *FileName)
-{
- isyslog(LOG_INFO, "loading %s", FileName);
- delete fileName;
- fileName = strdup(FileName);
- FILE *f = fopen(fileName, "r");
- if (f) {
- int line = 0;
- char buffer[MAXPARSEBUFFER];
- bool result = true;
- while (fgets(buffer, sizeof(buffer), f) > 0) {
- line++;
- stripspace(buffer);
- if (!isempty(buffer)) {
- if (*buffer != '#' && !Parse(buffer)) {
- esyslog(LOG_ERR, "error in %s, line %d\n", fileName, line);
- result = false;
- }
- }
- }
- fclose(f);
- return result;
- }
+bool cSetup::Parse(const char *Name, const char *Value)
+{
+ if (!strcasecmp(Name, "OSDLanguage")) OSDLanguage = atoi(Value);
+ else if (!strcasecmp(Name, "PrimaryDVB")) PrimaryDVB = atoi(Value);
+ else if (!strcasecmp(Name, "ShowInfoOnChSwitch")) ShowInfoOnChSwitch = atoi(Value);
+ else if (!strcasecmp(Name, "MenuScrollPage")) MenuScrollPage = atoi(Value);
+ else if (!strcasecmp(Name, "MarkInstantRecord")) MarkInstantRecord = atoi(Value);
+ else if (!strcasecmp(Name, "NameInstantRecord")) strn0cpy(NameInstantRecord, Value, MaxFileName);
+ else if (!strcasecmp(Name, "InstantRecordTime")) InstantRecordTime = atoi(Value);
+ else if (!strcasecmp(Name, "LnbSLOF")) LnbSLOF = atoi(Value);
+ else if (!strcasecmp(Name, "LnbFrequLo")) LnbFrequLo = atoi(Value);
+ else if (!strcasecmp(Name, "LnbFrequHi")) LnbFrequHi = atoi(Value);
+ else if (!strcasecmp(Name, "DiSEqC")) DiSEqC = atoi(Value);
+ else if (!strcasecmp(Name, "SetSystemTime")) SetSystemTime = atoi(Value);
+ else if (!strcasecmp(Name, "TimeTransponder")) TimeTransponder = atoi(Value);
+ else if (!strcasecmp(Name, "MarginStart")) MarginStart = atoi(Value);
+ else if (!strcasecmp(Name, "MarginStop")) MarginStop = atoi(Value);
+ else if (!strcasecmp(Name, "EPGScanTimeout")) EPGScanTimeout = atoi(Value);
+ else if (!strcasecmp(Name, "EPGBugfixLevel")) EPGBugfixLevel = atoi(Value);
+ else if (!strcasecmp(Name, "SVDRPTimeout")) SVDRPTimeout = atoi(Value);
+ else if (!strcasecmp(Name, "SortTimers")) SortTimers = atoi(Value);
+ else if (!strcasecmp(Name, "PrimaryLimit")) PrimaryLimit = atoi(Value);
+ else if (!strcasecmp(Name, "DefaultPriority")) DefaultPriority = atoi(Value);
+ else if (!strcasecmp(Name, "DefaultLifetime")) DefaultLifetime = atoi(Value);
+ else if (!strcasecmp(Name, "UseSubtitle")) UseSubtitle = atoi(Value);
+ else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value);
+ else if (!strcasecmp(Name, "VideoFormat")) VideoFormat = atoi(Value);
+ else if (!strcasecmp(Name, "RecordDolbyDigital")) RecordDolbyDigital = atoi(Value);
+ else if (!strcasecmp(Name, "ChannelInfoPos")) ChannelInfoPos = atoi(Value);
+ else if (!strcasecmp(Name, "OSDwidth")) OSDwidth = atoi(Value);
+ else if (!strcasecmp(Name, "OSDheight")) OSDheight = atoi(Value);
+ else if (!strcasecmp(Name, "OSDMessageTime")) OSDMessageTime = atoi(Value);
+ else if (!strcasecmp(Name, "MaxVideoFileSize")) MaxVideoFileSize = atoi(Value);
+ else if (!strcasecmp(Name, "SplitEditedFiles")) SplitEditedFiles = atoi(Value);
+ else if (!strcasecmp(Name, "MinEventTimeout")) MinEventTimeout = atoi(Value);
+ else if (!strcasecmp(Name, "MinUserInactivity")) MinUserInactivity = atoi(Value);
+ else if (!strcasecmp(Name, "MultiSpeedMode")) MultiSpeedMode = atoi(Value);
+ else if (!strcasecmp(Name, "ShowReplayMode")) ShowReplayMode = atoi(Value);
+ else if (!strcasecmp(Name, "CaCaps")) return ParseCaCaps(Value);
+ else if (!strcasecmp(Name, "CurrentChannel")) CurrentChannel = atoi(Value);
+ else if (!strcasecmp(Name, "CurrentVolume")) CurrentVolume = atoi(Value);
else
- LOG_ERROR_STR(FileName);
- return false;
+ return false;
+ return true;
}
-bool cSetup::Save(const char *FileName)
-{
- if (!FileName)
- FileName = fileName;
- if (FileName) {
- cSafeFile f(FileName);
- if (f.Open()) {
- fprintf(f, "# VDR Setup\n");
- fprintf(f, "OSDLanguage = %d\n", OSDLanguage);
- fprintf(f, "PrimaryDVB = %d\n", PrimaryDVB);
- fprintf(f, "ShowInfoOnChSwitch = %d\n", ShowInfoOnChSwitch);
- fprintf(f, "MenuScrollPage = %d\n", MenuScrollPage);
- fprintf(f, "MarkInstantRecord = %d\n", MarkInstantRecord);
- fprintf(f, "NameInstantRecord = %s\n", NameInstantRecord);
- fprintf(f, "InstantRecordTime = %d\n", InstantRecordTime);
- fprintf(f, "LnbSLOF = %d\n", LnbSLOF);
- fprintf(f, "LnbFrequLo = %d\n", LnbFrequLo);
- fprintf(f, "LnbFrequHi = %d\n", LnbFrequHi);
- fprintf(f, "DiSEqC = %d\n", DiSEqC);
- fprintf(f, "SetSystemTime = %d\n", SetSystemTime);
- fprintf(f, "TimeTransponder = %d\n", TimeTransponder);
- fprintf(f, "MarginStart = %d\n", MarginStart);
- fprintf(f, "MarginStop = %d\n", MarginStop);
- fprintf(f, "EPGScanTimeout = %d\n", EPGScanTimeout);
- fprintf(f, "EPGBugfixLevel = %d\n", EPGBugfixLevel);
- fprintf(f, "SVDRPTimeout = %d\n", SVDRPTimeout);
- fprintf(f, "SortTimers = %d\n", SortTimers);
- fprintf(f, "PrimaryLimit = %d\n", PrimaryLimit);
- fprintf(f, "DefaultPriority = %d\n", DefaultPriority);
- fprintf(f, "DefaultLifetime = %d\n", DefaultLifetime);
- fprintf(f, "UseSubtitle = %d\n", UseSubtitle);
- fprintf(f, "RecordingDirs = %d\n", RecordingDirs);
- fprintf(f, "VideoFormat = %d\n", VideoFormat);
- fprintf(f, "RecordDolbyDigital = %d\n", RecordDolbyDigital);
- fprintf(f, "ChannelInfoPos = %d\n", ChannelInfoPos);
- fprintf(f, "OSDwidth = %d\n", OSDwidth);
- fprintf(f, "OSDheight = %d\n", OSDheight);
- fprintf(f, "OSDMessageTime = %d\n", OSDMessageTime);
- fprintf(f, "MaxVideoFileSize = %d\n", MaxVideoFileSize);
- fprintf(f, "SplitEditedFiles = %d\n", SplitEditedFiles);
- fprintf(f, "MinEventTimeout = %d\n", MinEventTimeout);
- fprintf(f, "MinUserInactivity = %d\n", MinUserInactivity);
- fprintf(f, "MultiSpeedMode = %d\n", MultiSpeedMode);
- fprintf(f, "ShowReplayMode = %d\n", ShowReplayMode);
- PrintCaCaps(f, "CaCaps");
- fprintf(f, "CurrentChannel = %d\n", CurrentChannel);
- fprintf(f, "CurrentVolume = %d\n", CurrentVolume);
- if (f.Close()) {
- isyslog(LOG_INFO, "saved setup to %s", FileName);
- return true;
- }
- }
+bool cSetup::Save(void)
+{
+ Store("OSDLanguage", OSDLanguage);
+ Store("PrimaryDVB", PrimaryDVB);
+ Store("ShowInfoOnChSwitch", ShowInfoOnChSwitch);
+ Store("MenuScrollPage", MenuScrollPage);
+ Store("MarkInstantRecord", MarkInstantRecord);
+ Store("NameInstantRecord", NameInstantRecord);
+ Store("InstantRecordTime", InstantRecordTime);
+ Store("LnbSLOF", LnbSLOF);
+ Store("LnbFrequLo", LnbFrequLo);
+ Store("LnbFrequHi", LnbFrequHi);
+ Store("DiSEqC", DiSEqC);
+ Store("SetSystemTime", SetSystemTime);
+ Store("TimeTransponder", TimeTransponder);
+ Store("MarginStart", MarginStart);
+ Store("MarginStop", MarginStop);
+ Store("EPGScanTimeout", EPGScanTimeout);
+ Store("EPGBugfixLevel", EPGBugfixLevel);
+ Store("SVDRPTimeout", SVDRPTimeout);
+ Store("SortTimers", SortTimers);
+ Store("PrimaryLimit", PrimaryLimit);
+ Store("DefaultPriority", DefaultPriority);
+ Store("DefaultLifetime", DefaultLifetime);
+ Store("UseSubtitle", UseSubtitle);
+ Store("RecordingDirs", RecordingDirs);
+ Store("VideoFormat", VideoFormat);
+ Store("RecordDolbyDigital", RecordDolbyDigital);
+ Store("ChannelInfoPos", ChannelInfoPos);
+ Store("OSDwidth", OSDwidth);
+ Store("OSDheight", OSDheight);
+ Store("OSDMessageTime", OSDMessageTime);
+ Store("MaxVideoFileSize", MaxVideoFileSize);
+ Store("SplitEditedFiles", SplitEditedFiles);
+ Store("MinEventTimeout", MinEventTimeout);
+ Store("MinUserInactivity", MinUserInactivity);
+ Store("MultiSpeedMode", MultiSpeedMode);
+ Store("ShowReplayMode", ShowReplayMode);
+ StoreCaCaps("CaCaps");
+ Store("CurrentChannel", CurrentChannel);
+ Store("CurrentVolume", CurrentVolume);
+
+ Sort();
+
+ if (cConfig<cSetupLine>::Save()) {
+ isyslog(LOG_INFO, "saved setup to %s", FileName());
+ return true;
}
- else
- esyslog(LOG_ERR, "attempt to save setup without file name");
return false;
}
-
diff --git a/config.h b/config.h
index 0f3420e0..0824553d 100644
--- a/config.h
+++ b/config.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: config.h 1.112 2002/04/26 13:56:30 kls Exp $
+ * $Id: config.h 1.113 2002/05/05 12:00:00 kls Exp $
*/
#ifndef __CONFIG_H
@@ -19,7 +19,7 @@
#include "eit.h"
#include "tools.h"
-#define VDRVERSION "1.0.2"
+#define VDRVERSION "1.1.0"
#define MAXPRIORITY 99
#define MAXLIFETIME 99
@@ -149,7 +149,7 @@ public:
cTimer(const cEventInfo *EventInfo);
virtual ~cTimer();
cTimer& operator= (const cTimer &Timer);
- bool operator< (const cTimer &Timer);
+ virtual bool operator< (const cListObject &ListObject);
const char *ToText(void);
bool Parse(const char *s);
bool Save(FILE *f);
@@ -220,7 +220,8 @@ private:
public:
cConfig(void) { fileName = NULL; }
virtual ~cConfig() { delete fileName; }
- virtual bool Load(const char *FileName, bool AllowComments = false)
+ const char *FileName(void) { return fileName; }
+ bool Load(const char *FileName, bool AllowComments = false)
{
Clear();
fileName = strdup(FileName);
@@ -323,14 +324,35 @@ extern cCommands Commands;
extern cSVDRPhosts SVDRPhosts;
extern cCaDefinitions CaDefinitions;
-class cSetup {
+class cSetupLine : public cListObject {
private:
- static char *fileName;
- void PrintCaCaps(FILE *f, const char *Name);
- bool ParseCaCaps(const char *Value);
+ char *plugin;
+ char *name;
+ char *value;
+public:
+ cSetupLine(void);
+ cSetupLine(const char *Name, const char *Value, const char *Plugin = NULL);
+ virtual ~cSetupLine();
+ virtual bool operator< (const cListObject &ListObject);
+ const char *Plugin(void) { return plugin; }
+ const char *Name(void) { return name; }
+ const char *Value(void) { return value; }
bool Parse(char *s);
+ bool Save(FILE *f);
+ };
+
+class cSetup : public cConfig<cSetupLine> {
+ friend class cPlugin; // needs to be able to call Store()
+private:
+ void StoreCaCaps(const char *Name);
+ bool ParseCaCaps(const char *Value);
+ bool Parse(const char *Name, const char *Value);
+ cSetupLine *Get(const char *Name, const char *Plugin = NULL);
+ void Store(const char *Name, const char *Value, const char *Plugin = NULL);
+ void Store(const char *Name, int Value, const char *Plugin = NULL);
public:
// Also adjust cMenuSetup (menu.c) when adding parameters here!
+ int __BeginData__;
int OSDLanguage;
int PrimaryDVB;
int ShowInfoOnChSwitch;
@@ -366,9 +388,11 @@ public:
int CaCaps[MAXDVBAPI][MAXCACAPS];
int CurrentChannel;
int CurrentVolume;
+ int __EndData__;
cSetup(void);
+ cSetup& operator= (const cSetup &s);
bool Load(const char *FileName);
- bool Save(const char *FileName = NULL);
+ bool Save(void);
};
extern cSetup Setup;
diff --git a/i18n.c b/i18n.c
index 5e44f532..4129cf7a 100644
--- a/i18n.c
+++ b/i18n.c
@@ -4,27 +4,29 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: i18n.c 1.86 2002/05/04 14:44:32 kls Exp $
+ * $Id: i18n.c 1.87 2002/05/09 13:40:51 kls Exp $
*
- * Slovenian translations provided by Miha Setina <mihasetina@softhome.net> and Matjaz Thaler <matjaz.thaler@guest.arnes.si>
- * Italian translations provided by Alberto Carraro <bertocar@tin.it>
- * Dutch translations provided by Arnold Niessen <niessen@iae.nl> <arnold.niessen@philips.com>
- * Portuguese translations provided by Paulo Lopes <pmml@netvita.pt>
- * French translations provided by Jean-Claude Repetto <jc@repetto.org>
- * Norwegian translations provided by Jørgen Tvedt <pjtvedt@online.no> and Truls Slevigen <truls@slevigen.no>
- * Finnish translations provided by Hannu Savolainen <hannu@opensound.com>
- * Polish translations provided by Michael Rakowski <mrak@gmx.de>
- * Spanish translations provided by Ruben Nunez Francisco <ruben.nunez@tang-it.com>
- * Greek translations provided by Dimitrios Dimitrakos <mail@dimitrios.de>
+ * Translations provided by:
+ *
+ * Slovenian Miha Setina <mihasetina@softhome.net> and Matjaz Thaler <matjaz.thaler@guest.arnes.si>
+ * Italian Alberto Carraro <bertocar@tin.it>
+ * Dutch Arnold Niessen <niessen@iae.nl> <arnold.niessen@philips.com>
+ * Portuguese Paulo Lopes <pmml@netvita.pt>
+ * French Jean-Claude Repetto <jc@repetto.org>
+ * Norwegian Jørgen Tvedt <pjtvedt@online.no> and Truls Slevigen <truls@slevigen.no>
+ * Finnish Hannu Savolainen <hannu@opensound.com>
+ * Polish Michael Rakowski <mrak@gmx.de>
+ * Spanish Ruben Nunez Francisco <ruben.nunez@tang-it.com>
+ * Greek Dimitrios Dimitrakos <mail@dimitrios.de>
*
*/
/*
* How to add a new language:
*
- * 1. Announce your translation action on the Linux-DVB mailing
+ * 1. Announce your translation action on the VDR mailing
* list to avoid duplicate work.
- * 2. Increase the value of 'NumLanguages'.
+ * 2. Increase the value of 'I18nNumLanguages' in 'i18n.h'.
* 3. Insert a new line in every member of the 'Phrases[]' array,
* containing the translated text for the new language.
* For example, assuming you want to add the Italian language,
@@ -46,7 +48,7 @@
* Note that only the characters defined in 'fontosd.c' will
* be available!
* 4. Compile VDR and test the new language by switching to it
- * in the "Setup" menu.
+ * in the "Setup/OSD" menu.
* 5. Send the modified 'i18n.c' file to <kls@cadsoft.de> to have
* it included in the next version of VDR.
*
@@ -60,15 +62,10 @@
*/
#include "i18n.h"
-#include <stdio.h>
#include "config.h"
#include "tools.h"
-const int NumLanguages = 12;
-
-typedef const char *tPhrase[NumLanguages];
-
-const tPhrase Phrases[] = {
+const tI18nPhrase Phrases[] = {
// The name of the language (this MUST be the first phrase!):
{ "English",
"Deutsch",
@@ -1260,6 +1257,19 @@ const tPhrase Phrases[] = {
"Varios",
"Diafora",
},
+ { "Plugins",
+ "Plugins",
+ "Plugins",
+ "Plugins",
+ "Plugins",
+ "Plugins",
+ "Plugins",
+ "Plugins",
+ "Plugins",
+ "Plugins",
+ "Plugins",
+ "Plugins",
+ },
{ "Restart",
"Neustart",
"Ponoven zagon",
@@ -2384,26 +2394,101 @@ const tPhrase Phrases[] = {
"buscando grabaciones...",
"Ginete sarosi egrafon...",
},
+ { "This plugin has no setup parameters!",
+ "Dieses Plugin hat keine Setup-Parameter!",
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ "",// TODO
+ },
{ NULL }
};
-const char *tr(const char *s)
+// --- cI18nEntry ------------------------------------------------------------
+
+class cI18nEntry : public cListObject {
+private:
+ const tI18nPhrase *phrases;
+ const char *plugin;
+public:
+ cI18nEntry(const tI18nPhrase * const Phrases, const char *Plugin);
+ const tI18nPhrase *Phrases(void) { return phrases; }
+ const char *Plugin(void) { return plugin; }
+ };
+
+cI18nEntry::cI18nEntry(const tI18nPhrase * const Phrases, const char *Plugin)
+{
+ phrases = Phrases;
+ plugin = Plugin;
+}
+
+// --- cI18nList -------------------------------------------------------------
+
+class cI18nList : public cList<cI18nEntry> {
+public:
+ cI18nEntry *Get(const char *Plugin);
+ const tI18nPhrase *GetPhrases(const char *Plugin);
+ };
+
+cI18nEntry *cI18nList::Get(const char *Plugin)
+{
+ if (Plugin) {
+ for (cI18nEntry *p = First(); p; p = Next(p)) {
+ if (strcmp(p->Plugin(), Plugin) == 0)
+ return p;
+ }
+ }
+ return NULL;
+}
+
+const tI18nPhrase *cI18nList::GetPhrases(const char *Plugin)
+{
+ cI18nEntry *p = Get(Plugin);
+ return p ? p->Phrases() : NULL;
+}
+
+cI18nList I18nList;
+
+// ---
+
+void I18nRegister(const tI18nPhrase * const Phrases, const char *Plugin)
+{
+ cI18nEntry *p = I18nList.Get(Plugin);
+ if (p)
+ I18nList.Del(p);
+ if (Phrases)
+ I18nList.Add(new cI18nEntry(Phrases, Plugin));
+}
+
+const char *I18nTranslate(const char *s, const char *Plugin)
{
if (Setup.OSDLanguage) {
- for (const tPhrase *p = Phrases; **p; p++) {
- if (strcmp(s, **p) == 0) {
- const char *t = (*p)[Setup.OSDLanguage];
- if (t && *t)
- return t;
- }
+ const tI18nPhrase *p = Plugin ? I18nList.GetPhrases(Plugin) : Phrases;
+ if (!p)
+ p = Phrases;
+ for (int i = ((p == Phrases) ? 1 : 2); i--; ) {
+ for (; **p; p++) {
+ if (strcmp(s, **p) == 0) {
+ const char *t = (*p)[Setup.OSDLanguage];
+ if (t && *t)
+ return t;
+ }
+ }
+ p = Phrases;
}
- esyslog(LOG_ERR, "no translation found for '%s' in language %d (%s)\n", s, Setup.OSDLanguage, Phrases[0][Setup.OSDLanguage]);
+ esyslog(LOG_ERR, "%s%sno translation found for '%s' in language %d (%s)\n", Plugin ? Plugin : "", Plugin ? ": " : "", s, Setup.OSDLanguage, Phrases[0][Setup.OSDLanguage]);
}
const char *p = strchr(s, '$');
return p ? p + 1 : s;
}
-const char * const * Languages(void)
+const char * const * I18nLanguages(void)
{
return &Phrases[0][0];
}
diff --git a/i18n.h b/i18n.h
index c94241a5..68fe7364 100644
--- a/i18n.h
+++ b/i18n.h
@@ -4,16 +4,28 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: i18n.h 1.1 2000/11/11 09:27:25 kls Exp $
+ * $Id: i18n.h 1.2 2002/05/05 13:42:31 kls Exp $
*/
#ifndef __I18N_H
#define __I18N_H
-extern const int NumLanguages;
+#include <stdio.h>
-const char *tr(const char *s);
+const int I18nNumLanguages = 12;
-const char * const * Languages(void);
+typedef const char *tI18nPhrase[I18nNumLanguages];
+
+void I18nRegister(const tI18nPhrase * const Phrases, const char *Plugin);
+
+const char *I18nTranslate(const char *s, const char *Plugin = NULL);
+
+const char * const * I18nLanguages(void);
+
+#ifdef PLUGIN_NAME_I18N
+#define tr(s) I18nTranslate(s, PLUGIN_NAME_I18N)
+#else
+#define tr(s) I18nTranslate(s)
+#endif
#endif //__I18N_H
diff --git a/menu.c b/menu.c
index 0eeba12c..ef758195 100644
--- a/menu.c
+++ b/menu.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: menu.c 1.189 2002/05/01 14:54:10 kls Exp $
+ * $Id: menu.c 1.190 2002/05/09 10:13:47 kls Exp $
*/
#include "menu.h"
@@ -16,6 +16,8 @@
#include "config.h"
#include "eit.h"
#include "i18n.h"
+#include "menuitems.h"
+#include "plugin.h"
#include "videodir.h"
#define MENUTIMEOUT 120 // seconds
@@ -28,125 +30,6 @@
const char *FileNameChars = " abcdefghijklmnopqrstuvwxyz0123456789-.#~";
-// --- cMenuEditItem ---------------------------------------------------------
-
-class cMenuEditItem : public cOsdItem {
-private:
- const char *name;
- const char *value;
-public:
- cMenuEditItem(const char *Name);
- ~cMenuEditItem();
- void SetValue(const char *Value);
- };
-
-cMenuEditItem::cMenuEditItem(const char *Name)
-{
- name = strdup(Name);
- value = NULL;
-}
-
-cMenuEditItem::~cMenuEditItem()
-{
- delete name;
- delete value;
-}
-
-void cMenuEditItem::SetValue(const char *Value)
-{
- delete value;
- value = strdup(Value);
- char *buffer = NULL;
- asprintf(&buffer, "%s:\t%s", name, value);
- SetText(buffer, false);
- Display();
-}
-
-// --- cMenuEditIntItem ------------------------------------------------------
-
-class cMenuEditIntItem : public cMenuEditItem {
-protected:
- int *value;
- int min, max;
- virtual void Set(void);
-public:
- cMenuEditIntItem(const char *Name, int *Value, int Min = 0, int Max = INT_MAX);
- virtual eOSState ProcessKey(eKeys Key);
- };
-
-cMenuEditIntItem::cMenuEditIntItem(const char *Name, int *Value, int Min, int Max)
-:cMenuEditItem(Name)
-{
- value = Value;
- min = Min;
- max = Max;
- Set();
-}
-
-void cMenuEditIntItem::Set(void)
-{
- char buf[16];
- snprintf(buf, sizeof(buf), "%d", *value);
- SetValue(buf);
-}
-
-eOSState cMenuEditIntItem::ProcessKey(eKeys Key)
-{
- eOSState state = cMenuEditItem::ProcessKey(Key);
-
- if (state == osUnknown) {
- int newValue;
- if (k0 <= Key && Key <= k9) {
- if (fresh) {
- *value = 0;
- fresh = false;
- }
- newValue = *value * 10 + (Key - k0);
- }
- else if (NORMALKEY(Key) == kLeft) { // TODO might want to increase the delta if repeated quickly?
- newValue = *value - 1;
- fresh = true;
- }
- else if (NORMALKEY(Key) == kRight) {
- newValue = *value + 1;
- fresh = true;
- }
- else
- return state;
- if ((!fresh || min <= newValue) && newValue <= max) {
- *value = newValue;
- Set();
- }
- state = osContinue;
- }
- return state;
-}
-
-// --- cMenuEditBoolItem -----------------------------------------------------
-
-class cMenuEditBoolItem : public cMenuEditIntItem {
-protected:
- const char *falseString, *trueString;
- virtual void Set(void);
-public:
- cMenuEditBoolItem(const char *Name, int *Value, const char *FalseString = NULL, const char *TrueString = NULL);
- };
-
-cMenuEditBoolItem::cMenuEditBoolItem(const char *Name, int *Value, const char *FalseString, const char *TrueString)
-:cMenuEditIntItem(Name, Value, 0, 1)
-{
- falseString = FalseString ? FalseString : tr("no");
- trueString = TrueString ? TrueString : tr("yes");
- Set();
-}
-
-void cMenuEditBoolItem::Set(void)
-{
- char buf[16];
- snprintf(buf, sizeof(buf), "%s", *value ? trueString : falseString);
- SetValue(buf);
-}
-
// --- cMenuEditChanItem -----------------------------------------------------
class cMenuEditChanItem : public cMenuEditIntItem {
@@ -451,282 +334,6 @@ eOSState cMenuEditTimeItem::ProcessKey(eKeys Key)
return state;
}
-// --- cMenuEditChrItem ------------------------------------------------------
-
-class cMenuEditChrItem : public cMenuEditItem {
-private:
- char *value;
- const char *allowed;
- const char *current;
- virtual void Set(void);
-public:
- cMenuEditChrItem(const char *Name, char *Value, const char *Allowed);
- ~cMenuEditChrItem();
- virtual eOSState ProcessKey(eKeys Key);
- };
-
-cMenuEditChrItem::cMenuEditChrItem(const char *Name, char *Value, const char *Allowed)
-:cMenuEditItem(Name)
-{
- value = Value;
- allowed = strdup(Allowed);
- current = strchr(allowed, *Value);
- if (!current)
- current = allowed;
- Set();
-}
-
-cMenuEditChrItem::~cMenuEditChrItem()
-{
- delete allowed;
-}
-
-void cMenuEditChrItem::Set(void)
-{
- char buf[2];
- snprintf(buf, sizeof(buf), "%c", *value);
- SetValue(buf);
-}
-
-eOSState cMenuEditChrItem::ProcessKey(eKeys Key)
-{
- eOSState state = cMenuEditItem::ProcessKey(Key);
-
- if (state == osUnknown) {
- if (NORMALKEY(Key) == kLeft) {
- if (current > allowed)
- current--;
- }
- else if (NORMALKEY(Key) == kRight) {
- if (*(current + 1))
- current++;
- }
- else
- return state;
- *value = *current;
- Set();
- state = osContinue;
- }
- return state;
-}
-
-// --- cMenuEditStrItem ------------------------------------------------------
-
-class cMenuEditStrItem : public cMenuEditItem {
-private:
- char *value;
- int length;
- const char *allowed;
- int pos;
- bool insert, newchar, uppercase;
- void SetHelpKeys(void);
- virtual void Set(void);
- char Inc(char c, bool Up);
-public:
- cMenuEditStrItem(const char *Name, char *Value, int Length, const char *Allowed);
- ~cMenuEditStrItem();
- virtual eOSState ProcessKey(eKeys Key);
- };
-
-cMenuEditStrItem::cMenuEditStrItem(const char *Name, char *Value, int Length, const char *Allowed)
-:cMenuEditItem(Name)
-{
- value = Value;
- length = Length;
- allowed = strdup(Allowed);
- pos = -1;
- insert = uppercase = false;
- newchar = true;
- Set();
-}
-
-cMenuEditStrItem::~cMenuEditStrItem()
-{
- delete allowed;
-}
-
-void cMenuEditStrItem::SetHelpKeys(void)
-{
- if (pos >= 0)
- Interface->Help(tr("ABC/abc"), tr(insert ? "Overwrite" : "Insert"), tr("Delete"));
- else
- Interface->Help(NULL);
-}
-
-void cMenuEditStrItem::Set(void)
-{
- char buf[1000];
- const char *fmt = insert && newchar ? "[]%c%s" : "[%c]%s";
-
- if (pos >= 0) {
- strncpy(buf, value, pos);
- snprintf(buf + pos, sizeof(buf) - pos - 2, fmt, *(value + pos), value + pos + 1);
- int width = Interface->Width() - Interface->GetCols()[0];
- if (cDvbApi::PrimaryDvbApi->WidthInCells(buf) <= width) {
- // the whole buffer fits on the screen
- SetValue(buf);
- return;
- }
- width *= cDvbApi::PrimaryDvbApi->CellWidth();
- width -= cDvbApi::PrimaryDvbApi->Width('>'); // assuming '<' and '>' have the same with
- int w = 0;
- int i = 0;
- int l = strlen(buf);
- while (i < l && w <= width)
- w += cDvbApi::PrimaryDvbApi->Width(buf[i++]);
- if (i >= pos + 4) {
- // the cursor fits on the screen
- buf[i - 1] = '>';
- buf[i] = 0;
- SetValue(buf);
- return;
- }
- // the cursor doesn't fit on the screen
- w = 0;
- if (buf[i = pos + 3]) {
- buf[i] = '>';
- buf[i + 1] = 0;
- }
- else
- i--;
- while (i >= 0 && w <= width)
- w += cDvbApi::PrimaryDvbApi->Width(buf[i--]);
- buf[++i] = '<';
- SetValue(buf + i);
- }
- else
- SetValue(value);
-}
-
-char cMenuEditStrItem::Inc(char c, bool Up)
-{
- const char *p = strchr(allowed, c);
- if (!p)
- p = allowed;
- if (Up) {
- if (!*++p)
- p = allowed;
- }
- else if (--p < allowed)
- p = allowed + strlen(allowed) - 1;
- return *p;
-}
-
-eOSState cMenuEditStrItem::ProcessKey(eKeys Key)
-{
- switch (Key) {
- case kRed: // Switch between upper- and lowercase characters
- if (pos >= 0 && (!insert || !newchar)) {
- uppercase = !uppercase;
- value[pos] = uppercase ? toupper(value[pos]) : tolower(value[pos]);
- }
- break;
- case kGreen: // Toggle insert/overwrite modes
- if (pos >= 0) {
- insert = !insert;
- newchar = true;
- }
- SetHelpKeys();
- break;
- case kYellow|k_Repeat:
- case kYellow: // Remove the character at current position; in insert mode it is the character to the right of cursor
- if (pos >= 0) {
- if (strlen(value) > 1) {
- memmove(value + pos, value + pos + 1, strlen(value) - pos);
- // reduce position, if we removed the last character
- if (pos == int(strlen(value)))
- pos--;
- }
- else if (strlen(value) == 1)
- value[0] = ' '; // This is the last character in the string, replace it with a blank
- if (isalpha(value[pos]))
- uppercase = isupper(value[pos]);
- newchar = true;
- }
- break;
- case kLeft|k_Repeat:
- case kLeft: if (pos > 0) {
- if (!insert || newchar)
- pos--;
- newchar = true;
- }
- if (!insert && isalpha(value[pos]))
- uppercase = isupper(value[pos]);
- break;
- case kRight|k_Repeat:
- case kRight: if (pos < length && pos < int(strlen(value)) ) {
- if (++pos >= int(strlen(value))) {
- if (pos >= 2 && value[pos - 1] == ' ' && value[pos - 2] == ' ')
- pos--; // allow only two blanks at the end
- else {
- value[pos] = ' ';
- value[pos + 1] = 0;
- }
- }
- }
- newchar = true;
- if (!insert && isalpha(value[pos]))
- uppercase = isupper(value[pos]);
- if (pos == 0)
- SetHelpKeys();
- break;
- case kUp|k_Repeat:
- case kUp:
- case kDown|k_Repeat:
- case kDown: if (pos >= 0) {
- if (insert && newchar) {
- // create a new character in insert mode
- if (int(strlen(value)) < length) {
- memmove(value + pos + 1, value + pos, strlen(value) - pos + 1);
- value[pos] = ' ';
- }
- }
- if (uppercase)
- value[pos] = toupper(Inc(tolower(value[pos]), NORMALKEY(Key) == kUp));
- else
- value[pos] = Inc( value[pos], NORMALKEY(Key) == kUp);
- newchar = false;
- }
- else
- return cMenuEditItem::ProcessKey(Key);
- break;
- case kOk: if (pos >= 0) {
- pos = -1;
- newchar = true;
- stripspace(value);
- SetHelpKeys();
- break;
- }
- // run into default
- default: return cMenuEditItem::ProcessKey(Key);
- }
- Set();
- return osContinue;
-}
-
-// --- cMenuEditStraItem -----------------------------------------------------
-
-class cMenuEditStraItem : public cMenuEditIntItem {
-private:
- const char * const *strings;
-protected:
- virtual void Set(void);
-public:
- cMenuEditStraItem(const char *Name, int *Value, int NumStrings, const char * const *Strings);
- };
-
-cMenuEditStraItem::cMenuEditStraItem(const char *Name, int *Value, int NumStrings, const char * const *Strings)
-:cMenuEditIntItem(Name, Value, 0, NumStrings - 1)
-{
- strings = Strings;
- Set();
-}
-
-void cMenuEditStraItem::Set(void)
-{
- SetValue(strings[*value]);
-}
-
// --- cMenuEditCaItem -------------------------------------------------------
class cMenuEditCaItem : public cMenuEditIntItem {
@@ -1018,116 +625,6 @@ eOSState cMenuChannels::ProcessKey(eKeys Key)
return state;
}
-// --- cMenuTextItem ---------------------------------------------------------
-
-class cMenuTextItem : public cOsdItem {
-private:
- char *text;
- int x, y, w, h, lines, offset;
- eDvbColor fgColor, bgColor;
- eDvbFont font;
-public:
- cMenuTextItem(const char *Text, int X, int Y, int W, int H = -1, eDvbColor FgColor = clrWhite, eDvbColor BgColor = clrBackground, eDvbFont Font = fontOsd);
- ~cMenuTextItem();
- int Height(void) { return h; }
- void Clear(void);
- virtual void Display(int Offset = -1, eDvbColor FgColor = clrWhite, eDvbColor BgColor = clrBackground);
- bool CanScrollUp(void) { return offset > 0; }
- bool CanScrollDown(void) { return h + offset < lines; }
- void ScrollUp(bool Page);
- void ScrollDown(bool Page);
- virtual eOSState ProcessKey(eKeys Key);
- };
-
-cMenuTextItem::cMenuTextItem(const char *Text, int X, int Y, int W, int H, eDvbColor FgColor, eDvbColor BgColor, eDvbFont Font)
-{
- x = X;
- y = Y;
- w = W;
- h = H;
- fgColor = FgColor;
- bgColor = BgColor;
- font = Font;
- offset = 0;
- eDvbFont oldFont = Interface->SetFont(font);
- text = Interface->WrapText(Text, w - 1, &lines);
- Interface->SetFont(oldFont);
- if (h < 0)
- h = lines;
-}
-
-cMenuTextItem::~cMenuTextItem()
-{
- delete text;
-}
-
-void cMenuTextItem::Clear(void)
-{
- Interface->Fill(x, y, w, h, bgColor);
-}
-
-void cMenuTextItem::Display(int Offset, eDvbColor FgColor, eDvbColor BgColor)
-{
- int l = 0;
- char *t = text;
- eDvbFont oldFont = Interface->SetFont(font);
- while (*t) {
- char *n = strchr(t, '\n');
- if (l >= offset) {
- if (n)
- *n = 0;
- Interface->Write(x, y + l - offset, t, fgColor, bgColor);
- if (n)
- *n = '\n';
- else
- break;
- }
- if (!n)
- break;
- t = n + 1;
- if (++l >= h + offset)
- break;
- }
- Interface->SetFont(oldFont);
- // scroll indicators use inverted color scheme!
- if (CanScrollUp()) Interface->Write(x + w - 1, y, "^", bgColor, fgColor);
- if (CanScrollDown()) Interface->Write(x + w - 1, y + h - 1, "v", bgColor, fgColor);
-}
-
-void cMenuTextItem::ScrollUp(bool Page)
-{
- if (CanScrollUp()) {
- Clear();
- offset = max(offset - (Page ? h : 1), 0);
- Display();
- }
-}
-
-void cMenuTextItem::ScrollDown(bool Page)
-{
- if (CanScrollDown()) {
- Clear();
- offset = min(offset + (Page ? h : 1), lines - h);
- Display();
- }
-}
-
-eOSState cMenuTextItem::ProcessKey(eKeys Key)
-{
- switch (Key) {
- case kLeft|k_Repeat:
- case kLeft:
- case kUp|k_Repeat:
- case kUp: ScrollUp(NORMALKEY(Key) == kLeft); break;
- case kRight|k_Repeat:
- case kRight:
- case kDown|k_Repeat:
- case kDown: ScrollDown(NORMALKEY(Key) == kRight); break;
- default: return osUnknown;
- }
- return osContinue;
-}
-
// --- cMenuText -------------------------------------------------------------
class cMenuText : public cOsdMenu {
@@ -2028,60 +1525,6 @@ eOSState cMenuRecordings::ProcessKey(eKeys Key)
return state;
}
-// --- cMenuSetupPage --------------------------------------------------------
-
-class cMenuSetupPage : public cOsdMenu {
-protected:
- cSetup data;
- int osdLanguage;
- void SetupTitle(const char *s);
- virtual void Set(void) = 0;
-public:
- cMenuSetupPage(void);
- virtual eOSState ProcessKey(eKeys Key);
- };
-
-cMenuSetupPage::cMenuSetupPage(void)
-:cOsdMenu("", 33)
-{
- data = Setup;
- osdLanguage = Setup.OSDLanguage;
-}
-
-void cMenuSetupPage::SetupTitle(const char *s)
-{
- char buf[40]; // can't call tr() for more than one string at a time!
- char *q = buf + snprintf(buf, sizeof(buf), "%s - ", tr("Setup"));
- snprintf(q, sizeof(buf) - strlen(buf), "%s", tr(s));
- SetTitle(buf);
-}
-
-eOSState cMenuSetupPage::ProcessKey(eKeys Key)
-{
- eOSState state = cOsdMenu::ProcessKey(Key);
-
- if (state == osUnknown) {
- switch (Key) {
- case kOk: state = (Setup.PrimaryDVB != data.PrimaryDVB) ? osSwitchDvb : osBack;
- cDvbApi::PrimaryDvbApi->SetVideoFormat(data.VideoFormat ? VIDEO_FORMAT_16_9 : VIDEO_FORMAT_4_3);
- Setup = data;
- Setup.Save();
- cDvbApi::SetCaCaps();
- break;
- default: break;
- }
- }
- if (data.OSDLanguage != osdLanguage) {
- int OriginalOSDLanguage = Setup.OSDLanguage;
- Setup.OSDLanguage = data.OSDLanguage;
- Set();
- Display();
- osdLanguage = data.OSDLanguage;
- Setup.OSDLanguage = OriginalOSDLanguage;
- }
- return state;
-}
-
// --- cMenuSetupOSD ---------------------------------------------------------
class cMenuSetupOSD : public cMenuSetupPage {
@@ -2095,7 +1538,7 @@ void cMenuSetupOSD::Set(void)
{
Clear();
SetupTitle("OSD");
- Add(new cMenuEditStraItem(tr("Setup.OSD$Language"), &data.OSDLanguage, NumLanguages, Languages()));
+ Add(new cMenuEditStraItem(tr("Setup.OSD$Language"), &data.OSDLanguage, I18nNumLanguages, I18nLanguages()));
Add(new cMenuEditIntItem( tr("Setup.OSD$Width"), &data.OSDwidth, MINOSDWIDTH, MAXOSDWIDTH));
Add(new cMenuEditIntItem( tr("Setup.OSD$Height"), &data.OSDheight, MINOSDHEIGHT, MAXOSDHEIGHT));
Add(new cMenuEditIntItem( tr("Setup.OSD$Message time (s)"), &data.OSDMessageTime, 1, 60));
@@ -2245,6 +1688,75 @@ void cMenuSetupMisc::Set(void)
Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$SVDRP timeout (s)"), &data.SVDRPTimeout));
}
+// --- cMenuSetupPluginItem --------------------------------------------------
+
+class cMenuSetupPluginItem : public cOsdItem {
+private:
+ int pluginIndex;
+public:
+ cMenuSetupPluginItem(const char *Name, int Index);
+ int PluginIndex(void) { return pluginIndex; }
+ };
+
+cMenuSetupPluginItem::cMenuSetupPluginItem(const char *Name, int Index)
+:cOsdItem(Name)
+{
+ pluginIndex = Index;
+}
+
+// --- cMenuSetupPlugins -----------------------------------------------------
+
+class cMenuSetupPlugins : public cMenuSetupPage {
+private:
+ virtual void Set(void);
+public:
+ cMenuSetupPlugins(void) { Set(); }
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+
+void cMenuSetupPlugins::Set(void)
+{
+ Clear();
+ SetupTitle("Plugins");
+ SetHasHotkeys();
+ for (int i = 0; ; i++) {
+ cPlugin *p = cPluginManager::GetPlugin(i);
+ if (p) {
+ char *buffer = NULL;
+ asprintf(&buffer, "%s (%s) - %s", p->Name(), p->Version(), p->Description());
+ Add(new cMenuSetupPluginItem(hk(buffer), i));
+ delete buffer;
+ }
+ else
+ break;
+ }
+}
+
+eOSState cMenuSetupPlugins::ProcessKey(eKeys Key)
+{
+ eOSState state = cOsdMenu::ProcessKey(Key); // not cMenuSetupPage::ProcessKey()!
+
+ if (state == osUnknown) {
+ switch (Key) {
+ case kOk: {
+ cMenuSetupPluginItem *item = (cMenuSetupPluginItem *)Get(Current());
+ if (item) {
+ cPlugin *p = cPluginManager::GetPlugin(item->PluginIndex());
+ if (p) {
+ cOsdMenu *menu = p->SetupMenu();
+ if (menu)
+ return AddSubMenu(menu);
+ Interface->Info(tr("This plugin has no setup parameters!"));
+ }
+ }
+ }
+ break;
+ default: break;
+ }
+ }
+ return state;
+}
+
// --- cMenuSetup ------------------------------------------------------------
class cMenuSetup : public cOsdMenu {
@@ -2265,7 +1777,9 @@ cMenuSetup::cMenuSetup(void)
void cMenuSetup::Set(void)
{
Clear();
- SetTitle(tr("Setup"));
+ char buffer[64];
+ snprintf(buffer, sizeof(buffer), "%s - VDR %s", tr("Setup"), VDRVERSION);
+ SetTitle(buffer);
SetHasHotkeys();
Add(new cOsdItem(hk(tr("OSD")), osUser1));
Add(new cOsdItem(hk(tr("EPG")), osUser2));
@@ -2275,7 +1789,9 @@ void cMenuSetup::Set(void)
Add(new cOsdItem(hk(tr("Recording")), osUser6));
Add(new cOsdItem(hk(tr("Replay")), osUser7));
Add(new cOsdItem(hk(tr("Miscellaneous")), osUser8));
- Add(new cOsdItem(hk(tr("Restart")), osUser9));
+ if (cPluginManager::HasPlugins())
+ Add(new cOsdItem(hk(tr("Plugins")), osUser9));
+ Add(new cOsdItem(hk(tr("Restart")), osUser10));
}
eOSState cMenuSetup::Restart(void)
@@ -2301,7 +1817,8 @@ eOSState cMenuSetup::ProcessKey(eKeys Key)
case osUser6: return AddSubMenu(new cMenuSetupRecord);
case osUser7: return AddSubMenu(new cMenuSetupReplay);
case osUser8: return AddSubMenu(new cMenuSetupMisc);
- case osUser9: return Restart();
+ case osUser9: return AddSubMenu(new cMenuSetupPlugins);
+ case osUser10: return Restart();
default: ;
}
if (Setup.OSDLanguage != osdLanguage) {
@@ -2364,6 +1881,22 @@ eOSState cMenuCommands::ProcessKey(eKeys Key)
return state;
}
+// --- cMenuPluginItem -------------------------------------------------------
+
+class cMenuPluginItem : public cOsdItem {
+private:
+ int pluginIndex;
+public:
+ cMenuPluginItem(const char *Name, int Index);
+ int PluginIndex(void) { return pluginIndex; }
+ };
+
+cMenuPluginItem::cMenuPluginItem(const char *Name, int Index)
+:cOsdItem(Name, osPlugin)
+{
+ pluginIndex = Index;
+}
+
// --- cMenuMain -------------------------------------------------------------
#define STOP_RECORDING tr(" Stop recording ")
@@ -2408,6 +1941,22 @@ void cMenuMain::Set(void)
Add(new cOsdItem(hk(tr("Channels")), osChannels));
Add(new cOsdItem(hk(tr("Timers")), osTimers));
Add(new cOsdItem(hk(tr("Recordings")), osRecordings));
+
+ // Plugins:
+
+ for (int i = 0; ; i++) {
+ cPlugin *p = cPluginManager::GetPlugin(i);
+ if (p) {
+ const char *item = p->MainMenuEntry();
+ if (item)
+ Add(new cMenuPluginItem(hk(item), i));
+ }
+ else
+ break;
+ }
+
+ // More basic menu items:
+
Add(new cOsdItem(hk(tr("Setup")), osSetup));
if (Commands.Count())
Add(new cOsdItem(hk(tr("Commands")), osCommands));
@@ -2475,6 +2024,19 @@ eOSState cMenuMain::ProcessKey(eKeys Key)
return osEnd;
}
break;
+ case osPlugin: {
+ cMenuPluginItem *item = (cMenuPluginItem *)Get(Current());
+ if (item) {
+ cPlugin *p = cPluginManager::GetPlugin(item->PluginIndex());
+ if (p) {
+ cOsdMenu *menu = p->MainMenuAction();
+ if (menu)
+ return AddSubMenu(menu);
+ }
+ }
+ state = osEnd;
+ }
+ break;
default: switch (Key) {
case kMenu: state = osEnd; break;
case kRed: if (!HasSubMenu())
diff --git a/menuitems.c b/menuitems.c
new file mode 100644
index 00000000..d09f5b70
--- /dev/null
+++ b/menuitems.c
@@ -0,0 +1,476 @@
+/*
+ * menuitems.c: General purpose menu items
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: menuitems.c 1.1 2002/05/09 10:10:12 kls Exp $
+ */
+
+#include "menuitems.h"
+#include <ctype.h>
+#include "i18n.h"
+
+// --- cMenuEditItem ---------------------------------------------------------
+
+cMenuEditItem::cMenuEditItem(const char *Name)
+{
+ name = strdup(Name);
+ value = NULL;
+}
+
+cMenuEditItem::~cMenuEditItem()
+{
+ delete name;
+ delete value;
+}
+
+void cMenuEditItem::SetValue(const char *Value)
+{
+ delete value;
+ value = strdup(Value);
+ char *buffer = NULL;
+ asprintf(&buffer, "%s:\t%s", name, value);
+ SetText(buffer, false);
+ Display();
+}
+
+// --- cMenuEditIntItem ------------------------------------------------------
+
+cMenuEditIntItem::cMenuEditIntItem(const char *Name, int *Value, int Min, int Max)
+:cMenuEditItem(Name)
+{
+ value = Value;
+ min = Min;
+ max = Max;
+ Set();
+}
+
+void cMenuEditIntItem::Set(void)
+{
+ char buf[16];
+ snprintf(buf, sizeof(buf), "%d", *value);
+ SetValue(buf);
+}
+
+eOSState cMenuEditIntItem::ProcessKey(eKeys Key)
+{
+ eOSState state = cMenuEditItem::ProcessKey(Key);
+
+ if (state == osUnknown) {
+ int newValue;
+ if (k0 <= Key && Key <= k9) {
+ if (fresh) {
+ *value = 0;
+ fresh = false;
+ }
+ newValue = *value * 10 + (Key - k0);
+ }
+ else if (NORMALKEY(Key) == kLeft) { // TODO might want to increase the delta if repeated quickly?
+ newValue = *value - 1;
+ fresh = true;
+ }
+ else if (NORMALKEY(Key) == kRight) {
+ newValue = *value + 1;
+ fresh = true;
+ }
+ else
+ return state;
+ if ((!fresh || min <= newValue) && newValue <= max) {
+ *value = newValue;
+ Set();
+ }
+ state = osContinue;
+ }
+ return state;
+}
+
+// --- cMenuEditBoolItem -----------------------------------------------------
+
+cMenuEditBoolItem::cMenuEditBoolItem(const char *Name, int *Value, const char *FalseString, const char *TrueString)
+:cMenuEditIntItem(Name, Value, 0, 1)
+{
+ falseString = FalseString ? FalseString : tr("no");
+ trueString = TrueString ? TrueString : tr("yes");
+ Set();
+}
+
+void cMenuEditBoolItem::Set(void)
+{
+ char buf[16];
+ snprintf(buf, sizeof(buf), "%s", *value ? trueString : falseString);
+ SetValue(buf);
+}
+
+// --- cMenuEditChrItem ------------------------------------------------------
+
+cMenuEditChrItem::cMenuEditChrItem(const char *Name, char *Value, const char *Allowed)
+:cMenuEditItem(Name)
+{
+ value = Value;
+ allowed = strdup(Allowed);
+ current = strchr(allowed, *Value);
+ if (!current)
+ current = allowed;
+ Set();
+}
+
+cMenuEditChrItem::~cMenuEditChrItem()
+{
+ delete allowed;
+}
+
+void cMenuEditChrItem::Set(void)
+{
+ char buf[2];
+ snprintf(buf, sizeof(buf), "%c", *value);
+ SetValue(buf);
+}
+
+eOSState cMenuEditChrItem::ProcessKey(eKeys Key)
+{
+ eOSState state = cMenuEditItem::ProcessKey(Key);
+
+ if (state == osUnknown) {
+ if (NORMALKEY(Key) == kLeft) {
+ if (current > allowed)
+ current--;
+ }
+ else if (NORMALKEY(Key) == kRight) {
+ if (*(current + 1))
+ current++;
+ }
+ else
+ return state;
+ *value = *current;
+ Set();
+ state = osContinue;
+ }
+ return state;
+}
+
+// --- cMenuEditStrItem ------------------------------------------------------
+
+cMenuEditStrItem::cMenuEditStrItem(const char *Name, char *Value, int Length, const char *Allowed)
+:cMenuEditItem(Name)
+{
+ value = Value;
+ length = Length;
+ allowed = strdup(Allowed);
+ pos = -1;
+ insert = uppercase = false;
+ newchar = true;
+ Set();
+}
+
+cMenuEditStrItem::~cMenuEditStrItem()
+{
+ delete allowed;
+}
+
+void cMenuEditStrItem::SetHelpKeys(void)
+{
+ if (pos >= 0)
+ Interface->Help(tr("ABC/abc"), tr(insert ? "Overwrite" : "Insert"), tr("Delete"));
+ else
+ Interface->Help(NULL);
+}
+
+void cMenuEditStrItem::Set(void)
+{
+ char buf[1000];
+ const char *fmt = insert && newchar ? "[]%c%s" : "[%c]%s";
+
+ if (pos >= 0) {
+ strncpy(buf, value, pos);
+ snprintf(buf + pos, sizeof(buf) - pos - 2, fmt, *(value + pos), value + pos + 1);
+ int width = Interface->Width() - Interface->GetCols()[0];
+ if (cDvbApi::PrimaryDvbApi->WidthInCells(buf) <= width) {
+ // the whole buffer fits on the screen
+ SetValue(buf);
+ return;
+ }
+ width *= cDvbApi::PrimaryDvbApi->CellWidth();
+ width -= cDvbApi::PrimaryDvbApi->Width('>'); // assuming '<' and '>' have the same with
+ int w = 0;
+ int i = 0;
+ int l = strlen(buf);
+ while (i < l && w <= width)
+ w += cDvbApi::PrimaryDvbApi->Width(buf[i++]);
+ if (i >= pos + 4) {
+ // the cursor fits on the screen
+ buf[i - 1] = '>';
+ buf[i] = 0;
+ SetValue(buf);
+ return;
+ }
+ // the cursor doesn't fit on the screen
+ w = 0;
+ if (buf[i = pos + 3]) {
+ buf[i] = '>';
+ buf[i + 1] = 0;
+ }
+ else
+ i--;
+ while (i >= 0 && w <= width)
+ w += cDvbApi::PrimaryDvbApi->Width(buf[i--]);
+ buf[++i] = '<';
+ SetValue(buf + i);
+ }
+ else
+ SetValue(value);
+}
+
+char cMenuEditStrItem::Inc(char c, bool Up)
+{
+ const char *p = strchr(allowed, c);
+ if (!p)
+ p = allowed;
+ if (Up) {
+ if (!*++p)
+ p = allowed;
+ }
+ else if (--p < allowed)
+ p = allowed + strlen(allowed) - 1;
+ return *p;
+}
+
+eOSState cMenuEditStrItem::ProcessKey(eKeys Key)
+{
+ switch (Key) {
+ case kRed: // Switch between upper- and lowercase characters
+ if (pos >= 0 && (!insert || !newchar)) {
+ uppercase = !uppercase;
+ value[pos] = uppercase ? toupper(value[pos]) : tolower(value[pos]);
+ }
+ break;
+ case kGreen: // Toggle insert/overwrite modes
+ if (pos >= 0) {
+ insert = !insert;
+ newchar = true;
+ }
+ SetHelpKeys();
+ break;
+ case kYellow|k_Repeat:
+ case kYellow: // Remove the character at current position; in insert mode it is the character to the right of cursor
+ if (pos >= 0) {
+ if (strlen(value) > 1) {
+ memmove(value + pos, value + pos + 1, strlen(value) - pos);
+ // reduce position, if we removed the last character
+ if (pos == int(strlen(value)))
+ pos--;
+ }
+ else if (strlen(value) == 1)
+ value[0] = ' '; // This is the last character in the string, replace it with a blank
+ if (isalpha(value[pos]))
+ uppercase = isupper(value[pos]);
+ newchar = true;
+ }
+ break;
+ case kLeft|k_Repeat:
+ case kLeft: if (pos > 0) {
+ if (!insert || newchar)
+ pos--;
+ newchar = true;
+ }
+ if (!insert && isalpha(value[pos]))
+ uppercase = isupper(value[pos]);
+ break;
+ case kRight|k_Repeat:
+ case kRight: if (pos < length && pos < int(strlen(value)) ) {
+ if (++pos >= int(strlen(value))) {
+ if (pos >= 2 && value[pos - 1] == ' ' && value[pos - 2] == ' ')
+ pos--; // allow only two blanks at the end
+ else {
+ value[pos] = ' ';
+ value[pos + 1] = 0;
+ }
+ }
+ }
+ newchar = true;
+ if (!insert && isalpha(value[pos]))
+ uppercase = isupper(value[pos]);
+ if (pos == 0)
+ SetHelpKeys();
+ break;
+ case kUp|k_Repeat:
+ case kUp:
+ case kDown|k_Repeat:
+ case kDown: if (pos >= 0) {
+ if (insert && newchar) {
+ // create a new character in insert mode
+ if (int(strlen(value)) < length) {
+ memmove(value + pos + 1, value + pos, strlen(value) - pos + 1);
+ value[pos] = ' ';
+ }
+ }
+ if (uppercase)
+ value[pos] = toupper(Inc(tolower(value[pos]), NORMALKEY(Key) == kUp));
+ else
+ value[pos] = Inc( value[pos], NORMALKEY(Key) == kUp);
+ newchar = false;
+ }
+ else
+ return cMenuEditItem::ProcessKey(Key);
+ break;
+ case kOk: if (pos >= 0) {
+ pos = -1;
+ newchar = true;
+ stripspace(value);
+ SetHelpKeys();
+ break;
+ }
+ // run into default
+ default: return cMenuEditItem::ProcessKey(Key);
+ }
+ Set();
+ return osContinue;
+}
+
+// --- cMenuEditStraItem -----------------------------------------------------
+
+cMenuEditStraItem::cMenuEditStraItem(const char *Name, int *Value, int NumStrings, const char * const *Strings)
+:cMenuEditIntItem(Name, Value, 0, NumStrings - 1)
+{
+ strings = Strings;
+ Set();
+}
+
+void cMenuEditStraItem::Set(void)
+{
+ SetValue(strings[*value]);
+}
+
+// --- cMenuTextItem ---------------------------------------------------------
+
+cMenuTextItem::cMenuTextItem(const char *Text, int X, int Y, int W, int H, eDvbColor FgColor, eDvbColor BgColor, eDvbFont Font)
+{
+ x = X;
+ y = Y;
+ w = W;
+ h = H;
+ fgColor = FgColor;
+ bgColor = BgColor;
+ font = Font;
+ offset = 0;
+ eDvbFont oldFont = Interface->SetFont(font);
+ text = Interface->WrapText(Text, w - 1, &lines);
+ Interface->SetFont(oldFont);
+ if (h < 0)
+ h = lines;
+}
+
+cMenuTextItem::~cMenuTextItem()
+{
+ delete text;
+}
+
+void cMenuTextItem::Clear(void)
+{
+ Interface->Fill(x, y, w, h, bgColor);
+}
+
+void cMenuTextItem::Display(int Offset, eDvbColor FgColor, eDvbColor BgColor)
+{
+ int l = 0;
+ char *t = text;
+ eDvbFont oldFont = Interface->SetFont(font);
+ while (*t) {
+ char *n = strchr(t, '\n');
+ if (l >= offset) {
+ if (n)
+ *n = 0;
+ Interface->Write(x, y + l - offset, t, fgColor, bgColor);
+ if (n)
+ *n = '\n';
+ else
+ break;
+ }
+ if (!n)
+ break;
+ t = n + 1;
+ if (++l >= h + offset)
+ break;
+ }
+ Interface->SetFont(oldFont);
+ // scroll indicators use inverted color scheme!
+ if (CanScrollUp()) Interface->Write(x + w - 1, y, "^", bgColor, fgColor);
+ if (CanScrollDown()) Interface->Write(x + w - 1, y + h - 1, "v", bgColor, fgColor);
+}
+
+void cMenuTextItem::ScrollUp(bool Page)
+{
+ if (CanScrollUp()) {
+ Clear();
+ offset = max(offset - (Page ? h : 1), 0);
+ Display();
+ }
+}
+
+void cMenuTextItem::ScrollDown(bool Page)
+{
+ if (CanScrollDown()) {
+ Clear();
+ offset = min(offset + (Page ? h : 1), lines - h);
+ Display();
+ }
+}
+
+eOSState cMenuTextItem::ProcessKey(eKeys Key)
+{
+ switch (Key) {
+ case kLeft|k_Repeat:
+ case kLeft:
+ case kUp|k_Repeat:
+ case kUp: ScrollUp(NORMALKEY(Key) == kLeft); break;
+ case kRight|k_Repeat:
+ case kRight:
+ case kDown|k_Repeat:
+ case kDown: ScrollDown(NORMALKEY(Key) == kRight); break;
+ default: return osUnknown;
+ }
+ return osContinue;
+}
+
+// --- cMenuSetupPage --------------------------------------------------------
+
+cMenuSetupPage::cMenuSetupPage(void)
+:cOsdMenu("", 33)
+{
+ data = Setup;
+ osdLanguage = Setup.OSDLanguage;
+}
+
+void cMenuSetupPage::SetupTitle(const char *s)
+{
+ char buf[40]; // can't call tr() for more than one string at a time!
+ char *q = buf + snprintf(buf, sizeof(buf), "%s - ", tr("Setup"));
+ snprintf(q, sizeof(buf) - strlen(buf), "%s", tr(s));
+ SetTitle(buf);
+}
+
+eOSState cMenuSetupPage::ProcessKey(eKeys Key)
+{
+ eOSState state = cOsdMenu::ProcessKey(Key);
+
+ if (state == osUnknown) {
+ switch (Key) {
+ case kOk: state = (Setup.PrimaryDVB != data.PrimaryDVB) ? osSwitchDvb : osBack;
+ cDvbApi::PrimaryDvbApi->SetVideoFormat(data.VideoFormat ? VIDEO_FORMAT_16_9 : VIDEO_FORMAT_4_3);
+ Setup = data;
+ Setup.Save();
+ cDvbApi::SetCaCaps();
+ break;
+ default: break;
+ }
+ }
+ if (data.OSDLanguage != osdLanguage) {
+ int OriginalOSDLanguage = Setup.OSDLanguage;
+ Setup.OSDLanguage = data.OSDLanguage;
+ Set();
+ Display();
+ osdLanguage = data.OSDLanguage;
+ Setup.OSDLanguage = OriginalOSDLanguage;
+ }
+ return state;
+}
diff --git a/menuitems.h b/menuitems.h
new file mode 100644
index 00000000..a08a7148
--- /dev/null
+++ b/menuitems.h
@@ -0,0 +1,110 @@
+/*
+ * menuitems.h: General purpose menu items
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: menuitems.h 1.1 2002/05/09 09:41:06 kls Exp $
+ */
+
+#ifndef __MENUITEMS_H
+#define __MENUITEMS_H
+
+#include "osd.h"
+
+class cMenuEditItem : public cOsdItem {
+private:
+ const char *name;
+ const char *value;
+public:
+ cMenuEditItem(const char *Name);
+ ~cMenuEditItem();
+ void SetValue(const char *Value);
+ };
+
+class cMenuEditIntItem : public cMenuEditItem {
+protected:
+ int *value;
+ int min, max;
+ virtual void Set(void);
+public:
+ cMenuEditIntItem(const char *Name, int *Value, int Min = 0, int Max = INT_MAX);
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+
+class cMenuEditBoolItem : public cMenuEditIntItem {
+protected:
+ const char *falseString, *trueString;
+ virtual void Set(void);
+public:
+ cMenuEditBoolItem(const char *Name, int *Value, const char *FalseString = NULL, const char *TrueString = NULL);
+ };
+
+class cMenuEditChrItem : public cMenuEditItem {
+private:
+ char *value;
+ const char *allowed;
+ const char *current;
+ virtual void Set(void);
+public:
+ cMenuEditChrItem(const char *Name, char *Value, const char *Allowed);
+ ~cMenuEditChrItem();
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+
+class cMenuEditStrItem : public cMenuEditItem {
+private:
+ char *value;
+ int length;
+ const char *allowed;
+ int pos;
+ bool insert, newchar, uppercase;
+ void SetHelpKeys(void);
+ virtual void Set(void);
+ char Inc(char c, bool Up);
+public:
+ cMenuEditStrItem(const char *Name, char *Value, int Length, const char *Allowed);
+ ~cMenuEditStrItem();
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+
+class cMenuEditStraItem : public cMenuEditIntItem {
+private:
+ const char * const *strings;
+protected:
+ virtual void Set(void);
+public:
+ cMenuEditStraItem(const char *Name, int *Value, int NumStrings, const char * const *Strings);
+ };
+
+class cMenuTextItem : public cOsdItem {
+private:
+ char *text;
+ int x, y, w, h, lines, offset;
+ eDvbColor fgColor, bgColor;
+ eDvbFont font;
+public:
+ cMenuTextItem(const char *Text, int X, int Y, int W, int H = -1, eDvbColor FgColor = clrWhite, eDvbColor BgColor = clrBackground, eDvbFont Font = fontOsd);
+ ~cMenuTextItem();
+ int Height(void) { return h; }
+ void Clear(void);
+ virtual void Display(int Offset = -1, eDvbColor FgColor = clrWhite, eDvbColor BgColor = clrBackground);
+ bool CanScrollUp(void) { return offset > 0; }
+ bool CanScrollDown(void) { return h + offset < lines; }
+ void ScrollUp(bool Page);
+ void ScrollDown(bool Page);
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+
+class cMenuSetupPage : public cOsdMenu {
+protected:
+ cSetup data;
+ int osdLanguage;
+ void SetupTitle(const char *s);
+ virtual void Set(void) = 0;
+public:
+ cMenuSetupPage(void);
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+
+#endif //__MENUITEMS_H
diff --git a/newplugin b/newplugin
new file mode 100755
index 00000000..7100bcd5
--- /dev/null
+++ b/newplugin
@@ -0,0 +1,250 @@
+#!/usr/bin/perl -w
+
+# newplugin: Initializing a new plugin source directory
+#
+# Creates a new plugin source directory from which to start implementing
+# a plugin for VDR.
+# See the file PLUGINS.html for detailed instructions on how to
+# write a plugin.
+#
+# Usage: newplugin <name>
+#
+# See the main source file 'vdr.c' for copyright information and
+# how to reach the author.
+#
+# $Id: newplugin 1.1 2002/05/09 15:12:26 kls Exp $
+
+$PLUGIN_NAME = $ARGV[0] || die "Usage: newplugin <name>\n";
+
+die "Please use only lowercase letters and digits in the plugin name\n" if ($PLUGIN_NAME =~ tr/a-z0-9//c);
+
+$PLUGIN_CLASS = ucfirst($PLUGIN_NAME);
+
+$PLUGIN_VERSION = "0.0.1";
+$PLUGIN_DESCRIPTION = "Enter description for '$PLUGIN_NAME' plugin";
+$PLUGIN_MAINENTRY = $PLUGIN_CLASS;
+
+$PLUGINS_SRC = "PLUGINS/src";
+
+$README = qq
+{This is a "plugin" for the Video Disk Recorder (VDR).
+
+Written by: Your Name <email\@host.dom>
+
+Project's homepage: URL
+
+Latest version available at: URL
+
+See the file COPYING for license information.
+
+Description:
+};
+
+$HISTORY_TITLE = "VDR Plugin '$PLUGIN_NAME' Revision History";
+$HISTORY_LINE = '-' x length($HISTORY_TITLE);
+$HISTORY_DATE = sprintf("%4d-%02d-%02d", (localtime)[5] + 1900, (localtime)[4] + 1, (localtime)[3]);
+$HISTORY = qq
+{$HISTORY_TITLE
+$HISTORY_LINE
+
+$HISTORY_DATE: Version $PLUGIN_VERSION
+
+- Initial revision.
+};
+
+$MAKEFILE = qq
+{#
+# Makefile for a Video Disk Recorder plugin
+#
+# \$Id\$
+
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+#
+PLUGIN = $PLUGIN_NAME
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = `grep 'static const char \\*VERSION *=' \$(PLUGIN).c | awk '{ print \$\$6 }' | sed -e 's/[";]//g'`
+
+### The directory environment:
+
+DVBDIR = ../../../../DVB/ost/include
+VDRDIR = ../../..
+VDRINC = \$(VDRDIR)/include
+LIBDIR = ../../lib
+TMPDIR = /tmp
+
+### The version number of VDR (taken from VDR's "config.h"):
+
+VDRVERSION = `grep 'define VDRVERSION ' \$(VDRDIR)/config.h | awk '{ print \$\$3 }' | sed -e 's/"//g'`
+
+### The name of the distribution archive:
+
+ARCHIVE = vdr-\$(PLUGIN)-\$(VERSION)
+
+### Includes and Defines (add further entries here):
+
+INCLUDES = -I\$(VDRINC) -I\$(DVBDIR)
+
+DEFINES = -DPLUGIN_NAME_I18N='"\$(PLUGIN)"'
+
+### The object files (add further files here):
+
+OBJS = \$(PLUGIN).o
+
+### The C++ compiler and options:
+
+CXX = g++
+CXXFLAGS = -O2 -Wall -Woverloaded-virtual -m486
+
+### Implicit rules:
+
+%.o: %.c
+ \$(CXX) \$(CXXFLAGS) -c \$(DEFINES) \$(INCLUDES) \$<
+
+# Dependencies:
+
+MAKEDEP = g++ -MM -MG
+DEPFILE = .dependencies
+\$(DEPFILE): Makefile
+ \@\$(MAKEDEP) \$(DEFINES) \$(INCLUDES) \$(OBJS:%.o=%.c) > \$\@
+
+include \$(DEPFILE)
+
+### Targets:
+
+all: libvdr-\$(PLUGIN).so
+
+libvdr-\$(PLUGIN).so: \$(OBJS)
+ \$(CXX) \$(CXXFLAGS) -shared \$(OBJS) -o \$\@
+ \@cp \$\@ \$(LIBDIR)/\$\@.\$(VDRVERSION)
+
+package: clean
+ \@-rm -rf \$(TMPDIR)/\$(ARCHIVE)
+ \@mkdir \$(TMPDIR)/\$(ARCHIVE)
+ \@cp -a * \$(TMPDIR)/\$(ARCHIVE)
+ \@tar czf \$(ARCHIVE).tgz -C \$(TMPDIR) \$(ARCHIVE)
+ \@-rm -rf \$(TMPDIR)/\$(ARCHIVE)
+ \@echo Distribution archive created as \$(ARCHIVE).tgz
+
+clean:
+ \@-rm -f \$(OBJS) \$(DEPFILE) *.so *.tgz core* *~
+};
+
+$MAIN = qq
+{/*
+ * $PLUGIN_NAME.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * \$Id\$
+ */
+
+#include <vdr/plugin.h>
+
+static const char *VERSION = "$PLUGIN_VERSION";
+static const char *DESCRIPTION = "$PLUGIN_DESCRIPTION";
+static const char *MAINMENUENTRY = "$PLUGIN_MAINENTRY";
+
+class cPlugin$PLUGIN_CLASS : public cPlugin {
+private:
+ // Add any member variables or functions you may need here.
+public:
+ cPlugin$PLUGIN_CLASS(void);
+ virtual ~cPlugin$PLUGIN_CLASS();
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return DESCRIPTION; }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual void Start(void);
+ virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
+ virtual cOsdMenu *MainMenuAction(void);
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *Name, const char *Value);
+ };
+
+cPlugin${PLUGIN_CLASS}::cPlugin$PLUGIN_CLASS(void)
+{
+ // Initialize any member varaiables here.
+ // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
+ // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
+}
+
+cPlugin${PLUGIN_CLASS}::~cPlugin$PLUGIN_CLASS()
+{
+ // Clean up after yourself!
+}
+
+const char *cPlugin${PLUGIN_CLASS}::CommandLineHelp(void)
+{
+ // Return a string that describes all known command line options.
+ return NULL;
+}
+
+bool cPlugin${PLUGIN_CLASS}::ProcessArgs(int argc, char *argv[])
+{
+ // Implement command line argument processing here if applicable.
+ return true;
+}
+
+void cPlugin${PLUGIN_CLASS}::Start(void)
+{
+ // Start any background activities the plugin shall perform.
+}
+
+cOsdMenu *cPlugin${PLUGIN_CLASS}::MainMenuAction(void)
+{
+ // Perform the action when selected from the main VDR menu.
+ return NULL;
+}
+
+cMenuSetupPage *cPlugin${PLUGIN_CLASS}::SetupMenu(void)
+{
+ // Return a setup menu in case the plugin supports one.
+ return NULL;
+}
+
+bool cPlugin${PLUGIN_CLASS}::SetupParse(const char *Name, const char *Value)
+{
+ // Parse your own setup parameters and store their values.
+ return false;
+}
+
+VDRPLUGINCREATOR(cPlugin$PLUGIN_CLASS); // Don't touch this!
+};
+
+$PLUGINDIR = "$PLUGINS_SRC/$PLUGIN_NAME";
+
+die "The directory $PLUGINS_SRC doesn't exist!\n" unless (-d "$PLUGINS_SRC");
+die "A plugin named '$PLUGIN_NAME' already exists in $PLUGINS_SRC!\n" if (-e "$PLUGINDIR");
+mkdir("$PLUGINDIR") || die "$!";
+
+CreateFile("README", $README);
+CreateFile("HISTORY", $HISTORY);
+CreateFile("Makefile", $MAKEFILE);
+CreateFile("$PLUGIN_NAME.c", $MAIN);
+`cp COPYING "$PLUGINDIR"` if (-e "COPYING");
+
+print qq{
+The new plugin source directory has been created in "$PLUGINDIR".
+
+The next steps you should perform now are:
+
+* edit the file "README" to adjust it to your specific implementation
+* fill in the code skeleton in "$PLUGIN_NAME.c" to implement your plugin function
+* add further source files if necessary
+* adapt the "Makefile" if necessary
+* do "make plugins" from the VDR source directory to build your plugin
+
+};
+
+sub CreateFile
+{
+ my ($Name, $Content) = @_;
+ open(FILE, ">$PLUGINDIR/$Name") || die "$Name: $!\n";
+ print FILE $Content;
+ close(FILE);
+}
+
diff --git a/osd.c b/osd.c
index 56ba9b5c..b23796a7 100644
--- a/osd.c
+++ b/osd.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: osd.c 1.23 2002/03/29 16:34:03 kls Exp $
+ * $Id: osd.c 1.24 2002/05/05 12:00:00 kls Exp $
*/
#include "osd.h"
@@ -105,12 +105,13 @@ cOsdMenu::~cOsdMenu()
const char *cOsdMenu::hk(const char *s)
{
- static char buffer[32];
+ static char buffer[64];
if (s && hasHotkeys) {
if (digit == 0 && '1' <= *s && *s <= '9' && *(s + 1) == ' ')
- digit = 10; // prevents automatic hotkeys - input already has them
- if (digit < 9) {
- snprintf(buffer, sizeof(buffer), " %d %s", ++digit, s);
+ digit = -1; // prevents automatic hotkeys - input already has them
+ if (digit >= 0) {
+ digit++;
+ snprintf(buffer, sizeof(buffer), " %c %s", (digit < 10) ? '0' + digit : ' ' , s);
s = buffer;
}
}
diff --git a/osd.h b/osd.h
index 8e825869..9b619ea5 100644
--- a/osd.h
+++ b/osd.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: osd.h 1.27 2002/03/10 16:18:11 kls Exp $
+ * $Id: osd.h 1.28 2002/05/05 12:00:00 kls Exp $
*/
#ifndef __OSD_H
@@ -23,6 +23,7 @@ enum eOSState { osUnknown,
osChannels,
osTimers,
osRecordings,
+ osPlugin,
osSetup,
osCommands,
osRecord,
@@ -43,6 +44,7 @@ enum eOSState { osUnknown,
osUser7,
osUser8,
osUser9,
+ osUser10,
};
class cOsdItem : public cListObject {
diff --git a/plugin.c b/plugin.c
new file mode 100644
index 00000000..ecae317e
--- /dev/null
+++ b/plugin.c
@@ -0,0 +1,319 @@
+/*
+ * plugin.c: The VDR plugin interface
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: plugin.c 1.1 2002/05/09 16:26:56 kls Exp $
+ */
+
+#include "plugin.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include "config.h"
+
+#define LIBVDR_PREFIX "libvdr-"
+#define SO_INDICATOR ".so."
+
+#define MAXPLUGINARGS 1024
+
+// --- cPlugin ---------------------------------------------------------------
+
+cPlugin::cPlugin(void)
+{
+ name = NULL;
+}
+
+cPlugin::~cPlugin()
+{
+ I18nRegister(NULL, Name());
+}
+
+void cPlugin::SetName(const char *s)
+{
+ name = s;
+}
+
+const char *cPlugin::CommandLineHelp(void)
+{
+ return NULL;
+}
+
+bool cPlugin::ProcessArgs(int argc, char *argv[])
+{
+ return true;
+}
+
+void cPlugin::Start(void)
+{
+}
+
+const char *cPlugin::MainMenuEntry(void)
+{
+ return NULL;
+}
+
+cOsdMenu *cPlugin::MainMenuAction(void)
+{
+ return NULL;
+}
+
+cMenuSetupPage *cPlugin::SetupMenu(void)
+{
+ return NULL;
+}
+
+bool cPlugin::SetupParse(const char *Name, const char *Value)
+{
+ return false;
+}
+
+void cPlugin::SetupStore(const char *Name, const char *Value)
+{
+ Setup.Store(Name, Value, this->Name());
+}
+
+void cPlugin::SetupStore(const char *Name, int Value)
+{
+ Setup.Store(Name, Value, this->Name());
+}
+
+void cPlugin::RegisterI18n(const tI18nPhrase * const Phrases)
+{
+ I18nRegister(Phrases, Name());
+}
+
+// --- cDll ------------------------------------------------------------------
+
+cDll::cDll(const char *FileName, const char *Args)
+{
+ fileName = strdup(FileName);
+ args = Args ? strdup(Args) : NULL;
+ handle = NULL;
+ plugin = NULL;
+}
+
+cDll::~cDll()
+{
+ delete plugin;
+ if (handle)
+ dlclose(handle);
+ delete args;
+ delete fileName;
+}
+
+static char *SkipQuote(char *s)
+{
+ char c = *s;
+ strcpy(s, s + 1);
+ while (*s && *s != c) {
+ if (*s == '\\')
+ strcpy(s, s + 1);
+ if (*s)
+ s++;
+ }
+ if (*s) {
+ strcpy(s, s + 1);
+ return s;
+ }
+ esyslog(LOG_ERR, "ERROR: missing closing %c", c);
+ fprintf(stderr, "vdr: missing closing %c\n", c);
+ return NULL;
+}
+
+bool cDll::Load(bool Log)
+{
+ if (Log)
+ isyslog(LOG_INFO, "loading plugin: %s", fileName);
+ if (handle) {
+ esyslog(LOG_ERR, "attempt to load plugin '%s' twice!", fileName);
+ return false;
+ }
+ handle = dlopen(fileName, RTLD_NOW);
+ const char *error = dlerror();
+ if (!error) {
+ void *(*creator)(void);
+ (void *)creator = dlsym(handle, "VDRPluginCreator");
+ if (!(error = dlerror()))
+ plugin = (cPlugin *)creator();
+ }
+ if (!error) {
+ if (plugin && args) {
+ int argc = 0;
+ char *argv[MAXPLUGINARGS];
+ char *p = args;
+ char *q = NULL;
+ bool done = false;
+ while (!done) {
+ if (!q)
+ q = p;
+ switch (*p) {
+ case '\\': strcpy(p, p + 1);
+ if (*p)
+ p++;
+ else {
+ esyslog(LOG_ERR, "ERROR: missing character after \\");
+ fprintf(stderr, "vdr: missing character after \\\n");
+ return false;
+ }
+ break;
+ case '"':
+ case '\'': if ((p = SkipQuote(p)) == NULL)
+ return false;
+ break;
+ default: if (!*p || isspace(*p)) {
+ done = !*p;
+ *p = 0;
+ if (q) {
+ if (argc < MAXPLUGINARGS - 1)
+ argv[argc++] = q;
+ else {
+ esyslog(LOG_ERR, "ERROR: plugin argument list too long");
+ fprintf(stderr, "vdr: plugin argument list too long\n");
+ return false;
+ }
+ q = NULL;
+ }
+ }
+ if (!done)
+ p++;
+ }
+ }
+ argv[argc] = NULL;
+ if (argc)
+ plugin->SetName(argv[0]);
+ optind = 0; // to reset the getopt() data
+ return !argc || plugin->ProcessArgs(argc, argv);
+ }
+ }
+ else {
+ esyslog(LOG_ERR, "ERROR: %s", error);
+ fprintf(stderr, "vdr: %s\n", error);
+ }
+ return !error && plugin;
+}
+
+// --- cPluginManager --------------------------------------------------------
+
+cPluginManager *cPluginManager::pluginManager = NULL;
+
+cPluginManager::cPluginManager(const char *Directory)
+{
+ directory = NULL;
+ if (pluginManager) {
+ fprintf(stderr, "vdr: attempt to create more than one plugin manager - exiting!\n");
+ exit(2);
+ }
+ SetDirectory(Directory);
+ pluginManager = this;
+}
+
+cPluginManager::~cPluginManager()
+{
+ Shutdown();
+ delete directory;
+ if (pluginManager == this)
+ pluginManager = NULL;
+}
+
+void cPluginManager::SetDirectory(const char *Directory)
+{
+ delete directory;
+ directory = Directory ? strdup(Directory) : NULL;
+}
+
+void cPluginManager::AddPlugin(const char *Args)
+{
+ if (strcmp(Args, "*") == 0) {
+ DIR *d = opendir(directory);
+ if (d) {
+ struct dirent *e;
+ while ((e = readdir(d)) != NULL) {
+ if (strstr(e->d_name, LIBVDR_PREFIX) == e->d_name) {
+ char *p = strstr(e->d_name, SO_INDICATOR);
+ if (p) {
+ *p = 0;
+ p += strlen(SO_INDICATOR);
+ if (strcmp(p, VDRVERSION) == 0) {
+ char *name = e->d_name + strlen(LIBVDR_PREFIX);
+ if (strcmp(name, "*") != 0) { // let's not get into a loop!
+ AddPlugin(e->d_name + strlen(LIBVDR_PREFIX));
+ }
+ }
+ }
+ }
+ }
+ closedir(d);
+ }
+ return;
+ }
+ char *s = strdup(Args);
+ char *p = strchr(s, ' ');
+ if (p)
+ *p = 0;
+ char *buffer = NULL;
+ asprintf(&buffer, "%s/%s%s%s%s", directory, LIBVDR_PREFIX, s, SO_INDICATOR, VDRVERSION);
+ dlls.Add(new cDll(buffer, Args));
+ delete buffer;
+ delete s;
+}
+
+bool cPluginManager::LoadPlugins(bool Log)
+{
+ for (cDll *dll = dlls.First(); dll; dll = dlls.Next(dll)) {
+ if (!dll->Load(Log))
+ return false;
+ }
+ return true;
+}
+
+void cPluginManager::StartPlugins(void)
+{
+ for (cDll *dll = dlls.First(); dll; dll = dlls.Next(dll)) {
+ cPlugin *p = dll->Plugin();
+ if (p) {
+ int Language = Setup.OSDLanguage;
+ Setup.OSDLanguage = 0; // the i18n texts are only available _after_ Start()
+ isyslog(LOG_INFO, "starting plugin: %s (%s): %s", p->Name(), p->Version(), p->Description());
+ Setup.OSDLanguage = Language;
+ dll->Plugin()->Start();
+ }
+ }
+}
+
+bool cPluginManager::HasPlugins(void)
+{
+ return pluginManager && pluginManager->dlls.Count();
+}
+
+cPlugin *cPluginManager::GetPlugin(int Index)
+{
+ cDll *dll = pluginManager ? pluginManager->dlls.Get(Index) : NULL;
+ return dll ? dll->Plugin() : NULL;
+}
+
+cPlugin *cPluginManager::GetPlugin(const char *Name)
+{
+ if (pluginManager) {
+ for (cDll *dll = pluginManager->dlls.First(); dll; dll = pluginManager->dlls.Next(dll)) {
+ cPlugin *p = dll->Plugin();
+ if (p && strcmp(p->Name(), Name) == 0)
+ return p;
+ }
+ }
+ return NULL;
+}
+
+void cPluginManager::Shutdown(bool Log)
+{
+ cDll *dll;
+ while ((dll = dlls.Last()) != NULL) {
+ if (Log) {
+ cPlugin *p = dll->Plugin();
+ if (p)
+ isyslog(LOG_INFO, "stopping plugin: %s", p->Name());
+ }
+ dlls.Del(dll);
+ }
+}
diff --git a/plugin.h b/plugin.h
new file mode 100644
index 00000000..99f330d4
--- /dev/null
+++ b/plugin.h
@@ -0,0 +1,81 @@
+/*
+ * plugin.h: The VDR plugin interface
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: plugin.h 1.1 2002/05/09 10:16:09 kls Exp $
+ */
+
+#ifndef __PLUGIN_H
+#define __PLUGIN_H
+
+#include "i18n.h"
+#include "menuitems.h"
+#include "osd.h"
+#include "tools.h"
+
+#define VDRPLUGINCREATOR(PluginClass) extern "C" void *VDRPluginCreator(void) { return new PluginClass; }
+
+class cPlugin {
+ friend class cDll;
+private:
+ const char *name;
+ void SetName(const char *s);
+public:
+ cPlugin(void);
+ virtual ~cPlugin();
+
+ const char *Name(void) { return name; }
+ virtual const char *Version(void) = 0;
+ virtual const char *Description(void) = 0;
+ virtual const char *CommandLineHelp(void);
+
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual void Start(void);
+
+ virtual const char *MainMenuEntry(void);
+ virtual cOsdMenu *MainMenuAction(void);
+
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *Name, const char *Value);
+ void SetupStore(const char *Name, const char *Value = NULL);
+ void SetupStore(const char *Name, int Value);
+
+ void RegisterI18n(const tI18nPhrase * const Phrases);
+ };
+
+class cDll : public cListObject {
+private:
+ char *fileName;
+ char *args;
+ void *handle;
+ cPlugin *plugin;
+public:
+ cDll(const char *FileName, const char *Args);
+ virtual ~cDll();
+ bool Load(bool Log = false);
+ cPlugin *Plugin(void) { return plugin; }
+ };
+
+class cDlls : public cList<cDll> {};
+
+class cPluginManager {
+private:
+ static cPluginManager *pluginManager;
+ char *directory;
+ cDlls dlls;
+public:
+ cPluginManager(const char *Directory);
+ virtual ~cPluginManager();
+ void SetDirectory(const char *Directory);
+ void AddPlugin(const char *Args);
+ bool LoadPlugins(bool Log = false);
+ void StartPlugins(void);
+ static bool HasPlugins(void);
+ static cPlugin *GetPlugin(int Index);
+ static cPlugin *GetPlugin(const char *Name);
+ void Shutdown(bool Log = false);
+ };
+
+#endif //__PLUGIN_H
diff --git a/remote.h b/remote.h
index bbdf2fac..184a42e9 100644
--- a/remote.h
+++ b/remote.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: remote.h 1.15 2001/07/22 14:42:59 kls Exp $
+ * $Id: remote.h 1.16 2002/05/09 11:43:04 kls Exp $
*/
#ifndef __REMOTE_H
@@ -95,7 +95,7 @@ public:
#elif !defined REMOTE_NONE
-#error Please define a remote control mode!
+// #error Please define a remote control mode!
#endif
diff --git a/vdr.1 b/vdr.1
index b26a9dff..06c0b84d 100644
--- a/vdr.1
+++ b/vdr.1
@@ -8,9 +8,9 @@
.\" License as specified in the file COPYING that comes with the
.\" vdr distribution.
.\"
-.\" $Id: vdr.1 1.3 2002/04/07 13:11:43 kls Exp $
+.\" $Id: vdr.1 1.4 2002/05/09 16:04:17 kls Exp $
.\"
-.TH vdr 1 "7 Apr 2002" "1.0.0" "Video Disk Recorder"
+.TH vdr 1 "9 May 2002" "1.1.0" "Video Disk Recorder"
.SH NAME
vdr - the Video Disk Recorder
.SH SYNOPSIS
@@ -68,6 +68,11 @@ Set logging to \fIlevel\fR.
\fB2\fR\ =\ errors and info, \fB3\fR\ =\ errors, info and debug.
The default logging level is \fB3\fR.
.TP
+.BI -L\ dir ,\ --lib= dir
+Search for plugins in directory \fIdir\fR (default is ./PLUGINS/lib).
+There can be several \fB-L\fR options with different \fIdir\fR values.
+Each of them will apply to the \fB-P\fR options following it.
+.TP
.B -m, --mute
Mute audio of the primary DVB device at startup.
.TP
@@ -77,6 +82,18 @@ The default SVDRP port is \fB2001\fR.
You need to edit the file \fIsvdrphosts.conf\fR in order to enable
access to the SVDRP port.
.TP
+.BI -P\ options ,\ --plugin= options
+Load a plugin, defined by the given \fIoptions\fR.
+The first word in \fIoptions\fR must be the name of an existing \fBvdr\fR
+plugin, optionally followed by a blank separated list of command line options
+for that plugin. If \fIoptions\fR contains any blanks, you need to enclose it
+in quotes, like for example
+
+\fBvdr -P "abc -a -b xyz"\fR
+
+which would load a plugin named \fBabc\fR, giving it the command line options
+\fB-a\ -b\ xyz\fR.
+.TP
.BI -r\ cmd ,\ --record= cmd
Call \fIcmd\fR before and after a recording.
.TP
@@ -104,6 +121,9 @@ Successful program execution.
.B 1
An error has been detected which requires the DVB driver and \fBvdr\fR
to be re-loaded.
+.TP
+.B 2
+An non-recoverable error has been detected, \fBvdr\fR has given up.
.SH FILES
.TP
.I channels.conf
diff --git a/vdr.c b/vdr.c
index 89aac3cb..1b04f4d8 100644
--- a/vdr.c
+++ b/vdr.c
@@ -22,7 +22,7 @@
*
* The project's page is at http://www.cadsoft.de/people/kls/vdr
*
- * $Id: vdr.c 1.103 2002/04/26 12:15:30 kls Exp $
+ * $Id: vdr.c 1.104 2002/05/05 12:00:00 kls Exp $
*/
#include <getopt.h>
@@ -36,6 +36,7 @@
#include "i18n.h"
#include "interface.h"
#include "menu.h"
+#include "plugin.h"
#include "recording.h"
#include "tools.h"
#include "videodir.h"
@@ -79,14 +80,18 @@ int main(int argc, char *argv[])
#define DEFAULTSVDRPPORT 2001
#define DEFAULTWATCHDOG 0 // seconds
+#define DEFAULTPLUGINDIR "./PLUGINS/lib"
int SVDRPport = DEFAULTSVDRPPORT;
const char *ConfigDirectory = NULL;
+ bool DisplayHelp = false;
+ bool DisplayVersion = false;
bool DaemonMode = false;
bool MuteAudio = false;
int WatchdogTimeout = DEFAULTWATCHDOG;
const char *Terminal = NULL;
const char *Shutdown = NULL;
+ cPluginManager PluginManager(DEFAULTPLUGINDIR);
static struct option long_options[] = {
{ "audio", required_argument, NULL, 'a' },
@@ -95,8 +100,10 @@ int main(int argc, char *argv[])
{ "device", required_argument, NULL, 'D' },
{ "epgfile", required_argument, NULL, 'E' },
{ "help", no_argument, NULL, 'h' },
+ { "lib", required_argument, NULL, 'L' },
{ "log", required_argument, NULL, 'l' },
{ "mute", no_argument, NULL, 'm' },
+ { "plugin", required_argument, NULL, 'P' },
{ "port", required_argument, NULL, 'p' },
{ "record", required_argument, NULL, 'r' },
{ "shutdown", required_argument, NULL, 's' },
@@ -108,8 +115,7 @@ int main(int argc, char *argv[])
};
int c;
- int option_index = 0;
- while ((c = getopt_long(argc, argv, "a:c:dD:E:hl:mp:r:s:t:v:Vw:", long_options, &option_index)) != -1) {
+ while ((c = getopt_long(argc, argv, "a:c:dD:E:hl:L:mp:P:r:s:t:v:Vw:", long_options, NULL)) != -1) {
switch (c) {
case 'a': cDvbApi::SetAudioCommand(optarg);
break;
@@ -128,40 +134,7 @@ int main(int argc, char *argv[])
break;
case 'E': cSIProcessor::SetEpgDataFileName(*optarg != '-' ? optarg : NULL);
break;
- case 'h': printf("Usage: vdr [OPTION]\n\n" // for easier orientation, this is column 80|
- " -a CMD, --audio=CMD send Dolby Digital audio to stdin of command CMD\n"
- " -c DIR, --config=DIR read config files from DIR (default is to read them\n"
- " from the video directory)\n"
- " -d, --daemon run in daemon mode\n"
- " -D NUM, --device=NUM use only the given DVB device (NUM = 0, 1, 2...)\n"
- " there may be several -D options (default: all DVB\n"
- " devices will be used)\n"
- " -E FILE --epgfile=FILE write the EPG data into the given FILE (default is\n"
- " %s); use '-E-' to disable this\n"
- " if FILE is a directory, the default EPG file will be\n"
- " created in that directory\n"
- " -h, --help print this help and exit\n"
- " -l LEVEL, --log=LEVEL set log level (default: 3)\n"
- " 0 = no logging, 1 = errors only,\n"
- " 2 = errors and info, 3 = errors, info and debug\n"
- " -m, --mute mute audio of the primary DVB device at startup\n"
- " -p PORT, --port=PORT use PORT for SVDRP (default: %d)\n"
- " 0 turns off SVDRP\n"
- " -r CMD, --record=CMD call CMD before and after a recording\n"
- " -s CMD, --shutdown=CMD call CMD to shutdown the computer\n"
- " -t TTY, --terminal=TTY controlling tty\n"
- " -V, --version print version information and exit\n"
- " -v DIR, --video=DIR use DIR as video directory (default: %s)\n"
- " -w SEC, --watchdog=SEC activate the watchdog timer with a timeout of SEC\n"
- " seconds (default: %d); '0' disables the watchdog\n"
- "\n"
- "Report bugs to <vdr-bugs@cadsoft.de>\n",
- cSIProcessor::GetEpgDataFileName() ? cSIProcessor::GetEpgDataFileName() : "'-'",
- DEFAULTSVDRPPORT,
- VideoDirectory,
- DEFAULTWATCHDOG
- );
- return 0;
+ case 'h': DisplayHelp = true;
break;
case 'l': if (isnumber(optarg)) {
int l = atoi(optarg);
@@ -173,6 +146,8 @@ int main(int argc, char *argv[])
fprintf(stderr, "vdr: invalid log level: %s\n", optarg);
return 2;
break;
+ case 'L': PluginManager.SetDirectory(optarg);
+ break;
case 'm': MuteAudio = true;
break;
case 'p': if (isnumber(optarg))
@@ -182,14 +157,15 @@ int main(int argc, char *argv[])
return 2;
}
break;
+ case 'P': PluginManager.AddPlugin(optarg);
+ break;
case 'r': cRecordingUserCommand::SetCommand(optarg);
break;
case 's': Shutdown = optarg;
break;
case 't': Terminal = optarg;
break;
- case 'V': printf("vdr, version %s\n", VDRVERSION);
- return 0;
+ case 'V': DisplayVersion = true;
break;
case 'v': VideoDirectory = optarg;
while (optarg && *optarg && optarg[strlen(optarg) - 1] == '/')
@@ -209,6 +185,71 @@ int main(int argc, char *argv[])
}
}
+ // Help and version info:
+
+ if (DisplayHelp || DisplayVersion) {
+ if (!PluginManager.HasPlugins())
+ PluginManager.AddPlugin("*"); // adds all available plugins
+ PluginManager.LoadPlugins();
+ if (DisplayHelp) {
+ printf("Usage: vdr [OPTIONS]\n\n" // for easier orientation, this is column 80|
+ " -a CMD, --audio=CMD send Dolby Digital audio to stdin of command CMD\n"
+ " -c DIR, --config=DIR read config files from DIR (default is to read them\n"
+ " from the video directory)\n"
+ " -d, --daemon run in daemon mode\n"
+ " -D NUM, --device=NUM use only the given DVB device (NUM = 0, 1, 2...)\n"
+ " there may be several -D options (default: all DVB\n"
+ " devices will be used)\n"
+ " -E FILE --epgfile=FILE write the EPG data into the given FILE (default is\n"
+ " %s); use '-E-' to disable this\n"
+ " if FILE is a directory, the default EPG file will be\n"
+ " created in that directory\n"
+ " -h, --help print this help and exit\n"
+ " -l LEVEL, --log=LEVEL set log level (default: 3)\n"
+ " 0 = no logging, 1 = errors only,\n"
+ " 2 = errors and info, 3 = errors, info and debug\n"
+ " -L DIR, --lib=DIR search for plugins in DIR (default is %s)\n"
+ " -m, --mute mute audio of the primary DVB device at startup\n"
+ " -p PORT, --port=PORT use PORT for SVDRP (default: %d)\n"
+ " 0 turns off SVDRP\n"
+ " -P OPT, --plugin=OPT load a plugin defined by the given options\n"
+ " -r CMD, --record=CMD call CMD before and after a recording\n"
+ " -s CMD, --shutdown=CMD call CMD to shutdown the computer\n"
+ " -t TTY, --terminal=TTY controlling tty\n"
+ " -v DIR, --video=DIR use DIR as video directory (default: %s)\n"
+ " -V, --version print version information and exit\n"
+ " -w SEC, --watchdog=SEC activate the watchdog timer with a timeout of SEC\n"
+ " seconds (default: %d); '0' disables the watchdog\n"
+ "\n",
+ cSIProcessor::GetEpgDataFileName() ? cSIProcessor::GetEpgDataFileName() : "'-'",
+ DEFAULTPLUGINDIR,
+ DEFAULTSVDRPPORT,
+ VideoDirectory,
+ DEFAULTWATCHDOG
+ );
+ }
+ if (DisplayVersion)
+ printf("vdr (%s) - The Video Disk Recorder\n", VDRVERSION);
+ if (PluginManager.HasPlugins()) {
+ if (DisplayHelp)
+ printf("Plugins: vdr -P\"name [OPTIONS]\"\n\n");
+ for (int i = 0; ; i++) {
+ cPlugin *p = PluginManager.GetPlugin(i);
+ if (p) {
+ const char *help = p->CommandLineHelp();
+ printf("%s (%s) - %s\n", p->Name(), p->Version(), p->Description());
+ if (DisplayHelp && help) {
+ printf("\n");
+ puts(help);
+ }
+ }
+ else
+ break;
+ }
+ }
+ return 0;
+ }
+
// Log file:
if (SysLogLevel > 0)
@@ -250,6 +291,11 @@ int main(int argc, char *argv[])
isyslog(LOG_INFO, "VDR version %s started", VDRVERSION);
+ // Load plugins:
+
+ if (!PluginManager.LoadPlugins(true))
+ return 2;
+
// Configuration data:
if (!ConfigDirectory)
@@ -276,6 +322,12 @@ int main(int argc, char *argv[])
cSIProcessor::Read();
+ // Start plugins:
+
+ PluginManager.StartPlugins();
+
+ // Channel:
+
Channels.SwitchTo(Setup.CurrentChannel);
if (MuteAudio)
cDvbApi::PrimaryDvbApi->ToggleMute();
@@ -529,6 +581,7 @@ int main(int argc, char *argv[])
Setup.CurrentChannel = cDvbApi::CurrentChannel();
Setup.CurrentVolume = cDvbApi::CurrentVolume();
Setup.Save();
+ PluginManager.Shutdown(true);
cVideoCutter::Stop();
delete Menu;
delete ReplayControl;