vdr  2.0.4
vdr.c
Go to the documentation of this file.
1 /*
2  * vdr.c: Video Disk Recorder main program
3  *
4  * Copyright (C) 2000, 2003, 2006, 2008, 2013 Klaus Schmidinger
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  *
21  * The author can be reached at vdr@tvdr.de
22  *
23  * The project's page is at http://www.tvdr.de
24  *
25  * $Id: vdr.c 2.57.1.1 2013/10/16 09:46:36 kls Exp $
26  */
27 
28 #include <getopt.h>
29 #include <grp.h>
30 #include <langinfo.h>
31 #include <locale.h>
32 #include <pwd.h>
33 #include <signal.h>
34 #include <stdlib.h>
35 #include <sys/capability.h>
36 #include <sys/prctl.h>
37 #include <termios.h>
38 #include <unistd.h>
39 #include "audio.h"
40 #include "channels.h"
41 #include "config.h"
42 #include "cutter.h"
43 #include "device.h"
44 #include "diseqc.h"
45 #include "dvbdevice.h"
46 #include "eitscan.h"
47 #include "epg.h"
48 #include "filetransfer.h"
49 #include "i18n.h"
50 #include "interface.h"
51 #include "keys.h"
52 #include "libsi/si.h"
53 #include "lirc.h"
54 #include "menu.h"
55 #include "osdbase.h"
56 #include "plugin.h"
57 #include "recording.h"
58 #include "shutdown.h"
59 #include "skinclassic.h"
60 #include "skinlcars.h"
61 #include "skinsttng.h"
62 #include "sourceparams.h"
63 #include "sources.h"
64 #include "themes.h"
65 #include "timers.h"
66 #include "tools.h"
67 #include "transfer.h"
68 #include "videodir.h"
69 
70 #define MINCHANNELWAIT 10 // seconds to wait between failed channel switchings
71 #define ACTIVITYTIMEOUT 60 // seconds before starting housekeeping
72 #define SHUTDOWNWAIT 300 // seconds to wait in user prompt before automatic shutdown
73 #define SHUTDOWNRETRY 360 // seconds before trying again to shut down
74 #define SHUTDOWNFORCEPROMPT 5 // seconds to wait in user prompt to allow forcing shutdown
75 #define SHUTDOWNCANCELPROMPT 5 // seconds to wait in user prompt to allow canceling shutdown
76 #define RESTARTCANCELPROMPT 5 // seconds to wait in user prompt before restarting on SIGHUP
77 #define MANUALSTART 600 // seconds the next timer must be in the future to assume manual start
78 #define CHANNELSAVEDELTA 600 // seconds before saving channels.conf after automatic modifications
79 #define DEVICEREADYTIMEOUT 30 // seconds to wait until all devices are ready
80 #define MENUTIMEOUT 120 // seconds of user inactivity after which an OSD display is closed
81 #define TIMERCHECKDELTA 10 // seconds between checks for timers that need to see their channel
82 #define TIMERDEVICETIMEOUT 8 // seconds before a device used for timer check may be reused
83 #define TIMERLOOKAHEADTIME 60 // seconds before a non-VPS timer starts and the channel is switched if possible
84 #define VPSLOOKAHEADTIME 24 // hours within which VPS timers will make sure their events are up to date
85 #define VPSUPTODATETIME 3600 // seconds before the event or schedule of a VPS timer needs to be refreshed
86 
87 #define EXIT(v) { ShutdownHandler.Exit(v); goto Exit; }
88 
89 static int LastSignal = 0;
90 
91 static bool SetUser(const char *UserName, bool UserDump)
92 {
93  if (UserName) {
94  struct passwd *user = getpwnam(UserName);
95  if (!user) {
96  fprintf(stderr, "vdr: unknown user: '%s'\n", UserName);
97  return false;
98  }
99  if (setgid(user->pw_gid) < 0) {
100  fprintf(stderr, "vdr: cannot set group id %u: %s\n", (unsigned int)user->pw_gid, strerror(errno));
101  return false;
102  }
103  if (initgroups(user->pw_name, user->pw_gid) < 0) {
104  fprintf(stderr, "vdr: cannot set supplemental group ids for user %s: %s\n", user->pw_name, strerror(errno));
105  return false;
106  }
107  if (setuid(user->pw_uid) < 0) {
108  fprintf(stderr, "vdr: cannot set user id %u: %s\n", (unsigned int)user->pw_uid, strerror(errno));
109  return false;
110  }
111  if (UserDump && prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0)
112  fprintf(stderr, "vdr: warning - cannot set dumpable: %s\n", strerror(errno));
113  setenv("HOME", user->pw_dir, 1);
114  setenv("USER", user->pw_name, 1);
115  setenv("LOGNAME", user->pw_name, 1);
116  setenv("SHELL", user->pw_shell, 1);
117  }
118  return true;
119 }
120 
121 static bool DropCaps(void)
122 {
123  // drop all capabilities except selected ones
124  cap_t caps = cap_from_text("= cap_sys_nice,cap_sys_time,cap_net_raw=ep");
125  if (!caps) {
126  fprintf(stderr, "vdr: cap_from_text failed: %s\n", strerror(errno));
127  return false;
128  }
129  if (cap_set_proc(caps) == -1) {
130  fprintf(stderr, "vdr: cap_set_proc failed: %s\n", strerror(errno));
131  cap_free(caps);
132  return false;
133  }
134  cap_free(caps);
135  return true;
136 }
137 
138 static bool SetKeepCaps(bool On)
139 {
140  // set keeping capabilities during setuid() on/off
141  if (prctl(PR_SET_KEEPCAPS, On ? 1 : 0, 0, 0, 0) != 0) {
142  fprintf(stderr, "vdr: prctl failed\n");
143  return false;
144  }
145  return true;
146 }
147 
148 static void SignalHandler(int signum)
149 {
150  switch (signum) {
151  case SIGPIPE:
152  break;
153  case SIGHUP:
154  LastSignal = signum;
155  break;
156  default:
157  LastSignal = signum;
158  Interface->Interrupt();
160  }
161  signal(signum, SignalHandler);
162 }
163 
164 static void Watchdog(int signum)
165 {
166  // Something terrible must have happened that prevented the 'alarm()' from
167  // being called in time, so let's get out of here:
168  esyslog("PANIC: watchdog timer expired - exiting!");
169  exit(1);
170 }
171 
172 int main(int argc, char *argv[])
173 {
174  // Save terminal settings:
175 
176  struct termios savedTm;
177  bool HasStdin = (tcgetpgrp(STDIN_FILENO) == getpid() || getppid() != (pid_t)1) && tcgetattr(STDIN_FILENO, &savedTm) == 0;
178 
179  // Initiate locale:
180 
181  setlocale(LC_ALL, "");
182 
183  // Command line options:
184 
185 #define dd(a, b) (*a ? a : b)
186 #define DEFAULTSVDRPPORT 6419
187 #define DEFAULTWATCHDOG 0 // seconds
188 #define DEFAULTVIDEODIR VIDEODIR
189 #define DEFAULTCONFDIR dd(CONFDIR, VideoDirectory)
190 #define DEFAULTCACHEDIR dd(CACHEDIR, VideoDirectory)
191 #define DEFAULTRESDIR dd(RESDIR, ConfigDirectory)
192 #define DEFAULTPLUGINDIR PLUGINDIR
193 #define DEFAULTLOCDIR LOCDIR
194 #define DEFAULTEPGDATAFILENAME "epg.data"
195 
196  bool StartedAsRoot = false;
197  const char *VdrUser = NULL;
198  bool UserDump = false;
199  int SVDRPport = DEFAULTSVDRPPORT;
200  const char *AudioCommand = NULL;
201  const char *VideoDirectory = DEFAULTVIDEODIR;
202  const char *ConfigDirectory = NULL;
203  const char *CacheDirectory = NULL;
204  const char *ResourceDirectory = NULL;
205  const char *LocaleDirectory = DEFAULTLOCDIR;
206  const char *EpgDataFileName = DEFAULTEPGDATAFILENAME;
207  bool DisplayHelp = false;
208  bool DisplayVersion = false;
209  bool DaemonMode = false;
210  int SysLogTarget = LOG_USER;
211  bool MuteAudio = false;
212  int WatchdogTimeout = DEFAULTWATCHDOG;
213  const char *Terminal = NULL;
214 
215  bool UseKbd = true;
216  const char *LircDevice = NULL;
217 #if !defined(REMOTE_KBD)
218  UseKbd = false;
219 #endif
220 #if defined(REMOTE_LIRC)
221  LircDevice = LIRC_DEVICE;
222 #endif
223 #if defined(VDR_USER)
224  VdrUser = VDR_USER;
225 #endif
226 
227  cPluginManager PluginManager(DEFAULTPLUGINDIR);
228 
229  static struct option long_options[] = {
230  { "audio", required_argument, NULL, 'a' },
231  { "cachedir", required_argument, NULL, 'c' | 0x100 },
232  { "config", required_argument, NULL, 'c' },
233  { "daemon", no_argument, NULL, 'd' },
234  { "device", required_argument, NULL, 'D' },
235  { "dirnames", required_argument, NULL, 'd' | 0x100 },
236  { "edit", required_argument, NULL, 'e' | 0x100 },
237  { "epgfile", required_argument, NULL, 'E' },
238  { "filesize", required_argument, NULL, 'f' | 0x100 },
239  { "genindex", required_argument, NULL, 'g' | 0x100 },
240  { "grab", required_argument, NULL, 'g' },
241  { "help", no_argument, NULL, 'h' },
242  { "instance", required_argument, NULL, 'i' },
243  { "lib", required_argument, NULL, 'L' },
244  { "lirc", optional_argument, NULL, 'l' | 0x100 },
245  { "localedir",required_argument, NULL, 'l' | 0x200 },
246  { "log", required_argument, NULL, 'l' },
247  { "mute", no_argument, NULL, 'm' },
248  { "no-kbd", no_argument, NULL, 'n' | 0x100 },
249  { "plugin", required_argument, NULL, 'P' },
250  { "port", required_argument, NULL, 'p' },
251  { "record", required_argument, NULL, 'r' },
252  { "resdir", required_argument, NULL, 'r' | 0x100 },
253  { "shutdown", required_argument, NULL, 's' },
254  { "split", no_argument, NULL, 's' | 0x100 },
255  { "terminal", required_argument, NULL, 't' },
256  { "user", required_argument, NULL, 'u' },
257  { "userdump", no_argument, NULL, 'u' | 0x100 },
258  { "version", no_argument, NULL, 'V' },
259  { "vfat", no_argument, NULL, 'v' | 0x100 },
260  { "video", required_argument, NULL, 'v' },
261  { "watchdog", required_argument, NULL, 'w' },
262  { NULL, no_argument, NULL, 0 }
263  };
264 
265  int c;
266  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) {
267  switch (c) {
268  case 'a': AudioCommand = optarg;
269  break;
270  case 'c' | 0x100:
271  CacheDirectory = optarg;
272  break;
273  case 'c': ConfigDirectory = optarg;
274  break;
275  case 'd': DaemonMode = true;
276  break;
277  case 'D': if (isnumber(optarg)) {
278  int n = atoi(optarg);
279  if (0 <= n && n < MAXDEVICES) {
281  break;
282  }
283  }
284  fprintf(stderr, "vdr: invalid DVB device number: %s\n", optarg);
285  return 2;
286  case 'd' | 0x100: {
287  char *s = optarg;
288  if (*s != ',') {
289  int n = strtol(s, &s, 10);
290  if (n <= 0 || n >= PATH_MAX) { // PATH_MAX includes the terminating 0
291  fprintf(stderr, "vdr: invalid directory path length: %s\n", optarg);
292  return 2;
293  }
294  DirectoryPathMax = n;
295  if (!*s)
296  break;
297  if (*s != ',') {
298  fprintf(stderr, "vdr: invalid delimiter: %s\n", optarg);
299  return 2;
300  }
301  }
302  s++;
303  if (!*s)
304  break;
305  if (*s != ',') {
306  int n = strtol(s, &s, 10);
307  if (n <= 0 || n > NAME_MAX) { // NAME_MAX excludes the terminating 0
308  fprintf(stderr, "vdr: invalid directory name length: %s\n", optarg);
309  return 2;
310  }
311  DirectoryNameMax = n;
312  if (!*s)
313  break;
314  if (*s != ',') {
315  fprintf(stderr, "vdr: invalid delimiter: %s\n", optarg);
316  return 2;
317  }
318  }
319  s++;
320  if (!*s)
321  break;
322  int n = strtol(s, &s, 10);
323  if (n != 0 && n != 1) {
324  fprintf(stderr, "vdr: invalid directory encoding: %s\n", optarg);
325  return 2;
326  }
327  DirectoryEncoding = n;
328  if (*s) {
329  fprintf(stderr, "vdr: unexpected data: %s\n", optarg);
330  return 2;
331  }
332  }
333  break;
334  case 'e' | 0x100:
335  return CutRecording(optarg) ? 0 : 2;
336  case 'E': EpgDataFileName = (*optarg != '-' ? optarg : NULL);
337  break;
338  case 'f' | 0x100:
339  Setup.MaxVideoFileSize = StrToNum(optarg) / MEGABYTE(1);
344  break;
345  case 'g' | 0x100:
346  return GenerateIndex(optarg) ? 0 : 2;
347  case 'g': cSVDRP::SetGrabImageDir(*optarg != '-' ? optarg : NULL);
348  break;
349  case 'h': DisplayHelp = true;
350  break;
351  case 'i': if (isnumber(optarg)) {
352  InstanceId = atoi(optarg);
353  if (InstanceId >= 0)
354  break;
355  }
356  fprintf(stderr, "vdr: invalid instance id: %s\n", optarg);
357  return 2;
358  case 'l': {
359  char *p = strchr(optarg, '.');
360  if (p)
361  *p = 0;
362  if (isnumber(optarg)) {
363  int l = atoi(optarg);
364  if (0 <= l && l <= 3) {
365  SysLogLevel = l;
366  if (!p)
367  break;
368  if (isnumber(p + 1)) {
369  int l = atoi(p + 1);
370  if (0 <= l && l <= 7) {
371  int targets[] = { LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7 };
372  SysLogTarget = targets[l];
373  break;
374  }
375  }
376  }
377  }
378  if (p)
379  *p = '.';
380  fprintf(stderr, "vdr: invalid log level: %s\n", optarg);
381  return 2;
382  }
383  case 'L': if (access(optarg, R_OK | X_OK) == 0)
384  PluginManager.SetDirectory(optarg);
385  else {
386  fprintf(stderr, "vdr: can't access plugin directory: %s\n", optarg);
387  return 2;
388  }
389  break;
390  case 'l' | 0x100:
391  LircDevice = optarg ? optarg : LIRC_DEVICE;
392  break;
393  case 'l' | 0x200:
394  if (access(optarg, R_OK | X_OK) == 0)
395  LocaleDirectory = optarg;
396  else {
397  fprintf(stderr, "vdr: can't access locale directory: %s\n", optarg);
398  return 2;
399  }
400  break;
401  case 'm': MuteAudio = true;
402  break;
403  case 'n' | 0x100:
404  UseKbd = false;
405  break;
406  case 'p': if (isnumber(optarg))
407  SVDRPport = atoi(optarg);
408  else {
409  fprintf(stderr, "vdr: invalid port number: %s\n", optarg);
410  return 2;
411  }
412  break;
413  case 'P': PluginManager.AddPlugin(optarg);
414  break;
415  case 'r': cRecordingUserCommand::SetCommand(optarg);
416  break;
417  case 'r' | 0x100:
418  ResourceDirectory = optarg;
419  break;
420  case 's': ShutdownHandler.SetShutdownCommand(optarg);
421  break;
422  case 's' | 0x100:
424  break;
425  case 't': Terminal = optarg;
426  if (access(Terminal, R_OK | W_OK) < 0) {
427  fprintf(stderr, "vdr: can't access terminal: %s\n", Terminal);
428  return 2;
429  }
430  break;
431  case 'u': if (*optarg)
432  VdrUser = optarg;
433  break;
434  case 'u' | 0x100:
435  UserDump = true;
436  break;
437  case 'V': DisplayVersion = true;
438  break;
439  case 'v' | 0x100:
440  DirectoryPathMax = 250;
441  DirectoryNameMax = 40;
442  DirectoryEncoding = true;
443  break;
444  case 'v': VideoDirectory = optarg;
445  while (optarg && *optarg && optarg[strlen(optarg) - 1] == '/')
446  optarg[strlen(optarg) - 1] = 0;
447  break;
448  case 'w': if (isnumber(optarg)) {
449  int t = atoi(optarg);
450  if (t >= 0) {
451  WatchdogTimeout = t;
452  break;
453  }
454  }
455  fprintf(stderr, "vdr: invalid watchdog timeout: %s\n", optarg);
456  return 2;
457  default: return 2;
458  }
459  }
460 
461  // Set user id in case we were started as root:
462 
463  if (VdrUser && geteuid() == 0) {
464  StartedAsRoot = true;
465  if (strcmp(VdrUser, "root")) {
466  if (!SetKeepCaps(true))
467  return 2;
468  if (!SetUser(VdrUser, UserDump))
469  return 2;
470  if (!SetKeepCaps(false))
471  return 2;
472  if (!DropCaps())
473  return 2;
474  }
475  }
476 
477  // Help and version info:
478 
479  if (DisplayHelp || DisplayVersion) {
480  if (!PluginManager.HasPlugins())
481  PluginManager.AddPlugin("*"); // adds all available plugins
482  PluginManager.LoadPlugins();
483  if (DisplayHelp) {
484  printf("Usage: vdr [OPTIONS]\n\n" // for easier orientation, this is column 80|
485  " -a CMD, --audio=CMD send Dolby Digital audio to stdin of command CMD\n"
486  " --cachedir=DIR save cache files in DIR (default: %s)\n"
487  " -c DIR, --config=DIR read config files from DIR (default: %s)\n"
488  " -d, --daemon run in daemon mode\n"
489  " -D NUM, --device=NUM use only the given DVB device (NUM = 0, 1, 2...)\n"
490  " there may be several -D options (default: all DVB\n"
491  " devices will be used)\n"
492  " --dirnames=PATH[,NAME[,ENC]]\n"
493  " set the maximum directory path length to PATH\n"
494  " (default: %d); if NAME is also given, it defines\n"
495  " the maximum directory name length (default: %d);\n"
496  " the optional ENC can be 0 or 1, and controls whether\n"
497  " special characters in directory names are encoded as\n"
498  " hex values (default: 0); if PATH or NAME are left\n"
499  " empty (as in \",,1\" to only set ENC), the defaults\n"
500  " apply\n"
501  " --edit=REC cut recording REC and exit\n"
502  " -E FILE, --epgfile=FILE write the EPG data into the given FILE (default is\n"
503  " '%s' in the cache directory)\n"
504  " '-E-' disables this\n"
505  " if FILE is a directory, the default EPG file will be\n"
506  " created in that directory\n"
507  " --filesize=SIZE limit video files to SIZE bytes (default is %dM)\n"
508  " only useful in conjunction with --edit\n"
509  " --genindex=REC generate index for recording REC and exit\n"
510  " -g DIR, --grab=DIR write images from the SVDRP command GRAB into the\n"
511  " given DIR; DIR must be the full path name of an\n"
512  " existing directory, without any \"..\", double '/'\n"
513  " or symlinks (default: none, same as -g-)\n"
514  " -h, --help print this help and exit\n"
515  " -i ID, --instance=ID use ID as the id of this VDR instance (default: 0)\n"
516  " -l LEVEL, --log=LEVEL set log level (default: 3)\n"
517  " 0 = no logging, 1 = errors only,\n"
518  " 2 = errors and info, 3 = errors, info and debug\n"
519  " if logging should be done to LOG_LOCALn instead of\n"
520  " LOG_USER, add '.n' to LEVEL, as in 3.7 (n=0..7)\n"
521  " -L DIR, --lib=DIR search for plugins in DIR (default is %s)\n"
522  " --lirc[=PATH] use a LIRC remote control device, attached to PATH\n"
523  " (default: %s)\n"
524  " --localedir=DIR search for locale files in DIR (default is\n"
525  " %s)\n"
526  " -m, --mute mute audio of the primary DVB device at startup\n"
527  " --no-kbd don't use the keyboard as an input device\n"
528  " -p PORT, --port=PORT use PORT for SVDRP (default: %d)\n"
529  " 0 turns off SVDRP\n"
530  " -P OPT, --plugin=OPT load a plugin defined by the given options\n"
531  " -r CMD, --record=CMD call CMD before and after a recording, and after\n"
532  " a recording has been edited or deleted\n"
533  " --resdir=DIR read resource files from DIR (default: %s)\n"
534  " -s CMD, --shutdown=CMD call CMD to shutdown the computer\n"
535  " --split split edited files at the editing marks (only\n"
536  " useful in conjunction with --edit)\n"
537  " -t TTY, --terminal=TTY controlling tty\n"
538  " -u USER, --user=USER run as user USER; only applicable if started as\n"
539  " root\n"
540  " --userdump allow coredumps if -u is given (debugging)\n"
541  " -v DIR, --video=DIR use DIR as video directory (default: %s)\n"
542  " -V, --version print version information and exit\n"
543  " --vfat for backwards compatibility (same as\n"
544  " --dirnames=250,40,1\n"
545  " -w SEC, --watchdog=SEC activate the watchdog timer with a timeout of SEC\n"
546  " seconds (default: %d); '0' disables the watchdog\n"
547  "\n",
550  PATH_MAX - 1,
551  NAME_MAX,
555  LIRC_DEVICE,
561  );
562  }
563  if (DisplayVersion)
564  printf("vdr (%s/%s) - The Video Disk Recorder\n", VDRVERSION, APIVERSION);
565  if (PluginManager.HasPlugins()) {
566  if (DisplayHelp)
567  printf("Plugins: vdr -P\"name [OPTIONS]\"\n\n");
568  for (int i = 0; ; i++) {
569  cPlugin *p = PluginManager.GetPlugin(i);
570  if (p) {
571  const char *help = p->CommandLineHelp();
572  printf("%s (%s) - %s\n", p->Name(), p->Version(), p->Description());
573  if (DisplayHelp && help) {
574  printf("\n");
575  puts(help);
576  }
577  }
578  else
579  break;
580  }
581  }
582  return 0;
583  }
584 
585  // Log file:
586 
587  if (SysLogLevel > 0)
588  openlog("vdr", LOG_CONS, SysLogTarget); // LOG_PID doesn't work as expected under NPTL
589 
590  // Check the video directory:
591 
592  if (!DirectoryOk(VideoDirectory, true)) {
593  fprintf(stderr, "vdr: can't access video directory %s\n", VideoDirectory);
594  return 2;
595  }
596 
597  // Daemon mode:
598 
599  if (DaemonMode) {
600  if (daemon(1, 0) == -1) {
601  fprintf(stderr, "vdr: %m\n");
602  esyslog("ERROR: %m");
603  return 2;
604  }
605  }
606  else if (Terminal) {
607  // Claim new controlling terminal
608  stdin = freopen(Terminal, "r", stdin);
609  stdout = freopen(Terminal, "w", stdout);
610  stderr = freopen(Terminal, "w", stderr);
611  HasStdin = true;
612  tcgetattr(STDIN_FILENO, &savedTm);
613  }
614 
615  isyslog("VDR version %s started", VDRVERSION);
616  if (StartedAsRoot && VdrUser)
617  isyslog("switched to user '%s'", VdrUser);
618  if (DaemonMode)
619  dsyslog("running as daemon (tid=%d)", cThread::ThreadId());
621 
622  // Set the system character table:
623 
624  char *CodeSet = NULL;
625  if (setlocale(LC_CTYPE, ""))
626  CodeSet = nl_langinfo(CODESET);
627  else {
628  char *LangEnv = getenv("LANG"); // last resort in case locale stuff isn't installed
629  if (LangEnv) {
630  CodeSet = strchr(LangEnv, '.');
631  if (CodeSet)
632  CodeSet++; // skip the dot
633  }
634  }
635  if (CodeSet) {
636  bool known = SI::SetSystemCharacterTable(CodeSet);
637  isyslog("codeset is '%s' - %s", CodeSet, known ? "known" : "unknown");
639  }
640 
641  // Initialize internationalization:
642 
643  I18nInitialize(LocaleDirectory);
644 
645  // Main program loop variables - need to be here to have them initialized before any EXIT():
646 
647  cEpgDataReader EpgDataReader;
648  cOsdObject *Menu = NULL;
649  int LastChannel = 0;
650  int LastTimerChannel = -1;
651  int PreviousChannel[2] = { 1, 1 };
652  int PreviousChannelIndex = 0;
653  time_t LastChannelChanged = time(NULL);
654  time_t LastInteract = 0;
655  int MaxLatencyTime = 0;
656  bool InhibitEpgScan = false;
657  bool IsInfoMenu = false;
658  cSkin *CurrentSkin = NULL;
659 
660  // Load plugins:
661 
662  if (!PluginManager.LoadPlugins(true))
663  EXIT(2);
664 
665  // Directories:
666 
667  SetVideoDirectory(VideoDirectory);
668  if (!ConfigDirectory)
669  ConfigDirectory = DEFAULTCONFDIR;
670  cPlugin::SetConfigDirectory(ConfigDirectory);
671  if (!CacheDirectory)
672  CacheDirectory = DEFAULTCACHEDIR;
673  cPlugin::SetCacheDirectory(CacheDirectory);
674  if (!ResourceDirectory)
675  ResourceDirectory = DEFAULTRESDIR;
676  cPlugin::SetResourceDirectory(ResourceDirectory);
677  cThemes::SetThemesDirectory("/var/lib/vdr/data/themes");
678 
679  // Configuration data:
680 
681  Setup.Load(AddDirectory(ConfigDirectory, "setup.conf"));
682  Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true);
683  Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC);
684  Scrs.Load(AddDirectory(ConfigDirectory, "scr.conf"), true);
685  Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
686  Timers.Load(AddDirectory(ConfigDirectory, "timers.conf"));
687  Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"));
688  RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"));
689  TimerCommands.Load(AddDirectory(ConfigDirectory, "timercmds.conf"));
690  SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true);
691  Keys.Load(AddDirectory(ConfigDirectory, "remote.conf"));
692  KeyMacros.Load(AddDirectory(ConfigDirectory, "keymacros.conf"), true);
693  Folders.Load(AddDirectory(ConfigDirectory, "folders.conf"));
694 
696  const char *msg = "no fonts available - OSD will not show any text!";
697  fprintf(stderr, "vdr: %s\n", msg);
698  esyslog("ERROR: %s", msg);
699  }
700 
701  // Recordings:
702 
703  Recordings.Update();
705 
706  // EPG data:
707 
708  if (EpgDataFileName) {
709  const char *EpgDirectory = NULL;
710  if (DirectoryOk(EpgDataFileName)) {
711  EpgDirectory = EpgDataFileName;
712  EpgDataFileName = DEFAULTEPGDATAFILENAME;
713  }
714  else if (*EpgDataFileName != '/' && *EpgDataFileName != '.')
715  EpgDirectory = CacheDirectory;
716  if (EpgDirectory)
717  cSchedules::SetEpgDataFileName(AddDirectory(EpgDirectory, EpgDataFileName));
718  else
719  cSchedules::SetEpgDataFileName(EpgDataFileName);
720  EpgDataReader.Start();
721  }
722 
723  // DVB interfaces:
724 
727 
728  // Initialize plugins:
729 
730  if (!PluginManager.InitializePlugins())
731  EXIT(2);
732 
733  // Primary device:
734 
736  if (!cDevice::PrimaryDevice() || !cDevice::PrimaryDevice()->HasDecoder()) {
737  if (cDevice::PrimaryDevice() && !cDevice::PrimaryDevice()->HasDecoder())
738  isyslog("device %d has no MPEG decoder", cDevice::PrimaryDevice()->DeviceNumber() + 1);
739  for (int i = 0; i < cDevice::NumDevices(); i++) {
740  cDevice *d = cDevice::GetDevice(i);
741  if (d && d->HasDecoder()) {
742  isyslog("trying device number %d instead", i + 1);
743  if (cDevice::SetPrimaryDevice(i + 1)) {
744  Setup.PrimaryDVB = i + 1;
745  break;
746  }
747  }
748  }
749  if (!cDevice::PrimaryDevice()) {
750  const char *msg = "no primary device found - using first device!";
751  fprintf(stderr, "vdr: %s\n", msg);
752  esyslog("ERROR: %s", msg);
754  EXIT(2);
755  if (!cDevice::PrimaryDevice()) {
756  const char *msg = "no primary device found - giving up!";
757  fprintf(stderr, "vdr: %s\n", msg);
758  esyslog("ERROR: %s", msg);
759  EXIT(2);
760  }
761  }
762  }
763 
764  // Check for timers in automatic start time window:
765 
767 
768  // User interface:
769 
770  Interface = new cInterface(SVDRPport);
771 
772  // Default skins:
773 
774  new cSkinLCARS;
775  new cSkinSTTNG;
776  new cSkinClassic;
779  CurrentSkin = Skins.Current();
780 
781  // Start plugins:
782 
783  if (!PluginManager.StartPlugins())
784  EXIT(2);
785 
786  // Set skin and theme in case they're implemented by a plugin:
787 
788  if (!CurrentSkin || CurrentSkin == Skins.Current() && strcmp(Skins.Current()->Name(), Setup.OSDSkin) != 0) {
791  }
792 
793  // Remote Controls:
794  if (LircDevice)
795  new cLircRemote(LircDevice);
796  if (!DaemonMode && HasStdin && UseKbd)
797  new cKbdRemote;
798  Interface->LearnKeys();
799 
800  // External audio:
801 
802  if (AudioCommand)
803  new cExternalAudio(AudioCommand);
804 
805  // Channel:
806 
808  dsyslog("not all devices ready after %d seconds", DEVICEREADYTIMEOUT);
809  if (*Setup.InitialChannel) {
810  if (isnumber(Setup.InitialChannel)) { // for compatibility with old setup.conf files
811  if (cChannel *Channel = Channels.GetByNumber(atoi(Setup.InitialChannel)))
812  Setup.InitialChannel = Channel->GetChannelID().ToString();
813  }
815  Setup.CurrentChannel = Channel->Number();
816  }
817  if (Setup.InitialVolume >= 0)
820  if (MuteAudio)
822  else
824 
825  // Signal handlers:
826 
827  if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN);
828  if (signal(SIGINT, SignalHandler) == SIG_IGN) signal(SIGINT, SIG_IGN);
829  if (signal(SIGTERM, SignalHandler) == SIG_IGN) signal(SIGTERM, SIG_IGN);
830  if (signal(SIGPIPE, SignalHandler) == SIG_IGN) signal(SIGPIPE, SIG_IGN);
831  if (WatchdogTimeout > 0)
832  if (signal(SIGALRM, Watchdog) == SIG_IGN) signal(SIGALRM, SIG_IGN);
833 
834  // Watchdog:
835 
836  if (WatchdogTimeout > 0) {
837  dsyslog("setting watchdog timer to %d seconds", WatchdogTimeout);
838  alarm(WatchdogTimeout); // Initial watchdog timer start
839  }
840 
841  // Main program loop:
842 
843 #define DELETE_MENU ((IsInfoMenu &= (Menu == NULL)), delete Menu, Menu = NULL)
844 
845  while (!ShutdownHandler.DoExit()) {
846 #ifdef DEBUGRINGBUFFERS
847  cRingBufferLinear::PrintDebugRBL();
848 #endif
849  // Attach launched player control:
851 
852  time_t Now = time(NULL);
853 
854  // Make sure we have a visible programme in case device usage has changed:
856  static time_t lastTime = 0;
858  if (!CamMenuActive() && Now - lastTime > MINCHANNELWAIT) { // !CamMenuActive() to avoid interfering with the CAM if a CAM menu is open
860  if (Channel && (Channel->Vpid() || Channel->Apid(0) || Channel->Dpid(0))) {
861  if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(Channel->Number())) // try to switch to the original channel...
862  ;
863  else if (LastTimerChannel > 0) {
864  Channel = Channels.GetByNumber(LastTimerChannel);
865  if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
866  ;
867  }
868  }
869  lastTime = Now; // don't do this too often
870  LastTimerChannel = -1;
871  }
872  }
873  else
874  lastTime = 0; // makes sure we immediately try again next time
875  }
876  // Update the OSD size:
877  {
878  static time_t lastOsdSizeUpdate = 0;
879  if (Now != lastOsdSizeUpdate) { // once per second
881  lastOsdSizeUpdate = Now;
882  }
883  }
884  // Restart the Watchdog timer:
885  if (WatchdogTimeout > 0) {
886  int LatencyTime = WatchdogTimeout - alarm(WatchdogTimeout);
887  if (LatencyTime > MaxLatencyTime) {
888  MaxLatencyTime = LatencyTime;
889  dsyslog("max. latency time %d seconds", MaxLatencyTime);
890  }
891  }
892  // Handle channel and timer modifications:
893  if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
894  int modified = Channels.Modified();
895  static time_t ChannelSaveTimeout = 0;
896  static int TimerState = 0;
897  // Channels and timers need to be stored in a consistent manner,
898  // therefore if one of them is changed, we save both.
899  if (modified == CHANNELSMOD_USER || Timers.Modified(TimerState))
900  ChannelSaveTimeout = 1; // triggers an immediate save
901  else if (modified && !ChannelSaveTimeout)
902  ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
903  bool timeout = ChannelSaveTimeout == 1 || ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active();
904  if ((modified || timeout) && Channels.Lock(false, 100)) {
905  if (timeout) {
906  Channels.Save();
907  Timers.Save();
908  ChannelSaveTimeout = 0;
909  }
910  for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
911  if (Channel->Modification(CHANNELMOD_RETUNE)) {
913  if (Channel->Number() == cDevice::CurrentChannel()) {
915  if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
916  isyslog("retuning due to modification of channel %d", Channel->Number());
917  Channels.SwitchTo(Channel->Number());
918  }
919  }
920  }
921  }
922  }
923  Channels.Unlock();
924  }
925  }
926  // Channel display:
927  if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) {
928  if (!Menu)
929  Menu = new cDisplayChannel(cDevice::CurrentChannel(), LastChannel >= 0);
930  LastChannel = cDevice::CurrentChannel();
931  LastChannelChanged = Now;
932  }
933  if (Now - LastChannelChanged >= Setup.ZapTimeout && LastChannel != PreviousChannel[PreviousChannelIndex])
934  PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel;
935  // Timers and Recordings:
936  if (!Timers.BeingEdited()) {
937  // Assign events to timers:
938  Timers.SetEvents();
939  // Must do all following calls with the exact same time!
940  // Process ongoing recordings:
942  // Start new recordings:
943  cTimer *Timer = Timers.GetMatch(Now);
944  if (Timer) {
945  if (!cRecordControls::Start(Timer))
946  Timer->SetPending(true);
947  else
948  LastTimerChannel = Timer->Channel()->Number();
949  }
950  // Make sure timers "see" their channel early enough:
951  static time_t LastTimerCheck = 0;
952  if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
953  InhibitEpgScan = false;
954  for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
955  bool InVpsMargin = false;
956  bool NeedsTransponder = false;
957  if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
958  if (Timer->HasFlags(tfVps)) {
959  if (Timer->Matches(Now, true, Setup.VpsMargin)) {
960  InVpsMargin = true;
961  Timer->SetInVpsMargin(InVpsMargin);
962  }
963  else if (Timer->Event()) {
964  InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
965  NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
966  }
967  else {
968  cSchedulesLock SchedulesLock;
969  const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
970  if (Schedules) {
971  const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
972  InVpsMargin = !Schedule; // we must make sure we have the schedule
973  NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
974  }
975  }
976  InhibitEpgScan |= InVpsMargin | NeedsTransponder;
977  }
978  else
979  NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
980  }
981  if (NeedsTransponder || InVpsMargin) {
982  // Find a device that provides the required transponder:
984  if (!Device && InVpsMargin)
986  // Switch the device to the transponder:
987  if (Device) {
988  bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
989  if (!Device->IsTunedToTransponder(Timer->Channel())) {
990  if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
991  cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
992  dsyslog("switching device %d to channel %d", Device->DeviceNumber() + 1, Timer->Channel()->Number());
993  if (Device->SwitchChannel(Timer->Channel(), false))
995  }
996  if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
997  Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
998  }
999  }
1000  }
1001  LastTimerCheck = Now;
1002  }
1003  // Delete expired timers:
1005  }
1006  if (!Menu && Recordings.NeedsUpdate()) {
1007  Recordings.Update();
1009  }
1010  // CAM control:
1011  if (!Menu && !cOsd::IsOpen())
1012  Menu = CamControl();
1013  // Queued messages:
1014  if (!Skins.IsOpen())
1016  // User Input:
1017  cOsdObject *Interact = Menu ? Menu : cControl::Control();
1018  eKeys key = Interface->GetKey(!Interact || !Interact->NeedsFastResponse());
1019  if (ISREALKEY(key)) {
1020  EITScanner.Activity();
1021  // Cancel shutdown countdown:
1024  // Set user active for MinUserInactivity time in the future:
1026  }
1027  // Keys that must work independent of any interactive mode:
1028  switch (int(key)) {
1029  // Menu control:
1030  case kMenu: {
1031  key = kNone; // nobody else needs to see this key
1032  bool WasOpen = Interact != NULL;
1033  bool WasMenu = Interact && Interact->IsMenu();
1034  if (Menu)
1035  DELETE_MENU;
1036  else if (cControl::Control()) {
1037  if (cOsd::IsOpen())
1038  cControl::Control()->Hide();
1039  else
1040  WasOpen = false;
1041  }
1042  if (!WasOpen || !WasMenu && !Setup.MenuKeyCloses)
1043  Menu = new cMenuMain;
1044  }
1045  break;
1046  // Info:
1047  case kInfo: {
1048  if (IsInfoMenu) {
1049  key = kNone; // nobody else needs to see this key
1050  DELETE_MENU;
1051  }
1052  else if (!Menu) {
1053  IsInfoMenu = true;
1054  if (cControl::Control()) {
1055  cControl::Control()->Hide();
1056  Menu = cControl::Control()->GetInfo();
1057  if (Menu)
1058  Menu->Show();
1059  else
1060  IsInfoMenu = false;
1061  }
1062  else {
1063  cRemote::Put(kOk, true);
1064  cRemote::Put(kSchedule, true);
1065  }
1066  key = kNone; // nobody else needs to see this key
1067  }
1068  }
1069  break;
1070  // Direct main menu functions:
1071  #define DirectMainFunction(function)\
1072  { DELETE_MENU;\
1073  if (cControl::Control())\
1074  cControl::Control()->Hide();\
1075  Menu = new cMenuMain(function);\
1076  key = kNone; } // nobody else needs to see this key
1077  case kSchedule: DirectMainFunction(osSchedule); break;
1078  case kChannels: DirectMainFunction(osChannels); break;
1079  case kTimers: DirectMainFunction(osTimers); break;
1081  case kSetup: DirectMainFunction(osSetup); break;
1082  case kCommands: DirectMainFunction(osCommands); break;
1083  case kUser0 ... kUser9: cRemote::PutMacro(key); key = kNone; break;
1084  case k_Plugin: {
1085  const char *PluginName = cRemote::GetPlugin();
1086  if (PluginName) {
1087  DELETE_MENU;
1088  if (cControl::Control())
1089  cControl::Control()->Hide();
1090  cPlugin *plugin = cPluginManager::GetPlugin(PluginName);
1091  if (plugin) {
1092  Menu = plugin->MainMenuAction();
1093  if (Menu)
1094  Menu->Show();
1095  }
1096  else
1097  esyslog("ERROR: unknown plugin '%s'", PluginName);
1098  }
1099  key = kNone; // nobody else needs to see these keys
1100  }
1101  break;
1102  // Channel up/down:
1103  case kChanUp|k_Repeat:
1104  case kChanUp:
1105  case kChanDn|k_Repeat:
1106  case kChanDn:
1107  if (!Interact)
1108  Menu = new cDisplayChannel(NORMALKEY(key));
1109  else if (cDisplayChannel::IsOpen() || cControl::Control()) {
1110  Interact->ProcessKey(key);
1111  continue;
1112  }
1113  else
1114  cDevice::SwitchChannel(NORMALKEY(key) == kChanUp ? 1 : -1);
1115  key = kNone; // nobody else needs to see these keys
1116  break;
1117  // Volume control:
1118  case kVolUp|k_Repeat:
1119  case kVolUp:
1120  case kVolDn|k_Repeat:
1121  case kVolDn:
1122  case kMute:
1123  if (key == kMute) {
1124  if (!cDevice::PrimaryDevice()->ToggleMute() && !Menu) {
1125  key = kNone; // nobody else needs to see these keys
1126  break; // no need to display "mute off"
1127  }
1128  }
1129  else
1131  if (!Menu && !cOsd::IsOpen())
1132  Menu = cDisplayVolume::Create();
1134  key = kNone; // nobody else needs to see these keys
1135  break;
1136  // Audio track control:
1137  case kAudio:
1138  if (cControl::Control())
1139  cControl::Control()->Hide();
1140  if (!cDisplayTracks::IsOpen()) {
1141  DELETE_MENU;
1142  Menu = cDisplayTracks::Create();
1143  }
1144  else
1146  key = kNone;
1147  break;
1148  // Subtitle track control:
1149  case kSubtitles:
1150  if (cControl::Control())
1151  cControl::Control()->Hide();
1153  DELETE_MENU;
1155  }
1156  else
1158  key = kNone;
1159  break;
1160  // Pausing live video:
1161  case kPlayPause:
1162  case kPause:
1163  if (!cControl::Control()) {
1164  DELETE_MENU;
1165  if (Setup.PauseKeyHandling) {
1166  if (Setup.PauseKeyHandling > 1 || Interface->Confirm(tr("Pause live video?"))) {
1168  Skins.QueueMessage(mtError, tr("No free DVB device to record!"));
1169  }
1170  }
1171  key = kNone; // nobody else needs to see this key
1172  }
1173  break;
1174  // Instant recording:
1175  case kRecord:
1176  if (!cControl::Control()) {
1177  if (cRecordControls::Start())
1178  Skins.QueueMessage(mtInfo, tr("Recording started"));
1179  key = kNone; // nobody else needs to see this key
1180  }
1181  break;
1182  // Power off:
1183  case kPower:
1184  isyslog("Power button pressed");
1185  DELETE_MENU;
1186  // Check for activity, request power button again if active:
1187  if (!ShutdownHandler.ConfirmShutdown(false) && Skins.Message(mtWarning, tr("VDR will shut down later - press Power to force"), SHUTDOWNFORCEPROMPT) != kPower) {
1188  // Not pressed power - set VDR to be non-interactive and power down later:
1190  break;
1191  }
1192  // No activity or power button pressed twice - ask for confirmation:
1193  if (!ShutdownHandler.ConfirmShutdown(true)) {
1194  // Non-confirmed background activity - set VDR to be non-interactive and power down later:
1196  break;
1197  }
1198  // Ask the final question:
1199  if (!Interface->Confirm(tr("Press any key to cancel shutdown"), SHUTDOWNCANCELPROMPT, true))
1200  // If final question was canceled, continue to be active:
1201  break;
1202  // Ok, now call the shutdown script:
1204  // Set VDR to be non-interactive and power down again later:
1206  // Do not attempt to automatically shut down for a while:
1208  break;
1209  default: break;
1210  }
1211  Interact = Menu ? Menu : cControl::Control(); // might have been closed in the mean time
1212  if (Interact) {
1213  LastInteract = Now;
1214  eOSState state = Interact->ProcessKey(key);
1215  if (state == osUnknown && Interact != cControl::Control()) {
1216  if (ISMODELESSKEY(key) && cControl::Control()) {
1217  state = cControl::Control()->ProcessKey(key);
1218  if (state == osEnd) {
1219  // let's not close a menu when replay ends:
1221  continue;
1222  }
1223  }
1224  else if (Now - cRemote::LastActivity() > MENUTIMEOUT)
1225  state = osEnd;
1226  }
1227  switch (state) {
1228  case osPause: DELETE_MENU;
1230  Skins.QueueMessage(mtError, tr("No free DVB device to record!"));
1231  break;
1232  case osRecord: DELETE_MENU;
1233  if (cRecordControls::Start())
1234  Skins.QueueMessage(mtInfo, tr("Recording started"));
1235  break;
1236  case osRecordings:
1237  DELETE_MENU;
1239  Menu = new cMenuMain(osRecordings, true);
1240  break;
1241  case osReplay: DELETE_MENU;
1244  break;
1245  case osStopReplay:
1246  DELETE_MENU;
1248  break;
1249  case osSwitchDvb:
1250  DELETE_MENU;
1252  Skins.QueueMessage(mtInfo, tr("Switching primary DVB..."));
1254  break;
1255  case osPlugin: DELETE_MENU;
1256  Menu = cMenuMain::PluginOsdObject();
1257  if (Menu)
1258  Menu->Show();
1259  break;
1260  case osBack:
1261  case osEnd: if (Interact == Menu)
1262  DELETE_MENU;
1263  else
1265  break;
1266  default: ;
1267  }
1268  }
1269  else {
1270  // Key functions in "normal" viewing mode:
1271  if (key != kNone && KeyMacros.Get(key)) {
1272  cRemote::PutMacro(key);
1273  key = kNone;
1274  }
1275  switch (int(key)) {
1276  // Toggle channels:
1277  case kChanPrev:
1278  case k0: {
1279  if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel || LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])
1280  PreviousChannelIndex ^= 1;
1281  Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
1282  break;
1283  }
1284  // Direct Channel Select:
1285  case k1 ... k9:
1286  // Left/Right rotates through channel groups:
1287  case kLeft|k_Repeat:
1288  case kLeft:
1289  case kRight|k_Repeat:
1290  case kRight:
1291  // Previous/Next rotates through channel groups:
1292  case kPrev|k_Repeat:
1293  case kPrev:
1294  case kNext|k_Repeat:
1295  case kNext:
1296  // Up/Down Channel Select:
1297  case kUp|k_Repeat:
1298  case kUp:
1299  case kDown|k_Repeat:
1300  case kDown:
1301  Menu = new cDisplayChannel(NORMALKEY(key));
1302  break;
1303  // Viewing Control:
1304  case kOk: LastChannel = -1; break; // forces channel display
1305  // Instant resume of the last viewed recording:
1306  case kPlay:
1310  }
1311  else
1312  DirectMainFunction(osRecordings); // no last viewed recording, so enter the Recordings menu
1313  break;
1314  default: break;
1315  }
1316  }
1317  if (!Menu) {
1318  if (!InhibitEpgScan)
1319  EITScanner.Process();
1320  if (!cCutter::Active() && cCutter::Ended()) {
1321  if (cCutter::Error())
1322  Skins.Message(mtError, tr("Editing process failed!"));
1323  else
1324  Skins.Message(mtInfo, tr("Editing process finished"));
1325  }
1327  if (cFileTransfer::Error())
1328  Skins.Message(mtError, tr("File transfer failed!"));
1329  else
1330  Skins.Message(mtInfo, tr("File transfer finished"));
1331  }
1332  }
1333 
1334  // SIGHUP shall cause a restart:
1335  if (LastSignal == SIGHUP) {
1336  if (ShutdownHandler.ConfirmRestart(true) && Interface->Confirm(tr("Press any key to cancel restart"), RESTARTCANCELPROMPT, true))
1337  EXIT(1);
1338  LastSignal = 0;
1339  }
1340 
1341  // Update the shutdown countdown:
1343  if (!ShutdownHandler.ConfirmShutdown(false))
1345  }
1346 
1348  // Handle housekeeping tasks
1349 
1350  // Shutdown:
1351  // Check whether VDR will be ready for shutdown in SHUTDOWNWAIT seconds:
1352  time_t Soon = Now + SHUTDOWNWAIT;
1354  if (ShutdownHandler.ConfirmShutdown(false))
1355  // Time to shut down - start final countdown:
1356  ShutdownHandler.countdown.Start(tr("VDR will shut down in %s minutes"), SHUTDOWNWAIT); // the placeholder is really %s!
1357  // Dont try to shut down again for a while:
1359  }
1360  // Countdown run down to 0?
1361  if (ShutdownHandler.countdown.Done()) {
1362  // Timed out, now do a final check:
1364  ShutdownHandler.DoShutdown(false);
1365  // Do this again a bit later:
1367  }
1368 
1369  // Disk housekeeping:
1372  // Plugins housekeeping:
1373  PluginManager.Housekeeping();
1374  }
1375 
1377 
1378  // Main thread hooks of plugins:
1379  PluginManager.MainThreadHook();
1380  }
1381 
1383  esyslog("emergency exit requested - shutting down");
1384 
1385 Exit:
1386 
1387  // Reset all signal handlers to default before Interface gets deleted:
1388  signal(SIGHUP, SIG_DFL);
1389  signal(SIGINT, SIG_DFL);
1390  signal(SIGTERM, SIG_DFL);
1391  signal(SIGPIPE, SIG_DFL);
1392  signal(SIGALRM, SIG_DFL);
1393 
1394  PluginManager.StopPlugins();
1397  cCutter::Stop();
1398  delete Menu;
1400  delete Interface;
1402  Remotes.Clear();
1403  Audios.Clear();
1404  Skins.Clear();
1405  SourceParams.Clear();
1406  if (ShutdownHandler.GetExitCode() != 2) {
1409  Setup.Save();
1410  }
1412  EpgHandlers.Clear();
1413  PluginManager.Shutdown(true);
1414  cSchedules::Cleanup(true);
1415  ReportEpgBugFixStats(true);
1416  if (WatchdogTimeout > 0)
1417  dsyslog("max. latency time %d seconds", MaxLatencyTime);
1418  if (LastSignal)
1419  isyslog("caught signal %d", LastSignal);
1421  esyslog("emergency exit!");
1422  isyslog("exiting, exit code %d", ShutdownHandler.GetExitCode());
1423  if (SysLogLevel > 0)
1424  closelog();
1425  if (HasStdin)
1426  tcsetattr(STDIN_FILENO, TCSANOW, &savedTm);
1427  return ShutdownHandler.GetExitCode();
1428 }
1429