vdr  1.7.27
vdr.c
Go to the documentation of this file.
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 }