mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
- Implemented automatic shutdown (see INSTALL and MANUAL for details). - New SVDRP command NEXT to show the next timer event. - The new remote control key "Power" can be used to turn the VDR machine off (this requires the presence of the '-s' option). - Fixed code for the default "Ok" button on the PC keyboard (was 0x162 on the "good old" keyboards (with the F-keys at the left side), while it changed to 0x15E on the newer keyboards). - When a recording is edited, the summary information (if present) is now also copied. - When a recording is running on the primary interface, any attempt to change the current channel will now lead to a "Channel locked" message. - The main program loop now first checks whether any timer recordings are finished, before starting a new timer recording. This is important in case one timer ends at the same time another timer starts. - New setup parameter OSDMessageTime to define how long an OSD message shall be displayed. - The "File" parameter of a timer can now contain the '~' character to store the recording in a hierarchical directory structure. The '~' character has been chosen since the file system's directory delimiter '/' may be part of a regular programme name (showing the directory hierarchy in the "Recordings" menu will follow later). - Repeating timers now create recordings that contain the 'Subtitle' information from the EPG data in their file name. Typically (on tv stations that care about their viewers) this contains the episode title of a series. The subtitle is appended to the timer's file name, separated by a '~' character, so that it results in all recordings of this timer being collected in a common subdirectory. You can disable this with the 'UseSubtitle' parameter in the "Setup" menu. - The summary information is now taken from the EPG data at the actual time of recording (no longer at the time the timer is created in the "Schedule" menu). If a timer already has summary data, that data will be used. If you have repeating timers in your 'timers.conf', you may want to make sure they do NOT contain any summary information (that's the last field in the timer definitions). Use your favourite text editor to delete that information. That way every recording will store the actual summary data at the time of the recording.
511 lines
20 KiB
C
511 lines
20 KiB
C
/*
|
|
* vdr.c: Video Disk Recorder main program
|
|
*
|
|
* Copyright (C) 2000 Klaus Schmidinger
|
|
*
|
|
* 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.
|
|
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
* The author can be reached at kls@cadsoft.de
|
|
*
|
|
* The project's page is at http://www.cadsoft.de/people/kls/vdr
|
|
*
|
|
* $Id: vdr.c 1.68 2001/09/01 14:50:40 kls Exp $
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <getopt.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include "config.h"
|
|
#include "dvbapi.h"
|
|
#ifdef DVDSUPPORT
|
|
#include "dvd.h"
|
|
#endif //DVDSUPPORT
|
|
#include "i18n.h"
|
|
#include "interface.h"
|
|
#include "menu.h"
|
|
#include "recording.h"
|
|
#include "tools.h"
|
|
#include "videodir.h"
|
|
|
|
#ifdef REMOTE_KBD
|
|
#define KEYS_CONF "keys-pc.conf"
|
|
#else
|
|
#define KEYS_CONF "keys.conf"
|
|
#endif
|
|
|
|
#define ACTIVITYTIMEOUT 60 // seconds before starting housekeeping
|
|
#define SHUTDOWNWAIT 300 // seconds to wait in user prompt before automatic shutdown
|
|
|
|
static int Interrupted = 0;
|
|
|
|
static void SignalHandler(int signum)
|
|
{
|
|
if (signum != SIGPIPE) {
|
|
Interrupted = signum;
|
|
Interface->Interrupt();
|
|
}
|
|
signal(signum, SignalHandler);
|
|
}
|
|
|
|
static void Watchdog(int signum)
|
|
{
|
|
// Something terrible must have happened that prevented the 'alarm()' from
|
|
// being called in time, so let's get out of here:
|
|
esyslog(LOG_ERR, "PANIC: watchdog timer expired - exiting!");
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
// Command line options:
|
|
|
|
#define DEFAULTSVDRPPORT 2001
|
|
#define DEFAULTWATCHDOG 0 // seconds
|
|
|
|
int SVDRPport = DEFAULTSVDRPPORT;
|
|
const char *ConfigDirectory = NULL;
|
|
bool DaemonMode = false;
|
|
int WatchdogTimeout = DEFAULTWATCHDOG;
|
|
const char *Terminal = NULL;
|
|
const char *Shutdown = NULL;
|
|
|
|
static struct option long_options[] = {
|
|
{ "audio", required_argument, NULL, 'a' },
|
|
{ "config", required_argument, NULL, 'c' },
|
|
{ "daemon", no_argument, NULL, 'd' },
|
|
{ "device", required_argument, NULL, 'D' },
|
|
{ "epgfile", required_argument, NULL, 'E' },
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "log", required_argument, NULL, 'l' },
|
|
{ "port", required_argument, NULL, 'p' },
|
|
{ "shutdown", required_argument, NULL, 's' },
|
|
{ "terminal", required_argument, NULL, 't' },
|
|
{ "video", required_argument, NULL, 'v' },
|
|
{ "dvd", required_argument, NULL, 'V' },
|
|
{ "watchdog", required_argument, NULL, 'w' },
|
|
{ NULL }
|
|
};
|
|
|
|
int c;
|
|
int option_index = 0;
|
|
while ((c = getopt_long(argc, argv, "a:c:dD:E:hl:p:s:t:v:V:w:", long_options, &option_index)) != -1) {
|
|
switch (c) {
|
|
case 'a': cDvbApi::SetAudioCommand(optarg);
|
|
break;
|
|
case 'c': ConfigDirectory = optarg;
|
|
break;
|
|
case 'd': DaemonMode = true; break;
|
|
case 'D': if (isnumber(optarg)) {
|
|
int n = atoi(optarg);
|
|
if (0 <= n && n < MAXDVBAPI) {
|
|
cDvbApi::SetUseDvbApi(n);
|
|
break;
|
|
}
|
|
}
|
|
fprintf(stderr, "vdr: invalid DVB device number: %s\n", optarg);
|
|
return 2;
|
|
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 display 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"
|
|
" -p PORT, --port=PORT use PORT for SVDRP (default: %d)\n"
|
|
" 0 turns off SVDRP\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 DEV, --dvd=DEV use DEV as the DVD device (default: %s)\n"
|
|
" -w SEC, --watchdog=SEC activate the watchdog timer with a timeout of SEC\n"
|
|
" seconds (default: %d); '0' disables the watchdog\n"
|
|
"\n"
|
|
"Report bugs to <vdr-bugs@cadsoft.de>\n",
|
|
cSIProcessor::GetEpgDataFileName() ? cSIProcessor::GetEpgDataFileName() : "'-'",
|
|
DEFAULTSVDRPPORT,
|
|
VideoDirectory,
|
|
#ifdef DVDSUPPORT
|
|
cDVD::DeviceName(),
|
|
#else
|
|
"no DVD support",
|
|
#endif //DVDSUPPORT
|
|
DEFAULTWATCHDOG
|
|
);
|
|
return 0;
|
|
break;
|
|
case 'l': if (isnumber(optarg)) {
|
|
int l = atoi(optarg);
|
|
if (0 <= l && l <= 3) {
|
|
SysLogLevel = l;
|
|
break;
|
|
}
|
|
}
|
|
fprintf(stderr, "vdr: invalid log level: %s\n", optarg);
|
|
return 2;
|
|
break;
|
|
case 'p': if (isnumber(optarg))
|
|
SVDRPport = atoi(optarg);
|
|
else {
|
|
fprintf(stderr, "vdr: invalid port number: %s\n", optarg);
|
|
return 2;
|
|
}
|
|
break;
|
|
case 's': Shutdown = optarg;
|
|
break;
|
|
case 't': Terminal = optarg;
|
|
break;
|
|
case 'v': VideoDirectory = optarg;
|
|
while (optarg && *optarg && optarg[strlen(optarg) - 1] == '/')
|
|
optarg[strlen(optarg) - 1] = 0;
|
|
break;
|
|
case 'V':
|
|
#ifdef DVDSUPPORT
|
|
cDVD::SetDeviceName(optarg);
|
|
if (!cDVD::DriveExists()) {
|
|
fprintf(stderr, "vdr: DVD drive not found: %s\n", optarg);
|
|
return 2;
|
|
}
|
|
#else
|
|
fprintf(stderr, "vdr: DVD support has not been compiled in!");
|
|
return 2;
|
|
#endif //DVDSUPPORT
|
|
break;
|
|
case 'w': if (isnumber(optarg)) {
|
|
int t = atoi(optarg);
|
|
if (t >= 0) {
|
|
WatchdogTimeout = t;
|
|
break;
|
|
}
|
|
}
|
|
fprintf(stderr, "vdr: invalid watchdog timeout: %s\n", optarg);
|
|
return 2;
|
|
break;
|
|
default: return 2;
|
|
}
|
|
}
|
|
|
|
// Log file:
|
|
|
|
if (SysLogLevel > 0)
|
|
openlog("vdr", LOG_PID | LOG_CONS, LOG_USER);
|
|
|
|
// Check the video directory:
|
|
|
|
if (!DirectoryOk(VideoDirectory, true)) {
|
|
fprintf(stderr, "vdr: can't access video directory %s\n", VideoDirectory);
|
|
return 2;
|
|
}
|
|
|
|
// Daemon mode:
|
|
|
|
if (DaemonMode) {
|
|
#if !defined(DEBUG_OSD) && !defined(REMOTE_KBD)
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
fprintf(stderr, "%m\n");
|
|
esyslog(LOG_ERR, "ERROR: %m");
|
|
return 2;
|
|
}
|
|
if (pid != 0)
|
|
return 0; // initial program immediately returns
|
|
fclose(stdin);
|
|
fclose(stdout);
|
|
fclose(stderr);
|
|
#else
|
|
fprintf(stderr, "vdr: can't run in daemon mode with DEBUG_OSD or REMOTE_KBD on!\n");
|
|
return 2;
|
|
#endif
|
|
}
|
|
else if (Terminal) {
|
|
// Claim new controlling terminal
|
|
stdin = freopen(Terminal, "r", stdin);
|
|
stdout = freopen(Terminal, "w", stdout);
|
|
stderr = freopen(Terminal, "w", stderr);
|
|
}
|
|
|
|
isyslog(LOG_INFO, "VDR version %s started", VDRVERSION);
|
|
|
|
// Configuration data:
|
|
|
|
if (!ConfigDirectory)
|
|
ConfigDirectory = VideoDirectory;
|
|
|
|
Setup.Load(AddDirectory(ConfigDirectory, "setup.conf"));
|
|
Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"));
|
|
Timers.Load(AddDirectory(ConfigDirectory, "timers.conf"));
|
|
Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"));
|
|
#if defined(REMOTE_LIRC)
|
|
Keys.SetDummyValues();
|
|
#elif !defined(REMOTE_NONE)
|
|
bool KeysLoaded = Keys.Load(AddDirectory(ConfigDirectory, KEYS_CONF));
|
|
#endif
|
|
|
|
// DVB interfaces:
|
|
|
|
if (!cDvbApi::Init())
|
|
return 2;
|
|
|
|
cDvbApi::SetPrimaryDvbApi(Setup.PrimaryDVB);
|
|
|
|
Channels.SwitchTo(Setup.CurrentChannel);
|
|
|
|
cEITScanner EITScanner;
|
|
|
|
// User interface:
|
|
|
|
Interface = new cInterface(SVDRPport);
|
|
#if !defined(REMOTE_LIRC) && !defined(REMOTE_NONE)
|
|
if (!KeysLoaded)
|
|
Interface->LearnKeys();
|
|
#endif
|
|
|
|
// Signal handlers:
|
|
|
|
if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN);
|
|
if (signal(SIGINT, SignalHandler) == SIG_IGN) signal(SIGINT, SIG_IGN);
|
|
if (signal(SIGTERM, SignalHandler) == SIG_IGN) signal(SIGTERM, SIG_IGN);
|
|
if (signal(SIGPIPE, SignalHandler) == SIG_IGN) signal(SIGPIPE, SIG_IGN);
|
|
if (WatchdogTimeout > 0)
|
|
if (signal(SIGALRM, Watchdog) == SIG_IGN) signal(SIGALRM, SIG_IGN);
|
|
|
|
// Main program loop:
|
|
|
|
cOsdBase *Menu = NULL;
|
|
cReplayControl *ReplayControl = NULL;
|
|
int LastChannel = -1;
|
|
int PreviousChannel = cDvbApi::CurrentChannel();
|
|
time_t LastActivity = 0;
|
|
int MaxLatencyTime = 0;
|
|
bool ForceShutdown = false;
|
|
|
|
if (WatchdogTimeout > 0) {
|
|
dsyslog(LOG_INFO, "setting watchdog timer to %d seconds", WatchdogTimeout);
|
|
alarm(WatchdogTimeout); // Initial watchdog timer start
|
|
}
|
|
|
|
while (!Interrupted) {
|
|
// Handle emergency exits:
|
|
if (cThread::EmergencyExit()) {
|
|
esyslog(LOG_ERR, "emergency exit requested - shutting down");
|
|
break;
|
|
}
|
|
// Restart the Watchdog timer:
|
|
if (WatchdogTimeout > 0) {
|
|
int LatencyTime = WatchdogTimeout - alarm(WatchdogTimeout);
|
|
if (LatencyTime > MaxLatencyTime) {
|
|
MaxLatencyTime = LatencyTime;
|
|
dsyslog(LOG_INFO, "max. latency time %d seconds", MaxLatencyTime);
|
|
}
|
|
}
|
|
// Channel display:
|
|
if (!EITScanner.Active() && cDvbApi::CurrentChannel() != LastChannel) {
|
|
if (!Menu)
|
|
Menu = new cDisplayChannel(cDvbApi::CurrentChannel(), LastChannel > 0);
|
|
PreviousChannel = LastChannel;
|
|
LastChannel = cDvbApi::CurrentChannel();
|
|
}
|
|
// Timers and Recordings:
|
|
if (!Menu) {
|
|
time_t Now = time(NULL); // must do both following calls with the exact same time!
|
|
cRecordControls::Process(Now);
|
|
cTimer *Timer = Timers.GetMatch(Now);
|
|
if (Timer) {
|
|
if (!cRecordControls::Start(Timer))
|
|
Timer->SetPending(true);
|
|
}
|
|
}
|
|
// User Input:
|
|
cOsdBase **Interact = Menu ? &Menu : (cOsdBase **)&ReplayControl;
|
|
eKeys key = Interface->GetKey(!*Interact || !(*Interact)->NeedsFastResponse());
|
|
if (NORMALKEY(key) != kNone) {
|
|
EITScanner.Activity();
|
|
LastActivity = time(NULL);
|
|
}
|
|
if (*Interact) {
|
|
switch ((*Interact)->ProcessKey(key)) {
|
|
case osMenu: DELETENULL(Menu);
|
|
Menu = new cMenuMain(ReplayControl);
|
|
break;
|
|
case osRecord: DELETENULL(Menu);
|
|
if (!cRecordControls::Start())
|
|
Interface->Error(tr("No free DVB device to record!"));
|
|
break;
|
|
case osRecordings:
|
|
DELETENULL(Menu);
|
|
DELETENULL(ReplayControl);
|
|
Menu = new cMenuRecordings;
|
|
break;
|
|
case osReplay: DELETENULL(Menu);
|
|
DELETENULL(ReplayControl);
|
|
ReplayControl = new cReplayControl;
|
|
break;
|
|
#ifdef DVDSUPPORT
|
|
case osDVD: DELETENULL(Menu);
|
|
DELETENULL(ReplayControl);
|
|
Menu = new cMenuDVD;
|
|
break;
|
|
#endif //DVDSUPPORT
|
|
case osStopReplay:
|
|
DELETENULL(*Interact);
|
|
DELETENULL(ReplayControl);
|
|
break;
|
|
case osSwitchDvb:
|
|
DELETENULL(*Interact);
|
|
Interface->Info(tr("Switching primary DVB..."));
|
|
cDvbApi::SetPrimaryDvbApi(Setup.PrimaryDVB);
|
|
break;
|
|
case osBack:
|
|
case osEnd: DELETENULL(*Interact);
|
|
break;
|
|
default: ;
|
|
}
|
|
}
|
|
else {
|
|
switch (key) {
|
|
// Toggle channels:
|
|
case k0:
|
|
if (PreviousChannel != cDvbApi::CurrentChannel())
|
|
Channels.SwitchTo(PreviousChannel);
|
|
break;
|
|
// Direct Channel Select:
|
|
case k1 ... k9:
|
|
Menu = new cDisplayChannel(key);
|
|
break;
|
|
// Left/Right rotates trough channel groups:
|
|
case kLeft|k_Repeat:
|
|
case kLeft:
|
|
case kRight|k_Repeat:
|
|
case kRight: {
|
|
int SaveGroup = CurrentGroup;
|
|
if (NORMALKEY(key) == kRight)
|
|
CurrentGroup = Channels.GetNextGroup(CurrentGroup) ;
|
|
else
|
|
CurrentGroup = Channels.GetPrevGroup(CurrentGroup < 1 ? 1 : CurrentGroup);
|
|
if (CurrentGroup < 0)
|
|
CurrentGroup = SaveGroup;
|
|
Menu = new cDisplayChannel(CurrentGroup, false, true);
|
|
break;
|
|
}
|
|
// Up/Down Channel Select:
|
|
case kUp|k_Repeat:
|
|
case kUp:
|
|
case kDown|k_Repeat:
|
|
case kDown: {
|
|
int n = cDvbApi::CurrentChannel() + (NORMALKEY(key) == kUp ? 1 : -1);
|
|
cChannel *channel = Channels.GetByNumber(n);
|
|
if (channel)
|
|
channel->Switch();
|
|
break;
|
|
}
|
|
// Menu Control:
|
|
case kMenu: Menu = new cMenuMain(ReplayControl); break;
|
|
// Viewing Control:
|
|
case kOk: LastChannel = -1; break; // forces channel display
|
|
// Power off:
|
|
case kPower: isyslog(LOG_INFO, "Power button pressed");
|
|
if (!Shutdown) {
|
|
Interface->Error(tr("Can't shutdown - option '-s' not given!"));
|
|
break;
|
|
}
|
|
if (cRecordControls::Active()) {
|
|
if (Interface->Confirm(tr("Recording - shut down anyway?")))
|
|
ForceShutdown = true;
|
|
}
|
|
LastActivity = 1; // not 0, see below!
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
if (!Menu) {
|
|
EITScanner.Process();
|
|
cVideoCutter::Active();
|
|
}
|
|
if (!*Interact && (!cRecordControls::Active() || ForceShutdown)) {
|
|
time_t Now = time(NULL);
|
|
if (Now - LastActivity > ACTIVITYTIMEOUT) {
|
|
// Shutdown:
|
|
if (Shutdown && (Setup.MinUserInactivity && Now - LastActivity > Setup.MinUserInactivity * 60 || ForceShutdown)) {
|
|
ForceShutdown = false;
|
|
cTimer *timer = Timers.GetNextActiveTimer();
|
|
time_t Next = timer ? timer->StartTime() : 0;
|
|
time_t Delta = timer ? Next - Now : 0;
|
|
if (timer)
|
|
dsyslog(LOG_INFO, "next timer event at %s", ctime(&Next));
|
|
if (!Next || Delta > Setup.MinEventTimeout * 60) {
|
|
if (!LastActivity) {
|
|
// Apparently the user started VDR manually
|
|
dsyslog(LOG_INFO, "assuming manual start of VDR");
|
|
LastActivity = Now;
|
|
continue; // skip the rest of the housekeeping for now
|
|
}
|
|
if (WatchdogTimeout > 0)
|
|
signal(SIGALRM, SIG_IGN);
|
|
if (Interface->Confirm(tr("Press any key to cancel shutdown"), LastActivity == 1 ? 5 : SHUTDOWNWAIT, true)) {
|
|
char *cmd;
|
|
asprintf(&cmd, "%s %ld %ld", Shutdown, Next, Delta);
|
|
isyslog(LOG_INFO, "executing '%s'", cmd);
|
|
system(cmd);
|
|
delete cmd;
|
|
}
|
|
else if (WatchdogTimeout > 0) {
|
|
alarm(WatchdogTimeout);
|
|
if (signal(SIGALRM, Watchdog) == SIG_IGN)
|
|
signal(SIGALRM, SIG_IGN);
|
|
}
|
|
LastActivity = Now; // don't try again too soon
|
|
continue; // skip the rest of the housekeeping for now
|
|
}
|
|
}
|
|
// Disk housekeeping:
|
|
RemoveDeletedRecordings();
|
|
}
|
|
}
|
|
}
|
|
if (Interrupted)
|
|
isyslog(LOG_INFO, "caught signal %d", Interrupted);
|
|
Setup.CurrentChannel = cDvbApi::CurrentChannel();
|
|
Setup.Save();
|
|
cVideoCutter::Stop();
|
|
delete Menu;
|
|
delete ReplayControl;
|
|
delete Interface;
|
|
cDvbApi::Cleanup();
|
|
if (WatchdogTimeout > 0)
|
|
dsyslog(LOG_INFO, "max. latency time %d seconds", MaxLatencyTime);
|
|
isyslog(LOG_INFO, "exiting");
|
|
if (SysLogLevel > 0)
|
|
closelog();
|
|
if (cThread::EmergencyExit()) {
|
|
esyslog(LOG_ERR, "emergency exit!");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|