From b527b2770868bccde05ad47393951fde5d51f79a Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Thu, 9 May 2002 16:26:56 +0200 Subject: Implemented plugin interface --- HISTORY | 14 +- Makefile | 28 +- PLUGINS.html | 620 ++++++++++++++++++++++++++++++++++++++++ PLUGINS/src/hello/COPYING | 340 ++++++++++++++++++++++ PLUGINS/src/hello/HISTORY | 6 + PLUGINS/src/hello/Makefile | 78 +++++ PLUGINS/src/hello/README | 11 + PLUGINS/src/hello/hello.c | 105 +++++++ PLUGINS/src/hello/i18n.c | 52 ++++ PLUGINS/src/hello/i18n.h | 11 + config.c | 375 ++++++++++++++---------- config.h | 42 ++- i18n.c | 143 ++++++++-- i18n.h | 20 +- menu.c | 690 +++++++++------------------------------------ menuitems.c | 476 +++++++++++++++++++++++++++++++ menuitems.h | 110 ++++++++ newplugin | 250 ++++++++++++++++ osd.c | 11 +- osd.h | 4 +- plugin.c | 319 +++++++++++++++++++++ plugin.h | 81 ++++++ remote.h | 4 +- vdr.1 | 24 +- vdr.c | 131 ++++++--- 25 files changed, 3136 insertions(+), 809 deletions(-) create mode 100644 PLUGINS.html create mode 100644 PLUGINS/src/hello/COPYING create mode 100644 PLUGINS/src/hello/HISTORY create mode 100644 PLUGINS/src/hello/Makefile create mode 100644 PLUGINS/src/hello/README create mode 100644 PLUGINS/src/hello/hello.c create mode 100644 PLUGINS/src/hello/i18n.c create mode 100644 PLUGINS/src/hello/i18n.h create mode 100644 menuitems.c create mode 100644 menuitems.h create mode 100755 newplugin create mode 100644 plugin.c create mode 100644 plugin.h 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 @@ + + +The VDR Plugin System + + + +

The VDR Plugin System

+ +VDR provides an easy to use plugin interface that allows additional functionality +to be added to the program by implementing a dynamically loadable library file. +This interface allows programmers to develop additional functionality for VDR completely +separate from the core VDR source, without the need of patching the original +VDR code (and all the problems of correlating various patches). +

+This document 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. + + + +


Quick start

+ +
Can't wait, can't wait!

+ +Actually you should read this entire document before starting to work with VDR plugins, +but you probably want to see something happening right away ;-) +

+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: +

+If you enjoyed this brief glimpse into VDR plugin handling, read through the rest of +this document and eventually write your own VDR plugin. + +

The name of the plugin

+ +
Give me some I.D.!

+ +One of the first things to consider when writing a VDR plugin is giving the thing +a proper name. This name will be used in the VDR command line in order to load +the plugin, and will also be the name of the plugin's source directory, as well +as part of the final library name. +

+The plugin's name should typically be as short as possible. Three letter +abbreviations like dvd (for a DVD player) or mp3 +(for an MP3 player) would be good choices. It is also recommended that the name +consists of only lowercase letters and digits. +No other characters should be used here. +

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


+const char *Name(void); +

+ +The actual name is derived from the plugin's library file name, as defined in the +next chapter. + +


The plugin directory structure

+ +
Where is everybody?

+ +By default plugins are located in a directory named PLUGINS below the +VDR source directory. Inside this directory the following subdirectory structure +is used: + +


+VDR/PLUGINS/src +VDR/PLUGINS/src/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 +

+ +The src directory contains one subdirectory for each plugin, which carries +the name of that plugin (in the above example that would be demo and +hello, 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 Makefile that provides the targets all and +clean, and that a call to make all actually produces a dynamically +loadable library file for that plugin (we'll get to the details later). +

+The lib directory contains the dynamically loadable libraries of all +available plugins. Note that the names of these files are created by concatenating +

+ + + +
libvdr-demo.so.1.1.0
VDR plugin
library prefix
name of
the plugin
shared object
indicator
VDR version number
this plugin was
compiled for
+

+The plugin library files can be stored in any directory. If the default organization +is not used, the path to the plugin directory has be be given to VDR through the +-L option. +

+The VDR Makefile contains the target plugins, which calls +make all in every directory found under VDR/PLUGINS/src, +plus the target plugins-clean, which calls make clean in +each of these directories. +

+If you download a plugin package +from the web, it will typically have a name like +

+vdr-demo-0.0.1.tgz +

+and will unpack into a directory named +

+vdr-demo-0.0.1 +

+To use the plugins and plugins-clean targets from the VDR Makefile +you need to unpack such an archive into the VDR/PLUGINS/src directory and +create a symbolic link with the basic plugin name, as in + +


+ln -s vdr-demo-0.0.1 demo +

+ +Since the VDR Makefile only searches for directories with names consisting +of only lowercase characters and digits, it will only follow the symbolic links, which +should lead to the current version of the plugin you want to use. This way you can +have several different versions of a plugin source (like vdr-demo-0.0.1 and +vdr-demo-0.0.2) and define which one to actually use through the symbolic link. + +


Initializing a new plugin directory

+ +
A room with a view

+ +Call the Perl script newplugin from the VDR source directory to create +a new plugin directory with a Makefile and a main source file implementing +the basic derived plugin class. +You will also find a README file there with some inital text, where you +should fill in actual information about your project. +A HISTORY file is set up with an "Initial revision" entry. As your project +evolves, you should add the changes here with date and version number. +

+newplugin also creates a copy of the GPL license file COPYING, +assuming that you will release your work under that license. Change this if you +have other plans. +

+Add further files and maybe subdirectories to your plugin source directory as +necessary. Don't forget to adapt the Makefile appropriately. + +


The actual implementation

+ +
Use the source, Luke!

+ +A newly initialized plugin doesn't really do very much yet. +If you load it into VDR you will find a new +entry in the main menu, with the same name as your plugin (where the first character +has been converted to uppercase). There will also be a new entry named "Plugins" in +the "Setup" menu, which will bring up a list of all loaded plugins, through which you +can access each plugin's own setup parameters (if it provides any). +

+To implement actual functionality into your plugin you need to edit the source file +that was generated as PLUGINS/src/name.c. Read the comments in that file +to see where you can bring in your own code. The following sections of this document +will walk you through the individual member functions of the plugin class. +

+Depending on what your plugin shall do, you may or may not need all of the given +member functions. Except for the MainMenuEntry() function they all by default +return values that will result in no actual functionality. You can either completely +delete unused functions from your source file, or just leave them as they are. +If your plugin shall not be accessible through VDR's main menu, simply remove +(or comment out) the line implementing the MainMenuEntry() function. +

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


+VDRPLUGINCREATOR(cPluginDemo); +

+ +This is the "magic" hook that allows VDR to actually load the plugin into +its memory. You don't need to worry about the details behind all this. +

+If your plugin requires additional source files, simply add them to your plugin's +source directory and adjust the Makefile accordingly. + +


Construction and Destruction

+ +
What goes up, must come down...

+ +The constructor and destructor of a plugin are defined as + +


+cPlugin(void); +virtual ~cPlugin(); +

+ +The constructor shall initialize any member variables the plugin defines, but +must not access any global structures of VDR. +It also must not create any threads or other large data structures. These things +are done in the Start() function later. +Constructing a plugin object shall not have any side effects or produce any output, +since VDR, for instance, has to create the plugin objects in order to get their +command line help - and after that immediately destroys them again. +

+The destructor has to clean up any data created by the plugin, and has to +take care that any threads the plugin may have created will be stopped. +

+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. + +


Version number

+ +
Which incarnation is this?

+ +Every plugin must have a version number of its own, which does not necessarily +have to be in any way related to the VDR version number. +VDR requests a plugin's version number through a call to the function + +


+virtual const char *Version(void) = 0; +

+ +Since this is a "pure" virtual function, any derived plugin class must +implement it. The returned string should identify this version of the plugin. +Typically this would be something like "0.0.1", but it may also contain other +information, like for instance "0.0.1pre2" or the like. The string should only +be as long as really necessary, and shall not contain the plugin's name itself. +Here's an example: + +


+static const char *VERSION = "0.0.1"; + +... + +const char *cPluginDemo::Version(void) +{ + return VERSION; +} +

+ +Note that the definition of the version number is expected to be located in the +main source file, and must be written as +

+static const char *VERSION = ...
+
+just like shown in the above example. This is a convention that allows the Makefile +to extract the version number when generating the file name for the distribution archive. +

+A new plugin project should start with version number 0.0.1 and should reach +version 1.0.0 once it is completely operative and well tested. Following the +Linux kernel version numbering scheme, versions with even release numbers +(like 1.0.x, 1.2.x, 1.4.x...) should be stable releases, +while those with odd release numbers (like 1.1.x, 1.3.x, +1.5.x...) are usually considered "under development". The three parts of +a version number are not limited to single digits, so a version number of 1.2.15 +would be acceptable. + +


Description

+ +
What is it that you do?

+ +In order to tell the user what exactly a plugin does, it must implement the function + +


+virtual const char *Description(void) = 0; +

+ +which returns a short, one line description of the plugin's purpose. + +


+virtual const char *Description(void) +{ + return "A simple demo plugin"; +} +

+ +


Command line arguments

+ +
Taking orders

+ +A VDR plugin can have command line arguments just like any normal program. +If a plugin wants to react on command line arguments, it needs to implement +the function + +


+virtual bool ProcessArgs(int argc, char *argv[]); +

+ +The parameters argc and argv have exactly the same meaning +as in a normal C program's main() function. +argv[0] contains the name of the plugin (as given in the -P +option of the vdr call). +

+Each plugin has its own set of command line options, which are totally independent +from those of any other plugin or VDR itself. +

+You can use the getopt() or getopt_long() function to process +these arguments. As with any normal C program, the strings pointed to by argv +will survive the entire lifetime of the plugin, so it is safe to store pointers to +these values inside the plugin. Here's an example: + +


+bool 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; +} +

+ +The return value must be true if all options have been processed +correctly, or false in case of an error. The first plugin that returns +false from a call to its ProcessArgs() function will cause VDR +to exit. + +


Command line help

+ +
Tell me about it...

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


+virtual const char *CommandLineHelp(void); +

+ +which will be called if the user enters the -h option when starting VDR. +The returned string should contain the command line help for this plugin, formatted +in the same way as done by VDR itself: + +


+const char *cPluginDemo::CommandLineHelp(void) +{ + return " -a ABC, --aaa=ABC do something nice with ABC\n" + " -b, --bbb activate 'plan B'\n"; +} +

+ +This command line help will be printed directly below VDR's help texts (separated +by a line indicating the plugin's name, version and description), so if you use the +same formatting as shown here it will line up nicely. +Note that all lines should be terminated with a newline character, and should +be shorter than 80 characters. + +


Getting started

+ +
Let's get ready to rumble!

+ +If a plugin implements a function that runs in the background (presumably in a +thread of its own), or wants to make use of internationalization, +it needs to implement the function + +


+virtual void Start(void); +

+ +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. +

+If the plugin doesn't implement any background functionality or internationalized +texts, it doesn't need to implement this function. + +


Main menu entry

+ +
Today's special is...

+ +If the plugin implements a feature that the user shall be able to access +from VDR's main menu, it needs to implement the function + +


+virtual const char *MainMenuEntry(void); +

+ +The default implementation returns a NULL pointer, which means that +this plugin will not have an item in the main menu. Here's an example of a +plugin that will have a main menu item: + +


+const char *cPluginDemo::MainMenuEntry(void) +{ + return "Demo"; +} +

+ +The menu entries of all plugins will be inserted into VDR's main menu right +after the Recordings item, in the same sequence as they were given +in the call to VDR. + +


User interaction

+ +
It's showtime!

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


+virtual cOsdMenu *MainMenuAction(void); +

+ +which can do one of two things: +

+ +It is very important that a call to MainMenuAction() 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. + + +

Setup parameters

+ +
Remember me...

+ +If a plugin requires its own setup parameters, it needs to implement the following +functions to handle these parameters: + +


+virtual cMenuSetupPage *SetupMenu(void); +virtual bool SetupParse(const char *Name, const char *Value); +

+ +The SetupMenu() function shall return the plugin's "Setup" menu +page, where the user can adjust all the parameters known to this plugin. +

+SetupParse() will be called for each parameter the plugin has +previously stored in the global setup data (see below). It shall return +true if the parameter was parsed correctly, false in case of +an error. If false is returned, an error message will be written to +the log file (and program execution will continue). +

+The plugin's setup parameters are stored in the same file as VDR's parameters. +In order to allow each plugin (and VDR itself) to have its own set of parameters, +the Name of each parameter will be preceeded with the plugin's +name, as in +

+demo.SomeParameter = 123 +

+The prefix will be handled by the core VDR setup code, so the individual +plugins need not worry about this. +

+To store its values in the global setup, a plugin has to call the function + +


+void SetupStore(const char *Name, type Value); +

+ +where Name is the name of the parameter ("SomeParameter" in the above +example, without the prefix "demo.") and Value is a simple data type (like +char *, int etc). +Note that this is not a function that the individual plugin class needs to implement! +SetupStore() is a non-virtual member function of the cPlugin class. +

+To remove a parameter from the setup data, call SetupStore() with the appropriate +name and without any value, as in +

+SetupStore("SomeParameter"); +

+The VDR menu "Setup/Plugins" will list all loaded plugins with their name, +version number and description. Selecting an item in this list will bring up +the plugin's "Setup" menu if that plugin has implemented the SetupMenu() +function. +

+Finally, a plugin doesn't have to implement the SetupMenu() if it only +needs setup parameters that are not directly user adjustable. It can use +SetupStore() and SetupParse() without presenting these +parameters to the user. + +


Internationalization

+ +
Welcome to Babylon!

+ +If a plugin displays texts to the user, it should implement internationalized +versions of these texts and call the function + +


+void RegisterI18n(const tI18nPhrase * const Phrases); +

+ +to register them with VDR's internationalization mechanism. +

+The call to this function must be done in the Start() function of the plugin: + +


+const tI18nPhrase Phrases[] = { + { "Hello world!", + "Hallo Welt!", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + }, + { NULL } + }; + +void cPluginDemo::Start(void) +{ + RegisterI18n(Phrases); +} +

+ +Each entry of type tI18nPhrase must have exactly as many members as defined +by the constant I18nNumLanguages in the file VDR/i18n.h, and the +sequence of the various languages must be the same as defined in VDR/i18n.c.
+It is very important that the array is terminated with a { NULL } +entry!. +

+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 +VDR/i18n.c) and ask them to provide the additional translations. +

+The actual runtime selection of the texts corresponding to the selected language +is done by wrapping each internationalized text with the tr() macro: + +


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

+ +The text given here must be the first one defined in the related Phrases +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 Phrases 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. + +


Loading plugins into VDR

+ +
Saddling up!

+ +Plugins are loaded into VDR using the command line option -P, as in + +


+vdr -Pdemo +

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


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

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


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

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


+vdr -L/usr/lib/vdr -Pdemo +

+ +There can be any number of -L options, and each of them will apply to the +-P options following it. +

+When started with the -h or -V option (for help +or version information, respectively), VDR will automatically load all plugins +in the default or given directory that match the VDR plugin +naming convention, +and display their help and/or version information in addition to its own output. + +


Building the distribution package

+ +
Let's get this show on the road!

+ +If you want to make your plugin available to other VDR users, you'll need to +make a package that can be easily distributed. +The Makefile that has been created by the call to +newplugin +provides the target package, which does this for you. +

+Simply change into your source directory and execute make package: + +


+cd VDR/PLUGINS/src/demo +make package +

+ +After this you should find a file named like + +


+vdr-demo-0.0.1.tgz +

+ +in your source directory, where demo will be replaced with your actual +plugin's name, and 0.0.1 will be your plugin's current version number. + + + 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. + + + Copyright (C) + + 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. + + , 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 + +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 +#include +#include +#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 + +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::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::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 { + 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 and Matjaz Thaler - * Italian translations provided by Alberto Carraro - * Dutch translations provided by Arnold Niessen - * Portuguese translations provided by Paulo Lopes - * French translations provided by Jean-Claude Repetto - * Norwegian translations provided by Jørgen Tvedt and Truls Slevigen - * Finnish translations provided by Hannu Savolainen - * Polish translations provided by Michael Rakowski - * Spanish translations provided by Ruben Nunez Francisco - * Greek translations provided by Dimitrios Dimitrakos + * Translations provided by: + * + * Slovenian Miha Setina and Matjaz Thaler + * Italian Alberto Carraro + * Dutch Arnold Niessen + * Portuguese Paulo Lopes + * French Jean-Claude Repetto + * Norwegian Jørgen Tvedt and Truls Slevigen + * Finnish Hannu Savolainen + * Polish Michael Rakowski + * Spanish Ruben Nunez Francisco + * Greek Dimitrios Dimitrakos * */ /* * 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 to have * it included in the next version of VDR. * @@ -60,15 +62,10 @@ */ #include "i18n.h" -#include #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 { +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 -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 +#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 +# +# 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 \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 + +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 + +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 +#include +#include +#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 {}; + +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 @@ -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 \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; -- cgit v1.2.3