diff -ruN vdr-1.3.37/CONTRIBUTORS vdr-1.3.38/CONTRIBUTORS
--- vdr-1.3.37/CONTRIBUTORS	2005-11-26 15:16:02.000000000 +0100
+++ vdr-1.3.38/CONTRIBUTORS	2006-01-08 12:03:44.000000000 +0100
@@ -16,6 +16,8 @@
  for fixing converting summary.vdr files that would result in a very long 'short text'
  for his help in testing and debugging reading the list of recordings in a
  separate thread
+ for reporting some unnecessary disk access when checking if there are deleted
+ recordings that need to be removed
 
 Plamen Ganev <pganev@com-it.net>
  for fixing the frequency offset for Hotbird channels
@@ -310,6 +312,7 @@
  for his support in keeping the Premiere World channels up to date in 'channels.conf'
  for pointing out that section filters should only be set if the device actually has
  a lock
+ for reporting a lockup with the RCU on NPTL systems
 
 Simon Bauschulte <SemiSchwabe@Brutzel.de>
  for his support in keeping the Premiere World channels up to date in 'channels.conf'
@@ -480,6 +483,8 @@
 Andreas Böttger <fboettger@t-online.de>
  for reporting a bug in skipping forward in time shift mode near the end of the recording
  for fixing setting system time to avoid time jumps in case of faulty data
+ for reporting a bug in the "Day" field of the "Edit timer" menu when pressing
+ '0' to switch from "single shot" to "weekly", followed by the "Right" key
 
 Onno Kreuzinger <ok@solutas.net>
  for reporting leftover references to the file FORMATS in MANUAL and svdrp.c
@@ -517,8 +522,9 @@
 Joerg Riechardt <J.Riechardt@gmx.de>
  for filling in some missing teletext PIDs
 
-Holger Wächtler <holger@convergence.de>
+Holger Wächtler <holger@qanu.de>
  for some valuable advice during adapting to the NEWSTRUCT driver
+ for suggesting to use FE_READ_STATUS to read the current frontend status
 
 Jürgen Zimmermann <jnzimmer@informatik.uni-kl.de>
  for adding some missing #includes to files in libdtv for gcc 3.2
@@ -531,6 +537,7 @@
  for implementing a default cRemote::Initialize()
  for suggesting to increase the default value for 'Min. user inactivity' to 300 minutes
  for suggesting to add cChannel::LinkChannels() and cChannel::RefChannel()
+ for suggesting to give a message when an instant recording is started
 
 Jeremy Hall <jhall@UU.NET>
  for fixing an incomplete initialization of the filter parameters in eit.c
@@ -821,6 +828,7 @@
  cThread::Start()
  for removing the LOCK_THREAD from the LIRC thread
  for making the Makefile patch friendlier
+ for a patch that was used for implementing setting the user id
 
 Thomas Koch <tom@harhar.net>
  for his support in keeping the Premiere World channels up to date in 'channels.conf'
@@ -1007,6 +1015,11 @@
  for suggesting to always use stream id 0xE0 for the video stream, to avoid problems
  with post processing tools that choke on different ids
  for fixing cDvbPlayer::SkipFrames() to properly handle radio recordings
+ for improving TS/PES conversion to better handle lost TS packets
+ for suggesting to make the DVB devices retune (and, if applicable, resend the
+ DiSEqC data) if the lock is lost
+ for fixing handling TS packets in cTS2PES
+ for adding a mutex to synchronize cDevice::PlayPesPacket() and SetCurrentAudioTrack()
 
 Richard Robson <richard_robson@beeb.net>
  for reporting freezing replay if a timer starts while in Transfer Mode from the
@@ -1252,6 +1265,8 @@
  for fixing initializing the day index when editing the weekday parameter of a
  repeating timer
  for figuring out some obscure length bytes the the CA PMT Reply data of AlphaCrypt CAMs
+ for fixing handling OSD areas that have invalid sizes
+ for removing unused variables in skinclassic.c and skinsttng.c
 
 Jürgen Schmitz <j.schmitz@web.de>
  for reporting a bug in displaying the current channel when switching via the SVDRP
@@ -1307,6 +1322,7 @@
  for reporting a bug in opening recording folders in case the last replayed recording
  no longer exists
  for reporting a missing check against MAXOSDAREAS in cOsd::CanHandleAreas()
+ for making the Makefile report a summary of failed plugins
 
 Sven Kreiensen <svenk@kammer.uni-hannover.de>
  for his help in keeping 'channels.conf.terr' up to date
@@ -1318,6 +1334,7 @@
  for updating the Romanian language texts and the iso8859-2 fonts
  for making VDR actually use the iso8859-15 fonts
  for suggesting to make the function ExchangeChars()
+ for reporting duplicate texts in i18n.c
 
 Mattias Grönlund <Mattias@Gronlund.net>
  for pointing out a missing cleanup at program exit in case there is a problem
@@ -1346,6 +1363,8 @@
  for reporting the missing Euro sign in iso8859-1
  for reporting a problem with making changes to timers through SVDRP while they
  are being edited via the menu
+ for suggesting to change the API of the functions cStatus::Recording() and
+ cStatus::Replaying(), so that they can provide the full file name of the recording
 
 Dino Ravnic <dino.ravnic@fer.hr>
  for fixing some characters in the iso8859-2 font file
@@ -1364,6 +1383,9 @@
  for a patch that was used to add the command line options '--lirc', '--rcu' and
  '--no-kbd'
  for adding '__attribute__' to functions that use printf() like parameters
+ for suggesting to write grabbed images to the SVDRP connection encoded in base64
+ for suggesting to open the file handle in the SVDRP GRAB command in a way that
+ it won't follow symbolic links, and to canonicalize the file name
 
 Sean Carlos <seanc@libero.it>
  for translating OSD texts to the Italian language
@@ -1416,6 +1438,7 @@
 Luca Olivetti <luca@ventoso.org>
  for making cDevice::AttachPlayer() keep the track language codes and descriptions
  in Transfer Mode
+ for suggesting to make the "Menu" key behave consistently
 
 Mikko Salo <mikko.salo@ppe.inet.fi>
  for suggesting to make the setup option "DVB/Video display format" available only
@@ -1432,6 +1455,10 @@
  for making LIRC command parsing more robust
  for fixing some typos in MANUAL
  for reporting that the default value for "Setup/EPG bugfix level" was wrong
+ for fixing initializing pthread_mutexattr_t and pthread_rwlockattr_t to avoid
+ warnings with g++ 4.1.0
+ for reporting warnings with g++ 4.1.0 regarding incrementing the 'state' variables
+ in the repacker classes in remux.c
 
 Steffen Beyer <cpunk@reactor.de>
  for fixing setting the colored button help after deleting a recording in case the next
@@ -1453,6 +1480,8 @@
 Johannes Stezenbach <js@linuxtv.org>
  for pointing out that the byte swap for big endian systems in cDvbOsd::Flush()
  is wrong
+ for suggesting to use gettid() syscall to get a thread's pid, so that we get a
+ useful value on NPTL systems
 
 Paavo Hartikainen <pahartik@sci.fi>
  for verifying that the byte swap for big endian systems in cDvbOsd::Flush() was
@@ -1538,6 +1567,10 @@
 
 Patrick Fischer <patrick_fischer@gmx.de>
  for reporting an error in the cFilter example in PLUGINS.html
+ for making the static cControl functions thread safe
+ for suggesting that the cTimer constructor should take an optional cChannel
+ for suggesting that any cReceivers still attached to a cDevice when that device
+ switches to a different transponder shall be automatically detached
 
 Ralf Müller <ralf@bj-ig.de>
  for a patch that was used to implement cUnbufferedFile
@@ -1547,3 +1580,48 @@
 
 Holger Brunn <holger.brunn@stud.uni-karlsruhe.de>
  for adding a copy constructor to cString and fixing its assignment operator
+
+Christian Vogt <c.v@nexgo.de>
+ for suggesting to take deleted recordings into account when displaying the
+ amount of free disk space
+
+Christof Steininger <christof.steininger@t-online.de>
+ for fixing a possible crash when displaying the "Low disk space!" message from
+ a background thread
+
+Kendy Kutzner <kutzner@ira.uka.de>
+ for making the version number of EPG events be stored in the epg.data file
+ for suggesting to add cTimer::SetPriority() to set a timer's priority
+
+Bob Withers <bwit@pobox.com>
+ for publishing a Base64 encoder at http://www.ruffboy.com/download.htm
+ (http://www.ruffboy.com/code/Base64.zip), part of which was used when writing
+ the cBase64Encoder class
+
+Javier Fernández-Sanguino Peña <jfs@computer.org>
+ for reporting a security hole in the way the SVDRP command GRAB writes the
+ image file
+
+Jürgen Schneider <ivory7@gmx.de>
+ for a patch that was used as a base to fix handling multi byte key sequences
+ in cKbdRemote
+
+Christian Wieninger <cwieninger@gmx.de>
+ for suggesting to add cMenuEditStrItem::InEditMode()
+
+Thiemo Gehrke <tgehrke@reel-multimedia.com>
+ for suggesting to add a setup option to turn off the automatic timeout of the
+ channel display in case it was invoked by a press of the "Ok" key
+
+Gavin Hamill <gdh@acentral.co.uk>
+ for reporting a missing #include "thread.h" in dvbspu.c
+
+Petri Hintukainen <Petri.Hintukainen@hut.fi>
+ for suggesting to disable the use of "fadvise" in cUnbufferedFile because there
+ have been several reports that it causes more problems than it solves
+
+Marcel Schaeben <mts280@gmx.de>
+ for his "Easy Input" patch
+
+Ingo Schneider <mail@ingo-schneider.de>
+ for adding a SleepMs() in cRecorder::Action() to avoid a busy loop
diff -ruN vdr-1.3.37/HISTORY vdr-1.3.38/HISTORY
--- vdr-1.3.37/HISTORY	2005-11-27 16:57:03.000000000 +0100
+++ vdr-1.3.38/HISTORY	2006-01-08 12:58:26.000000000 +0100
@@ -1491,9 +1491,9 @@
   --- Makefile    2002/06/10 16:24:06     1.4
   +++ Makefile    2002/09/17 15:36:36     1.5
   @@ -15,7 +15,12 @@
- 
+
    ### The directory environment:
- 
+
   +ifdef NEWSTRUCT
   +DVBDIR = ../../../../DVB/include
   +DEFINES += -DNEWSTRUCT
@@ -1504,12 +1504,12 @@
    VDRINC = $(VDRDIR)/include
    LIBDIR = ../../lib
   @@ -34,7 +39,7 @@
- 
+
    INCLUDES = -I$(VDRINC) -I$(DVBDIR)
- 
+
   -DEFINES = -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
   +DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
- 
+
    ### The object files (add further files here):
   -------------------------------------------------------
 
@@ -2705,7 +2705,7 @@
 - Timers can now be set to use the VPS information to control recording a programme.
   The new setup options "Recording/Use VPS" and "Recording/VPS margin", as well as
   the "VPS" option in the individual timers, can be used to control this feature
-  (see MANUAL for details). 
+  (see MANUAL for details).
   Note that this feature will certainly need a lot of testing before it can be
   called "safe"!
 - The "Schedule" and "What's on now/next?" menus now have an additional column
@@ -3270,7 +3270,7 @@
   botton left side.
 - The new setup option "DVB/Audio languages" can be used to control which audio
   language shall be selected in case a channel broadcasts in different languages
-  (see MANUAL for details). 
+  (see MANUAL for details).
 - The "Left" and "Right" keys in the "Audio" menu can be used to switch between
   the left and right stereo channels in case there are different audio tracks
   in these channels (see MANUAL for details).
@@ -3962,3 +3962,175 @@
 - The SVDRP command MESG uses the new message queueing facility, so MESG
   commands may now be executed at any time, and the message will be displayed
   (no more "pending message").
+
+2006-01-08: Version 1.3.38
+
+- Fixed handling second audio and Dolby Digital PIDs for encrypted channels
+  (was broken in version 1.3.37).
+- Improved TS/PES conversion to better handle lost TS packets (thanks to
+  Reinhard Nissl).
+- Limited the frequency of log messages from the cRepackers.
+- Now using the gettid() syscall to get a thread's pid, so that we get a
+  useful value on NPTL systems (suggested by Johannes Stezenbach).
+- Fixed the RCU remote control handling to avoid problems with NPTL (thanks
+  to Andreas Share for reporting a lockup with the RCU on NPTL systems).
+- When displaying the amount of free disk space, the space consumed by
+  recordings that have been "deleted" but not yet actually "removed" is now
+  taken into account (suggested by Christian Vogt).
+- Now avoiding unnecessary disk access when checking if there are deleted
+  recordings that need to be removed (reported by Carsten Koch).
+- Fixed handling the DELETEDLIFETIME when removing deleted recordings. Now
+  a deleted recording is retained at least DELETEDLIFETIME seconds before
+  actually removing it.
+  The value of DELETEDLIFETIME has been changed to 300. So after (possibly
+  inadvertently) deleting a recording, there will be at least 5 minutes
+  in which it can be recovered (unless a new recording immediately requires
+  the disk space). The count starts again at 0 every time VDR is started.
+- Fixed a possible crash when displaying the "Low disk space!" message from
+  a background thread (thanks to Christof Steininger).
+- Fixed handling OSD areas that have invalid sizes (thanks to Marco Schlüßler).
+- Added a mutex to AssertFreeDiskSpace() to make sure calls from foreground
+  and background threads won't interfere.
+- The main menu now dynamically updates its contents in case an instant
+  recording or replay stops, etc.
+- The version number of EPG events is now also stored in the epg.data file
+  (thanks to Kendy Kutzner).
+- EPG events that are no longer in the currently broadcasted data stream are
+  now automatically deleted.
+- Removed an invalid access to Event->schedule in cSchedule::DelEvent().
+- Modified cSchedule::Cleanup() (events are always sorted by time).
+- Schedules are now cleaned up once every hour (not only at 05:00).
+- The "Schedule" and "What's on now/next?" menus are now updated if a timer
+  is set or modified.
+- cTimer no longer has its own 'schedule' member, it rather uses that of the
+  event it has been set to.
+- The "Red" button in the "Schedule", "What's on now/next?" and "Event" menus
+  now immediately creates a timer for the selected event and marks it with 'T'.
+  If the event is already marked with 'T', the "Red" button opens the "Edit
+  timer" menu for that timer.
+- Removing deleted recordings is now done in a separate thread.
+- Dropped the unused "stop recording on primary interface" stuff.
+- Converting a grabbed image to JPEG is now done with the new function
+  RgbToJpeg() (see tools.h).
+- The SVDRP command GRAB now determines the image type (JPEG or PNM) from the
+  extension (".jpg", ".jpeg" or ".pnm") of the given file name. The explicit
+  'jpeg' or 'pnm' parameter is still accepted for backward compatibility, but
+  has no meaning any more.
+- The function cDevice::GrabImage() no longer writes the grabbed image to a
+  file, but rather returns a pointer to the image in memory. The wrapper
+  function cDevice::GrabImageFile() can be used to write the grabbed image
+  directly to a file. Plugins that used the old version of cDevice::GrabImage()
+  need to be adapted to the new interface.
+- The new class cBase64Encoder (see tools.h) can be used to encode data in
+  base64 (thanks to Bob Withers for publishing his Base64 class).
+- The SVDRP command GRAB now writes the image data to the SVDRP connection
+  (encoded in base64) if the given file name consists of only the file
+  extension (".jpg", ".jpeg" or ".pnm"), or if only "-" is given as file
+  name (based on a suggestion from Darren Salt).
+  A simple way of viewing a grabbed image on a remote host is:
+
+  svdrpsend.pl -d <hostname> 'grab -' | sed -n -e 's/^216-//p' -e '1ibegin-base64 644 -' -e '$a====' | uudecode | display
+
+- The new command line option '-g' must be given if the SVDRP command GRAB
+  shall be allowed to write image files to disk. The parameter to this option
+  must be the full path name of an existing directory, without any "..", double
+  '/' or symlinks. By default, or if "-g- is given, grabbing to files is
+  not allowed any more because of potential security risks.
+- Modified the way the SVDRP command GRAB writes the grabbed image to a file
+  to avoid a security hole (CAN-2005-0071, reported by Javier Fernández-Sanguino
+  Peña):
+  + The file handle is now opened in a way that it won't follow symbolic links
+    (suggested by Darren Salt).
+  + The given file name is now canonicalized, so that it won't contain any
+    ".." or symlinks (suggested by Darren Salt).
+  + Grabbing to files is limited to the directory given in the the command
+    line option '-g'. By default grabbing to files is not allowed any more.
+- Updated the Greek OSD texts (thanks to Dimitrios Dimitrakos).
+- Changed all "illegal" to "invalid" in error messages (there's nothing "illegal"
+  in VDR ;-).
+- When started as user 'root' VDR now switches to a lesser privileged user id,
+  keeping the capability to set the system time (based on a patch from Ludwig
+  Nussel). By default the user id 'vdr' is used, which can be changed through
+  the new command line option '-u'. Note that for security reasons VDR will no
+  longer run as user 'root' (unless you explicitly start it with '-u root',
+  but this is not recommended!). The 'runvdr' script has been changed to
+  use the '-u' option.
+- Changed the API of the functions cStatus::Recording() and cStatus::Replaying(),
+  so that they can provide the full file name of the recording. Plugins that use
+  these (or the related cStatus::Msg...() functions) need to be adapted
+  (suggested by Andreas Brugger).
+- The DVB devices now retune (and, if applicable, resend the DiSEqC data) if
+  the lock is lost (based on a patch from Reinhard Nissl).
+- Fixed handling multi byte key sequences in cKbdRemote (based on a patch from
+  Jürgen Schneider).
+- Removed unused variables in skinclassic.c and skinsttng.c (thanks to Marco
+  Schlüßler).
+- Made the static cControl functions thread safe (thanks to Patrick Fischer).
+- Fixed initializing pthread_mutexattr_t and pthread_rwlockattr_t to avoid
+  warnings with g++ 4.1.0 (thanks to Ville Skyttä).
+- Fixed incrementing the 'state' variables in the repacker classes in remux.c
+  to avoid warnings with g++ 4.1.0 (reported by Ville Skyttä).
+- The Makefile now reports a summary of failed plugins (thanks to Udo Richter).
+- The cTimer constructor can now take an optional cChannel (suggested by
+  Patrick Fischer).
+- Fixed setting the main thread id if VDR is running as a daemon.
+- Fixed handling TS packets in cTS2PES (thanks to Reinhard Nissl).
+- Added cTimer::SetPriority() to set a timer's priority (suggested by Kendy Kutzner).
+- Added cMenuEditStrItem::InEditMode() (suggested by Christian Wieninger).
+- Now using FE_READ_STATUS to read the current frontend status (suggested by
+  Holger Wächtler).
+- The "Menu" key now behaves consistently. If there is anything on the OSD, it
+  is closed when the "Menu" key is pressed, and if there is nothing on the OSD,
+  the "Menu" key opens the main menu (suggested by Luca Olivetti).
+- The new option "Setup/OSD/Timeout requested channel info" can be used to turn
+  off the automatic timeout of the channel display in case it was invoked by
+  a press of the "Ok" key (suggested by Thiemo Gehrke).
+- A message is now given when an instant recording is started (suggested by
+  Helmut Auer). Actually the code was already there, just commented out - don't
+  remember why it wasn't active...
+- Removed an obsolete "Summary" text from i18n.c and preceded all key definition
+  texts with "Key$" to avoid duplicates (reported by Lucian Muresan).
+- Preceded all button texts with "Button$".
+- Removed obsolete "Eject", "Language" and "scanning recordings..." texts.
+- Added missing #include "thread.h" to dvbspu.c (reported by Gavin Hamill).
+- Disabled the use of "fadvise" in cUnbufferedFile because there have been
+  several reports that it causes more problems than it solves (suggested by
+  Petri Hintukainen). If you want to use "fadvise", you can activate the line
+  //#define USE_FADVISE
+  in tools.c.
+- Removed unused 'offset' member from cOsdItem.
+- In the "Channels" menu the numeric keys now position the cursor to the channel
+  with the given number (see MANUAL, section "Remote Control Keys", note (3) for
+  details).
+- The "Mark/Move" function in the "Channels" menu now also works in the non-numeric
+  sort modes.
+- The default cOsdObject::Show() now automatically calls cOsdMenu::Display() if
+  this is a menu.
+- The new "Info" key brings up information on the currently viewed programme
+  or recording. For a live programme this is the same as "Schedule/Ok", i.e. the
+  description of the current EPG event. For a recording this is the same as shown
+  by the "Info" button in the "Recordings" menu. Plugins that implement players
+  can overwrite their cControl::GetInfo() function to show their own info (see
+  PLUGINS.html for details). Pressing the "Info" key again while the info is
+  displayed will close the OSD. In order to assign this new key to an existing
+  remote control setup, the remote.conf file needs to be deleted and VDR has
+  to be restarted to go through the process of learning the remote control keys.
+- Any cReceivers still attached to a cDevice when that device switches to a
+  different transponder are now automatically detached (suggested by Patrick
+  Fischer).
+- The "flags" of a timer are now handled as an unsigned integer value. In order
+  to do this, the interface of cMenuEditBitItem also had to be changed.
+- In string entry fields (like, e.g., the file name of a recording) the characters
+  can now be entered by pressing the numeric keys, the same way as on a
+  telephone keypad (based on the "Easy Input" patch from Marcel Schaeben).
+- Fixed the "Day" field of the "Edit timer" menu when pressing '0' to switch
+  from "single shot" to "weekly", followed by the "Right" key (reported by
+  Andreas Böttger).
+- The file 'ca.conf' is obsolete and has been removed.
+- Revised all descriptions regarding CICAM.
+- Adapted c(Dvb)Device::ProvidesCa() to the dynamic CA handling.
+- Added a mutex to synchronize cDevice::PlayPesPacket() and SetCurrentAudioTrack()
+  (thanks to Reinhard Nissl).
+- Added a SleepMs() in cRecorder::Action() to avoid a busy loop (thanks to Ingo
+  Schneider).
+- Cleaned up some trailing white space.
diff -ruN vdr-1.3.37/INSTALL vdr-1.3.38/INSTALL
--- vdr-1.3.37/INSTALL	2005-09-03 15:25:50.000000000 +0200
+++ vdr-1.3.38/INSTALL	2005-12-31 14:09:17.000000000 +0100
@@ -132,6 +132,19 @@
 VDR will go into 'deamon' mode and the initial program call will return
 immediately!
 
+Setting the system time:
+------------------------
+
+If you want VDR to set the system time according to the data received
+from the transponder, you need to start VDR as user 'root'. VDR will
+then only keep the capability to set the system time, and set its
+user id to a lesser privileged one ('vdr' by default, can be set
+to a different value with the '-u' option).
+You also need to enable the "EPG/Set system time" option in VDR's
+Setup menu, and select a transponder from which you want to receive
+the time in "Use time from transponder". Make sure you select a transponder
+that has a reliable clock - some transponders are quite off.
+
 Automatic shutdown:
 -------------------
 
diff -ruN vdr-1.3.37/MANUAL vdr-1.3.38/MANUAL
--- vdr-1.3.37/MANUAL	2005-09-09 17:07:32.000000000 +0200
+++ vdr-1.3.38/MANUAL	2006-01-08 12:53:08.000000000 +0100
@@ -1,7 +1,7 @@
 Video Disk Recorder User's Manual
 ---------------------------------
 
-Version 1.2
+Version 1.3
 -----------
 
 * Remote Control Keys
@@ -18,7 +18,7 @@
   Left    Prev group  -            Page up    Page up    Decrement     Page up           Search back      Sel. channel
   Right   Next group  -            Page down  Page down  Increment     Page down         Search forward   Sel. channel
   Ok      Ch display  Select       Switch     Edit       Accept        Play              Progress disp.   Switch & Close
-  Menu    Menu on     Menu off     Menu off   Menu off   Menu off      Menu off          Menu on          Menu on
+  Menu    Menu on     Menu off     Menu off   Menu off   Menu off      Menu off          Menu off         Menu off
   Back    -           Menu off     VDR menu   VDR menu   Discard       VDR menu          Recordings menu  Close
   Red     -           Record       Edit       Edit       ABC/abc       Play/Commands(2)  Jump             -
   Green   -           Audio        New        New        Ins/Ovr       Rewind            Skip -60s        -
@@ -30,9 +30,14 @@
   are used to enter the data, and the Left key can be used to delete the last
   entered digit.
 
+  In a text input field (like, e.g., the file name of a recording) the characters
+  can be entered by pressing the numeric keys, the same way as on a telephone
+  keypad.
+
   If your remote control provides additional keys, they can be used for the
   following functions:
 
+  Info        display information on the currently viewed programme or recording
   Play        resume normal replay
   Pause       pause replay or live video
   Stop        stop replay
@@ -74,7 +79,12 @@
       to "mark" a timer for moving.
   (2) See "Processing Recordings" below.
   (3) In the "Channels" menu the '0' key switches the sort mode through "by number",
-      "by name" and "by provider".
+      "by name" and "by provider". Other numeric input positions the cursor to
+      the channel with the number entered so far. If there is no channel with that
+      number, nothing happens. While entering a channel number, the '0' key will
+      be treated as part of that number, not as a sort mode toggle. If no numeric
+      key has been pressed for more than one second, the number is reset and '0'
+      functions as sort mode toggle again.
   (4) In the "Timers" menu, when on the "Day" item, the '0' key toggles between
       a single shot and a repeating timer. If "Day" indicates a repeating timer,
       the keys '1'...'7' can be used to toggle the individual days ('1' is Monday).
@@ -141,14 +151,17 @@
   "Schedule" menu of the current channel in the list.
 
   The "Red" button allows you to instantly program a timer to record the
-  selected programme. You will get into the "Edit Timer" menu in which
-  everything has already been filled in, and you can make any modifications
+  selected programme. After pressing this button, the current event will
+  be marked with 'T', and the function of the "Red" button will change from
+  "Record" to "Timer". Pressing "Red" on an event marked with 'T' will open
+  the "Edit timer" menu for this timer, where you can make any modifications
   you may want to apply. Note that the Start and Stop time are offset by the
   MarginStart and MarginStop parameters (see Setup) in order to make sure the
   entire programme is recorded in case it doesn't exactly adhere to its
   published start/stop times. Of course, no guarantee can be given that the
   default margin values will be sufficient, so in case this recording is
-  really important you may want to add an extra margin ;-)
+  really important you may want to add an extra margin ;-). VPS recordings
+  will use the exact Start (or VPS) and Stop times as given in the event.
 
   The "Blue" button can be pressed to switch to the channel with the selected
   programme.
@@ -373,7 +386,7 @@
 * Programming the Timer
 
   Use the "Timer" menu to maintain your list of timer controlled recordings.
-  The parameters in the "Edit Timer" menu have the following meanings:
+  The parameters in the "Edit timer" menu have the following meanings:
 
   Active:    Defines whether the timer will be processed (set it to 'no' to
              temporarily disable a timer).
@@ -493,6 +506,10 @@
                          always displayed when pressing the "Ok" button in
                          normal viewing mode.
 
+  Timeout requested channel info = yes
+                         Turns the automatic timeout of the channel display (when
+                         invoked by a press of the "Ok" key) on or off.
+
   Scroll pages = yes     no  = when pressing the "Down" ("Up") key while the cursor
                                is on the last (first) line of a list page, the
                                list is scrolled down (up) a single line and the cursor will
@@ -639,17 +656,11 @@
 
   CICAM:
 
-  CICAM DVBn m           Defines the "Conditional Access" capabilities of the DVB
-                         card 'n'. Each DVB card can provide up to two CICAM
-                         methods ('m' = [1, 2]).
-
-                         In the 'setup.conf' file the value consists of the card
-                         number, followed by a list of decryption method values
-                         (defined in 'ca.conf').
-                         For instance
-                           CaCaps = 3 101 102
-                         would define that card number 3 is able to decrypt
-                         "Premiere World" and the "ORF".
+  CICAM DVBn m           Shows the CAMs that each device contains, where 'n' is
+                         the number of the device, and 'm' is the number of the
+                         Common Interface slot of that device. The "Red" key
+                         can be pressed to enter the CAM menu, and the "Green" key
+                         triggers a reset of the selected CAM.
 
   Recording:
 
diff -ruN vdr-1.3.37/Makefile vdr-1.3.38/Makefile
--- vdr-1.3.37/Makefile	2005-09-02 16:23:38.000000000 +0200
+++ vdr-1.3.38/Makefile	2006-01-01 16:12:05.000000000 +0100
@@ -4,7 +4,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: Makefile 1.79 2005/09/02 14:23:38 kls Exp $
+# $Id: Makefile 1.81 2006/01/01 15:12:05 kls Exp $
 
 .DELETE_ON_ERROR:
 
@@ -27,7 +27,7 @@
 LSIDIR   = ./libsi
 MANDIR   = /usr/local/man
 BINDIR   = /usr/local/bin
-LIBS     = -ljpeg -lpthread -ldl
+LIBS     = -ljpeg -lpthread -ldl -lcap
 INCLUDES =
 
 PLUGINDIR= ./PLUGINS
@@ -183,7 +183,12 @@
 # Plugins:
 
 plugins: include-dir
-	@for i in `ls $(PLUGINDIR)/src | grep -v '[^a-z0-9]'`; do $(MAKE) -C "$(PLUGINDIR)/src/$$i" all; done
+	@failed="";\
+	for i in `ls $(PLUGINDIR)/src | grep -v '[^a-z0-9]'`; do\
+	    echo "Plugin $$i:";\
+	    $(MAKE) -C "$(PLUGINDIR)/src/$$i" all || failed="$$failed $$i";\
+	    done;\
+	if [ -n "$$failed" ] ; then echo; echo "*** failed plugins:$$failed"; echo; fi
 
 plugins-clean:
 	@for i in `ls $(PLUGINDIR)/src | grep -v '[^a-z0-9]'`; do $(MAKE) -C "$(PLUGINDIR)/src/$$i" clean; done
diff -ruN vdr-1.3.37/PLUGINS/src/sky/channels.conf.sky vdr-1.3.38/PLUGINS/src/sky/channels.conf.sky
--- vdr-1.3.37/PLUGINS/src/sky/channels.conf.sky	2005-08-07 16:51:02.000000000 +0200
+++ vdr-1.3.38/PLUGINS/src/sky/channels.conf.sky	2006-01-04 22:07:12.000000000 +0100
@@ -17,7 +17,7 @@
 #         page exists, 'x' is entered.
 #
 S28.2E-2-2027-4705:106:sky_one
-S28.2E-2-2027-5104:107:sky_one_mix
+S28.2E-2-2027-5104:107:sky_two
 S28.2E-2-2044-10070:118:itv2
 S28.2E-2-2023-4905:130:scifi
 S28.2E-2-2025-5904:127:paramount
diff -ruN vdr-1.3.37/PLUGINS/src/sky/getskyepg.pl vdr-1.3.38/PLUGINS/src/sky/getskyepg.pl
--- vdr-1.3.37/PLUGINS/src/sky/getskyepg.pl	2004-02-15 14:35:52.000000000 +0100
+++ vdr-1.3.38/PLUGINS/src/sky/getskyepg.pl	2006-01-08 11:21:32.000000000 +0100
@@ -8,7 +8,7 @@
 #
 # See the README file for copyright information and how to reach the author.
 #
-# $Id: getskyepg.pl 1.3 2004/02/15 13:35:52 kls Exp $
+# $Id: getskyepg.pl 1.4 2006/01/08 10:21:32 kls Exp $
 
 use Getopt::Std;
 use Time::Local;
@@ -151,7 +151,7 @@
              $gmt[1] = $m; # minutes
              $gmt[2] = $h; # hours
              $time = timegm(@gmt) + ($day - 1) * $SecsInDay + ($h < 12 ? $dt : 0);
-             # comensate for DST:
+             # compensate for DST:
              $time += $DST if (localtime($time))[8];
              # create EPG data:
              if ($Time) {
diff -ruN vdr-1.3.37/PLUGINS/src/status/HISTORY vdr-1.3.38/PLUGINS/src/status/HISTORY
--- vdr-1.3.37/PLUGINS/src/status/HISTORY	2002-12-13 15:56:50.000000000 +0100
+++ vdr-1.3.38/PLUGINS/src/status/HISTORY	2005-12-31 16:20:19.000000000 +0100
@@ -31,3 +31,7 @@
 2002-12-13: Version 0.1.0
 
 - Changed setting of CXX and CXXFLAGS variables in Makefile.
+
+2005-12-31: Version 0.2.0
+
+- API change in cStatus.
diff -ruN vdr-1.3.37/PLUGINS/src/status/status.c vdr-1.3.38/PLUGINS/src/status/status.c
--- vdr-1.3.37/PLUGINS/src/status/status.c	2002-12-13 16:01:53.000000000 +0100
+++ vdr-1.3.38/PLUGINS/src/status/status.c	2005-12-31 16:19:45.000000000 +0100
@@ -3,13 +3,13 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: status.c 1.7 2002/12/13 15:01:53 kls Exp $
+ * $Id: status.c 1.8 2005/12/31 15:19:45 kls Exp $
  */
 
 #include <vdr/plugin.h>
 #include <vdr/status.h>
 
-static const char *VERSION        = "0.1.0";
+static const char *VERSION        = "0.2.0";
 static const char *DESCRIPTION    = "Status monitor test";
 static const char *MAINMENUENTRY  = NULL;
 
@@ -18,8 +18,8 @@
 class cStatusTest : public cStatus {
 protected:
   virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber);
-  virtual void Recording(const cDevice *Device, const char *Name);
-  virtual void Replaying(const cControl *Control, const char *Name);
+  virtual void Recording(const cDevice *Device, const char *Name, const char *FileName, bool On);
+  virtual void Replaying(const cControl *Control, const char *Name, const char *FileName, bool On);
   virtual void SetVolume(int Volume, bool Absolute);
   virtual void OsdClear(void);
   virtual void OsdTitle(const char *Title);
@@ -36,14 +36,14 @@
   dsyslog("status: cStatusTest::ChannelSwitch  %d %d", Device->CardIndex(), ChannelNumber);
 }
 
-void cStatusTest::Recording(const cDevice *Device, const char *Name)
+void cStatusTest::Recording(const cDevice *Device, const char *Name, const char *FileName, bool On)
 {
-  dsyslog("status: cStatusTest::Recording  %d %s", Device->CardIndex(), Name);
+  dsyslog("status: cStatusTest::Recording  %d %s %s %d", Device->CardIndex(), Name, FileName, On);
 }
 
-void cStatusTest::Replaying(const cControl *Control, const char *Name)
+void cStatusTest::Replaying(const cControl *Control, const char *Name, const char *FileName, bool On)
 {
-  dsyslog("status: cStatusTest::Replaying  %s", Name);
+  dsyslog("status: cStatusTest::Replaying  %s %s %d", Name, FileName, On);
 }
 
 void cStatusTest::SetVolume(int Volume, bool Absolute)
diff -ruN vdr-1.3.37/PLUGINS.html vdr-1.3.38/PLUGINS.html
--- vdr-1.3.37/PLUGINS.html	2005-11-27 16:46:12.000000000 +0100
+++ vdr-1.3.38/PLUGINS.html	2006-01-08 12:48:08.000000000 +0100
@@ -9,23 +9,23 @@
 <center><b>Version 1.3</b></center>
 <p>
 <center>
-Copyright &copy; 2005 Klaus Schmidinger<br>
+Copyright &copy; 2006 Klaus Schmidinger<br>
 <a href="mailto:kls@cadsoft.de">kls@cadsoft.de</a><br>
 <a href="http://www.cadsoft.de/vdr">www.cadsoft.de/vdr</a>
 </center>
 <p>
-<!--X1.3.21--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
-Important modifications introduced in version 1.3.21 are marked like this.
-<!--X1.3.21--></td></tr></table>
-<!--X1.3.30--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
+<!--X1.3.30--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
 Important modifications introduced in version 1.3.30 are marked like this.
 <!--X1.3.30--></td></tr></table>
-<!--X1.3.31--><table width=100%><tr><td bgcolor=#AA0000>&nbsp;</td><td width=100%>
+<!--X1.3.31--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
 Important modifications introduced in version 1.3.31 are marked like this.
 <!--X1.3.31--></td></tr></table>
-<!--X1.3.37--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+<!--X1.3.37--><table width=100%><tr><td bgcolor=#AA0000>&nbsp;</td><td width=100%>
 Important modifications introduced in version 1.3.37 are marked like this.
 <!--X1.3.37--></td></tr></table>
+<!--X1.3.38--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+Important modifications introduced in version 1.3.38 are marked like this.
+<!--X1.3.38--></td></tr></table>
 <p>
 VDR provides an easy to use plugin interface that allows additional functionality
 to be added to the program by implementing a dynamically loadable library file.
@@ -66,10 +66,10 @@
 <li><a href="#The Setup menu">The Setup menu</a>
 <li><a href="#Configuration files">Configuration files</a>
 <li><a href="#Internationalization">Internationalization</a>
-<!--X1.3.30--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
+<!--X1.3.30--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
 <li><a href="#Custom services">Custom services</a>
 <!--X1.3.30--></td></tr></table>
-<!--X1.3.31--><table width=100%><tr><td bgcolor=#AA0000>&nbsp;</td><td width=100%>
+<!--X1.3.31--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
 <li><a href="#SVDRP commands">SVDRP commands</a>
 <!--X1.3.31--></td></tr></table>
 <li><a href="#Loading plugins into VDR">Loading plugins into VDR</a>
@@ -85,9 +85,7 @@
 <li><a href="#Skins">Skins</a>
 <li><a href="#Themes">Themes</a>
 <li><a href="#Devices">Devices</a>
-<!--X1.3.21--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
 <li><a href="#Audio">Audio</a>
-<!--X1.3.21--></td></tr></table>
 <li><a href="#Remote Control">Remote Control</a>
 </ul>
 </ul>
@@ -520,7 +518,7 @@
 
 in which it shall stop them.
 <p>
-The <tt>Stop()</tt> function will only be called if a previous call to the 
+The <tt>Stop()</tt> function will only be called if a previous call to the
 <a href="#Getting started"><tt>Start()</tt></a> function of that plugin has
 returned <i>true</i>. The <tt>Stop()</tt> functions are called in the reverse order
 as the <a href="#Getting started"><tt>Start()</tt></a> functions were called.
@@ -866,7 +864,7 @@
 and then in the global VDR texts. So a plugin can make use of texts defined by the
 core VDR code.
 
-<!--X1.3.30--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
+<!--X1.3.30--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
 <a name="Custom services"><hr><h2>Custom services</h2>
 
 <center><i><b>What can I do for you?</b></i></center><p>
@@ -937,7 +935,7 @@
 
 <!--X1.3.30--></td></tr></table>
 
-<!--X1.3.31--><table width=100%><tr><td bgcolor=#AA0000>&nbsp;</td><td width=100%>
+<!--X1.3.31--><table width=100%><tr><td bgcolor=#00AA00>&nbsp;</td><td width=100%>
 <a name="SVDRP commands"><hr><h2>SVDRP commands</h2>
 
 <center><i><b>Infinite Diversity in Infinite Combinations</b></i></center><p>
@@ -1264,6 +1262,9 @@
   cMyControl(void);
   virtual ~cMyControl();
   virtual void Hide(void);
+<!--X1.3.38--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+  virtual cOsdObject *GetInfo(void);
+<!--X1.3.38--></td></tr></table>
   virtual eOSState ProcessKey(eKeys Key);
   };
 </pre></td></tr></table><p>
@@ -1292,8 +1293,14 @@
 A derived <tt>cControl</tt> <b>must</b> implement the <tt>Hide()</tt> function, in which
 it has to hide itself from the OSD, in case it uses it. <tt>Hide()</tt> may be called at
 any time, and it may be called even if the <tt>cControl</tt> is not visible at the moment.
-The reason for this is that the <tt>Menu</tt> button shall always bring up the main VDR
-menu, so any active <tt>cControl</tt> needs to be hidden when that button is pressed.
+<p>
+<!--X1.3.38--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+The <tt>GetInfo()</tt> function is called when the user presses the <tt>Info</tt> button,
+and shall return a pointer to a <tt>cOsdObject</tt> that contains information
+about the currently played programme. The caller takes ownership of the returned
+pointer and will delete it when it is no longer used. If no information is available,
+<tt>NULL</tt> shall be returned.
+<!--X1.3.38--></td></tr></table>
 <p>
 Finally, to get things going, a plugin that implements a player (and the surrounding
 infrastructure like displaying a list of playable stuff etc) simply has to call the
@@ -1515,7 +1522,7 @@
 areas. Drawing areas are always rectangular and may not overlap (but do not need
 to be adjacent).
 
-<!--X1.3.37--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+<!--X1.3.37--><table width=100%><tr><td bgcolor=#AA0000>&nbsp;</td><td width=100%>
 <p>
 Directly accessing the OSD is only allowed from the foreground thread, which
 restricts this to a <tt>cOsdObject</tt> returned from the plugin's <tt>MainMenuAction()</tt>
@@ -1840,9 +1847,7 @@
   virtual void Action(void);
 public:
   cMyAudio(void);
-<!--X1.3.21--><table width=100%><tr><td bgcolor=#0000AA>&nbsp;</td><td width=100%>
   virtual void Play(const uchar *Data, int Length, uchar Id);
-<!--X1.3.21--></td></tr></table>
   virtual void Mute(bool On);
   virtual void Clear(void);
   };
diff -ruN vdr-1.3.37/ca.conf vdr-1.3.38/ca.conf
--- vdr-1.3.37/ca.conf	2004-02-08 16:10:13.000000000 +0100
+++ vdr-1.3.38/ca.conf	1970-01-01 01:00:00.000000000 +0100
@@ -1,17 +0,0 @@
-# Conditional Access configuration for VDR
-#
-# Format:
-#
-# number  description
-#
-# Please contact kls@cadsoft.de before assigning a new number
-# to a description, in order to keep them unique.
-
-0      Free To Air
-
-# Special values to "hard code" a channel to a specific DVB card:
-
-1      DVB 1
-2      DVB 2
-3      DVB 3
-4      DVB 4
diff -ruN vdr-1.3.37/channels.c vdr-1.3.38/channels.c
--- vdr-1.3.37/channels.c	2005-09-11 16:22:24.000000000 +0200
+++ vdr-1.3.38/channels.c	2005-12-30 16:41:24.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: channels.c 1.46 2005/09/11 14:22:24 kls Exp $
+ * $Id: channels.c 1.47 2005/12/30 15:41:24 kls Exp $
  */
 
 #include "channels.h"
@@ -586,7 +586,7 @@
            return p;
         }
      }
-  esyslog("ERROR: illegal value for parameter '%c'", *(s - 1));
+  esyslog("ERROR: invalid value for parameter '%c'", *(s - 1));
   return NULL;
 }
 
diff -ruN vdr-1.3.37/channels.conf vdr-1.3.38/channels.conf
--- vdr-1.3.37/channels.conf	2005-11-27 16:59:18.000000000 +0100
+++ vdr-1.3.38/channels.conf	2006-01-08 12:08:44.000000000 +0100
@@ -2,18 +2,18 @@
 SAT.1;ProSiebenSat.1:12480:vC34:S19.2E:27500:1791:1792=deu;1795=deu:34:0:46:133:33:0
 ProSieben;ProSiebenSat.1:12480:vC34:S19.2E:27500:255:256=deu;257=deu:32:0:898:133:33:0
 RTL2;RTL World:12187:hC34:S19.2E:27500:166:128=deu:68:0:12020:1:1089:0
-Das Erste;ARD:11836:hC34:S19.2E:27500:101:102=deu:104:0:28106:1:1101:0
+Das Erste;ARD:11836:hC34:S19.2E:27500:101:102=deu;106=deu:104:0:28106:1:1101:0
 Bayerisches FS;ARD:11836:hC34:S19.2E:27500:201:202=deu:204:0:28107:1:1101:0
 hr-fernsehen;ARD:11836:hC34:S19.2E:27500:301:302=deu:304:0:28108:1:1101:0
 NDR FS MV;ARD:12109:hC34:S19.2E:27500:2401:2402=deu:2404:0:28224:1:1073:0
 SR SÜDWEST Ferns.;ARD:12265:hC34:S19.2E:27500:1301:1302=deu:1304:0:28486:1:1093:0
 WDR Köln;ARD:11836:hC34:S19.2E:27500:601:602=deu:604:0:28111:1:1101:0
-BR-alpha;ARD:11836:hC34:S19.2E:27500:701:702=deu;703:704:0:28112:1:1101:0
+BR-alpha;ARD:11836:hC34:S19.2E:27500:701:702=deu:704:0:28112:1:1101:0
 SÜDWEST Ferns. BW;ARD:11836:hC34:S19.2E:27500:801:802=deu:804:0:28113:1:1101:0
 Phoenix;ARD:11836:hC34:S19.2E:27500:901:902=deu:904:0:28114:1:1101:0
 ZDF;ZDFvision:11953:hC34:S19.2E:27500:110:120=deu,121=2ch;125=dd:130:0:28006:1:1079:0
 3sat;ZDFvision:11953:hC34:S19.2E:27500:210:220=deu,221=2ch;225=dd:230:0:28007:1:1079:0
-KiKa;ZDFvision:11953:hC34:S19.2E:27500:310:320:0:0:28008:1:1079:0
+KiKa;ZDFvision:11953:hC34:S19.2E:27500:310:320=deu:330:0:28008:1:1079:0
 arte;ARD:11836:hC34:S19.2E:27500:401:402=deu,403=fra:404:0:28109:1:1101:0
 ORF1;ORF:12692:hC56:S19.2E:22000:160:161=deu;163=deu:165:1762,D05,1702,1801:13001:1:1117:0
 ORF2;ORF:12692:hC56:S19.2E:22000:500:501=deu;503=deu:505:1762,D05,1702,1801:13002:1:1117:0
@@ -28,14 +28,14 @@
 HSE24,HSE24;BetaDigital:12480:vC34:S19.2E:27500:1279:1280=deu:37:0:40:133:33:0
 Bloomberg TV Germany;Bloomberg:12551:vC56:S19.2E:22000:162:99=deu:0:0:12160:1:1108:0
 EURONEWS;CSAT:11817:vC34:S19.2E:27500:163:92=fra,93=eng,94=ita,95=esl,91=rus,98=por,99=deu:0:0:8004:1:1070:0
-rbb Brandenburg;ARD:12109:hC34:S19.2E:27500:501:502=deu:504:0:28205:1:1073:0
+rbb Brandenburg;ARD:12109:hC34:S19.2E:27500:601:602=deu:604:0:28205:1:1073:0
 Sky News:11597:vC56:S19.2E:22000:305+131:306=eng:0:0:28707:1:1026:0
 Veronica/JETIX;CANALDIGITAAL:12574:hC56:S19.2E:22000:518+8190:92=dut:38:622,100:5020:53:1109:0
 BVN;CANALDIGITAAL:12574:hC56:S19.2E:22000:515+8190:96=dut:36:0:5025:53:1109:0
 n-tv;RTL World:12187:hC34:S19.2E:27500:169:73=deu:80:0:12090:1:1089:0
 Al Jazeera;CANALSATELLITE:11567:vC56:S19.2E:22000:55:56=ara:0:0:9021:1:1024:0
 TW1;ORF:12662:hC56:S19.2E:22000:1010:1011=deu:1013:0:13101:1:1115:0
-Eurosport;ZDFvision:11953:hC34:S19.2E:27500:410:420=deu:430:0:28009:1:1079:0
+Eurosport;SES Astra:12226:hC34:S19.2E:27500:101+8190:103=deu:102:0:31200:1:1091:0
 EinsExtra;ARD:12109:hC34:S19.2E:27500:101:102=deu:0:0:28201:1:1073:0
 EinsFestival;ARD:12109:hC34:S19.2E:27500:201:202=deu:0:0:28202:1:1073:0
 EinsPlus;ARD:12109:hC34:S19.2E:27500:301:302=deu:0:0:28203:1:1073:0
@@ -44,11 +44,11 @@
 MDR FERNSEHEN;ARD:12109:hC34:S19.2E:27500:401:402=deu:404:0:28204:1:1073:0
 rbb Berlin;ARD:12109:hC34:S19.2E:27500:601:602=deu:604:0:28206:1:1073:0
 :Premiere World
-PREMIERE START,START;PREMIERE:11797:hC34:S19.2E:27500:255:256=deu:32:1:8:133:2:0
-PREMIERE 1,PREM 1;PREMIERE:11797:hC34:S19.2E:27500:511:512=deu,513=deu;515=deu:32:1702,1722,1801:10:133:2:0
-PREMIERE 2,PREM 2;PREMIERE:11797:hC34:S19.2E:27500:1791:1792=deu,1793=deu;1795=deu:32:1702,1801,1722:11:133:2:0
+PREMIERE START,START;PREMIERE:11797:hC34:S19.2E:27500:255:256=deu:32:1801,1722,1702:8:133:2:0
+PREMIERE 1,PREM 1;PREMIERE:11797:hC34:S19.2E:27500:511:512=deu,513=deu;515=deu:32:1801,1722,1702:10:133:2:0
+PREMIERE 2,PREM 2;PREMIERE:11797:hC34:S19.2E:27500:1791:1792=deu,1793=deu;1795=deu:32:1801,1722,1702:11:133:2:0
 PREMIERE 3,PREM 3;PREMIERE:11797:hC34:S19.2E:27500:2303:2304=deu,2305=deu:32:1722,1702,1801:43:133:2:0
-PREMIERE 4,PREM 4;PREMIERE:11797:hC34:S19.2E:27500:767:768=deu:32:1801,1722,1702:9:133:2:0
+PREMIERE 4,PREM 4;PREMIERE:11797:hC34:S19.2E:27500:767:768=deu,769=deu:32:1801,1722,1702:9:133:2:0
 PREMIERE 5,PREM 5;PREMIERE:11797:hC34:S19.2E:27500:1279:1280=deu,1281=deu:32:1722,1702,1801:29:133:2:0
 PREMIERE 6,PREM 6;PREMIERE:11797:hC34:S19.2E:27500:1535:1536=deu:32:1702,1722,1801:41:133:2:0
 PREMIERE 7,PREM 7;PREMIERE:11797:hC34:S19.2E:27500:1023:1024=deu:32:1801,1702,1722:20:133:2:0
@@ -57,10 +57,10 @@
 PREMIERE DIREKT,DIREKT;PREMIERE:12031:hC34:S19.2E:27500:2815:2816=deu,2817=deu;2819=deu:0:0:18:133:4:0
 :PW Erotic
 BEATE-UHSE.TV,B-UHSE;PREMIERE:11758:hC34:S19.2E:27500:1791:1792=deu:32:1722,1702,1801:21:133:17:0
-EROTIK - AB 18!,AB 18!;PREMIERE:12031:hC34:S19.2E:27500:1279:1280=deu:0:1810,1801,1702,1722:513:133:4:0
+EROTIK - AB 18!,AB 18!;PREMIERE:12031:hC34:S19.2E:27500:1279:1280=deu:0:1722,1801,1702,1810:513:133:4:0
 :Sportsworld
-PREMIERE SPORT PORTAL,SPORT PORTAL;PREMIERE:11719:hC34:S19.2E:27500:255:256=deu,257=deu:32:1702,1722,1801:17:133:3:0
-PREMIERE WIN,WIN;PREMIERE:12031:hC34:S19.2E:27500:3839:3840=deu:32:0:27:133:4:0
+PREMIERE SPORT PORTAL,SPORT PORTAL;PREMIERE:11719:hC34:S19.2E:27500:255:256=deu,257=deu:32:1722,1801,1702:17:133:3:0
+PREMIERE WIN,WIN;PREMIERE:12031:hC34:S19.2E:27500:3839:3840=deu:33:0:27:133:4:0
 :Beta Digital
 N24;ProSiebenSat.1:12480:vC34:S19.2E:27500:2047:2048=deu:36:0:47:133:33:0
 LibertyTV FR;LibertyTV.com:12610:vC56:S19.2E:22000:941:943=deu:0:0:12199:1:1112:0
@@ -92,9 +92,9 @@
 Sky Two;BSkyB:12226:hC23:S28.2E:27500:514+8190:642=eng,662=NAR:578:960,961:5104:2:2027:0
 ITV2;BSkyB:10758:vC56:S28.2E:22000:2314:2315=eng,2363=NAR:2317:0:10070:2:2044:0
 Sci-Fi;BSkyB:12148:hC23:S28.2E:27500:512+8190:640=eng:576:960,961:4905:2:2023:0
-Paramount;BSkyB:12187:hC23:S28.2E:27500:518+8190:666=eng,686=NAR:582:960,961:5904:2:2025:0
+ParaComedy 1;BSkyB:12187:hC23:S28.2E:27500:518+8190:666=eng,686=NAR:582:960,961:5904:2:2025:0
 Paramount;BSkyB:11526:vC23:S28.2E:27500:2317+2306:2318=eng:2319:960,961:50305:2:2404:0
-Paramount 2;BSkyB:11914:hC23:S28.2E:27500:514+8190:642=eng,662=NAR:578:960,961:4504:2:2011:0
+ParaComedy 2;BSkyB:11914:hC23:S28.2E:27500:514+8190:642=eng,662=NAR:578:960,961:4504:2:2011:0
 Discovery;BSkyB:11875:hC23:S28.2E:27500:2308:2310=eng,2311=NAR:2309:960,961:6201:2:2009:0
 Sky Movies 1;BSkyB:11836:hC23:S28.2E:27500:518+8190:646=eng,653=NAR;686=eng:582:960,961:4303:2:2007:0
 Sky Movies 2;BSkyB:11836:hC23:S28.2E:27500:519+8190:647=eng,667=NAR;687=eng:583:960,961:4302:2:2007:0
@@ -115,8 +115,11 @@
 CNN;BSkyB:12051:vC23:S28.2E:27500:2313:2315=eng:2314:0:7140:2:2018:0
 BBC PARL'MNT;BSkyB:10847:vC56:S28.2E:22000:2327:2328=eng:2331:0:6902:2:2050:0
 IGLESIA MME;T-Systems/MTI:11200:vC56:S13.0E:27500:4097:4098:0:0:4733:318:13400:0
-Euro1080;EURO1080:12168:vC56:S19.2E:27500:308:256:0:FF:21100:1:1088:0
-Astra HD:12441:vC34:S19.2E:27500:133+80:134=eng:0:FF:29700:0:0:0
+Euro1080 HD-5;Euro1080:10758:vC78:S23.5E:22000:34:160=eng:0:0:1085:9999:3104:0
+Euro1080;EURO1080:12168:vC56:S19.2E:27500:308:256:0:0:21100:1:1088:0
+SMD HD;SES ASTRA:12699:vC56:S19.2E:22000:133+80:234=eng:0:0:29700:0:0:0
+Astra HD:12441:vC34:S19.2E:27500:133+80:134=eng:0:0:29700:0:0:0
 eng-WRN-multi;WRN:12597:vC34:S13.0E:27500:0:2132:0:0:8230:318:9400:0
-TVS Teleport Bonn;DMV:11535:vC34:S1.0W:5632:308+8190:256=eng,257=eng:0:2:1:65535:1:0
+Challenger Tv;Telespazio:11304:hC34:S13.0E:27500:490:491:0:0:8409:318:500:0
+TVS Teleport Bonn;DMV:11535:vC34:S1.0W:5632:308+8190:256=eng,257=eng:0:3:1:65535:1:0
 :@1000 New channels
diff -ruN vdr-1.3.37/channels.h vdr-1.3.38/channels.h
--- vdr-1.3.37/channels.h	2005-09-17 11:59:14.000000000 +0200
+++ vdr-1.3.38/channels.h	2006-01-07 14:00:43.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: channels.h 1.36 2005/09/17 09:59:14 kls Exp $
+ * $Id: channels.h 1.37 2006/01/07 13:00:43 kls Exp $
  */
 
 #ifndef __CHANNELS_H
@@ -39,6 +39,14 @@
 #define MAXLANGCODE1 4 // a 3 letter language code, zero terminated
 #define MAXLANGCODE2 8 // up to two 3 letter language codes, separated by '+' and zero terminated
 
+#define CA_FTA           0x0000
+#define CA_DVB_MIN       0x0001
+#define CA_DVB_MAX       0x000F
+#define CA_USER_MIN      0x0010
+#define CA_USER_MAX      0x00FF
+#define CA_ENCRYPTED_MIN 0x0100
+#define CA_ENCRYPTED_MAX 0xFFFF
+
 struct tChannelParameterMap {
   int userValue;
   int driverValue;
diff -ruN vdr-1.3.37/ci.c vdr-1.3.38/ci.c
--- vdr-1.3.37/ci.c	2005-11-26 14:36:51.000000000 +0100
+++ vdr-1.3.38/ci.c	2006-01-07 16:07:16.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: ci.c 1.40 2005/11/26 13:36:51 kls Exp $
+ * $Id: ci.c 1.42 2006/01/07 15:07:16 kls Exp $
  */
 
 #include "ci.h"
@@ -185,7 +185,7 @@
             size = 6;
             }
          else
-            esyslog("ERROR: illegal data length for TPDU tag 0x%02X: %d", Tag, Length);
+            esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d", Tag, Length);
          break;
     case T_DATA_LAST:
     case T_DATA_MORE:
@@ -198,7 +198,7 @@
             size = Length + (p - data);
             }
          else
-            esyslog("ERROR: illegal data length for TPDU tag 0x%02X: %d", Tag, Length);
+            esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d", Tag, Length);
          break;
     default:
          esyslog("ERROR: unknown TPDU tag: 0x%02X", Tag);
@@ -1639,6 +1639,15 @@
   return result;
 }
 
+int cCiHandler::NumCams(void)
+{
+  int result = 0;
+  for (int i = 0; i < MAX_CI_SLOT; i++)
+      if (moduleReady[i])
+         result++;
+  return result;
+}
+
 bool cCiHandler::Ready(void)
 {
   cMutexLock MutexLock(&mutex);
diff -ruN vdr-1.3.37/ci.h vdr-1.3.38/ci.h
--- vdr-1.3.37/ci.h	2005-11-26 14:37:42.000000000 +0100
+++ vdr-1.3.38/ci.h	2006-01-07 16:03:05.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: ci.h 1.19 2005/11/26 13:37:42 kls Exp $
+ * $Id: ci.h 1.21 2006/01/07 15:03:05 kls Exp $
  */
 
 #ifndef __CI_H
@@ -112,6 +112,7 @@
   cList<cCiCaProgramData> caProgramList;
   int ResourceIdToInt(const uint8_t *Data);
   bool Send(uint8_t Tag, int SessionId, int ResourceId = 0, int Status = -1);
+  const unsigned short *GetCaSystemIds(int Slot);
   cCiSession *GetSessionBySessionId(int SessionId);
   cCiSession *GetSessionByResourceId(int ResourceId, int Slot);
   cCiSession *CreateSession(int ResourceId);
@@ -123,18 +124,35 @@
 public:
   ~cCiHandler();
   static cCiHandler *CreateCiHandler(const char *FileName);
+       ///< Creates a new cCiHandler for the given CA device.
   int NumSlots(void) { return numSlots; }
+       ///< Returns the number of CAM slots provided by this CA device.
+  int NumCams(void);
+       ///< Returns the number of actual CAMs inserted into this CA device.
   bool Ready(void);
+       ///< Returns true if all CAMs in this CA device are ready.
   bool Process(int Slot = -1);
        ///< Processes the given Slot. If Slot is -1, all slots are processed.
        ///< Returns false in case of an error.
   bool HasUserIO(void) { return hasUserIO; }
+       ///< Returns true if there is a pending user interaction, which shall
+       ///< be retrieved via GetMenu() or GetEnquiry().
   bool EnterMenu(int Slot);
+       ///< Requests the CAM in the given Slot to start its menu.
   cCiMenu *GetMenu(void);
+       ///< Gets a pending menu, or NULL if there is no menu.
   cCiEnquiry *GetEnquiry(void);
+       ///< Gets a pending enquiry, or NULL if there is no enquiry.
   const char *GetCamName(int Slot);
-  const unsigned short *GetCaSystemIds(int Slot);
+       ///< Returns the name of the CAM in the given Slot, or NULL if there
+       ///< is no CAM in that slot.
   bool ProvidesCa(const unsigned short *CaSystemIds); //XXX Slot???
+       ///< Returns true if any of the CAMs can provide one of the given
+       ///< CaSystemIds. This doesn't necessarily mean that it will be
+       ///< possible to actually decrypt such a programme, since CAMs
+       ///< usually advertise several CA system ids, while the actual
+       ///< decryption is controlled by the smart card inserted into
+       ///< the CAM.
   void SetSource(int Source, int Transponder);
        ///< Sets the Source and Transponder of the device this cCiHandler is
        ///< currently tuned to. If Source or Transponder are different than
@@ -145,11 +163,14 @@
        ///< to SetPid() will (de)activate one of these entries.
   void SetPid(int Pid, bool Active);
        ///< Sets the given Pid (which has previously been added through a
-       ///< call to AddPid()) to Active. If Active is true, a later call to
-       ///< StartDecrypting() will send the full list of currently active CA_PMT
-       ///< entries to the CAM, including this one.
+       ///< call to AddPid()) to Active. A later call to StartDecrypting() will
+       ///< send the full list of currently active CA_PMT entries to the CAM.
   bool CanDecrypt(int ProgramNumber);
        ///< XXX
+       ///< Returns true if there is a CAM in this CA device that is able
+       ///< to decrypt the programme with the given ProgramNumber. The PIDs
+       ///< for this ProgramNumber must have been set through previous calls
+       ///< to SetPid().
   void StartDecrypting(void);
        ///< Triggers sending all currently active CA_PMT entries to the CAM,
        ///< so that it will start decrypting.
diff -ruN vdr-1.3.37/config.c vdr-1.3.38/config.c
--- vdr-1.3.37/config.c	2005-09-09 17:08:59.000000000 +0200
+++ vdr-1.3.38/config.c	2006-01-07 13:28:49.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: config.c 1.138 2005/09/09 15:08:59 kls Exp $
+ * $Id: config.c 1.140 2006/01/07 12:28:49 kls Exp $
  */
 
 #include "config.h"
@@ -120,24 +120,6 @@
   return (Address & mask) == addr.s_addr;
 }
 
-// -- cCaDefinition ----------------------------------------------------------
-
-cCaDefinition::cCaDefinition(void)
-{
-  number = 0;
-  description = NULL;
-}
-
-cCaDefinition::~cCaDefinition()
-{
-  free(description);
-}
-
-bool cCaDefinition::Parse(const char *s)
-{
-  return 2 == sscanf(s, "%d %a[^\n]", &number, &description) && description && *description;
-}
-
 // -- cCommands --------------------------------------------------------------
 
 cCommands Commands;
@@ -158,21 +140,6 @@
   return false;
 }
 
-// -- cCaDefinitions ---------------------------------------------------------
-
-cCaDefinitions CaDefinitions;
-
-const cCaDefinition *cCaDefinitions::Get(int Number)
-{
-  cCaDefinition *p = First();
-  while (p) {
-        if (p->Number() == Number)
-           return p;
-        p = (cCaDefinition *)p->Next();
-        }
-  return NULL;
-}
-
 // -- cSetupLine -------------------------------------------------------------
 
 cSetupLine::cSetupLine(void)
@@ -250,6 +217,7 @@
   strcpy(OSDTheme, "default");
   PrimaryDVB = 1;
   ShowInfoOnChSwitch = 1;
+  TimeoutRequChInfo = 1;
   MenuScrollPage = 1;
   MenuScrollWrap = 0;
   MarkInstantRecord = 1;
@@ -408,6 +376,7 @@
   else if (!strcasecmp(Name, "OSDTheme"))            strn0cpy(OSDTheme, Value, MaxThemeName);
   else if (!strcasecmp(Name, "PrimaryDVB"))          PrimaryDVB         = atoi(Value);
   else if (!strcasecmp(Name, "ShowInfoOnChSwitch"))  ShowInfoOnChSwitch = atoi(Value);
+  else if (!strcasecmp(Name, "TimeoutRequChInfo"))   TimeoutRequChInfo  = atoi(Value);
   else if (!strcasecmp(Name, "MenuScrollPage"))      MenuScrollPage     = atoi(Value);
   else if (!strcasecmp(Name, "MenuScrollWrap"))      MenuScrollWrap     = atoi(Value);
   else if (!strcasecmp(Name, "MarkInstantRecord"))   MarkInstantRecord  = atoi(Value);
@@ -473,6 +442,7 @@
   Store("OSDTheme",           OSDTheme);
   Store("PrimaryDVB",         PrimaryDVB);
   Store("ShowInfoOnChSwitch", ShowInfoOnChSwitch);
+  Store("TimeoutRequChInfo",  TimeoutRequChInfo);
   Store("MenuScrollPage",     MenuScrollPage);
   Store("MenuScrollWrap",     MenuScrollWrap);
   Store("MarkInstantRecord",  MarkInstantRecord);
diff -ruN vdr-1.3.37/config.h vdr-1.3.38/config.h
--- vdr-1.3.37/config.h	2005-11-11 14:22:02.000000000 +0100
+++ vdr-1.3.38/config.h	2006-01-07 13:57:42.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: config.h 1.235 2005/11/11 13:22:02 kls Exp $
+ * $Id: config.h 1.238 2006/01/07 12:57:42 kls Exp $
  */
 
 #ifndef __CONFIG_H
@@ -19,8 +19,8 @@
 #include "i18n.h"
 #include "tools.h"
 
-#define VDRVERSION  "1.3.37"
-#define VDRVERSNUM   10337  // Version * 10000 + Major * 100 + Minor
+#define VDRVERSION  "1.3.38"
+#define VDRVERSNUM   10338  // Version * 10000 + Major * 100 + Minor
 
 #define MAXPRIORITY 99
 #define MAXLIFETIME 99
@@ -61,20 +61,6 @@
   bool Accepts(in_addr_t Address);
   };
 
-#define CACONFBASE 100
-
-class cCaDefinition : public cListObject {
-private:
-  int number;
-  char *description;
-public:
-  cCaDefinition(void);
-  ~cCaDefinition();
-  bool Parse(const char *s);
-  int Number(void) const { return number; }
-  const char *Description(void) const { return description; }
-  };
-
 template<class T> class cConfig : public cList<T> {
 private:
   char *fileName;
@@ -166,15 +152,9 @@
   bool Acceptable(in_addr_t Address);
   };
 
-class cCaDefinitions : public cConfig<cCaDefinition> {
-public:
-  const cCaDefinition *Get(int Number);
-  };
-
 extern cCommands Commands;
 extern cCommands RecordingCommands;
 extern cSVDRPhosts SVDRPhosts;
-extern cCaDefinitions CaDefinitions;
 
 class cSetupLine : public cListObject {
 private:
@@ -210,6 +190,7 @@
   char OSDTheme[MaxThemeName];
   int PrimaryDVB;
   int ShowInfoOnChSwitch;
+  int TimeoutRequChInfo;
   int MenuScrollPage;
   int MenuScrollWrap;
   int MarkInstantRecord;
diff -ruN vdr-1.3.37/device.c vdr-1.3.38/device.c
--- vdr-1.3.37/device.c	2005-11-26 13:56:09.000000000 +0100
+++ vdr-1.3.38/device.c	2006-01-08 12:39:37.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: device.c 1.112 2005/11/26 12:56:09 kls Exp $
+ * $Id: device.c 1.121 2006/01/08 11:39:37 kls Exp $
  */
 
 #include "device.h"
@@ -222,7 +222,7 @@
         esyslog("ERROR: nextCardIndex too big (%d)", nextCardIndex);
      }
   else if (n < 0)
-     esyslog("ERROR: illegal value in IncCardIndex(%d)", n);
+     esyslog("ERROR: invalid value in IncCardIndex(%d)", n);
   return nextCardIndex;
 }
 
@@ -302,7 +302,7 @@
             pri = 6; // receiving with same priority but fewer Ca's
          else
             pri = 7; // all others
-         if (pri < select) {
+         if (pri <= select) {
             select = pri;
             d = device[i];
             if (NeedsDetachReceivers)
@@ -322,9 +322,36 @@
       }
 }
 
-bool cDevice::GrabImage(const char *FileName, bool Jpeg, int Quality, int SizeX, int SizeY)
+uchar *cDevice::GrabImage(int &Size, bool Jpeg, int Quality, int SizeX, int SizeY)
 {
-  return false;
+  return NULL;
+}
+
+bool cDevice::GrabImageFile(const char *FileName, bool Jpeg, int Quality, int SizeX, int SizeY)
+{
+  int result = 0;
+  int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
+  if (fd >= 0) {
+     int ImageSize;
+     uchar *Image = GrabImage(ImageSize, Jpeg, Quality, SizeX, SizeY);
+     if (Image) {
+        if (safe_write(fd, Image, ImageSize) == ImageSize)
+           isyslog("grabbed image to %s", FileName);
+        else {
+           LOG_ERROR_STR(FileName);
+           result |= 1;
+           }
+        free(Image);
+        }
+     else
+        result |= 1;
+     close(fd);
+     }
+  else {
+     LOG_ERROR_STR(FileName);
+     result |= 1;
+     }
+  return result == 0;
 }
 
 void cDevice::SetVideoDisplayFormat(eVideoDisplayFormat VideoDisplayFormat)
@@ -577,10 +604,14 @@
   if (LiveView)
      StopReplay();
 
+  // If this card is switched to an other transponder, any receivers still
+  // attached to it ineed to be automatically detached:
+  bool NeedsDetachReceivers = false;
+
   // If this card can't receive this channel, we must not actually switch
   // the channel here, because that would irritate the driver when we
   // start replaying in Transfer Mode immediately after switching the channel:
-  bool NeedsTransferMode = (LiveView && IsPrimaryDevice() && !ProvidesChannel(Channel, Setup.PrimaryLimit));
+  bool NeedsTransferMode = (LiveView && IsPrimaryDevice() && !ProvidesChannel(Channel, Setup.PrimaryLimit, &NeedsDetachReceivers));
 
   eSetChannelResult Result = scrOk;
 
@@ -588,11 +619,14 @@
   // use the card that actually can receive it and transfer data from there:
 
   if (NeedsTransferMode) {
-     cDevice *CaDevice = GetDevice(Channel, 0);
+     cDevice *CaDevice = GetDevice(Channel, 0, &NeedsDetachReceivers);
      if (CaDevice && CanReplay()) {
         cStatus::MsgChannelSwitch(this, 0); // only report status if we are actually going to switch the channel
-        if (CaDevice->SetChannel(Channel, false) == scrOk) // calling SetChannel() directly, not SwitchChannel()!
+        if (CaDevice->SetChannel(Channel, false) == scrOk) { // calling SetChannel() directly, not SwitchChannel()!
+           if (NeedsDetachReceivers)
+              CaDevice->DetachAllReceivers();
            cControl::Launch(new cTransferControl(CaDevice, Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids()));
+           }
         else
            Result = scrNoTransfer;
         }
@@ -613,7 +647,7 @@
         ciHandler->SetSource(Channel->Source(), Channel->Transponder());
 // Men at work - please stand clear! ;-)
 #ifdef XXX_DO_MULTIPLE_CA_CHANNELS
-        if (Channel->Ca() > CACONFBASE) {
+        if (Channel->Ca() >= CA_ENCRYPTED_MIN) {
 #endif
            ciHandler->AddPid(Channel->Sid(), Channel->Vpid(), 2);
            for (const int *Apid = Channel->Apids(); *Apid; Apid++)
@@ -626,13 +660,15 @@
            }
 #endif
         }
+     if (NeedsDetachReceivers)
+        DetachAllReceivers();
      if (SetChannelDevice(Channel, LiveView)) {
         // Start section handling:
         if (sectionHandler) {
            sectionHandler->SetChannel(Channel);
            sectionHandler->SetStatus(true);
            }
-        // Start decrypting any PIDs the might have been set in SetChannelDevice():
+        // Start decrypting any PIDs that might have been set in SetChannelDevice():
         if (ciHandler)
            ciHandler->StartDecrypting();
         }
@@ -794,6 +830,7 @@
 bool cDevice::SetCurrentAudioTrack(eTrackType Type)
 {
   if (ttNone < Type && Type < ttDolbyLast) {
+     cMutexLock MutexLock(&mutexCurrentAudioTrack);
      if (IS_DOLBY_TRACK(Type))
         SetDigitalAudioDevice(true);
      currentAudioTrack = Type;
@@ -947,6 +984,7 @@
 
 int cDevice::PlayPesPacket(const uchar *Data, int Length, bool VideoOnly)
 {
+  cMutexLock MutexLock(&mutexCurrentAudioTrack);
   bool FirstLoop = true;
   uchar c = Data[3];
   const uchar *Start = Data;
@@ -972,7 +1010,7 @@
                uchar SubStreamId = Data[PayloadOffset];
                uchar SubStreamType = SubStreamId & 0xF0;
                uchar SubStreamIndex = SubStreamId & 0x1F;
-        
+
                // Compatibility mode for old VDR recordings, where 0xBD was only AC3:
 pre_1_3_19_PrivateStreamDeteced:
                if (pre_1_3_19_PrivateStream) {
@@ -1111,7 +1149,7 @@
   int Ca = Channel->Ca();
   if (Ca == CardIndex() + 1)
      return 1; // exactly _this_ card was requested
-  if (Ca && Ca <= MAXDEVICES)
+  if (Ca && Ca <= CA_DVB_MAX)
      return 0; // a specific card was requested, but not _this_ one
   return !Ca; // by default every card can provide FTA
 }
@@ -1242,6 +1280,15 @@
      }
 }
 
+void cDevice::DetachAllReceivers(void)
+{
+  cMutexLock MutexLock(&mutexReceiver);
+  for (int i = 0; i < MAXRECEIVERS; i++) {
+      if (receiver[i])
+         Detach(receiver[i]);
+      }
+}
+
 // --- cTSBuffer -------------------------------------------------------------
 
 cTSBuffer::cTSBuffer(int File, int Size, int CardIndex)
diff -ruN vdr-1.3.37/device.h vdr-1.3.38/device.h
--- vdr-1.3.37/device.h	2005-11-05 16:25:41.000000000 +0100
+++ vdr-1.3.38/device.h	2006-01-08 11:10:26.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: device.h 1.66 2005/11/05 15:25:41 kls Exp $
+ * $Id: device.h 1.70 2006/01/08 10:10:26 kls Exp $
  */
 
 #ifndef __DEVICE_H
@@ -170,14 +170,16 @@
          ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
   int DeviceNumber(void) const;
          ///< Returns the number of this device (0 ... MAXDEVICES - 1).
-  virtual int ProvidesCa(const cChannel *Channel) const;//XXX PLUGINS.html!!!
-         //XXX describe changed functionality!!!
-         ///< Checks whether this device provides the given value in its
-         ///< caCaps. Returns 0 if the value is not provided, 1 if only this
-         ///< value is provided, and > 1 if this and other values are provided.
-         ///< If the given value is equal to the number of this device,
-         ///< 1 is returned. If it is 0 (FTA), 1 plus the number of other values
-         ///< in caCaps is returned.
+  virtual int ProvidesCa(const cChannel *Channel) const;
+         ///< Checks whether this device provides the conditional access
+         ///< facilities to decrypt the given Channel.
+         ///< Returns 0 if the Channel can't be decrypted, 1 if this is a
+         ///< Free To Air channel or only exactly this device can decrypt it,
+         ///< and > 1 if this device can decrypt the Channel.
+         ///< If the result is greater than 1 and the device has more than one
+         ///< CAM, the value will be increased by the number of CAMs, which
+         ///< allows to select the device with the smallest number of CAMs
+         ///< in order to preserve resources for other recordings.
   virtual bool HasDecoder(void) const;
          ///< Tells whether this device has an MPEG decoder.
 
@@ -305,11 +307,9 @@
 // Image Grab facilities
 
 public:
-  virtual bool GrabImage(const char *FileName, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1);
-         ///< Capture a single frame as an image.
-         ///< Grabs the currently visible screen image into the given file, with the
-         ///< given parameters.
-         ///< \param FileName The name of the file to write. Should include the proper extension.
+  virtual uchar *GrabImage(int &Size, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1);
+         ///< Grabs the currently visible screen image.
+         ///< \param Size The size of the returned data block.
          ///< \param Jpeg If true will write a JPEG file. Otherwise a PNM file will be written.
          ///< \param Quality The compression factor for JPEG. 1 will create a very blocky
          ///<        and small image, 70..80 will yield reasonable quality images while keeping the
@@ -317,7 +317,13 @@
          ///<        but very high quality image.
          ///< \param SizeX The number of horizontal pixels in the frame (default is the current screen width).
          ///< \param SizeY The number of vertical pixels in the frame (default is the current screen height).
-         ///< \return True if all went well. */
+         ///< \return A pointer to the grabbed image data, or NULL in case of an error.
+         ///< The caller takes ownership of the returned memory and must free() it once it isn't needed any more.
+  bool GrabImageFile(const char *FileName, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1);
+         ///< Calls GrabImage() and stores the resulting image in a file with the given name.
+         ///< \return True if all went well.
+         ///< The caller is responsible for making sure that the given file name
+         ///< doesn't lead to overwriting any important other file.
 
 // Video format facilities
 
@@ -338,6 +344,7 @@
 private:
   tTrackId availableTracks[ttMaxTrackTypes];
   eTrackType currentAudioTrack;
+  cMutex mutexCurrentAudioTrack;
   int currentAudioTrackMissingCount;
   bool pre_1_3_19_PrivateStream;
 protected:
@@ -516,6 +523,8 @@
        ///< Detaches the given receiver from this device.
   void DetachAll(int Pid);
        ///< Detaches all receivers from this device for this pid.
+  void DetachAllReceivers(void);
+       ///< Detaches all receivers from this device.
   };
 
 /// Derived cDevice classes that can receive channels will have to provide
diff -ruN vdr-1.3.37/diseqc.c vdr-1.3.38/diseqc.c
--- vdr-1.3.37/diseqc.c	2005-01-09 14:05:11.000000000 +0100
+++ vdr-1.3.38/diseqc.c	2005-12-30 16:41:48.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: diseqc.c 1.4 2005/01/09 13:05:11 kls Exp $
+ * $Id: diseqc.c 1.5 2005/12/30 15:41:48 kls Exp $
  */
 
 #include "diseqc.h"
@@ -65,7 +65,7 @@
         cCondWait::SleepMs(n);
      return p;
      }
-  esyslog("ERROR: illegal value for wait time in '%s'", s - 1);
+  esyslog("ERROR: invalid value for wait time in '%s'", s - 1);
   return NULL;
 }
 
@@ -85,7 +85,7 @@
                  t = skipspace(p);
                  }
               else {
-                 esyslog("ERROR: illegal code at '%s'", t);
+                 esyslog("ERROR: invalid code at '%s'", t);
                  return NULL;
                  }
               }
diff -ruN vdr-1.3.37/dvbdevice.c vdr-1.3.38/dvbdevice.c
--- vdr-1.3.37/dvbdevice.c	2005-11-26 14:23:11.000000000 +0100
+++ vdr-1.3.38/dvbdevice.c	2006-01-07 16:15:01.000000000 +0100
@@ -4,18 +4,11 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbdevice.c 1.138 2005/11/26 13:23:11 kls Exp $
+ * $Id: dvbdevice.c 1.149 2006/01/07 15:15:01 kls Exp $
  */
 
 #include "dvbdevice.h"
 #include <errno.h>
-extern "C" {
-#ifdef boolean
-#define HAVE_BOOLEAN
-#endif
-#include <jpeglib.h>
-#undef boolean
-}
 #include <limits.h>
 #include <linux/videodev.h>
 #include <linux/dvb/audio.h>
@@ -47,6 +40,13 @@
 #define DEV_DVB_AUDIO     "audio"
 #define DEV_DVB_CA        "ca"
 
+#define DVBS_TUNE_TIMEOUT  2000 //ms
+#define DVBS_LOCK_TIMEOUT  2000 //ms
+#define DVBC_TUNE_TIMEOUT  5000 //ms
+#define DVBC_LOCK_TIMEOUT  2000 //ms
+#define DVBT_TUNE_TIMEOUT  9000 //ms
+#define DVBT_LOCK_TIMEOUT  2000 //ms
+
 class cDvbName {
 private:
   char buffer[PATH_MAX];
@@ -73,6 +73,9 @@
   enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked };
   int fd_frontend;
   int cardIndex;
+  int tuneTimeout;
+  int lockTimeout;
+  time_t lastTimeoutReport;
   fe_type_t frontendType;
   cCiHandler *ciHandler;
   cChannel channel;
@@ -81,7 +84,7 @@
   cMutex mutex;
   cCondVar locked;
   cCondVar newSet;
-  bool GetFrontendEvent(dvb_frontend_event &Event, int TimeoutMs = 0);
+  bool GetFrontendStatus(fe_status_t &Status, int TimeoutMs = 0);
   bool SetFrontend(void);
   virtual void Action(void);
 public:
@@ -98,6 +101,9 @@
   cardIndex = CardIndex;
   frontendType = FrontendType;
   ciHandler = CiHandler;
+  tuneTimeout = 0;
+  lockTimeout = 0;
+  lastTimeoutReport = 0;
   diseqcCommands = NULL;
   tunerStatus = tsIdle;
   if (frontendType == FE_QPSK)
@@ -125,6 +131,7 @@
   if (Tune)
      tunerStatus = tsSet;
   channel = *Channel;
+  lastTimeoutReport = 0;
   newSet.Broadcast();
 }
 
@@ -140,26 +147,18 @@
   return tunerStatus >= tsLocked;
 }
 
-bool cDvbTuner::GetFrontendEvent(dvb_frontend_event &Event, int TimeoutMs)
+bool cDvbTuner::GetFrontendStatus(fe_status_t &Status, int TimeoutMs)
 {
   if (TimeoutMs) {
-     struct pollfd pfd;
-     pfd.fd = fd_frontend;
-     pfd.events = POLLIN | POLLPRI;
-     do {
-        int stat = poll(&pfd, 1, TimeoutMs);
-        if (stat == 1)
-           break;
-        if (stat < 0) {
-           if (errno == EINTR)
-              continue;
-           esyslog("ERROR: frontend %d poll failed: %m", cardIndex);
-           }
-        return false;
-        } while (0);
+     cPoller Poller(fd_frontend);
+     if (Poller.Poll(TimeoutMs)) {
+        dvb_frontend_event Event;
+        while (ioctl(fd_frontend, FE_GET_EVENT, &Event) == 0)
+              ; // just to clear the event queue - we'll read the actual status below
+        }
      }
   do {
-     int stat = ioctl(fd_frontend, FE_GET_EVENT, &Event);
+     int stat = ioctl(fd_frontend, FE_READ_STATUS, &Status);
      if (stat == 0)
         return true;
      if (stat < 0) {
@@ -245,6 +244,9 @@
          Frontend.inversion = fe_spectral_inversion_t(channel.Inversion());
          Frontend.u.qpsk.symbol_rate = channel.Srate() * 1000UL;
          Frontend.u.qpsk.fec_inner = fe_code_rate_t(channel.CoderateH());
+
+         tuneTimeout = DVBS_TUNE_TIMEOUT;
+         lockTimeout = DVBS_LOCK_TIMEOUT;
          }
          break;
     case FE_QAM: { // DVB-C
@@ -256,6 +258,9 @@
          Frontend.u.qam.symbol_rate = channel.Srate() * 1000UL;
          Frontend.u.qam.fec_inner = fe_code_rate_t(channel.CoderateH());
          Frontend.u.qam.modulation = fe_modulation_t(channel.Modulation());
+
+         tuneTimeout = DVBC_TUNE_TIMEOUT;
+         lockTimeout = DVBC_LOCK_TIMEOUT;
          }
          break;
     case FE_OFDM: { // DVB-T
@@ -271,6 +276,9 @@
          Frontend.u.ofdm.transmission_mode = fe_transmit_mode_t(channel.Transmission());
          Frontend.u.ofdm.guard_interval = fe_guard_interval_t(channel.Guard());
          Frontend.u.ofdm.hierarchy_information = fe_hierarchy_t(channel.Hierarchy());
+
+         tuneTimeout = DVBT_TUNE_TIMEOUT;
+         lockTimeout = DVBT_LOCK_TIMEOUT;
          }
          break;
     default:
@@ -286,30 +294,54 @@
 
 void cDvbTuner::Action(void)
 {
-  dvb_frontend_event event;
+  cTimeMs Timer;
+  bool LostLock = false;
+  fe_status_t Status = (fe_status_t)0;
   while (Running()) {
-        bool hasEvent = GetFrontendEvent(event, 1);
-
+        fe_status_t NewStatus;
+        if (GetFrontendStatus(NewStatus, 10))
+           Status = NewStatus;
         cMutexLock MutexLock(&mutex);
         switch (tunerStatus) {
           case tsIdle:
                break;
           case tsSet:
-               if (hasEvent)
-                  continue;
                tunerStatus = SetFrontend() ? tsTuned : tsIdle;
+               Timer.Set(tuneTimeout);
                continue;
           case tsTuned:
-          case tsLocked:
-               if (hasEvent) {
-                  if (event.status & FE_REINIT) {
-                     tunerStatus = tsSet;
-                     esyslog("ERROR: frontend %d was reinitialized - re-tuning", cardIndex);
+               if (Timer.TimedOut()) {
+                  tunerStatus = tsSet;
+                  diseqcCommands = NULL;
+                  if (time(NULL) - lastTimeoutReport > 60) { // let's not get too many of these
+                     esyslog("ERROR: frontend %d timed out while tuning to channel %d, tp %d", cardIndex, channel.Number(), channel.Transponder());
+                     lastTimeoutReport = time(NULL);
                      }
-                  if (event.status & FE_HAS_LOCK) {
-                     tunerStatus = tsLocked;
-                     locked.Broadcast();
+                  continue;
+                  }
+          case tsLocked:
+               if (Status & FE_REINIT) {
+                  tunerStatus = tsSet;
+                  diseqcCommands = NULL;
+                  esyslog("ERROR: frontend %d was reinitialized", cardIndex);
+                  lastTimeoutReport = 0;
+                  continue;
+                  }
+               else if (Status & FE_HAS_LOCK) {
+                  if (LostLock) {
+                     esyslog("frontend %d regained lock on channel %d, tp %d", cardIndex, channel.Number(), channel.Transponder());
+                     LostLock = false;
                      }
+                  tunerStatus = tsLocked;
+                  locked.Broadcast();
+                  lastTimeoutReport = 0;
+                  }
+               else if (tunerStatus == tsLocked) {
+                  LostLock = true;
+                  esyslog("ERROR: frontend %d lost lock on channel %d, tp %d", cardIndex, channel.Number(), channel.Transponder());
+                  tunerStatus = tsTuned;
+                  Timer.Set(lockTimeout);
+                  lastTimeoutReport = 0;
                   continue;
                   }
           }
@@ -471,13 +503,21 @@
 
 int cDvbDevice::ProvidesCa(const cChannel *Channel) const
 {
-  if (Channel->Ca() >= 0x0100 && ciHandler) {
-     unsigned short ids[MAXCAIDS + 1];
-     for (int i = 0; i <= MAXCAIDS; i++) // '<=' copies the terminating 0!
-         ids[i] = Channel->Ca(i);
-     return ciHandler->ProvidesCa(ids);
+  int NumCams = 0;
+  if (ciHandler) {
+     NumCams = ciHandler->NumCams();
+     if (Channel->Ca() >= CA_ENCRYPTED_MIN) {
+        unsigned short ids[MAXCAIDS + 1];
+        for (int i = 0; i <= MAXCAIDS; i++) // '<=' copies the terminating 0!
+            ids[i] = Channel->Ca(i);
+        if (ciHandler->ProvidesCa(ids))
+           return NumCams + 1;
+        }
      }
-  return cDevice::ProvidesCa(Channel);
+  int result = cDevice::ProvidesCa(Channel);
+  if (result > 0)
+     result += NumCams;
+  return result;
 }
 
 cSpuDecoder *cDvbDevice::GetSpuDecoder(void)
@@ -487,103 +527,84 @@
   return spuDecoder;
 }
 
-bool cDvbDevice::GrabImage(const char *FileName, bool Jpeg, int Quality, int SizeX, int SizeY)
+uchar *cDvbDevice::GrabImage(int &Size, bool Jpeg, int Quality, int SizeX, int SizeY)
 {
   if (devVideoIndex < 0)
-     return false;
+     return NULL;
   char buffer[PATH_MAX];
   snprintf(buffer, sizeof(buffer), "%s%d", DEV_VIDEO, devVideoIndex);
   int videoDev = open(buffer, O_RDWR);
-  if (videoDev < 0)
-     LOG_ERROR_STR(buffer);
   if (videoDev >= 0) {
-     int result = 0;
+     uchar *result = NULL;
      struct video_mbuf mbuf;
-     result |= ioctl(videoDev, VIDIOCGMBUF, &mbuf);
-     if (result == 0) {
+     if (ioctl(videoDev, VIDIOCGMBUF, &mbuf) == 0) {
         int msize = mbuf.size;
         unsigned char *mem = (unsigned char *)mmap(0, msize, PROT_READ | PROT_WRITE, MAP_SHARED, videoDev, 0);
         if (mem && mem != (unsigned char *)-1) {
            // set up the size and RGB
            struct video_capability vc;
-           result |= ioctl(videoDev, VIDIOCGCAP, &vc);
-           struct video_mmap vm;
-           vm.frame = 0;
-           if ((SizeX > 0) && (SizeX <= vc.maxwidth) &&
-               (SizeY > 0) && (SizeY <= vc.maxheight)) {
-              vm.width = SizeX;
-              vm.height = SizeY;
-              }
-           else {
-              vm.width = vc.maxwidth;
-              vm.height = vc.maxheight;
-              }
-           vm.format = VIDEO_PALETTE_RGB24;
-           result |= ioctl(videoDev, VIDIOCMCAPTURE, &vm);
-           result |= ioctl(videoDev, VIDIOCSYNC, &vm.frame);
-           // make RGB out of BGR:
-           int memsize = vm.width * vm.height;
-           unsigned char *mem1 = mem;
-           for (int i = 0; i < memsize; i++) {
-               unsigned char tmp = mem1[2];
-               mem1[2] = mem1[0];
-               mem1[0] = tmp;
-               mem1 += 3;
-               }
-
-           if (Quality < 0)
-              Quality = 100;
-
-           isyslog("grabbing to %s (%s %d %d %d)", FileName, Jpeg ? "JPEG" : "PNM", Quality, vm.width, vm.height);
-           FILE *f = fopen(FileName, "wb");
-           if (f) {
-              if (Jpeg) {
-                 // write JPEG file:
-                 struct jpeg_compress_struct cinfo;
-                 struct jpeg_error_mgr jerr;
-                 cinfo.err = jpeg_std_error(&jerr);
-                 jpeg_create_compress(&cinfo);
-                 jpeg_stdio_dest(&cinfo, f);
-                 cinfo.image_width = vm.width;
-                 cinfo.image_height = vm.height;
-                 cinfo.input_components = 3;
-                 cinfo.in_color_space = JCS_RGB;
-
-                 jpeg_set_defaults(&cinfo);
-                 jpeg_set_quality(&cinfo, Quality, true);
-                 jpeg_start_compress(&cinfo, true);
-
-                 int rs = vm.width * 3;
-                 JSAMPROW rp[vm.height];
-                 for (int k = 0; k < vm.height; k++)
-                     rp[k] = &mem[rs * k];
-                 jpeg_write_scanlines(&cinfo, rp, vm.height);
-                 jpeg_finish_compress(&cinfo);
-                 jpeg_destroy_compress(&cinfo);
+           if (ioctl(videoDev, VIDIOCGCAP, &vc) == 0) {
+              struct video_mmap vm;
+              vm.frame = 0;
+              if ((SizeX > 0) && (SizeX <= vc.maxwidth) &&
+                  (SizeY > 0) && (SizeY <= vc.maxheight)) {
+                 vm.width = SizeX;
+                 vm.height = SizeY;
                  }
               else {
-                 // write PNM file:
-                 if (fprintf(f, "P6\n%d\n%d\n255\n", vm.width, vm.height) < 0 ||
-                     fwrite(mem, vm.width * vm.height * 3, 1, f) != 1) {
-                    LOG_ERROR_STR(FileName);
-                    result |= 1;
+                 vm.width = vc.maxwidth;
+                 vm.height = vc.maxheight;
+                 }
+              vm.format = VIDEO_PALETTE_RGB24;
+              if (ioctl(videoDev, VIDIOCMCAPTURE, &vm) == 0 && ioctl(videoDev, VIDIOCSYNC, &vm.frame) == 0) {
+                 // make RGB out of BGR:
+                 int memsize = vm.width * vm.height;
+                 unsigned char *mem1 = mem;
+                 for (int i = 0; i < memsize; i++) {
+                     unsigned char tmp = mem1[2];
+                     mem1[2] = mem1[0];
+                     mem1[0] = tmp;
+                     mem1 += 3;
+                     }
+
+                 if (Quality < 0)
+                    Quality = 100;
+
+                 isyslog("grabbing to %s %d %d %d", Jpeg ? "JPEG" : "PNM", Quality, vm.width, vm.height);
+                 if (Jpeg) {
+                    // convert to JPEG:
+                    result = RgbToJpeg(mem, vm.width, vm.height, Size, Quality);
+                    if (!result)
+                       esyslog("ERROR: failed to convert image to JPEG");
+                    }
+                 else {
+                    // convert to PNM:
+                    char buf[32];
+                    snprintf(buf, sizeof(buf), "P6\n%d\n%d\n255\n", vm.width, vm.height);
+                    int l = strlen(buf);
+                    int bytes = memsize * 3;
+                    Size = l + bytes;
+                    result = MALLOC(uchar, Size);
+                    if (result) {
+                       memcpy(result, buf, l);
+                       memcpy(result + l, mem, bytes);
+                       }
+                    else
+                       esyslog("ERROR: failed to convert image to PNM");
                     }
                  }
-              fclose(f);
-              }
-           else {
-              LOG_ERROR_STR(FileName);
-              result |= 1;
               }
            munmap(mem, msize);
            }
         else
-           result |= 1;
+           esyslog("ERROR: failed to memmap video device");
         }
      close(videoDev);
-     return result == 0;
+     return result;
      }
-  return false;
+  else
+     LOG_ERROR_STR(buffer);
+  return NULL;
 }
 
 void cDvbDevice::SetVideoDisplayFormat(eVideoDisplayFormat VideoDisplayFormat)
@@ -754,7 +775,7 @@
            if (Channel->Vpid() && !HasPid(Channel->Vpid()) || Channel->Apid(0) && !HasPid(Channel->Apid(0))) {
 #ifdef DO_MULTIPLE_RECORDINGS
 #ifndef DO_MULTIPLE_CA_CHANNELS
-              if (Ca() > CACONFBASE || Channel->Ca() > CACONFBASE)
+              if (Ca() >= CA_ENCRYPTED_MIN || Channel->Ca() >= CA_ENCRYPTED_MIN)
                  needsDetachReceivers = Ca() != Channel->Ca();
               else
 #endif
@@ -825,6 +846,11 @@
         esyslog("ERROR: failed to set PIDs for channel %d on device %d", Channel->Number(), CardIndex() + 1);
         return false;
         }
+     //XXX quick workaround for additional live audio PIDs:
+     if (ciHandler) {
+        ciHandler->SetPid(Channel->Apid(1), true);
+        ciHandler->SetPid(Channel->Dpid(0), true);
+        }
      if (IsPrimaryDevice())
         AddPid(Channel->Tpid(), ptTeletext);
      CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, true)); // actually one would expect 'false' here, but according to Marco Schlüßler <marco@lordzodiac.de> this works
diff -ruN vdr-1.3.37/dvbdevice.h vdr-1.3.38/dvbdevice.h
--- vdr-1.3.37/dvbdevice.h	2005-11-11 15:51:38.000000000 +0100
+++ vdr-1.3.38/dvbdevice.h	2005-12-29 14:33:12.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbdevice.h 1.36 2005/11/11 14:51:38 kls Exp $
+ * $Id: dvbdevice.h 1.37 2005/12/29 13:33:12 kls Exp $
  */
 
 #ifndef __DVBDEVICE_H
@@ -85,7 +85,7 @@
   static int devVideoOffset;
   int devVideoIndex;
 public:
-  virtual bool GrabImage(const char *FileName, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1);
+  virtual uchar *GrabImage(int &Size, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1);
 
 // Video format facilities
 
diff -ruN vdr-1.3.37/dvbosd.c vdr-1.3.38/dvbosd.c
--- vdr-1.3.37/dvbosd.c	2005-05-22 12:57:45.000000000 +0200
+++ vdr-1.3.38/dvbosd.c	2005-12-30 16:41:54.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbosd.c 1.27 2005/05/22 10:57:45 kls Exp $
+ * $Id: dvbosd.c 1.29 2005/12/30 15:41:54 kls Exp $
  */
 
 #include "dvbosd.h"
@@ -39,7 +39,7 @@
   osdDev = OsdDev;
   shown = false;
   if (osdDev < 0)
-     esyslog("ERROR: illegal OSD device handle (%d)!", osdDev);
+     esyslog("ERROR: invalid OSD device handle (%d)!", osdDev);
   else {
      osdMem = MAXOSDMEMORY;
 #ifdef OSD_CAP_MEMSIZE
@@ -88,6 +88,8 @@
             return oeBppNotSupported;
          if ((Areas[i].Width() & (8 / Areas[i].bpp - 1)) != 0)
             return oeWrongAlignment;
+         if (Areas[i].Width() < 1 || Areas[i].Height() < 1 || Areas[i].Width() > 720 || Areas[i].Height() > 576)
+            return oeWrongAreaSize;
          TotalMemory += Areas[i].Width() * Areas[i].Height() / (8 / Areas[i].bpp);
          }
      if (TotalMemory > osdMem)
diff -ruN vdr-1.3.37/dvbplayer.c vdr-1.3.38/dvbplayer.c
--- vdr-1.3.37/dvbplayer.c	2005-10-31 13:33:48.000000000 +0100
+++ vdr-1.3.38/dvbplayer.c	2006-01-08 12:39:41.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbplayer.c 1.41 2005/10/31 12:33:48 kls Exp $
+ * $Id: dvbplayer.c 1.42 2006/01/08 11:39:41 kls Exp $
  */
 
 #include "dvbplayer.h"
@@ -621,7 +621,7 @@
      int Current, Total;
      GetIndex(Current, Total, true);
      int OldCurrent = Current;
-     // As GetNextIFrame() increments/decrements at least once, the 
+     // As GetNextIFrame() increments/decrements at least once, the
      // destination frame (= Current + Frames) must be adjusted by
      // -1/+1 respectively.
      Current = index->GetNextIFrame(Current + Frames + (Frames > 0 ? -1 : 1), Frames > 0);
diff -ruN vdr-1.3.37/dvbspu.c vdr-1.3.38/dvbspu.c
--- vdr-1.3.37/dvbspu.c	2005-11-05 13:08:15.000000000 +0100
+++ vdr-1.3.38/dvbspu.c	2006-01-08 12:39:46.000000000 +0100
@@ -8,16 +8,15 @@
  *
  * parts of this file are derived from the OMS program.
  *
- * $Id: dvbspu.c 1.17 2005/11/05 12:08:15 kls Exp $
+ * $Id: dvbspu.c 1.19 2006/01/08 11:39:46 kls Exp $
  */
 
+#include "dvbspu.h"
 #include <assert.h>
 #include <string.h>
 #include <inttypes.h>
 #include <math.h>
-
 #include "device.h"
-#include "dvbspu.h"
 
 /*
  * cDvbSpubitmap:
@@ -28,7 +27,7 @@
  * Inputs:
  *  - a SPU rle encoded image on creation, which will be decoded into
  *    the full screen indexed bitmap
- *  
+ *
  * Output:
  *  - a minimal sized cDvbSpuBitmap a given palette, the indexed bitmap
  *    will be scanned to get the smallest possible resulting bitmap considering
@@ -436,7 +435,7 @@
 
             prev_DCSQ_offset = DCSQ_offset;
             DCSQ_offset = spuU32(i);
-            DEBUG("offs = %d, DCSQ = %d, prev_DCSQ = %d\n", 
+            DEBUG("offs = %d, DCSQ = %d, prev_DCSQ = %d\n",
                            i, DCSQ_offset, prev_DCSQ_offset);
             i += 2;
 
diff -ruN vdr-1.3.37/dvbspu.h vdr-1.3.38/dvbspu.h
--- vdr-1.3.37/dvbspu.h	2005-11-05 13:08:47.000000000 +0100
+++ vdr-1.3.38/dvbspu.h	2006-01-05 11:18:31.000000000 +0100
@@ -8,16 +8,16 @@
  *
  * parts of this file are derived from the OMS program.
  *
- * $Id: dvbspu.h 1.10 2005/11/05 12:08:47 kls Exp $
+ * $Id: dvbspu.h 1.11 2006/01/05 10:18:31 kls Exp $
  */
 
 #ifndef __DVBSPU_H
 #define __DVBSPU_H
 
 #include <inttypes.h>
-
 #include "osd.h"
 #include "spu.h"
+#include "thread.h"
 
 typedef struct sDvbSpuPalDescr {
     uint8_t index;
diff -ruN vdr-1.3.37/eit.c vdr-1.3.38/eit.c
--- vdr-1.3.37/eit.c	2005-11-04 15:19:16.000000000 +0100
+++ vdr-1.3.38/eit.c	2005-12-26 12:50:09.000000000 +0100
@@ -8,7 +8,7 @@
  * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
  * Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
  *
- * $Id: eit.c 1.112 2005/11/04 14:19:16 kls Exp $
+ * $Id: eit.c 1.113 2005/12/26 11:50:09 kls Exp $
  */
 
 #include "eit.h"
@@ -43,6 +43,8 @@
 
   bool Empty = true;
   bool Modified = false;
+  time_t SegmentStart = 0;
+  time_t SegmentEnd = 0;
 
   SI::EIT::Event SiEitEvent;
   for (SI::Loop::Iterator it; eventLoop.getNext(SiEitEvent, it); ) {
@@ -50,6 +52,9 @@
       if (SiEitEvent.getStartTime() == 0 || SiEitEvent.getStartTime() > 0 && SiEitEvent.getDuration() == 0)
          continue;
       Empty = false;
+      if (!SegmentStart)
+         SegmentStart = SiEitEvent.getStartTime();
+      SegmentEnd = SiEitEvent.getStartTime() + SiEitEvent.getDuration();
       cEvent *newEvent = NULL;
       cEvent *rEvent = NULL;
       cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), SiEitEvent.getStartTime());
@@ -242,6 +247,7 @@
      pSchedule->SetPresentSeen();
   if (Modified) {
      pSchedule->Sort();
+     pSchedule->DropOutdated(SegmentStart, SegmentEnd, Tid, getVersionNumber());
      Schedules->SetModified(pSchedule);
      }
 }
diff -ruN vdr-1.3.37/eitscan.c vdr-1.3.38/eitscan.c
--- vdr-1.3.37/eitscan.c	2005-11-05 16:24:36.000000000 +0100
+++ vdr-1.3.38/eitscan.c	2006-01-07 15:10:17.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: eitscan.c 1.29 2005/11/05 15:24:36 kls Exp $
+ * $Id: eitscan.c 1.30 2006/01/07 14:10:17 kls Exp $
  */
 
 #include "eitscan.h"
@@ -147,7 +147,7 @@
                   for (cScanData *ScanData = scanList->First(); ScanData; ScanData = scanList->Next(ScanData)) {
                       const cChannel *Channel = ScanData->GetChannel();
                       if (Channel) {
-                         if (!Channel->Ca() || Channel->Ca() == Device->DeviceNumber() + 1 || Channel->Ca() >= 0x0100) {
+                         if (!Channel->Ca() || Channel->Ca() == Device->DeviceNumber() + 1 || Channel->Ca() >= CA_ENCRYPTED_MIN) {
                             if (Device->ProvidesTransponder(Channel)) {
                                if (!Device->Receiving()) {
                                   bool MaySwitchTransponder = Device->MaySwitchTransponder();
diff -ruN vdr-1.3.37/epg.c vdr-1.3.38/epg.c
--- vdr-1.3.37/epg.c	2005-11-11 14:37:43.000000000 +0100
+++ vdr-1.3.38/epg.c	2005-12-30 16:41:59.000000000 +0100
@@ -7,7 +7,7 @@
  * Original version (as used in VDR before 1.3.0) written by
  * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
  *
- * $Id: epg.c 1.40 2005/11/11 13:37:43 kls Exp $
+ * $Id: epg.c 1.47 2005/12/30 15:41:59 kls Exp $
  */
 
 #include "epg.h"
@@ -234,7 +234,7 @@
 {
   if (InfoOnly || startTime + duration + Setup.EPGLinger * 60 >= time(NULL)) {
      if (!InfoOnly)
-        fprintf(f, "%sE %u %ld %d %X\n", Prefix, eventID, startTime, duration, tableID);
+        fprintf(f, "%sE %u %ld %d %X %X\n", Prefix, eventID, startTime, duration, tableID, version);
      if (!isempty(title))
         fprintf(f, "%sT %s\n", Prefix, title);
      if (!isempty(shortText))
@@ -296,8 +296,9 @@
                           time_t StartTime;
                           int Duration;
                           unsigned int TableID = 0;
-                          int n = sscanf(t, "%u %ld %d %X", &EventID, &StartTime, &Duration, &TableID);
-                          if (n == 3 || n == 4) {
+                          unsigned int Version = 0xFF;
+                          int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
+                          if (n >= 3 && n <= 5) {
                              Event = (cEvent *)Schedule->GetEvent(EventID, StartTime);
                              cEvent *newEvent = NULL;
                              if (Event)
@@ -306,6 +307,7 @@
                                 Event = newEvent = new cEvent(EventID);
                              if (Event) {
                                 Event->SetTableID(TableID);
+                                Event->SetVersion(Version);
                                 Event->SetStartTime(StartTime);
                                 Event->SetDuration(Duration);
                                 if (newEvent)
@@ -635,7 +637,6 @@
   if (Event->schedule == this) {
      UnhashEvent(Event);
      events.Del(Event);
-     Event->schedule = NULL;
      }
 }
 
@@ -737,6 +738,31 @@
   events.Sort();
 }
 
+void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
+{
+  if (SegmentStart > 0 && SegmentEnd > 0) {
+     for (cEvent *p = events.First(); p; p = events.Next(p)) {
+         if (p->EndTime() > SegmentStart) {
+            if (p->StartTime() < SegmentEnd) {
+               // The event overlaps with the given time segment.
+               if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
+                  // The segment overwrites all events from tables with higher ids, and
+                  // within the same table id all events must have the same version.
+                  // We can't delete the event right here because a timer might have
+                  // a pointer to it, so let's set its id and start time to 0 to have it
+                  // "phased out":
+                  UnhashEvent(p);
+                  p->eventID = 0;
+                  p->startTime = 0;
+                  }
+               }
+            else
+               break;
+            }
+         }
+     }
+}
+
 void cSchedule::Cleanup(void)
 {
   Cleanup(time(NULL));
@@ -745,15 +771,12 @@
 void cSchedule::Cleanup(time_t Time)
 {
   cEvent *Event;
-  for (int a = 0; true ; a++) {
-      Event = events.Get(a);
-      if (!Event)
-         break;
-      if (!Event->HasTimer() && Event->EndTime() + Setup.EPGLinger * 60 + 3600 < Time) { // adding one hour for safety
-         DelEvent(Event);
-         a--;
-         }
-      }
+  while ((Event = events.First()) != NULL) {
+        if (!Event->HasTimer() && Event->EndTime() + Setup.EPGLinger * 60 + 3600 < Time) // adding one hour for safety
+           DelEvent(Event);
+        else
+           break;
+        }
 }
 
 void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const
@@ -811,7 +834,7 @@
                        }
                     }
                  else {
-                    esyslog("ERROR: illegal channel ID: %s", s);
+                    esyslog("ERROR: invalid channel ID: %s", s);
                     return false;
                     }
                  }
@@ -871,7 +894,7 @@
   time_t now = time(NULL);
   struct tm tm_r;
   struct tm *ptm = localtime_r(&now, &tm_r);
-  if (now - lastCleanup > 3600 && ptm->tm_hour == 5) {
+  if (now - lastCleanup > 3600) {
      isyslog("cleaning up schedules data");
      cSchedulesLock SchedulesLock(true, 1000);
      cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
@@ -880,7 +903,8 @@
             p->Cleanup(now);
         }
      lastCleanup = now;
-     ReportEpgBugFixStats(true);
+     if (ptm->tm_hour == 5)
+        ReportEpgBugFixStats(true);
      }
   if (epgDataFileName && now - lastDump > 600) {
      cSafeFile f(epgDataFileName);
diff -ruN vdr-1.3.37/epg.h vdr-1.3.38/epg.h
--- vdr-1.3.37/epg.h	2005-09-11 14:54:30.000000000 +0200
+++ vdr-1.3.38/epg.h	2005-12-27 15:31:24.000000000 +0100
@@ -7,7 +7,7 @@
  * Original version (as used in VDR before 1.3.0) written by
  * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
  *
- * $Id: epg.h 1.26 2005/09/11 12:54:30 kls Exp $
+ * $Id: epg.h 1.28 2005/12/27 14:31:24 kls Exp $
  */
 
 #ifndef __EPG_H
@@ -67,6 +67,7 @@
   ~cEvent();
   virtual int Compare(const cListObject &ListObject) const;
   tChannelID ChannelID(void) const;
+  const cSchedule *Schedule(void) const { return schedule; }
   u_int16_t EventID(void) const { return eventID; }
   uchar TableID(void) const { return tableID; }
   uchar Version(void) const { return version; }
@@ -128,6 +129,7 @@
   void ClrRunningStatus(cChannel *Channel = NULL);
   void ResetVersions(void);
   void Sort(void);
+  void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
   void Cleanup(time_t Time);
   void Cleanup(void);
   cEvent *AddEvent(cEvent *Event);
diff -ruN vdr-1.3.37/genfontfile.c vdr-1.3.38/genfontfile.c
--- vdr-1.3.37/genfontfile.c	2004-12-18 16:00:00.000000000 +0100
+++ vdr-1.3.38/genfontfile.c	2006-01-08 12:39:51.000000000 +0100
@@ -154,9 +154,9 @@
     character.byte2 = (i + fontinfo->min_char_or_byte2) & 255;
     character.byte1 = (i + fontinfo->min_char_or_byte2) >> 8;
 
-    /* XXX we could use XDrawImageString16 which would also paint the backing 
+    /* XXX we could use XDrawImageString16 which would also paint the backing
 
-       rectangle but X server bugs in some scalable font rasterizers makes it 
+       rectangle but X server bugs in some scalable font rasterizers makes it
 
        more effective to do XFillRectangles to clear the pixmap and
        XDrawImage16 for the text.  */
diff -ruN vdr-1.3.37/i18n.c vdr-1.3.38/i18n.c
--- vdr-1.3.37/i18n.c	2005-11-04 15:36:27.000000000 +0100
+++ vdr-1.3.38/i18n.c	2006-01-08 12:56:31.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: i18n.c 1.220 2005/11/04 14:36:27 kls Exp $
+ * $Id: i18n.c 1.230 2006/01/08 11:56:31 kls Exp $
  *
  * Translations provided by:
  *
@@ -221,7 +221,7 @@
     "Ajastimet",
     "Timery",
     "Timer",
-    "×ñïíïäéáêüðôçò",
+    "×ñïíïðñïãñáììáôéóìïß",
     "Timers",
     "Timer-e",
     "Felvétel beprogramozása",
@@ -263,7 +263,7 @@
     "Tallenteen tiedot",
     "",// TODO
     "",// TODO
-    "ÅããñáöÞ",
+    "Ðëçñïöïñßåò EããñáöÞò",
     "Inspelning",
     "Detaliile înregistrãrii",
     "",// TODO
@@ -347,7 +347,7 @@
     "Muokkaa kanavaa",
     "Ustawienie kanalu",
     "Modificar canal",
-    "ÐñïóáñìïãÝò Êáíáëéïý",
+    "Ôñïðïðïßçóç Êáíáëéïý",
     "Ändra kanal",
     "Modificare canal",
     "Csatornák beállítása",
@@ -368,7 +368,7 @@
     "Muokkaa ajastinta",
     "Ustawienie timerow",
     "Modificar timer",
-    "ÐñïóáñìïãÞ ÷ñïíïäéáêüðôç",
+    "Ôñïðïðïßçóç ÷ñïíïðñïãñáììáôéóìïý",
     "Ändra timer",
     "Modificare timer",
     "Felvétel beprogramozásának megváltoztatása",
@@ -399,27 +399,6 @@
     "Sündmus",
     "Udsendelse",
   },
-  { "Summary",
-    "Inhalt",
-    "Vsebina",
-    "Sommario",
-    "Inhoud",
-    "Resumo",
-    "Résumé",
-    "Sammendrag",
-    "Yhteenveto",
-    "Zawartosc",
-    "Resúmen",
-    "Ðåñéå÷üìåíï",
-    "Sammanfattning",
-    "Detalii",
-    "Tartalom",
-    "Sinopsi",
-    "¾ßØáÐÝØÕ",
-    "Sadr¾aj",
-    "Kokkuvõte",
-    "Omtale",
-  },
   { "Info",
     "Info",
     "Info",
@@ -504,8 +483,29 @@
     "Järgmisena eetris",
     "Hvad vises som det næste?",
   },
+  { "Summary",
+    "Inhalt",
+    "Vsebina",
+    "Sommario",
+    "Inhoud",
+    "Resumo",
+    "Résumé",
+    "Sammendrag",
+    "Yhteenveto",
+    "Zawartosc",
+    "Resumen",
+    "Ðåñéå÷üìåíï",
+    "Sammanfattning",
+    "Cuprins",
+    "Tartalom",
+    "Resum",
+    "¾ßØáÐÝØÕ",
+    "Sadr¾aj",
+    "Kokkuvõte",
+    "Omtale",
+  },
   // Button texts (should not be more than 10 characters!):
-  { "Edit",
+  { "Button$Edit",
     "Editieren",
     "Uredi",
     "Modifica",
@@ -526,7 +526,7 @@
     "Muuda",
     "Rediger",
   },
-  { "New",
+  { "Button$New",
     "Neu",
     "Novo",
     "Nuovo",
@@ -547,7 +547,7 @@
     "Uus",
     "Ny",
   },
-  { "Delete",
+  { "Button$Delete",
     "Löschen",
     "Izbri¹i",
     "Cancella",
@@ -568,7 +568,7 @@
     "Kustuta",
     "Slet",
   },
-  { "Mark",
+  { "Button$Mark",
     "Markieren",
     "Oznaèi",
     "Marca",
@@ -589,7 +589,7 @@
     "Siirda",
     "Markér",
   },
-  { "On/Off",
+  { "Button$On/Off",
     "Ein/Aus",
     "Vklop/Izklop",
     "On/Off",
@@ -610,7 +610,7 @@
     "Sees/Väljas",
     "Til/Fra",
   },
-  { "Record",
+  { "Button$Record",
     "Aufnehmen",
     "Posnemi",
     "Registra",
@@ -631,7 +631,7 @@
     "Salvesta",
     "Optag",
   },
-  { "Play",
+  { "Button$Play",
     "Wiedergabe",
     "Predvajaj",
     "Riproduci",
@@ -642,7 +642,7 @@
     "Toista",
     "Odtwarzac",
     "Play",
-    "ÁíáìåôÜäïóç",
+    "ÁíáðáñáãùãÞ",
     "Spela upp",
     "Redare",
     "Lejátszani",
@@ -652,7 +652,28 @@
     "Start",
     "Afspil",
   },
-  { "Rewind",
+  { "Button$Pause",
+    "Pause",
+    "Pavza",
+    "Pausa",
+    "Pauze",
+    "",// TODO
+    "Pause",
+    "",// TODO
+    "Tauko",
+    "Przerwa",
+    "Pausa",
+    "Ðáýóç",
+    "Pausa",
+    "Pauzã",
+    "Szünet",
+    "Pausa",
+    "¿Ðã×Ð",
+    "Pauza",
+    "Paus",
+    "Pause",
+  },
+  { "Button$Rewind",
     "Anfang",
     "Na zaèetek",
     "Da inizio",
@@ -663,7 +684,7 @@
     "Alkuun",
     "Poczatek",
     "Rebobinar",
-    "Áñ÷Þ",
+    "ÌåôáöïñÜ óôçí Áñ÷Þ",
     "Återspolning",
     "Înapoi",
     "Vissza az elejére",
@@ -694,7 +715,7 @@
     "Stopp",
     "Stop",
   },
-  { "Resume",
+  { "Button$Resume",
     "Weiter",
     "Nadaljuj",
     "Riprendi",
@@ -705,7 +726,7 @@
     "Jatka",
     "Dalej",
     "Continuar",
-    "ÓõíÝ÷åéá",
+    "ÅðáíáöïñÜ",
     "Fortsätt",
     "Revenire",
     "Tovább",
@@ -715,28 +736,7 @@
     "Jätka",
     "Fortsæt",
   },
-  { "Summary",
-    "Inhalt",
-    "Vsebina",
-    "Sommario",
-    "Inhoud",
-    "Resumo",
-    "Résumé",
-    "Sammendrag",
-    "Yhteenveto",
-    "Zawartosc",
-    "Resumen",
-    "Ðåñéå÷üìåíï",
-    "Sammanfattning",
-    "Cuprins",
-    "Tartalom",
-    "Resum",
-    "¾ßØáÐÝØÕ",
-    "Sadr¾aj",
-    "Kokkuvõte",
-    "Omtale",
-  },
-  { "Open",
+  { "Button$Open",
     "Öffnen",
     "Odpri",
     "Apri",
@@ -747,7 +747,7 @@
     "Avaa",
     "Otworzyc",
     "Abrir",
-    "¢íïéãìá",
+    "Áíïéãìá",
     "Öppna",
     "Deschide",
     "Kinyitni",
@@ -757,7 +757,7 @@
     "Ava",
     "Åbn",
   },
-  { "Switch",
+  { "Button$Switch",
     "Umschalten",
     "Preklopi",
     "Cambia",
@@ -778,7 +778,7 @@
     "Vali",
     "Skift",
   },
-  { "Now",
+  { "Button$Now",
     "Jetzt",
     "Sedaj",
     "Adesso",
@@ -799,7 +799,7 @@
     "Nüüd",
     "Nu",
   },
-  { "Next",
+  { "Button$Next",
     "Nächste",
     "Sledi",
     "Prossimo",
@@ -841,49 +841,7 @@
     "Kava",
     "Program",
   },
-  { "Language",
-    "Sprache",
-    "Jezik",
-    "Linguaggio",
-    "Taal",
-    "Linguagem",
-    "Langue",
-    "Språk",
-    "Kieli",
-    "Jezyk",
-    "Idioma",
-    "Ãëþóóá",
-    "Språk",
-    "Limba",
-    "Nyelv",
-    "Idioma",
-    "Ï×ëÚ",
-    "Jezik",
-    "Keel",
-    "Sprog",
-  },
-  { "Eject",
-    "Auswerfen",
-    "Izvr¾i",
-    "Eject",
-    "Eject",
-    "Ejectar",
-    "Ejection",
-    "Eject",
-    "Avaa",
-    "Wyrzucenie",
-    "Expulsar",
-    "ÅîáãùãÞ",
-    "Mata ut",
-    "Ejecteazã",
-    "Kidobni",
-    "Expulsar",
-    "¸×ÒÛÕçì",
-    "Izbaci",
-    "Ava",
-    "Skub ud",
-  },
-  { "ABC/abc",
+  { "Button$ABC/abc",
     "ABC/abc",
     "ABC/abc",
     "ABC/abc",
@@ -904,7 +862,7 @@
     "ABC/abc",
     "ABC/abc",
   },
-  { "Insert",
+  { "Button$Insert",
     "Einfügen",
     "Vstavi",
     "Inserisci",
@@ -925,7 +883,7 @@
     "Lisa (INS)",
     "Indsæt",
   },
-  { "Overwrite",
+  { "Button$Overwrite",
     "Überschreiben",
     "Prepi¹i",
     "Sovrascrivi",
@@ -946,7 +904,7 @@
     "Asenda (OVR)",
     "Overskriv",
   },
-  { "Menu",
+  { "Button$Menu",
     "Menü",
     "Meni",
     "Menù",
@@ -967,7 +925,7 @@
     "Menüü",
     "Menu",
   },
-  { "Reset",
+  { "Button$Reset",
     "Reset",
     "Reset",
     "Reset",
@@ -988,7 +946,7 @@
     "Nullimine",
     "Nulstille",
   },
-  { "Scan",
+  { "Button$Scan",
     "Scan",
     "I¹èi",
     "Scansione",
@@ -1009,6 +967,27 @@
     "Uuenda",
     "Skan",
   },
+  { "Button$Audio",
+    "Audio",
+    "Zvok",
+    "",// TODO
+    "Audio",
+    "",// TODO
+    "Audio",
+    "",// TODO
+    "Ääni",
+    "",// TODO
+    "",// TODO
+    "¹÷ïò",
+    "Ljud",
+    "Sunet",
+    "",// TODO
+    "",// TODO
+    "Ï×ëÚ",
+    "",// TODO
+    "Audio",
+    "Audio",
+  },
   // Confirmations:
   { "Delete channel?",
     "Kanal löschen?",
@@ -1042,7 +1021,7 @@
     "Poistetaanko ajastin?",
     "Usunac timer?",
     "¿Eliminar timer?",
-    "ÄéáãñáöÞ ÷ñïíïäéáêüðôç?",
+    "ÄéáãñáöÞ ÷ñïíïðñïãñáììáôéóìïý;?",
     "Ta bort timern?",
     "ªterg timer-ul?",
     "Felvétel beprogramozásának törlése?",
@@ -1063,7 +1042,7 @@
     "Poistetaanko tallenne?",
     "Usunac nagranie?",
     "¿Eliminar grabacion?",
-    "ÄéáãñáöÞ áñ÷åßïõ?",
+    "ÄéáãñáöÞ åããñáöÞò?",
     "Ta bort inspelningen?",
     "ªterg înregistrarea?",
     "Felvétel törlése?",
@@ -1084,7 +1063,7 @@
     "Ajastettu tallennus käynnissä - poistetaanko silti?",
     "Nagrywanie w trakcie - napewno usunac?",
     "¿Timer activo - de verdad eliminarlo?",
-    "×ñïíïäéáêüðôçò óÝ åîÝëéîç - ÄéáãñáöÞ óßãïõñá?",
+    "×ñïíïðñïãñáììáôéóìüò óÝ åîÝëéîç - ÄéáãñáöÞ óßãïõñá?",
     "Timerstyrd inspelning pågår - Avbryta ändå?",
     "Timer-ul tocmai înregistreazã - ºterg, totuºi?",
     "Felvétel folyamatban van - mégis törölni?",
@@ -1115,27 +1094,6 @@
     "Lõpetan salvestamise?",
     "Stop optagelse?",
   },
-  { "on primary interface",
-    "auf dem primären Interface",
-    "na primarni napravi",
-    "su interfaccia primaria",
-    "op eerste interface",
-    "no interface primário",
-    "sur la carte primaire",
-    "på første enhet",
-    "ensisijaisella sovittimella",
-    "na pierwszym interfejsie",
-    "en interface primario",
-    "óôÞí êýñéá êÜñôá",
-    "från den första enheten?",
-    "pe prima interfaþã",
-    "az elsö kártyán",
-    "a la interfície primària",
-    "ÝÐ ÞáÝÞÒÝÞÜ ãáâàÞÙáâÒÕ",
-    "na primarnom ureðaju",
-    "peavastuvõtjal",
-    "på primær enhed",
-  },
   { "Cancel editing?",
     "Schneiden abbrechen?",
     "®elite prekiniti urejanje?",
@@ -1337,7 +1295,7 @@
     "Lähde",
     "Zrodlo",
     "",//TODO
-    "Äéåñåýíçóç",
+    "ÐçãÞ",
     "Källa",
     "Sursã",
     "Forrás",
@@ -1536,6 +1494,48 @@
     "CA",
     "CA",
   },
+  { "Free To Air",
+    "frei empfangbar",
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+  },
+  { "encrypted",
+    "verschlüsselt",
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+  },
   { "Sid",
     "Sid",
     "Sid",
@@ -1568,7 +1568,7 @@
     "Inversio",
     "Inversion",
     "Inversion",
-    "Inversion",
+    "ÁíôéóôñïöÞ",
     "Inversion",
     "Inversiune",
     "Inversion",
@@ -1589,7 +1589,7 @@
     "Kaistanleveys",
     "Szerokosc pasma",
     "Bandwidth",
-    "Bandwidth",
+    "Åýñïò Óõ÷íïôÞôùí",
     "Bandbredd",
     "Lãrgime de bandã",
     "Bandwidth",
@@ -1610,7 +1610,7 @@
     "Suojaustaso (HP)",
     "CoderateH",
     "CoderateH",
-    "CoderateH",
+    "Ñõèìüò Êþäéêá H",
     "CoderateH",
     "CoderateH",
     "CoderateH",
@@ -1631,7 +1631,7 @@
     "Suojaustaso (LP)",
     "CoderateL",
     "CoderateL",
-    "CoderateL",
+    "Ñõèìüò Êþäéêá L",
     "CoderateL",
     "CoderateL",
     "CoderateL",
@@ -1652,7 +1652,7 @@
     "Modulaatio",
     "Modulacja",
     "Modulation",
-    "Modulation",
+    "Äéáìüñöùóç",
     "Modulation",
     "Modulaþie",
     "Modulation",
@@ -1673,7 +1673,7 @@
     "Transmissio",
     "Transmisja",
     "Transmission",
-    "Transmission",
+    "ÌåôÜäïóç",
     "Transmission",
     "Transmisiune",
     "Transmission",
@@ -1694,7 +1694,7 @@
     "Suojaväli",
     "Guard",
     "Guard",
-    "Guard",
+    "Ðñïóôáóßá",
     "Guard",
     "Guard",
     "Guard",
@@ -1948,7 +1948,7 @@
     "Kanava on ajastimen käytössä!",
     "Kanal jest zajety przez timer nagran",
     "¡Canal está ocupado por un timer!",
-    "Ôï êáíÜëç ÷ñéóéìïðïéåßôáé áðü ÷ñïíïäéáêüðôç!",
+    "Ôï êáíÜëç ÷ñéóéìïðïéåßôáé áðü ÷ñïíïðñïãñáììáôéóìü!",
     "Kanalen används av en timer!",
     "Canalul este utilizat de un timer!",
     "Csatornát más használja!",
@@ -2053,7 +2053,7 @@
     "*** Virheellinen kanavavalinta! ***",
     "*** Niewazny kanal ***",
     "*** Canal inválido ***",
-    "*** ¢êõñï êáíÜëç ***",
+    "*** Áêõñï êáíÜëç ***",
     "*** Felaktig kanal ***",
     "*** Canal invalid ***",
     "*** Érvénytelen csatorna ***",
@@ -2347,7 +2347,7 @@
     "CA-moduulin palautus alkutilaan epäonnistui!",
     "Nieudany CAM-Reset!",
     "¡No puedo reiniciar la CAM!",
-    "Áäýíáôï íá ãßíåé åðáíáöïñÜ óôü CAM",
+    "Áäýíáôç ç åðáíáöïñÜ óôü CAM",
     "Kan inte återställa CAM!",
     "Nu pot reseta CAM",
     "A CAM-Reset nem sikerült",
@@ -2368,7 +2368,7 @@
     "CA-moduuli palautettu alkutilaan",
     "CAM-Reset wykonany",
     "CAM reiniciada",
-    "Óôï CAM Ýãéíå åðáíáöïñÜ",
+    "¸ãéíå åðáíáöïñÜ óôï CAM",
     "CA modulen har återställts",
     "CAM-ul a fost resetat",
     "A CAM vissza lett állítva",
@@ -2410,7 +2410,7 @@
     "Äänen kieli ei ole valittavissa!",
     "",//TODO
     "",//TODO
-    "ÁíåðÜñêåéá Þ÷ïõ",
+    "Ìç äéáèÝóéìïò Þ÷ïò",
     "Ljud saknas!"
     "Lipseºte sunetul!",
     "",//TODO
@@ -2453,7 +2453,7 @@
     "Ohjelmaopas",
     "EPG",
     "Guía de Programación",
-    "Ïäçãüò ðñïãñÜììáôïò",
+    "Çëåêôñïíéêüò ïäçãüò ðñïãñÜììáôïò",
     "EPG",
     "EPG",
     "EPG",
@@ -2558,7 +2558,7 @@
     "Toisto",
     "Odtwarzanie",
     "Opciones de Reproducción",
-    "ÁíáìåôÜäïóç",
+    "ÁíáðáñáãùãÞ",
     "Uppspelning",
     "Redare",
     "Lejátszás",
@@ -2600,7 +2600,7 @@
     "Laajennokset",
     "Plugins",
     "Plugins",
-    "Âßóìáôá",
+    "ÅðåêôÜóåéò",
     "Moduler",
     "Plugin-uri",
     "Plugins",
@@ -2621,7 +2621,7 @@
     "Laajennos",
     "Plugin",
     "Plugin",
-    "Âßóìá",
+    "ÅðÝêôáóç",
     "Modul",
     "Plugin (modul adiþional)",
     "Plugin",
@@ -2968,6 +2968,27 @@
     "Kanaliteate näitamine",
     "Info ved kanalskift",
   },
+  { "Setup.OSD$Timeout requested channel info",
+    "Angeforderte Kanalinfo schließen",
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+    "", // TODO
+  },
   { "Setup.OSD$Scroll pages",
     "Seitenweise scrollen",
     "Drsni meni",
@@ -2979,7 +3000,7 @@
     "Valikoiden vieritys sivuttain",
     "Przesuwac stronami",
     "Desplazar página entera",
-    "Êßëéóç óåëßäáò",
+    "Êýëéóç óåëßäáò",
     "Bläddra sidor",
     "Deruleazã pagini",
     "Oldalanként léptetmi",
@@ -3000,7 +3021,7 @@
     "Valikoiden vieritys ympäri",
     "",// TODO
     "",// TODO
-    "Êßëéóç ãýñù-ãýñù",
+    "Êýëéóç ãýñù-ãýñù",
     "Rulla texten",
     "Derulare circularã",
     "",// TODO
@@ -3021,7 +3042,7 @@
     "Järjestä ajastimet",
     "Sortowanie timerow",
     "Ordenar timers",
-    "ÏñãÜíùóç ðñïãñáìáôéóìÝíùí",
+    "ÏñãÜíùóç ÷ñïíïðñïãñáììáôéóìþí",
     "Sortera timers",
     "Sortare timer-e",
     "A beprogramozott felvételek elrendezése",
@@ -3063,7 +3084,7 @@
     "Ohjelmaoppaan taustapäivitys (h)",
     "Czas do skanu EPG (h)",
     "Tiempo hasta exploración EPG (h)",
-    "×ñüíïò ìÝ÷ñé åîÝôáóç EPG óå þñåò",
+    "×ñüíïò äéÜñêåéáò åîÝôáóçò EPG óå þñåò",
     "EPG sökning timeout",
     "Interval achiziþie EPG (h)",
     "Fennmaradt idö az EPG-g (h)",
@@ -3084,7 +3105,7 @@
     "Ohjelmaoppaan korjaustaso",
     "Poziom bledow EPG",
     "Nivel para arreglar EPG",
-    "Âáèìüò äéüñèïóçò ïäçãïý EPG",
+    "Âáèìüò äéüñèùóçò ïäçãïý EPG",
     "Nivå för EPG bugfix",
     "Nivel corecþie EPG",
     "EPG hibaelhárítás",
@@ -3105,7 +3126,7 @@
     "Vanha tieto näkyy (min)",
     "",// TODO
     "",// TODO
-    "¸íäåéêóç îåðåñáóìÝíïí ðëçñïöïñéþí (ëåðôÜ)",
+    "¸íäåéîç îåðåñáóìÝíùí ðëçñïöïñéþí (ëåðôÜ)",
     "Visa gammal information (min)",
     "Date EPG expirate cel mult (min)",
     "",// TODO
@@ -3168,7 +3189,7 @@
     "Suosikkikielet",
     "",// TODO
     "",// TODO
-    "Ðñïôéíüìåíåò ãëþóåò",
+    "Ðñïôåéíüìåíåò ãëþóóåò",
     "Önskade språk",
     "Limbi preferate",
     "",// TODO
@@ -3189,7 +3210,7 @@
     "Suosikkikieli",
     "",// TODO
     "",// TODO
-    "Ðñïôéíüìåíç ãëþóá",
+    "Ðñïôåéíüìåíç ãëþóóá",
     "Önskat språk",
     "Limba preferatã",
     "",// TODO
@@ -3231,7 +3252,7 @@
     "Näyttömuoto",
     "",//TODO
     "",//TODO
-    "ÌïñöÞ Ýíäåéêóçò Âßíôåï",
+    "ÌïñöÞ áðåéêüíéóçò Âßíôåï",
     "Format för video display",
     "Formatul redãrii video",
     "",//TODO
@@ -3336,7 +3357,7 @@
     "Käytä Dolby Digital -ääntä",
     "",//TODO
     "",//TODO
-    "×ñçóéìïðïßçóç Þ÷ïõ Dolby Digital",
+    "×ñÞóç Þ÷ïõ Dolby Digital",
     "Använd Dolby Digital",
     "Sunet Dolby Digital",
     "",//TODO
@@ -3357,7 +3378,7 @@
     "Päivitä kanavat",
     "",// TODO
     "",// TODO
-    "áíáíÝïóç êáíáëéþí",
+    "ÅíçìÝñùóç êáíáëéþí",
     "Uppdatera kanaler",
     "Actualizare canale",
     "",// TODO
@@ -3378,7 +3399,7 @@
     "vain nimet",
     "",// TODO
     "",// TODO
-    "ìüíï üíïìá",
+    "ìüíï ïíüìáôá",
     "bara namn",
     "doar numele",
     "",// TODO
@@ -3399,7 +3420,7 @@
     "nimet ja PID:it",
     "",// TODO
     "",// TODO
-    "üíïìá êáß PID",
+    "Ïíüìáôá êáß PIDs",
     "namn och PID",
     "nume si PID-uri",
     "",// TODO
@@ -3420,7 +3441,7 @@
     "uudet kanavat",
     "",// TODO
     "",// TODO
-    "ðñïóèÞêç íÝïí êáíáëéþí",
+    "ðñïóèÞêç íÝùí êáíáëéþí",
     "lägg till nya kanaler",
     "adãugare canale noi",
     "",// TODO
@@ -3441,7 +3462,7 @@
     "uudet transponderit",
     "",// TODO
     "",// TODO
-    "ðñïóèÞêç íÝïí transponder",
+    "ðñïóèÞêç íÝïõ áíáìåôáäüôç",
     "lägg till nya transponders",
     "adãugare transpondere noi",
     "",// TODO
@@ -3462,7 +3483,7 @@
     "Äänen kielet",
     "",//TODO
     "",//TODO
-    "Ãëþóåò Þ÷ïõ",
+    "Ãëþóóåò Þ÷ïõ",
     "Antal ljudspråk",
     "Limbi sunet",
     "",//TODO
@@ -3483,7 +3504,7 @@
     "Äänen kieli",
     "",//TODO
     "",//TODO
-    "Ãëþóá Þ÷ïõ",
+    "Ãëþóóá Þ÷ïõ",
     "Ljudspråk",
     "Limba sunetului",
     "",//TODO
@@ -3609,7 +3630,7 @@
     "Aloitusmarginaali (min)",
     "Poczatkowy czas buforowy (min)",
     "Comenzar grabación antes (min)",
-    "Ðñüóèåôïò ÷ñüíïò ðñßí áñ÷Þ (ëåðôÜ)",
+    "Ðñüóèåôïò ÷ñüíïò óôçí áñ÷Þ (ëåðôÜ)",
     "Marginal för start (min)",
     "Marjã la pornire (min)",
     "Idöeltolódás a kezdésnél (min)",
@@ -3672,7 +3693,7 @@
     "Tallenteen oletusprioriteetti",
     "Priorytet pierwotny",
     "Prioridad predefinida",
-    "Ðñïôåñáéüôçôá",
+    "ÐñïêáèïñéóìÝíç ðñïôåñáéüôçôá",
     "Normal prioritet",
     "Prioritate implicitã",
     "Default priority",
@@ -3798,7 +3819,7 @@
     "VPS-toiminnon aloitusmarginaali (s)",
     "",// TODO
     "",// TODO
-    "Ðåñéèüñéï VPS (ä)",
+    "Ðåñéèþñéï VPS (ä)",
     "VPS marginal (s)",
     "Marjã de timp la utilizare VPS (s)",
     "",// TODO
@@ -4104,26 +4125,48 @@
     "ManTirOnsTorFreLørSøn",
   },
   // The allowed characters in strings:
-  { " abcdefghijklmnopqrstuvwxyz0123456789-.#~",
-    " aäbcdefghijklmnoöpqrstuüvwxyz0123456789-.#~",
-    " abcèdefghijklmnopqrs¹tuvwxyz¾0123456789-.#~",
-    " aàbcdeéèfghiîjklmnopqrstuùvwxyz0123456789-.#~",
-    " abcdefghijklmnopqrstuvwxyz0123456789-.#~áäàïóöòúüù",
-    "",// TODO
-    " aàbcçdeéèêfghiîjklmnoôpqrstuùûvwxyz0123456789-.#~",
-    "",// TODO
-    " abcdefghijklmnopqrstuvwxyzåäö0123456789-.#~",
-    " abcdefghijklmnopqrstuvwxyz0123456789-.#~",
-    " aábcdeéfghiíjklmnñoópqrstuúvwxyz0123456789-.#~",
-    " áÜâãäåÝæçÞèéßêëìíîïüðñóòôõýö÷øùþ0123456789-.#~abcdefghijklmnopqrstuvwxyz",
-    " abcdefghijklmnopqrstuvwxyzåäö0123456789-.#~",
-    " aãâbcdefghiîjklmnopqrsºtþuvwxyz0123456789-.#~",
-    " aábcdeéfghiíjklmnoóöpqrstuúüvwxyz0123456789-.,#~",
-    " aàbcçdeéèfghiíjklmnoòpqrstuúvwxyz0123456789-.,#~_·",
-    " abcdefghijklmnopqrstuvwxyzÐÑÒÓÔÕñÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìîï0123456789-.#~",
-    " abcèædðefghijklmnopqrs¹tuvwxyz¾0123456789-.#~", // hrv
-    " abcdefghijklmnopqrsðzþtuvwõäöüxyå0123456789-.#~",
-    " abcdefghijklmnopqrstuvwxyzæøå0123456789-.#~",
+  { " abcdefghijklmnopqrstuvwxyz0123456789-.#~,/_@",
+    " aäbcdefghijklmnoöpqrstuüvwxyz0123456789-.#~,/_@",
+    " abcèdefghijklmnopqrs¹tuvwxyz¾0123456789-.#~,/_@",
+    " aàbcdeéèfghiîjklmnopqrstuùvwxyz0123456789-.#~,/_@",
+    " abcdefghijklmnopqrstuvwxyz0123456789-.#~,/_@áäàïóöòúüù",
+    "",// TODO
+    " aàbcçdeéèêfghiîjklmnoôpqrstuùûvwxyz0123456789-.#~,/_@",
+    "",// TODO
+    " abcdefghijklmnopqrstuvwxyzåäö0123456789-.#~,/_@",
+    " abcdefghijklmnopqrstuvwxyz0123456789-.#~,/_@",
+    " aábcdeéfghiíjklmnñoópqrstuúvwxyz0123456789-.#~,/_@",
+    " áÜâãäåÝæçÞèéßêëìíîïüðñóòôõýö÷øùþ0123456789-.#~,/_@abcdefghijklmnopqrstuvwxyz",
+    " abcdefghijklmnopqrstuvwxyzåäö0123456789-.#~,/_@",
+    " aãâbcdefghiîjklmnopqrsºtþuvwxyz0123456789-.#~,/_@",
+    " aábcdeéfghiíjklmnoóöpqrstuúüvwxyz0123456789-.,#~,/_@",
+    " aàbcçdeéèfghiíjklmnoòpqrstuúvwxyz0123456789-.,#~,/_@·",
+    " abcdefghijklmnopqrstuvwxyzÐÑÒÓÔÕñÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìîï0123456789-.#~,/_@",
+    " abcèædðefghijklmnopqrs¹tuvwxyz¾0123456789-.#~,/_@", // hrv
+    " abcdefghijklmnopqrsðzþtuvwõäöüxyå0123456789-.#~,/_@",
+    " abcdefghijklmnopqrstuvwxyzæøå0123456789-.#~,/_@",
+  },
+  // The character maps for entering letters via the numeric keys:
+  { " 0\t-.#~,/_@1\tabc2\tdef3\tghi4\tjkl5\tmno6\tpqrs7\ttuv8\twxyz9",
+    " 0\t-.#~,/_@1\tabcä2\tdef3\tghi4\tjkl5\tmnoö6\tpqrs7\ttuvü8\twxyz9",
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
   },
   // Learning keys:
   { "Learning Remote Control Keys",
@@ -4221,7 +4264,7 @@
     "Älä paina kaukosäätimen näppäimiä...",
     "Nie naciskac klawiszy...",
     "No pulse ninguna tecla...",
-    "ÌÞí ðéÝæåéò ðëÞêôñá...",
+    "ÌÞí ðáôÜò ðëÞêôñá...",
     "Tryck inte på någon knapp...",
     "Nu apãsaþi nici o tastã...",
     "Ne nyomjon meg gombot...",
@@ -4368,7 +4411,7 @@
     "näppäimen puuttuessa paina 'Valikko'",
     "(Nacisnac 'Menu' by ominac klawisz)",
     "Pulse 'Menu' para saltarse esta tecla",
-    "ÐÜôá 'ìåíïý' ãéá ðñïóðÝñáóç áõôïý ôïý ðëÞêôñïõ",
+    "Ðßåóå 'ìåíïý' ãéá ðñïóðÝñáóç áõôïý ôïý ðëÞêôñïõ",
     "(Tryck 'Meny' för att hoppa över knappen.)",
     "Apãsaþi 'Meniu' pentru a sãri peste aceastã tastã",
     "A Menü gombot nyomni ennek a gombnak a kihagyásához",
@@ -4389,7 +4432,7 @@
     "Vaihe 3: Näppäinkoodien tallentaminen",
     "Faza 3: Zapamietac Kod",
     "Fase 3: Guardar códigos de teclas",
-    "Öáóç 3: Áðïèßêåõóç êùäéêþí",
+    "Öáóç 3: ÁðïèÞêåõóç êùäéêþí",
     "Fas 3: Spara knappkoder",
     "Faza 3: Salvarea codurilor de taste",
     "Harmadik lépés: kód mentése",
@@ -4410,7 +4453,7 @@
     "Paina 'Ylös' tallentaaksesi ja 'Alas' peruuttaaksesi",
     "'Gora' zapamietuje, 'Dol' przerywa",
     "Pulse 'Arriba' para guardar, 'Abajo' para anular",
-    "'ÐÜíù' áðïèßêåõóç, 'ÊÜôù' áêýñùóç",
+    "Ðßåóå 'ÐÜíù' ãéá áðïèÞêåõóç, 'ÊÜôù' ãéá áêýñùóç",
     "Tryck 'Upp' för att spara eller 'Ner' för att avsluta.",
     "Apãsaþi 'Sus' pentru salvare, 'Jos' pentru anulare",
     "'Fel' mentés, 'Le´ mégse",
@@ -4421,7 +4464,7 @@
     "Tryk 'Op' for at gemme, 'Ned' for at annullere",
   },
   // Key names:
-  { "Up",
+  { "Key$Up",
     "Auf",
     "Gor",
     "Su",
@@ -4442,7 +4485,7 @@
     "Üles",
     "Op",
   },
-  { "Down",
+  { "Key$Down",
     "Ab",
     "Dol",
     "Giù",
@@ -4463,7 +4506,7 @@
     "Alla",
     "Ned",
   },
-  { "Menu",
+  { "Key$Menu",
     "Menü",
     "Meni",
     "Menù",
@@ -4484,7 +4527,7 @@
     "Menüü",
     "Menu",
   },
-  { "Ok",
+  { "Key$Ok",
     "Ok",
     "Ok",
     "Ok",
@@ -4505,7 +4548,7 @@
     "Ok",
     "Ok",
   },
-  { "Back",
+  { "Key$Back",
     "Zurück",
     "Nazaj",
     "Indietro",
@@ -4526,7 +4569,7 @@
     "Tagasi",
     "Tilbage",
   },
-  { "Left",
+  { "Key$Left",
     "Links",
     "Levo",
     "Sinistra",
@@ -4547,7 +4590,7 @@
     "Vasakule",
     "Venstre",
   },
-  { "Right",
+  { "Key$Right",
     "Rechts",
     "Desno",
     "Destra",
@@ -4568,7 +4611,7 @@
     "Paremale",
     "Højre",
   },
-  { "Red",
+  { "Key$Red",
     "Rot",
     "Rdeèa",
     "Rosso",
@@ -4589,7 +4632,7 @@
     "Punane",
     "Rød",
   },
-  { "Green",
+  { "Key$Green",
     "Grün",
     "Zelena",
     "Verde",
@@ -4610,7 +4653,7 @@
     "Roheline",
     "Grøn",
   },
-  { "Yellow",
+  { "Key$Yellow",
     "Gelb",
     "Rumena",
     "Giallo",
@@ -4631,7 +4674,7 @@
     "Kollane",
     "Gul",
   },
-  { "Blue",
+  { "Key$Blue",
     "Blau",
     "Modra",
     "Blu",
@@ -4652,7 +4695,28 @@
     "Sinine",
     "Blå",
   },
-  { "Play",
+  { "Key$Info",
+    "Info",
+    "Info",
+    "",//TODO
+    "Info",
+    "",//TODO
+    "Info",
+    "",//TODO
+    "Tiedot",
+    "",//TODO
+    "",//TODO
+    "Ðëçñïöïñßåò",
+    "Info",
+    "Info",
+    "",//TODO
+    "",//TODO
+    "¸ÝäÞ",
+    "Info",
+    "Info",
+    "Info",
+  },
+  { "Key$Play",
     "Wiedergabe",
     "Predvajaj",
     "Riproduci",
@@ -4663,7 +4727,7 @@
     "Toista",
     "Odtworzenie",
     "Reproducir",
-    "AíáìåôÜäïóç",
+    "ÁíáðáñáãùãÞ",
     "Spela upp",
     "Redare",
     "Lejátszás",
@@ -4673,7 +4737,7 @@
     "Start",
     "Afspil",
   },
-  { "Pause",
+  { "Key$Pause",
     "Pause",
     "Pavza",
     "Pausa",
@@ -4684,7 +4748,7 @@
     "Tauko",
     "Przerwa",
     "Pausa",
-    "ÄéÜëåéììá",
+    "Ðáýóç",
     "Pausa",
     "Pauzã",
     "Szünet",
@@ -4694,7 +4758,7 @@
     "Paus",
     "Pause",
   },
-  { "Stop",
+  { "Key$Stop",
     "Stop",
     "Ustavi",
     "Stop",
@@ -4715,7 +4779,7 @@
     "Stopp",
     "Stop",
   },
-  { "Record",
+  { "Key$Record",
     "Aufnehmen",
     "Snemaj",
     "Registra",
@@ -4736,7 +4800,7 @@
     "Salvestamine",
     "Optag",
   },
-  { "FastFwd",
+  { "Key$FastFwd",
     "Vorlauf",
     "Hitro naprej",
     "Avanti Veloce",
@@ -4747,7 +4811,7 @@
     "Pikakelaus >>",
     "Naprzod",
     "Adelante rápido",
-    "Ðñïüèéóç åìðñüò",
+    "Ðñïþèçóç åìðñüò",
     "Snabbspolning framåt",
     "Derulare înainte",
     "Elöre pörgetni",
@@ -4757,7 +4821,7 @@
     "Edasikerimine",
     "Spol fremad",
   },
-  { "FastRew",
+  { "Key$FastRew",
     "Rücklauf",
     "Hitro nazaj",
     "Indietro Veloce",
@@ -4768,7 +4832,7 @@
     "Pikakelaus <<",
     "Wstecz",
     "Atrás rápido",
-    "Ðñïüèéóç ðßóù",
+    "Ðñïþèçóç ðßóù",
     "Snabbspolning bakåt",
     "Derulare înapoi",
     "Vissza pörgetni",
@@ -4778,7 +4842,7 @@
     "Tagasikerimine",
     "Spol tilbage",
   },
-  { "Power",
+  { "Key$Power",
     "Ausschalten",
     "Izklop",
     "Power",
@@ -4799,7 +4863,7 @@
     "Toide",
     "Sluk",
   },
-  { "Channel+",
+  { "Key$Channel+",
     "Kanal+",
     "Program+",
     "Canale +",
@@ -4820,7 +4884,7 @@
     "Kanal+",
     "Kanal+",
   },
-  { "Channel-",
+  { "Key$Channel-",
     "Kanal-",
     "Program-",
     "Canale -",
@@ -4831,7 +4895,7 @@
     "Kanava -",
     "Kanal-",
     "Canal -",
-    "ÊáíÜëç-",
+    "ÊáíÜëé-",
     "Kanal-",
     "Canal-",
     "Csatorna-",
@@ -4841,7 +4905,7 @@
     "Kanal-",
     "Kanal-",
   },
-  { "Volume+",
+  { "Key$Volume+",
     "Lautstärke+",
     "Glasnost+",
     "Volume +",
@@ -4862,7 +4926,7 @@
     "Helitugevus+",
     "Lydstyrke+",
   },
-  { "Volume-",
+  { "Key$Volume-",
     "Lautstärke-",
     "Glasnost-",
     "Volume -",
@@ -4883,7 +4947,7 @@
     "Helitugevus-",
     "Lydstyrke-",
   },
-  { "Mute",
+  { "Key$Mute",
     "Stumm",
     "Izklop zvoka",
     "Mute",
@@ -4904,7 +4968,7 @@
     "Hääletu",
     "Sluk lyd",
   },
-  { "Audio",
+  { "Key$Audio",
     "Audio",
     "Zvok",
     "",// TODO
@@ -5105,7 +5169,7 @@
     "vapaana",
     "pozostalo",
     "libre",
-    "Êåíü",
+    "Åëåýèåñïò",
     "ledigt",
     "liber",
     "szabad",
@@ -5168,7 +5232,7 @@
     " Lopeta toisto",
     " Zatrzymac odtwarzanie",
     " Parar reprodución",
-    " ÔÝëïò áíáìåôÜäïóçò",
+    " ÔÝëïò áíáðáñáãùãÞò",
     " Avsluta uppspelning",
     " Opreºte redarea",
     " Lejátszást befejzni",
@@ -5273,7 +5337,7 @@
     "Muokkaus aloitettu",
     "Uruchomiony proces montazu",
     "Proceso modificación iniciado",
-    "Áñ÷Þ åðåîåñãáóßáò",
+    "Áñ÷éóå ç åðåîåñãáóßá",
     "Redigeringen startar",
     "Montajul înregistrãrii a început",
     "Vágás elindítva",
@@ -5325,26 +5389,26 @@
     "Redigeerimine ebaõnnestus",
     "Redigeringsproces fejlede!",
   },
-  { "scanning recordings...",
-    "Aufzeichnungen werden durchsucht...",
-    "iskanje posnetkov...",
-    "scansione registrazioni...",
-    "Doorzoeken opnames...",
-    "A pesquisar gravações...",
-    "Recherche des enregistrements...",
-    "Går igjennom opptakene...",
-    "haetaan tallenteita...",
-    "Skan nagran...",
-    "buscando grabaciones...",
-    "ÅîÝôáóç åããñáöþí...",
-    "Söker igenom inspelningarna...",
-    "Caut înregistrãri...",
-    "Felvett adások böngészése...",
-    "cercant gravacions...",
-    "ÁÚÐÝØàÞÒÐÝØÕ ×ÐßØáÕÙ...",
-    "pretra¾ivanje snimljenog...",
-    "salvestuste skaneerimine...",
-    "skanner optagelser...",
+  { "Recording started",
+    "Aufzeichnung gestartet",
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
   },
   { "Pausing live video...",
     "Live-Signal wird angehalten...",
@@ -5357,7 +5421,7 @@
     "Pysäytetään lähetys...",
     "Zatrzymany program biezacy...",
     "Emisión en directo parada...",
-    "ÄéÜëåéììá æùíôáíïý óÞìáôïò",
+    "ÐÜãùìá æùíôáíïý óÞìáôïò",
     "Pausar direktinspelningen",
     "Trec în pauzã emisiunea transmisã...",
     "Az élö adás megállítva...",
@@ -5399,7 +5463,7 @@
     "Tällä laajennoksella ei ole asetuksia!",
     "Ten plugin niema parametrow!",
     "Este plugin no admite configuración",
-    "Áõôü ôï âßóìá äåí Ý÷åé ðáñÜìåôñïõò",
+    "ÁõôÞ ç åðÝêôáóç äåí Ý÷åé ðáñÜìåôñïõò!",
     "Den här modulen har inga parametrar",
     "Acest plugin nu se configureazã!",
     "Ennek a plugin-nak nincs setup-parametere!",
@@ -5462,7 +5526,7 @@
     "Ei esitystä",
     "",// TODO
     "",// TODO
-    "Ï÷é ôßôëï",
+    "×ùñßò Ôßôëï",
     "ingen titel",
     "Fãrã titlu",
     "",// TODO
diff -ruN vdr-1.3.37/interface.c vdr-1.3.38/interface.c
--- vdr-1.3.37/interface.c	2005-11-27 16:31:06.000000000 +0100
+++ vdr-1.3.38/interface.c	2006-01-04 16:44:19.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: interface.c 1.70 2005/11/27 15:31:06 kls Exp $
+ * $Id: interface.c 1.71 2006/01/04 15:44:19 kls Exp $
  */
 
 #include "interface.h"
@@ -84,7 +84,9 @@
      eKeys NewKey = kUp;
      while (NewKey != kNone) {
            char *Prompt;
-           asprintf(&Prompt, tr("Press key for '%s'"), tr(cKey::ToString(NewKey)));
+           char buf[32];
+           snprintf(buf, sizeof(buf), "Key$%s", cKey::ToString(NewKey));
+           asprintf(&Prompt, tr("Press key for '%s'"), tr(buf));
            DisplayMenu->SetItem(Prompt, 4, false, false);
            free(Prompt);
            cRemote::Clear();
diff -ruN vdr-1.3.37/keymacros.conf vdr-1.3.38/keymacros.conf
--- vdr-1.3.37/keymacros.conf	2002-12-01 11:46:47.000000000 +0100
+++ vdr-1.3.38/keymacros.conf	2006-01-05 16:41:00.000000000 +0100
@@ -9,4 +9,5 @@
 
 Red       Recordings
 Green     Schedule
+Yellow    Info
 Blue      Timers
diff -ruN vdr-1.3.37/keys.c vdr-1.3.38/keys.c
--- vdr-1.3.37/keys.c	2005-09-17 13:27:40.000000000 +0200
+++ vdr-1.3.38/keys.c	2006-01-05 16:39:26.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: keys.c 1.9 2005/09/17 11:27:40 kls Exp $
+ * $Id: keys.c 1.10 2006/01/05 15:39:26 kls Exp $
  */
 
 #include "keys.h"
@@ -32,6 +32,7 @@
                     { k7,             "7"          },
                     { k8,             "8"          },
                     { k9,             "9"          },
+                    { kInfo,          "Info"       },
                     { kPlay,          "Play"       },
                     { kPause,         "Pause"      },
                     { kStop,          "Stop"       },
diff -ruN vdr-1.3.37/keys.h vdr-1.3.38/keys.h
--- vdr-1.3.37/keys.h	2004-12-27 12:10:59.000000000 +0100
+++ vdr-1.3.38/keys.h	2006-01-05 16:39:06.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: keys.h 1.6 2004/12/27 11:10:59 kls Exp $
+ * $Id: keys.h 1.7 2006/01/05 15:39:06 kls Exp $
  */
 
 #ifndef __KEYS_H
@@ -26,6 +26,7 @@
              kYellow,
              kBlue,
              k0, k1, k2, k3, k4, k5, k6, k7, k8, k9,
+             kInfo,
              kPlay,
              kPause,
              kStop,
diff -ruN vdr-1.3.37/menu.c vdr-1.3.38/menu.c
--- vdr-1.3.37/menu.c	2005-11-05 18:29:22.000000000 +0100
+++ vdr-1.3.38/menu.c	2006-01-08 12:39:57.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menu.c 1.376 2005/11/05 17:29:22 kls Exp $
+ * $Id: menu.c 1.390 2006/01/08 11:39:57 kls Exp $
  */
 
 #include "menu.h"
@@ -31,6 +31,7 @@
 
 #define MAXWAIT4EPGINFO   3 // seconds
 #define MODETIMEOUT       3 // seconds
+#define DISKSPACECHEK     5 // seconds between disk space checks in the main menu
 
 #define MAXRECORDCONTROLS (MAXDEVICES * MAXRECEIVERS)
 #define MAXINSTANTRECTIME (24 * 60 - 1) // 23:59 hours
@@ -41,28 +42,25 @@
 // --- cMenuEditCaItem -------------------------------------------------------
 
 class cMenuEditCaItem : public cMenuEditIntItem {
-private:
-  const cCaDefinition *ca;
-  bool allowCardNr;
 protected:
   virtual void Set(void);
 public:
-  cMenuEditCaItem(const char *Name, int *Value, bool AllowCardNr = false);
+  cMenuEditCaItem(const char *Name, int *Value);
   eOSState ProcessKey(eKeys Key);
   };
 
-cMenuEditCaItem::cMenuEditCaItem(const char *Name, int *Value, bool AllowCardNr)
+cMenuEditCaItem::cMenuEditCaItem(const char *Name, int *Value)
 :cMenuEditIntItem(Name, Value, 0)
 {
-  ca = CaDefinitions.Get(*Value);
-  allowCardNr = AllowCardNr;
   Set();
 }
 
 void cMenuEditCaItem::Set(void)
 {
-  if (ca)
-     SetValue(ca->Description());
+  if (*value == CA_FTA)
+     SetValue(tr("Free To Air"));
+  else if (*value >= CA_ENCRYPTED_MIN)
+     SetValue(tr("encrypted"));
   else
      cMenuEditIntItem::Set();
 }
@@ -72,18 +70,8 @@
   eOSState state = cMenuEditItem::ProcessKey(Key);
 
   if (state == osUnknown) {
-     if (NORMALKEY(Key) == kLeft) { // TODO might want to increase the delta if repeated quickly?
-        if (ca && ca->Prev()) {
-           ca = (cCaDefinition *)ca->Prev();
-           *value = ca->Number();
-           }
-        }
-     else if (NORMALKEY(Key) == kRight) {
-        if (ca && ca->Next() && (allowCardNr || ((cCaDefinition *)ca->Next())->Number() > MAXDEVICES)) {
-           ca = (cCaDefinition *)ca->Next();
-           *value = ca->Number();
-           }
-        }
+     if (NORMALKEY(Key) == kLeft && *value >= CA_ENCRYPTED_MIN)
+        *value = CA_FTA;
      else
         return cMenuEditIntItem::ProcessKey(Key);
      Set();
@@ -265,7 +253,7 @@
   Add(new cMenuEditIntItem( tr("Dpid1"),        &data.dpids[0], 0, 0x1FFF));
   Add(new cMenuEditIntItem( tr("Dpid2"),        &data.dpids[1], 0, 0x1FFF));
   Add(new cMenuEditIntItem( tr("Tpid"),         &data.tpid,  0, 0x1FFF));
-  Add(new cMenuEditCaItem(  tr("CA"),           &data.caids[0], true));//XXX
+  Add(new cMenuEditCaItem(  tr("CA"),           &data.caids[0]));
   Add(new cMenuEditIntItem( tr("Sid"),          &data.sid, 1, 0xFFFF));
   /* XXX not yet used
   Add(new cMenuEditIntItem( tr("Nid"),          &data.nid, 0));
@@ -380,12 +368,17 @@
 
 // --- cMenuChannels ---------------------------------------------------------
 
+#define CHANNELNUMBERTIMEOUT 1000 //ms
+
 class cMenuChannels : public cOsdMenu {
 private:
+  int number;
+  cTimeMs numberTimer;
   void Setup(void);
   cChannel *GetChannel(int Index);
   void Propagate(void);
 protected:
+  eOSState Number(eKeys Key);
   eOSState Switch(void);
   eOSState Edit(void);
   eOSState New(void);
@@ -400,6 +393,7 @@
 cMenuChannels::cMenuChannels(void)
 :cOsdMenu(tr("Channels"), CHNUMWIDTH)
 {
+  number = 0;
   Setup();
   Channels.IncBeingEdited();
 }
@@ -427,7 +421,7 @@
   if (cMenuChannelItem::SortMode() != cMenuChannelItem::csmNumber)
      Sort();
   SetCurrent(currentItem);
-  SetHelp(tr("Edit"), tr("New"), tr("Delete"), cMenuChannelItem::SortMode() == cMenuChannelItem::csmNumber ? tr("Mark") : NULL);
+  SetHelp(tr("Button$Edit"), tr("Button$New"), tr("Button$Delete"), tr("Button$Mark"));
   Display();
 }
 
@@ -446,6 +440,30 @@
   Channels.SetModified(true);
 }
 
+eOSState cMenuChannels::Number(eKeys Key)
+{
+  if (HasSubMenu())
+     return osContinue;
+  if (numberTimer.TimedOut())
+     number = 0;
+  if (!number && Key == k0) {
+     cMenuChannelItem::IncSortMode();
+     Setup();
+     }
+  else {
+     number = number * 10 + Key - k0;
+     for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) {
+         if (!ci->Channel()->GroupSep() && ci->Channel()->Number() == number) {
+            SetCurrent(ci);
+            Display();
+            break;
+            }
+         }
+     numberTimer.Set(CHANNELNUMBERTIMEOUT);
+     }
+  return osContinue;
+}
+
 eOSState cMenuChannels::Switch(void)
 {
   if (HasSubMenu())
@@ -530,14 +548,13 @@
     default:
          if (state == osUnknown) {
             switch (Key) {
-              case k0:      cMenuChannelItem::IncSortMode();
-                            Setup();
-                            break;
+              case k0 ... k9:
+                            return Number(Key);
               case kOk:     return Switch();
               case kRed:    return Edit();
               case kGreen:  return New();
               case kYellow: return Delete();
-              case kBlue:   if (!HasSubMenu() && cMenuChannelItem::SortMode() == cMenuChannelItem::csmNumber)
+              case kBlue:   if (!HasSubMenu())
                                Mark();
                             break;
               default: break;
@@ -771,7 +788,7 @@
       Add(new cMenuTimerItem(timer));
   if (Setup.SortTimers)
      Sort();
-  SetHelp(tr("Edit"), tr("New"), tr("Delete"), Setup.SortTimers ? tr("On/Off") : tr("Mark"));
+  SetHelp(tr("Button$Edit"), tr("Button$New"), tr("Button$Delete"), Setup.SortTimers ? tr("Button$On/Off") : tr("Button$Mark"));
   Timers.IncBeingEdited();
 }
 
@@ -897,7 +914,9 @@
      cChannel *channel = Channels.GetByChannelID(event->ChannelID(), true);
      if (channel) {
         SetTitle(channel->Name());
-        SetHelp(tr("Record"), NULL, NULL, CanSwitch ? tr("Switch") : NULL);
+        int TimerMatch = tmNone;
+        Timers.GetMatch(event, &TimerMatch);
+        SetHelp(TimerMatch == tmFull ? tr("Timer") : tr("Button$Record"), NULL, NULL, CanSwitch ? tr("Button$Switch") : NULL);
         }
      }
 }
@@ -939,36 +958,59 @@
   return state;
 }
 
-// --- cMenuWhatsOnItem ------------------------------------------------------
+// --- cMenuScheduleItem -----------------------------------------------------
 
-class cMenuWhatsOnItem : public cOsdItem {
+class cMenuScheduleItem : public cOsdItem {
 public:
   const cEvent *event;
   const cChannel *channel;
-  cMenuWhatsOnItem(const cEvent *Event, cChannel *Channel);
+  int timerMatch;
+  cMenuScheduleItem(const cEvent *Event, cChannel *Channel = NULL);
+  bool Update(bool Force = false);
 };
 
-cMenuWhatsOnItem::cMenuWhatsOnItem(const cEvent *Event, cChannel *Channel)
+cMenuScheduleItem::cMenuScheduleItem(const cEvent *Event, cChannel *Channel)
 {
   event = Event;
   channel = Channel;
-  char *buffer = NULL;
-  int TimerMatch;
-  char t = Timers.GetMatch(Event, &TimerMatch) ? (TimerMatch == tmFull) ? 'T' : 't' : ' ';
-  char v = event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' ';
-  char r = event->IsRunning() ? '*' : ' ';
-  asprintf(&buffer, "%d\t%.*s\t%s\t%c%c%c\t%s", channel->Number(), 6, channel->ShortName(true), *event->GetTimeString(), t, v, r, event->Title());
-  SetText(buffer, false);
+  timerMatch = tmNone;
+  Update(true);
+}
+
+static char *TimerMatchChars = " tT";
+
+bool cMenuScheduleItem::Update(bool Force)
+{
+  bool result = false;
+  int OldTimerMatch = timerMatch;
+  Timers.GetMatch(event, &timerMatch);
+  if (Force || timerMatch != OldTimerMatch) {
+     char *buffer = NULL;
+     char t = TimerMatchChars[timerMatch];
+     char v = event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' ';
+     char r = event->IsRunning() ? '*' : ' ';
+     if (channel)
+        asprintf(&buffer, "%d\t%.*s\t%s\t%c%c%c\t%s", channel->Number(), 6, channel->ShortName(true), *event->GetTimeString(), t, v, r, event->Title());
+     else
+        asprintf(&buffer, "%.*s\t%s\t%c%c%c\t%s", 6, *event->GetDateString(), *event->GetTimeString(), t, v, r, event->Title());
+     SetText(buffer, false);
+     result = true;
+     }
+  return result;
 }
 
 // --- cMenuWhatsOn ----------------------------------------------------------
 
 class cMenuWhatsOn : public cOsdMenu {
 private:
+  bool now;
+  int helpKeys;
   eOSState Record(void);
   eOSState Switch(void);
   static int currentChannel;
   static const cEvent *scheduleEvent;
+  bool Update(void);
+  void SetHelpKeys(void);
 public:
   cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr);
   static int CurrentChannel(void) { return currentChannel; }
@@ -983,18 +1025,47 @@
 cMenuWhatsOn::cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr)
 :cOsdMenu(Now ? tr("What's on now?") : tr("What's on next?"), CHNUMWIDTH, 7, 6, 4)
 {
+  now = Now;
+  helpKeys = -1;
   for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
       if (!Channel->GroupSep()) {
          const cSchedule *Schedule = Schedules->GetSchedule(Channel->GetChannelID());
          if (Schedule) {
             const cEvent *Event = Now ? Schedule->GetPresentEvent() : Schedule->GetFollowingEvent();
             if (Event)
-               Add(new cMenuWhatsOnItem(Event, Channel), Channel->Number() == CurrentChannelNr);
+               Add(new cMenuScheduleItem(Event, Channel), Channel->Number() == CurrentChannelNr);
             }
          }
       }
   currentChannel = CurrentChannelNr;
-  SetHelp(Count() ? tr("Record") : NULL, Now ? tr("Next") : tr("Now"), tr("Button$Schedule"), tr("Switch"));
+  SetHelpKeys();
+}
+
+bool cMenuWhatsOn::Update(void)
+{
+  bool result = false;
+  for (cOsdItem *item = First(); item; item = Next(item)) {
+      if (((cMenuScheduleItem *)item)->Update())
+         result = true;
+      }
+  return result;
+}
+
+void cMenuWhatsOn::SetHelpKeys(void)
+{
+  cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
+  int NewHelpKeys = 0;
+  if (item) {
+     if (item->timerMatch == tmFull)
+        NewHelpKeys = 2;
+     else
+        NewHelpKeys = 1;
+     }
+  if (NewHelpKeys != helpKeys) {
+     const char *Red[] = { NULL, tr("Button$Record"), tr("Timer") };
+     SetHelp(Red[NewHelpKeys], now ? tr("Button$Next") : tr("Button$Now"), tr("Button$Schedule"), tr("Button$Switch"));
+     helpKeys = NewHelpKeys;
+     }
 }
 
 const cEvent *cMenuWhatsOn::ScheduleEvent(void)
@@ -1006,7 +1077,7 @@
 
 eOSState cMenuWhatsOn::Switch(void)
 {
-  cMenuWhatsOnItem *item = (cMenuWhatsOnItem *)Get(Current());
+  cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
   if (item) {
      cChannel *channel = Channels.GetByChannelID(item->event->ChannelID(), true);
      if (channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true))
@@ -1018,21 +1089,39 @@
 
 eOSState cMenuWhatsOn::Record(void)
 {
-  cMenuWhatsOnItem *item = (cMenuWhatsOnItem *)Get(Current());
+  cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
   if (item) {
+     if (item->timerMatch == tmFull) {
+        int tm = tmNone;
+        cTimer *timer = Timers.GetMatch(item->event, &tm);
+        if (timer)
+           return AddSubMenu(new cMenuEditTimer(timer));
+        }
      cTimer *timer = new cTimer(item->event);
      cTimer *t = Timers.GetTimer(timer);
      if (t) {
         delete timer;
         timer = t;
+        return AddSubMenu(new cMenuEditTimer(timer));
+        }
+     else {
+        Timers.Add(timer);
+        timer->Matches();
+        Timers.SetModified();
+        isyslog("timer %s added (active)", *timer->ToDescr());
+        if (HasSubMenu())
+           CloseSubMenu();
+        if (Update())
+           Display();
+        SetHelpKeys();
         }
-     return AddSubMenu(new cMenuEditTimer(timer, !t));
      }
   return osContinue;
 }
 
 eOSState cMenuWhatsOn::ProcessKey(eKeys Key)
 {
+  bool HadSubMenu = HasSubMenu();
   eOSState state = cOsdMenu::ProcessKey(Key);
 
   if (state == osUnknown) {
@@ -1042,7 +1131,7 @@
        case kYellow: state = osBack;
                      // continue with kGreen
        case kGreen:  {
-                       cMenuWhatsOnItem *mi = (cMenuWhatsOnItem *)Get(Current());
+                       cMenuScheduleItem *mi = (cMenuScheduleItem *)Get(Current());
                        if (mi) {
                           scheduleEvent = mi->event;
                           currentChannel = mi->channel->Number();
@@ -1051,34 +1140,20 @@
                      break;
        case kBlue:   return Switch();
        case kOk:     if (Count())
-                        return AddSubMenu(new cMenuEvent(((cMenuWhatsOnItem *)Get(Current()))->event, true));
+                        return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->event, true));
                      break;
        default:      break;
        }
      }
+  else if (!HasSubMenu()) {
+     if (HadSubMenu && Update())
+        Display();
+     if (Key != kNone)
+        SetHelpKeys();
+     }
   return state;
 }
 
-// --- cMenuScheduleItem -----------------------------------------------------
-
-class cMenuScheduleItem : public cOsdItem {
-public:
-  const cEvent *event;
-  cMenuScheduleItem(const cEvent *Event);
-};
-
-cMenuScheduleItem::cMenuScheduleItem(const cEvent *Event)
-{
-  event = Event;
-  char *buffer = NULL;
-  int TimerMatch;
-  char t = Timers.GetMatch(Event, &TimerMatch) ? (TimerMatch == tmFull) ? 'T' : 't' : ' ';
-  char v = event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' ';
-  char r = event->IsRunning() ? '*' : ' ';
-  asprintf(&buffer, "%.*s\t%s\t%c%c%c\t%s", 6, *event->GetDateString(), *event->GetTimeString(), t, v, r, event->Title());
-  SetText(buffer, false);
-}
-
 // --- cMenuSchedule ---------------------------------------------------------
 
 class cMenuSchedule : public cOsdMenu {
@@ -1087,9 +1162,12 @@
   const cSchedules *schedules;
   bool now, next;
   int otherChannel;
+  int helpKeys;
   eOSState Record(void);
   eOSState Switch(void);
   void PrepareSchedule(cChannel *Channel);
+  bool Update(void);
+  void SetHelpKeys(void);
 public:
   cMenuSchedule(void);
   virtual ~cMenuSchedule();
@@ -1101,12 +1179,13 @@
 {
   now = next = false;
   otherChannel = 0;
+  helpKeys = -1;
   cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
   if (channel) {
      cMenuWhatsOn::SetCurrentChannel(channel->Number());
      schedules = cSchedules::Schedules(schedulesLock);
      PrepareSchedule(channel);
-     SetHelp(Count() ? tr("Record") : NULL, tr("Now"), tr("Next"));
+     SetHelpKeys();
      }
 }
 
@@ -1135,17 +1214,61 @@
      }
 }
 
+bool cMenuSchedule::Update(void)
+{
+  bool result = false;
+  for (cOsdItem *item = First(); item; item = Next(item)) {
+      if (((cMenuScheduleItem *)item)->Update())
+         result = true;
+      }
+  return result;
+}
+
+void cMenuSchedule::SetHelpKeys(void)
+{
+  cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
+  int NewHelpKeys = 0;
+  if (item) {
+     if (item->timerMatch == tmFull)
+        NewHelpKeys = 2;
+     else
+        NewHelpKeys = 1;
+     }
+  if (NewHelpKeys != helpKeys) {
+     const char *Red[] = { NULL, tr("Button$Record"), tr("Timer") };
+     SetHelp(Red[NewHelpKeys], tr("Button$Now"), tr("Button$Next"));
+     helpKeys = NewHelpKeys;
+     }
+}
+
 eOSState cMenuSchedule::Record(void)
 {
   cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
   if (item) {
+     if (item->timerMatch == tmFull) {
+        int tm = tmNone;
+        cTimer *timer = Timers.GetMatch(item->event, &tm);
+        if (timer)
+           return AddSubMenu(new cMenuEditTimer(timer));
+        }
      cTimer *timer = new cTimer(item->event);
      cTimer *t = Timers.GetTimer(timer);
      if (t) {
         delete timer;
         timer = t;
+        return AddSubMenu(new cMenuEditTimer(timer));
+        }
+     else {
+        Timers.Add(timer);
+        timer->Matches();
+        Timers.SetModified();
+        isyslog("timer %s added (active)", *timer->ToDescr());
+        if (HasSubMenu())
+           CloseSubMenu();
+        if (Update())
+           Display();
+        SetHelpKeys();
         }
-     return AddSubMenu(new cMenuEditTimer(timer, !t));
      }
   return osContinue;
 }
@@ -1162,6 +1285,7 @@
 
 eOSState cMenuSchedule::ProcessKey(eKeys Key)
 {
+  bool HadSubMenu = HasSubMenu();
   eOSState state = cOsdMenu::ProcessKey(Key);
 
   if (state == osUnknown) {
@@ -1204,11 +1328,15 @@
            PrepareSchedule(channel);
            if (channel->Number() != cDevice::CurrentChannel()) {
               otherChannel = channel->Number();
-              SetHelp(Count() ? tr("Record") : NULL, tr("Now"), tr("Next"), tr("Switch"));
+              SetHelp(Count() ? tr("Button$Record") : NULL, tr("Button$Now"), tr("Button$Next"), tr("Button$Switch"));
               }
            Display();
            }
         }
+     else if (HadSubMenu && Update())
+        Display();
+     if (Key != kNone)
+        SetHelpKeys();
      }
   return state;
 }
@@ -1461,7 +1589,7 @@
 {
   recording = Recording;
   if (recording)
-     SetHelp(tr("Play"), tr("Rewind"));
+     SetHelp(tr("Button$Play"), tr("Button$Rewind"));
 }
 
 void cMenuRecording::Display(void)
@@ -1586,9 +1714,9 @@
   if (NewHelpKeys != helpKeys) {
      switch (NewHelpKeys) {
        case 0: SetHelp(NULL); break;
-       case 1: SetHelp(tr("Open")); break;
+       case 1: SetHelp(tr("Button$Open")); break;
        case 2:
-       case 3: SetHelp(RecordingCommands.Count() ? tr("Commands") : tr("Play"), tr("Rewind"), tr("Delete"), NewHelpKeys == 3 ? tr("Info") : NULL);
+       case 3: SetHelp(RecordingCommands.Count() ? tr("Commands") : tr("Button$Play"), tr("Button$Rewind"), tr("Button$Delete"), NewHelpKeys == 3 ? tr("Info") : NULL);
        }
      helpKeys = NewHelpKeys;
      }
@@ -1876,6 +2004,7 @@
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Channel info position"),  &data.ChannelInfoPos, tr("bottom"), tr("top")));
   Add(new cMenuEditIntItem( tr("Setup.OSD$Channel info time (s)"),  &data.ChannelInfoTime, 1, 60));
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Info on channel switch"), &data.ShowInfoOnChSwitch));
+  Add(new cMenuEditBoolItem(tr("Setup.OSD$Timeout requested channel info"), &data.TimeoutRequChInfo));
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Scroll pages"),           &data.MenuScrollPage));
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Scroll wraps"),           &data.MenuScrollWrap));
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Sort timers"),            &data.SortTimers));
@@ -1918,7 +2047,7 @@
            themeIndex = d ? themes.GetThemeIndex(d) : 0;
         free(d);
         }
-     
+
      Set();
      Setup.OSDLanguage = OriginalOSDLanguage;
      }
@@ -1943,7 +2072,7 @@
       ;
   originalNumLanguages = numLanguages;
   SetSection(tr("EPG"));
-  SetHelp(tr("Scan"));
+  SetHelp(tr("Button$Scan"));
   Setup();
 }
 
@@ -2196,7 +2325,7 @@
             }
          }
       }
-  SetHelp(tr("Menu"), tr("Reset"));
+  SetHelp(tr("Button$Menu"), tr("Button$Reset"));
 }
 
 eOSState cMenuSetupCICAM::Menu(void)
@@ -2469,14 +2598,19 @@
 // --- cMenuMain -------------------------------------------------------------
 
 #define STOP_RECORDING tr(" Stop recording ")
-#define ON_PRIMARY_INTERFACE tr("on primary interface")
 
 cOsdObject *cMenuMain::pluginOsdObject = NULL;
 
-cMenuMain::cMenuMain(bool Replaying, eOSState State)
+cMenuMain::cMenuMain(eOSState State)
 :cOsdMenu("")
 {
-  replaying = Replaying;
+  lastDiskSpaceCheck = 0;
+  lastFreeMB = 0;
+  replaying = false;
+  stopReplayItem = NULL;
+  cancelEditingItem = NULL;
+  stopRecordingItem = NULL;
+  recordControlsState = 0;
   Set();
 
   // Initial submenus:
@@ -2502,23 +2636,9 @@
 void cMenuMain::Set(void)
 {
   Clear();
-  //XXX //SetTitle("VDR"); // this is done below, including disk usage
+  SetTitle("VDR");
   SetHasHotkeys();
 
-  // Title with disk usage:
-
-#define MB_PER_MINUTE 25.75 // this is just an estimate!
-
-  char buffer[40];
-  int FreeMB;
-  int Percent = VideoDiskSpace(&FreeMB);
-  int Minutes = int(double(FreeMB) / MB_PER_MINUTE);
-  int Hours = Minutes / 60;
-  Minutes %= 60;
-  snprintf(buffer, sizeof(buffer), "%s  -  %s %d%%  -  %2d:%02d %s", tr("VDR"), tr("Disk"), Percent, Hours, Minutes, tr("free"));
-  //XXX -> skin function!!!
-  SetTitle(buffer);
-
   // Basic menu items:
 
   Add(new cOsdItem(hk(tr("Schedule")),   osSchedule));
@@ -2545,37 +2665,82 @@
   if (Commands.Count())
      Add(new cOsdItem(hk(tr("Commands")),  osCommands));
 
-  // Replay control:
+  Update(true);
 
-  if (replaying)
-     Add(new cOsdItem(tr(" Stop replaying"), osStopReplay));
+  Display();
+}
 
-  // Record control:
+#define MB_PER_MINUTE 25.75 // this is just an estimate!
 
-  if (cRecordControls::StopPrimary()) {
-     char *buffer = NULL;
-     asprintf(&buffer, "%s%s", STOP_RECORDING, ON_PRIMARY_INTERFACE);
-     Add(new cOsdItem(buffer, osStopRecord));
-     free(buffer);
-     }
+bool cMenuMain::Update(bool Force)
+{
+  bool result = false;
 
-  const char *s = NULL;
-  while ((s = cRecordControls::GetInstantId(s)) != NULL) {
-        char *buffer = NULL;
-        asprintf(&buffer, "%s%s", STOP_RECORDING, s);
-        Add(new cOsdItem(buffer, osStopRecord));
-        free(buffer);
-        }
+  // Title with disk usage:
+  if (Force || time(NULL) - lastDiskSpaceCheck > DISKSPACECHEK) {
+     int FreeMB;
+     int Percent = VideoDiskSpace(&FreeMB);
+     if (Force || FreeMB != lastFreeMB) {
+        int Minutes = int(double(FreeMB) / MB_PER_MINUTE);
+        int Hours = Minutes / 60;
+        Minutes %= 60;
+        char buffer[40];
+        snprintf(buffer, sizeof(buffer), "%s  -  %s %d%%  -  %2d:%02d %s", tr("VDR"), tr("Disk"), Percent, Hours, Minutes, tr("free"));
+        //XXX -> skin function!!!
+        SetTitle(buffer);
+        result = true;
+        }
+     lastDiskSpaceCheck = time(NULL);
+     }
+
+  bool NewReplaying = cControl::Control() != NULL;
+  if (Force || NewReplaying != replaying) {
+     replaying = NewReplaying;
+     // Replay control:
+     if (replaying && !stopReplayItem)
+        Add(stopReplayItem = new cOsdItem(tr(" Stop replaying"), osStopReplay));
+     else if (stopReplayItem && !replaying) {
+        Del(stopReplayItem->Index());
+        stopReplayItem = NULL;
+        }
+     // Color buttons:
+     SetHelp(!replaying ? tr("Button$Record") : NULL, tr("Button$Audio"), replaying ? NULL : tr("Button$Pause"), replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Button$Resume") : NULL);
+     result = true;
+     }
 
   // Editing control:
+  bool CutterActive = cCutter::Active();
+  if (CutterActive && !cancelEditingItem) {
+     Add(cancelEditingItem = new cOsdItem(tr(" Cancel editing"), osCancelEdit));
+     result = true;
+     }
+  else if (cancelEditingItem && !CutterActive) {
+     Del(cancelEditingItem->Index());
+     cancelEditingItem = NULL;
+     result = true;
+     }
 
-  if (cCutter::Active())
-     Add(new cOsdItem(tr(" Cancel editing"), osCancelEdit));
-
-  // Color buttons:
+  // Record control:
+  if (cRecordControls::StateChanged(recordControlsState)) {
+     while (stopRecordingItem) {
+           cOsdItem *it = Next(stopRecordingItem);
+           Del(stopRecordingItem->Index());
+           stopRecordingItem = it;
+           }
+     const char *s = NULL;
+     while ((s = cRecordControls::GetInstantId(s)) != NULL) {
+           char *buffer = NULL;
+           asprintf(&buffer, "%s%s", STOP_RECORDING, s);
+           cOsdItem *item = new cOsdItem(osStopRecord);
+           item->SetText(buffer, false);
+           Add(item);
+           if (!stopRecordingItem)
+              stopRecordingItem = item;
+           }
+     result = true;
+     }
 
-  SetHelp(!replaying ? tr("Record") : NULL, tr("Audio"), replaying ? NULL : tr("Pause"), replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Resume") : NULL);
-  Display();
+  return result;
 }
 
 eOSState cMenuMain::ProcessKey(eKeys Key)
@@ -2595,11 +2760,7 @@
     case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) {
                           cOsdItem *item = Get(Current());
                           if (item) {
-                             const char *s = item->Text() + strlen(STOP_RECORDING);
-                             if (strcmp(s, ON_PRIMARY_INTERFACE) == 0)
-                                cRecordControls::StopPrimary(true);
-                             else
-                                cRecordControls::Stop(item->Text() + strlen(STOP_RECORDING));
+                             cRecordControls::Stop(item->Text() + strlen(STOP_RECORDING));
                              return osEnd;
                              }
                           }
@@ -2647,6 +2808,8 @@
                default:      break;
                }
     }
+  if (!HasSubMenu() && Update(HadSubMenu))
+     Display();
   if (Key != kNone) {
      if (Setup.OSDLanguage != osdLanguage) {
         Set();
@@ -2710,6 +2873,7 @@
   withInfo = !Switched || Setup.ShowInfoOnChSwitch;
   displayChannel = Skins.Current()->DisplayChannel(withInfo);
   number = 0;
+  timeout = Switched || Setup.TimeoutRequChInfo;
   channel = Channels.GetByNumber(Number);
   lastPresent = lastFollowing = NULL;
   if (channel) {
@@ -2893,7 +3057,7 @@
                      return osEnd;
                      }
     };
-  if (lastTime.Elapsed() < (uint64)(Setup.ChannelInfoTime * 1000)) {
+  if (!timeout || lastTime.Elapsed() < (uint64)(Setup.ChannelInfoTime * 1000)) {
      if (!number && group < 0 && channel && channel->Number() != cDevice::CurrentChannel())
         Refresh(); // makes sure a channel switch through the SVDRP CHAN command is displayed
      DisplayInfo();
@@ -2996,7 +3160,7 @@
          }
       }
   timeout.Set(TRACKTIMEOUT);
-  displayTracks = Skins.Current()->DisplayTracks(tr("Audio"), numTracks, descriptions);
+  displayTracks = Skins.Current()->DisplayTracks(tr("Button$Audio"), numTracks, descriptions);
   Show();
 }
 
@@ -3147,7 +3311,7 @@
      recorder = new cRecorder(fileName, ch->Ca(), timer->Priority(), ch->Vpid(), ch->Apids(), ch->Dpids(), ch->Spids());
      if (device->AttachReceiver(recorder)) {
         Recording.WriteInfo();
-        cStatus::MsgRecording(device, Recording.Name());
+        cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true);
         if (!Timer && !cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo()
            cReplayControl::SetRecording(fileName, Recording.Name());
         Recordings.AddByName(fileName);
@@ -3206,7 +3370,7 @@
      DELETENULL(recorder);
      timer->SetRecording(false);
      timer = NULL;
-     cStatus::MsgRecording(device, NULL);
+     cStatus::MsgRecording(device, NULL, fileName, false);
      cRecordingUserCommand::InvokeCommand(RUC_AFTERRECORDING, fileName);
      }
 }
@@ -3222,9 +3386,11 @@
 // --- cRecordControls -------------------------------------------------------
 
 cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL };
+int cRecordControls::state = 0;
 
 bool cRecordControls::Start(cTimer *Timer, bool Pause)
 {
+  ChangeState();
   int ch = Timer ? Timer->Channel()->Number() : cDevice::CurrentChannel();
   cChannel *channel = Channels.GetByNumber(ch);
 
@@ -3262,6 +3428,7 @@
 
 void cRecordControls::Stop(const char *InstantId)
 {
+  ChangeState();
   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
       if (RecordControls[i]) {
          const char *id = RecordControls[i]->InstantId();
@@ -3281,6 +3448,7 @@
 
 void cRecordControls::Stop(cDevice *Device)
 {
+  ChangeState();
   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
       if (RecordControls[i]) {
          if (RecordControls[i]->Device() == Device) {
@@ -3291,20 +3459,6 @@
       }
 }
 
-bool cRecordControls::StopPrimary(bool DoIt)
-{
-  if (cDevice::PrimaryDevice()->Receiving()) {
-     //XXX+ disabled for the moment - might become obsolete with DVB_DRIVER_VERSION >= 2002090101
-     cDevice *device = NULL;//XXX cDevice::GetDevice(cDevice::PrimaryDevice()->Ca(), 0);
-     if (device) {
-        if (DoIt)
-           Stop(cDevice::PrimaryDevice());
-        return true;
-        }
-     }
-  return false;
-}
-
 bool cRecordControls::PauseLiveVideo(void)
 {
   Skins.Message(mtStatus, tr("Pausing live video..."));
@@ -3349,8 +3503,10 @@
 {
   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
       if (RecordControls[i]) {
-         if (!RecordControls[i]->Process(t))
+         if (!RecordControls[i]->Process(t)) {
             DELETENULL(RecordControls[i]);
+            ChangeState();
+            }
          }
       }
 }
@@ -3365,6 +3521,7 @@
                RecordControls[i]->Stop();
                // This will restart the recording, maybe even from a different
                // device in case conditional access has changed.
+               ChangeState();
                }
             }
          }
@@ -3384,6 +3541,15 @@
 {
   for (int i = 0; i < MAXRECORDCONTROLS; i++)
       DELETENULL(RecordControls[i]);
+  ChangeState();
+}
+
+bool cRecordControls::StateChanged(int &State)
+{
+  int NewState = state;
+  bool Result = State != NewState;
+  State = state;
+  return Result;
 }
 
 // --- cReplayControl --------------------------------------------------------
@@ -3403,13 +3569,13 @@
   timeSearchActive = false;
   marks.Load(fileName);
   cRecording Recording(fileName);
-  cStatus::MsgReplaying(this, Recording.Name());
+  cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true);
 }
 
 cReplayControl::~cReplayControl()
 {
   Hide();
-  cStatus::MsgReplaying(this, NULL);
+  cStatus::MsgReplaying(this, NULL, fileName, false);
   Stop();
 }
 
@@ -3698,6 +3864,14 @@
      }
 }
 
+cOsdObject *cReplayControl::GetInfo(void)
+{
+  cRecording *Recording = Recordings.GetByName(cReplayControl::LastReplayed());
+  if (Recording)
+     return new cMenuRecording(Recording);
+  return NULL;
+}
+
 eOSState cReplayControl::ProcessKey(eKeys Key)
 {
   if (!Active())
diff -ruN vdr-1.3.37/menu.h vdr-1.3.38/menu.h
--- vdr-1.3.37/menu.h	2005-11-05 18:26:09.000000000 +0100
+++ vdr-1.3.38/menu.h	2006-01-06 12:30:38.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menu.h 1.77 2005/11/05 17:26:09 kls Exp $
+ * $Id: menu.h 1.81 2006/01/06 11:30:38 kls Exp $
  */
 
 #ifndef __MENU_H
@@ -55,11 +55,18 @@
 
 class cMenuMain : public cOsdMenu {
 private:
+  time_t lastDiskSpaceCheck;
+  int lastFreeMB;
   bool replaying;
+  cOsdItem *stopReplayItem;
+  cOsdItem *cancelEditingItem;
+  cOsdItem *stopRecordingItem;
+  int recordControlsState;
   static cOsdObject *pluginOsdObject;
   void Set(void);
+  bool Update(bool Force = false);
 public:
-  cMenuMain(bool Replaying, eOSState State = osUnknown);
+  cMenuMain(eOSState State = osUnknown);
   virtual eOSState ProcessKey(eKeys Key);
   static cOsdObject *PluginOsdObject(void);
   };
@@ -71,6 +78,7 @@
   bool withInfo;
   cTimeMs lastTime;
   int number;
+  bool timeout;
   cChannel *channel;
   const cEvent *lastPresent;
   const cEvent *lastFollowing;
@@ -189,11 +197,11 @@
 class cRecordControls {
 private:
   static cRecordControl *RecordControls[];
+  static int state;
 public:
   static bool Start(cTimer *Timer = NULL, bool Pause = false);
   static void Stop(const char *InstantId);
   static void Stop(cDevice *Device);
-  static bool StopPrimary(bool DoIt = false);
   static bool PauseLiveVideo(void);
   static const char *GetInstantId(const char *LastInstantId);
   static cRecordControl *GetRecordControl(const char *FileName);
@@ -201,6 +209,8 @@
   static void ChannelDataModified(cChannel *Channel);
   static bool Active(void);
   static void Shutdown(void);
+  static void ChangeState(void) { state++; }
+  static bool StateChanged(int &State);
   };
 
 class cReplayControl : public cDvbPlayerControl {
@@ -230,6 +240,7 @@
 public:
   cReplayControl(void);
   virtual ~cReplayControl();
+  virtual cOsdObject *GetInfo(void);
   virtual eOSState ProcessKey(eKeys Key);
   virtual void Show(void);
   virtual void Hide(void);
diff -ruN vdr-1.3.37/menuitems.c vdr-1.3.38/menuitems.c
--- vdr-1.3.37/menuitems.c	2005-11-12 13:22:10.000000000 +0100
+++ vdr-1.3.38/menuitems.c	2006-01-07 16:37:03.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menuitems.c 1.24 2005/11/12 12:22:10 kls Exp $
+ * $Id: menuitems.c 1.29 2006/01/07 15:37:03 kls Exp $
  */
 
 #include "menuitems.h"
@@ -15,7 +15,7 @@
 #include "skins.h"
 #include "status.h"
 
-const char *FileNameChars = " abcdefghijklmnopqrstuvwxyz0123456789-.#~";
+const char *FileNameChars = " abcdefghijklmnopqrstuvwxyz0123456789-.#~,/_@";
 
 // --- cMenuEditItem ---------------------------------------------------------
 
@@ -116,7 +116,7 @@
 
 // --- cMenuEditBitItem ------------------------------------------------------
 
-cMenuEditBitItem::cMenuEditBitItem(const char *Name, int *Value, int Mask, const char *FalseString, const char *TrueString)
+cMenuEditBitItem::cMenuEditBitItem(const char *Name, uint *Value, uint Mask, const char *FalseString, const char *TrueString)
 :cMenuEditBoolItem(Name, &bit, FalseString, TrueString)
 {
   value = Value;
@@ -243,6 +243,9 @@
   pos = -1;
   insert = uppercase = false;
   newchar = true;
+  charMap = tr(" 0\t-.#~,/_@1\tabc2\tdef3\tghi4\tjkl5\tmno6\tpqrs7\ttuv8\twxyz9");
+  currentChar = NULL;
+  lastKey = kNone;
   Set();
 }
 
@@ -253,8 +256,8 @@
 
 void cMenuEditStrItem::SetHelpKeys(void)
 {
-  if (pos >= 0)
-     cSkinDisplay::Current()->SetButtons(tr("ABC/abc"), tr(insert ? "Overwrite" : "Insert"), tr("Delete"));
+  if (InEditMode())
+     cSkinDisplay::Current()->SetButtons(tr("Button$ABC/abc"), tr(insert ? "Button$Overwrite" : "Button$Insert"), tr("Button$Delete"));
   else
      cSkinDisplay::Current()->SetButtons(NULL);
 }
@@ -264,7 +267,7 @@
   char buf[1000];
   const char *fmt = insert && newchar ? "[]%c%s" : "[%c]%s";
 
-  if (pos >= 0) {
+  if (InEditMode()) {
      const cFont *font = cFont::GetFont(fontOsd);
      strncpy(buf, value, pos);
      snprintf(buf + pos, sizeof(buf) - pos - 2, fmt, *(value + pos), value + pos + 1);
@@ -320,9 +323,12 @@
 
 eOSState cMenuEditStrItem::ProcessKey(eKeys Key)
 {
+  bool SameKey = Key == lastKey;
+  if (Key != kNone)
+     lastKey = Key;
   switch (Key) {
     case kRed:   // Switch between upper- and lowercase characters
-                 if (pos >= 0) {
+                 if (InEditMode()) {
                     if (!insert || !newchar) {
                        uppercase = !uppercase;
                        value[pos] = uppercase ? toupper(value[pos]) : tolower(value[pos]);
@@ -332,7 +338,7 @@
                     return osUnknown;
                  break;
     case kGreen: // Toggle insert/overwrite modes
-                 if (pos >= 0) {
+                 if (InEditMode()) {
                     insert = !insert;
                     newchar = true;
                     SetHelpKeys();
@@ -342,7 +348,7 @@
                  break;
     case kYellow|k_Repeat:
     case kYellow: // Remove the character at current position; in insert mode it is the character to the right of cursor
-                 if (pos >= 0) {
+                 if (InEditMode()) {
                     if (strlen(value) > 1) {
                        if (!insert || pos < int(strlen(value)) - 1)
                           memmove(value + pos, value + pos + 1, strlen(value) - pos);
@@ -361,7 +367,7 @@
                  break;
     case kBlue|k_Repeat:
     case kBlue:  // consume the key only if in edit-mode
-                 if (pos >= 0)
+                 if (InEditMode())
                     ;
                  else
                     return osUnknown;
@@ -395,7 +401,7 @@
     case kUp|k_Repeat:
     case kUp:
     case kDown|k_Repeat:
-    case kDown:  if (pos >= 0) {
+    case kDown:  if (InEditMode()) {
                     if (insert && newchar) {
                        // create a new character in insert mode
                        if (int(strlen(value)) < length - 1) {
@@ -412,7 +418,39 @@
                  else
                     return cMenuEditItem::ProcessKey(Key);
                  break;
-    case kOk:    if (pos >= 0) {
+    case k0 ... k9: {
+                 if (!SameKey)
+                    currentChar = NULL;
+                 if (InEditMode()) {
+                    if (insert && newchar) {
+                       // create a new character in insert mode
+                       if (int(strlen(value)) < length - 1) {
+                          memmove(value + pos + 1, value + pos, strlen(value) - pos + 1);
+                          value[pos] = ' ';
+                          }
+                       }
+                    if (!currentChar || !*currentChar || *currentChar == '\t') {
+                       // find the beginning of the character map entry for Key
+                       int n = Key - k0;
+                       currentChar = charMap;
+                       while (n > 0 && *currentChar) {
+                             if (*currentChar++ == '\t')
+                                n--;
+                             }
+                       }
+                    if (*currentChar && *currentChar != '\t') {
+                       value[pos] = *currentChar;
+                       if (uppercase)
+                          value[pos] = toupper(value[pos]);
+                       currentChar++;
+                       }
+                    newchar = false;
+                    }
+                 else
+                    return cMenuEditItem::ProcessKey(Key);
+                 }
+                 break;
+    case kOk:    if (InEditMode()) {
                     pos = -1;
                     newchar = true;
                     stripspace(value);
@@ -420,7 +458,7 @@
                     break;
                     }
                  // run into default
-    default:     if (pos >= 0 && BASICKEY(Key) == kKbd) {
+    default:     if (InEditMode() && BASICKEY(Key) == kKbd) {
                     int c = KEYKBD(Key);
                     if (c <= 0xFF) {
                        const char *p = strchr(allowed, tolower(c));
@@ -667,6 +705,7 @@
               }
            else {
               *weekdays = days[cTimer::GetWDay(*value)];
+              dayindex = FindDayIndex(*weekdays);
               oldvalue = *value;
               *value = 0;
               }
diff -ruN vdr-1.3.37/menuitems.h vdr-1.3.38/menuitems.h
--- vdr-1.3.37/menuitems.h	2005-11-11 14:26:51.000000000 +0100
+++ vdr-1.3.38/menuitems.h	2006-01-06 16:16:25.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menuitems.h 1.12 2005/11/11 13:26:51 kls Exp $
+ * $Id: menuitems.h 1.15 2006/01/06 15:16:25 kls Exp $
  */
 
 #ifndef __MENUITEMS_H
@@ -44,12 +44,12 @@
 
 class cMenuEditBitItem : public cMenuEditBoolItem {
 protected:
-  int *value;
+  uint *value;
+  uint mask;
   int bit;
-  int mask;
   virtual void Set(void);
 public:
-  cMenuEditBitItem(const char *Name, int *Value, int Mask, const char *FalseString = NULL, const char *TrueString = NULL);
+  cMenuEditBitItem(const char *Name, uint *Value, uint Mask, const char *FalseString = NULL, const char *TrueString = NULL);
   };
 
 class cMenuEditNumItem : public cMenuEditItem {
@@ -82,9 +82,14 @@
   char *allowed;
   int pos;
   bool insert, newchar, uppercase;
+  const char *charMap;
+  const char *currentChar;
+  eKeys lastKey;
   void SetHelpKeys(void);
   virtual void Set(void);
   char Inc(char c, bool Up);
+protected:
+  bool InEditMode(void) { return pos >= 0; }
 public:
   cMenuEditStrItem(const char *Name, char *Value, int Length, const char *Allowed);
   ~cMenuEditStrItem();
diff -ruN vdr-1.3.37/osd.c vdr-1.3.38/osd.c
--- vdr-1.3.37/osd.c	2005-11-04 15:19:31.000000000 +0100
+++ vdr-1.3.38/osd.c	2005-12-30 16:42:04.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osd.c 1.64 2005/11/04 14:19:31 kls Exp $
+ * $Id: osd.c 1.65 2005/12/30 15:42:04 kls Exp $
  */
 
 #include "osd.h"
@@ -145,7 +145,7 @@
         esyslog("ERROR: can't allocate bitmap!");
      }
   else
-     esyslog("ERROR: illegal bitmap parameters (%d, %d)!", width, height);
+     esyslog("ERROR: invalid bitmap parameters (%d, %d)!", width, height);
 }
 
 bool cBitmap::Contains(int x, int y) const
diff -ruN vdr-1.3.37/osd.h vdr-1.3.38/osd.h
--- vdr-1.3.37/osd.h	2005-06-19 12:35:25.000000000 +0200
+++ vdr-1.3.38/osd.h	2005-12-18 13:56:21.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osd.h 1.49 2005/06/19 10:35:25 kls Exp $
+ * $Id: osd.h 1.50 2005/12/18 12:56:21 kls Exp $
  */
 
 #ifndef __OSD_H
@@ -37,6 +37,7 @@
                  oeAreasOverlap,
                  oeWrongAlignment,
                  oeOutOfMemory,
+                 oeWrongAreaSize,
                  oeUnknown,
                };
 
diff -ruN vdr-1.3.37/osdbase.c vdr-1.3.38/osdbase.c
--- vdr-1.3.37/osdbase.c	2005-10-09 12:56:26.000000000 +0200
+++ vdr-1.3.38/osdbase.c	2006-01-08 12:40:02.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osdbase.c 1.24 2005/10/09 10:56:26 kls Exp $
+ * $Id: osdbase.c 1.28 2006/01/08 11:40:02 kls Exp $
  */
 
 #include "osdbase.h"
@@ -19,7 +19,6 @@
 cOsdItem::cOsdItem(eOSState State)
 {
   text = NULL;
-  offset = -1;
   state = State;
   selectable = true;
   fresh = true;
@@ -28,7 +27,6 @@
 cOsdItem::cOsdItem(const char *Text, eOSState State, bool Selectable)
 {
   text = NULL;
-  offset = -1;
   state = State;
   selectable = Selectable;
   fresh = true;
@@ -61,6 +59,14 @@
   return Key == kOk ? state : osUnknown;
 }
 
+// --- cOsdObject ------------------------------------------------------------
+
+void cOsdObject::Show(void)
+{
+  if (isMenu)
+     ((cOsdMenu *)this)->Display();
+}
+
 // --- cOsdMenu --------------------------------------------------------------
 
 cSkinDisplayMenu *cOsdMenu::displayMenu = NULL;
@@ -155,10 +161,10 @@
 {
   cList<cOsdItem>::Del(Get(Index));
   int count = Count();
-  while (current < count && !SelectableItem(current)) 
+  while (current < count && !SelectableItem(current))
         current++;
   if (current == count) {
-     while (current > 0 && !SelectableItem(current))  
+     while (current > 0 && !SelectableItem(current))
            current--;
      }
   if (Index == first && first > 0)
@@ -252,6 +258,8 @@
 
 void cOsdMenu::Clear(void)
 {
+  if (marked >= 0)
+     SetStatus(NULL);
   first = 0;
   current = marked = -1;
   cList<cOsdItem>::Clear();
@@ -363,7 +371,7 @@
      CursorUp();
 }
 
-void cOsdMenu::PageDown(void) 
+void cOsdMenu::PageDown(void)
 {
   int oldCurrent = current;
   int oldFirst = first;
@@ -455,6 +463,7 @@
         }
      }
   switch (Key) {
+    case k0:      return osUnknown;
     case k1...k9: return hasHotkeys ? HotKey(Key) : osUnknown;
     case kUp|k_Repeat:
     case kUp:   CursorUp();   break;
diff -ruN vdr-1.3.37/osdbase.h vdr-1.3.38/osdbase.h
--- vdr-1.3.37/osdbase.h	2005-10-02 11:18:20.000000000 +0200
+++ vdr-1.3.38/osdbase.h	2006-01-06 12:55:30.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osdbase.h 1.12 2005/10/02 09:18:20 kls Exp $
+ * $Id: osdbase.h 1.14 2006/01/06 11:55:30 kls Exp $
  */
 
 #ifndef __OSDBASE_H
@@ -49,7 +49,6 @@
 class cOsdItem : public cListObject {
 private:
   char *text;
-  int offset;
   eOSState state;
   bool selectable;
 protected:
@@ -78,7 +77,7 @@
   virtual ~cOsdObject() {}
   bool NeedsFastResponse(void) { return needsFastResponse; }
   bool IsMenu(void) { return isMenu; }
-  virtual void Show(void) {}
+  virtual void Show(void);
   virtual eOSState ProcessKey(eKeys Key) { return osUnknown; }
   };
 
diff -ruN vdr-1.3.37/player.c vdr-1.3.38/player.c
--- vdr-1.3.37/player.c	2004-12-12 12:21:07.000000000 +0100
+++ vdr-1.3.38/player.c	2006-01-06 12:30:07.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: player.c 1.9 2004/12/12 11:21:07 kls Exp $
+ * $Id: player.c 1.11 2006/01/06 11:30:07 kls Exp $
  */
 
 #include "player.h"
@@ -40,6 +40,7 @@
 // --- cControl --------------------------------------------------------------
 
 cControl *cControl::control = NULL;
+cMutex cControl::mutex;
 
 cControl::cControl(cPlayer *Player, bool Hidden)
 {
@@ -54,19 +55,27 @@
      control = NULL;
 }
 
+cOsdObject *cControl::GetInfo(void)
+{
+  return NULL;
+}
+
 cControl *cControl::Control(void)
 {
+  cMutexLock MutexLock(&mutex);
   return (control && !control->hidden) ? control : NULL;
 }
 
 void cControl::Launch(cControl *Control)
 {
+  cMutexLock MutexLock(&mutex);
   delete control;
   control = Control;
 }
 
 void cControl::Attach(void)
 {
+  cMutexLock MutexLock(&mutex);
   if (control && !control->attached && control->player && !control->player->IsAttached()) {
      if (cDevice::PrimaryDevice()->AttachPlayer(control->player))
         control->attached = true;
@@ -79,6 +88,7 @@
 
 void cControl::Shutdown(void)
 {
+  cMutexLock MutexLock(&mutex);
   cControl *c = control; // avoids recursions
   control = NULL;
   delete c;
diff -ruN vdr-1.3.37/player.h vdr-1.3.38/player.h
--- vdr-1.3.37/player.h	2005-05-22 13:07:42.000000000 +0200
+++ vdr-1.3.38/player.h	2006-01-06 12:29:27.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: player.h 1.17 2005/05/22 11:07:42 kls Exp $
+ * $Id: player.h 1.19 2006/01/06 11:29:27 kls Exp $
  */
 
 #ifndef __PLAYER_H
@@ -62,6 +62,7 @@
 class cControl : public cOsdObject {
 private:
   static cControl *control;
+  static cMutex mutex;
   bool attached;
   bool hidden;
 protected:
@@ -70,6 +71,7 @@
   cControl(cPlayer *Player, bool Hidden = false);
   virtual ~cControl();
   virtual void Hide(void) = 0;
+  virtual cOsdObject *GetInfo(void);
   bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) { return player->GetIndex(Current, Total, SnapToIFrame); }
   bool GetReplayMode(bool &Play, bool &Forward, int &Speed) { return player->GetReplayMode(Play, Forward, Speed); }
   static void Launch(cControl *Control);
diff -ruN vdr-1.3.37/plugin.c vdr-1.3.38/plugin.c
--- vdr-1.3.37/plugin.c	2005-08-27 18:13:24.000000000 +0200
+++ vdr-1.3.38/plugin.c	2006-01-08 12:40:05.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: plugin.c 1.15 2005/08/27 16:13:24 kls Exp $
+ * $Id: plugin.c 1.16 2006/01/08 11:40:05 kls Exp $
  */
 
 #include "plugin.h"
@@ -25,7 +25,7 @@
 
 char *cPlugin::configDirectory = NULL;
 
-cPlugin::cPlugin(void) 
+cPlugin::cPlugin(void)
 {
   name = NULL;
   started = false;
diff -ruN vdr-1.3.37/rcu.c vdr-1.3.38/rcu.c
--- vdr-1.3.37/rcu.c	2005-08-15 14:30:21.000000000 +0200
+++ vdr-1.3.38/rcu.c	2006-01-08 12:40:09.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: rcu.c 1.10 2005/08/15 12:30:21 kls Exp $
+ * $Id: rcu.c 1.13 2006/01/08 11:40:09 kls Exp $
  */
 
 #include "rcu.h"
@@ -23,8 +23,8 @@
   dp = 0;
   mode = modeB;
   code = 0;
-  numberToSend = -1;
-  lastNumber = 0;
+  number = 0;
+  data = 0;
   receivedCommand = false;
   if ((f = open(DeviceName, O_RDWR | O_NONBLOCK)) >= 0) {
      struct termios t;
@@ -32,7 +32,7 @@
         cfsetspeed(&t, B9600);
         cfmakeraw(&t);
         if (tcsetattr(f, TCSAFLUSH, &t) == 0) {
-           Number(0);//XXX 8888???
+           SetNumber(8888);
            const char *Setup = GetSetup();
            if (Setup) {
               code = *Setup;
@@ -95,13 +95,12 @@
 
   time_t LastCodeRefresh = 0;
   cTimeMs FirstTime;
+  unsigned char LastCode = 0, LastMode = 0;
   uint64 LastCommand = 0;
+  unsigned int LastData = 0;
   bool repeat = false;
 
   while (Running() && f >= 0) {
-
-        LOCK_THREAD;
-
         if (ReceiveByte(REPEATLIMIT) == 'X') {
            for (int i = 0; i < 6; i++) {
                int b = ReceiveByte();
@@ -140,11 +139,22 @@
            LastCommand = 0;
            }
         else {
-           LastCommand = 0;
-           if (numberToSend >= 0) {
-              Number(numberToSend);
-              numberToSend = -1;
+           unsigned int d = data;
+           if (d != LastData) {
+              SendData(d);
+              LastData = d;
               }
+           unsigned char c = code;
+           if (c != LastCode) {
+              SendCommand(c);
+              LastCode = c;
+              }
+           unsigned char m = mode;
+           if (m != LastMode) {
+              SendCommand(m);
+              LastMode = m;
+              }
+           LastCommand = 0;
            }
         if (code && time(NULL) - LastCodeRefresh > 60) {
            SendCommand(code); // in case the PIC listens to the wrong code
@@ -192,8 +202,6 @@
 
 bool cRcuRemote::SendByte(unsigned char c)
 {
-  LOCK_THREAD;
-
   for (int retry = 5; retry--;) {
       if (SendByteHandshake(c))
          return true;
@@ -201,32 +209,34 @@
   return false;
 }
 
-bool cRcuRemote::SetCode(unsigned char Code)
+bool cRcuRemote::SendData(unsigned int n)
+{
+  for (int i = 0; i < 4; i++) {
+      if (!SendByte(n & 0x7F))
+         return false;
+      n >>= 8;
+      }
+  return SendCommand(mode);
+}
+
+void cRcuRemote::SetCode(unsigned char Code)
 {
   code = Code;
-  return SendCommand(code);
 }
 
-bool cRcuRemote::SetMode(unsigned char Mode)
+void cRcuRemote::SetMode(unsigned char Mode)
 {
   mode = Mode;
-  return SendCommand(mode);
 }
 
 bool cRcuRemote::SendCommand(unsigned char Cmd)
-{ 
+{
   return SendByte(Cmd | 0x80);
 }
 
-bool cRcuRemote::Digit(int n, int v)
-{ 
-  return SendByte(((n & 0x03) << 5) | (v & 0x0F) | (((dp >> n) & 0x01) << 4));
-}
-
-bool cRcuRemote::Number(int n, bool Hex)
+void cRcuRemote::SetNumber(int n, bool Hex)
 {
-  LOCK_THREAD;
-
+  number = n;
   if (!Hex) {
      char buf[8];
      sprintf(buf, "%4d", n & 0xFFFF);
@@ -237,19 +247,17 @@
          n = (n << 4) | ((*d - '0') & 0x0F);
          }
      }
-  lastNumber = n;
+  unsigned int m = 0;
   for (int i = 0; i < 4; i++) {
-      if (!Digit(i, n))
-         return false;
+      m <<= 8;
+      m |= ((i & 0x03) << 5) | (n & 0x0F) | (((dp >> i) & 0x01) << 4);
       n >>= 4;
       }
-  return SendCommand(mode);
+  data = m;
 }
 
-bool cRcuRemote::String(char *s)
+void cRcuRemote::SetString(char *s)
 {
-  LOCK_THREAD;
-
   const char *chars = mode == modeH ? "0123456789ABCDEF" : "0123456789-EHLP ";
   int n = 0;
 
@@ -262,16 +270,16 @@
              }
           }
       }
-  return Number(n, true);
+  SetNumber(n, true);
 }
 
 void cRcuRemote::SetPoints(unsigned char Dp, bool On)
-{ 
+{
   if (On)
      dp |= Dp;
   else
      dp &= ~Dp;
-  Number(lastNumber, true);
+  SetNumber(number);
 }
 
 bool cRcuRemote::DetectCode(unsigned char *Code)
@@ -291,12 +299,12 @@
      SetMode(modeH);
      char buf[5];
      sprintf(buf, "C0D%c", *Code);
-     String(buf);
+     SetString(buf);
      SetCode(*Code);
      cCondWait::SleepMs(2 * REPEATDELAY);
      if (receivedCommand) {
         SetMode(modeB);
-        String("----");
+        SetString("----");
         return true;
         }
      if (*Code < 'D') {
@@ -310,13 +318,11 @@
 
 void cRcuRemote::ChannelSwitch(const cDevice *Device, int ChannelNumber)
 {
-  if (ChannelNumber && Device->IsPrimaryDevice()) {
-     LOCK_THREAD;
-     numberToSend = cDevice::CurrentChannel();
-     }
+  if (ChannelNumber && Device->IsPrimaryDevice())
+     SetNumber(cDevice::CurrentChannel());
 }
 
-void cRcuRemote::Recording(const cDevice *Device, const char *Name)
+void cRcuRemote::Recording(const cDevice *Device, const char *Name, const char *FileName, bool On)
 {
   SetPoints(1 << Device->DeviceNumber(), Device->Receiving());
 }
diff -ruN vdr-1.3.37/rcu.h vdr-1.3.38/rcu.h
--- vdr-1.3.37/rcu.h	2005-07-31 12:18:00.000000000 +0200
+++ vdr-1.3.38/rcu.h	2005-12-31 16:09:25.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: rcu.h 1.4 2005/07/31 10:18:00 kls Exp $
+ * $Id: rcu.h 1.6 2005/12/31 15:09:25 kls Exp $
  */
 
 #ifndef __RCU_H
@@ -19,23 +19,23 @@
   enum { modeH = 'h', modeB = 'b', modeS = 's' };
   int f;
   unsigned char dp, code, mode;
-  int numberToSend;
-  int lastNumber;
+  int number;
+  unsigned int data;
   bool receivedCommand;
   bool SendCommand(unsigned char Cmd);
   int ReceiveByte(int TimeoutMs = 0);
   bool SendByteHandshake(unsigned char c);
   bool SendByte(unsigned char c);
-  bool Digit(int n, int v);
-  bool SetCode(unsigned char Code);
-  bool SetMode(unsigned char Mode);
-  bool Number(int n, bool Hex = false);
+  bool SendData(unsigned int n);
+  void SetCode(unsigned char Code);
+  void SetMode(unsigned char Mode);
+  void SetNumber(int n, bool Hex = false);
   void SetPoints(unsigned char Dp, bool On);
-  bool String(char *s);
+  void SetString(char *s);
   bool DetectCode(unsigned char *Code);
   virtual void Action(void);
   virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber);
-  virtual void Recording(const cDevice *Device, const char *Name);
+  virtual void Recording(const cDevice *Device, const char *Name, const char *FileName, bool On);
 public:
   cRcuRemote(const char *DeviceName);
   virtual ~cRcuRemote();
diff -ruN vdr-1.3.37/recorder.c vdr-1.3.38/recorder.c
--- vdr-1.3.37/recorder.c	2005-10-31 13:35:29.000000000 +0100
+++ vdr-1.3.38/recorder.c	2006-01-08 12:01:25.000000000 +0100
@@ -4,13 +4,13 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recorder.c 1.16 2005/10/31 12:35:29 kls Exp $
+ * $Id: recorder.c 1.17 2006/01/08 11:01:25 kls Exp $
  */
 
+#include "recorder.h"
 #include <stdarg.h>
 #include <stdio.h>
 #include <unistd.h>
-#include "recorder.h"
 
 #define RECORDERBUFSIZE  MEGABYTE(5)
 
@@ -171,6 +171,8 @@
            int Count = remux->Put(b, r);
            if (Count)
               ringBuffer->Del(Count);
+           else
+              cCondWait::SleepMs(100); // avoid busy loop when resultBuffer is full in cRemux::Put()
            }
         }
 }
diff -ruN vdr-1.3.37/recording.c vdr-1.3.38/recording.c
--- vdr-1.3.37/recording.c	2005-11-04 15:19:44.000000000 +0100
+++ vdr-1.3.38/recording.c	2006-01-08 12:40:13.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recording.c 1.124 2005/11/04 14:19:44 kls Exp $
+ * $Id: recording.c 1.132 2006/01/08 11:40:13 kls Exp $
  */
 
 #include "recording.h"
@@ -48,8 +48,8 @@
 
 #define MINDISKSPACE 1024 // MB
 
-#define DELETEDLIFETIME     1 // hours after which a deleted recording will be actually removed
-#define REMOVECHECKDELTA 3600 // seconds between checks for removing deleted files
+#define REMOVECHECKDELTA   60 // seconds between checks for removing deleted files
+#define DELETEDLIFETIME   300 // seconds after which a deleted recording will be actually removed
 #define DISKCHECKDELTA    100 // seconds between checks for free disk space
 #define REMOVELATENCY      10 // seconds to wait until next check after removing a file
 
@@ -60,46 +60,67 @@
 
 bool VfatFileSystem = false;
 
-static cRecordings DeletedRecordings(true);
+cRecordings DeletedRecordings(true);
+
+// --- cRemoveDeletedRecordingsThread ----------------------------------------
+
+class cRemoveDeletedRecordingsThread : public cThread {
+protected:
+  virtual void Action(void);
+public:
+  cRemoveDeletedRecordingsThread(void);
+  };
+
+cRemoveDeletedRecordingsThread::cRemoveDeletedRecordingsThread(void)
+:cThread("remove deleted recordings")
+{
+}
+
+void cRemoveDeletedRecordingsThread::Action(void)
+{
+  // Make sure only one instance of VDR does this:
+  cLockFile LockFile(VideoDirectory);
+  if (LockFile.Lock()) {
+     cThreadLock DeletedRecordingsLock(&DeletedRecordings);
+     for (cRecording *r = DeletedRecordings.First(); r; ) {
+         if (r->deleted && time(NULL) - r->deleted > DELETEDLIFETIME) {
+            cRecording *next = DeletedRecordings.Next(r);
+            r->Remove();
+            DeletedRecordings.Del(r);
+            r = next;
+            RemoveEmptyVideoDirectories();
+            continue;
+            }
+         r = DeletedRecordings.Next(r);
+         }
+     }
+}
+
+static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread;
+
+// ---
 
 void RemoveDeletedRecordings(void)
 {
   static time_t LastRemoveCheck = 0;
-  if (LastRemoveCheck == 0) {
-     DeletedRecordings.Update();
-     LastRemoveCheck = time(NULL) - REMOVECHECKDELTA * 9 / 10;
-     }
-  else if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
-     // Make sure only one instance of VDR does this:
-     cLockFile LockFile(VideoDirectory);
-     if (!LockFile.Lock())
-        return;
-     // Remove the oldest file that has been "deleted":
-     cThreadLock DeletedRecordingsLock(&DeletedRecordings);
-     if (DeletedRecordings.Count()) {
-        cRecording *r = DeletedRecordings.First();
-        cRecording *r0 = r;
-        while (r) {
-              if (r->start < r0->start)
-                 r0 = r;
-              r = DeletedRecordings.Next(r);
-              }
-        if (r0 && time(NULL) - r0->start > DELETEDLIFETIME * 3600) {
-           r0->Remove();
-           DeletedRecordings.Del(r0);
-           RemoveEmptyVideoDirectories();
-           LastRemoveCheck += REMOVELATENCY;
-           return;
-           }
+  if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
+     if (!RemoveDeletedRecordingsThread.Active()) {
+        cThreadLock DeletedRecordingsLock(&DeletedRecordings);
+        for (cRecording *r = DeletedRecordings.First(); r; r = DeletedRecordings.Next(r)) {
+            if (r->deleted && time(NULL) - r->deleted > DELETEDLIFETIME) {
+               RemoveDeletedRecordingsThread.Start();
+               break;
+               }
+            }
         }
-     else
-        DeletedRecordings.Update();
      LastRemoveCheck = time(NULL);
      }
 }
 
 void AssertFreeDiskSpace(int Priority)
 {
+  static cMutex Mutex;
+  cMutexLock MutexLock(&Mutex);
   // With every call to this function we try to actually remove
   // a file, or mark a file for removal ("delete" it), so that
   // it will get removed during the next call.
@@ -160,7 +181,7 @@
            }
         // Unable to free disk space, but there's nothing we can do about that...
         isyslog("...no old recording found, giving up");
-        Interface->Confirm(tr("Low disk space!"), 30);
+        Skins.QueueMessage(mtWarning, tr("Low disk space!"), 5, -1);
         }
      LastFreeDiskCheck = time(NULL);
      }
@@ -400,6 +421,8 @@
   sortBuffer = NULL;
   fileName = NULL;
   name = NULL;
+  fileSizeMB = -1; // unknown
+  deleted = 0;
   // set up the actual name:
   const char *Title = Event ? Event->Title() : NULL;
   const char *Subtitle = Event ? Event->ShortText() : NULL;
@@ -453,6 +476,8 @@
 cRecording::cRecording(const char *FileName)
 {
   resume = RESUME_NOT_INITIALIZED;
+  fileSizeMB = -1; // unknown
+  deleted = 0;
   titleBuffer = NULL;
   sortBuffer = NULL;
   fileName = strdup(FileName);
@@ -525,7 +550,7 @@
               // so assume the short text is missing and concatenate
               // line 1 and line 2 to be the long text:
               int len = strlen(data[1]);
-              if (len > 80) { 
+              if (len > 80) {
                  data[1] = (char *)realloc(data[1], len + 1 + strlen(data[2]) + 1);
                  strcat(data[1], "\n");
                  strcat(data[1], data[2]);
@@ -714,7 +739,7 @@
   bool result = true;
   char *NewName = strdup(FileName());
   char *ext = strrchr(NewName, '.');
-  if (strcmp(ext, RECEXT) == 0) {
+  if (ext && strcmp(ext, RECEXT) == 0) {
      strncpy(ext, DELEXT, strlen(ext));
      if (access(NewName, F_OK) == 0) {
         // the new name already exists, so let's remove that one first:
@@ -814,6 +839,10 @@
                        Add(r);
                        ChangeState();
                        Unlock();
+                       if (deleted) {
+                          r->fileSizeMB = DirSizeMB(buffer);
+                          r->deleted = time(NULL);
+                          }
                        }
                     else
                        delete r;
@@ -883,12 +912,33 @@
   LOCK_THREAD;
   cRecording *recording = GetByName(FileName);
   if (recording) {
-     Del(recording);
+     cThreadLock DeletedRecordingsLock(&DeletedRecordings);
+     Del(recording, false);
+     char *ext = strrchr(recording->FileName(), '.');
+     if (ext) {
+        strncpy(ext, DELEXT, strlen(ext));
+        recording->fileSizeMB = DirSizeMB(recording->FileName());
+        recording->deleted = time(NULL);
+        DeletedRecordings.Add(recording);
+        }
+     else
+        delete recording;
      ChangeState();
      TouchUpdate();
      }
 }
 
+int cRecordings::TotalFileSizeMB(void)
+{
+  int size = 0;
+  LOCK_THREAD;
+  for (cRecording *recording = First(); recording; recording = Next(recording)) {
+      if (recording->fileSizeMB > 0)
+         size += recording->fileSizeMB;
+      }
+  return size;
+}
+
 void cRecordings::ResetResume(const char *ResumeFileName)
 {
   LOCK_THREAD;
diff -ruN vdr-1.3.37/recording.h vdr-1.3.38/recording.h
--- vdr-1.3.37/recording.h	2005-10-31 13:27:12.000000000 +0100
+++ vdr-1.3.38/recording.h	2005-12-18 12:26:51.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recording.h 1.46 2005/10/31 12:27:12 kls Exp $
+ * $Id: recording.h 1.48 2005/12/18 11:26:51 kls Exp $
  */
 
 #ifndef __RECORDING_H
@@ -55,12 +55,14 @@
   };
 
 class cRecording : public cListObject {
+  friend class cRecordings;
 private:
   mutable int resume;
   mutable char *titleBuffer;
   mutable char *sortBuffer;
   mutable char *fileName;
   mutable char *name;
+  mutable int fileSizeMB;
   cRecordingInfo *info;
   static char *StripEpisodeName(char *s);
   char *SortName(void) const;
@@ -69,6 +71,7 @@
   time_t start;
   int priority;
   int lifetime;
+  time_t deleted;
   cRecording(cTimer *Timer, const cEvent *Event);
   cRecording(const char *FileName);
   virtual ~cRecording();
@@ -126,9 +129,11 @@
   cRecording *GetByName(const char *FileName);
   void AddByName(const char *FileName);
   void DelByName(const char *FileName);
+  int TotalFileSizeMB(void); ///< Only for deleted recordings!
   };
 
 extern cRecordings Recordings;
+extern cRecordings DeletedRecordings;
 
 class cMark : public cListObject {
 public:
diff -ruN vdr-1.3.37/remote.c vdr-1.3.38/remote.c
--- vdr-1.3.37/remote.c	2005-09-03 14:29:48.000000000 +0200
+++ vdr-1.3.38/remote.c	2006-01-01 15:21:07.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: remote.c 1.45 2005/09/03 12:29:48 kls Exp $
+ * $Id: remote.c 1.46 2006/01/01 14:21:07 kls Exp $
  */
 
 #include "remote.h"
@@ -262,43 +262,72 @@
   return (Code <= 0xFF) ? Code : kfNone;
 }
 
-void cKbdRemote::Action(void)
+int cKbdRemote::ReadKey(void)
 {
   cPoller Poller(STDIN_FILENO);
+  if (Poller.Poll(50)) {
+     uchar ch = 0;
+     int r = safe_read(STDIN_FILENO, &ch, 1);
+     if (r == 1)
+        return ch;
+     if (r < 0)
+        LOG_ERROR_STR("cKbdRemote");
+     }
+  return -1;
+}
+
+uint64 cKbdRemote::ReadKeySequence(void)
+{
+  uint64 k = 0;
+  int key1;
+
+  if ((key1 = ReadKey()) >= 0) {
+     k = key1;
+     if (key1 == 0x1B) {
+         // Start of escape sequence
+         if ((key1 = ReadKey()) >= 0) {
+            k <<= 8;
+            k |= key1 & 0xFF;
+            switch (key1) {
+              case 0x4F: // 3-byte sequence
+                   if ((key1 = ReadKey()) >= 0) {
+                      k <<= 8;
+                      k |= key1 & 0xFF;
+                      }
+                   break;
+              case 0x5B: // 3- or more-byte sequence
+                   if ((key1 = ReadKey()) >= 0) {
+                      k <<= 8;
+                      k |= key1 & 0xFF;
+                      switch (key1) {
+                        case 0x31 ... 0x3F: // more-byte sequence
+                             while (key1 != 0x7E) {
+                                   k <<= 8;
+                                   k |= key1 & 0xFF;
+                                   if ((key1 = ReadKey()) < 0)
+                                      break; // Sequence ends here
+                                   }
+                             break;
+                        }
+                      }
+                   break;
+              }
+            }
+        }
+     }
+  return k;
+}
+
+void cKbdRemote::Action(void)
+{
   while (Running()) {
-        if (Poller.Poll(100)) {
-           uint64 Command = 0;
-           uint i = 0;
-           while (Running() && i < sizeof(Command)) {
-                 uchar ch;
-                 int r = read(STDIN_FILENO, &ch, 1);
-                 if (r == 1) {
-                    Command <<= 8;
-                    Command |= ch;
-                    i++;
-                    }
-                 else if (r == 0) {
-                    // don't know why, but sometimes special keys that start with
-                    // 0x1B ('ESC') cause a short gap between the 0x1B and the rest
-                    // of their codes, so we'll need to wait some 100ms to see if
-                    // there is more coming up - or whether this really is the 'ESC'
-                    // key (if somebody knows how to clean this up, please let me know):
-                    if (Command == 0x1B && Poller.Poll(100))
-                       continue;
-                    if (Command) {
-                       if (rawMode || !Put(Command)) {
-                          int func = MapCodeToFunc(Command);
-                          if (func)
-                             Put(KBDKEY(func));
-                          }
-                       }
-                    break;
-                    }
-                 else {
-                    LOG_ERROR;
-                    break;
-                    }
-                 }
+        uint64 Command = ReadKeySequence();
+        if (Command) {
+           if (rawMode || !Put(Command)) {
+              int func = MapCodeToFunc(Command);
+              if (func)
+                 Put(KBDKEY(func));
+              }
            }
         }
 }
diff -ruN vdr-1.3.37/remote.h vdr-1.3.38/remote.h
--- vdr-1.3.37/remote.h	2005-09-03 14:28:42.000000000 +0200
+++ vdr-1.3.38/remote.h	2006-01-01 15:00:50.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: remote.h 1.31 2005/09/03 12:28:42 kls Exp $
+ * $Id: remote.h 1.32 2006/01/01 14:00:50 kls Exp $
  */
 
 #ifndef __REMOTE_H
@@ -89,6 +89,8 @@
   static bool rawMode;
   struct termios savedTm;
   virtual void Action(void);
+  int ReadKey(void);
+  uint64 ReadKeySequence(void);
   int MapCodeToFunc(uint64 Code);
 public:
   cKbdRemote(void);
diff -ruN vdr-1.3.37/remux.c vdr-1.3.38/remux.c
--- vdr-1.3.37/remux.c	2005-09-11 15:26:50.000000000 +0200
+++ vdr-1.3.38/remux.c	2006-01-08 12:40:16.000000000 +0100
@@ -11,7 +11,7 @@
  * The cRepacker family's code was originally written by Reinhard Nissl <rnissl@gmx.de>,
  * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de.
  *
- * $Id: remux.c 1.47 2005/09/11 13:26:50 kls Exp $
+ * $Id: remux.c 1.53 2006/01/08 11:40:16 kls Exp $
  */
 
 #include "remux.h"
@@ -88,15 +88,21 @@
 
 // --- cRepacker -------------------------------------------------------------
 
+#define MIN_LOG_INTERVAL 10 // min. # of seconds between two consecutive log messages of a cRepacker
+#define LOG(a...) (LogAllowed() && (esyslog(a), true))
+
 class cRepacker {
 protected:
   bool initiallySyncing;
   int maxPacketSize;
   uint8_t subStreamId;
-  static void DroppedData(const char *Reason, int Count) { esyslog("%s (dropped %d bytes)", Reason, Count); }
+  time_t lastLog;
+  int suppressedLogMessages;
+  bool LogAllowed(void);
+  void DroppedData(const char *Reason, int Count) { LOG("%s (dropped %d bytes)", Reason, Count); }
 public:
   static int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded);
-  cRepacker(void) { initiallySyncing = true; maxPacketSize = 6 + 65535; subStreamId = 0; }
+  cRepacker(void);
   virtual ~cRepacker() {}
   virtual void Reset(void) { initiallySyncing = true; }
   virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) = 0;
@@ -106,6 +112,30 @@
   void SetSubStreamId(uint8_t SubStreamId) { subStreamId = SubStreamId; }
   };
 
+cRepacker::cRepacker(void)
+{
+  initiallySyncing = true;
+  maxPacketSize = 6 + 65535;
+  subStreamId = 0;
+  suppressedLogMessages = 0;;
+  lastLog = 0;
+}
+
+bool cRepacker::LogAllowed(void)
+{
+  bool Allowed = time(NULL) - lastLog >= MIN_LOG_INTERVAL;
+  lastLog = time(NULL);
+  if (Allowed) {
+     if (suppressedLogMessages) {
+        esyslog("%d cRepacker messages suppressed", suppressedLogMessages);
+        suppressedLogMessages = 0;
+        }
+     }
+  else
+     suppressedLogMessages++;
+  return Allowed;
+}
+
 int cRepacker::Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded)
 {
   if (CapacityNeeded >= Count && ResultBuffer->Free() < CapacityNeeded) {
@@ -160,7 +190,7 @@
      // just skip packets with no payload
      int PesPayloadOffset = 0;
      if (AnalyzePesHeader(fragmentData, fragmentLen, PesPayloadOffset) <= phInvalid)
-        esyslog("cCommonRepacker: invalid PES packet encountered in fragment buffer!");
+        LOG("cCommonRepacker: invalid PES packet encountered in fragment buffer!");
      else if (6 + PacketLen <= PesPayloadOffset) {
         fragmentLen = 0;
         return true; // skip empty packet
@@ -181,7 +211,7 @@
      // just skip packets with no payload
      int PesPayloadOffset = 0;
      if (AnalyzePesHeader(pesHeader, pesHeaderLen, PesPayloadOffset) <= phInvalid)
-        esyslog("cCommonRepacker: invalid PES packet encountered in header buffer!");
+        LOG("cCommonRepacker: invalid PES packet encountered in header buffer!");
      else if (6 + PacketLen <= PesPayloadOffset) {
         pesHeaderLen = 0;
         return true; // skip empty packet
@@ -216,7 +246,8 @@
     syncing,
     findPicture,
     scanPicture
-    } state;
+    };
+  int state;
 public:
   cVideoRepacker(void);
   virtual void Reset(void);
@@ -278,14 +309,14 @@
            // which kind of start code have we got?
            switch (*data) {
              case 0xB9 ... 0xFF: // system start codes
-                  esyslog("cVideoRepacker: found system start code: stream seems to be scrambled or not demultiplexed");
+                  LOG("cVideoRepacker: found system start code: stream seems to be scrambled or not demultiplexed");
                   break;
              case 0xB0 ... 0xB1: // reserved start codes
              case 0xB6:
-                  esyslog("cVideoRepacker: found reserved start code: stream seems to be scrambled");
+                  LOG("cVideoRepacker: found reserved start code: stream seems to be scrambled");
                   break;
              case 0xB4: // sequence error code
-                  isyslog("cVideoRepacker: found sequence error code: stream seems to be damaged");
+                  LOG("cVideoRepacker: found sequence error code: stream seems to be damaged");
              case 0xB2: // user data start code
              case 0xB5: // extension start code
                   break;
@@ -307,7 +338,7 @@
                      if (initiallySyncing) // omit report for the typical initial case
                         initiallySyncing = false;
                      else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes
-                        esyslog("cVideoRepacker: skipped %d bytes to sync on next picture", skippedBytes - SkippedBytesLimit);
+                        LOG("cVideoRepacker: skipped %d bytes to sync on next picture", skippedBytes - SkippedBytesLimit);
                      skippedBytes = 0;
                      // if there is a PES header available, then use it ...
                      if (pesHeaderBackupLen > 0) {
@@ -350,13 +381,13 @@
                      // maximum we can hold in one PES packet
                      packetTodo = maxPacketSize - pesHeaderLen;
                      // go on with finding the picture data
-                     ((int &)state)++;
+                     state++;
                      }
                   break;
              case 0x01 ... 0xAF: // slice start codes
                   if (state == findPicture) {
                      // go on with scanning the picture data
-                     ((int &)state)++;
+                     state++;
                      }
                   break;
              }
@@ -465,7 +496,7 @@
   // report that syncing dropped some bytes
   if (skippedBytes > SkippedBytesLimit) {
      if (!initiallySyncing) // omit report for the typical initial case
-        esyslog("cVideoRepacker: skipped %d bytes while syncing on next picture", skippedBytes - SkippedBytesLimit);
+        LOG("cVideoRepacker: skipped %d bytes while syncing on next picture", skippedBytes - SkippedBytesLimit);
      skippedBytes = SkippedBytesLimit;
      }
 }
@@ -517,7 +548,8 @@
   enum eState {
     syncing,
     scanFrame
-    } state;
+    };
+  int state;
   int frameTodo;
   int frameSize;
   int cid;
@@ -674,7 +706,7 @@
                  if (initiallySyncing) // omit report for the typical initial case
                     initiallySyncing = false;
                  else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes
-                    esyslog("cAudioRepacker(0x%02X): skipped %d bytes to sync on next audio frame", cid, skippedBytes - SkippedBytesLimit);
+                    LOG("cAudioRepacker(0x%02X): skipped %d bytes to sync on next audio frame", cid, skippedBytes - SkippedBytesLimit);
                  skippedBytes = 0;
                  // if there is a PES header available, then use it ...
                  if (pesHeaderBackupLen > 0) {
@@ -714,10 +746,10 @@
                  payload = data;
                  // maximum we can hold in one PES packet
                  packetTodo = maxPacketSize - pesHeaderLen;
-                 // expected remainder of audio frame: so far we have read 3 bytes from the frame header 
+                 // expected remainder of audio frame: so far we have read 3 bytes from the frame header
                  frameTodo = frameSize - 3;
                  // go on with collecting the frame's data
-                 ((int &)state)++;
+                 state++;
                  }
               }
            }
@@ -832,7 +864,7 @@
   // report that syncing dropped some bytes
   if (skippedBytes > SkippedBytesLimit) {
      if (!initiallySyncing) // omit report for the typical initial case
-        esyslog("cAudioRepacker(0x%02X): skipped %d bytes while syncing on next audio frame", cid, skippedBytes - SkippedBytesLimit);
+        LOG("cAudioRepacker(0x%02X): skipped %d bytes while syncing on next audio frame", cid, skippedBytes - SkippedBytesLimit);
      skippedBytes = SkippedBytesLimit;
      }
 }
@@ -898,14 +930,15 @@
   uchar chk1;
   uchar chk2;
   int ac3todo;
-  enum {
+  enum eState {
     find_0b,
     find_77,
     store_chk1,
     store_chk2,
     get_length,
     output_packet
-    } state;
+    };
+  int state;
   int skippedBytes;
   void ResetPesHeader(bool ContinuationFrame = false);
   void AppendSubStreamID(bool ContinuationFrame = false);
@@ -1090,7 +1123,7 @@
         switch (state) {
           case find_0b:
                if (*data == 0x0B) {
-                  ++(int &)state;
+                  state++;
                   // copy header information once for later use
                   if (pesHeaderBackupLen > 0) {
                      pesHeaderLen = pesHeaderBackupLen;
@@ -1113,21 +1146,21 @@
                done++;
                todo--;
                skippedBytes++; // collect number of skipped bytes while syncing
-               ++(int &)state;
+               state++;
                continue;
           case store_chk1:
                chk1 = *data++;
                done++;
                todo--;
                skippedBytes++; // collect number of skipped bytes while syncing
-               ++(int &)state;
+               state++;
                continue;
           case store_chk2:
                chk2 = *data++;
                done++;
                todo--;
                skippedBytes++; // collect number of skipped bytes while syncing
-               ++(int &)state;
+               state++;
                continue;
           case get_length:
                ac3todo = 2 * frameSizes[*data];
@@ -1157,7 +1190,7 @@
                if (initiallySyncing) // omit report for the typical initial case
                   initiallySyncing = false;
                else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes
-                  esyslog("cDolbyRepacker: skipped %d bytes to sync on next AC3 frame", skippedBytes - SkippedBytesLimit);
+                  LOG("cDolbyRepacker: skipped %d bytes to sync on next AC3 frame", skippedBytes - SkippedBytesLimit);
                skippedBytes = 0;
                // append read data to header for common output processing
                pesHeader[pesHeaderLen++] = 0x0B;
@@ -1165,7 +1198,7 @@
                pesHeader[pesHeaderLen++] = chk1;
                pesHeader[pesHeaderLen++] = chk2;
                ac3todo -= 4;
-               ++(int &)state;
+               state++;
                // fall through to output
           case output_packet: {
                int bite = 0;
@@ -1188,7 +1221,7 @@
   // report that syncing dropped some bytes
   if (skippedBytes > SkippedBytesLimit) {
      if (!initiallySyncing) // omit report for the typical initial case
-        esyslog("cDolbyRepacker: skipped %d bytes while syncing on next AC3 frame", skippedBytes - 4);
+        LOG("cDolbyRepacker: skipped %d bytes while syncing on next AC3 frame", skippedBytes - 4);
      skippedBytes = SkippedBytesLimit;
      }
 }
@@ -1248,9 +1281,10 @@
 #define CONT_CNT_MASK  0x0F
 
 // Flags:
+#define PAY_LOAD       0x10
+#define ADAPT_FIELD    0x20
 #define PAY_START      0x40
 #define TS_ERROR       0x80
-#define ADAPT_FIELD    0x20
 
 #define MAX_PLENGTH  0xFFFF          // the maximum PES packet length (theoretically)
 #define MMAX_PLENGTH (64*MAX_PLENGTH) // some stations send PES packets that are extremely large, e.g. DVB-T in Finland or HDTV 1920x1080
@@ -1635,7 +1669,11 @@
 
   if (Buf[1] & TS_ERROR)
      tsErrors++;
-  if ((Buf[3] ^ ccCounter) & CONT_CNT_MASK) {
+
+  if (!(Buf[3] & (ADAPT_FIELD | PAY_LOAD)))
+     return; // discard TS packet with adaption_field_control set to '00'.
+
+  if ((Buf[3] & PAY_LOAD) && ((Buf[3] ^ ccCounter) & CONT_CNT_MASK)) {
      // This should check duplicates and packets which do not increase the counter.
      // But as the errors usually come in bursts this should be enough to
      // show you there is something wrong with signal quality.
@@ -1650,12 +1688,14 @@
      }
 
   if (Buf[1] & PAY_START) {
-     if (plength == MMAX_PLENGTH - 6 && found > 6) {
+     if (found > 6) {
+        if (plength != MMAX_PLENGTH - 6 && plength != found - 6)
+           dsyslog("PES packet shortened to %d bytes (expected: %d bytes)", found, plength + 6);
         plength = found - 6;
-        found = 0;
         send_ipack();
         reset_ipack();
         }
+     found = 0;
      }
 
   uint8_t off = 0;
@@ -1666,7 +1706,8 @@
         return;
      }
 
-  instant_repack(Buf + 4 + off, TS_SIZE - 4 - off);
+  if (Buf[3] & PAY_LOAD)
+     instant_repack(Buf + 4 + off, TS_SIZE - 4 - off);
 }
 
 // --- cRemux ----------------------------------------------------------------
diff -ruN vdr-1.3.37/ringbuffer.c vdr-1.3.38/ringbuffer.c
--- vdr-1.3.37/ringbuffer.c	2004-10-15 15:49:25.000000000 +0200
+++ vdr-1.3.38/ringbuffer.c	2005-12-30 16:42:08.000000000 +0100
@@ -7,7 +7,7 @@
  * Parts of this file were inspired by the 'ringbuffy.c' from the
  * LinuxDVB driver (see linuxtv.org).
  *
- * $Id: ringbuffer.c 1.21 2004/10/15 13:49:25 kls Exp $
+ * $Id: ringbuffer.c 1.23 2005/12/30 15:42:08 kls Exp $
  */
 
 #include "ringbuffer.h"
@@ -46,7 +46,7 @@
   int percent = Fill * 100 / (Size() - 1) / PERCENTAGEDELTA * PERCENTAGEDELTA;
   if (percent != lastPercent) {
      if (percent >= PERCENTAGETHRESHOLD && percent > lastPercent || percent < PERCENTAGETHRESHOLD && lastPercent >= PERCENTAGETHRESHOLD) {
-        dsyslog("buffer usage: %d%% (tid=%ld)", percent, getThreadTid);
+        dsyslog("buffer usage: %d%% (tid=%d)", percent, getThreadTid);
         lastPercent = percent;
         }
      }
@@ -165,10 +165,10 @@
         Clear();
         }
      else
-        esyslog("ERROR: illegal margin for ring buffer (%d > %d)", Margin, Size / 2);
+        esyslog("ERROR: invalid margin for ring buffer (%d > %d)", Margin, Size / 2);
      }
   else
-     esyslog("ERROR: illegal size for ring buffer (%d)", Size);
+     esyslog("ERROR: invalid size for ring buffer (%d)", Size);
 #ifdef DEBUGRINGBUFFERS
   lastHead = head;
   lastTail = tail;
@@ -286,7 +286,7 @@
   uchar *p = NULL;
   int Head = head;
   if (getThreadTid <= 0)
-     getThreadTid = pthread_self();
+     getThreadTid = cThread::ThreadId();
   int rest = Size() - tail;
   if (rest < margin && Head < tail) {
      int t = margin - rest;
diff -ruN vdr-1.3.37/ringbuffer.h vdr-1.3.38/ringbuffer.h
--- vdr-1.3.37/ringbuffer.h	2004-10-15 15:50:46.000000000 +0200
+++ vdr-1.3.38/ringbuffer.h	2005-12-10 11:54:51.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: ringbuffer.h 1.16 2004/10/15 13:50:46 kls Exp $
+ * $Id: ringbuffer.h 1.17 2005/12/10 10:54:51 kls Exp $
  */
 
 #ifndef __RINGBUFFER_H
@@ -23,7 +23,7 @@
   int overflowCount;
   int overflowBytes;
 protected:
-  pthread_t getThreadTid;
+  tThreadId getThreadTid;
   int maxFill;//XXX
   int lastPercent;
   bool statistics;//XXX
diff -ruN vdr-1.3.37/runvdr vdr-1.3.38/runvdr
--- vdr-1.3.37/runvdr	2004-11-21 12:30:00.000000000 +0100
+++ vdr-1.3.38/runvdr	2005-12-31 17:04:53.000000000 +0100
@@ -7,7 +7,7 @@
 #
 # Set the environment variable VDRUSR to the user id you
 # want VDR to run with. If VDRUSR is not set, VDR will run
-# as 'root', which is not necessarily advisable.
+# as user 'vdr'.
 #
 # Since this script loads the DVB driver, it must be started
 # as user 'root'.
@@ -18,11 +18,11 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: runvdr 1.14 2004/11/21 11:30:00 kls Exp $
+# $Id: runvdr 1.15 2005/12/31 16:04:53 kls Exp $
 
 DVBDIR="../DVB/driver"
 VDRPRG="./vdr"
-VDRCMD="$VDRPRG -w 60 $*"
+VDRCMD="$VDRPRG -u $VDRUSR -w 60 $*"
 
 LSMOD="`/sbin/lsmod | grep -w '^dvb' | wc -l`"
 KILL="/usr/bin/killall -q -TERM"
@@ -33,7 +33,7 @@
    fi
 
 while (true) do
-      su $VDRUSR -c "$VDRCMD"
+      $VDRCMD
       if test $? -eq 0 -o $? -eq 2; then exit; fi
       date
       echo "restarting VDR"
diff -ruN vdr-1.3.37/skinclassic.c vdr-1.3.38/skinclassic.c
--- vdr-1.3.37/skinclassic.c	2005-05-16 12:45:07.000000000 +0200
+++ vdr-1.3.38/skinclassic.c	2006-01-01 15:37:58.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skinclassic.c 1.12 2005/05/16 10:45:07 kls Exp $
+ * $Id: skinclassic.c 1.13 2006/01/01 14:37:58 kls Exp $
  */
 
 #include "skinclassic.h"
@@ -378,7 +378,6 @@
   int x0, x1;
   int y0, y1, y2, y3;
   int lastCurrentWidth;
-  bool message;
 public:
   cSkinClassicDisplayReplay(bool ModeOnly);
   virtual ~cSkinClassicDisplayReplay();
@@ -397,7 +396,6 @@
   const cFont *font = cFont::GetFont(fontOsd);
   int lineHeight = font->Height();
   lastCurrentWidth = 0;
-  message = false;
   x0 = 0;
   x1 = Setup.OSDWidth;
   y0 = 0;
@@ -467,12 +465,9 @@
   if (Text) {
      osd->SaveRegion(x0, y2, x1 - 1, y3 - 1);
      osd->DrawText(x0, y2, Text, Theme.Color(clrMessageStatusFg + 2 * Type), Theme.Color(clrMessageStatusBg + 2 * Type), font, x1 - x0, y3 - y2, taCenter);
-     message = true;
      }
-  else {
+  else
      osd->RestoreRegion();
-     message = false;
-     }
 }
 
 void cSkinClassicDisplayReplay::Flush(void)
diff -ruN vdr-1.3.37/skins.c vdr-1.3.38/skins.c
--- vdr-1.3.37/skins.c	2005-11-27 16:52:25.000000000 +0100
+++ vdr-1.3.38/skins.c	2006-01-08 12:40:18.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skins.c 1.6 2005/11/27 15:52:25 kls Exp $
+ * $Id: skins.c 1.7 2006/01/08 11:40:18 kls Exp $
  */
 
 #include "skins.h"
@@ -323,7 +323,7 @@
          }
       else
          break;
-      } 
+      }
   queueMessageMutex.Unlock();
 }
 
diff -ruN vdr-1.3.37/skins.h vdr-1.3.38/skins.h
--- vdr-1.3.37/skins.h	2005-11-27 16:41:44.000000000 +0100
+++ vdr-1.3.38/skins.h	2006-01-08 12:40:21.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skins.h 1.9 2005/11/27 15:41:44 kls Exp $
+ * $Id: skins.h 1.10 2006/01/08 11:40:21 kls Exp $
  */
 
 #ifndef __SKINS_H
@@ -197,7 +197,7 @@
        ///< take care that it is erased from the display when a Current string
        ///< _with_ ".ff" is followed by one without it.
   virtual void SetTotal(const char *Total) = 0;
-       ///< Sets the total length of the recording, as a user readable 
+       ///< Sets the total length of the recording, as a user readable
        ///< string if the form "h:mm:ss".
   virtual void SetJump(const char *Jump) = 0;
        ///< Sets the prompt that allows the user to enter a jump point.
diff -ruN vdr-1.3.37/skinsttng.c vdr-1.3.38/skinsttng.c
--- vdr-1.3.37/skinsttng.c	2005-08-15 13:14:59.000000000 +0200
+++ vdr-1.3.38/skinsttng.c	2006-01-01 15:38:14.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skinsttng.c 1.15 2005/08/15 11:14:59 kls Exp $
+ * $Id: skinsttng.c 1.16 2006/01/01 14:38:14 kls Exp $
  */
 
 // Star Trek: The Next Generation® is a registered trademark of Paramount Pictures
@@ -643,7 +643,6 @@
   int y0, y1, y2, y3, y4, y5, y6, y7;
   tColor frameColor;
   int lastCurrentWidth;
-  bool message;
 public:
   cSkinSTTNGDisplayReplay(bool ModeOnly);
   virtual ~cSkinSTTNGDisplayReplay();
@@ -666,7 +665,6 @@
   int lineHeight = font->Height();
   frameColor = Theme.Color(clrReplayFrame);
   lastCurrentWidth = 0;
-  message = false;
   cBitmap bm(play_xpm);
   x0 = 0;
   x1 = max(SymbolWidth, bm.Width());
@@ -772,12 +770,9 @@
      osd->SaveRegion(x2, y6, x4 - 1, y7 - 1);
      osd->DrawRectangle(x2, y6, x3 - 1, y7 - 1, Theme.Color(clrBackground));
      osd->DrawText(x3, y6, Text, Theme.Color(clrMessageStatusFg + 2 * Type), Theme.Color(clrMessageStatusBg + 2 * Type), font, x4 - x3, 0, taCenter);
-     message = true;
      }
-  else {
+  else
      osd->RestoreRegion();
-     message = false;
-     }
 }
 
 void cSkinSTTNGDisplayReplay::Flush(void)
diff -ruN vdr-1.3.37/status.c vdr-1.3.38/status.c
--- vdr-1.3.37/status.c	2005-01-09 12:51:04.000000000 +0100
+++ vdr-1.3.38/status.c	2005-12-31 16:10:10.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: status.c 1.7 2005/01/09 11:51:04 kls Exp $
+ * $Id: status.c 1.8 2005/12/31 15:10:10 kls Exp $
  */
 
 #include "status.h"
@@ -29,16 +29,16 @@
       sm->ChannelSwitch(Device, ChannelNumber);
 }
 
-void cStatus::MsgRecording(const cDevice *Device, const char *Name)
+void cStatus::MsgRecording(const cDevice *Device, const char *Name, const char *FileName, bool On)
 {
   for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
-      sm->Recording(Device, Name);
+      sm->Recording(Device, Name, FileName, On);
 }
 
-void cStatus::MsgReplaying(const cControl *Control, const char *Name)
+void cStatus::MsgReplaying(const cControl *Control, const char *Name, const char *FileName, bool On)
 {
   for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
-      sm->Replaying(Control, Name);
+      sm->Replaying(Control, Name, FileName, On);
 }
 
 void cStatus::MsgSetVolume(int Volume, bool Absolute)
diff -ruN vdr-1.3.37/status.h vdr-1.3.38/status.h
--- vdr-1.3.37/status.h	2005-01-09 12:50:21.000000000 +0100
+++ vdr-1.3.38/status.h	2005-12-31 16:15:25.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: status.h 1.8 2005/01/09 11:50:21 kls Exp $
+ * $Id: status.h 1.9 2005/12/31 15:15:25 kls Exp $
  */
 
 #ifndef __STATUS_H
@@ -24,15 +24,17 @@
                // Indicates a channel switch on the given DVB device.
                // If ChannelNumber is 0, this is before the channel is being switched,
                // otherwise ChannelNumber is the number of the channel that has been switched to.
-  virtual void Recording(const cDevice *Device, const char *Name) {}
-               // The given DVB device has started recording Name. Name is the name of the
-               // recording, without any directory path.
-               // If Name is NULL, the recording has ended. 
-  virtual void Replaying(const cControl *Control, const char *Name) {}
-               // The given player control has started replaying Name. Name is the name of the
-               // recording, without any directory path. In case of a player that can't provide
+  virtual void Recording(const cDevice *Device, const char *Name, const char *FileName, bool On) {}
+               // The given DVB device has started (On = true) or stopped (On = false) recording Name.
+               // Name is the name of the recording, without any directory path. The full file name
+               // of the recording is given in FileName, which may be NULL in case there is no
+               // actual file involved. If On is false, Name may be NULL.
+  virtual void Replaying(const cControl *Control, const char *Name, const char *FileName, bool On) {}
+               // The given player control has started (On = true) or stopped (On = false) replaying Name.
+               // Name is the name of the recording, without any directory path. In case of a player that can't provide
                // a name, Name can be a string that identifies the player type (like, e.g., "DVD").
-               // If Name is NULL, the replay has ended. 
+               // The full file name of the recording is given in FileName, which may be NULL in case there is no
+               // actual file involved. If On is false, Name may be NULL.
   virtual void SetVolume(int Volume, bool Absolute) {}
                // The volume has been set to the given value, either
                // absolutely or relative to the current volume.
@@ -70,8 +72,8 @@
   virtual ~cStatus();
   // These functions are called whenever the related status information changes:
   static void MsgChannelSwitch(const cDevice *Device, int ChannelNumber);
-  static void MsgRecording(const cDevice *Device, const char *Name);
-  static void MsgReplaying(const cControl *Control, const char *Name);
+  static void MsgRecording(const cDevice *Device, const char *Name, const char *FileName, bool On);
+  static void MsgReplaying(const cControl *Control, const char *Name, const char *FileName, bool On);
   static void MsgSetVolume(int Volume, bool Absolute);
   static void MsgSetAudioTrack(int Index, const char * const *Tracks);
   static void MsgSetAudioChannel(int AudioChannel);
diff -ruN vdr-1.3.37/svdrp.c vdr-1.3.38/svdrp.c
--- vdr-1.3.37/svdrp.c	2005-11-27 16:29:28.000000000 +0100
+++ vdr-1.3.38/svdrp.c	2005-12-30 16:42:29.000000000 +0100
@@ -10,7 +10,7 @@
  * and interact with the Video Disk Recorder - or write a full featured
  * graphical interface that sits on top of an SVDRP connection.
  *
- * $Id: svdrp.c 1.84 2005/11/27 15:29:28 kls Exp $
+ * $Id: svdrp.c 1.89 2005/12/30 15:42:29 kls Exp $
  */
 
 #include "svdrp.h"
@@ -201,10 +201,16 @@
   "    Edit the recording with the given number. Before a recording can be\n"
   "    edited, an LSTR command must have been executed in order to retrieve\n"
   "    the recording numbers.",
-  "GRAB <filename> [ jpeg | pnm [ <quality> [ <sizex> <sizey> ] ] ]\n"
+  "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
   "    Grab the current frame and save it to the given file. Images can\n"
-  "    be stored as JPEG (default) or PNM, at the given quality (default\n"
-  "    is 'maximum', only applies to JPEG) and size (default is full screen).",
+  "    be stored as JPEG or PNM, depending on the given file name extension.\n"
+  "    The quality of the grabbed image can be in the range 0..100, where 100\n"
+  "    (the default) means \"best\" (only applies to JPEG). The size parameters\n"
+  "    define the size of the resulting image (default is full screen).\n"
+  "    If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
+  "    data will be sent to the SVDRP connection encoded in base64. The same\n"
+  "    happens if '-' (a minus sign) is given as file name, in which case the\n"
+  "    image format defaults to JPEG.",
   "HELP [ <topic> ]\n"
   "    The HELP command gives help info.",
   "HITK [ <key> ]\n"
@@ -307,6 +313,7 @@
 
  214 Help message
  215 EPG or recording data record
+ 216 Image grab data (base 64)
  220 VDR service ready
  221 VDR service closing transmission channel
  250 Requested VDR action okay, completed
@@ -354,6 +361,8 @@
   return NULL;
 }
 
+char *cSVDRP::grabImageDir = NULL;
+
 cSVDRP::cSVDRP(int Port)
 :socket(Port)
 {
@@ -656,36 +665,54 @@
      const char *delim = " \t";
      char *strtok_next;
      FileName = strtok_r(p, delim, &strtok_next);
-     if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
-        if (strcasecmp(p, "JPEG") == 0)
+     // image type:
+     char *Extension = strrchr(FileName, '.');
+     if (Extension) {
+        if (strcasecmp(Extension, ".jpg") == 0 || strcasecmp(Extension, ".jpeg") == 0)
            Jpeg = true;
-        else if (strcasecmp(p, "PNM") == 0)
+        else if (strcasecmp(Extension, ".pnm") == 0)
            Jpeg = false;
         else {
-           Reply(501, "Unknown image type \"%s\"", p);
+           Reply(501, "Unknown image type \"%s\"", Extension + 1);
            return;
            }
+        if (Extension == FileName)
+           FileName = NULL;
+        }
+     else if (strcmp(FileName, "-") == 0)
+        FileName = NULL;
+     else {
+        Reply(501, "Missing filename extension in \"%s\"", FileName);
+        return;
         }
+     // image quality (and obsolete type):
      if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
-        if (isnumber(p))
-           Quality = atoi(p);
-        else {
-           Reply(501, "Illegal quality \"%s\"", p);
-           return;
+        if (strcasecmp(p, "JPEG") == 0 || strcasecmp(p, "PNM") == 0) {
+           // tolerate for backward compatibility
+           p = strtok_r(NULL, delim, &strtok_next);
+           }
+        if (p) {
+           if (isnumber(p))
+              Quality = atoi(p);
+           else {
+              Reply(501, "Invalid quality \"%s\"", p);
+              return;
+              }
            }
         }
+     // image size:
      if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
         if (isnumber(p))
            SizeX = atoi(p);
         else {
-           Reply(501, "Illegal sizex \"%s\"", p);
+           Reply(501, "Invalid sizex \"%s\"", p);
            return;
            }
         if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
            if (isnumber(p))
               SizeY = atoi(p);
            else {
-              Reply(501, "Illegal sizey \"%s\"", p);
+              Reply(501, "Invalid sizey \"%s\"", p);
               return;
               }
            }
@@ -698,8 +725,67 @@
         Reply(501, "Unexpected parameter \"%s\"", p);
         return;
         }
-     if (cDevice::PrimaryDevice()->GrabImage(FileName, Jpeg, Quality, SizeX, SizeY))
-        Reply(250, "Grabbed image %s", Option);
+     // canonicalize the file name:
+     char RealFileName[PATH_MAX];
+     if (FileName) {
+        if (grabImageDir) {
+           char *s;
+           asprintf(&s, "%s/%s", grabImageDir, FileName);
+           FileName = s;
+           char *slash = strrchr(FileName, '/'); // there definitely is one
+           *slash = 0;
+           char *r = realpath(FileName, RealFileName);
+           *slash = '/';
+           if (!r) {
+              LOG_ERROR_STR(FileName);
+              Reply(501, "Invalid file name \"%s\"", FileName);
+              free(s);
+              return;
+              }
+           strcat(RealFileName, slash);
+           FileName = RealFileName;
+           free(s);
+           if (strncmp(FileName, grabImageDir, strlen(grabImageDir)) != 0) {
+              Reply(501, "Invalid file name \"%s\"", FileName);
+              return;
+              }
+           }
+        else {
+           Reply(550, "Grabbing to file not allowed (use \"GRAB -\" instead)");
+           return;
+           }
+        }
+     // actual grabbing:
+     int ImageSize;
+     uchar *Image = cDevice::PrimaryDevice()->GrabImage(ImageSize, Jpeg, Quality, SizeX, SizeY);
+     if (Image) {
+        if (FileName) {
+           int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
+           if (fd >= 0) {
+              if (safe_write(fd, Image, ImageSize) == ImageSize) {
+                 isyslog("grabbed image to %s", FileName);
+                 Reply(250, "Grabbed image %s", Option);
+                 }
+              else {
+                 LOG_ERROR_STR(FileName);
+                 Reply(451, "Can't write to '%s'", FileName);
+                 }
+              close(fd);
+              }
+           else {
+              LOG_ERROR_STR(FileName);
+              Reply(451, "Can't open '%s'", FileName);
+              }
+           }
+        else {
+           cBase64Encoder Base64(Image, ImageSize);
+           const char *s;
+           while ((s = Base64.NextLine()) != NULL)
+                 Reply(-216, s);
+           Reply(216, "Grabbed image %s", Option);
+           }
+        free(Image);
+        }
      else
         Reply(451, "Grab image failed");
      }
@@ -1482,4 +1568,10 @@
   return false;
 }
 
+void cSVDRP::SetGrabImageDir(const char *GrabImageDir)
+{
+  free(grabImageDir);
+  grabImageDir = GrabImageDir ? strdup(GrabImageDir) : NULL;
+}
+
 //TODO more than one connection???
diff -ruN vdr-1.3.37/svdrp.h vdr-1.3.38/svdrp.h
--- vdr-1.3.37/svdrp.h	2005-11-27 16:26:42.000000000 +0100
+++ vdr-1.3.38/svdrp.h	2005-12-30 15:46:38.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: svdrp.h 1.26 2005/11/27 15:26:42 kls Exp $
+ * $Id: svdrp.h 1.27 2005/12/30 14:46:38 kls Exp $
  */
 
 #ifndef __SVDRP_H
@@ -49,6 +49,7 @@
   int length;
   char *cmdLine;
   time_t lastActivity;
+  static char *grabImageDir;
   void Close(bool Timeout = false);
   bool Send(const char *s, int length = -1);
   void Reply(int Code, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
@@ -87,6 +88,7 @@
   ~cSVDRP();
   bool HasConnection(void) { return file.IsOpen(); }
   bool Process(void);
+  static void SetGrabImageDir(const char *GrabImageDir);
   };
 
 #endif //__SVDRP_H
diff -ruN vdr-1.3.37/thread.c vdr-1.3.38/thread.c
--- vdr-1.3.37/thread.c	2005-11-27 16:15:53.000000000 +0100
+++ vdr-1.3.38/thread.c	2006-01-04 16:01:22.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: thread.c 1.46 2005/11/27 15:15:53 kls Exp $
+ * $Id: thread.c 1.50 2006/01/04 15:01:22 kls Exp $
  */
 
 #include "thread.h"
@@ -12,6 +12,7 @@
 #include <malloc.h>
 #include <stdarg.h>
 #include <sys/resource.h>
+#include <sys/syscall.h>
 #include <sys/time.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -136,7 +137,9 @@
 
 cRwLock::cRwLock(bool PreferWriter)
 {
-  pthread_rwlockattr_t attr = { PreferWriter ? PTHREAD_RWLOCK_PREFER_WRITER_NP : PTHREAD_RWLOCK_PREFER_READER_NP };
+  pthread_rwlockattr_t attr;
+  pthread_rwlockattr_init(&attr);
+  pthread_rwlockattr_setkind_np(&attr, PreferWriter ? PTHREAD_RWLOCK_PREFER_WRITER_NP : PTHREAD_RWLOCK_PREFER_READER_NP);
   pthread_rwlock_init(&rwlock, &attr);
 }
 
@@ -170,7 +173,9 @@
 cMutex::cMutex(void)
 {
   locked = 0;
-  pthread_mutexattr_t attr = { PTHREAD_MUTEX_ERRORCHECK_NP };
+  pthread_mutexattr_t attr;
+  pthread_mutexattr_init(&attr);
+  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
   pthread_mutex_init(&mutex, &attr);
 }
 
@@ -193,13 +198,14 @@
 
 // --- cThread ---------------------------------------------------------------
 
-tThreadId cThread::mainThreadId = cThread::ThreadId();
+tThreadId cThread::mainThreadId = 0;
 bool cThread::emergencyExitRequested = false;
 
 cThread::cThread(const char *Description)
 {
   active = running = false;
   childTid = 0;
+  childThreadId = 0;
   description = NULL;
   SetDescription(Description);
 }
@@ -230,11 +236,12 @@
 
 void *cThread::StartThread(cThread *Thread)
 {
+  Thread->childThreadId = ThreadId();
   if (Thread->description)
-     dsyslog("%s thread started (pid=%d, tid=%ld)", Thread->description, getpid(), pthread_self());
+     dsyslog("%s thread started (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId);
   Thread->Action();
   if (Thread->description)
-     dsyslog("%s thread ended (pid=%d, tid=%ld)", Thread->description, getpid(), pthread_self());
+     dsyslog("%s thread ended (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId);
   Thread->running = false;
   Thread->active = false;
   return NULL;
@@ -292,7 +299,7 @@
                return;
             cCondWait::SleepMs(10);
             }
-        esyslog("ERROR: thread %ld won't end (waited %d seconds) - canceling it...", childTid, WaitSeconds);
+        esyslog("ERROR: thread %d won't end (waited %d seconds) - canceling it...", childThreadId, WaitSeconds);
         }
      pthread_cancel(childTid);
      childTid = 0;
@@ -308,6 +315,21 @@
   return emergencyExitRequested = true; // yes, it's an assignment, not a comparison!
 }
 
+_syscall0(pid_t, gettid)
+
+tThreadId cThread::ThreadId(void)
+{
+  return gettid();
+}
+
+void cThread::SetMainThreadId(void)
+{
+  if (mainThreadId == 0)
+     mainThreadId = ThreadId();
+  else
+     esyslog("ERROR: attempt to set main thread id to %d while it already is %d", ThreadId(), mainThreadId);
+}
+
 // --- cMutexLock ------------------------------------------------------------
 
 cMutexLock::cMutexLock(cMutex *Mutex)
diff -ruN vdr-1.3.37/thread.h vdr-1.3.38/thread.h
--- vdr-1.3.37/thread.h	2005-11-27 16:16:50.000000000 +0100
+++ vdr-1.3.38/thread.h	2006-01-08 12:40:23.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: thread.h 1.32 2005/11/27 15:16:50 kls Exp $
+ * $Id: thread.h 1.36 2006/01/08 11:40:23 kls Exp $
  */
 
 #ifndef __THREAD_H
@@ -72,7 +72,7 @@
   void Unlock(void);
   };
 
-typedef pthread_t tThreadId;
+typedef pid_t tThreadId;
 
 class cThread {
   friend class cThreadLock;
@@ -80,6 +80,7 @@
   bool active;
   bool running;
   pthread_t childTid;
+  tThreadId childThreadId;
   cMutex mutex;
   char *description;
   static tThreadId mainThreadId;
@@ -106,7 +107,7 @@
   cThread(const char *Description = NULL);
        ///< Creates a new thread.
        ///< If Description is present, a log file entry will be made when
-       ///< the thread starts and stops. The Start() function must be called 
+       ///< the thread starts and stops. The Start() function must be called
        ///< to actually start the thread.
   virtual ~cThread();
   void SetDescription(const char *Description, ...) __attribute__ ((format (printf, 2, 3)));
@@ -115,8 +116,9 @@
   bool Active(void);
        ///< Checks whether the thread is still alive.
   static bool EmergencyExit(bool Request = false);
-  static tThreadId ThreadId(void) { return pthread_self(); }
+  static tThreadId ThreadId(void);
   static tThreadId IsMainThread(void) { return ThreadId() == mainThreadId; }
+  static void SetMainThreadId(void);
   };
 
 // cMutexLock can be used to easily set a lock on mutex and make absolutely
diff -ruN vdr-1.3.37/timers.c vdr-1.3.38/timers.c
--- vdr-1.3.37/timers.c	2005-09-09 17:22:33.000000000 +0200
+++ vdr-1.3.38/timers.c	2006-01-08 12:40:29.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: timers.c 1.36 2005/09/09 15:22:33 kls Exp $
+ * $Id: timers.c 1.41 2006/01/08 11:40:29 kls Exp $
  */
 
 #include "timers.h"
@@ -20,14 +20,14 @@
 
 // -- cTimer -----------------------------------------------------------------
 
-cTimer::cTimer(bool Instant, bool Pause)
+cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
 {
   startTime = stopTime = 0;
   recording = pending = inVpsMargin = false;
   flags = tfNone;
   if (Instant)
      SetFlags(tfActive | tfInstant);
-  channel = Channels.GetByNumber(cDevice::CurrentChannel());
+  channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel());
   time_t t = time(NULL);
   struct tm tm_r;
   struct tm *now = localtime_r(&t, &tm_r);
@@ -110,7 +110,7 @@
   char *buffer;
   strreplace(file, ':', '|');
   strreplace(summary, '\n', '|');
-  asprintf(&buffer, "%d:%s:%s:%04d:%04d:%d:%d:%s:%s\n", flags, UseChannelID ? *Channel()->GetChannelID().ToString() : *itoa(Channel()->Number()), *PrintDay(day, weekdays), start, stop, priority, lifetime, file, summary ? summary : "");
+  asprintf(&buffer, "%u:%s:%s:%04d:%04d:%d:%d:%s:%s\n", flags, UseChannelID ? *Channel()->GetChannelID().ToString() : *itoa(Channel()->Number()), *PrintDay(day, weekdays), start, stop, priority, lifetime, file, summary ? summary : "");
   strreplace(summary, '|', '\n');
   strreplace(file, '|', ':');
   return cString(buffer, true);
@@ -244,7 +244,7 @@
      s = s2;
      }
   bool result = false;
-  if (8 <= sscanf(s, "%d :%a[^:]:%a[^:]:%d :%d :%d :%d :%a[^:\n]:%a[^\n]", &flags, &channelbuffer, &daybuffer, &start, &stop, &priority, &lifetime, &filebuffer, &summary)) {
+  if (8 <= sscanf(s, "%u :%a[^:]:%a[^:]:%d :%d :%d :%d :%a[^:\n]:%a[^\n]", &flags, &channelbuffer, &daybuffer, &start, &stop, &priority, &lifetime, &filebuffer, &summary)) {
      if (summary && !*skipspace(summary)) {
         free(summary);
         summary = NULL;
@@ -363,7 +363,7 @@
      }
 
   if (HasFlags(tfActive)) {
-     if (HasFlags(tfVps) && !Directly && event && event->Vps() && schedule && schedule->PresentSeenWithin(30)) {
+     if (HasFlags(tfVps) && !Directly && event && event->Vps() && event->Schedule() && event->Schedule()->PresentSeenWithin(30)) {
         startTime = event->StartTime();
         stopTime = event->EndTime();
         return event->IsRunning(true);
@@ -425,7 +425,7 @@
   return stopTime;
 }
 
-void cTimer::SetEvent(const cSchedule *Schedule, const cEvent *Event)
+void cTimer::SetEvent(const cEvent *Event)
 {
   if (event != Event) { //XXX TODO check event data, too???
      if (Event) {
@@ -436,7 +436,6 @@
         }
      else
         isyslog("timer %s set to no event", *ToDescr());
-     schedule = Event ? Schedule : NULL;
      event = Event;
      }
 }
@@ -463,22 +462,27 @@
   inVpsMargin = InVpsMargin;
 }
 
-void cTimer::SetFlags(int Flags)
+void cTimer::SetPriority(int Priority)
+{
+  priority = Priority;
+}
+
+void cTimer::SetFlags(uint Flags)
 {
   flags |= Flags;
 }
 
-void cTimer::ClrFlags(int Flags)
+void cTimer::ClrFlags(uint Flags)
 {
   flags &= ~Flags;
 }
 
-void cTimer::InvFlags(int Flags)
+void cTimer::InvFlags(uint Flags)
 {
   flags ^= Flags;
 }
 
-bool cTimer::HasFlags(int Flags) const
+bool cTimer::HasFlags(uint Flags) const
 {
   return (flags & Flags) == Flags;
 }
@@ -616,7 +620,7 @@
                             distance = e->EndTime() - now;
                          if (Event && overlap == Overlap) {
                             if (Overlap > FULLMATCH) { // this means VPS
-                               if (abs(Distance) < abs(distance)) 
+                               if (abs(Distance) < abs(distance))
                                   break; // we've already found the closest VPS event
                                }
                             else if (e->Duration() <= Event->Duration())
@@ -629,7 +633,7 @@
                       }
                   if (Event && Event->EndTime() < now - EXPIRELATENCY && !Event->IsRunning())
                      Event = NULL;
-                  ti->SetEvent(Schedule, Event);
+                  ti->SetEvent(Event);
                   }
                }
             }
diff -ruN vdr-1.3.37/timers.h vdr-1.3.38/timers.h
--- vdr-1.3.37/timers.h	2005-05-07 12:36:35.000000000 +0200
+++ vdr-1.3.38/timers.h	2006-01-06 15:13:17.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: timers.h 1.19 2005/05/07 10:36:35 kls Exp $
+ * $Id: timers.h 1.23 2006/01/06 14:13:17 kls Exp $
  */
 
 #ifndef __TIMERS_H
@@ -29,7 +29,7 @@
 private:
   mutable time_t startTime, stopTime;
   bool recording, pending, inVpsMargin;
-  int flags;
+  uint flags;
   cChannel *channel;
   mutable time_t day; ///< midnight of the day this timer shall hit, or of the first day it shall hit in case of a repeating timer
   int weekdays;       ///< bitmask, lowest bits: SSFTWTM  (the 'M' is the LSB)
@@ -39,10 +39,9 @@
   int lifetime;
   char file[MaxFileName];
   char *summary;
-  const cSchedule *schedule;
   const cEvent *event;
 public:
-  cTimer(bool Instant = false, bool Pause = false);
+  cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL);
   cTimer(const cEvent *Event);
   virtual ~cTimer();
   cTimer& operator= (const cTimer &Timer);
@@ -50,7 +49,7 @@
   bool Recording(void) const { return recording; }
   bool Pending(void) const { return pending; }
   bool InVpsMargin(void) const { return inVpsMargin; }
-  int Flags(void) const { return flags; }
+  uint Flags(void) const { return flags; }
   const cChannel *Channel(void) const { return channel; }
   time_t Day(void) const { return day; }
   int WeekDays(void) const { return weekdays; }
@@ -78,14 +77,15 @@
   bool Expired(void) const;
   time_t StartTime(void) const;
   time_t StopTime(void) const;
-  void SetEvent(const cSchedule *Schedule, const cEvent *Event);
+  void SetEvent(const cEvent *Event);
   void SetRecording(bool Recording);
   void SetPending(bool Pending);
   void SetInVpsMargin(bool InVpsMargin);
-  void SetFlags(int Flags);
-  void ClrFlags(int Flags);
-  void InvFlags(int Flags);
-  bool HasFlags(int Flags) const;
+  void SetPriority(int Priority);
+  void SetFlags(uint Flags);
+  void ClrFlags(uint Flags);
+  void InvFlags(uint Flags);
+  bool HasFlags(uint Flags) const;
   void Skip(void);
   void OnOff(void);
   cString PrintFirstDay(void) const;
diff -ruN vdr-1.3.37/tools.c vdr-1.3.38/tools.c
--- vdr-1.3.37/tools.c	2005-11-26 15:12:31.000000000 +0100
+++ vdr-1.3.38/tools.c	2006-01-08 12:40:35.000000000 +0100
@@ -4,13 +4,20 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: tools.c 1.104 2005/11/26 14:12:31 kls Exp $
+ * $Id: tools.c 1.109 2006/01/08 11:40:35 kls Exp $
  */
 
 #include "tools.h"
 #include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
+extern "C" {
+#ifdef boolean
+#define HAVE_BOOLEAN
+#endif
+#include <jpeglib.h>
+#undef boolean
+}
 #include <stdarg.h>
 #include <stdlib.h>
 #include <sys/time.h>
@@ -421,6 +428,42 @@
   return false;
 }
 
+int DirSizeMB(const char *DirName)
+{
+  cReadDir d(DirName);
+  if (d.Ok()) {
+     int size = 0;
+     struct dirent *e;
+     while (size >= 0 && (e = d.Next()) != NULL) {
+           if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) {
+              char *buffer;
+              asprintf(&buffer, "%s/%s", DirName, e->d_name);
+              struct stat st;
+              if (stat(buffer, &st) == 0) {
+                 if (S_ISDIR(st.st_mode)) {
+                    int n = DirSizeMB(buffer);
+                    if (n >= 0)
+                       size += n;
+                    else
+                       size = -1;
+                    }
+                 else
+                    size += st.st_size / MEGABYTE(1);
+                 }
+              else {
+                 LOG_ERROR_STR(buffer);
+                 size = -1;
+                 }
+              free(buffer);
+              }
+           }
+     return size;
+     }
+  else
+     LOG_ERROR_STR(DirName);
+  return -1;
+}
+
 char *ReadLink(const char *FileName)
 {
   char RealName[PATH_MAX];
@@ -615,6 +658,145 @@
   return buf;
 }
 
+// --- RgbToJpeg -------------------------------------------------------------
+
+#define JPEGCOMPRESSMEM 500000
+
+struct tJpegCompressData {
+  int size;
+  uchar *mem;
+  };
+
+static void JpegCompressInitDestination(j_compress_ptr cinfo)
+{
+  tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data;
+  if (jcd) {
+     cinfo->dest->free_in_buffer = jcd->size = JPEGCOMPRESSMEM;
+     cinfo->dest->next_output_byte = jcd->mem = MALLOC(uchar, jcd->size);
+     }
+}
+
+static boolean JpegCompressEmptyOutputBuffer(j_compress_ptr cinfo)
+{
+  tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data;
+  if (jcd) {
+     int Used = jcd->size;
+     jcd->size += JPEGCOMPRESSMEM;
+     jcd->mem = (uchar *)realloc(jcd->mem, jcd->size);
+     if (jcd->mem) {
+        cinfo->dest->next_output_byte = jcd->mem + Used;
+        cinfo->dest->free_in_buffer = jcd->size - Used;
+        return TRUE;
+        }
+     }
+  return FALSE;
+}
+
+static void JpegCompressTermDestination(j_compress_ptr cinfo)
+{
+  tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data;
+  if (jcd) {
+     int Used = cinfo->dest->next_output_byte - jcd->mem;
+     if (Used < jcd->size) {
+        jcd->size = Used;
+        jcd->mem = (uchar *)realloc(jcd->mem, jcd->size);
+        }
+     }
+}
+
+uchar *RgbToJpeg(uchar *Mem, int Width, int Height, int &Size, int Quality)
+{
+  if (Quality < 0)
+     Quality = 0;
+  else if (Quality > 100)
+     Quality = 100;
+
+  jpeg_destination_mgr jdm;
+
+  jdm.init_destination = JpegCompressInitDestination;
+  jdm.empty_output_buffer = JpegCompressEmptyOutputBuffer;
+  jdm.term_destination = JpegCompressTermDestination;
+
+  struct jpeg_compress_struct cinfo;
+  struct jpeg_error_mgr jerr;
+  cinfo.err = jpeg_std_error(&jerr);
+  jpeg_create_compress(&cinfo);
+  cinfo.dest = &jdm;
+  tJpegCompressData jcd;
+  cinfo.client_data = &jcd;
+  cinfo.image_width = Width;
+  cinfo.image_height = Height;
+  cinfo.input_components = 3;
+  cinfo.in_color_space = JCS_RGB;
+
+  jpeg_set_defaults(&cinfo);
+  jpeg_set_quality(&cinfo, Quality, true);
+  jpeg_start_compress(&cinfo, true);
+
+  int rs = Width * 3;
+  JSAMPROW rp[Height];
+  for (int k = 0; k < Height; k++)
+      rp[k] = &Mem[rs * k];
+  jpeg_write_scanlines(&cinfo, rp, Height);
+  jpeg_finish_compress(&cinfo);
+  jpeg_destroy_compress(&cinfo);
+
+  Size = jcd.size;
+  return jcd.mem;
+}
+
+// --- cBase64Encoder --------------------------------------------------------
+
+const char *cBase64Encoder::b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+cBase64Encoder::cBase64Encoder(const uchar *Data, int Length, int MaxResult)
+{
+  data = Data;
+  length = Length;
+  maxResult = MaxResult;
+  i = 0;
+  result = MALLOC(char, maxResult + 1);
+}
+
+cBase64Encoder::~cBase64Encoder()
+{
+  free(result);
+}
+
+const char *cBase64Encoder::NextLine(void)
+{
+  int r = 0;
+  while (i < length && r < maxResult - 3) {
+        result[r++] = b64[(data[i] >> 2) & 0x3F];
+        char c = (data[i] << 4) & 0x3F;
+        if (++i < length)
+           c |= (data[i] >> 4) & 0x0F;
+        result[r++] = b64[c];
+        if (i < length) {
+           c = (data[i] << 2) & 0x3F;
+           if (++i < length)
+              c |= (data[i] >> 6) & 0x03;
+           result[r++] = b64[c];
+           }
+        else {
+           i++;
+           result[r++] = '=';
+           }
+        if (i < length) {
+           c = data[i] & 0x3F;
+           result[r++] = b64[c];
+           }
+        else
+           result[r++] = '=';
+        i++;
+        }
+  if (r > 0) {
+     result[r] = 0;
+     return result;
+     }
+  return NULL;
+}
+
 // --- cReadLine -------------------------------------------------------------
 
 cReadLine::cReadLine(void)
@@ -858,6 +1040,8 @@
 
 // --- cUnbufferedFile -------------------------------------------------------
 
+//#define USE_FADVISE
+
 #define READ_AHEAD MEGABYTE(2)
 #define WRITE_BUFFER MEGABYTE(10)
 
@@ -882,6 +1066,7 @@
 
 int cUnbufferedFile::Close(void)
 {
+#ifdef USE_FADVISE
   if (fd >= 0) {
      if (ahead > end)
         end = ahead;
@@ -894,6 +1079,7 @@
      begin = end = ahead = -1;
      written = 0;
      }
+#endif
   int OldFd = fd;
   fd = -1;
   return close(OldFd);
@@ -909,6 +1095,7 @@
 ssize_t cUnbufferedFile::Read(void *Data, size_t Size)
 {
   if (fd >= 0) {
+#ifdef USE_FADVISE
      off_t pos = lseek(fd, 0, SEEK_CUR);
      // jump forward - adjust end position
      if (pos > end)
@@ -922,7 +1109,9 @@
      if (begin >= 0 && end > begin)
         posix_fadvise(fd, begin - KILOBYTE(200), end - begin + KILOBYTE(200), POSIX_FADV_DONTNEED);//XXX macros/parameters???
      begin = pos;
+#endif
      ssize_t bytesRead = safe_read(fd, Data, Size);
+#ifdef USE_FADVISE
      if (bytesRead > 0) {
         pos += bytesRead;
         end = pos;
@@ -935,6 +1124,7 @@
         }
      else
         end = pos;
+#endif
      return bytesRead;
      }
   return -1;
@@ -943,8 +1133,11 @@
 ssize_t cUnbufferedFile::Write(const void *Data, size_t Size)
 {
   if (fd >=0) {
+#ifdef USE_FADVISE
      off_t pos = lseek(fd, 0, SEEK_CUR);
+#endif
      ssize_t bytesWritten = safe_write(fd, Data, Size);
+#ifdef USE_FADVISE
      if (bytesWritten >= 0) {
         written += bytesWritten;
         if (begin >= 0) {
@@ -964,6 +1157,7 @@
            written = 0;
            }
         }
+#endif
      return bytesWritten;
      }
   return -1;
@@ -1090,7 +1284,7 @@
 // --- cListBase -------------------------------------------------------------
 
 cListBase::cListBase(void)
-{ 
+{
   objects = lastObject = NULL;
   count = 0;
 }
@@ -1101,7 +1295,7 @@
 }
 
 void cListBase::Add(cListObject *Object, cListObject *After)
-{ 
+{
   if (After && After != lastObject) {
      After->Next()->Insert(Object);
      After->Append(Object);
@@ -1117,7 +1311,7 @@
 }
 
 void cListBase::Ins(cListObject *Object, cListObject *Before)
-{ 
+{
   if (Before && Before != objects) {
      Before->Prev()->Append(Object);
      Before->Insert(Object);
diff -ruN vdr-1.3.37/tools.h vdr-1.3.38/tools.h
--- vdr-1.3.37/tools.h	2005-11-26 15:03:47.000000000 +0100
+++ vdr-1.3.38/tools.h	2006-01-08 12:40:37.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: tools.h 1.84 2005/11/26 14:03:47 kls Exp $
+ * $Id: tools.h 1.89 2006/01/08 11:40:37 kls Exp $
  */
 
 #ifndef __TOOLS_H
@@ -110,6 +110,7 @@
 bool MakeDirs(const char *FileName, bool IsDirectory = false);
 bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks = false);
 bool RemoveEmptyDirectories(const char *DirName, bool RemoveThis = false);
+int DirSizeMB(const char *DirName); ///< returns the total size of the files in the given directory, or -1 in case of an error
 char *ReadLink(const char *FileName); ///< returns a new string allocated on the heap, which the caller must delete (or NULL in case of an error)
 bool SpinUpDisk(const char *FileName);
 void TouchFile(const char *FileName);
@@ -120,6 +121,39 @@
 cString TimeToString(time_t t);
 cString DateString(time_t t);
 cString TimeString(time_t t);
+uchar *RgbToJpeg(uchar *Mem, int Width, int Height, int &Size, int Quality = 100);
+    ///< Converts the given Memory to a JPEG image and returns a pointer
+    ///< to the resulting image. Mem must point to a data block of exactly
+    ///< (Width * Height) triplets of RGB image data bytes. Upon return, Size
+    ///< will hold the number of bytes of the resulting JPEG data.
+    ///< Quality can be in the range 0..100 and controls the quality of the
+    ///< resulting image, where 100 is "best". The caller takes ownership of
+    ///< the result and has to delete it once it is no longer needed.
+    ///< The result may be NULL in case of an error.
+
+class cBase64Encoder {
+private:
+  const uchar *data;
+  int length;
+  int maxResult;
+  int i;
+  char *result;
+  static const char *b64;
+public:
+  cBase64Encoder(const uchar *Data, int Length, int MaxResult = 64);
+      ///< Sets up a new base 64 encoder for the given Data, with the given Length.
+      ///< Data will not be copied and must be valid as long as NextLine() will be
+      ///< called. MaxResult defines the maximum number of characters in any
+      ///< result line. The resulting lines may be shorter than MaxResult in case
+      ///< its value is not a multiple of 4.
+  ~cBase64Encoder();
+  const char *NextLine(void);
+      ///< Returns the next line of encoded data (terminated by '\0'), or NULL if
+      ///< there is no more encoded data. The caller must call NextLine() and process
+      ///< each returned line until NULL is returned, in order to get the entire
+      ///< data encoded. The returned data is only valid until the next time NextLine()
+      ///< is called, or until the object is destroyed.
+  };
 
 class cTimeMs {
 private:
@@ -152,7 +186,7 @@
   bool Add(int FileHandle, bool Out);
   bool Poll(int TimeoutMs = 0);
   };
-  
+
 class cReadDir {
 private:
   DIR *directory;
diff -ruN vdr-1.3.37/vdr.1 vdr-1.3.38/vdr.1
--- vdr-1.3.37/vdr.1	2005-10-09 14:31:03.000000000 +0200
+++ vdr-1.3.38/vdr.1	2006-01-08 12:51:36.000000000 +0100
@@ -2,15 +2,15 @@
 .\" ** The above line should force tbl to be a preprocessor **
 .\" Man page for vdr
 .\"
-.\" Copyright (C) 2004 Klaus Schmidinger
+.\" Copyright (C) 2006 Klaus Schmidinger
 .\"
 .\" You may distribute under the terms of the GNU General Public
 .\" License as specified in the file COPYING that comes with the
 .\" vdr distribution.
 .\"
-.\" $Id: vdr.1 1.15 2005/10/09 12:31:03 kls Exp $
+.\" $Id: vdr.1 1.20 2006/01/08 11:51:36 kls Exp $
 .\"
-.TH vdr 1 "19 Dec 2004" "1.3.18" "Video Disk Recorder"
+.TH vdr 1 "08 Jan 2006" "1.3.38" "Video Disk Recorder"
 .SH NAME
 vdr - the Video Disk Recorder
 .SH SYNOPSIS
@@ -59,6 +59,13 @@
 If \fIfile\fR is a directory, the file \fIepg.data\fR
 will be created in that directory.
 .TP
+.BI \-g,\ \-\-grab= dir
+Write images from the SVDRP command GRAB into the
+given directory \fIdir\fR. \fIdir\fR must be the full path name of an
+existing directory, without any "..", double '/'
+or symlinks. By default, or if \fB\-g\-\fR is given,
+grabbing images to disk is disabled.
+.TP
 .B \-h, \-\-help
 Print a help message and exit.
 .TP
@@ -121,6 +128,13 @@
 .BI \-t\  tty ,\ \-\-terminal= tty
 Set the controlling terminal.
 .TP
+.BI \-u\  user ,\ \-\-user= user
+Run as user \fIuser\fR in case vdr was started as user 'root'.
+Starting vdr as 'root' is necessary if the system time shall
+be set from the transponder data, but for security reasons
+during normal operation vdr switches to a lesser privileged
+user id. By default the user 'vdr' is used.
+.TP
 .BI \-v\  dir ,\ \-\-video= dir
 Use \fIdir\fR as video directory.
 The default is \fI/video\fR.
@@ -199,7 +213,7 @@
 .SH REPORTING BUGS
 Report bugs to <vdr\-bugs@cadsoft.de>.
 .SH COPYRIGHT
-Copyright \(co 2004 Klaus Schmidinger.
+Copyright \(co 2006 Klaus Schmidinger.
 
 This is free software; see the source for copying conditions.  There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff -ruN vdr-1.3.37/vdr.5 vdr-1.3.38/vdr.5
--- vdr-1.3.37/vdr.5	2005-09-26 23:38:44.000000000 +0200
+++ vdr-1.3.38/vdr.5	2006-01-08 12:52:03.000000000 +0100
@@ -2,15 +2,15 @@
 .\" ** The above line should force tbl to be a preprocessor **
 .\" Man page for vdr file formats
 .\"
-.\" Copyright (C) 2004 Klaus Schmidinger
+.\" Copyright (C) 2006 Klaus Schmidinger
 .\"
 .\" You may distribute under the terms of the GNU General Public
 .\" License as specified in the file COPYING that comes with the
 .\" vdr distribution.
 .\"
-.\" $Id: vdr.5 1.39 2005/09/26 21:38:44 kls Exp $
+.\" $Id: vdr.5 1.45 2006/01/08 11:52:03 kls Exp $
 .\"
-.TH vdr 5 "19 Mar 2005" "1.3.23" "Video Disk Recorder Files"
+.TH vdr 5 "08 Jan 2006" "1.3.38" "Video Disk Recorder Files"
 .SH NAME
 vdr file formats - the Video Disk Recorder Files
 .SH DESCRIPTION
@@ -152,7 +152,7 @@
 l l.
 \fB0000\fR@Free To Air
 \fB0001...000F\fR@explicitly requires the device with the given number
-\fB0010...00FF\fR@reserved for user defined assignments defined in \fIca.conf\fR
+\fB0010...00FF\fR@reserved for user defined assignments
 \fB0100...FFFF\fR@specific decryption methods as broadcast in the data stream\fR
 .TE
 Values in the range 0001...00FF will not be overwritten, all other values
@@ -207,7 +207,7 @@
 The fields in a timer definition have the following meaning (from left
 to right):
 .TP
-.B Status
+.B Flags
 The individual bits in this field have the following meaning:
 .TS
 tab (@);
@@ -219,7 +219,7 @@
 .TE
 Bits other than these can be used by external programs to mark active timers
 and recognize if the user has modified them. When a user modifies an active
-timer, the upper 16 bits of this 32 bit parameter will be explicitly set to 0.
+timer, the upper 16 bits of this unsigned 32 bit parameter will be explicitly set to 0.
 
 Note: in order to allow future extensibility, external programs using the
 \fBstatus\fR parameter should only use the upper 16 bit of this 32 bit parameter
@@ -227,7 +227,7 @@
 .TP
 .B Channel
 The channel to record from. This is either the channel number as shown in the
-on-screen menus, or a complete channel ID. When reading \fItimers.conf\fR 
+on-screen menus, or a complete channel ID. When reading \fItimers.conf\fR
 any channel numbers will be mapped to the respective channel ids and when
 the file is written again, there will only be channel ids. Channel numbers
 are accepted as input in order to allow easier creation of timers when
@@ -389,24 +389,6 @@
 There can be any number of actions in a line, including none at all - in which case
 the entry would be used only to set the LOF to use for the given frequency range
 and polarization.
-.SS CONDITIONAL ACCESS
-The file \fIca.conf\fR defines the numbers to be used in the \fBConditional access\fR
-field of channels in \fIchannels.conf\fR and assigns descriptive texts to them.
-Example:
-
-\fB101    Premiere World\fR
-
-Anything after (and including) a '#' character is comment.
-
-Value lines consist of an integer number, followed by a text describing
-this decryption method (typically the name of the pay tv service using this
-decryption method).
-
-The special value \fB0\fR means \fBFree To Air\fR, which can be used for
-channels that don't require additional decryption hardware.
-
-The values \fB1...4\fR can be used for channels that for some reason explicitly
-need a given DVB card (for backward compatibility).
 .SS REMOTE CONTROL KEYS
 The file \fIremote.conf\fR contains the key assignments for all remote control
 units. Each line consists of one key assignment in the following format:
@@ -627,7 +609,7 @@
 tab (@);
 l l.
 \fBC\fR@<channel id> <channel name>
-\fBE\fR@<event id> <start time> <duration> <table id>
+\fBE\fR@<event id> <start time> <duration> <table id> <version>
 \fBT\fR@<title>
 \fBS\fR@<short text>
 \fBD\fR@<description>
@@ -654,6 +636,7 @@
 <start time>   @is the time (as a time_t integer) in UTC when this event starts
 <duration>     @is the time (in seconds) that this event will take
 <table id>     @is a hex number that indicates the table this event is contained in (if this is left empty or 0 this event will not be overwritten or modified by data that comes from the DVB stream)
+<version>      @is a hex number that indicates the event's version number inside its table (optional)
 <title>        @is the title of the event
 <short text>   @is the short text of the event (typically the name of the episode etc.)
 <description>  @is the description of the event (any '|' characters will be interpreted as newlines)
@@ -673,7 +656,7 @@
 .SH REPORTING BUGS
 Report bugs to <vdr\-bugs@cadsoft.de>.
 .SH COPYRIGHT
-Copyright \(co 2004 Klaus Schmidinger.
+Copyright \(co 2006 Klaus Schmidinger.
 
 This is free software; see the source for copying conditions.  There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff -ruN vdr-1.3.37/vdr.c vdr-1.3.38/vdr.c
--- vdr-1.3.37/vdr.c	2005-11-27 16:56:18.000000000 +0100
+++ vdr-1.3.38/vdr.c	2006-01-08 12:49:03.000000000 +0100
@@ -1,7 +1,7 @@
 /*
  * vdr.c: Video Disk Recorder main program
  *
- * Copyright (C) 2000, 2003 Klaus Schmidinger
+ * Copyright (C) 2000, 2003, 2006 Klaus Schmidinger
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -22,13 +22,17 @@
  *
  * The project's page is at http://www.cadsoft.de/vdr
  *
- * $Id: vdr.c 1.220 2005/11/27 15:56:18 kls Exp $
+ * $Id: vdr.c 1.233 2006/01/08 11:49:03 kls Exp $
  */
 
 #include <getopt.h>
+#include <grp.h>
 #include <locale.h>
+#include <pwd.h>
 #include <signal.h>
 #include <stdlib.h>
+#include <sys/capability.h>
+#include <sys/prctl.h>
 #include <termios.h>
 #include <unistd.h>
 #include "audio.h"
@@ -72,6 +76,57 @@
 
 static int Interrupted = 0;
 
+static bool SetUser(const char *UserName)
+{
+  if (UserName) {
+     struct passwd *user = getpwnam(UserName);
+     if (!user) {
+        fprintf(stderr, "vdr: unknown user: '%s'\n", UserName);
+        return false;
+        }
+     if (setgid(user->pw_gid) < 0) {
+        fprintf(stderr, "vdr: cannot set group id %u: %s\n", (unsigned int)user->pw_gid, strerror(errno));
+        return false;
+        }
+     if (initgroups(user->pw_name, user->pw_gid) < 0) {
+        fprintf(stderr, "vdr: cannot set supplemental group ids for user %s: %s\n", user->pw_name, strerror(errno));
+        return false;
+        }
+     if (setuid(user->pw_uid) < 0) {
+        fprintf(stderr, "vdr: cannot set user id %u: %s\n", (unsigned int)user->pw_uid, strerror(errno));
+        return false;
+        }
+     }
+  return true;
+}
+
+static bool SetCapSysTime(void)
+{
+  // drop all capabilities except cap_sys_time
+  cap_t caps = cap_from_text("= cap_sys_time=ep");
+  if (!caps) {
+     fprintf(stderr, "vdr: cap_from_text failed: %s\n", strerror(errno));
+     return false;
+     }
+  if (cap_set_proc(caps) == -1) {
+     fprintf(stderr, "vdr: cap_set_proc failed: %s\n", strerror(errno));
+     cap_free(caps);
+     return false;
+     }
+  cap_free(caps);
+  return true;
+}
+
+static bool SetKeepCaps(bool On)
+{
+  // set keeping capabilities during setuid() on/off
+  if (prctl(PR_SET_KEEPCAPS, On ? 1 : 0, 0, 0, 0) != 0) {
+     fprintf(stderr, "vdr: prctl failed\n");
+     return false;
+     }
+  return true;
+}
+
 static void SignalHandler(int signum)
 {
   if (signum != SIGPIPE) {
@@ -102,11 +157,14 @@
 
   // Command line options:
 
+#define DEFAULTVDRUSER   "vdr"
 #define DEFAULTSVDRPPORT 2001
 #define DEFAULTWATCHDOG     0 // seconds
 #define DEFAULTPLUGINDIR PLUGINDIR
 #define DEFAULTEPGDATAFILENAME "epg.data"
 
+  bool StartedAsRoot = false;
+  const char *VdrUser = DEFAULTVDRUSER;
   int SVDRPport = DEFAULTSVDRPPORT;
   const char *AudioCommand = NULL;
   const char *ConfigDirectory = NULL;
@@ -144,6 +202,7 @@
       { "daemon",   no_argument,       NULL, 'd' },
       { "device",   required_argument, NULL, 'D' },
       { "epgfile",  required_argument, NULL, 'E' },
+      { "grab",     required_argument, NULL, 'g' },
       { "help",     no_argument,       NULL, 'h' },
       { "lib",      required_argument, NULL, 'L' },
       { "lirc",     optional_argument, NULL, 'l' | 0x100 },
@@ -156,6 +215,7 @@
       { "record",   required_argument, NULL, 'r' },
       { "shutdown", required_argument, NULL, 's' },
       { "terminal", required_argument, NULL, 't' },
+      { "user",     required_argument, NULL, 'u' },
       { "version",  no_argument,       NULL, 'V' },
       { "vfat",     no_argument,       NULL, 'v' | 0x100 },
       { "video",    required_argument, NULL, 'v' },
@@ -164,7 +224,7 @@
     };
 
   int c;
-  while ((c = getopt_long(argc, argv, "a:c:dD:E:hl:L:mp:P:r:s:t:v:Vw:", long_options, NULL)) != -1) {
+  while ((c = getopt_long(argc, argv, "a:c:dD:E:g:hl:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) {
         switch (c) {
           case 'a': AudioCommand = optarg;
                     break;
@@ -183,6 +243,8 @@
                     break;
           case 'E': EpgDataFileName = (*optarg != '-' ? optarg : NULL);
                     break;
+          case 'g': cSVDRP::SetGrabImageDir(*optarg != '-' ? optarg : NULL);
+                    break;
           case 'h': DisplayHelp = true;
                     break;
           case 'l': {
@@ -248,6 +310,9 @@
                        return 2;
                        }
                     break;
+          case 'u': if (*optarg)
+                       VdrUser = optarg;
+                    break;
           case 'V': DisplayVersion = true;
                     break;
           case 'v' | 0x100:
@@ -270,6 +335,20 @@
           }
         }
 
+  // Set user id in case we were started as root:
+
+  if (getuid() == 0) {
+     StartedAsRoot = true;
+     if (!SetKeepCaps(true))
+        return 2;
+     if (!SetUser(VdrUser))
+        return 2;
+     if (!SetKeepCaps(false))
+        return 2;
+     if (!SetCapSysTime())
+        return 2;
+     }
+
   // Help and version info:
 
   if (DisplayHelp || DisplayVersion) {
@@ -285,11 +364,15 @@
                "  -D NUM,   --device=NUM   use only the given DVB device (NUM = 0, 1, 2...)\n"
                "                           there may be several -D options (default: all DVB\n"
                "                           devices will be used)\n"
-               "  -E FILE   --epgfile=FILE write the EPG data into the given FILE (default is\n"
+               "  -E FILE,  --epgfile=FILE write the EPG data into the given FILE (default is\n"
                "                           '%s' in the video directory)\n"
                "                           '-E-' disables this\n"
                "                           if FILE is a directory, the default EPG file will be\n"
                "                           created in that directory\n"
+               "  -g DIR,   --grab=DIR     write images from the SVDRP command GRAB into the\n"
+               "                           given DIR; DIR must be the full path name of an\n"
+               "                           existing directory, without any \"..\", double '/'\n"
+               "                           or symlinks (default: none, same as -g-)\n"
                "  -h,       --help         print this help and exit\n"
                "  -l LEVEL, --log=LEVEL    set log level (default: 3)\n"
                "                           0 = no logging, 1 = errors only,\n"
@@ -309,6 +392,8 @@
                "  -r CMD,   --record=CMD   call CMD before and after a recording\n"
                "  -s CMD,   --shutdown=CMD call CMD to shutdown the computer\n"
                "  -t TTY,   --terminal=TTY controlling tty\n"
+               "  -u USER,  --user=USER    run as user USER (default: %s); only applicable\n"
+               "                           if started as root\n"
                "  -v DIR,   --video=DIR    use DIR as video directory (default: %s)\n"
                "  -V,       --version      print version information and exit\n"
                "            --vfat         encode special characters in recording names to\n"
@@ -321,6 +406,7 @@
                LIRC_DEVICE,
                DEFAULTSVDRPPORT,
                RCU_DEVICE,
+               DEFAULTVDRUSER,
                VideoDirectory,
                DEFAULTWATCHDOG
                );
@@ -371,7 +457,7 @@
 
   if (DaemonMode) {
      if (daemon(1, 0) == -1) {
-        fprintf(stderr, "%m\n");
+        fprintf(stderr, "vdr: %m\n");
         esyslog("ERROR: %m");
         return 2;
         }
@@ -385,12 +471,16 @@
      }
 
   isyslog("VDR version %s started", VDRVERSION);
+  if (StartedAsRoot)
+     isyslog("switched to user '%s'", VdrUser);
+  if (DaemonMode)
+     dsyslog("running as daemon (tid=%d)", cThread::ThreadId());
+  cThread::SetMainThreadId();
 
   // Main program loop variables - need to be here to have them initialized before any EXIT():
 
   cOsdObject *Menu = NULL;
-  cOsdObject *Temp = NULL;
-  int LastChannel = -1;
+  int LastChannel = 0;
   int LastTimerChannel = -1;
   int PreviousChannel[2] = { 1, 1 };
   int PreviousChannelIndex = 0;
@@ -401,6 +491,7 @@
   bool ForceShutdown = false;
   bool UserShutdown = false;
   bool TimerInVpsMargin = false;
+  bool IsInfoMenu = false;
 
   // Load plugins:
 
@@ -423,7 +514,6 @@
         Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"), true) &&
         RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"), true) &&
         SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true) &&
-        CaDefinitions.Load(AddDirectory(ConfigDirectory, "ca.conf"), true) &&
         Keys.Load(AddDirectory(ConfigDirectory, "remote.conf")) &&
         KeyMacros.Load(AddDirectory(ConfigDirectory, "keymacros.conf"), true)
         ))
@@ -434,6 +524,7 @@
   // Recordings:
 
   Recordings.Update();
+  DeletedRecordings.Update();
 
   // EPG data:
 
@@ -555,6 +646,8 @@
 
   // Main program loop:
 
+#define DELETE_MENU ((IsInfoMenu &= (Menu == NULL)), delete Menu, Menu = NULL)
+
   while (!Interrupted) {
         // Handle emergency exits:
         if (cThread::EmergencyExit()) {
@@ -576,8 +669,8 @@
                      && !(LastTimerChannel > 0 && Channels.SwitchTo(LastTimerChannel)) // ...or the one used by the last timer...
                      && !cDevice::SwitchChannel(1) // ...or the next higher available one...
                      && !cDevice::SwitchChannel(-1)) // ...or the next lower available one
-                    ; 
-                 }   
+                    ;
+                 }
               lastTime = time(NULL); // don't do this too often
               LastTimerChannel = -1;
               }
@@ -626,7 +719,7 @@
         // Channel display:
         if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) {
            if (!Menu)
-              Menu = Temp = new cDisplayChannel(cDevice::CurrentChannel(), LastChannel > 0);
+              Menu = new cDisplayChannel(cDevice::CurrentChannel(), LastChannel >= 0);
            LastChannel = cDevice::CurrentChannel();
            LastChannelChanged = time(NULL);
            }
@@ -667,8 +760,10 @@
                   TimerInVpsMargin = true;
                }
            }
-        if (!Menu && Recordings.NeedsUpdate())
+        if (!Menu && Recordings.NeedsUpdate()) {
            Recordings.Update();
+           DeletedRecordings.Update();
+           }
         // CAM control:
         if (!Menu && !cOsd::IsOpen()) {
            Menu = CamControl();
@@ -692,22 +787,39 @@
           // Menu control:
           case kMenu:
                key = kNone; // nobody else needs to see this key
-               if (Menu) {
-                  DELETENULL(Menu);
-                  if (!Temp)
-                     break;
-                  }
-               if (cControl::Control())
+               if (Menu)
+                  DELETE_MENU;
+               else if (cControl::Control() && cOsd::IsOpen())
                   cControl::Control()->Hide();
-               Menu = new cMenuMain(cControl::Control());
-               Temp = NULL;
+               else
+                  Menu = new cMenuMain;
+               break;
+          // Info:
+          case kInfo: {
+               bool WasInfoMenu = IsInfoMenu;
+               DELETE_MENU;
+               if (!WasInfoMenu) {
+                  IsInfoMenu = true;
+                  if (cControl::Control()) {
+                     cControl::Control()->Hide();
+                     Menu = cControl::Control()->GetInfo();
+                     if (Menu)
+                        Menu->Show();
+                     else
+                        IsInfoMenu = false;
+                     }
+                  else {
+                     cRemote::Put(kOk, true);
+                     cRemote::Put(kSchedule, true);
+                     }
+                  }
+               }
                break;
           #define DirectMainFunction(function)\
-            DELETENULL(Menu);\
+            DELETE_MENU;\
             if (cControl::Control())\
                cControl::Control()->Hide();\
-            Menu = new cMenuMain(cControl::Control(), function);\
-            Temp = NULL;\
+            Menu = new cMenuMain(function);\
             key = kNone; // nobody else needs to see this key
           case kSchedule:   DirectMainFunction(osSchedule); break;
           case kChannels:   DirectMainFunction(osChannels); break;
@@ -717,18 +829,14 @@
           case kCommands:   DirectMainFunction(osCommands); break;
           case kUser1 ... kUser9: cRemote::PutMacro(key); key = kNone; break;
           case k_Plugin: {
-               DELETENULL(Menu);
-               Temp = NULL;
+               DELETE_MENU;
                if (cControl::Control())
                   cControl::Control()->Hide();
                cPlugin *plugin = cPluginManager::GetPlugin(cRemote::GetPlugin());
                if (plugin) {
-                  Menu = Temp = plugin->MainMenuAction();
-                  if (Menu) {
+                  Menu = plugin->MainMenuAction();
+                  if (Menu)
                      Menu->Show();
-                     if (Menu->IsMenu())
-                        ((cOsdMenu*)Menu)->Display();
-                     }
                   }
                else
                   esyslog("ERROR: unknown plugin '%s'", cRemote::GetPlugin());
@@ -757,7 +865,7 @@
                else
                   cDevice::PrimaryDevice()->SetVolume(NORMALKEY(key) == kVolDn ? -VOLUMEDELTA : VOLUMEDELTA);
                if (!Menu && !cOsd::IsOpen())
-                  Menu = Temp = cDisplayVolume::Create();
+                  Menu = cDisplayVolume::Create();
                cDisplayVolume::Process(key);
                key = kNone; // nobody else needs to see these keys
                break;
@@ -765,12 +873,10 @@
           case kAudio:
                if (cControl::Control())
                   cControl::Control()->Hide();
-               if (Temp && !cDisplayTracks::IsOpen()) {
-                  DELETENULL(Menu);
-                  Temp = NULL;
+               if (!cDisplayTracks::IsOpen()) {
+                  DELETE_MENU;
+                  Menu = cDisplayTracks::Create();
                   }
-               if (!Menu && !cOsd::IsOpen())
-                  Menu = Temp = cDisplayTracks::Create();
                else
                   cDisplayTracks::Process(key);
                key = kNone;
@@ -778,8 +884,7 @@
           // Pausing live video:
           case kPause:
                if (!cControl::Control()) {
-                  DELETENULL(Menu);
-                  Temp = NULL;
+                  DELETE_MENU;
                   if (!cRecordControls::PauseLiveVideo())
                      Skins.Message(mtError, tr("No free DVB device to record!"));
                   key = kNone; // nobody else needs to see this key
@@ -797,8 +902,7 @@
                break;
           // Power off:
           case kPower: isyslog("Power button pressed");
-                       DELETENULL(Menu);
-                       Temp = NULL;
+                       DELETE_MENU;
                        if (!Shutdown) {
                           Skins.Message(mtError, tr("Can't shutdown - option '-s' not given!"));
                           break;
@@ -828,53 +932,46 @@
                  state = osEnd;
               }
            switch (state) {
-             case osPause:  DELETENULL(Menu);
+             case osPause:  DELETE_MENU;
                             cControl::Shutdown(); // just in case
-                            Temp = NULL;
                             if (!cRecordControls::PauseLiveVideo())
                                Skins.Message(mtError, tr("No free DVB device to record!"));
                             break;
-             case osRecord: DELETENULL(Menu);
-                            Temp = NULL;
+             case osRecord: DELETE_MENU;
                             if (cRecordControls::Start())
-                               ;//XXX Skins.Message(mtInfo, tr("Recording"));
+                               Skins.Message(mtInfo, tr("Recording started"));
                             else
                                Skins.Message(mtError, tr("No free DVB device to record!"));
                             break;
              case osRecordings:
-                            DELETENULL(Menu);
+                            DELETE_MENU;
                             cControl::Shutdown();
-                            Temp = NULL;
-                            Menu = new cMenuMain(false, osRecordings);
+                            Menu = new cMenuMain(osRecordings);
                             break;
-             case osReplay: DELETENULL(Menu);
+             case osReplay: DELETE_MENU;
                             cControl::Shutdown();
-                            Temp = NULL;
                             cControl::Launch(new cReplayControl);
                             break;
              case osStopReplay:
-                            DELETENULL(Menu);
+                            DELETE_MENU;
                             cControl::Shutdown();
-                            Temp = NULL;
                             break;
              case osSwitchDvb:
-                            DELETENULL(Menu);
+                            DELETE_MENU;
                             cControl::Shutdown();
-                            Temp = NULL;
                             Skins.Message(mtInfo, tr("Switching primary DVB..."));
                             cDevice::SetPrimaryDevice(Setup.PrimaryDVB);
                             break;
-             case osPlugin: DELETENULL(Menu);
-                            Menu = Temp = cMenuMain::PluginOsdObject();
+             case osPlugin: DELETE_MENU;
+                            Menu = cMenuMain::PluginOsdObject();
                             if (Menu)
                                Menu->Show();
                             break;
              case osBack:
              case osEnd:    if (Interact == Menu)
-                               DELETENULL(Menu);
+                               DELETE_MENU;
                             else
                                cControl::Shutdown();
-                            Temp = NULL;
                             break;
              default:       ;
              }
@@ -913,7 +1010,6 @@
              case kPlay:
                   if (cReplayControl::LastReplayed()) {
                      cControl::Shutdown();
-                     Temp = NULL;
                      cControl::Launch(new cReplayControl);
                      }
                   break;
diff -ruN vdr-1.3.37/videodir.c vdr-1.3.38/videodir.c
--- vdr-1.3.37/videodir.c	2005-10-31 13:07:41.000000000 +0100
+++ vdr-1.3.38/videodir.c	2005-12-18 11:33:20.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: videodir.c 1.13 2005/10/31 12:07:41 kls Exp $
+ * $Id: videodir.c 1.14 2005/12/18 10:33:20 kls Exp $
  */
 
 #include "videodir.h"
@@ -16,6 +16,7 @@
 #include <string.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#include "recording.h"
 #include "tools.h"
 
 const char *VideoDirectory = VIDEODIR;
@@ -185,12 +186,17 @@
 int VideoDiskSpace(int *FreeMB, int *UsedMB)
 {
   int free = 0, used = 0;
+  int deleted = DeletedRecordings.TotalFileSizeMB();
   cVideoDirectory Dir;
   do {
      int u;
      free += Dir.FreeMB(&u);
      used += u;
      } while (Dir.Next());
+  if (deleted > used)
+     deleted = used; // let's not get beyond 100%
+  free += deleted;
+  used -= deleted;
   if (FreeMB)
      *FreeMB = free;
   if (UsedMB)
