From 54a2e99c7b86cafa5ad350171f021589ef2a80df Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sat, 11 Nov 2000 16:38:41 +0100 Subject: [PATCH] Implemented 'Commands' menu --- FORMATS | 24 ++++++++++++++++++ HISTORY | 3 +++ MANUAL | 24 ++++++++++++++++++ config.c | 63 +++++++++++++++++++++++++++++++++++++++++++++-- config.h | 20 +++++++++++++-- i18n.c | 5 +++- menu.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++--------- osd.h | 3 ++- tools.c | 14 ++++++++++- tools.h | 3 ++- vdr.c | 3 ++- 11 files changed, 217 insertions(+), 20 deletions(-) diff --git a/FORMATS b/FORMATS index a86cfd2c..3eee494f 100644 --- a/FORMATS +++ b/FORMATS @@ -66,3 +66,27 @@ Video Disk Recorder File Formats See the MANUAL file for a description of the available options. +* commands.conf + + This file contains the definitions of commands that can be executed from + the "Main" menus "Commands" option. + + Each line contains one command definition in the following format: + + title : command + + where 'title' is the string the will be displayed in the "Commands" menu, + and 'command' is the actual command string that will be executed when this + option is selected. The delimiting ':' may be surrounded by any number of + white space characters. + + In order to avoid error messages to stderr, every command should have + stderr redirected to stdout. Everything the command prints to stdout will + be displayed in a result window, with 'title' as its title. + + Examples: + + Check for new mail: /usr/local/bin/checkmail 2>&1 + CPU status : /usr/loval/bin/cpustatus 2>&1 + Disk space : df -h | grep '/video' | awk '{ print 100 - $5 "% free"; }' + diff --git a/HISTORY b/HISTORY index bfca2d2c..bf328597 100644 --- a/HISTORY +++ b/HISTORY @@ -278,3 +278,6 @@ Video Disk Recorder Revision History - Fixed a timing problem with OSD refresh and SVDRP. - Avoiding multiple definitions of the same timer in the "Schedule" menu (this could happen when pressing the "Red" button while editing the timer). +- There can now be a configuration file named 'commands.conf' that defines + commands that can be executed through the "Main" menu's "Commands" option + (see FORMATS for details on how to define these commands). diff --git a/MANUAL b/MANUAL index b3f1cc72..3f0b1f7b 100644 --- a/MANUAL +++ b/MANUAL @@ -273,3 +273,27 @@ Video Disk Recorder User's Manual MarginStart = 2 Defines how many minutes before the official start time MarginStop = 10 of a broadcast VDR shall start recording, and how long after the official end time it shall stop recording. + +* Executing system commands + + The "Main" menu option "Commands" allows you to execute any system commands + defined in the configuration file 'commands.conf' (see FORMATS for details). + The "Commands" option will only be present in the "Main" menu if a valid + 'commands.conf' file containing at least one command definition has been + found at program start. + + This feature can be used to do virtually anything, like checking for new + mail, displaying the CPU temperature - you name it! All you need to do is + enter the necessary command definition into 'commands.conf' and implement + the actual command that will be called. Such a command can typically be a + shell script or a Perl program. Anything that command writes to stdout will + be displayed on a result screen after executing the command. In order to + avoid error messages going to stderr, command definitions should redirect + stderr to stdout (see FORMATS). + + WARNING: THE COMMANDS DEFINED IN 'commands.conf' WILL BE EXECUTED UNDER THE + ======= SAME USER ID THAT VDR IS RUNNING WITH. BE VERY CAREFUL WHEN + DEFINING THESE COMMANDS AND MAKE SURE THEY DON'T HARM YOUR SYSTEM, + ESPECIALLY IF YOU ARE RUNNING VDR UNDER A HIGH PRIVILEGED USER ID + (LIKE 'root'). + diff --git a/config.c b/config.c index 0da6ebf5..d4f8b383 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.31 2000/11/11 09:56:01 kls Exp $ + * $Id: config.c 1.32 2000/11/11 15:41:07 kls Exp $ */ #include "config.h" @@ -426,7 +426,7 @@ bool cTimer::Parse(const char *s) delete summary; summary = NULL; //XXX Apparently sscanf() doesn't work correctly if the last %a argument - //XXX results in an empty string (this firt occured when the EIT gathering + //XXX results in an empty string (this first occured when the EIT gathering //XXX was put into a separate thread - don't know why this happens... //XXX As a cure we copy the original string and add a blank. //XXX If anybody can shed some light on why sscanf() failes here, I'd love @@ -539,10 +539,69 @@ cTimer *cTimer::GetMatch(void) return t0; } +// --- cCommand ------------------------------------------------------------- + +char *cCommand::result = NULL; + +cCommand::cCommand(void) +{ + title = command = NULL; +} + +cCommand::~cCommand() +{ + delete title; + delete command; +} + +bool cCommand::Parse(const char *s) +{ + const char *p = strchr(s, ':'); + if (p) { + int l = p - s; + if (l > 0) { + title = new char[l + 1]; + strn0cpy(title, s, l + 1); + if (!isempty(title)) { + command = stripspace(strdup(skipspace(p + 1))); + return !isempty(command); + } + } + } + return false; +} + +const char *cCommand::Execute(void) +{ + dsyslog(LOG_INFO, "executing command '%s'", command); + delete result; + result = NULL; + FILE *p = popen(command, "r"); + if (p) { + int l = 0; + int c; + while ((c = fgetc(p)) != EOF) { + if (l % 20 == 0) + result = (char *)realloc(result, l + 21); + result[l++] = c; + } + if (result) + result[l] = 0; + pclose(p); + } + else + esyslog(LOG_ERR, "ERROR: can't open pipe for command '%s'", command); + return result; +} + // -- cKeys ------------------------------------------------------------------ cKeys Keys; +// -- cCommands -------------------------------------------------------------- + +cCommands Commands; + // -- cChannels -------------------------------------------------------------- int CurrentGroup = -1; diff --git a/config.h b/config.h index 679e88ed..c6f6ba99 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.31 2000/11/11 10:39:00 kls Exp $ + * $Id: config.h 1.32 2000/11/11 14:39:40 kls Exp $ */ #ifndef __CONFIG_H @@ -115,7 +115,7 @@ public: char *summary; cTimer(bool Instant = false); cTimer(const cEventInfo *EventInfo); - ~cTimer(); + virtual ~cTimer(); cTimer& operator= (const cTimer &Timer); const char *ToText(void); bool Parse(const char *s); @@ -132,6 +132,19 @@ public: static const char *PrintDay(int d); }; +class cCommand : public cListObject { +private: + char *title; + char *command; + static char *result; +public: + cCommand(void); + virtual ~cCommand(); + bool Parse(const char *s); + const char *Title(void) { return title; } + const char *Execute(void); + }; + template class cConfig : public cList { private: char *fileName; @@ -217,11 +230,14 @@ public: cTimer *GetTimer(cTimer *Timer); }; +class cCommands : public cConfig {}; + extern int CurrentGroup; extern cChannels Channels; extern cTimers Timers; extern cKeys Keys; +extern cCommands Commands; class cSetup { private: diff --git a/i18n.c b/i18n.c index 06b1241d..94009cb1 100644 --- a/i18n.c +++ b/i18n.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: i18n.c 1.1 2000/11/11 10:39:27 kls Exp $ + * $Id: i18n.c 1.2 2000/11/11 16:20:47 kls Exp $ */ /* @@ -70,6 +70,9 @@ const tPhrase Phrases[] = { { "Setup", "Einstellungen", }, + { "Commands", + "Befehle", + }, { "Edit Channel", "Kanal Editieren", }, diff --git a/menu.c b/menu.c index 0096a8a9..c9959a4f 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.45 2000/11/11 12:55:10 kls Exp $ + * $Id: menu.c 1.46 2000/11/11 15:22:56 kls Exp $ */ #include "menu.h" @@ -850,26 +850,30 @@ eOSState cMenuTextItem::ProcessKey(eKeys Key) return osContinue; } -// --- cMenuSummary ---------------------------------------------------------- +// --- cMenuText ------------------------------------------------------------- -class cMenuSummary : public cOsdMenu { +class cMenuText : public cOsdMenu { public: - cMenuSummary(const char *Text); + cMenuText(const char *Title, const char *Text); virtual eOSState ProcessKey(eKeys Key); }; -cMenuSummary::cMenuSummary(const char *Text) -:cOsdMenu(tr("Summary")) +cMenuText::cMenuText(const char *Title, const char *Text) +:cOsdMenu(Title) { Add(new cMenuTextItem(Text, 1, 2, MenuColumns - 2, MAXOSDITEMS)); } -eOSState cMenuSummary::ProcessKey(eKeys Key) +eOSState cMenuText::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); - if (state == osUnknown) - state = osContinue; + if (state == osUnknown) { + switch (Key) { + case kOk: return osBack; + default: state = osContinue; + } + } return state; } @@ -1058,7 +1062,7 @@ eOSState cMenuTimers::Summary(void) return osContinue; cTimer *ti = Timers.Get(Current()); if (ti && ti->summary && *ti->summary) - return AddSubMenu(new cMenuSummary(ti->summary)); + return AddSubMenu(new cMenuText(tr("Summary"), ti->summary)); return Edit(); // convenience for people not using the Summary feature ;-) } @@ -1442,7 +1446,7 @@ eOSState cMenuRecordings::Summary(void) return osContinue; cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); if (ri && ri->recording->Summary() && *ri->recording->Summary()) - return AddSubMenu(new cMenuSummary(ri->recording->Summary())); + return AddSubMenu(new cMenuText(tr("Summary"), ri->recording->Summary())); return osContinue; } @@ -1524,6 +1528,52 @@ eOSState cMenuSetup::ProcessKey(eKeys Key) return state; } +// --- cMenuCommands --------------------------------------------------------- + +class cMenuCommands : public cOsdMenu { +private: + eOSState Execute(void); +public: + cMenuCommands(void); + virtual eOSState ProcessKey(eKeys Key); + }; + +cMenuCommands::cMenuCommands(void) +:cOsdMenu(tr("Commands")) +{ + int i = 0; + cCommand *command; + + while ((command = Commands.Get(i)) != NULL) { + Add(new cOsdItem(command->Title())); + i++; + } +} + +eOSState cMenuCommands::Execute(void) +{ + cCommand *command = Commands.Get(Current()); + if (command) { + const char *Result = command->Execute(); + if (Result) + return AddSubMenu(new cMenuText(command->Title(), Result)); + } + return osContinue; +} + +eOSState cMenuCommands::ProcessKey(eKeys Key) +{ + eOSState state = cOsdMenu::ProcessKey(Key); + + if (state == osUnknown) { + switch (Key) { + case kOk: return Execute(); + default: break; + } + } + return state; +} + // --- cMenuMain ------------------------------------------------------------- #define STOP_RECORDING tr("Stop recording ") @@ -1536,6 +1586,8 @@ cMenuMain::cMenuMain(bool Replaying) Add(new cOsdItem(tr("Timers"), osTimers)); Add(new cOsdItem(tr("Recordings"), osRecordings)); Add(new cOsdItem(tr("Setup"), osSetup)); + if (Commands.Count()) + Add(new cOsdItem(tr("Commands"), osCommands)); if (Replaying) Add(new cOsdItem(tr("Stop replaying"), osStopReplay)); const char *s = NULL; @@ -1560,6 +1612,7 @@ eOSState cMenuMain::ProcessKey(eKeys Key) case osTimers: return AddSubMenu(new cMenuTimers); case osRecordings: return AddSubMenu(new cMenuRecordings); case osSetup: return AddSubMenu(new cMenuSetup); + case osCommands: return AddSubMenu(new cMenuCommands); case osStopRecord: if (Interface->Confirm(tr("Stop Recording?"))) { cOsdItem *item = Get(Current()); if (item) { diff --git a/osd.h b/osd.h index 74794d49..af189584 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.15 2000/11/10 15:28:28 kls Exp $ + * $Id: osd.h 1.16 2000/11/11 14:49:29 kls Exp $ */ #ifndef __OSD_H @@ -24,6 +24,7 @@ enum eOSState { osUnknown, osTimers, osRecordings, osSetup, + osCommands, osRecord, osReplay, osStopRecord, diff --git a/tools.c b/tools.c index 4acea0b3..6d86f162 100644 --- a/tools.c +++ b/tools.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.c 1.22 2000/10/29 11:21:55 kls Exp $ + * $Id: tools.c 1.23 2000/11/11 15:17:12 kls Exp $ */ #define _GNU_SOURCE @@ -92,6 +92,18 @@ char *skipspace(const char *s) return (char *)s; } +char *stripspace(char *s) +{ + if (s && *s) { + for (char *p = s + strlen(s) - 1; p >= s; p--) { + if (!isspace(*p)) + break; + *p = 0; + } + } + return s; +} + bool isempty(const char *s) { return !(s && *skipspace(s)); diff --git a/tools.h b/tools.h index b5bdd278..0794a1a6 100644 --- a/tools.h +++ b/tools.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.h 1.18 2000/10/29 11:19:20 kls Exp $ + * $Id: tools.h 1.19 2000/11/11 15:14:40 kls Exp $ */ #ifndef __TOOLS_H @@ -41,6 +41,7 @@ char *readline(FILE *f); char *strn0cpy(char *dest, const char *src, size_t n); char *strreplace(char *s, char c1, char c2); char *skipspace(const char *s); +char *stripspace(char *s); bool isempty(const char *s); int time_ms(void); void delay_ms(int ms); diff --git a/vdr.c b/vdr.c index e39db311..060a9d00 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.44 2000/11/10 16:13:27 kls Exp $ + * $Id: vdr.c 1.45 2000/11/11 14:40:11 kls Exp $ */ #include @@ -165,6 +165,7 @@ int main(int argc, char *argv[]) Setup.Load(AddDirectory(ConfigDirectory, "setup.conf")); Channels.Load(AddDirectory(ConfigDirectory, "channels.conf")); Timers.Load(AddDirectory(ConfigDirectory, "timers.conf")); + Commands.Load(AddDirectory(ConfigDirectory, "commands.conf")); #ifdef REMOTE_LIRC Keys.SetDummyValues(); #else