Index: vdr.c
===================================================================
--- vdr.c	(Revision 821)
+++ vdr.c	(Arbeitskopie)
@@ -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"
@@ -186,7 +187,6 @@
   bool MuteAudio = false;
   int WatchdogTimeout = DEFAULTWATCHDOG;
   const char *Terminal = NULL;
-  const char *Shutdown = NULL;
 
   bool UseKbd = true;
   const char *LircDevice = NULL;
@@ -315,7 +315,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) {
@@ -500,11 +500,8 @@
   int PreviousChannel[2] = { 1, 1 };
   int PreviousChannelIndex = 0;
   time_t LastChannelChanged = time(NULL);
-  time_t LastActivity = 0;
   time_t LastCamMenu = 0;
   int MaxLatencyTime = 0;
-  bool ForceShutdown = false;
-  bool UserShutdown = false;
   bool InhibitEpgScan = false;
   bool IsInfoMenu = false;
   cSkin *CurrentSkin = NULL;
@@ -599,6 +596,11 @@
         }
      }
 
+
+  // Check for timers in automatic start time window:
+  Shutdown.CheckManualStart(MANUALSTART);
+
+
   // User interface:
 
   Interface = new cInterface(SVDRPport);
@@ -864,9 +866,14 @@
         // User Input:
         cOsdObject *Interact = Menu ? Menu : cControl::Control();
         eKeys key = Interface->GetKey((!Interact || !Interact->NeedsFastResponse()) && time(NULL) - LastCamMenu > LASTCAMMENUTIMEOUT);
-        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           
+           Shutdown.SetUserInactiveTimeout();
            }
         // Keys that must work independent of any interactive mode:
         switch (key) {
@@ -1004,37 +1011,27 @@
                   }
                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!"));
-                  break;
-                  }
-               LastActivity = 1; // not 0, see below!
-               UserShutdown = true;
-               if (cRecordControls::Active()) {
-                  if (!Interface->Confirm(tr("Recording - shut down anyway?")))
-                     break;
-                  }
-               if (cPluginManager::Active(tr("shut down anyway?")))
-                  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;
+               
+               // Check for activity and ask for confirmation
+               if (Shutdown.ConfirmShutdown(true)) {
+                  // Ask the final question
+                  if (Interface->Confirm(tr("Press any key to cancel shutdown"), 5, true)) {
+                     // Ok, now call the shutdown scripts
+                     Shutdown.DoShutdown(true);
+                     // and set user inactive in 5 minutes
+                     Shutdown.SetUserInactiveTimeout(SHUTDOWNRETRY,true);
                      }
+                  // If final question was canceled, user continues to be interactive
                   }
-               ForceShutdown = true;
+               else {
+                  // Background activity, just go non-interactive
+                  Shutdown.SetUserInactiveTimeout(0,true); 
+                  }
+                  
                break;
-               }
           default: break;
           }
         Interact = Menu ? Menu : cControl::Control(); // might have been closed in the mean time
@@ -1049,12 +1046,12 @@
                     continue;
                     }
                  }
-              else if (time(NULL) - LastActivity > MENUTIMEOUT)
+              else if (cRemote::LastActivity() > MENUTIMEOUT)
                  state = osEnd;
               }
            // TODO make the CAM menu stay open in case of automatic updates and have it return osContinue; then the following two lines can be removed again
-           else if (state == osEnd && LastActivity > 1)
-              LastActivity = time(NULL);
+           else if (state == osEnd)
+              cRemote::TouchLastActivity();
            switch (state) {
              case osPause:  DELETE_MENU;
                             cControl::Shutdown(); // just in case
@@ -1154,74 +1151,73 @@
                  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:
+           if (Shutdown.IsUserInactive() && !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);
+              
+              // In any case, become active for 5 minutes before checking again
+              Shutdown.SetUserInactiveTimeout(SHUTDOWNRETRY, true);
               }
+
+           // 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.SetUserInactiveTimeout(SHUTDOWNRETRY, true);
+              }
+ 
+           // 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));
+           
+           fprintf(stderr,"Counter: %s\r",Shutdown.countdown ? "Yes" : "No ");
+           
+           DebugTime = Now;
+           }
+
+        #endif
         }
   if (Interrupted)
      isyslog("caught signal %d", Interrupted);
Index: Makefile
===================================================================
--- Makefile	(Revision 821)
+++ Makefile	(Arbeitskopie)
@@ -35,7 +35,7 @@
 OBJS = audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdevice.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	(Revision 0)
+++ shutdown.c	(Revision 0)
@@ -0,0 +1,184 @@
+/*
+ * 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 "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;
+  shutdownCommand = NULL;
+}
+
+void cShutdown::CheckManualStart(int ManualStart)
+{
+  cTimer *timer = Timers.GetNextActiveTimer();
+  time_t Next  = timer ? timer->StartTime() : 0;
+  time_t Delta = timer ? Next - time(NULL) : 0;
+  
+  if (!timer || 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
+     SetUserInactiveTimeout(0);
+}
+
+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);
+  SystemExec(cmd);
+  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 (cRecordControls::Active()) {
+     if (!Interactive || !Interface->Confirm(tr("Recording - shut down anyway?")))
+        return false;
+     }
+  if (cCutter::Active()) {
+     if (!Interactive || !Interface->Confirm(tr("Cutting - shut down anyway?")))
+        return false;
+     }
+  if (cPluginManager::Active(Interactive ? tr("shut down anyway?") : NULL))
+     return false;
+
+  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) {
+     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;
+     }
+     
+  return true;  
+}
+
+bool cShutdown::DoShutdown(bool Force)
+{
+  time_t Now = time(NULL);
+  cTimer *timer = Timers.GetNextActiveTimer();
+  time_t Next  = timer ? timer->StartTime() : 0;
+  time_t Delta = timer ? 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)
+     dsyslog("next timer event at %s", *TimeToString(Next));
+
+  if (timer)
+     CallShutdownCommand(Next, timer->Channel()->Number(), timer->File(), Force);
+  else
+     CallShutdownCommand(0, 0, "", Force);
+
+  return true;
+}
Index: shutdown.h
===================================================================
--- shutdown.h	(Revision 0)
+++ shutdown.h	(Revision 0)
@@ -0,0 +1,93 @@
+/*
+ * 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.
+  
+  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(void) { return activeTimeout && activeTimeout <= time(NULL); }
+      ///< Check whether VDR is in interactive mode or non-interactive mode (waiting for shutdown).
+  
+  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 MinUserInactivity in the future.
+      ///< Otherwise, seconds in the future.
+      ///< If MinUserInactivity = 0 and Force = false, Seconds is ignored and VDR will 
+      ///< stay interactive forever.
+  
+  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	(Revision 821)
+++ remote.c	(Arbeitskopie)
@@ -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);
+         TouchLastActivity();
          return k;
          }
       else if (!WaitMs || !keyPressed.TimedWait(mutex, WaitMs) && repeatTimeout.TimedOut())
Index: remote.h
===================================================================
--- remote.h	(Revision 821)
+++ remote.h	(Arbeitskopie)
@@ -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,12 @@
       ///< 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().
+  static void TouchLastActivity() { lastActivity = time(NULL); }
+      ///< Set last key press time to now.
   };
 
 class cRemotes : public cList<cRemote> {};
