mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
- In order to avoid problems on NPTL systems, VDR now checks for the presence of NPTL at program start, and if it is, exits and tells the user to do 'export LD_ASSUME_KERNEL=2.4.1' before starting VDR. - Revisited the "Fixed missing audio after replaying a DVD" change because it introduced a sound disturbance when switching between channels on the same transponder (thanks to Marco Schlüßler). - In order to avoid problems on UTF-8 systems, VDR now checks for the presence of UTF-8 at program start, and if it is, exits and tells the user to turn off UTF-8 before starting VDR (thanks to Ludwig Nussel for pointing out a problem with systems that are set to use UTF-8). There are also problems in case the video partition is mounted with "iocharset=utf8" (thanks to Jörg Knitter for reporting this one). Please also read the "IMPORTANT NOTES" section in the INSTALL file! - Some changes to the SPU decoder interface (thanks to Sven Goethel). - Some improvements in cOsd creation (thanks to some suggestions by Jouni Karvo). - Fixed calculating the OSD width and height (thanks to Olaf Henkel for reporting a problem with long event texts in the "Classic VDR" skin). - Fixed switching channels while an encrypted channel is being recorded, because the channel was switched if the new channel was on the same transponder and was a radio channel or an unencrypted channel (thanks to Martin Dauskardt for reporting this one). - No longer using the external 'find' command to scan the video directory for recordings (based on a suggestion by Mirko Dölle). - The list of recordings is now kept statically in memory to avoid long delays when opening the "Recordings" menu. As a side effect, external modifications to the video directory are no longer immediately reflected in the "Recordings" menu. If a plugin manipulates the video directory in any way, it can call the function Recordings.TriggerUpdate() to trigger an update of the list of recordings. If some external tool manipulates the video directory, it can touch the file '.update' in the video directory to trigger an update of the list of recordings. - Fixed a memory leak in theme description handling (thanks to Sascha Volkenandt). - Added cDevice::Flush() to make sure that all data in the video card's buffers has been processed (thanks to Reinhard Nissl). Currently this is not yet actually implemented for FF DVB cards. - Fixed handling the color button texts in cMenuEditStrItem (thanks to Maynard Cedric for reporting this one). - Fixed the description of cRingBufferLinear (thanks to Ludwig Nussel for pointing out this one). - Fixed cRingBufferLinear::Get() in case the buffer wraps around (thanks to Ludwig Nussel for reporting this one).
915 lines
36 KiB
C
915 lines
36 KiB
C
/*
|
|
* vdr.c: Video Disk Recorder main program
|
|
*
|
|
* Copyright (C) 2000, 2003 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/vdr
|
|
*
|
|
* $Id: vdr.c 1.184 2004/06/13 13:52:09 kls Exp $
|
|
*/
|
|
|
|
#include <getopt.h>
|
|
#include <locale.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#include "audio.h"
|
|
#include "channels.h"
|
|
#include "config.h"
|
|
#include "cutter.h"
|
|
#include "device.h"
|
|
#include "diseqc.h"
|
|
#include "dvbdevice.h"
|
|
#include "eitscan.h"
|
|
#include "epg.h"
|
|
#include "i18n.h"
|
|
#include "interface.h"
|
|
#include "keys.h"
|
|
#include "lirc.h"
|
|
#include "menu.h"
|
|
#include "osdbase.h"
|
|
#include "plugin.h"
|
|
#include "rcu.h"
|
|
#include "recording.h"
|
|
#include "skinclassic.h"
|
|
#include "skinsttng.h"
|
|
#include "sources.h"
|
|
#include "themes.h"
|
|
#include "timers.h"
|
|
#include "tools.h"
|
|
#include "transfer.h"
|
|
#include "videodir.h"
|
|
|
|
#define MINCHANNELWAIT 10 // seconds to wait between failed channel switchings
|
|
#define ACTIVITYTIMEOUT 60 // seconds before starting housekeeping
|
|
#define SHUTDOWNWAIT 300 // seconds to wait in user prompt before automatic shutdown
|
|
#define MANUALSTART 600 // seconds the next timer must be in the future to assume manual start
|
|
|
|
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("PANIC: watchdog timer expired - exiting!");
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
#ifdef _CS_GNU_LIBPTHREAD_VERSION
|
|
// Check for NPTL and exit if present - VDR apparently doesn't run well with NPTL:
|
|
char LibPthreadVersion[128];
|
|
if (confstr(_CS_GNU_LIBPTHREAD_VERSION, LibPthreadVersion, sizeof(LibPthreadVersion) > 0)) {
|
|
if (strstr(LibPthreadVersion, "NPTL")) {
|
|
fprintf(stderr, "vdr: please turn off NPTL by setting 'export LD_ASSUME_KERNEL=2.4.1' before starting VDR\n");
|
|
return 2;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Check for UTF-8 and exit if present - asprintf() will fail if it encounters 8 bit ASCII codes
|
|
char *LangEnv;
|
|
if ((LangEnv = getenv("LANG")) != NULL && strcasestr(LangEnv, "utf") ||
|
|
(LangEnv = getenv("LC_TYPE")) != NULL && strcasestr(LangEnv, "utf")) {
|
|
fprintf(stderr, "vdr: please turn off UTF-8 before starting VDR\n");
|
|
return 2;
|
|
}
|
|
|
|
// Save terminal settings:
|
|
|
|
struct termios savedTm;
|
|
bool HasStdin = (tcgetpgrp(STDIN_FILENO) == getpid() || getppid() != (pid_t)1) && tcgetattr(STDIN_FILENO, &savedTm) == 0;
|
|
|
|
// Initiate locale:
|
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
// Command line options:
|
|
|
|
#define DEFAULTSVDRPPORT 2001
|
|
#define DEFAULTWATCHDOG 0 // seconds
|
|
#define DEFAULTPLUGINDIR PLUGINDIR
|
|
#define DEFAULTEPGDATAFILENAME "epg.data"
|
|
|
|
int SVDRPport = DEFAULTSVDRPPORT;
|
|
const char *AudioCommand = NULL;
|
|
const char *ConfigDirectory = NULL;
|
|
const char *EpgDataFileName = DEFAULTEPGDATAFILENAME;
|
|
bool DisplayHelp = false;
|
|
bool DisplayVersion = false;
|
|
bool DaemonMode = false;
|
|
int SysLogTarget = LOG_USER;
|
|
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' },
|
|
{ "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' },
|
|
{ "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' },
|
|
{ "terminal", required_argument, NULL, 't' },
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ "video", required_argument, NULL, 'v' },
|
|
{ "watchdog", required_argument, NULL, 'w' },
|
|
{ NULL }
|
|
};
|
|
|
|
int c;
|
|
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': AudioCommand = 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 < MAXDEVICES) {
|
|
cDevice::SetUseDevice(n);
|
|
break;
|
|
}
|
|
}
|
|
fprintf(stderr, "vdr: invalid DVB device number: %s\n", optarg);
|
|
return 2;
|
|
break;
|
|
case 'E': EpgDataFileName = (*optarg != '-' ? optarg : NULL);
|
|
break;
|
|
case 'h': DisplayHelp = true;
|
|
break;
|
|
case 'l': {
|
|
char *p = strchr(optarg, '.');
|
|
if (p)
|
|
*p = 0;
|
|
if (isnumber(optarg)) {
|
|
int l = atoi(optarg);
|
|
if (0 <= l && l <= 3) {
|
|
SysLogLevel = l;
|
|
if (!p)
|
|
break;
|
|
if (isnumber(p + 1)) {
|
|
int l = atoi(p + 1);
|
|
if (0 <= l && l <= 7) {
|
|
int targets[] = { LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7 };
|
|
SysLogTarget = targets[l];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (p)
|
|
*p = '.';
|
|
fprintf(stderr, "vdr: invalid log level: %s\n", optarg);
|
|
return 2;
|
|
}
|
|
break;
|
|
case 'L': if (access(optarg, R_OK | X_OK) == 0)
|
|
PluginManager.SetDirectory(optarg);
|
|
else {
|
|
fprintf(stderr, "vdr: can't access plugin directory: %s\n", optarg);
|
|
return 2;
|
|
}
|
|
break;
|
|
case 'm': MuteAudio = true;
|
|
break;
|
|
case 'p': if (isnumber(optarg))
|
|
SVDRPport = atoi(optarg);
|
|
else {
|
|
fprintf(stderr, "vdr: invalid port number: %s\n", optarg);
|
|
return 2;
|
|
}
|
|
break;
|
|
case 'P': PluginManager.AddPlugin(optarg);
|
|
break;
|
|
case 'r': cRecordingUserCommand::SetCommand(optarg);
|
|
break;
|
|
case 's': Shutdown = optarg;
|
|
break;
|
|
case 't': Terminal = optarg;
|
|
if (access(Terminal, R_OK | W_OK) < 0) {
|
|
fprintf(stderr, "vdr: can't access terminal: %s\n", Terminal);
|
|
return 2;
|
|
}
|
|
break;
|
|
case 'V': DisplayVersion = true;
|
|
break;
|
|
case 'v': VideoDirectory = optarg;
|
|
while (optarg && *optarg && optarg[strlen(optarg) - 1] == '/')
|
|
optarg[strlen(optarg) - 1] = 0;
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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' in the video directory)\n"
|
|
" '-E-' disables 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"
|
|
" if logging should be done to LOG_LOCALn instead of\n"
|
|
" LOG_USER, add '.n' to LEVEL, as in 3.7 (n=0..7)\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",
|
|
DEFAULTEPGDATAFILENAME,
|
|
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)
|
|
openlog("vdr", LOG_PID | LOG_CONS, SysLogTarget);
|
|
|
|
// 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) {
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
fprintf(stderr, "%m\n");
|
|
esyslog("ERROR: %m");
|
|
return 2;
|
|
}
|
|
if (pid != 0)
|
|
return 0; // initial program immediately returns
|
|
fclose(stdin);
|
|
fclose(stdout);
|
|
fclose(stderr);
|
|
}
|
|
else if (Terminal) {
|
|
// Claim new controlling terminal
|
|
stdin = freopen(Terminal, "r", stdin);
|
|
stdout = freopen(Terminal, "w", stdout);
|
|
stderr = freopen(Terminal, "w", stderr);
|
|
HasStdin = true;
|
|
}
|
|
|
|
isyslog("VDR version %s started", VDRVERSION);
|
|
|
|
// Load plugins:
|
|
|
|
if (!PluginManager.LoadPlugins(true))
|
|
return 2;
|
|
|
|
// Configuration data:
|
|
|
|
if (!ConfigDirectory)
|
|
ConfigDirectory = VideoDirectory;
|
|
|
|
cPlugin::SetConfigDirectory(ConfigDirectory);
|
|
cThemes::SetThemesDirectory(AddDirectory(ConfigDirectory, "themes"));
|
|
|
|
Setup.Load(AddDirectory(ConfigDirectory, "setup.conf"));
|
|
if (!(Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true) &&
|
|
Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC) &&
|
|
Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true) &&
|
|
Timers.Load(AddDirectory(ConfigDirectory, "timers.conf")) &&
|
|
Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"), true) &&
|
|
RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"), true) &&
|
|
SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true) &&
|
|
CaDefinitions.Load(AddDirectory(ConfigDirectory, "ca.conf"), true) &&
|
|
Keys.Load(AddDirectory(ConfigDirectory, "remote.conf")) &&
|
|
KeyMacros.Load(AddDirectory(ConfigDirectory, "keymacros.conf"), true)
|
|
))
|
|
return 2;
|
|
|
|
cFont::SetCode(I18nCharSets()[Setup.OSDLanguage]);
|
|
|
|
// EPG data:
|
|
|
|
if (EpgDataFileName) {
|
|
if (DirectoryOk(EpgDataFileName))
|
|
EpgDataFileName = AddDirectory(EpgDataFileName, DEFAULTEPGDATAFILENAME);
|
|
else if (*EpgDataFileName != '/' && *EpgDataFileName != '.')
|
|
EpgDataFileName = AddDirectory(VideoDirectory, EpgDataFileName);
|
|
}
|
|
cSchedules::SetEpgDataFileName(EpgDataFileName);
|
|
cSchedules::Read();
|
|
|
|
// DVB interfaces:
|
|
|
|
cDvbDevice::Initialize();
|
|
|
|
// Initialize plugins:
|
|
|
|
if (!PluginManager.InitializePlugins())
|
|
return 2;
|
|
|
|
// Primary device:
|
|
|
|
cDevice::SetPrimaryDevice(Setup.PrimaryDVB);
|
|
if (!cDevice::PrimaryDevice() || !cDevice::PrimaryDevice()->HasDecoder()) {
|
|
if (cDevice::PrimaryDevice() && !cDevice::PrimaryDevice()->HasDecoder())
|
|
isyslog("device %d has no MPEG decoder", cDevice::PrimaryDevice()->DeviceNumber() + 1);
|
|
for (int i = 0; i < cDevice::NumDevices(); i++) {
|
|
cDevice *d = cDevice::GetDevice(i);
|
|
if (d && d->HasDecoder()) {
|
|
isyslog("trying device number %d instead", i + 1);
|
|
if (cDevice::SetPrimaryDevice(i + 1)) {
|
|
Setup.PrimaryDVB = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!cDevice::PrimaryDevice()) {
|
|
const char *msg = "no primary device found - using first device!";
|
|
fprintf(stderr, "vdr: %s\n", msg);
|
|
esyslog("ERROR: %s", msg);
|
|
if (!cDevice::SetPrimaryDevice(1))
|
|
return 2;
|
|
if (!cDevice::PrimaryDevice()) {
|
|
const char *msg = "no primary device found - giving up!";
|
|
fprintf(stderr, "vdr: %s\n", msg);
|
|
esyslog("ERROR: %s", msg);
|
|
return 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// User interface:
|
|
|
|
Interface = new cInterface(SVDRPport);
|
|
|
|
// Start plugins:
|
|
|
|
if (!PluginManager.StartPlugins())
|
|
return 2;
|
|
|
|
// Skins:
|
|
|
|
new cSkinClassic;
|
|
new cSkinSTTNG;
|
|
Skins.SetCurrent(Setup.OSDSkin);
|
|
cThemes::Load(Skins.Current()->Name(), Setup.OSDTheme, Skins.Current()->Theme());
|
|
|
|
// Remote Controls:
|
|
#if defined(REMOTE_RCU)
|
|
new cRcuRemote("/dev/ttyS1");
|
|
#elif defined(REMOTE_LIRC)
|
|
new cLircRemote("/dev/lircd");
|
|
#endif
|
|
#if defined(REMOTE_KBD)
|
|
if (!DaemonMode && HasStdin)
|
|
new cKbdRemote;
|
|
#endif
|
|
Interface->LearnKeys();
|
|
|
|
// External audio:
|
|
|
|
if (AudioCommand)
|
|
new cExternalAudio(AudioCommand);
|
|
|
|
// Channel:
|
|
|
|
Channels.SwitchTo(Setup.CurrentChannel);
|
|
if (MuteAudio)
|
|
cDevice::PrimaryDevice()->ToggleMute();
|
|
else
|
|
cDevice::PrimaryDevice()->SetVolume(Setup.CurrentVolume, true);
|
|
|
|
// Recordings:
|
|
|
|
Recordings.Load();
|
|
|
|
// 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);
|
|
|
|
// Watchdog:
|
|
|
|
if (WatchdogTimeout > 0) {
|
|
dsyslog("setting watchdog timer to %d seconds", WatchdogTimeout);
|
|
alarm(WatchdogTimeout); // Initial watchdog timer start
|
|
}
|
|
|
|
// Main program loop:
|
|
|
|
cOsdObject *Menu = NULL;
|
|
cOsdObject *Temp = NULL;
|
|
int LastChannel = -1;
|
|
int LastTimerChannel = -1;
|
|
int PreviousChannel[2] = { 1, 1 };
|
|
int PreviousChannelIndex = 0;
|
|
time_t LastChannelChanged = time(NULL);
|
|
time_t LastActivity = 0;
|
|
int MaxLatencyTime = 0;
|
|
bool ForceShutdown = false;
|
|
bool UserShutdown = false;
|
|
bool TimerInVpsMargin = false;
|
|
|
|
while (!Interrupted) {
|
|
// Handle emergency exits:
|
|
if (cThread::EmergencyExit()) {
|
|
esyslog("emergency exit requested - shutting down");
|
|
break;
|
|
}
|
|
// Attach launched player control:
|
|
cControl::Attach();
|
|
// Make sure we have a visible programme in case device usage has changed:
|
|
if (!EITScanner.Active() && cDevice::PrimaryDevice()->HasDecoder() && !cDevice::PrimaryDevice()->HasProgramme()) {
|
|
static time_t lastTime = 0;
|
|
if (time(NULL) - lastTime > MINCHANNELWAIT) {
|
|
cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel());
|
|
if (Channel && (Channel->Vpid() || Channel->Apid1())) {
|
|
if (!Channels.SwitchTo(cDevice::CurrentChannel()) // try to switch to the original channel...
|
|
&& !(LastTimerChannel > 0 && Channels.SwitchTo(LastTimerChannel)) // ...or the one used by the last timer...
|
|
&& !cDevice::SwitchChannel(1) // ...or the next higher available one...
|
|
&& !cDevice::SwitchChannel(-1)) // ...or the next lower available one
|
|
;
|
|
}
|
|
lastTime = time(NULL); // don't do this too often
|
|
LastTimerChannel = -1;
|
|
}
|
|
}
|
|
// Restart the Watchdog timer:
|
|
if (WatchdogTimeout > 0) {
|
|
int LatencyTime = WatchdogTimeout - alarm(WatchdogTimeout);
|
|
if (LatencyTime > MaxLatencyTime) {
|
|
MaxLatencyTime = LatencyTime;
|
|
dsyslog("max. latency time %d seconds", MaxLatencyTime);
|
|
}
|
|
}
|
|
// Handle channel modifications:
|
|
if (!Channels.BeingEdited() && Channels.Modified()) {
|
|
if (Channels.Lock(false, 100)) {
|
|
Channels.Save(); //XXX only after user changes???
|
|
Timers.Save();
|
|
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
|
if (Channel->Modification(CHANNELMOD_RETUNE)) {
|
|
cRecordControls::ChannelDataModified(Channel);
|
|
if (Channel->Number() == cDevice::CurrentChannel()) {
|
|
if (!cDevice::PrimaryDevice()->Replaying() || cTransferControl::ReceiverDevice()) {
|
|
if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
|
|
isyslog("retuning due to modification of channel %d", Channel->Number());
|
|
Channels.SwitchTo(Channel->Number());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Channels.Unlock();
|
|
}
|
|
}
|
|
// Channel display:
|
|
if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) {
|
|
if (!Menu)
|
|
Menu = Temp = new cDisplayChannel(cDevice::CurrentChannel(), LastChannel > 0);
|
|
LastChannel = cDevice::CurrentChannel();
|
|
LastChannelChanged = time(NULL);
|
|
}
|
|
if (time(NULL) - LastChannelChanged >= Setup.ZapTimeout && LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])
|
|
PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel;
|
|
// Timers and Recordings:
|
|
if (!Timers.BeingEdited()) {
|
|
// Assign events to timers:
|
|
if (time(NULL) - LastActivity > 10) {
|
|
static time_t LastSetEvents = 0;//XXX trigger by actual EPG data modification???
|
|
if (time(NULL) - LastSetEvents > 5) {
|
|
Timers.SetEvents();
|
|
LastSetEvents = time(NULL);
|
|
}
|
|
}
|
|
time_t Now = time(NULL); // must do all following calls with the exact same time!
|
|
// Process ongoing recordings:
|
|
cRecordControls::Process(Now);
|
|
// Start new recordings:
|
|
cTimer *Timer = Timers.GetMatch(Now);
|
|
if (Timer) {
|
|
if (!cRecordControls::Start(Timer))
|
|
Timer->SetPending(true);
|
|
else
|
|
LastTimerChannel = Timer->Channel()->Number();
|
|
}
|
|
// Make sure VPS timers "see" their channel early enough:
|
|
TimerInVpsMargin = false;
|
|
for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
|
|
if (Timer->HasFlags(tfActive | tfVps) && !Timer->Recording() && !Timer->Pending() && Timer->Matches(Now + Setup.VpsMargin, true)) {
|
|
if (!Timer->InVpsMargin()) {
|
|
Timer->SetInVpsMargin(true);
|
|
//XXX if not primary device has TP???
|
|
LastTimerChannel = Timer->Channel()->Number();
|
|
cRecordControls::Start(Timer); // will only switch the device
|
|
}
|
|
}
|
|
else
|
|
Timer->SetInVpsMargin(false);
|
|
if (Timer->InVpsMargin())
|
|
TimerInVpsMargin = true;
|
|
}
|
|
}
|
|
if (!Menu && Recordings.NeedsUpdate())
|
|
Recordings.Load();
|
|
// CAM control:
|
|
if (!Menu && !cOsd::IsOpen())
|
|
Menu = CamControl();
|
|
// User Input:
|
|
cOsdObject *Interact = Menu ? Menu : cControl::Control();
|
|
eKeys key = Interface->GetKey(!Interact || !Interact->NeedsFastResponse());
|
|
if (NORMALKEY(key) != kNone) {
|
|
EITScanner.Activity();
|
|
LastActivity = time(NULL);
|
|
}
|
|
// Keys that must work independent of any interactive mode:
|
|
switch (key) {
|
|
// Menu control:
|
|
case kMenu:
|
|
key = kNone; // nobody else needs to see this key
|
|
if (Menu) {
|
|
DELETENULL(Menu);
|
|
if (!Temp)
|
|
break;
|
|
}
|
|
if (cControl::Control())
|
|
cControl::Control()->Hide();
|
|
Menu = new cMenuMain(cControl::Control());
|
|
Temp = NULL;
|
|
break;
|
|
#define DirectMainFunction(function...)\
|
|
DELETENULL(Menu);\
|
|
if (cControl::Control())\
|
|
cControl::Control()->Hide();\
|
|
Menu = new cMenuMain(cControl::Control(), function);\
|
|
Temp = NULL;\
|
|
key = kNone; // nobody else needs to see this key
|
|
case kSchedule: DirectMainFunction(osSchedule); break;
|
|
case kChannels: DirectMainFunction(osChannels); break;
|
|
case kTimers: DirectMainFunction(osTimers); break;
|
|
case kRecordings: DirectMainFunction(osRecordings); break;
|
|
case kSetup: DirectMainFunction(osSetup); break;
|
|
case kCommands: DirectMainFunction(osCommands); break;
|
|
case kUser1 ... kUser9: cRemote::PutMacro(key); key = kNone; break;
|
|
case k_Plugin: DirectMainFunction(osPlugin, cRemote::GetPlugin()); break;
|
|
// Channel up/down:
|
|
case kChanUp|k_Repeat:
|
|
case kChanUp:
|
|
case kChanDn|k_Repeat:
|
|
case kChanDn:
|
|
cDevice::SwitchChannel(NORMALKEY(key) == kChanUp ? 1 : -1);
|
|
break;
|
|
// Volume Control:
|
|
case kVolUp|k_Repeat:
|
|
case kVolUp:
|
|
case kVolDn|k_Repeat:
|
|
case kVolDn:
|
|
case kMute:
|
|
if (key == kMute) {
|
|
if (!cDevice::PrimaryDevice()->ToggleMute() && !Menu) {
|
|
key = kNone; // nobody else needs to see these keys
|
|
break; // no need to display "mute off"
|
|
}
|
|
}
|
|
else
|
|
cDevice::PrimaryDevice()->SetVolume(NORMALKEY(key) == kVolDn ? -VOLUMEDELTA : VOLUMEDELTA);
|
|
if (!Menu && !cOsd::IsOpen())
|
|
Menu = Temp = cDisplayVolume::Create();
|
|
cDisplayVolume::Process(key);
|
|
key = kNone; // nobody else needs to see these keys
|
|
break;
|
|
// Pausing live video:
|
|
case kPause:
|
|
if (!cControl::Control()) {
|
|
DELETENULL(Menu);
|
|
Temp = NULL;
|
|
if (!cRecordControls::PauseLiveVideo())
|
|
Skins.Message(mtError, tr("No free DVB device to record!"));
|
|
key = kNone; // nobody else needs to see this key
|
|
}
|
|
break;
|
|
// Instant recording:
|
|
case kRecord:
|
|
if (!cControl::Control()) {
|
|
if (cRecordControls::Start())
|
|
;//XXX Skins.Message(mtInfo, tr("Recording"));
|
|
else
|
|
Skins.Message(mtError, tr("No free DVB device to record!"));
|
|
key = kNone; // nobody else needs to see this key
|
|
}
|
|
break;
|
|
// Power off:
|
|
case kPower: isyslog("Power button pressed");
|
|
DELETENULL(Menu);
|
|
cControl::Shutdown();
|
|
Temp = NULL;
|
|
if (!Shutdown) {
|
|
Skins.Message(mtError, 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!
|
|
UserShutdown = true;
|
|
break;
|
|
default: break;
|
|
}
|
|
Interact = Menu ? Menu : cControl::Control(); // might have been closed in the mean time
|
|
if (Interact) {
|
|
eOSState state = Interact->ProcessKey(key);
|
|
if (state == osUnknown && ISMODELESSKEY(key) && cControl::Control() && Interact != cControl::Control())
|
|
state = cControl::Control()->ProcessKey(key);
|
|
switch (state) {
|
|
case osPause: DELETENULL(Menu);
|
|
cControl::Shutdown(); // just in case
|
|
Temp = NULL;
|
|
if (!cRecordControls::PauseLiveVideo())
|
|
Skins.Message(mtError, tr("No free DVB device to record!"));
|
|
break;
|
|
case osRecord: DELETENULL(Menu);
|
|
Temp = NULL;
|
|
if (cRecordControls::Start())
|
|
;//XXX Skins.Message(mtInfo, tr("Recording"));
|
|
else
|
|
Skins.Message(mtError, tr("No free DVB device to record!"));
|
|
break;
|
|
case osRecordings:
|
|
DELETENULL(Menu);
|
|
cControl::Shutdown();
|
|
Temp = NULL;
|
|
Menu = new cMenuMain(false, osRecordings);
|
|
break;
|
|
case osReplay: DELETENULL(Menu);
|
|
cControl::Shutdown();
|
|
Temp = NULL;
|
|
cControl::Launch(new cReplayControl);
|
|
break;
|
|
case osStopReplay:
|
|
DELETENULL(Menu);
|
|
cControl::Shutdown();
|
|
Temp = NULL;
|
|
break;
|
|
case osSwitchDvb:
|
|
DELETENULL(Menu);
|
|
cControl::Shutdown();
|
|
Temp = NULL;
|
|
Skins.Message(mtInfo, tr("Switching primary DVB..."));
|
|
cDevice::SetPrimaryDevice(Setup.PrimaryDVB);
|
|
break;
|
|
case osPlugin: DELETENULL(Menu);
|
|
Menu = Temp = cMenuMain::PluginOsdObject();
|
|
if (Menu)
|
|
Menu->Show();
|
|
break;
|
|
case osBack:
|
|
case osEnd: if (Interact == Menu)
|
|
DELETENULL(Menu);
|
|
else
|
|
cControl::Shutdown();
|
|
Temp = NULL;
|
|
break;
|
|
default: ;
|
|
}
|
|
}
|
|
else {
|
|
// Key functions in "normal" viewing mode:
|
|
switch (key) {
|
|
// Toggle channels:
|
|
case k0: {
|
|
if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel || LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])
|
|
PreviousChannelIndex ^= 1;
|
|
Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
|
|
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:
|
|
Menu = new cDisplayChannel(NORMALKEY(key));
|
|
break;
|
|
// Up/Down Channel Select:
|
|
case kUp|k_Repeat:
|
|
case kUp:
|
|
case kDown|k_Repeat:
|
|
case kDown:
|
|
cDevice::SwitchChannel(NORMALKEY(key) == kUp ? 1 : -1);
|
|
break;
|
|
// Viewing Control:
|
|
case kOk: LastChannel = -1; break; // forces channel display
|
|
// Key macros:
|
|
case kRed:
|
|
case kGreen:
|
|
case kYellow:
|
|
case kBlue: cRemote::PutMacro(key); break;
|
|
default: break;
|
|
}
|
|
}
|
|
if (!Menu) {
|
|
if (!TimerInVpsMargin)
|
|
EITScanner.Process();
|
|
if (!cCutter::Active() && cCutter::Ended()) {
|
|
if (cCutter::Error())
|
|
Skins.Message(mtError, tr("Editing process failed!"));
|
|
else
|
|
Skins.Message(mtInfo, tr("Editing process finished"));
|
|
}
|
|
}
|
|
if (!Interact && ((!cRecordControls::Active() && !cCutter::Active() && (!Interface->HasSVDRPConnection() || UserShutdown)) || ForceShutdown)) {
|
|
time_t Now = time(NULL);
|
|
if (Now - LastActivity > ACTIVITYTIMEOUT) {
|
|
// Shutdown:
|
|
if (Shutdown && (Setup.MinUserInactivity || LastActivity == 1) && Now - LastActivity > Setup.MinUserInactivity * 60) {
|
|
cTimer *timer = Timers.GetNextActiveTimer();
|
|
time_t Next = timer ? timer->StartTime() : 0;
|
|
time_t Delta = timer ? Next - Now : 0;
|
|
if (!LastActivity) {
|
|
if (!timer || Delta > MANUALSTART) {
|
|
// Apparently the user started VDR manually
|
|
dsyslog("assuming manual start of VDR");
|
|
LastActivity = Now;
|
|
continue; // don't run into the actual shutdown procedure below
|
|
}
|
|
else
|
|
LastActivity = 1;
|
|
}
|
|
if (UserShutdown && Next && Delta <= Setup.MinEventTimeout * 60 && !ForceShutdown) {
|
|
char *buf;
|
|
asprintf(&buf, tr("Recording in %d minutes, shut down anyway?"), Delta / 60);
|
|
if (Interface->Confirm(buf))
|
|
ForceShutdown = true;
|
|
else
|
|
UserShutdown = false;
|
|
free(buf);
|
|
}
|
|
if (!Next || Delta > Setup.MinEventTimeout * 60 || ForceShutdown) {
|
|
ForceShutdown = false;
|
|
if (timer)
|
|
dsyslog("next timer event at %s", ctime(&Next));
|
|
if (WatchdogTimeout > 0)
|
|
signal(SIGALRM, SIG_IGN);
|
|
if (Interface->Confirm(tr("Press any key to cancel shutdown"), UserShutdown ? 5 : SHUTDOWNWAIT, true)) {
|
|
int Channel = timer ? timer->Channel()->Number() : 0;
|
|
const char *File = timer ? timer->File() : "";
|
|
Delta = Next - time(NULL); // compensates for Confirm() timeout
|
|
char *cmd;
|
|
asprintf(&cmd, "%s %ld %ld %d \"%s\" %d", Shutdown, Next, Delta, Channel, strescape(File, "\"$"), UserShutdown);
|
|
isyslog("executing '%s'", cmd);
|
|
SystemExec(cmd);
|
|
free(cmd);
|
|
}
|
|
else if (WatchdogTimeout > 0) {
|
|
alarm(WatchdogTimeout);
|
|
if (signal(SIGALRM, Watchdog) == SIG_IGN)
|
|
signal(SIGALRM, SIG_IGN);
|
|
}
|
|
LastActivity = time(NULL); // don't try again too soon
|
|
UserShutdown = false;
|
|
continue; // skip the rest of the housekeeping for now
|
|
}
|
|
}
|
|
// Disk housekeeping:
|
|
RemoveDeletedRecordings();
|
|
cSchedules::Cleanup();
|
|
// Plugins housekeeping:
|
|
PluginManager.Housekeeping();
|
|
}
|
|
}
|
|
}
|
|
if (Interrupted)
|
|
isyslog("caught signal %d", Interrupted);
|
|
cRecordControls::Shutdown();
|
|
cCutter::Stop();
|
|
delete Menu;
|
|
cControl::Shutdown();
|
|
delete Interface;
|
|
cOsdProvider::Shutdown();
|
|
Remotes.Clear();
|
|
Audios.Clear();
|
|
Skins.Clear();
|
|
Setup.CurrentChannel = cDevice::CurrentChannel();
|
|
Setup.CurrentVolume = cDevice::CurrentVolume();
|
|
Setup.Save();
|
|
cDevice::Shutdown();
|
|
PluginManager.Shutdown(true);
|
|
ReportEpgBugFixStats();
|
|
if (WatchdogTimeout > 0)
|
|
dsyslog("max. latency time %d seconds", MaxLatencyTime);
|
|
isyslog("exiting");
|
|
if (SysLogLevel > 0)
|
|
closelog();
|
|
if (HasStdin)
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &savedTm);
|
|
if (cThread::EmergencyExit()) {
|
|
esyslog("emergency exit!");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|