Index: newplugin
===================================================================
--- newplugin	(.../tags/1.5.0)	(Revision 846)
+++ newplugin	(.../branches/shutdown)	(Revision 846)
@@ -169,6 +169,7 @@
   virtual void Housekeeping(void);
   virtual void MainThreadHook(void);
   virtual cString Active(void);
+  virtual time_t NextWakeupEvent(void);
   virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
   virtual cOsdObject *MainMenuAction(void);
   virtual cMenuSetupPage *SetupMenu(void);
@@ -236,6 +237,12 @@
   return NULL;
 }
 
+time_t cPlugin${PLUGIN_CLASS}::NextWakeupEvent(void)
+{
+  // Return custom wakeup time for shutdown script
+  return 0;
+}
+
 cOsdObject *cPlugin${PLUGIN_CLASS}::MainMenuAction(void)
 {
   // Perform the action when selected from the main VDR menu.
Index: INSTALL
===================================================================
--- INSTALL	(.../tags/1.5.0)	(Revision 846)
+++ INSTALL	(.../branches/shutdown)	(Revision 846)
@@ -160,30 +160,32 @@
 (see the Setup parameters in MANUAL).
 
 The command given in the '-s' option will be called with five parameters.
-The first one is the time (in UTC) of the next timer event (as a time_t
-type number), and the second one is the number of seconds from the current
-time until the next timer event. Your program can choose which one to use
-for programming some sort of hardware device that makes sure the computer
-will be restarted in time before the next timer event. Your program must
-also initiate the actual shutdown procedure of the computer. After this
-your program should return to VDR. VDR will not automatically exit after
-calling the shutdown program, but will rather continue normally until it
-receives a SIGTERM when the computer is actually shut down. So in case
-the shutdown fails, or the shutdown program for some reason decides not to
-perform a shutdown, VDR will stay up and running and will call the shutdown
-program again after another five minutes.
+The first one is the time (in UTC) of the next timer event or plugin wakeup
+event (as a time_t type number), and the second one is the number of 
+seconds from the current time until the next timer event. Your program can 
+choose which one to use for programming some sort of hardware device that
+makes sure the computer will be restarted in time before the next timer
+event. Your program must also initiate the actual shutdown procedure of the
+computer. VDR will not automatically exit after calling the shutdown
+program, but will rather continue normally until it receives a SIGTERM when
+the computer is actually shut down. So in case the shutdown fails, or the
+shutdown program for some reason decides not to perform a shutdown, VDR
+will stay up and running and will call the shutdown program again after
+another five minutes. The command will be started in a separate background
+session, so it can continue to run even after VDR has terminated.
 
-If there are currently no timers active, both parameters will be '0'.
-In that case the program shall not set the hardware for automatic restart
-and only perform the system shutdown. A program that uses the second parameter
-to set the hardware for restart must therefore also check whether the first
-parameter is '0'.
+If there are currently no timers active and there is no plugin wakeup
+event, both parameters will be '0'. In that case the program shall not set
+the hardware for automatic restart and only perform the system shutdown. A
+program that uses the second parameter to set the hardware for restart must
+therefore also check whether the first parameter is '0'.
 
-The third parameter contains the number of the channel that will be recorded
-by the next timer (or 0 if no timer is present), and the fourth parameter
-contains the file name of the recording as defined in the timer (or an empty
-string if no timer is present). These can be used by the shutdown program to
-show that information on some display interface etc.
+If the wakeup time is a timer time, the third parameter will be the number
+of the channel that will be recorded, otherwise it will be 0. The fourth 
+parameter contains the file name of the recording as defined in the timer,
+the name of the plugin that requested the wakeup time, or an empty string
+if no wakeup time is present. These can be used by the shutdown program 
+to show that information on some display interface etc.
 
 The fifth parameter indicates the reason why the shutdown was requested.
 '0' means this is an automatic shutdown due to some timeout, while '1' means
Index: plugin.c
===================================================================
--- plugin.c	(.../tags/1.5.0)	(Revision 846)
+++ plugin.c	(.../branches/shutdown)	(Revision 846)
@@ -80,6 +80,11 @@
   return NULL;
 }
 
+time_t cPlugin::NextWakeupEvent(void)
+{
+  return 0;
+}
+
 const char *cPlugin::MainMenuEntry(void)
 {
   return NULL;
@@ -403,6 +408,26 @@
   return false;
 }
 
+cPlugin *cPluginManager::GetNextWakeupEventPlugin(void)
+{
+  time_t Next = 0;
+  cPlugin *NextPlugin = NULL;
+  time_t Now = time(NULL);
+  if (pluginManager) {
+     for (cDll *dll = pluginManager->dlls.First(); dll; dll = pluginManager->dlls.Next(dll)) {
+         cPlugin *p = dll->Plugin();
+         if (p) {
+            time_t t = p->NextWakeupEvent();
+            if (t > Now && (!Next || t < Next)) {
+               Next = t;
+               NextPlugin = p;
+               }
+            }
+         }
+     }
+  return NextPlugin;
+}
+
 bool cPluginManager::HasPlugins(void)
 {
   return pluginManager && pluginManager->dlls.Count();
Index: plugin.h
===================================================================
--- plugin.h	(.../tags/1.5.0)	(Revision 846)
+++ plugin.h	(.../branches/shutdown)	(Revision 846)
@@ -17,6 +17,9 @@
 
 #define VDRPLUGINCREATOR(PluginClass) extern "C" void *VDRPluginCreator(void) { return new PluginClass; }
 
+#define PATCH_SHUTDOWN_REWRITE 0.3
+// Drop this after integration into VDR 1.5.x
+
 class cPlugin {
   friend class cDll;
   friend class cPluginManager;
@@ -41,6 +44,7 @@
   virtual void Housekeeping(void);
   virtual void MainThreadHook(void);
   virtual cString Active(void);
+  virtual time_t NextWakeupEvent(void);
 
   virtual const char *MainMenuEntry(void);
   virtual cOsdObject *MainMenuAction(void);
@@ -93,6 +97,7 @@
   void Housekeeping(void);
   void MainThreadHook(void);
   static bool Active(const char *Prompt = NULL);
+  static cPlugin *GetNextWakeupEventPlugin(void);
   static bool HasPlugins(void);
   static cPlugin *GetPlugin(int Index);
   static cPlugin *GetPlugin(const char *Name);
Index: thread.c
===================================================================
--- thread.c	(.../tags/1.5.0)	(Revision 846)
+++ thread.c	(.../branches/shutdown)	(Revision 846)
@@ -505,7 +505,7 @@
 
 // --- SystemExec ------------------------------------------------------------
 
-int SystemExec(const char *Command)
+int SystemExec(const char *Command, bool Detached)
 {
   pid_t pid;
 
@@ -515,14 +515,25 @@
      }
 
   if (pid > 0) { // parent process
-     int status;
-     if (waitpid(pid, &status, 0) < 0) {
+     int status = 0;
+     if (!Detached && waitpid(pid, &status, 0) < 0) {
         LOG_ERROR;
         return -1;
         }
      return status;
      }
   else { // child process
+     if (Detached) {
+        // Start a new session
+        pid_t sid=setsid();
+        if (sid < 0) LOG_ERROR;
+
+        // close STDIN and re-open as /dev/null
+        int devnull = open("/dev/null", O_RDONLY);
+        if (devnull < 0) LOG_ERROR;
+        if (dup2(devnull, 0) < 0) LOG_ERROR;
+        }  
+     
      int MaxPossibleFileDescriptors = getdtablesize();
      for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
          close(i); //close all dup'ed filedescriptors
Index: thread.h
===================================================================
--- thread.h	(.../tags/1.5.0)	(Revision 846)
+++ thread.h	(.../branches/shutdown)	(Revision 846)
@@ -175,7 +175,9 @@
 
 // SystemExec() implements a 'system()' call that closes all unnecessary file
 // descriptors in the child process.
+// With Detached=true, calls command in background and in a separate session,
+// with stdin connected to /dev/null.
 
-int SystemExec(const char *Command);
+int SystemExec(const char *Command, bool Detached = false);
 
 #endif //__THREAD_H
Index: PLUGINS.html
===================================================================
--- PLUGINS.html	(.../tags/1.5.0)	(Revision 846)
+++ PLUGINS.html	(.../branches/shutdown)	(Revision 846)
@@ -675,6 +675,37 @@
 the shutdown will take place. As soon as one prompt is not confirmed, no
 further plugins will be queried and no shutdown will be done.
 
+<a name="Wakeup"><hr><h2>Wakeup</h2>
+
+<center><i><b>Wake me up before you go-go</b></i></center><p>
+
+If a plugin wants to schedule activity for a later time, or wants to perform
+periodic activity at a certain time at night, and if VDR shall wake up from 
+shutdown at that time, the plugin can implement the function
+
+<p><table><tr><td bgcolor=#F0F0F0><pre>
+virtual time_t NextWakeupEvent(void);
+</pre></td></tr></table><p>
+
+which shall return the time of the next custom wakeup time, or 0 if no wakeup
+is planned. VDR will pass the most recent wakeup time of all plugins, or the next 
+timer time, whichever comes first, to the shutdown script. The following sample 
+will wake up VDR every night at 1:00:
+
+<p><table><tr><td bgcolor=#F0F0F0><pre>
+time_t NextWakeupEvent(void) {
+  time_t Now = time(NULL);
+  time_t Event = cTimer::SetTime(Now, cTimer::TimeToInt(100));
+  if (Event <= Now) Event = cTimer::IncDay(Event, 1);
+  return Event; 
+}
+</pre></td></tr></table><p>
+
+After wakeup, the plugin shall continue to return the event time and shall
+return a string on <tt>Active()</tt> at that time, otherwise VDR may shut down
+again instantly. If <tt>NextWakeupEvent()</tt> returns a time that is not in 
+the future, the time will be ignored.
+
 <a name="Setup parameters"><hr><h2>Setup parameters</h2>
 
 <center><i><b>Remember me...</b></i></center><p>
Index: config.c
===================================================================
--- config.c	(.../tags/1.5.0)	(Revision 846)
+++ config.c	(.../branches/shutdown)	(Revision 846)
@@ -267,6 +267,7 @@
   SplitEditedFiles = 0;
   MinEventTimeout = 30;
   MinUserInactivity = 300;
+  NextWakeupEvent = 0;
   MultiSpeedMode = 0;
   ShowReplayMode = 0;
   ResumeID = 0;
@@ -428,6 +429,7 @@
   else if (!strcasecmp(Name, "SplitEditedFiles"))    SplitEditedFiles   = atoi(Value);
   else if (!strcasecmp(Name, "MinEventTimeout"))     MinEventTimeout    = atoi(Value);
   else if (!strcasecmp(Name, "MinUserInactivity"))   MinUserInactivity  = atoi(Value);
+  else if (!strcasecmp(Name, "NextWakeupEvent"))     NextWakeupEvent    = atoi(Value);
   else if (!strcasecmp(Name, "MultiSpeedMode"))      MultiSpeedMode     = atoi(Value);
   else if (!strcasecmp(Name, "ShowReplayMode"))      ShowReplayMode     = atoi(Value);
   else if (!strcasecmp(Name, "ResumeID"))            ResumeID           = atoi(Value);
@@ -496,6 +498,7 @@
   Store("SplitEditedFiles",   SplitEditedFiles);
   Store("MinEventTimeout",    MinEventTimeout);
   Store("MinUserInactivity",  MinUserInactivity);
+  Store("NextWakeupEvent",    NextWakeupEvent);
   Store("MultiSpeedMode",     MultiSpeedMode);
   Store("ShowReplayMode",     ShowReplayMode);
   Store("ResumeID",           ResumeID);
Index: config.h
===================================================================
--- config.h	(.../tags/1.5.0)	(Revision 846)
+++ config.h	(.../branches/shutdown)	(Revision 846)
@@ -244,6 +244,7 @@
   int MaxVideoFileSize;
   int SplitEditedFiles;
   int MinEventTimeout, MinUserInactivity;
+  time_t NextWakeupEvent;
   int MultiSpeedMode;
   int ShowReplayMode;
   int ResumeID;
Index: vdr.c
===================================================================
--- vdr.c	(.../tags/1.5.0)	(Revision 846)
+++ vdr.c	(.../branches/shutdown)	(Revision 846)
@@ -54,6 +54,7 @@
 #include "plugin.h"
 #include "rcu.h"
 #include "recording.h"
+#include "shutdown.h"
 #include "skinclassic.h"
 #include "skinsttng.h"
 #include "sources.h"
@@ -70,7 +71,7 @@
 #define CHANNELSAVEDELTA  600 // seconds before saving channels.conf after automatic modifications
 #define DEVICEREADYTIMEOUT 30 // seconds to wait until all devices are ready
 #define MENUTIMEOUT       120 // seconds of user inactivity after which an OSD display is closed
-#define SHUTDOWNRETRY     300 // seconds before trying again to shut down
+#define SHUTDOWNRETRY     360 // seconds before trying again to shut down
 #define TIMERCHECKDELTA    10 // seconds between checks for timers that need to see their channel
 #define TIMERDEVICETIMEOUT  8 // seconds before a device used for timer check may be reused
 #define TIMERLOOKAHEADTIME 60 // seconds before a non-VPS timer starts and the channel is switched if possible
@@ -184,7 +185,6 @@
   bool MuteAudio = false;
   int WatchdogTimeout = DEFAULTWATCHDOG;
   const char *Terminal = NULL;
-  const char *Shutdown = NULL;
 
   bool UseKbd = true;
   const char *LircDevice = NULL;
@@ -313,7 +313,7 @@
                     break;
           case 'r': cRecordingUserCommand::SetCommand(optarg);
                     break;
-          case 's': Shutdown = optarg;
+          case 's': Shutdown.SetShutdownCommand(optarg);
                     break;
           case 't': Terminal = optarg;
                     if (access(Terminal, R_OK | W_OK) < 0) {
@@ -498,10 +498,7 @@
   int PreviousChannel[2] = { 1, 1 };
   int PreviousChannelIndex = 0;
   time_t LastChannelChanged = time(NULL);
-  time_t LastActivity = 0;
   int MaxLatencyTime = 0;
-  bool ForceShutdown = false;
-  bool UserShutdown = false;
   bool InhibitEpgScan = false;
   bool IsInfoMenu = false;
   cSkin *CurrentSkin = NULL;
@@ -596,6 +593,11 @@
         }
      }
 
+
+  // Check for timers in automatic start time window:
+  Shutdown.CheckManualStart(MANUALSTART);
+
+
   // User interface:
 
   Interface = new cInterface(SVDRPport);
@@ -856,9 +858,14 @@
         // User Input:
         cOsdObject *Interact = Menu ? Menu : cControl::Control();
         eKeys key = Interface->GetKey(!Interact || !Interact->NeedsFastResponse());
-        if (NORMALKEY(key) != kNone) {
+        if (NORMALKEY(key) != kNone && NORMALKEY(key) != k_Plugin) {
            EITScanner.Activity();
-           LastActivity = time(NULL);
+
+           // Cancel 5 minute shutdown countdown:
+           if (Shutdown.countdown) Shutdown.countdown.Cancel();
+
+           // Set user active for MinUserInactivity time in the future
+           Shutdown.SetUserInactiveTimeout();
            }
         // Keys that must work independent of any interactive mode:
         switch (key) {
@@ -996,37 +1003,39 @@
                   }
                break;
           // Power off:
-          case kPower: {
+          case kPower:
                isyslog("Power button pressed");
                DELETE_MENU;
-               if (!Shutdown) {
-                  Skins.Message(mtError, tr("Can't shutdown - option '-s' not given!"));
+               
+               // Check for activity, request power button again if active
+               if (!Shutdown.ConfirmShutdown(false)
+                   && Skins.Message(mtWarning, tr("VDR will shut down later. Press power to force."), 5) != kPower) {
+                  // Not pressed power. Set VDR to be non-interactive and power down later.
+                  Shutdown.SetUserInactive();
                   break;
                   }
-               LastActivity = 1; // not 0, see below!
-               UserShutdown = true;
-               if (cRecordControls::Active()) {
-                  if (!Interface->Confirm(tr("Recording - shut down anyway?")))
-                     break;
+               
+               // No activity or 2x power button pressed. Ask for confirmations.
+               if (!Shutdown.ConfirmShutdown(true)) {
+                  // Non-confirmed background activity, set VDR to be non-interactive and power down later.
+                  Shutdown.SetUserInactive();
+                  break;
                   }
-               if (cPluginManager::Active(tr("shut down anyway?")))
+                
+               // Ask the final question
+               if (!Interface->Confirm(tr("Press any key to cancel shutdown"), 5, true))
+                  // If final question was canceled, continue to be active
                   break;
-               if (!cRecordControls::Active()) {
-                  cTimer *timer = Timers.GetNextActiveTimer();
-                  time_t Next  = timer ? timer->StartTime() : 0;
-                  time_t Delta = timer ? Next - time(NULL) : 0;
-                  if (Next && Delta <= Setup.MinEventTimeout * 60) {
-                     char *buf;
-                     asprintf(&buf, tr("Recording in %ld minutes, shut down anyway?"), Delta / 60);
-                     bool confirm = Interface->Confirm(buf);
-                     free(buf);
-                     if (!confirm)
-                        break;
-                     }
-                  }
-               ForceShutdown = true;
+                
+               // Ok, now call the shutdown scripts
+               Shutdown.DoShutdown(true);
+               // Set VDR to be non-interactive and power down again later.
+               Shutdown.SetUserInactive();
+               // Do not attempt to automatically shut down for 6 minutes
+               Shutdown.SetRetry(SHUTDOWNRETRY);
+
+               // will retry shutdown in 6 minutes
                break;
-               }
           default: break;
           }
         Interact = Menu ? Menu : cControl::Control(); // might have been closed in the mean time
@@ -1041,7 +1050,7 @@
                     continue;
                     }
                  }
-              else if (time(NULL) - LastActivity > MENUTIMEOUT)
+              else if (cRemote::LastActivity() > MENUTIMEOUT)
                  state = osEnd;
               }
            switch (state) {
@@ -1143,74 +1152,82 @@
                  Skins.Message(mtInfo, tr("Editing process finished"));
               }
            }
-        if (!Interact && ((!cRecordControls::Active() && !cCutter::Active() && (!Interface->HasSVDRPConnection() || UserShutdown)) || ForceShutdown)) {
-           time_t Now = time(NULL);
-           if (Now - LastActivity > ACTIVITYTIMEOUT) {
-              // Shutdown:
-              if (Shutdown && (Setup.MinUserInactivity || LastActivity == 1) && Now - LastActivity > Setup.MinUserInactivity * 60) {
-                 cTimer *timer = Timers.GetNextActiveTimer();
-                 time_t Next  = timer ? timer->StartTime() : 0;
-                 time_t Delta = timer ? Next - Now : 0;
-                 if (!LastActivity) {
-                    if (!timer || Delta > MANUALSTART) {
-                       // Apparently the user started VDR manually
-                       dsyslog("assuming manual start of VDR");
-                       LastActivity = Now;
-                       continue; // don't run into the actual shutdown procedure below
-                       }
-                    else
-                       LastActivity = 1;
-                    }
-                 if (timer && Delta < Setup.MinEventTimeout * 60 && ForceShutdown) {
-                    Delta = Setup.MinEventTimeout * 60;
-                    Next = Now + Delta;
-                    timer = NULL;
-                    dsyslog("reboot at %s", *TimeToString(Next));
-                    }
-                 if (!ForceShutdown && cPluginManager::Active()) {
-                    LastActivity = Now - Setup.MinUserInactivity * 60 + SHUTDOWNRETRY; // try again later
-                    continue;
-                    }
-                 if (!Next || Delta > Setup.MinEventTimeout * 60 || ForceShutdown) {
-                    ForceShutdown = false;
-                    if (timer)
-                       dsyslog("next timer event at %s", *TimeToString(Next));
-                    if (WatchdogTimeout > 0)
-                       signal(SIGALRM, SIG_IGN);
-                    if (Interface->Confirm(tr("Press any key to cancel shutdown"), UserShutdown ? 5 : SHUTDOWNWAIT, true)) {
-                       cControl::Shutdown();
-                       int Channel = timer ? timer->Channel()->Number() : 0;
-                       const char *File = timer ? timer->File() : "";
-                       if (timer)
-                          Delta = Next - time(NULL); // compensates for Confirm() timeout
-                       char *cmd;
-                       asprintf(&cmd, "%s %ld %ld %d \"%s\" %d", Shutdown, Next, Delta, Channel, *strescape(File, "\"$"), UserShutdown);
-                       isyslog("executing '%s'", cmd);
-                       SystemExec(cmd);
-                       free(cmd);
-                       LastActivity = time(NULL) - Setup.MinUserInactivity * 60 + SHUTDOWNRETRY; // try again later
-                       }
-                    else {
-                      LastActivity = Now;
-                      if (WatchdogTimeout > 0) {
-                         alarm(WatchdogTimeout);
-                         if (signal(SIGALRM, Watchdog) == SIG_IGN)
-                            signal(SIGALRM, SIG_IGN);
-                         }
-                      }
-                    UserShutdown = false;
-                    continue; // skip the rest of the housekeeping for now
-                    }
-                 }
-              // Disk housekeeping:
-              RemoveDeletedRecordings();
-              cSchedules::Cleanup();
-              // Plugins housekeeping:
-              PluginManager.Housekeeping();
+        // Update the shutdown countdown:
+        if (Shutdown.countdown && Shutdown.countdown.Update()) {
+           // Also on countdown, every 10 seconds:
+           
+           // Any action that would cancel shutdown?
+           if (!Shutdown.ConfirmShutdown(false)) 
+              Shutdown.countdown.Cancel();
+           }
+
+        if (!Interact && !cRecordControls::Active() && !cCutter::Active() && !Interface->HasSVDRPConnection() && cRemote::LastActivity() > ACTIVITYTIMEOUT) {
+           // Handle housekeeping tasks
+
+           // Shutdown:
+           // Check whether VDR will be ready for shutdown in 5 minutes:
+           time_t Soon = time(NULL) + SHUTDOWNWAIT;
+           if (Shutdown.IsUserInactive(Soon) && Shutdown.Retry(Soon) && !Shutdown.countdown) {
+              if (Shutdown.ConfirmShutdown(false))
+                 // Time to shut down. Start final 5 minute countdown
+                 Shutdown.countdown.Start(tr("VDR will shut down in %s minutes"),SHUTDOWNWAIT);
+              
+              // Dont try to shut down again for next 6 minutes
+              Shutdown.SetRetry(SHUTDOWNRETRY);
               }
+
+           // Countdown run down to 0?              
+           if (Shutdown.countdown.Done()) {
+              // Timed out, now do a final check
+              if (Shutdown.IsUserInactive() && Shutdown.ConfirmShutdown(false)) 
+                 // and call the shutdown command
+                 Shutdown.DoShutdown(false);
+  
+              // Do this again a bit later
+              Shutdown.SetRetry(SHUTDOWNRETRY);
+              }
+ 
+           // Disk housekeeping:
+           RemoveDeletedRecordings();
+           cSchedules::Cleanup();
+           // Plugins housekeeping:
+           PluginManager.Housekeeping();
            }
+           
         // Main thread hooks of plugins:
         PluginManager.MainThreadHook();
+        
+        
+#define DebugTimeouts
+#ifdef DebugTimeouts
+        
+        static time_t DebugTime = time(NULL);
+        time_t Now = time(NULL);
+        if (Now - DebugTime >= 1) {
+           if (!cRemote::LastActivityTime())
+              fprintf(stderr,"LastActivity: Never ");
+           else
+              fprintf(stderr,"LastActivity: %5i ",cRemote::LastActivity());
+
+           time_t ActiveTimeout = Shutdown.GetUserInactiveTime();
+           if (!ActiveTimeout)
+              fprintf(stderr,"ActiveTimeout:  Never ");
+           else
+              fprintf(stderr,"ActiveTimeout: %6i ",(int)(Now - ActiveTimeout));
+           
+           time_t Retry = Shutdown.GetRetry();
+           if (!Retry)
+              fprintf(stderr,"Retry:  Never ");
+           else
+              fprintf(stderr,"Retry: %6i ",(int)(Now - Retry));
+           
+           fprintf(stderr,"Counter: %s\r",Shutdown.countdown ? "Yes" : "No ");
+           
+           fflush(stderr);
+           DebugTime = Now;
+           }
+
+#endif
         }
   if (Interrupted)
      isyslog("caught signal %d", Interrupted);
Index: Makefile
===================================================================
--- Makefile	(.../tags/1.5.0)	(Revision 846)
+++ Makefile	(.../branches/shutdown)	(Revision 846)
@@ -35,7 +35,7 @@
 OBJS = audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdevice.o dvbci.o dvbosd.o\
        dvbplayer.o dvbspu.o eit.o eitscan.o epg.o filter.o font.o i18n.o interface.o keys.o\
        lirc.o menu.o menuitems.o nit.o osdbase.o osd.o pat.o player.o plugin.o rcu.o\
-       receiver.o recorder.o recording.o remote.o remux.o ringbuffer.o sdt.o sections.o\
+       receiver.o recorder.o recording.o remote.o remux.o ringbuffer.o sdt.o sections.o shutdown.o\
        skinclassic.o skins.o skinsttng.o sources.o spu.o status.o svdrp.o themes.o thread.o\
        timers.o tools.o transfer.o vdr.o videodir.o
 
Index: shutdown.c
===================================================================
--- shutdown.c	(.../tags/1.5.0)	(Revision 0)
+++ shutdown.c	(.../branches/shutdown)	(Revision 846)
@@ -0,0 +1,222 @@
+/*
+ * shutdown.c: Handling for shutdown and inactivity
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: $
+ */
+
+#include "shutdown.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "channels.h"
+#include "config.h"
+#include "cutter.h"
+#include "i18n.h"
+#include "interface.h"
+#include "menu.h"
+#include "plugin.h"
+#include "timers.h"
+#include "tools.h"
+
+cShutdown Shutdown;
+
+cCountdown::cCountdown(void) 
+{
+  timeout = 0;
+  counter = 0;
+  timedOut = false;
+  format = NULL;
+}
+
+void cCountdown::Start(const char *Format, int Seconds)
+{
+  timeout = time(NULL) + Seconds;
+  counter = -1;
+  timedOut = false;
+  format = Format;
+  Update();
+}  
+
+void cCountdown::Cancel(void)
+{ 
+  if (timeout) { 
+     timeout = 0;
+     timedOut = false;
+     Skins.Message(mtStatus, NULL); 
+     } 
+}
+
+bool cCountdown::Update(void)
+{
+  if (!timeout) return false;
+
+  int NewCounter = (timeout - time(NULL) + 9) / 10;
+  if (NewCounter <= 0) 
+     timedOut=true;
+     
+  if (counter != NewCounter) {
+     counter = NewCounter;
+     
+     char time[10];
+     char *Message;
+     snprintf(time, sizeof time, "%i:%i0", counter>0 ? counter/6 : 0, counter>0 ? counter%6 : 0);
+     asprintf(&Message,format,time);
+     
+     Skins.Message(mtStatus, Message);
+     free(Message);
+     return true;
+     }
+  return false;
+}  
+
+cShutdown::cShutdown(void)
+{
+  activeTimeout = 0;
+  retry = 0;
+  shutdownCommand = NULL;
+}
+
+void cShutdown::CheckManualStart(int ManualStart)
+{
+  time_t Delta = Setup.NextWakeupEvent ? Setup.NextWakeupEvent - time(NULL) : 0;
+  
+  if (!Setup.NextWakeupEvent || abs(Delta) > ManualStart) {
+     // Apparently the user started VDR manually
+     dsyslog("assuming manual start of VDR");
+     // Set inactive after MinUserInactivity
+     SetUserInactiveTimeout();
+     }
+  else
+     // Set inactive from now on
+     SetUserInactive();
+}
+
+void cShutdown::SetShutdownCommand(const char *ShutdownCommand)
+{
+  shutdownCommand = ShutdownCommand;
+}
+
+void cShutdown::CallShutdownCommand(time_t NextEvent, int Channel, const char *File, bool UserShutdown)
+{
+  time_t Delta = NextEvent ? NextEvent - time(NULL) : 0;
+  char *cmd;
+
+  asprintf(&cmd, "%s %ld %ld %d \"%s\" %d", shutdownCommand, NextEvent, Delta, Channel, *strescape(File, "\"$"), UserShutdown);
+  isyslog("executing '%s'", cmd);
+  if (SystemExec(cmd, true) == 0)
+     // Remember this wakeup time for comparison on reboot
+     Setup.NextWakeupEvent = NextEvent;
+  free(cmd);
+}
+
+void cShutdown::SetUserInactiveTimeout(int Seconds, bool Force)
+{
+  if (!Setup.MinUserInactivity && !Force) {
+    activeTimeout = 0;
+    return;
+  }
+  if (Seconds < 0) Seconds = Setup.MinUserInactivity * 60;
+  activeTimeout = time(NULL) + Seconds;
+}
+
+bool cShutdown::ConfirmShutdown(bool Interactive)
+{
+  if (!shutdownCommand) {
+     if (Interactive) Skins.Message(mtError, tr("Can't shutdown - option '-s' not given!"));
+     return false;
+     }
+  if (cReplayControl::NowReplaying()) {
+     if (!Interactive || !Interface->Confirm(tr("Replaying - shut down anyway?")))
+        return false;
+     }
+  if (cCutter::Active()) {
+     if (!Interactive || !Interface->Confirm(tr("Cutting - shut down anyway?")))
+        return false;
+     }
+
+  cTimer *timer = Timers.GetNextActiveTimer();
+  time_t Next  = timer ? timer->StartTime() : 0;
+  time_t Delta = timer ? Next - time(NULL) : 0;
+
+  if (cRecordControls::Active() || (Next && Delta <= 0)) {
+     // VPS recordings in timer end margin may cause Delta <= 0
+     
+     if (!Interactive || !Interface->Confirm(tr("Recording - shut down anyway?")))
+        return false;
+     }
+  else if (Next && Delta <= Setup.MinEventTimeout * 60) {
+     // Timer within Min Event Timeout
+     if (!Interactive) return false;
+     
+     char *buf;
+     asprintf(&buf, tr("Recording in %ld minutes, shut down anyway?"), Delta / 60);
+     bool confirm = Interface->Confirm(buf);
+     free(buf);
+     if (!confirm)
+        return false;
+     }
+
+  if (cPluginManager::Active(Interactive ? tr("shut down anyway?") : NULL))
+     return false;
+  
+  cPlugin *Plugin = cPluginManager::GetNextWakeupEventPlugin();
+  Next = Plugin ? Plugin->NextWakeupEvent() : 0;
+  Delta = Next ? Next - time(NULL) : 0;
+  if (Next && Delta <= Setup.MinEventTimeout * 60) {
+     // Plugin event within Min Event Timeout
+     if (!Interactive) return false;
+     
+     char *buf;
+     asprintf(&buf, tr("Plugin activity in %ld minutes, shut down anyway?"), Delta / 60);
+     bool confirm = Interface->Confirm(buf);
+     free(buf);
+     if (!confirm)
+        return false;
+     }
+  
+  return true;  
+}
+
+bool cShutdown::DoShutdown(bool Force)
+{
+  time_t Now = time(NULL);
+  cTimer *timer = Timers.GetNextActiveTimer();
+  cPlugin *Plugin = cPluginManager::GetNextWakeupEventPlugin();
+    
+  time_t Next  = timer ? timer->StartTime() : 0;
+  time_t NextPlugin = Plugin ? Plugin->NextWakeupEvent() : 0;
+  if (NextPlugin && (!Next || Next > NextPlugin)) {
+     Next = NextPlugin;
+     timer = NULL;
+     }
+  time_t Delta = Next ? Next - Now : 0;
+
+  if (Next && Delta < Setup.MinEventTimeout * 60) {
+     if (!Force)
+        return false;
+     Delta = Setup.MinEventTimeout * 60;
+     Next = Now + Delta;
+     timer = NULL;
+     dsyslog("reboot at %s", *TimeToString(Next));
+     }
+
+  if (Next && timer) {
+     dsyslog("next timer event at %s", *TimeToString(Next));
+     CallShutdownCommand(Next, timer->Channel()->Number(), timer->File(), Force);
+     }
+  else if (Next && Plugin) {
+     CallShutdownCommand(Next, 0, Plugin->Name(), Force);
+     dsyslog("next plugin event at %s", *TimeToString(Next));
+     }
+  else
+     // Next should always be 0 here. Just for safety, pass it.
+     CallShutdownCommand(Next, 0, "", Force);
+
+  return true;
+}
Index: shutdown.h
===================================================================
--- shutdown.h	(.../tags/1.5.0)	(Revision 0)
+++ shutdown.h	(.../branches/shutdown)	(Revision 846)
@@ -0,0 +1,110 @@
+/*
+ * shutdown.h: Handling for shutdown and inactivity
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: $
+ */
+
+#ifndef __SHUTDOWN_H
+#define __SHUTDOWN_H
+
+#include <time.h>
+
+class cCountdown {
+private:
+  time_t timeout;     ///< 5-minute countdown timer
+  int counter;        ///< last shown time in 10s units
+  bool timedOut;      ///< Countdown did run down to 0 and was not cancelled
+  const char *format; ///< Format string for message display, %s is placeholder
+  
+public:
+  cCountdown(void);
+
+  void Start(const char *Message, int Seconds); 
+      ///< Start the 5 minute shutdown warning countdown
+
+  void Cancel(void); 
+      ///< Cancel the 5 minute shutdown warning countdown
+
+  bool Done(void) { if (!timedOut) return false; Cancel(); return true; }
+      ///< Check if countdown timer has run out without cancelling
+
+  operator bool(void) const { return timeout != 0; }
+      ///< Check if countdown is running
+
+  bool Update(void);
+      ///< Update status display of the countdown
+      ///< Returns true on actual update
+};
+
+class cShutdown {
+private:
+  time_t activeTimeout;
+      ///< Time when VDR will become non-interactive. 0 means never.
+  time_t retry;
+      ///< Time for retrying the shutdown. 
+  
+  const char *shutdownCommand;
+      ///< Command for shutting down VDR, set with -s option
+
+public:
+  cShutdown(void);
+
+  void CheckManualStart(int ManualStart);
+      ///< Check whether next timer is in ManualStart time window.
+      ///< If yes, assume non-interactive use
+  
+  void SetShutdownCommand(const char *ShutdownCommand);
+      ///< Set the command string for shutdown command
+  
+  void CallShutdownCommand(time_t NextEvent, int Channel, const char *File, bool UserShutdown);
+      ///< Call the shutdown command, giving it these parameters
+  
+  bool IsUserInactive(time_t AtTime = 0) { return activeTimeout && activeTimeout <= (AtTime ? AtTime : time(NULL)); }
+      ///< Check whether VDR is in interactive mode or non-interactive mode (waiting for shutdown).
+      ///< AtTime checks whether VDR will probably be inactive at that time.
+
+  time_t GetUserInactiveTime(void) { return activeTimeout; }
+      ///< Time when user will become non-inactive, or 0 if never
+  
+  void SetUserInactiveTimeout(int Seconds = -1, bool Force = false);
+      ///< Set the time when VDR will switch into non-interactive mode or power down.
+      ///< -1 means Setup.MinUserInactivity in the future.
+      ///< Otherwise, seconds in the future.
+      ///< If MinUserInactivity = 0 and Force = false, Seconds is ignored and VDR will 
+      ///< stay interactive forever.
+      
+  void SetUserInactive() { SetUserInactiveTimeout(0, true); }
+      ///< Set VDR manually into non-interactive mode.
+  
+  bool Retry(time_t AtTime = 0) { return retry <= (AtTime ? AtTime : time(NULL)); }
+      ///< Check whether its time to re-try the shutdown.
+      ///< AtTime checks whether VDR will probably be inactive at that time.
+  
+  time_t GetRetry(void) { return retry; }
+      ///< Time when shutdown retry block ends.
+
+  void SetRetry(int Seconds) { retry = time(NULL) + Seconds; }
+      ///< Set shutdown retry so that VDR will not try to automatically shut down
+      ///< within Seconds.
+  
+  bool ConfirmShutdown(bool Ask);
+      ///< Check for background activity that blocks shutdown.
+      ///< Returns immediately and without user interaction if Ask=false
+      ///< Asks for confirmation if ask=true
+      ///< Returns true if ready for shutdown.
+  
+  bool DoShutdown(bool Force);
+      ///< Call the shutdown script with data of the next pending timer.
+      ///< Fails if force = false and a timer is running or within MinEventTimeout.
+      ///< Always calls shutdown on force = true.
+      ///< Returns true on success.
+    
+  cCountdown countdown;
+};
+
+extern cShutdown Shutdown;
+
+#endif
Index: remote.c
===================================================================
--- remote.c	(.../tags/1.5.0)	(Revision 846)
+++ remote.c	(.../branches/shutdown)	(Revision 846)
@@ -31,6 +31,7 @@
 cCondVar cRemote::keyPressed;
 const char *cRemote::keyMacroPlugin = NULL;
 const char *cRemote::callPlugin = NULL;
+time_t cRemote::lastActivity = 0;
 
 cRemote::cRemote(const char *Name)
 {
@@ -183,6 +184,7 @@
             out = 0;
          if ((k & k_Repeat) != 0)
             repeatTimeout.Set(REPEATTIMEOUT);
+         lastActivity = time(NULL);
          return k;
          }
       else if (!WaitMs || !keyPressed.TimedWait(mutex, WaitMs) && repeatTimeout.TimedOut())
Index: remote.h
===================================================================
--- remote.h	(.../tags/1.5.0)	(Revision 846)
+++ remote.h	(.../branches/shutdown)	(Revision 846)
@@ -28,6 +28,7 @@
   static char *unknownCode;
   static cMutex mutex;
   static cCondVar keyPressed;
+  static time_t lastActivity;
   static const char *keyMacroPlugin;
   static const char *callPlugin;
   char *name;
@@ -61,6 +62,10 @@
       ///< plugin name will be reset to NULL by this call.
   static bool HasKeys(void);
   static eKeys Get(int WaitMs = 1000, char **UnknownCode = NULL);
+  static int LastActivity() { return time(NULL) - lastActivity; }
+      ///< Seconds since last key was delivered by Get().
+  static time_t LastActivityTime() { return lastActivity; }
+      ///< Absolute time when last key was delivered by Get().
   };
 
 class cRemotes : public cList<cRemote> {};

Eigenschaftsänderungen: 
___________________________________________________________________
Name: svnmerge-integrated
   + /vdr/trunk:1-833

