vdr
1.7.27
|
00001 /* 00002 * vdr.c: Video Disk Recorder main program 00003 * 00004 * Copyright (C) 2000, 2003, 2006, 2008 Klaus Schmidinger 00005 * 00006 * This program is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU General Public License 00008 * as published by the Free Software Foundation; either version 2 00009 * of the License, or (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License 00017 * along with this program; if not, write to the Free Software 00018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00019 * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 00020 * 00021 * The author can be reached at kls@tvdr.de 00022 * 00023 * The project's page is at http://www.tvdr.de 00024 * 00025 * $Id: vdr.c 2.35 2012/03/14 09:09:19 kls Exp $ 00026 */ 00027 00028 #include <getopt.h> 00029 #include <grp.h> 00030 #include <langinfo.h> 00031 #include <locale.h> 00032 #include <pwd.h> 00033 #include <signal.h> 00034 #include <stdlib.h> 00035 #include <sys/capability.h> 00036 #include <sys/prctl.h> 00037 #include <termios.h> 00038 #include <unistd.h> 00039 #include "audio.h" 00040 #include "channels.h" 00041 #include "config.h" 00042 #include "cutter.h" 00043 #include "device.h" 00044 #include "diseqc.h" 00045 #include "dvbdevice.h" 00046 #include "eitscan.h" 00047 #include "epg.h" 00048 #include "filetransfer.h" 00049 #include "i18n.h" 00050 #include "interface.h" 00051 #include "keys.h" 00052 #include "libsi/si.h" 00053 #include "lirc.h" 00054 #include "menu.h" 00055 #include "osdbase.h" 00056 #include "plugin.h" 00057 #include "recording.h" 00058 #include "shutdown.h" 00059 #include "skinclassic.h" 00060 #include "skinsttng.h" 00061 #include "sourceparams.h" 00062 #include "sources.h" 00063 #include "themes.h" 00064 #include "timers.h" 00065 #include "tools.h" 00066 #include "transfer.h" 00067 #include "videodir.h" 00068 00069 #define MINCHANNELWAIT 10 // seconds to wait between failed channel switchings 00070 #define ACTIVITYTIMEOUT 60 // seconds before starting housekeeping 00071 #define SHUTDOWNWAIT 300 // seconds to wait in user prompt before automatic shutdown 00072 #define SHUTDOWNRETRY 360 // seconds before trying again to shut down 00073 #define SHUTDOWNFORCEPROMPT 5 // seconds to wait in user prompt to allow forcing shutdown 00074 #define SHUTDOWNCANCELPROMPT 5 // seconds to wait in user prompt to allow canceling shutdown 00075 #define RESTARTCANCELPROMPT 5 // seconds to wait in user prompt before restarting on SIGHUP 00076 #define MANUALSTART 600 // seconds the next timer must be in the future to assume manual start 00077 #define CHANNELSAVEDELTA 600 // seconds before saving channels.conf after automatic modifications 00078 #define DEVICEREADYTIMEOUT 30 // seconds to wait until all devices are ready 00079 #define MENUTIMEOUT 120 // seconds of user inactivity after which an OSD display is closed 00080 #define TIMERCHECKDELTA 10 // seconds between checks for timers that need to see their channel 00081 #define TIMERDEVICETIMEOUT 8 // seconds before a device used for timer check may be reused 00082 #define TIMERLOOKAHEADTIME 60 // seconds before a non-VPS timer starts and the channel is switched if possible 00083 #define VPSLOOKAHEADTIME 24 // hours within which VPS timers will make sure their events are up to date 00084 #define VPSUPTODATETIME 3600 // seconds before the event or schedule of a VPS timer needs to be refreshed 00085 00086 #define EXIT(v) { ShutdownHandler.Exit(v); goto Exit; } 00087 00088 static int LastSignal = 0; 00089 00090 static bool SetUser(const char *UserName, bool UserDump)//XXX name? 00091 { 00092 if (UserName) { 00093 struct passwd *user = getpwnam(UserName); 00094 if (!user) { 00095 fprintf(stderr, "vdr: unknown user: '%s'\n", UserName); 00096 return false; 00097 } 00098 if (setgid(user->pw_gid) < 0) { 00099 fprintf(stderr, "vdr: cannot set group id %u: %s\n", (unsigned int)user->pw_gid, strerror(errno)); 00100 return false; 00101 } 00102 if (initgroups(user->pw_name, user->pw_gid) < 0) { 00103 fprintf(stderr, "vdr: cannot set supplemental group ids for user %s: %s\n", user->pw_name, strerror(errno)); 00104 return false; 00105 } 00106 if (setuid(user->pw_uid) < 0) { 00107 fprintf(stderr, "vdr: cannot set user id %u: %s\n", (unsigned int)user->pw_uid, strerror(errno)); 00108 return false; 00109 } 00110 if (UserDump && prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) 00111 fprintf(stderr, "vdr: warning - cannot set dumpable: %s\n", strerror(errno)); 00112 } 00113 return true; 00114 } 00115 00116 static bool DropCaps(void) 00117 { 00118 // drop all capabilities except selected ones 00119 cap_t caps = cap_from_text("= cap_sys_nice,cap_sys_time,cap_net_raw=ep"); 00120 if (!caps) { 00121 fprintf(stderr, "vdr: cap_from_text failed: %s\n", strerror(errno)); 00122 return false; 00123 } 00124 if (cap_set_proc(caps) == -1) { 00125 fprintf(stderr, "vdr: cap_set_proc failed: %s\n", strerror(errno)); 00126 cap_free(caps); 00127 return false; 00128 } 00129 cap_free(caps); 00130 return true; 00131 } 00132 00133 static bool SetKeepCaps(bool On) 00134 { 00135 // set keeping capabilities during setuid() on/off 00136 if (prctl(PR_SET_KEEPCAPS, On ? 1 : 0, 0, 0, 0) != 0) { 00137 fprintf(stderr, "vdr: prctl failed\n"); 00138 return false; 00139 } 00140 return true; 00141 } 00142 00143 static void SignalHandler(int signum) 00144 { 00145 switch (signum) { 00146 case SIGPIPE: 00147 break; 00148 case SIGHUP: 00149 LastSignal = signum; 00150 break; 00151 default: 00152 LastSignal = signum; 00153 Interface->Interrupt(); 00154 ShutdownHandler.Exit(0); 00155 } 00156 signal(signum, SignalHandler); 00157 } 00158 00159 static void Watchdog(int signum) 00160 { 00161 // Something terrible must have happened that prevented the 'alarm()' from 00162 // being called in time, so let's get out of here: 00163 esyslog("PANIC: watchdog timer expired - exiting!"); 00164 exit(1); 00165 } 00166 00167 int main(int argc, char *argv[]) 00168 { 00169 // Save terminal settings: 00170 00171 struct termios savedTm; 00172 bool HasStdin = (tcgetpgrp(STDIN_FILENO) == getpid() || getppid() != (pid_t)1) && tcgetattr(STDIN_FILENO, &savedTm) == 0; 00173 00174 // Initiate locale: 00175 00176 setlocale(LC_ALL, ""); 00177 setlocale(LC_NUMERIC, "C"); // makes sure any floating point numbers written use a decimal point 00178 00179 // Command line options: 00180 00181 #define DEFAULTSVDRPPORT 6419 00182 #define DEFAULTWATCHDOG 0 // seconds 00183 #define DEFAULTCONFDIR CONFDIR 00184 #define DEFAULTPLUGINDIR PLUGINDIR 00185 #define DEFAULTEPGDATAFILENAME "epg.data" 00186 00187 bool StartedAsRoot = false; 00188 const char *VdrUser = NULL; 00189 bool UserDump = false; 00190 int SVDRPport = DEFAULTSVDRPPORT; 00191 const char *AudioCommand = NULL; 00192 const char *ConfigDirectory = NULL; 00193 const char *EpgDataFileName = DEFAULTEPGDATAFILENAME; 00194 bool DisplayHelp = false; 00195 bool DisplayVersion = false; 00196 bool DaemonMode = false; 00197 int SysLogTarget = LOG_USER; 00198 bool MuteAudio = false; 00199 int WatchdogTimeout = DEFAULTWATCHDOG; 00200 const char *Terminal = NULL; 00201 const char *LocaleDir = NULL; 00202 00203 bool UseKbd = true; 00204 const char *LircDevice = NULL; 00205 #if !defined(REMOTE_KBD) 00206 UseKbd = false; 00207 #endif 00208 #if defined(REMOTE_LIRC) 00209 LircDevice = LIRC_DEVICE; 00210 #endif 00211 #if defined(VDR_USER) 00212 VdrUser = VDR_USER; 00213 #endif 00214 00215 cPluginManager PluginManager(DEFAULTPLUGINDIR); 00216 00217 static struct option long_options[] = { 00218 { "audio", required_argument, NULL, 'a' }, 00219 { "config", required_argument, NULL, 'c' }, 00220 { "daemon", no_argument, NULL, 'd' }, 00221 { "device", required_argument, NULL, 'D' }, 00222 { "edit", required_argument, NULL, 'e' | 0x100 }, 00223 { "epgfile", required_argument, NULL, 'E' }, 00224 { "filesize", required_argument, NULL, 'f' | 0x100 }, 00225 { "genindex", required_argument, NULL, 'g' | 0x100 }, 00226 { "grab", required_argument, NULL, 'g' }, 00227 { "help", no_argument, NULL, 'h' }, 00228 { "instance", required_argument, NULL, 'i' }, 00229 { "lib", required_argument, NULL, 'L' }, 00230 { "lirc", optional_argument, NULL, 'l' | 0x100 }, 00231 { "localedir",required_argument, NULL, 'l' | 0x200 }, 00232 { "log", required_argument, NULL, 'l' }, 00233 { "mute", no_argument, NULL, 'm' }, 00234 { "no-kbd", no_argument, NULL, 'n' | 0x100 }, 00235 { "plugin", required_argument, NULL, 'P' }, 00236 { "port", required_argument, NULL, 'p' }, 00237 { "record", required_argument, NULL, 'r' }, 00238 { "shutdown", required_argument, NULL, 's' }, 00239 { "split", no_argument, NULL, 's' | 0x100 }, 00240 { "terminal", required_argument, NULL, 't' }, 00241 { "user", required_argument, NULL, 'u' }, 00242 { "userdump", no_argument, NULL, 'u' | 0x100 }, 00243 { "version", no_argument, NULL, 'V' }, 00244 { "vfat", no_argument, NULL, 'v' | 0x100 }, 00245 { "video", required_argument, NULL, 'v' }, 00246 { "watchdog", required_argument, NULL, 'w' }, 00247 { NULL, no_argument, NULL, 0 } 00248 }; 00249 00250 int c; 00251 while ((c = getopt_long(argc, argv, "a:c:dD:e:E:g:hi:l:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) { 00252 switch (c) { 00253 case 'a': AudioCommand = optarg; 00254 break; 00255 case 'c': ConfigDirectory = optarg; 00256 break; 00257 case 'd': DaemonMode = true; break; 00258 case 'D': if (isnumber(optarg)) { 00259 int n = atoi(optarg); 00260 if (0 <= n && n < MAXDEVICES) { 00261 cDevice::SetUseDevice(n); 00262 break; 00263 } 00264 } 00265 fprintf(stderr, "vdr: invalid DVB device number: %s\n", optarg); 00266 return 2; 00267 break; 00268 case 'e' | 0x100: 00269 return CutRecording(optarg) ? 0 : 2; 00270 case 'E': EpgDataFileName = (*optarg != '-' ? optarg : NULL); 00271 break; 00272 case 'f' | 0x100: 00273 Setup.MaxVideoFileSize = StrToNum(optarg) / MEGABYTE(1); 00274 if (Setup.MaxVideoFileSize < MINVIDEOFILESIZE) 00275 Setup.MaxVideoFileSize = MINVIDEOFILESIZE; 00276 if (Setup.MaxVideoFileSize > MAXVIDEOFILESIZETS) 00277 Setup.MaxVideoFileSize = MAXVIDEOFILESIZETS; 00278 break; 00279 case 'g' | 0x100: 00280 return GenerateIndex(optarg) ? 0 : 2; 00281 case 'g': cSVDRP::SetGrabImageDir(*optarg != '-' ? optarg : NULL); 00282 break; 00283 case 'h': DisplayHelp = true; 00284 break; 00285 case 'i': if (isnumber(optarg)) { 00286 InstanceId = atoi(optarg); 00287 if (InstanceId >= 0) 00288 break; 00289 } 00290 fprintf(stderr, "vdr: invalid instance id: %s\n", optarg); 00291 return 2; 00292 case 'l': { 00293 char *p = strchr(optarg, '.'); 00294 if (p) 00295 *p = 0; 00296 if (isnumber(optarg)) { 00297 int l = atoi(optarg); 00298 if (0 <= l && l <= 3) { 00299 SysLogLevel = l; 00300 if (!p) 00301 break; 00302 if (isnumber(p + 1)) { 00303 int l = atoi(p + 1); 00304 if (0 <= l && l <= 7) { 00305 int targets[] = { LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7 }; 00306 SysLogTarget = targets[l]; 00307 break; 00308 } 00309 } 00310 } 00311 } 00312 if (p) 00313 *p = '.'; 00314 fprintf(stderr, "vdr: invalid log level: %s\n", optarg); 00315 return 2; 00316 } 00317 break; 00318 case 'L': if (access(optarg, R_OK | X_OK) == 0) 00319 PluginManager.SetDirectory(optarg); 00320 else { 00321 fprintf(stderr, "vdr: can't access plugin directory: %s\n", optarg); 00322 return 2; 00323 } 00324 break; 00325 case 'l' | 0x100: 00326 LircDevice = optarg ? optarg : LIRC_DEVICE; 00327 break; 00328 case 'l' | 0x200: 00329 if (access(optarg, R_OK | X_OK) == 0) 00330 LocaleDir = optarg; 00331 else { 00332 fprintf(stderr, "vdr: can't access locale directory: %s\n", optarg); 00333 return 2; 00334 } 00335 break; 00336 case 'm': MuteAudio = true; 00337 break; 00338 case 'n' | 0x100: 00339 UseKbd = false; 00340 break; 00341 case 'p': if (isnumber(optarg)) 00342 SVDRPport = atoi(optarg); 00343 else { 00344 fprintf(stderr, "vdr: invalid port number: %s\n", optarg); 00345 return 2; 00346 } 00347 break; 00348 case 'P': PluginManager.AddPlugin(optarg); 00349 break; 00350 case 'r': cRecordingUserCommand::SetCommand(optarg); 00351 break; 00352 case 's': ShutdownHandler.SetShutdownCommand(optarg); 00353 break; 00354 case 's' | 0x100: 00355 Setup.SplitEditedFiles = 1; 00356 break; 00357 case 't': Terminal = optarg; 00358 if (access(Terminal, R_OK | W_OK) < 0) { 00359 fprintf(stderr, "vdr: can't access terminal: %s\n", Terminal); 00360 return 2; 00361 } 00362 break; 00363 case 'u': if (*optarg) 00364 VdrUser = optarg; 00365 break; 00366 case 'u' | 0x100: 00367 UserDump = true; 00368 break; 00369 case 'V': DisplayVersion = true; 00370 break; 00371 case 'v' | 0x100: 00372 VfatFileSystem = true; 00373 break; 00374 case 'v': VideoDirectory = optarg; 00375 while (optarg && *optarg && optarg[strlen(optarg) - 1] == '/') 00376 optarg[strlen(optarg) - 1] = 0; 00377 break; 00378 case 'w': if (isnumber(optarg)) { 00379 int t = atoi(optarg); 00380 if (t >= 0) { 00381 WatchdogTimeout = t; 00382 break; 00383 } 00384 } 00385 fprintf(stderr, "vdr: invalid watchdog timeout: %s\n", optarg); 00386 return 2; 00387 break; 00388 default: return 2; 00389 } 00390 } 00391 00392 // Set user id in case we were started as root: 00393 00394 if (VdrUser && geteuid() == 0) { 00395 StartedAsRoot = true; 00396 if (strcmp(VdrUser, "root")) { 00397 if (!SetKeepCaps(true)) 00398 return 2; 00399 if (!SetUser(VdrUser, UserDump)) 00400 return 2; 00401 if (!SetKeepCaps(false)) 00402 return 2; 00403 if (!DropCaps()) 00404 return 2; 00405 } 00406 } 00407 00408 // Help and version info: 00409 00410 if (DisplayHelp || DisplayVersion) { 00411 if (!PluginManager.HasPlugins()) 00412 PluginManager.AddPlugin("*"); // adds all available plugins 00413 PluginManager.LoadPlugins(); 00414 if (DisplayHelp) { 00415 printf("Usage: vdr [OPTIONS]\n\n" // for easier orientation, this is column 80| 00416 " -a CMD, --audio=CMD send Dolby Digital audio to stdin of command CMD\n" 00417 " -c DIR, --config=DIR read config files from DIR (default: %s)\n" 00418 " -d, --daemon run in daemon mode\n" 00419 " -D NUM, --device=NUM use only the given DVB device (NUM = 0, 1, 2...)\n" 00420 " there may be several -D options (default: all DVB\n" 00421 " devices will be used)\n" 00422 " --edit=REC cut recording REC and exit\n" 00423 " -E FILE, --epgfile=FILE write the EPG data into the given FILE (default is\n" 00424 " /var/cache/vdr/%s)\n" 00425 " '-E-' disables this\n" 00426 " if FILE is a directory, the default EPG file will be\n" 00427 " created in that directory\n" 00428 " --filesize=SIZE limit video files to SIZE bytes (default is %dM)\n" 00429 " only useful in conjunction with --edit\n" 00430 " --genindex=REC generate index for recording REC and exit\n" 00431 " -g DIR, --grab=DIR write images from the SVDRP command GRAB into the\n" 00432 " given DIR; DIR must be the full path name of an\n" 00433 " existing directory, without any \"..\", double '/'\n" 00434 " or symlinks (default: none, same as -g-)\n" 00435 " -h, --help print this help and exit\n" 00436 " -i ID, --instance=ID use ID as the id of this VDR instance (default: 0)\n" 00437 " -l LEVEL, --log=LEVEL set log level (default: 3)\n" 00438 " 0 = no logging, 1 = errors only,\n" 00439 " 2 = errors and info, 3 = errors, info and debug\n" 00440 " if logging should be done to LOG_LOCALn instead of\n" 00441 " LOG_USER, add '.n' to LEVEL, as in 3.7 (n=0..7)\n" 00442 " -L DIR, --lib=DIR search for plugins in DIR (default is %s)\n" 00443 " --lirc[=PATH] use a LIRC remote control device, attached to PATH\n" 00444 " (default: %s)\n" 00445 " --localedir=DIR search for locale files in DIR (default is\n" 00446 " %s)\n" 00447 " -m, --mute mute audio of the primary DVB device at startup\n" 00448 " --no-kbd don't use the keyboard as an input device\n" 00449 " -p PORT, --port=PORT use PORT for SVDRP (default: %d)\n" 00450 " 0 turns off SVDRP\n" 00451 " -P OPT, --plugin=OPT load a plugin defined by the given options\n" 00452 " -r CMD, --record=CMD call CMD before and after a recording\n" 00453 " -s CMD, --shutdown=CMD call CMD to shutdown the computer\n" 00454 " --split split edited files at the editing marks (only\n" 00455 " useful in conjunction with --edit)\n" 00456 " -t TTY, --terminal=TTY controlling tty\n" 00457 " -u USER, --user=USER run as user USER; only applicable if started as\n" 00458 " root\n" 00459 " --userdump allow coredumps if -u is given (debugging)\n" 00460 " -v DIR, --video=DIR use DIR as video directory (default: %s)\n" 00461 " -V, --version print version information and exit\n" 00462 " --vfat encode special characters in recording names to\n" 00463 " avoid problems with VFAT file systems\n" 00464 " -w SEC, --watchdog=SEC activate the watchdog timer with a timeout of SEC\n" 00465 " seconds (default: %d); '0' disables the watchdog\n" 00466 "\n", 00467 DEFAULTCONFDIR, 00468 DEFAULTEPGDATAFILENAME, 00469 MAXVIDEOFILESIZEDEFAULT, 00470 DEFAULTPLUGINDIR, 00471 LIRC_DEVICE, 00472 LOCDIR, 00473 DEFAULTSVDRPPORT, 00474 VideoDirectory, 00475 DEFAULTWATCHDOG 00476 ); 00477 } 00478 if (DisplayVersion) 00479 printf("vdr (%s/%s) - The Video Disk Recorder\n", VDRVERSION, APIVERSION); 00480 if (PluginManager.HasPlugins()) { 00481 if (DisplayHelp) 00482 printf("Plugins: vdr -P\"name [OPTIONS]\"\n\n"); 00483 for (int i = 0; ; i++) { 00484 cPlugin *p = PluginManager.GetPlugin(i); 00485 if (p) { 00486 const char *help = p->CommandLineHelp(); 00487 printf("%s (%s) - %s\n", p->Name(), p->Version(), p->Description()); 00488 if (DisplayHelp && help) { 00489 printf("\n"); 00490 puts(help); 00491 } 00492 } 00493 else 00494 break; 00495 } 00496 } 00497 return 0; 00498 } 00499 00500 // Log file: 00501 00502 if (SysLogLevel > 0) 00503 openlog("vdr", LOG_CONS, SysLogTarget); // LOG_PID doesn't work as expected under NPTL 00504 00505 // Check the video directory: 00506 00507 if (!DirectoryOk(VideoDirectory, true)) { 00508 fprintf(stderr, "vdr: can't access video directory %s\n", VideoDirectory); 00509 return 2; 00510 } 00511 00512 // Daemon mode: 00513 00514 if (DaemonMode) { 00515 if (daemon(1, 0) == -1) { 00516 fprintf(stderr, "vdr: %m\n"); 00517 esyslog("ERROR: %m"); 00518 return 2; 00519 } 00520 } 00521 else if (Terminal) { 00522 // Claim new controlling terminal 00523 stdin = freopen(Terminal, "r", stdin); 00524 stdout = freopen(Terminal, "w", stdout); 00525 stderr = freopen(Terminal, "w", stderr); 00526 HasStdin = true; 00527 tcgetattr(STDIN_FILENO, &savedTm); 00528 } 00529 00530 isyslog("VDR version %s started", VDRVERSION); 00531 if (StartedAsRoot && VdrUser) 00532 isyslog("switched to user '%s'", VdrUser); 00533 if (DaemonMode) 00534 dsyslog("running as daemon (tid=%d)", cThread::ThreadId()); 00535 cThread::SetMainThreadId(); 00536 00537 // Set the system character table: 00538 00539 char *CodeSet = NULL; 00540 if (setlocale(LC_CTYPE, "")) 00541 CodeSet = nl_langinfo(CODESET); 00542 else { 00543 char *LangEnv = getenv("LANG"); // last resort in case locale stuff isn't installed 00544 if (LangEnv) { 00545 CodeSet = strchr(LangEnv, '.'); 00546 if (CodeSet) 00547 CodeSet++; // skip the dot 00548 } 00549 } 00550 if (CodeSet) { 00551 bool known = SI::SetSystemCharacterTable(CodeSet); 00552 isyslog("codeset is '%s' - %s", CodeSet, known ? "known" : "unknown"); 00553 cCharSetConv::SetSystemCharacterTable(CodeSet); 00554 } 00555 00556 // Initialize internationalization: 00557 00558 I18nInitialize(LocaleDir); 00559 00560 // Main program loop variables - need to be here to have them initialized before any EXIT(): 00561 00562 cEpgDataReader EpgDataReader; 00563 cOsdObject *Menu = NULL; 00564 int LastChannel = 0; 00565 int LastTimerChannel = -1; 00566 int PreviousChannel[2] = { 1, 1 }; 00567 int PreviousChannelIndex = 0; 00568 time_t LastChannelChanged = time(NULL); 00569 time_t LastInteract = 0; 00570 int MaxLatencyTime = 0; 00571 bool InhibitEpgScan = false; 00572 bool IsInfoMenu = false; 00573 bool CheckHasProgramme = false; 00574 cSkin *CurrentSkin = NULL; 00575 00576 // Load plugins: 00577 00578 if (!PluginManager.LoadPlugins(true)) 00579 EXIT(2); 00580 00581 // Configuration data: 00582 00583 if (!ConfigDirectory) 00584 ConfigDirectory = DEFAULTCONFDIR; 00585 00586 cPlugin::SetConfigDirectory(ConfigDirectory); 00587 cThemes::SetThemesDirectory("/var/lib/vdr/data/themes"); 00588 00589 Setup.Load(AddDirectory(ConfigDirectory, "setup.conf")); 00590 Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true); 00591 Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC); 00592 Scrs.Load(AddDirectory(ConfigDirectory, "scr.conf"), true); 00593 Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true); 00594 Timers.Load(AddDirectory(ConfigDirectory, "timers.conf")); 00595 Commands.Load(AddDirectory(ConfigDirectory, "commands.conf")); 00596 RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf")); 00597 TimerCommands.Load(AddDirectory(ConfigDirectory, "timercmds.conf")); 00598 SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true); 00599 Keys.Load(AddDirectory(ConfigDirectory, "remote.conf")); 00600 KeyMacros.Load(AddDirectory(ConfigDirectory, "keymacros.conf"), true); 00601 Folders.Load(AddDirectory(ConfigDirectory, "folders.conf")); 00602 00603 if (!*cFont::GetFontFileName(Setup.FontOsd)) { 00604 const char *msg = "no fonts available - OSD will not show any text!"; 00605 fprintf(stderr, "vdr: %s\n", msg); 00606 esyslog("ERROR: %s", msg); 00607 } 00608 00609 // Recordings: 00610 00611 Recordings.Update(); 00612 DeletedRecordings.Update(); 00613 00614 // EPG data: 00615 00616 if (EpgDataFileName) { 00617 const char *EpgDirectory = NULL; 00618 if (DirectoryOk(EpgDataFileName)) { 00619 EpgDirectory = EpgDataFileName; 00620 EpgDataFileName = DEFAULTEPGDATAFILENAME; 00621 } 00622 else if (*EpgDataFileName != '/' && *EpgDataFileName != '.') 00623 EpgDirectory = "/var/cache/vdr"; 00624 if (EpgDirectory) 00625 cSchedules::SetEpgDataFileName(AddDirectory(EpgDirectory, EpgDataFileName)); 00626 else 00627 cSchedules::SetEpgDataFileName(EpgDataFileName); 00628 EpgDataReader.Start(); 00629 } 00630 00631 // DVB interfaces: 00632 00633 cDvbDevice::Initialize(); 00634 cDvbDevice::BondDevices(Setup.DeviceBondings); 00635 00636 // Initialize plugins: 00637 00638 if (!PluginManager.InitializePlugins()) 00639 EXIT(2); 00640 00641 // Primary device: 00642 00643 cDevice::SetPrimaryDevice(Setup.PrimaryDVB); 00644 if (!cDevice::PrimaryDevice() || !cDevice::PrimaryDevice()->HasDecoder()) { 00645 if (cDevice::PrimaryDevice() && !cDevice::PrimaryDevice()->HasDecoder()) 00646 isyslog("device %d has no MPEG decoder", cDevice::PrimaryDevice()->DeviceNumber() + 1); 00647 for (int i = 0; i < cDevice::NumDevices(); i++) { 00648 cDevice *d = cDevice::GetDevice(i); 00649 if (d && d->HasDecoder()) { 00650 isyslog("trying device number %d instead", i + 1); 00651 if (cDevice::SetPrimaryDevice(i + 1)) { 00652 Setup.PrimaryDVB = i + 1; 00653 break; 00654 } 00655 } 00656 } 00657 if (!cDevice::PrimaryDevice()) { 00658 const char *msg = "no primary device found - using first device!"; 00659 fprintf(stderr, "vdr: %s\n", msg); 00660 esyslog("ERROR: %s", msg); 00661 if (!cDevice::SetPrimaryDevice(1)) 00662 EXIT(2); 00663 if (!cDevice::PrimaryDevice()) { 00664 const char *msg = "no primary device found - giving up!"; 00665 fprintf(stderr, "vdr: %s\n", msg); 00666 esyslog("ERROR: %s", msg); 00667 EXIT(2); 00668 } 00669 } 00670 } 00671 00672 // Check for timers in automatic start time window: 00673 00674 ShutdownHandler.CheckManualStart(MANUALSTART); 00675 00676 // User interface: 00677 00678 Interface = new cInterface(SVDRPport); 00679 00680 // Default skins: 00681 00682 new cSkinSTTNG; 00683 new cSkinClassic; 00684 Skins.SetCurrent(Setup.OSDSkin); 00685 cThemes::Load(Skins.Current()->Name(), Setup.OSDTheme, Skins.Current()->Theme()); 00686 CurrentSkin = Skins.Current(); 00687 00688 // Start plugins: 00689 00690 if (!PluginManager.StartPlugins()) 00691 EXIT(2); 00692 00693 // Set skin and theme in case they're implemented by a plugin: 00694 00695 if (!CurrentSkin || CurrentSkin == Skins.Current() && strcmp(Skins.Current()->Name(), Setup.OSDSkin) != 0) { 00696 Skins.SetCurrent(Setup.OSDSkin); 00697 cThemes::Load(Skins.Current()->Name(), Setup.OSDTheme, Skins.Current()->Theme()); 00698 } 00699 00700 // Remote Controls: 00701 if (LircDevice) 00702 new cLircRemote(LircDevice); 00703 if (!DaemonMode && HasStdin && UseKbd) 00704 new cKbdRemote; 00705 Interface->LearnKeys(); 00706 00707 // External audio: 00708 00709 if (AudioCommand) 00710 new cExternalAudio(AudioCommand); 00711 00712 // Channel: 00713 00714 if (!cDevice::WaitForAllDevicesReady(DEVICEREADYTIMEOUT)) 00715 dsyslog("not all devices ready after %d seconds", DEVICEREADYTIMEOUT); 00716 if (*Setup.InitialChannel) { 00717 if (isnumber(Setup.InitialChannel)) { // for compatibility with old setup.conf files 00718 if (cChannel *Channel = Channels.GetByNumber(atoi(Setup.InitialChannel))) 00719 Setup.InitialChannel = Channel->GetChannelID().ToString(); 00720 } 00721 if (cChannel *Channel = Channels.GetByChannelID(tChannelID::FromString(Setup.InitialChannel))) 00722 Setup.CurrentChannel = Channel->Number(); 00723 } 00724 if (Setup.InitialVolume >= 0) 00725 Setup.CurrentVolume = Setup.InitialVolume; 00726 Channels.SwitchTo(Setup.CurrentChannel); 00727 if (MuteAudio) 00728 cDevice::PrimaryDevice()->ToggleMute(); 00729 else 00730 cDevice::PrimaryDevice()->SetVolume(Setup.CurrentVolume, true); 00731 00732 // Signal handlers: 00733 00734 if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); 00735 if (signal(SIGINT, SignalHandler) == SIG_IGN) signal(SIGINT, SIG_IGN); 00736 if (signal(SIGTERM, SignalHandler) == SIG_IGN) signal(SIGTERM, SIG_IGN); 00737 if (signal(SIGPIPE, SignalHandler) == SIG_IGN) signal(SIGPIPE, SIG_IGN); 00738 if (WatchdogTimeout > 0) 00739 if (signal(SIGALRM, Watchdog) == SIG_IGN) signal(SIGALRM, SIG_IGN); 00740 00741 // Watchdog: 00742 00743 if (WatchdogTimeout > 0) { 00744 dsyslog("setting watchdog timer to %d seconds", WatchdogTimeout); 00745 alarm(WatchdogTimeout); // Initial watchdog timer start 00746 } 00747 00748 // Main program loop: 00749 00750 #define DELETE_MENU ((IsInfoMenu &= (Menu == NULL)), delete Menu, Menu = NULL) 00751 00752 while (!ShutdownHandler.DoExit()) { 00753 #ifdef DEBUGRINGBUFFERS 00754 cRingBufferLinear::PrintDebugRBL(); 00755 #endif 00756 // Attach launched player control: 00757 cControl::Attach(); 00758 00759 time_t Now = time(NULL); 00760 00761 // Make sure we have a visible programme in case device usage has changed: 00762 if (!EITScanner.Active() && cDevice::PrimaryDevice()->HasDecoder() && !cDevice::PrimaryDevice()->HasProgramme()) { 00763 static time_t lastTime = 0; 00764 if ((!Menu || CheckHasProgramme) && Now - lastTime > MINCHANNELWAIT) { // !Menu to avoid interfering with the CAM if a CAM menu is open 00765 cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel()); 00766 if (Channel && (Channel->Vpid() || Channel->Apid(0) || Channel->Dpid(0))) { 00767 if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(Channel->Number())) // try to switch to the original channel... 00768 ; 00769 else if (LastTimerChannel > 0) { 00770 Channel = Channels.GetByNumber(LastTimerChannel); 00771 if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(LastTimerChannel)) // ...or the one used by the last timer 00772 ; 00773 } 00774 } 00775 lastTime = Now; // don't do this too often 00776 LastTimerChannel = -1; 00777 CheckHasProgramme = false; 00778 } 00779 } 00780 // Update the OSD size: 00781 { 00782 static time_t lastOsdSizeUpdate = 0; 00783 if (Now != lastOsdSizeUpdate) { // once per second 00784 cOsdProvider::UpdateOsdSize(); 00785 lastOsdSizeUpdate = Now; 00786 } 00787 } 00788 // Restart the Watchdog timer: 00789 if (WatchdogTimeout > 0) { 00790 int LatencyTime = WatchdogTimeout - alarm(WatchdogTimeout); 00791 if (LatencyTime > MaxLatencyTime) { 00792 MaxLatencyTime = LatencyTime; 00793 dsyslog("max. latency time %d seconds", MaxLatencyTime); 00794 } 00795 } 00796 // Handle channel and timer modifications: 00797 if (!Channels.BeingEdited() && !Timers.BeingEdited()) { 00798 int modified = Channels.Modified(); 00799 static time_t ChannelSaveTimeout = 0; 00800 static int TimerState = 0; 00801 // Channels and timers need to be stored in a consistent manner, 00802 // therefore if one of them is changed, we save both. 00803 if (modified == CHANNELSMOD_USER || Timers.Modified(TimerState)) 00804 ChannelSaveTimeout = 1; // triggers an immediate save 00805 else if (modified && !ChannelSaveTimeout) 00806 ChannelSaveTimeout = Now + CHANNELSAVEDELTA; 00807 bool timeout = ChannelSaveTimeout == 1 || ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active(); 00808 if ((modified || timeout) && Channels.Lock(false, 100)) { 00809 if (timeout) { 00810 Channels.Save(); 00811 Timers.Save(); 00812 ChannelSaveTimeout = 0; 00813 } 00814 for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { 00815 if (Channel->Modification(CHANNELMOD_RETUNE)) { 00816 cRecordControls::ChannelDataModified(Channel); 00817 if (Channel->Number() == cDevice::CurrentChannel()) { 00818 if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) { 00819 if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder 00820 isyslog("retuning due to modification of channel %d", Channel->Number()); 00821 Channels.SwitchTo(Channel->Number()); 00822 } 00823 } 00824 } 00825 } 00826 } 00827 Channels.Unlock(); 00828 } 00829 } 00830 // Channel display: 00831 if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) { 00832 if (!Menu) 00833 Menu = new cDisplayChannel(cDevice::CurrentChannel(), LastChannel >= 0); 00834 LastChannel = cDevice::CurrentChannel(); 00835 LastChannelChanged = Now; 00836 } 00837 if (Now - LastChannelChanged >= Setup.ZapTimeout && LastChannel != PreviousChannel[PreviousChannelIndex]) 00838 PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel; 00839 // Timers and Recordings: 00840 if (!Timers.BeingEdited()) { 00841 // Assign events to timers: 00842 Timers.SetEvents(); 00843 // Must do all following calls with the exact same time! 00844 // Process ongoing recordings: 00845 cRecordControls::Process(Now); 00846 // Start new recordings: 00847 cTimer *Timer = Timers.GetMatch(Now); 00848 if (Timer) { 00849 if (!cRecordControls::Start(Timer)) 00850 Timer->SetPending(true); 00851 else 00852 LastTimerChannel = Timer->Channel()->Number(); 00853 } 00854 // Make sure timers "see" their channel early enough: 00855 static time_t LastTimerCheck = 0; 00856 if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often 00857 InhibitEpgScan = false; 00858 for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) { 00859 bool InVpsMargin = false; 00860 bool NeedsTransponder = false; 00861 if (Timer->HasFlags(tfActive) && !Timer->Recording()) { 00862 if (Timer->HasFlags(tfVps)) { 00863 if (Timer->Matches(Now, true, Setup.VpsMargin)) { 00864 InVpsMargin = true; 00865 Timer->SetInVpsMargin(InVpsMargin); 00866 } 00867 else if (Timer->Event()) { 00868 InVpsMargin = Timer->Event()->StartTime() <= Now && Timer->Event()->RunningStatus() == SI::RunningStatusUndefined; 00869 NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME); 00870 } 00871 else { 00872 cSchedulesLock SchedulesLock; 00873 const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); 00874 if (Schedules) { 00875 const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel()); 00876 InVpsMargin = !Schedule; // we must make sure we have the schedule 00877 NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME); 00878 } 00879 } 00880 InhibitEpgScan |= InVpsMargin | NeedsTransponder; 00881 } 00882 else 00883 NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME); 00884 } 00885 if (NeedsTransponder || InVpsMargin) { 00886 // Find a device that provides the required transponder: 00887 cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY); 00888 if (!Device && InVpsMargin) 00889 Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY); 00890 // Switch the device to the transponder: 00891 if (Device) { 00892 bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme(); 00893 if (!Device->IsTunedToTransponder(Timer->Channel())) { 00894 if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice()) 00895 cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode 00896 dsyslog("switching device %d to channel %d", Device->DeviceNumber() + 1, Timer->Channel()->Number()); 00897 if (Device->SwitchChannel(Timer->Channel(), false)) 00898 Device->SetOccupied(TIMERDEVICETIMEOUT); 00899 } 00900 if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme()) 00901 Skins.Message(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel 00902 } 00903 } 00904 } 00905 LastTimerCheck = Now; 00906 } 00907 // Delete expired timers: 00908 Timers.DeleteExpired(); 00909 } 00910 if (!Menu && Recordings.NeedsUpdate()) { 00911 Recordings.Update(); 00912 DeletedRecordings.Update(); 00913 } 00914 // CAM control: 00915 if (!Menu && !cOsd::IsOpen()) 00916 Menu = CamControl(); 00917 // Queued messages: 00918 if (!Skins.IsOpen()) 00919 Skins.ProcessQueuedMessages(); 00920 // User Input: 00921 cOsdObject *Interact = Menu ? Menu : cControl::Control(); 00922 eKeys key = Interface->GetKey(!Interact || !Interact->NeedsFastResponse()); 00923 if (ISREALKEY(key)) { 00924 EITScanner.Activity(); 00925 // Cancel shutdown countdown: 00926 if (ShutdownHandler.countdown) 00927 ShutdownHandler.countdown.Cancel(); 00928 // Set user active for MinUserInactivity time in the future: 00929 ShutdownHandler.SetUserInactiveTimeout(); 00930 } 00931 // Keys that must work independent of any interactive mode: 00932 switch (int(key)) { 00933 // Menu control: 00934 case kMenu: { 00935 key = kNone; // nobody else needs to see this key 00936 bool WasOpen = Interact != NULL; 00937 bool WasMenu = Interact && Interact->IsMenu(); 00938 if (Menu) 00939 DELETE_MENU; 00940 else if (cControl::Control()) { 00941 if (cOsd::IsOpen()) 00942 cControl::Control()->Hide(); 00943 else 00944 WasOpen = false; 00945 } 00946 if (!WasOpen || !WasMenu && !Setup.MenuKeyCloses) 00947 Menu = new cMenuMain; 00948 } 00949 break; 00950 // Info: 00951 case kInfo: { 00952 if (IsInfoMenu) { 00953 key = kNone; // nobody else needs to see this key 00954 DELETE_MENU; 00955 } 00956 else if (!Menu) { 00957 IsInfoMenu = true; 00958 if (cControl::Control()) { 00959 cControl::Control()->Hide(); 00960 Menu = cControl::Control()->GetInfo(); 00961 if (Menu) 00962 Menu->Show(); 00963 else 00964 IsInfoMenu = false; 00965 } 00966 else { 00967 cRemote::Put(kOk, true); 00968 cRemote::Put(kSchedule, true); 00969 } 00970 key = kNone; // nobody else needs to see this key 00971 } 00972 } 00973 break; 00974 // Direct main menu functions: 00975 #define DirectMainFunction(function)\ 00976 DELETE_MENU;\ 00977 if (cControl::Control())\ 00978 cControl::Control()->Hide();\ 00979 Menu = new cMenuMain(function);\ 00980 key = kNone; // nobody else needs to see this key 00981 case kSchedule: DirectMainFunction(osSchedule); break; 00982 case kChannels: DirectMainFunction(osChannels); break; 00983 case kTimers: DirectMainFunction(osTimers); break; 00984 case kRecordings: DirectMainFunction(osRecordings); break; 00985 case kSetup: DirectMainFunction(osSetup); break; 00986 case kCommands: DirectMainFunction(osCommands); break; 00987 case kUser0 ... kUser9: cRemote::PutMacro(key); key = kNone; break; 00988 case k_Plugin: { 00989 const char *PluginName = cRemote::GetPlugin(); 00990 if (PluginName) { 00991 DELETE_MENU; 00992 if (cControl::Control()) 00993 cControl::Control()->Hide(); 00994 cPlugin *plugin = cPluginManager::GetPlugin(PluginName); 00995 if (plugin) { 00996 Menu = plugin->MainMenuAction(); 00997 if (Menu) 00998 Menu->Show(); 00999 } 01000 else 01001 esyslog("ERROR: unknown plugin '%s'", PluginName); 01002 } 01003 key = kNone; // nobody else needs to see these keys 01004 } 01005 break; 01006 // Channel up/down: 01007 case kChanUp|k_Repeat: 01008 case kChanUp: 01009 case kChanDn|k_Repeat: 01010 case kChanDn: 01011 if (!Interact) 01012 Menu = new cDisplayChannel(NORMALKEY(key)); 01013 else if (cDisplayChannel::IsOpen() || cControl::Control()) { 01014 Interact->ProcessKey(key); 01015 continue; 01016 } 01017 else 01018 cDevice::SwitchChannel(NORMALKEY(key) == kChanUp ? 1 : -1); 01019 key = kNone; // nobody else needs to see these keys 01020 break; 01021 // Volume control: 01022 case kVolUp|k_Repeat: 01023 case kVolUp: 01024 case kVolDn|k_Repeat: 01025 case kVolDn: 01026 case kMute: 01027 if (key == kMute) { 01028 if (!cDevice::PrimaryDevice()->ToggleMute() && !Menu) { 01029 key = kNone; // nobody else needs to see these keys 01030 break; // no need to display "mute off" 01031 } 01032 } 01033 else 01034 cDevice::PrimaryDevice()->SetVolume(NORMALKEY(key) == kVolDn ? -VOLUMEDELTA : VOLUMEDELTA); 01035 if (!Menu && !cOsd::IsOpen()) 01036 Menu = cDisplayVolume::Create(); 01037 cDisplayVolume::Process(key); 01038 key = kNone; // nobody else needs to see these keys 01039 break; 01040 // Audio track control: 01041 case kAudio: 01042 if (cControl::Control()) 01043 cControl::Control()->Hide(); 01044 if (!cDisplayTracks::IsOpen()) { 01045 DELETE_MENU; 01046 Menu = cDisplayTracks::Create(); 01047 } 01048 else 01049 cDisplayTracks::Process(key); 01050 key = kNone; 01051 break; 01052 // Subtitle track control: 01053 case kSubtitles: 01054 if (cControl::Control()) 01055 cControl::Control()->Hide(); 01056 if (!cDisplaySubtitleTracks::IsOpen()) { 01057 DELETE_MENU; 01058 Menu = cDisplaySubtitleTracks::Create(); 01059 } 01060 else 01061 cDisplaySubtitleTracks::Process(key); 01062 key = kNone; 01063 break; 01064 // Pausing live video: 01065 case kPause: 01066 if (!cControl::Control()) { 01067 DELETE_MENU; 01068 if (Setup.PauseKeyHandling) { 01069 if (Setup.PauseKeyHandling > 1 || Interface->Confirm(tr("Pause live video?"))) { 01070 if (!cRecordControls::PauseLiveVideo()) 01071 Skins.Message(mtError, tr("No free DVB device to record!")); 01072 } 01073 } 01074 key = kNone; // nobody else needs to see this key 01075 } 01076 break; 01077 // Instant recording: 01078 case kRecord: 01079 if (!cControl::Control()) { 01080 if (cRecordControls::Start()) 01081 Skins.Message(mtInfo, tr("Recording started")); 01082 key = kNone; // nobody else needs to see this key 01083 } 01084 break; 01085 // Power off: 01086 case kPower: 01087 isyslog("Power button pressed"); 01088 DELETE_MENU; 01089 // Check for activity, request power button again if active: 01090 if (!ShutdownHandler.ConfirmShutdown(false) && Skins.Message(mtWarning, tr("VDR will shut down later - press Power to force"), SHUTDOWNFORCEPROMPT) != kPower) { 01091 // Not pressed power - set VDR to be non-interactive and power down later: 01092 ShutdownHandler.SetUserInactive(); 01093 break; 01094 } 01095 // No activity or power button pressed twice - ask for confirmation: 01096 if (!ShutdownHandler.ConfirmShutdown(true)) { 01097 // Non-confirmed background activity - set VDR to be non-interactive and power down later: 01098 ShutdownHandler.SetUserInactive(); 01099 break; 01100 } 01101 // Ask the final question: 01102 if (!Interface->Confirm(tr("Press any key to cancel shutdown"), SHUTDOWNCANCELPROMPT, true)) 01103 // If final question was canceled, continue to be active: 01104 break; 01105 // Ok, now call the shutdown script: 01106 ShutdownHandler.DoShutdown(true); 01107 // Set VDR to be non-interactive and power down again later: 01108 ShutdownHandler.SetUserInactive(); 01109 // Do not attempt to automatically shut down for a while: 01110 ShutdownHandler.SetRetry(SHUTDOWNRETRY); 01111 break; 01112 default: break; 01113 } 01114 Interact = Menu ? Menu : cControl::Control(); // might have been closed in the mean time 01115 if (Interact) { 01116 LastInteract = Now; 01117 eOSState state = Interact->ProcessKey(key); 01118 if (state == osUnknown && Interact != cControl::Control()) { 01119 if (ISMODELESSKEY(key) && cControl::Control()) { 01120 state = cControl::Control()->ProcessKey(key); 01121 if (state == osEnd) { 01122 // let's not close a menu when replay ends: 01123 cControl::Shutdown(); 01124 continue; 01125 } 01126 } 01127 else if (Now - cRemote::LastActivity() > MENUTIMEOUT) 01128 state = osEnd; 01129 } 01130 switch (state) { 01131 case osPause: DELETE_MENU; 01132 if (!cRecordControls::PauseLiveVideo()) 01133 Skins.Message(mtError, tr("No free DVB device to record!")); 01134 break; 01135 case osRecord: DELETE_MENU; 01136 if (cRecordControls::Start()) 01137 Skins.Message(mtInfo, tr("Recording started")); 01138 break; 01139 case osRecordings: 01140 DELETE_MENU; 01141 cControl::Shutdown(); 01142 Menu = new cMenuMain(osRecordings); 01143 CheckHasProgramme = true; // to have live tv after stopping replay with 'Back' 01144 break; 01145 case osReplay: DELETE_MENU; 01146 cControl::Shutdown(); 01147 cControl::Launch(new cReplayControl); 01148 break; 01149 case osStopReplay: 01150 DELETE_MENU; 01151 cControl::Shutdown(); 01152 break; 01153 case osSwitchDvb: 01154 DELETE_MENU; 01155 cControl::Shutdown(); 01156 Skins.Message(mtInfo, tr("Switching primary DVB...")); 01157 cDevice::SetPrimaryDevice(Setup.PrimaryDVB); 01158 break; 01159 case osPlugin: DELETE_MENU; 01160 Menu = cMenuMain::PluginOsdObject(); 01161 if (Menu) 01162 Menu->Show(); 01163 break; 01164 case osBack: 01165 case osEnd: if (Interact == Menu) 01166 DELETE_MENU; 01167 else 01168 cControl::Shutdown(); 01169 break; 01170 default: ; 01171 } 01172 } 01173 else { 01174 // Key functions in "normal" viewing mode: 01175 if (key != kNone && KeyMacros.Get(key)) { 01176 cRemote::PutMacro(key); 01177 key = kNone; 01178 } 01179 switch (int(key)) { 01180 // Toggle channels: 01181 case kChanPrev: 01182 case k0: { 01183 if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel || LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1]) 01184 PreviousChannelIndex ^= 1; 01185 Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]); 01186 break; 01187 } 01188 // Direct Channel Select: 01189 case k1 ... k9: 01190 // Left/Right rotates through channel groups: 01191 case kLeft|k_Repeat: 01192 case kLeft: 01193 case kRight|k_Repeat: 01194 case kRight: 01195 // Previous/Next rotates through channel groups: 01196 case kPrev|k_Repeat: 01197 case kPrev: 01198 case kNext|k_Repeat: 01199 case kNext: 01200 // Up/Down Channel Select: 01201 case kUp|k_Repeat: 01202 case kUp: 01203 case kDown|k_Repeat: 01204 case kDown: 01205 Menu = new cDisplayChannel(NORMALKEY(key)); 01206 break; 01207 // Viewing Control: 01208 case kOk: LastChannel = -1; break; // forces channel display 01209 // Instant resume of the last viewed recording: 01210 case kPlay: 01211 if (cReplayControl::LastReplayed()) { 01212 cControl::Shutdown(); 01213 cControl::Launch(new cReplayControl); 01214 } 01215 break; 01216 default: break; 01217 } 01218 } 01219 if (!Menu) { 01220 if (!InhibitEpgScan) 01221 EITScanner.Process(); 01222 if (!cCutter::Active() && cCutter::Ended()) { 01223 if (cCutter::Error()) 01224 Skins.Message(mtError, tr("Editing process failed!")); 01225 else 01226 Skins.Message(mtInfo, tr("Editing process finished")); 01227 } 01228 if (!cFileTransfer::Active() && cFileTransfer::Ended()) { 01229 if (cFileTransfer::Error()) 01230 Skins.Message(mtError, tr("File transfer failed!")); 01231 else 01232 Skins.Message(mtInfo, tr("File transfer finished")); 01233 } 01234 } 01235 01236 // SIGHUP shall cause a restart: 01237 if (LastSignal == SIGHUP) { 01238 if (ShutdownHandler.ConfirmRestart(true) && Interface->Confirm(tr("Press any key to cancel restart"), RESTARTCANCELPROMPT, true)) 01239 EXIT(1); 01240 LastSignal = 0; 01241 } 01242 01243 // Update the shutdown countdown: 01244 if (ShutdownHandler.countdown && ShutdownHandler.countdown.Update()) { 01245 if (!ShutdownHandler.ConfirmShutdown(false)) 01246 ShutdownHandler.countdown.Cancel(); 01247 } 01248 01249 if ((Now - LastInteract) > ACTIVITYTIMEOUT && !cRecordControls::Active() && !cCutter::Active() && !cFileTransfer::Active() && !Interface->HasSVDRPConnection() && (Now - cRemote::LastActivity()) > ACTIVITYTIMEOUT) { 01250 // Handle housekeeping tasks 01251 01252 // Shutdown: 01253 // Check whether VDR will be ready for shutdown in SHUTDOWNWAIT seconds: 01254 time_t Soon = Now + SHUTDOWNWAIT; 01255 if (ShutdownHandler.IsUserInactive(Soon) && ShutdownHandler.Retry(Soon) && !ShutdownHandler.countdown) { 01256 if (ShutdownHandler.ConfirmShutdown(false)) 01257 // Time to shut down - start final countdown: 01258 ShutdownHandler.countdown.Start(tr("VDR will shut down in %s minutes"), SHUTDOWNWAIT); // the placeholder is really %s! 01259 // Dont try to shut down again for a while: 01260 ShutdownHandler.SetRetry(SHUTDOWNRETRY); 01261 } 01262 // Countdown run down to 0? 01263 if (ShutdownHandler.countdown.Done()) { 01264 // Timed out, now do a final check: 01265 if (ShutdownHandler.IsUserInactive() && ShutdownHandler.ConfirmShutdown(false)) 01266 ShutdownHandler.DoShutdown(false); 01267 // Do this again a bit later: 01268 ShutdownHandler.SetRetry(SHUTDOWNRETRY); 01269 } 01270 01271 // Disk housekeeping: 01272 RemoveDeletedRecordings(); 01273 cSchedules::Cleanup(); 01274 // Plugins housekeeping: 01275 PluginManager.Housekeeping(); 01276 } 01277 01278 // Main thread hooks of plugins: 01279 PluginManager.MainThreadHook(); 01280 } 01281 01282 if (ShutdownHandler.EmergencyExitRequested()) 01283 esyslog("emergency exit requested - shutting down"); 01284 01285 Exit: 01286 01287 // Reset all signal handlers to default before Interface gets deleted: 01288 signal(SIGHUP, SIG_DFL); 01289 signal(SIGINT, SIG_DFL); 01290 signal(SIGTERM, SIG_DFL); 01291 signal(SIGPIPE, SIG_DFL); 01292 signal(SIGALRM, SIG_DFL); 01293 01294 PluginManager.StopPlugins(); 01295 cRecordControls::Shutdown(); 01296 cFileTransfer::Stop(); 01297 cCutter::Stop(); 01298 delete Menu; 01299 cControl::Shutdown(); 01300 delete Interface; 01301 cOsdProvider::Shutdown(); 01302 Remotes.Clear(); 01303 Audios.Clear(); 01304 Skins.Clear(); 01305 SourceParams.Clear(); 01306 if (ShutdownHandler.GetExitCode() != 2) { 01307 Setup.CurrentChannel = cDevice::CurrentChannel(); 01308 Setup.CurrentVolume = cDevice::CurrentVolume(); 01309 Setup.Save(); 01310 } 01311 cDevice::Shutdown(); 01312 EpgHandlers.Clear(); 01313 PluginManager.Shutdown(true); 01314 cSchedules::Cleanup(true); 01315 ReportEpgBugFixStats(); 01316 if (WatchdogTimeout > 0) 01317 dsyslog("max. latency time %d seconds", MaxLatencyTime); 01318 if (LastSignal) 01319 isyslog("caught signal %d", LastSignal); 01320 if (ShutdownHandler.EmergencyExitRequested()) 01321 esyslog("emergency exit!"); 01322 isyslog("exiting, exit code %d", ShutdownHandler.GetExitCode()); 01323 if (SysLogLevel > 0) 01324 closelog(); 01325 if (HasStdin) 01326 tcsetattr(STDIN_FILENO, TCSANOW, &savedTm); 01327 return ShutdownHandler.GetExitCode(); 01328 }