Implemented 'Commands' menu

This commit is contained in:
Klaus Schmidinger 2000-11-11 16:38:41 +01:00
parent 9c499caf87
commit 54a2e99c7b
11 changed files with 217 additions and 20 deletions

24
FORMATS
View File

@ -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"; }'

View File

@ -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).

24
MANUAL
View File

@ -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').

View File

@ -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;

View File

@ -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 T> class cConfig : public cList<T> {
private:
char *fileName;
@ -217,11 +230,14 @@ public:
cTimer *GetTimer(cTimer *Timer);
};
class cCommands : public cConfig<cCommand> {};
extern int CurrentGroup;
extern cChannels Channels;
extern cTimers Timers;
extern cKeys Keys;
extern cCommands Commands;
class cSetup {
private:

5
i18n.c
View File

@ -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",
},

75
menu.c
View File

@ -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) {

3
osd.h
View File

@ -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,

14
tools.c
View File

@ -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));

View File

@ -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);

3
vdr.c
View File

@ -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 <getopt.h>
@ -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