diff -ruN vdr-2.3.8/CONTRIBUTORS vdr-2.3.9/CONTRIBUTORS
--- vdr-2.3.8/CONTRIBUTORS	2017-06-25 13:54:32.000000000 +0200
+++ vdr-2.3.9/CONTRIBUTORS	2018-03-17 11:55:43.000000000 +0100
@@ -1847,7 +1847,7 @@
  for exporting some libsi functions
  for suggesting to add functions to cDevice that allow derived output devices to
  implement scaling the video to a given size and location
- fpr sorting sources.conf by continous azimuth
+ for sorting sources.conf by continuous azimuth
 
 Mattias Gr�nlund <Mattias@Gronlund.net>
  for pointing out a missing cleanup at program exit in case there is a problem
@@ -2627,6 +2627,9 @@
  for making cEpgHandlers::BeginSegmentTransfer() boolean
  for suggesting to change tEventID back to u_int32_t
  for adding the 'aux' member to cEvent
+ for reporting a possible deadlock when quickly zapping through encrypted channels
+ for a patch that was used to implement cStatus::MarksModified()
+ for suggesting to no longer log unaligned marks in cMarks::Align()
 
 Peter Pinnau <vdr@unterbrecher.de>
  for reporting that 'uint32_t' requires including stdint.h in font.h on some systems
@@ -2837,6 +2840,11 @@
  false
  for reporting an invalid lock sequence in the epgsearch plugin, which turned out to
  be an abandoned member of class cSchedulesLock
+ for fixing handling VPS events outside the LingerLimit, which could cause recordings to
+ stop prematurely
+ for fixing handling timers during the change from DST to winter time
+ for fixing a possible crash with plugins that retrieve player information after a
+ replay has been stopped, but before the replay control has been destroyed
 
 Timo Helkio <timolavi@mbnet.fi>
  for reporting a hangup when replaying a TS recording with subtitles activated
@@ -3106,6 +3114,7 @@
  for fixing the link to "svdrpsend (1)" in the vdr.1 man page
  for updating links in the INSTALL file
  for reporting double slashes in file names processed with AddDirectory()
+ for using the 'example' macro in vdr.5
 
 Dominic Evans <oldmanuk@gmail.com>
  for making the SVDRP command LSTC accepts channel IDs
@@ -3277,6 +3286,24 @@
  various Now and Schedule menus for different channels
  for the "jumpingseconds" patch
  for reporting a bug in drawing very long menu titles in the LCARS skin
+ for reporting and helping to debug a crash when stopping VDR
+ for reporting a crash when moving a recording between different volumes
+ for reporting a deadlock when moving a folder containing several recordings between
+ different volumes
+ for fixing a lengthy write lock on the Recordings list in case of moving a folder with
+ more than one recording
+ for implementing the parameter "OSD/Sorting direction for recordings"
+ for suggesting to stay in the original folder when moving a recording to a different
+ folder
+ for reporting problem with locking the Recordings list in the cutting process
+ for suggesting to change the log message ""ERROR: copying directory '%s' to '%s' ended
+ prematurely" from "error" to "info"
+ for suggesting to allow opening a folder when selecting a folder for a recording or
+ timer, even if it doesn't contain any subfolders
+ for reporting a possible locking problem in cMenuPathEdit::ApplyChanges() when the
+ lock is held while the error message is displayed
+ for reporting that the info of a newly edited recording was not available immediately
+ after starting the editing process
 
 Marek Nazarko <mnazarko@gmail.com>
  for translating OSD texts to the Polish language
@@ -3370,6 +3397,8 @@
  for adding cFont::Width(void) to get the default character width and allow stretched
  font drawing in high level OSDs
  for fixing regenerating the index of audio recordings
+ for implementing the SVDRP commands 'LSTD' and 'PRIM'
+ for adding some comments regarding font height
 
 Eike Sauer <EikeSauer@t-online.de>
  for reporting a problem with channels that need more than 5 TS packets for detecting
@@ -3414,6 +3443,7 @@
  for suggesting to optionally list the channels with channel ids in the SVDRP command LSTC
  for suggesting to include the channel ID in log messages about switching channels
  for reporting a problem with the SVDRP command CHAN while the channel display is open
+ for suggesting to automatically close the CAM menu when the current channel is switched
 
 Stefan Schallenberg <infos@nafets.de>
  for adding the functions IndexOf(), InsertUnique(), AppendUnique() and RemoveElement()
@@ -3443,6 +3473,9 @@
  for suggesting to use $(Q) to control Makefile verbosity
  for adding handling DEBUG to the Make.config.template file, in order to control
  code optimization
+ for suggesting to change the macros used to control deprecated code or functions
+ to numeric values (0 and 1), so that they can be controlled at compile time, without
+ having to edit the actual source code
 
 Martin Schirrmacher <schirrmie@gmail.com>
  for suggesting to provide a way for skin plugins to get informed about the currently
@@ -3511,3 +3544,9 @@
 Daniel Scheller <d.scheller@gmx.net>
  for reporting a problem with detecting whether a CAM replies to queries, which didn't
  work on some systems since the implementation of RI_HOST_CONTROL
+
+Onur Sent�rk <onur@sentek.org>
+ for making the MTD mapper avoid immediately reusing unique PIDs when switching channels
+
+Helmut Binder <cco@aon.at>
+ for improving calculating signal strength and quality
diff -ruN vdr-2.3.8/HISTORY vdr-2.3.9/HISTORY
--- vdr-2.3.8/HISTORY	2017-06-30 11:49:48.000000000 +0200
+++ vdr-2.3.9/HISTORY	2018-03-18 13:03:46.000000000 +0100
@@ -3905,7 +3905,7 @@
 - Updated the French OSD texts (thanks to Nicolas Huillard).
 - Fixed the cFilter example in PLUGINS.html (reported by Patrick Fischer).
 - The new class cUnbufferedFile is used for the recording files to avoid
-  trashing the file system cache (based on a patch by Ralf M�ller).
+  thrashing the file system cache (based on a patch by Ralf M�ller).
 
 2005-11-06: Version 1.3.36
 
@@ -8866,7 +8866,7 @@
   font drawing in high level OSDs (thanks to Thomas Reufer).
 - Fixed regenerating the index of audio recordings (thanks to Thomas Reufer).
 - Fixed building VDR with systemd >= 230 (thanks to Ville Skytt�).
-- Sorted sources.conf by continous azimuth (thanks to Lucian Muresan).
+- Sorted sources.conf by continuous azimuth (thanks to Lucian Muresan).
 - Added 'S58.5E Kazsat 3' to sources.conf (thanks to Aitugan Sarbassov).
 - Fixed truncated date/time strings in the skins on multi-byte UTF-8 systems
   (reported by Sergey Chernyavskiy).
@@ -9161,3 +9161,158 @@
   This is especially useful if one VDR mounts the video directory of an other one into
   a subdirectory.
 - SVDRP peering can now be limited to the default SVDRP host (see MANUAL for details).
+
+2018-03-18: Version 2.3.9
+
+- Updated the Italian OSD texts (thanks to Diego Pierotto).
+- Updated the Finnish OSD texts (thanks to Rolf Ahrenberg).
+- Fixed a possible crash when stopping VDR (thanks to Matthias Senzel for reporting and
+  helping to debug this one).
+- Fixed handling VPS events outside the LingerLimit, which could cause recordings to
+  stop prematurely (thanks to Johann Friedrichs).
+- Fixed an invalid lock sequence when trying to remove a deleted recording in case
+  of low disk space.
+- Now making sure that AssertFreeDiskSpace() is called with the maximum timer
+  priority in case there are several timers recording with different priorities.
+- The MTD mapper now avoids immediately reusing unique PIDs when switching channels,
+  to prevent possible problems with old data in buffers (thanks to Onur Sent�rk).
+- The function cDevice::GetVideoSystem() (which has been deprecated since version 2.1.6)
+  has been finally removed.
+- The macros used to control deprecated code or functions have been changed to hold
+  numeric values (0 and 1), so that they can be controlled at compile time, without
+  having to edit the actual source code (suggested by Jasmin Jessich).
+- The default for DEPRECATED_VDR_CHARSET_OVERRIDE has been set to 0, which means VDR
+  no longer reacts on the environment variable VDR_CHARSET_OVERRIDE. You can add
+  'DEPRECATED_VDR_CHARSET_OVERRIDE=1' when compiling in order to restore this
+  functionality. However, it is recommended to use the command line option --chartab
+  instead.
+- The timeout for the channel display is now reset whenever the channel or EPG data
+  changes.
+- OSD menus now try to keep the offset of the list cursor at a constant position on
+  the screen, even if the list is modified while being displayed.
+- The LCARS skin's main menu now reacts to changes of the current channel's name.
+- If an event in the Schedules menu is marked with a 'T' or 'I' and the user presses the
+  Red button to edit the timer, local timers are now preferred over remote timers
+  in case there is more than one timer that will record that event.
+- Switching the primary device is no longer done via osSwitchDvb (which has been
+  removed), but rather by the main program loop reacting to changes in Setup.PrimaryDVB.
+- The new SVDRP commands 'LSTD' and 'PRIM' can be used to list all available devices
+  and to switch the primary device (thanks to Thomas Reufer).
+- Added some comments regarding font height (thanks to Thomas Reufer).
+- Fixed handling timers during the change from DST to winter time (thanks to Johann
+  Friedrichs).
+- Added missing checks of 'player' in member functions of cControl, and setting
+  cControl::player to NULL in cDvbPlayerControl::Stop() to avoid a possible crash
+  with plugins that retrieve player information after a replay has been stopped, but
+  before the replay control has been destroyed (thanks to Johann Friedrich).
+- Now calling Hide() and cStatus::MsgReplaying(..., false) from cReplayControl::Stop(),
+  to inform plugins about an ending replay session before the replay control gets
+  destroyed.
+- Fixed a possible crash when moving a recording between different volumes (reported by
+  Matthias Senzel).
+- Fixed positioning the cursor in the Recordings menu when moving a recording between
+  different volumes.
+- Added a note to PLUGINS.html about writing log messages in English.
+- Fixed a deadlock when moving a folder containing several recordings between
+  different volumes (reported by Matthias Senzel).
+- Fixed positioning to the current item when changing the sort mode in the Recordings
+  menu, in case there is a LastReplayed recording.
+- The CAM menu is now automatically closed when the current channel is switched
+  (suggested by Dietmar Spingler).
+- Fixed a lengthy write lock on the Recordings list in case of moving a folder with
+  more than one recording (thanks to Matthias Senzel).
+- If TS packets are not accepted by the output device in Transfer Mode, this is now
+  reported only once per minute in the log file.
+- The new setup option "OSD/Sorting direction for recordings" can be used to switch
+  the sequence in which recordings are presented in the "Recordings" menu between
+  ascending (oldest first) and descendeng (newest first) (thanks to Matthias Senzel).
+- When moving recordings between volumes, the "Recordings" menu now displays those items
+  that have not yet been moved completely as non-selectable. This avoids situations
+  where trying to play such a recording might fail.
+- Fixed canceling moving a folder with several recordings between volumes.
+- When moving a recording to a different folder, the cursor is no longer placed on the
+  new location of the recording, but rather stays in the original folder (suggested by
+  Matthias Senzel). If the original folder got empty by moving away the last recording
+  it contained, the cursor is moved up until a non empty folder is found.
+- Changed the log message ""ERROR: copying directory '%s' to '%s' ended prematurely" from
+  "error" to "info", because any actual error would have already been reported before this
+  (suggested by Matthias Senzel).
+- When selecting a folder for a recording or timer, it is now possible to open a folder
+  even if it doesn't contain any subfolders (suggested by Matthias Senzel).
+- Fixed a possible deadlock when detaching a receiver from a device.
+- Moved any locking from cutter.c into recording.c, to avoid a problem with locking
+  the Recordings list (reported by Matthias Senzel).
+- Now using the 'example' macro in vdr.5 (thanks to Chris Mayo).
+- Now unlocking the Recordings list before displaying an error message in
+  cMenuPathEdit::ApplyChanges() and cReplayControl::Stop() (reported by Matthias Senzel).
+- Fixed a possible deadlock when quickly zapping through encrypted channels (reported
+  by J�rg Wendel).
+- The new function cStatus::MarksModified() can be implemented by plugins to get
+  informed about any modifications to the editing marks of the currently played
+  recording (based on a patch from J�rg Wendel).
+- Fixed handling editing marks in the replay progress display, in case the marks are
+  deleted via the Info/Edit menu of the currently played recording (the progress
+  display still displayed them).
+- Limited some CAM related log messages to the actual master CAM, if any.
+- The Perl script 'peerdemo' shows how one can find all the VDRs in the local network
+  using the peer connection mechanism.
+- Added the UPDATE-2.4.0 file.
+- Making sure cSVDRPClient::Process() reads the entire reply once it started reading,
+  even if no Response parameter is given.
+- Replaced the warning regarding the open SVDRP port in the INSTALL file with a remark
+  about using svdrphosts.conf to completely disable SVDRP access.
+- Added a note about the fixed UDP port for SVDRP discovery to vdr.1.
+- Fixed updating the Timers menu after turning a local timer on/off with the Red
+  button.
+- Fixed keeping the cursor position in the Recordings menu in case a timer starts
+  recording while the menu is open.
+- When a timer is newly created in the Timers menu, it now immediately appears at the
+  correct position in the list, rather than first being added at the end and then
+  jumping to the proper offset.
+- Fixed getting the info of a newly edited recording (reported by Matthias Senzel).
+- Improved calculating signal strength and quality (thanks to Helmut Binder).
+- While a timer is recording, the file '.timer' in the recording directory now contains
+  the full id of the timer that is currently recording into this directory. This is used
+  to determine whether a timer is still recording on a remote VDR when deleting a recording
+  from the Recordings menu.
+- Fixed handling SVDRP peering for more than one instance of VDR on the same machine, and
+  improved logging and debug output.
+- Fixed case inconsistency with SVDRPDefaultHost in config.c.
+- Added a section about the '.sort' file to vdr.5.
+- Initiating the client side of a peer-to-peer SVDRP connection is now done with the new
+  SVDRP command CONN instead of using the UDP port with the server's address.
+  This change requires that all VDRs that shall take part in a peer-to-peer network need
+  to be updated to this version.
+- Moved handling remote timers into cSVDRPClientHandler::ProcessConnections().
+- Combined Start/StopSVDRPServer/ClientHandler() into Start/StopSVDRPHandler().
+- Updated the Polish OSD texts (thanks to Tomasz Maciej Nowak).
+- When remote timers are fetched from a peer VDR, we no longer blindly delete and re-add
+  them, but rather compare them and make only the minimum necessary changes.
+- Fixed the CompareInts() function.
+- Disabled the use of posix_fadvise() when reading (i.e. replaying), since it caused
+  stuttering replay in fast forward and fast rewind mode in case the video directory
+  is mounted via NFS. You can re-enable it by setting the macro USE_FADVISE_READ to 1
+  in tools.c.
+- Modified cStateLock's SetExplicitModify() and IncState() (changed to SetModified()) to
+  allow for the introduction of syncing a separate cStateKey to a cStateLock.
+- Assigning events to timers no longer triggers sending a POLL to all peer VDRs.
+- When making modifications to remote timers, the local VDR no longer sends a POLL to
+  all remote VDRs.
+- Fixed removing a cStateKey from a cStateLock (setting StateKey.stateLock = NULL was
+  done too late, after the lock had already been released).
+- Now writing the info file before attaching the recorder to the device, to make sure it
+  is present when the recorder needs to update the fps value.
+- Making sure the Schedules menu has a proper title, even if it is empty.
+- Removed sending the SVDRP command UPDR to peer VDRs whenever a change is made to the
+  recordings in the video directory (which was introduced in version 2.3.8), because it
+  triggered re-reading the video directory too fast.
+- Improved handling VPS timers to better react to EPG changes during an ongoing recording.
+- Commented out the logging in cMarks::Align(), to avoid log entries in case of editing
+  marks that are not generated by VDR itself, and thus may be a little off (suggested by
+  J�rg Wendel). You can activate this line again for debugging if necessary.
+- Made the input buffer in cSVDRPClient dynamic.
+- Fixed handling parameters in the S2SatelliteDeliverySystemDescriptor and
+  T2DeliverySystemDescriptor that were overwritten when parsing the
+  SatelliteDeliverySystemDescriptor or TerrestrialDeliverySystemDescriptor, respectively.
+- Modified cMenuTimers::Delete() to avoid a lengthy lock on the Timers list while prompting
+  the user.
diff -ruN vdr-2.3.8/INSTALL vdr-2.3.9/INSTALL
--- vdr-2.3.8/INSTALL	2017-06-12 10:57:12.000000000 +0200
+++ vdr-2.3.9/INSTALL	2018-02-09 16:11:35.000000000 +0100
@@ -73,16 +73,15 @@
 on port 6419 (use the --port=PORT option to change this). For details
 about the SVDRP syntax see the source file 'svdrp.c'.
 
-WARNING: DUE TO THE OPEN SVDRP PORT THIS PROGRAM MAY CONSTITUTE A
-=======  POTENTIAL SECURITY HAZARD! IF YOU ARE NOT RUNNING VDR IN
-         A CONTROLLED ENVIRONMENT, YOU MAY WANT TO DISABLE SVDRP
-         BY USING '--port=0'!
-
 The file 'svdrphosts.conf' can be used to define which hosts are allowed
 to access the SVDRP port. By default only localhost (127.0.0.1) is granted
 access. If you want to give other hosts access to your SVDRP port you need to
 add their IP numbers to 'svdrphosts.conf'.
 
+You can disable SVDRP access entirely by either running VDR with '--port=0',
+or by removing all entries (including 127.0.0.1 for the localhost) from
+'svdrphosts.conf'.
+
 If the program shall run as a daemon, use the --daemon option. This
 will completely detach it from the terminal and will continue as a
 background process.
diff -ruN vdr-2.3.8/MANUAL vdr-2.3.9/MANUAL
--- vdr-2.3.8/MANUAL	2017-06-30 11:49:39.000000000 +0200
+++ vdr-2.3.9/MANUAL	2017-12-09 15:52:53.000000000 +0100
@@ -665,6 +665,13 @@
                          If a particular sort mode has been selected for a folder by
                          pressing '0', the default no longer applies to that folder.
 
+  Sorting direction for recordings = ascending
+                         When recordings are sorted "by time", they appear in ascending
+                         order (i.e. "oldest" to "newest"). If this parameter is set to
+                         "descending", they will be presented "newest" to "oldest.
+                         Note that in the latter case, if "Always sort folders first"
+                         is "yes", folders will appear in reverse alphabetical order.
+
   Number keys for characters = yes
                          Controls whether the number keys can be used to enter
                          characters in a text input field. You may want to set this
diff -ruN vdr-2.3.8/PLUGINS/src/hello/po/pl_PL.po vdr-2.3.9/PLUGINS/src/hello/po/pl_PL.po
--- vdr-2.3.8/PLUGINS/src/hello/po/pl_PL.po	2017-06-10 18:11:50.414471527 +0200
+++ vdr-2.3.9/PLUGINS/src/hello/po/pl_PL.po	2018-03-14 23:46:37.853274172 +0100
@@ -2,34 +2,37 @@
 # Copyright (C) 2015 Klaus Schmidinger <vdr@tvdr.de>
 # This file is distributed under the same license as the VDR package.
 # Michael Rakowski <mrak@gmx.de>, 2002
+# Tomasz Maciej Nowak <tmn505@gmail.com>, 2018
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: vdr-hello 2.2.0\n"
 "Report-Msgid-Bugs-To: <see README>\n"
 "POT-Creation-Date: 2012-12-18 14:04+0100\n"
-"PO-Revision-Date: 2007-08-11 12:34+0200\n"
-"Last-Translator: Michael Rakowski <mrak@gmx.de>\n"
+"PO-Revision-Date: 2018-02-19 00:41+0100\n"
+"Last-Translator: Tomasz Maciej Nowak <tmn505@gmail.com>\n"
 "Language-Team: Polish <vdr@linuxtv.org>\n"
-"Language: pl\n"
+"Language: pl_PL\n"
 "MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=ISO-8859-2\n"
+"Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Poedit 2.0.6\n"
 
 msgid "A friendly greeting"
-msgstr ""
+msgstr "Przyjazne pozdrowienie"
 
 msgid "Hello"
-msgstr ""
+msgstr "Witaj"
 
 msgid "Greeting time (s)"
-msgstr ""
+msgstr "Czas pozdrowienia (s)"
 
 msgid "Use alternate greeting"
-msgstr ""
+msgstr "Użyj alternatywnego pozdrowienia"
 
 msgid "Howdy folks!"
-msgstr ""
+msgstr "Siema ziomy!"
 
 msgid "Hello world!"
-msgstr ""
+msgstr "Witaj świecie!"
diff -ruN vdr-2.3.8/PLUGINS/src/pictures/HISTORY vdr-2.3.9/PLUGINS/src/pictures/HISTORY
--- vdr-2.3.8/PLUGINS/src/pictures/HISTORY	2015-07-23 12:15:35.000000000 +0200
+++ vdr-2.3.9/PLUGINS/src/pictures/HISTORY	2017-10-06 17:12:25.000000000 +0200
@@ -100,6 +100,11 @@
 
 - Official release.
 
-2015-07.23: Version 2.3.1
+2015-07-23: Version 2.3.1
 
 - Added a missing 'const'.
+
+2017-10-06: Version 2.3.2
+
+- Adapted the pic2mpg script to new ffmpeg options.
+- No longer using 'convert' to scale/rotate the pictures.
diff -ruN vdr-2.3.8/PLUGINS/src/pictures/pic2mpg vdr-2.3.9/PLUGINS/src/pictures/pic2mpg
--- vdr-2.3.8/PLUGINS/src/pictures/pic2mpg	2013-07-01 10:33:38.000000000 +0200
+++ vdr-2.3.9/PLUGINS/src/pictures/pic2mpg	2017-10-06 16:42:18.000000000 +0200
@@ -7,7 +7,7 @@
 #
 # See the README file for copyright information and how to reach the author.
 #
-# $Id: pic2mpg 4.0 2013/07/01 08:33:38 kls Exp $
+# $Id: pic2mpg 4.1 2017/10/06 14:42:18 kls Exp $
 
 use File::Path;
 use File::Spec;
@@ -128,7 +128,7 @@
     for $dir (@Dirs) {
         $dir = EscapeMeta($dir);
         print "removing $dir\n";
-        !system("rm -rf $dir") || die "$dir: $!\n";
+        Exec("rm -rf $dir");
         }
     }
 
@@ -142,17 +142,18 @@
   my $Exif = ImageInfo($Pict);
   my $Orientation = $$Exif{"Orientation"};
   my ($Degrees) = $Orientation =~ /Rotate ([0-9]+)/;
-  my $Rotate = $Degrees ? "-rotate $Degrees" : "";
+  my $Rotate = ($Degrees == 90) ? "transpose=clock" : ($Degrees == 180) ? "hflip,vflip" : ($Degrees == 270) ? "transpose=cclock" : "";
+  $Rotate .= ',' if ($Rotate);
+  my $Background = '#000000@1';
   print "orientation = '$Orientation' -> rotation = $Rotate\n" if ($Detailed);
   $Pict = EscapeMeta($Pict);
   $Mpeg = EscapeMeta($Mpeg);
   print "$Pict -> $Mpeg $Rotate\n" if $ListFiles;
-  my $Cmd = "convert $Pict -background '#000000' $Rotate -resize $Size -gravity center -extent $Extent ppm:- | "
-          . "ffmpeg -f image2pipe -vcodec ppm -i pipe:0 -an -vcodec libx264 -vpre baseline -s $Size -qscale 2 -f mpegts -y $Mpeg "
+  my $Cmd = "ffmpeg -i $Pict -vf '${Rotate}scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:$Background' -c:v libx264 -pix_fmt yuv420p -f mpegts -y $Mpeg "
           . ($Detailed ? "" : "2>/dev/null");
-  !system($Cmd) || die "$Cmd: $!\n";
+  Exec($Cmd);
   $Cmd = "touch -r $Pict $Mpeg";
-  !system($Cmd) || die "$Cmd: $!\n";
+  Exec($Cmd);
 }
 
 sub EscapeMeta
@@ -162,3 +163,10 @@
   $s =~ s/([$META])/\\$1/g;
   return $s;
 }
+
+sub Exec
+{
+  my $Cmd = shift;
+  print "==> '$Cmd'\n" if ($Verbose);
+  !system($Cmd) || die "$Cmd: $!\n";
+}
diff -ruN vdr-2.3.8/PLUGINS/src/pictures/pictures.c vdr-2.3.9/PLUGINS/src/pictures/pictures.c
--- vdr-2.3.8/PLUGINS/src/pictures/pictures.c	2015-07-23 12:14:22.000000000 +0200
+++ vdr-2.3.9/PLUGINS/src/pictures/pictures.c	2017-10-06 17:10:44.000000000 +0200
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: pictures.c 4.1 2015/07/23 10:14:22 kls Exp $
+ * $Id: pictures.c 4.2 2017/10/06 15:10:44 kls Exp $
  */
 
 #include <getopt.h>
@@ -11,7 +11,7 @@
 #include "menu.h"
 #include "player.h"
 
-static const char *VERSION       = "2.3.1";
+static const char *VERSION       = "2.3.2";
 static const char *DESCRIPTION   = trNOOP("A simple picture viewer");
 static const char *MAINMENUENTRY = trNOOP("Pictures");
 
diff -ruN vdr-2.3.8/PLUGINS/src/pictures/po/pl_PL.po vdr-2.3.9/PLUGINS/src/pictures/po/pl_PL.po
--- vdr-2.3.8/PLUGINS/src/pictures/po/pl_PL.po	1970-01-01 01:00:00.000000000 +0100
+++ vdr-2.3.9/PLUGINS/src/pictures/po/pl_PL.po	2018-03-14 23:46:40.341235312 +0100
@@ -0,0 +1,34 @@
+# VDR plugin language source file.
+# Copyright (C) 2015 Klaus Schmidinger <vdr@tvdr.de>
+# This file is distributed under the same license as the VDR package.
+# Tomasz Maciej Nowak <tmn505@gmail.com>, 2018
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-pictures 2.2.0\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2018-02-19 00:48+0100\n"
+"PO-Revision-Date: 2018-02-19 00:53+0100\n"
+"Last-Translator: Tomasz Maciej Nowak <tmn505@gmail.com>\n"
+"Language-Team: Polish <vdr@linuxtv.org>\n"
+"Language: pl_PL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Poedit 2.0.6\n"
+
+msgid "Pictures"
+msgstr "Zdjęcia"
+
+msgid "A simple picture viewer"
+msgstr "Prosta przeglądarka zdjęć"
+
+msgid "Picture directory"
+msgstr "Katalog ze zdjęciami"
+
+msgid "Slide show delay (s)"
+msgstr "Pokaz slajdów opóźnienie (s)"
+
+msgid "No picture directory has been defined!"
+msgstr "Nie zdefiniowano katalogu ze zdjęciami!"
diff -ruN vdr-2.3.8/PLUGINS/src/skincurses/po/pl_PL.po vdr-2.3.9/PLUGINS/src/skincurses/po/pl_PL.po
--- vdr-2.3.8/PLUGINS/src/skincurses/po/pl_PL.po	1970-01-01 01:00:00.000000000 +0100
+++ vdr-2.3.9/PLUGINS/src/skincurses/po/pl_PL.po	2018-03-14 23:46:41.361219381 +0100
@@ -0,0 +1,32 @@
+# VDR plugin language source file.
+# Copyright (C) 2015 Klaus Schmidinger <vdr@tvdr.de>
+# This file is distributed under the same license as the VDR package.
+# Tomasz Maciej Nowak <tmn505@gmail.com>, 2018
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-skincurses 2.2.0\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2018-02-19 00:48+0100\n"
+"PO-Revision-Date: 2018-02-19 01:02+0100\n"
+"Last-Translator: Tomasz Maciej Nowak <tmn505@gmail.com>\n"
+"Language-Team: Polish <vdr@linuxtv.org>\n"
+"Language: pl_PL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Poedit 2.0.6\n"
+
+msgid "A text only skin"
+msgstr "Skóra tekstowa"
+
+msgid "Key$Mute"
+msgstr "Wycisz"
+
+#. TRANSLATORS: note the trailing blank!
+msgid "Volume "
+msgstr "Głośność "
+
+msgid "Text mode"
+msgstr "Tryb tekstowy"
diff -ruN vdr-2.3.8/PLUGINS.html vdr-2.3.9/PLUGINS.html
--- vdr-2.3.8/PLUGINS.html	2017-04-02 14:57:28.000000000 +0200
+++ vdr-2.3.9/PLUGINS.html	2017-11-29 11:00:48.000000000 +0100
@@ -605,7 +605,10 @@
 normal operation! The only exceptions being special debug information as described
 above, fatal error messages that will cause VDR to abort, or if it is the sole
 purpose of the plugin to display something on stdout, like for instance the
-<i>skincurses</i> plugin, which displays the OSD at the console.
+<i>skincurses</i> plugin, which displays the OSD at the console.<br>
+<br>
+Please make any log messages in <b>ENGLISH</b>! Logs are usually sent to the developers
+of a program, and they may not be able to read them if they are in an exotic language.
 </modified>
 
 <hr><h2><a name="Main menu entry">Main menu entry</a></h2>
diff -ruN vdr-2.3.8/UPDATE-2.4.0 vdr-2.3.9/UPDATE-2.4.0
--- vdr-2.3.8/UPDATE-2.4.0	1970-01-01 01:00:00.000000000 +0100
+++ vdr-2.3.9/UPDATE-2.4.0	2018-03-11 14:19:30.000000000 +0100
@@ -0,0 +1,440 @@
+This is a summary of the changes in VDR 2.4.0 since the last stable
+version 2.2.0. It only contains things that are of actual importance
+to the user and doesn't mention the many fixes and improvements that
+have been made "behind the scenes".
+
+See the file HISTORY for a detailed list of all changes.
+
+Peering:
+
+- If there is more than one VDR in the local network, they can now form a peer-to-peer
+  network, so that timers can be moved freely between them.
+  The following changes have been made to implement this:
+  + VDR now sends out a broadcast to port 6419/udp, which was assigned to 'svdrp-disc'
+    by the IANA. VDRs listening on that port will automatically initiate an SVDRP
+    connection to the broadcasting VDR, and in turn send out a broadcast to make
+    other VDRs connect to them. That way all VDRs within the local network will
+    have permanent "peer-to-peer" SVDRP connections between each other. The
+    configuration in the svdrphosts.conf file is taken into account when considering
+    whether or not to respond to an SVDRP discover broadcast.
+  + The new SVDRP command PING is used by automatically established peer-to-peer
+    connections to keep them alive.
+  + The new function GetSVDRPServerNames() can be used to get a list of all VDRs
+    this VDR is connected to via SVDRP.
+  + The new function ExecSVDRPCommand() can be used to execute an SVDRP command on
+    one of the servers this VDR is connected to, and retrieve the result.
+    The helper functions SVDRPCode() and SVDRPValue() can be used to easily access
+    the codes and values returned by ExecSVDRPCommand().
+  + The new SVDRP command POLL is used by automatically established peer-to-peer
+    connections to trigger fetching remote timers.
+  + The new options "Setup/Miscellaneous/SVDRP peering", ".../SVDRP host name" and
+    ".../SVDRP default host" can be used to configure automatic peering between VDRs
+    in the same network. Peering is disabled by default and can be enabled by setting
+    "SVDRP peering" to "yes".
+  + The "Edit timer" menu now has a new parameter "Record on", which can be used to
+    select the VDR on which this timer shall record. Timers can be freely moved
+    between connected VDRs by simply selecting the desired machine in this field.
+  + The cTimer class now has a new member named 'remote', which holds the name of the
+    remote server this timer will record on. If this is NULL, it is a local timer.
+  + Added a note to the "Pausing live video" section of the MANUAL, stating that
+    the timer for paused live video will always record on the local VDR, even if
+    an "SVDRP default host" has been set for normal timer recordings.
+  + The Perl script 'peerdemo' shows how one can find all the VDRs in the local network
+    using the peer connection mechanism.
+
+Conditional Access:
+
+- Implemented support for MTD ("Multi Transponder Decryption"). This allows a CAM
+  that is capable of decrypting more than one channel ("Multi Channel Decryption")
+  to decrypt channels from different transponders. See the remarks in mtd.h on
+  what a derived cCamSlot class needs to do in order to activate MTD.
+- The Setup/CAM menu now displays which device an individual CAM is currently
+  assigned to.
+- The channel/CAM relations (i.e. the information which CAM can decrypt a given
+  channel) are now stored in the file 'cam.data' in the cache directory.
+  This speeds up switching to encrypted channels after newly starting VDR, in case
+  there is more than one CAM in the system.
+  The file 'cam.data' is not written if it is read-only.
+- The mechanism of trying different CAMs when switching to an encrypted channel is
+  now only triggered if there acually is more than one CAM in the system.
+- CAMs that can handle multiple devices at the same time can now indicate this
+  by creating the first cCamSlot as usual, and every other cCamSlot by giving
+  it the first one as its "MasterSlot". To VDR this means that when searching
+  for a CAM that can decrypt a particular channel, it only needs to ask the
+  master CAM slot whether it is suitable for decrypting, and can skip all the
+  other slots belonging to the same master. This can greatly speed up channel
+  switching on systems with more than one CAM (that can handle multiple devices).
+- The LCARS skin now displays the master CAM's number when a device is tuned to
+  an encrypted channel.
+- The Setup/CAM menu now only displays master CAMs.
+- Detecting whether a particular CAM actually decrypts a given channel is now
+  done separately for each receiver.
+- The function cCamSlot::Decrypt() can now also be called with Data == NULL.
+  This is necessary to allow CAMs that copy the incoming data into a separate buffer
+  to return previously received and decrypted TS packets. See ci.h for details.
+  Plugins that implement a derived cCamSlot need to properly handle this case, and
+  plugins that implement a derived cDevice need to call Decrypt() in their
+  GetTSPacket() function even if the incoming buffer is currently empty (see
+  cDvbDevice::GetTSPacket()).
+- CAMs are now sent a generated EIT packet that contains a single 'present event' for
+  the current SID, in order to avoid any parental rating dialogs.
+- When selecting a device/CAM combination for live viewing, CAMs that are known to
+  decrypt the requested channel are now given a higher priority than prefering the
+  primary device.
+- Extended the CI API to allow plugins to implement additional CAM resources.
+- The new configuration file 'camresponses.conf' can be used to define automatic
+  responses to CAM menus, for instance to avoid annyoing popup messages or entering
+  the parental rating PIN. See vdr.5 for details.
+
+Timers:
+
+- The cTimer class now has a new member named 'remote', which holds the name of the
+  remote server this timer will record on. If this is NULL, it is a local timer.
+- Timers from other VDRs that are connected to this VDR via SVDRP are now
+  automatically fetched and stored in the global Timers list. In order for this
+  to work, all of the channels used by timers on the remote VDR must also be
+  defined on the local VDR (however, not necessarily in the same sequence).
+- Accessing the global Timers list now has to be protected by proper locking,
+  because SVDRP commands are now executed in a separate thread.
+  The introduction of this locking mechanism required the following changes:
+  + The new classes cStateLock and cStateKey are used to implement locking
+    with quick detection of state changes.
+  + cConfig::cConfig() now has a parameter that indicates whether this list
+    requires locking.
+  + The global lists of Timers, Channels, Schedules and Recordings are no longer
+    static variables. They are now pointers that need to be retrieved through
+    a call to cTimers::GetTimersRead/Write(), cChannels::GetChannelsRead/Write(),
+    cSchedules::GetSchedulesRead/Write() and cRecordings::GetRecordingsRead/Write(),
+    respectively.
+  + References from/to link channels are now removed in cChannels::Del() rather
+    than cChannel::~cChannel(), to make sure the caller holds a proper lock.
+  + cChannel::HasTimer() has been removed. This information is now retrieved
+    via cSchedule::HasTimer().
+  + Several member functions of cChannel, cTimer, cMarks and cRecording have
+    been made 'const', and some of them are now available as both 'const' and
+    'non-const' versions.
+  + The cChannel::Set...() functions are now 'bool' and return true if they have
+    actually changed any of the channels's members.
+  + cChannels::SetModified() has been renamed to cChannels::SetModifiedByUser().
+  + cChannels::Modified() has been renamed to cChannels::ModifiedByUser(), and
+    now has a 'State' parameter that allows the caller to see whether a channel
+    has been modified since the last call to this function with the same State
+    variable.
+  + The macros CHANNELSMOD_NONE/_AUTO/_USER have been removed.
+  + cMarks now requires locking via cStateKey.
+  + cSortedTimers now requires a pointer to the list of timers.
+  + cEvent::HasTimer() no longer scans the list of timers to check whether an event
+    is referenced by a timer, but rather keeps score of how many timers reference
+    it. This was necessary in order to avoid having to lock the list of timers from
+    within a cEvent.
+  + The new class cListGarbageCollector is used to temporary store any objects deleted
+    from cLists that require locking. This allows pointers to such objects to be
+    dereferenced even if the objects are no longer part of the list.
+  + cListBase::Contains() can be used to check whether a particular object is still
+    contained in that list.
+  + Outdated events are no longer "phased out", but rather deleted right away and thus
+    taken care of by the new "garbage collector" of the list.
+  + Deleted cRecording objects are no longer kept in a list of "vanished" recordings,
+    but are rather taken care of by the new "garbage collector" of the list.
+  + cSchedules::ClearAll() has been removed. The functionality is now implemented
+    directly in cSVDRPServer::CmdCLRE().
+  + cSchedule now has a member Modified(), which can be used with a State variable
+    to quickly determine whether this schedule has been modified since the last call
+    to this function with the same State variable.
+  + cSchedulesLock has been removed. Locking the list of schedules is now done via
+    the cList's new locking mechanism.
+  + The 'OnlyRunningStatus' parameters in cEpgHandler::BeginSegmentTransfer() and
+    cEpgHandler::EndSegmentTransfer() are now obsolete. They are still present in
+    the interface for backward compatibility, but may be removed in a future version.
+    Their value is always 'false'.
+  + The constant tcMod is no longer used in cStatus::TimerChange(). The definition is
+    still there for backward compatibility.
+- Plugins that access the global lists of Timers, Channels, Recordings or Schedules
+  will need to be adapted as follows:
+  + Instead of directly accessing the global variables Timers, Channels or Recordings,
+    they need to set up a cStateKey variable and call the proper getter function,
+    as in
+      cStateKey StateKey;
+      if (const cTimers *Timers = cTimers::GetTimersRead(StateKey)) {
+         // access the timers
+         StateKey.Remove();
+         }
+    and
+      cStateKey StateKey;
+      if (cTimers *Timers = cTimers::GetTimersWrite(StateKey)) {
+         // access the timers
+         StateKey.Remove();
+         }
+    See timers.h, thread.h and tools.h for details on this new locking mechanism.
+  + There are convenience macros for easily accessing these lists without having
+    to explicitly set up a cStateKey and calling its Remove() function. These macros
+    have the form LOCK_*_READ/WRITE (with '*' being TIMERS, CHANNELS, SCHEDULES or
+    RECORDINGS). Simply put such a macro before the point where you need to access
+    the respective list, and there will be a pointer named Timers, Channels, Schedules
+    or Recordings, respectively, which is valid until the end of the current block.
+  + If a plugin needs to access several of the global lists in parallel, locking must
+    always be done in the sequence Timers, Channels, Recordings, Schedules. This is
+    necessary to make sure that different threads that need to lock several lists at
+    the same time don't end up in a deadlock.
+  + Some pointer variables may need to be made 'const'. The compiler will tell you
+    about these.
+- If a timer is newly created with the Red button in the Schedule menu, and the timer
+  is presented to the user in the "Edit timer" menu because it will start immediately,
+  it now *must* be confirmed with "Ok" to set the timer. Otherwise the timer will not
+  be created.
+- The function cTimer::ToText() no longer returns a newline character at the end of
+  the string. The newline is now added by the caller as necessary. This was changed
+  because cTimer::ToText() is now also needed in a context where the terminating
+  newline can't be used. Consequently, cChannel::ToText() and cMark::ToText() have
+  been modified accordingly.
+- Timers now have unique ids instead of numbers, which remain valid as long as this
+  instance of VDR is running. This means that timers are no longer continuously
+  numbered from 1 to N in LSTT. There may be
+  gaps in the sequence, in case timers have been deleted.
+- Timers are now linked to EPG events even if they are inactive. By default Events that
+  are linked to inactive timers are marked with 'I' and 'i', depending on whether the
+  timer would record the entire Event or only part of it.
+  The function cSkinDisplayMenu::SetItemEvent() now has an additional parameter named
+  TimerActive, which indicates whether the timer that would record this event (if any)
+  is active. A plugin may react on this when displaying a menu line for an event.
+  The old version of cSkinDisplayMenu::SetItemEvent() (without the TimerActive
+  parameter) is still there for backwards compatibility. It may be removed in a future
+  version, so plugin authors should switch to the new one.
+- Improved handling VPS timers to better react to EPG changes during an ongoing recording.
+
+Plugins:
+
+- The dvbhddevice plugin is no longer part of the VDR source archive.
+  You can get the latest version of this plugin from the author's repository at
+  https://bitbucket.org/powARman/dvbhddevice.
+- The dvbsddevice and rcu plugins are no longer part of the VDR source archive.
+  You can get the latest versions of these plugins from ftp://ftp.tvdr.de/vdr/Plugins.
+- The -V and -h options now list the plugins in alphabetical order.
+- Added some guidelines and recommendations to the 'Logging' section of PLUGINS.html.
+  The most important being: implement a command line option to control the level
+  of logging (in particular allow turning off logging completely!) and never print
+  anything to stdout or stderr (unless one of the listed exceptions applies).
+- Added a note to PLUGINS.html about writing log messages in English.
+- The new function cStatus::MarksModified() can be implemented by plugins to get
+  informed about any modifications to the editing marks of the currently played
+  recording.
+
+Skins:
+
+- The main menu of the LCARS skin now displays a small rectangle on the left side
+  of a timer if this is a remote timer. The color of that rectangle changes if
+  the timer is currently recording on the remote VDR.
+- Skins can now implement cSkinDisplayMenu::MenuOrientation() to display horizontal
+  menus.
+- The LCARS skin now displays the master CAM's number when a device is tuned to
+  an encrypted channel.
+
+Remote control:
+
+- The new setup option "Recording/Record key handling" can be used to define
+  what happens if the Record key on the remote control is pressed during
+  live tv.
+- If the Channel+/- keys are pressed while in the Schedules menu, the menu is now
+  switched to the EPG of the new current channel.
+
+Devices:
+
+- The command line option -D now accepts the value '-' (as in -D-), which prevents
+  VDR from using any DVB devices.
+- The function cDevice::SetCurrentChannel(const cChannel *Channel) is now deprecated
+  and may be removed in a future version. Use SetCurrentChannel(int ChannelNumber)
+  instead.
+- Signal strength and quality (CNR) are now determined via DVB API 5 (if available).
+  Fallback is the old DVB API 3 method.
+- The new function cDevice::SignalStats() (if implemented by an actual device) returns
+  statistics about the currently received signal.
+- The function cDevice::GetVideoSystem() (which has been deprecated since version 2.1.6)
+  has been finally removed.
+- Switching the primary device is no longer done via osSwitchDvb (which has been
+  removed), but rather by the main program loop reacting to changes in Setup.PrimaryDVB.
+
+EPG:
+
+- The character 0x0D is now stripped from EPG texts.
+- The EPG scanner no longer moves the dish if there is a positioner.
+- The function cEpgHandlers::BeginSegmentTransfer() is now boolean.
+  See the description in epg.h for the meaning of the return value.
+- The cEvent class now has a new member 'aux', in which external applications can
+  store auxiliary information with an event. This string has no meaning whatsoever to
+  VDR itself, and it will not be written into the info file of a recording that is
+  made for such an event.
+- Changed the default return value of cEpgHandler::BeginSegmentTransfer() to true, to
+  avoid problems with derived classes that don't implement this function.
+
+OSD:
+
+- The new function cOsd::MaxPixmapSize() can be called to determine the maximum size
+  a cPixmap may have on the current OSD. The 'osddemo' example has been modified
+  accordingly. Plugin authors may want to use this function in case they use pixmaps
+  that are larger than the full OSD size. The default implementation sets this limit
+  to 2048x2048 pixel.
+- Added some comment to cPixmap about the relation between OSD, ViewPort and DrawPort.
+- The new setup option "OSD/Default sort mode for recordings" can be used to define
+  how recordings shall be sorted by default (either by time or by name, with "by time"
+  being the default). If a particular sort mode has been selected for a folder by
+  pressing '0', the default no longer applies to that folder. Repeating timers no
+  longer write a ".sort" file into a recordings folder to have the recordings sorted
+  by time.
+- The function cOsd::GetBitmap() is now 'protected'. If a plugin doesn't compile with
+  this version of VDR, you can uncomment the line
+  //#define DEPRECATED_GETBITMAP
+  in osd.h as a quick workaround. In the long run the plugin will need to be adapted.
+- Background modifications of channels, timers and events are now displayed immediately
+  in the corresponding menus.
+- The Timers menu now displays the name of the remote VDR in front of the timer's
+  file name, if this is a remote timer.
+- The width and height of the OSD are now limited to the actual maximum dimensions
+  of the output device, taking into account the top and left offset.
+- Added a note to the description of cFont::Size(), regarding possible differences
+  between it and cFont::Height().
+- Added cFont::Width(void) to get the default character width and allow stretched
+  font drawing in high level OSDs.
+- cOsdMenu::Display() now checks whether the OSD size has changed and if so calls
+  SetDisplayMenu().
+- The option "Setup/Miscellaneous/Show channel names with source" can now be set to
+  "type" or "full" to show either the type or the full name of the source.
+- The "Channels" menu now indicates whether a channel is encrypted ('X') or a radio
+  channel ('R').
+- The timeout for the channel display is now reset whenever the channel or EPG data
+  changes.
+- OSD menus now try to keep the offset of the list cursor at a constant position on
+  the screen, even if the list is modified while being displayed.
+- If an event in the Schedules menu is marked with a 'T' or 'I' and the user presses the
+  Red button to edit the timer, local timers are now preferred over remote timers
+  in case there is more than one timer that will record that event.
+- The new setup option "OSD/Sorting direction for recordings" can be used to switch
+  the sequence in which recordings are presented in the "Recordings" menu between
+  ascending (oldest first) and descendeng (newest first).
+- When selecting a folder for a recording or timer, it is now possible to open a folder
+  even if it doesn't contain any subfolders.
+
+Recordings:
+
+- Recordings now have unique ids instead of numbers, which remain valid as long as
+  this instance of VDR is running. This means that recordings are no longer continuously
+  numbered from 1 to N in LSTR. There may be gaps in the sequence, in case recordings
+  have been deleted, and they are not necessarily listed in numeric order.
+- Added detection of 24fps.
+- The script that gets called for recordings is now also called right before a
+  recording is edited, with the first parameter being "editing".
+- Implemented a frame parser for H.265 (HEVC) recordings.
+- When moving recordings between volumes, the "Recordings" menu now displays those items
+  that have not yet been moved completely as non-selectable. This avoids situations
+  where trying to play such a recording might fail.
+- When moving a recording to a different folder, the cursor is no longer placed on the
+  new location of the recording, but rather stays in the original folder.
+  If the original folder got empty by moving away the last recording
+  it contained, the cursor is moved up until a non empty folder is found.
+
+SVDRP:
+
+- The SVDRP port now accepts multiple concurrent connections. You can now keep an
+  SVDRP connection open as long as you wish, without preventing others from
+  connecting. Note, though, that SVDRP connections still get closed automatically
+  if there has been no activity for 300 seconds (configurable via
+  "Setup/Miscellaneous/SVDRP timeout (s)").
+- The SVDRP log messages have been unified and now always contain the IP and port
+  number of the remote host.
+- SVDRP connections are now handled in a separate "SVDRP server handler" thread,
+  which makes them more responsive. Note that there is only one thread that handles
+  all concurrent SVDRP connections. That way each SVDRP command is guaranteed to be
+  processed separately, without interfering with any other SVDRP commands that might
+  be issued at the same time. Plugins that implement SVDRP commands may need to take
+  care of proper locking if the commands access global data.
+- You can now set DumpSVDRPDataTransfer in svdrp.c to true to have all SVDRP
+  communication printed to the console for debugging.
+- The SVDRP commands that deal with timers (DELT, LSTT, MODT, NEWT, NEXT and UPDT)
+  as well as any log messages that refer to timers, now use a unique id for each
+  timer, which remains valid as long as this instance of VDR is running. This means
+  that timers are no longer continuously numbered from 1 to N in LSTT. There may be
+  gaps in the sequence, in case timers have been deleted.
+- All timer related response strings from SVDRP commands now use the channel ID
+  instead of channel numbers.
+- The SVDRP command DELT no longer checks whether the timer that shall be deleted
+  is currently recording.
+- The SVDRP command DELC now refuses to delete the very last channel in the list,
+  to avoid ending up with an empty channel list.
+- The SVDRP commands that deal with recordings (DELR, EDIT, LSTR, MOVR, and PLAY)
+  now use a unique id for each recording, which remains valid as long as this
+  instance of VDR is running. This means that recordings are no longer continuously
+  numbered from 1 to N in LSTR. There may be gaps in the sequence, in case recordings
+  have been deleted, and they are not necessarily listed in numeric order.
+- Changed 'number' to 'id' in the help texts of SVDRP commands that deal with
+  timers.
+- The SVDRP command LSTC can now list the channels with channel ids if the option
+  ':ids' is given.
+- If 0 is given as the channel number in the SVDRP command LSTC, the data of the
+  current channel is listed.
+- The new SVDRP commands 'LSTD' and 'PRIM' can be used to list all available devices
+  and to switch the primary device.
+
+Misc:
+
+- Added a section about Output Devices to the INSTALL file.
+- The -u option now also accepts a numerical user id.
+- The cRwLock class now allows nested read locks within a write lock from the
+  same thread. This fixes possible crashes when moving or deleting channels in
+  the menu or through SVDRP (as well as other operations that try to acquire a
+  read lock within a write lock).
+- Added support for the systemd watchdog.
+- PIDs can now be added to and deleted from a cReceiver while it is attached to
+  a cDevice, without having to detach it first and re-attach it afterwards.
+- Log messages about switching channels now include the channel ID.
+- The constructor of cHash (via cHashBase) now has an additional parameter (OwnObjects)
+  which, if set to true, makes the hash take ownership of the hashed objects, so that
+  they are deleted when the hash is cleared or destroyed.
+- cListObject now implements a private copy constructor and assignment operator, to keep
+  derived objects from calling them implicitly.
+- The Makefiles have been modified so that during the build process they no longer
+  display the actual (lengthy) commands, but rather just the name of the file that
+  is being built, as in
+  CC vdr.o
+  The first two characters indicate the kind of operation (CC=compile, LD=link,
+  AR=archive, MO=msgfmt, GT=xgettext, PO=msgmerge, IN=install).
+  This way it is much easier to spot error messages and warnings, since they are not
+  buried under tons of text.
+  Add VERBOSE=1 to the 'make' call in the VDR source directory to see the
+  actual commands that are executed.
+  Plugin authors should modify their makefiles accordingly, by simply preceeding
+  the respective commands with '$(Q)' and inserting '@echo XX $@' (where XX is one
+  of the character combinations listed above) before the command.
+  The newplugin script has also been modified accordingly.
+  Note that if you build a plugin directly in the plugin's own source directory,
+  the $(Q) macro won't be defined and commands will be displayed. You can add
+  Q=@ to the make call to have it less verbose (provided the plugin's Makefile
+  was modified as described above).
+- Added backtrace functions for debugging (see cBackTrace in thread.h).
+- Added checking the correct sequence of locking global lists.
+  At the first occurrence of an invalid locking
+  sequence, the 20 most recent locks will be written to the log file, followed by a
+  backtrace that led to the call in question. This code can be activated by defining
+  the macro DEBUG_LOCKSEQ in thread.c (which is on by default).
+  When debugging an actual invalid locking sequence, you can additionally define
+  the macro DEBUG_LOCKCALL in thread.c, which will add information about the caller
+  of each lock. Note that this may cause some stress on the CPU, therefore it is off
+  by default.
+- The file Make.config.template now reacts on DEBUG=1 in the 'make' command line,
+  and disables code optimizations by setting -O0.
+  This can be helpful when backtracing highly optimized code. You may want to
+  'make distclean' before running 'make' with a modified setting of DEBUG, to make
+  sure all object files are newly compiled.
+- Introduced the new macro DISABLE_TEMPLATES_COLLIDING_WITH_STL, which can be defined
+  before including tools.h in case some plugin needs to use the STL and gets error
+  messages regarding one of the template functions defined in tools.h.
+- The macros used to control deprecated code or functions have been changed to hold
+  numeric values (0 and 1), so that they can be controlled at compile time, without
+  having to edit the actual source code.
+- The default for DEPRECATED_VDR_CHARSET_OVERRIDE has been set to 0, which means VDR
+  no longer reacts on the environment variable VDR_CHARSET_OVERRIDE. You can add
+  'DEPRECATED_VDR_CHARSET_OVERRIDE=1' when compiling in order to restore this
+  functionality. However, it is recommended to use the command line option --chartab
+  instead.
+- Disabled the use of posix_fadvise() when reading (i.e. replaying), since it caused
+  stuttering replay in fast forward and fast rewind mode in case the video directory
+  is mounted via NFS. You can re-enable it by setting the macro USE_FADVISE_READ to 1
+  in tools.c.
diff -ruN vdr-2.3.8/ci.c vdr-2.3.9/ci.c
--- vdr-2.3.8/ci.c	2017-06-19 14:13:38.000000000 +0200
+++ vdr-2.3.9/ci.c	2018-02-03 13:55:03.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 4.18 2017/06/19 12:13:38 kls Exp $
+ * $Id: ci.c 4.20 2018/02/03 12:55:03 kls Exp $
  */
 
 #include "ci.h"
@@ -122,6 +122,8 @@
   uchar *bufp;
   uchar mtdCatBuffer[TS_SIZE]; // TODO: handle multi packet CATs!
   int length;
+  cMutex mutex;
+  bool handlingPid;
   void AddEmmPid(int Pid);
   void DelEmmPids(void);
 public:
@@ -130,6 +132,15 @@
   virtual void Receive(const uchar *Data, int Length);
   bool HasCaPids(void) const { return NumPids() - emmPids.Size() - 1 > 0; }
   void Reset(void) { DelEmmPids(); catVersion = -1; }
+  bool HandlingPid(void);
+       ///< The cCaPidReceiver adds/deletes PIDs to/from the base class cReceiver,
+       ///< which in turn does the same on the cDevice it is attached to. The cDevice
+       ///< then sets the PIDs on the assigned cCamSlot, which can cause a deadlock on the
+       ///< cCamSlot's mutex if a cReceiver is detached from the device at the same time.
+       ///< Since these PIDs, however, are none that have to be decrypted,
+       ///< it is not necessary to set them in the CAM. Therefore this function is
+       ///< used in cCamSlot::SetPid() to detect this situation, and thus avoid the
+       ///< deadlock.
   };
 
 cCaPidReceiver::cCaPidReceiver(void)
@@ -137,7 +148,11 @@
   catVersion = -1;
   bufp = NULL;
   length = 0;
+  handlingPid = false;
+  cMutexLock MutexLock(&mutex);
+  handlingPid = true;
   AddPid(CATPID);
+  handlingPid = false;
 }
 
 void cCaPidReceiver::AddEmmPid(int Pid)
@@ -147,14 +162,20 @@
          return;
       }
   emmPids.Append(Pid);
+  cMutexLock MutexLock(&mutex);
+  handlingPid = true;
   AddPid(Pid);
+  handlingPid = false;
 }
 
 void cCaPidReceiver::DelEmmPids(void)
 {
+  cMutexLock MutexLock(&mutex);
+  handlingPid = true;
   for (int i = 0; i < emmPids.Size(); i++)
       DelPid(emmPids[i]);
   emmPids.Clear();
+  handlingPid = false;
 }
 
 void cCaPidReceiver::Receive(const uchar *Data, int Length)
@@ -239,6 +260,12 @@
      }
 }
 
+bool cCaPidReceiver::HandlingPid(void)
+{
+  cMutexLock MutexLock(&mutex);
+  return handlingPid;
+}
+
 // --- cCaActivationReceiver -------------------------------------------------
 
 // A receiver that is used to make the device stay on a given channel and
@@ -279,7 +306,7 @@
      if (TsIsScrambled(Data))
         lastScrambledTime = Now;
      else if (Now - lastScrambledTime > UNSCRAMBLE_TIME) {
-        dsyslog("CAM %d: activated!", camSlot->SlotNumber());
+        dsyslog("CAM %d: activated!", camSlot->MasterSlotNumber());
         Skins.QueueMessage(mtInfo, tr("CAM activated!"));
         cDevice *d = Device();
         Detach();
@@ -1111,10 +1138,12 @@
        case AOT_CA_PMT_REPLY: {
             dbgprotocol("Slot %d: <== Ca Pmt Reply (%d)", CamSlot()->SlotNumber(), SessionId());
             if (!repliesToQuery) {
-               dsyslog("CAM %d: replies to QUERY - multi channel decryption (MCD) possible", CamSlot()->SlotNumber());
+               if (CamSlot()->IsMasterSlot())
+                  dsyslog("CAM %d: replies to QUERY - multi channel decryption (MCD) possible", CamSlot()->SlotNumber());
                repliesToQuery = true;
                if (CamSlot()->MtdAvailable()) {
-                  dsyslog("CAM %d: supports multi transponder decryption (MTD)", CamSlot()->SlotNumber());
+                  if (CamSlot()->IsMasterSlot())
+                     dsyslog("CAM %d: supports multi transponder decryption (MTD)", CamSlot()->SlotNumber());
                   CamSlot()->MtdActivate(true);
                   }
                }
@@ -2588,6 +2617,8 @@
 
 void cCamSlot::SetPid(int Pid, bool Active)
 {
+  if (caPidReceiver && caPidReceiver->HandlingPid())
+     return;
   cMutexLock MutexLock(&mutex);
   for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
       for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) {
diff -ruN vdr-2.3.8/ci.h vdr-2.3.9/ci.h
--- vdr-2.3.8/ci.h	2017-06-09 16:21:26.000000000 +0200
+++ vdr-2.3.9/ci.h	2018-03-17 13:17:37.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 4.10 2017/06/09 14:21:26 kls Exp $
+ * $Id: ci.h 4.12 2018/03/17 12:17:37 kls Exp $
  */
 
 #ifndef __CI_H
@@ -304,7 +304,7 @@
   virtual ~cCamSlot();
   bool IsMasterSlot(void) { return !masterSlot; }
        ///< Returns true if this CAM slot itself is a master slot (which means that
-       ///< it doesn't have pointer to another CAM slot that's its master).
+       ///< it doesn't have a pointer to another CAM slot that's its master).
   cCamSlot *MasterSlot(void) { return masterSlot ? masterSlot : this; }
        ///< Returns this CAM slot's master slot, or a pointer to itself if it is a
        ///< master slot.
@@ -402,7 +402,7 @@
        ///< call to AddPid()) to Active. A later call to StartDecrypting() will
        ///< send the full list of currently active CA_PMT entries to the CAM.
   virtual void AddChannel(const cChannel *Channel);
-       ///< Adds all PIDs if the given Channel to the current list of PIDs.
+       ///< Adds all PIDs of the given Channel to the current list of PIDs.
        ///< If the source or transponder of the channel are different than
        ///< what was given in a previous call to AddChannel(), any previously
        ///< added PIDs will be cleared.
diff -ruN vdr-2.3.8/config.c vdr-2.3.9/config.c
--- vdr-2.3.8/config.c	2017-05-21 12:25:26.000000000 +0200
+++ vdr-2.3.9/config.c	2018-02-15 15:40:36.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 4.6 2017/05/21 10:25:26 kls Exp $
+ * $Id: config.c 4.8 2018/02/15 14:40:36 kls Exp $
  */
 
 #include "config.h"
@@ -432,6 +432,7 @@
   FoldersInTimerMenu = 1;
   AlwaysSortFoldersFirst = 1;
   DefaultSortModeRec = rsmTime;
+  RecSortingDirection = rsdAscending;
   NumberKeysForChars = 1;
   ColorKey0 = 0;
   ColorKey1 = 1;
@@ -641,7 +642,7 @@
   else if (!strcasecmp(Name, "SVDRPTimeout"))        SVDRPTimeout       = atoi(Value);
   else if (!strcasecmp(Name, "SVDRPPeering"))        SVDRPPeering       = atoi(Value);
   else if (!strcasecmp(Name, "SVDRPHostName"))     { if (*Value) strn0cpy(SVDRPHostName, Value, sizeof(SVDRPHostName)); }
-  else if (!strcasecmp(Name, "SVDRPdefaultHost"))    strn0cpy(SVDRPDefaultHost, Value, sizeof(SVDRPDefaultHost));
+  else if (!strcasecmp(Name, "SVDRPDefaultHost"))    strn0cpy(SVDRPDefaultHost, Value, sizeof(SVDRPDefaultHost));
   else if (!strcasecmp(Name, "ZapTimeout"))          ZapTimeout         = atoi(Value);
   else if (!strcasecmp(Name, "ChannelEntryTimeout")) ChannelEntryTimeout= atoi(Value);
   else if (!strcasecmp(Name, "RcRepeatDelay"))       RcRepeatDelay      = atoi(Value);
@@ -658,6 +659,7 @@
   else if (!strcasecmp(Name, "RecordingDirs"))       RecordingDirs      = atoi(Value);
   else if (!strcasecmp(Name, "FoldersInTimerMenu"))  FoldersInTimerMenu = atoi(Value);
   else if (!strcasecmp(Name, "AlwaysSortFoldersFirst")) AlwaysSortFoldersFirst = atoi(Value);
+  else if (!strcasecmp(Name, "RecSortingDirection")) RecSortingDirection= atoi(Value);
   else if (!strcasecmp(Name, "DefaultSortModeRec"))  DefaultSortModeRec = atoi(Value);
   else if (!strcasecmp(Name, "NumberKeysForChars"))  NumberKeysForChars = atoi(Value);
   else if (!strcasecmp(Name, "ColorKey0"))           ColorKey0          = atoi(Value);
@@ -789,6 +791,7 @@
   Store("RecordingDirs",      RecordingDirs);
   Store("FoldersInTimerMenu", FoldersInTimerMenu);
   Store("AlwaysSortFoldersFirst", AlwaysSortFoldersFirst);
+  Store("RecSortingDirection",RecSortingDirection);
   Store("DefaultSortModeRec", DefaultSortModeRec);
   Store("NumberKeysForChars", NumberKeysForChars);
   Store("ColorKey0",          ColorKey0);
diff -ruN vdr-2.3.8/config.h vdr-2.3.9/config.h
--- vdr-2.3.8/config.h	2017-06-12 10:58:26.000000000 +0200
+++ vdr-2.3.9/config.h	2017-12-09 15:15:58.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 4.12 2017/06/12 08:58:26 kls Exp $
+ * $Id: config.h 4.14 2017/12/09 14:15:58 kls Exp $
  */
 
 #ifndef __CONFIG_H
@@ -22,13 +22,13 @@
 
 // VDR's own version number:
 
-#define VDRVERSION  "2.3.8"
-#define VDRVERSNUM   20308  // Version * 10000 + Major * 100 + Minor
+#define VDRVERSION  "2.3.9"
+#define VDRVERSNUM   20309  // Version * 10000 + Major * 100 + Minor
 
 // The plugin API's version number:
 
-#define APIVERSION  "2.3.8"
-#define APIVERSNUM   20308  // Version * 10000 + Major * 100 + Minor
+#define APIVERSION  "2.3.9"
+#define APIVERSNUM   20309  // Version * 10000 + Major * 100 + Minor
 
 // When loading plugins, VDR searches them by their APIVERSION, which
 // may be smaller than VDRVERSION in case there have been no changes to
@@ -306,6 +306,7 @@
   int FoldersInTimerMenu;
   int AlwaysSortFoldersFirst;
   int DefaultSortModeRec;
+  int RecSortingDirection;
   int NumberKeysForChars;
   int ColorKey0, ColorKey1, ColorKey2, ColorKey3;
   int VideoDisplayFormat;
diff -ruN vdr-2.3.8/cutter.c vdr-2.3.9/cutter.c
--- vdr-2.3.8/cutter.c	2017-05-21 11:45:06.000000000 +0200
+++ vdr-2.3.9/cutter.c	2018-01-18 13:19:31.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: cutter.c 4.3 2017/05/21 09:45:06 kls Exp $
+ * $Id: cutter.c 4.6 2018/01/18 12:19:31 kls Exp $
  */
 
 #include "cutter.h"
@@ -677,8 +677,6 @@
               cRecordingUserCommand::InvokeCommand(RUC_EDITINGRECORDING, editedVersionName, originalVersionName);
               if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) {
                  Recording.WriteInfo(editedVersionName);
-                 LOCK_RECORDINGS_WRITE;
-                 Recordings->AddByName(editedVersionName, false);
                  cuttingThread = new cCuttingThread(originalVersionName, editedVersionName);
                  return true;
                  }
@@ -702,9 +700,6 @@
         esyslog("ERROR: '%s' during editing process", Error);
      if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0)
         cControl::Shutdown();
-     cVideoDirectory::RemoveVideoFile(editedVersionName);
-     LOCK_RECORDINGS_WRITE;
-     Recordings->DelByName(editedVersionName);
      }
 }
 
diff -ruN vdr-2.3.8/device.c vdr-2.3.9/device.c
--- vdr-2.3.8/device.c	2017-05-30 13:06:11.000000000 +0200
+++ vdr-2.3.9/device.c	2018-01-27 15:45:08.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 4.23 2017/05/30 11:06:11 kls Exp $
+ * $Id: device.c 4.26 2018/01/27 14:45:08 kls Exp $
  */
 
 #include "device.h"
@@ -120,6 +120,7 @@
   delete dvbSubtitleConverter;
   if (this == primaryDevice)
      primaryDevice = NULL;
+  Cancel(3);
 }
 
 bool cDevice::WaitForAllDevicesReady(int Timeout)
@@ -1673,6 +1674,7 @@
                  int Pid = TsPid(b);
                  bool IsScrambled = TsIsScrambled(b);
                  for (int i = 0; i < MAXRECEIVERS; i++) {
+                     cMutexLock MutexLock(&mutexReceiver);
                      cReceiver *Receiver = receiver[i];
                      if (Receiver && Receiver->WantsPid(Pid)) {
                         Receiver->Receive(b, TS_SIZE);
@@ -1768,10 +1770,8 @@
                 }
              }
          Receiver->Activate(true);
-         Lock();
          Receiver->device = this;
          receiver[i] = Receiver;
-         Unlock();
          if (camSlot && Receiver->priority > MINPRIORITY) { // priority check to avoid an infinite loop with the CAM slot's caPidReceiver
             camSlot->StartDecrypting();
             if (camSlot->WantsTsData()) {
@@ -1801,13 +1801,11 @@
   if (!Receiver || Receiver->device != this)
      return;
   bool receiversLeft = false;
-  cMutexLock MutexLock(&mutexReceiver);
+  mutexReceiver.Lock();
   for (int i = 0; i < MAXRECEIVERS; i++) {
       if (receiver[i] == Receiver) {
-         Lock();
          receiver[i] = NULL;
          Receiver->device = NULL;
-         Unlock();
          Receiver->Activate(false);
          for (int n = 0; n < Receiver->numPids; n++)
              DelPid(Receiver->pids[n]);
@@ -1815,6 +1813,7 @@
       else if (receiver[i])
          receiversLeft = true;
       }
+  mutexReceiver.Unlock();
   if (camSlot) {
      if (Receiver->priority > MINPRIORITY) { // priority check to avoid an infinite loop with the CAM slot's caPidReceiver
         camSlot->StartDecrypting();
diff -ruN vdr-2.3.8/device.h vdr-2.3.9/device.h
--- vdr-2.3.8/device.h	2017-05-30 13:06:11.000000000 +0200
+++ vdr-2.3.9/device.h	2017-11-02 15:47:33.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 4.10 2017/05/30 11:06:11 kls Exp $
+ * $Id: device.h 4.12 2017/11/02 14:47:33 kls Exp $
  */
 
 #ifndef __DEVICE_H
@@ -55,13 +55,6 @@
                  // KNOWN TO YOUR PLAYER.
                };
 
-//#define DEPRECATED_VIDEOSYSTEM
-#ifdef DEPRECATED_VIDEOSYSTEM
-enum eVideoSystem { vsPAL,
-                    vsNTSC
-                  };
-#endif
-
 enum eVideoDisplayFormat { vdfPanAndScan,
                            vdfLetterBox,
                            vdfCenterCutOut
@@ -356,8 +349,10 @@
 public:
   static int CurrentChannel(void) { return primaryDevice ? currentChannel : 0; }
          ///< Returns the number of the current channel on the primary device.
-#define DEPRECATED_SETCURRENTCHANNEL
-#ifdef DEPRECATED_SETCURRENTCHANNEL
+#ifndef DEPRECATED_SETCURRENTCHANNEL
+#define DEPRECATED_SETCURRENTCHANNEL 1
+#endif
+#if DEPRECATED_SETCURRENTCHANNEL
   static void SetCurrentChannel(const cChannel *Channel) { currentChannel = Channel ? Channel->Number() : 0; }
 #endif
   static void SetCurrentChannel(int ChannelNumber) { currentChannel = ChannelNumber; }
@@ -510,12 +505,6 @@
          ///< if this device has an MPEG decoder).
          ///< NOTE: this is only for SD devices. HD devices shall implement their
          ///< own setup menu with the necessary parameters for controlling output.
-#ifdef DEPRECATED_VIDEOSYSTEM
-  virtual eVideoSystem GetVideoSystem(void) { return vsPAL; }
-         ///< Returns the video system of the currently displayed material
-         ///< (default is PAL).
-         ///< This function is deprecated and will be removed in a future version!
-#endif
   virtual void GetVideoSize(int &Width, int &Height, double &VideoAspect);
          ///< Returns the Width, Height and VideoAspect ratio of the currently
          ///< displayed video material. Width and Height are given in pixel
diff -ruN vdr-2.3.8/dvbdevice.c vdr-2.3.9/dvbdevice.c
--- vdr-2.3.8/dvbdevice.c	2017-05-09 13:50:38.000000000 +0200
+++ vdr-2.3.9/dvbdevice.c	2018-02-15 16:37:01.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbdevice.c 4.13 2017/05/09 11:50:38 kls Exp $
+ * $Id: dvbdevice.c 4.16 2018/02/15 15:37:01 kls Exp $
  */
 
 #include "dvbdevice.h"
@@ -699,6 +699,231 @@
   return 100 - v * v;
 }
 
+#define REF_S1(q1)                 (mod == QPSK)   ? q1 : 0
+#define REF_S2(q1, q2, q3, q4)     (mod == QPSK)   ? q1 : (mod == PSK_8)  ? q2 : (mod == APSK_16) ? q3 : (mod == APSK_32) ? q4 : 0
+#define REF_T1(q1, q2, q3)         (mod == QPSK)   ? q1 : (mod == QAM_16) ? q2 : (mod == QAM_64)  ? q3 : 0
+#define REF_T2(q1, q2, q3, q4)     (mod == QPSK)   ? q1 : (mod == QAM_16) ? q2 : (mod == QAM_64)  ? q3 : (mod == QAM_256) ? q4 : 0
+#define REF_C1(q1, q2, q3, q4, q5) (mod == QAM_16) ? q1 : (mod == QAM_32) ? q2 : (mod == QAM_64)  ? q3 : (mod == QAM_128) ? q4 : (mod == QAM_256) ? q5: 0
+
+int StrengthToSSI(const cChannel *Channel, int Strength, int FeModulation, int FeCoderateH, int FeFec)
+{
+  // Strength in 0.001dBm (dBm x 1000)
+  cDvbTransponderParameters dtp(Channel->Parameters());
+  int ssi = 0; // 0-100
+  int mod = (FeModulation >= 0) ? FeModulation : dtp.Modulation();
+  int cod = (FeCoderateH >= 0) ? FeCoderateH : dtp.CoderateH(); // DVB-T
+  int fec = (FeFec >= 0) ? FeFec : dtp.CoderateH();
+  if (Channel->IsTerr()) {
+     int pref = 0;
+     // NorDig Unified Ver. 2.6 - 3.4.4.6 Page 43 ff.
+     // reference values : pref-15dBm = 0%, pref+35dBm = 100%
+     if (dtp.System() == DVB_SYSTEM_1) { // DVB-T
+        fec = cod; // adjustment for DVB-T
+        if (mod == QAM_AUTO) mod = QPSK;
+        switch (fec) {  // dBm:        Q4  Q16  Q64
+          case FEC_1_2: pref = REF_T1(-93, -87, -82); break;
+          default:
+          case FEC_2_3: pref = REF_T1(-91, -85, -80); break;
+          case FEC_3_4: pref = REF_T1(-90, -84, -78); break;
+          case FEC_5_6: pref = REF_T1(-89, -83, -77); break;
+          case FEC_7_8: pref = REF_T1(-88, -82, -76); break;
+          }
+        }
+     else { // DVB-T2
+        if (mod == QAM_AUTO) mod = QAM_64;
+        switch (fec) {  // dBm:        Q4  Q16  Q64 Q256
+          case FEC_1_2: pref = REF_T2(-96, -91, -86, -82); break;
+          default:
+          case FEC_3_5: pref = REF_T2(-95, -89, -85, -80); break;
+          case FEC_2_3: pref = REF_T2(-94, -88, -83, -78); break;
+          case FEC_3_4: pref = REF_T2(-93, -87, -82, -76); break;
+          case FEC_4_5: pref = REF_T2(-92, -86, -81, -75); break;
+          case FEC_5_6: pref = REF_T2(-92, -86, -80, -74); break;
+          }
+        }
+     if (pref) {
+        int prel = (Strength / 1000) - pref;
+        ssi = (prel < -15) ? 0 :
+              (prel <   0) ? (prel + 15) * 2 / 3 :       //  0% -  10%
+              (prel <  20) ? prel * 4 + 10 :             // 10% -  90%
+              (prel <  35) ? (prel - 20) * 2 / 3 + 90 :  // 90% - 100%
+              100;
+#ifdef DEBUG_SIGNALSTRENGTH
+        fprintf(stderr, "SSI-T: STR:%d, Pref:%d, Prel:%d, ssi:%d%%(sys:%d, mod:%d, fec:%d)\n", Strength, pref, prel, ssi, dtp.System(), mod, fec);
+#endif
+        }
+     }
+  else if (Channel->IsCable()) { // ! COMPLETELY UNTESTED !
+     // Formula: pref(dB) = -174.0 + NoiseFigure + SymRef + CnRef
+     // NoiseFigure = 6.5 dB;               -> Tuner specific - range: 3.5 .. 9.0 dB
+     // SymRef = 10*log(6900000) = 68.5 dB; -> for Symbolrate of 6900 kSym/sec (TV: 6900, 6750 or 6111 kSym/sec)
+     // ==> pref(dB) = -174.0 + 6.5 + 68.5 + CnRef[modulation]{20,23,26,29,32}; (+/- 3 dB tuner specific)
+     if (mod == QAM_AUTO) mod = QAM_256;
+     //                Q16  Q32  Q64 Q128 Q256
+     int pref = REF_C1(-79, -76, -73, -70, -67);
+     if (pref) {
+        int prel = (Strength / 1000) - pref;
+        ssi = (prel < -15) ? 0 :
+              (prel <   0) ? (prel + 15) * 2 / 3 :       //  0% -  10%
+              (prel <  20) ? prel * 4 + 10 :             // 10% -  90%
+              (prel <  35) ? (prel - 20) * 2 / 3 + 90 :  // 90% - 100%
+              100;
+#ifdef DEBUG_SIGNALSTRENGTH
+        fprintf(stderr, "SSI-C: STR:%d, Pref:%d, Prel:%d, ssi:%d%%(mod:%d)\n", Strength, pref, prel, ssi, mod);
+#endif
+        }
+     }
+  else if (Channel->IsSat())
+     ssi = dB1000toPercent(Strength, -95000, -20000); // defaults
+  return ssi;
+}
+
+// Due to missing values or the different meanings of the reported error rate, ber_sqi is currently not taken into account
+#define IGNORE_BER 1
+#define BER_ERROR_FREE (1000*1000*1000) // 1/10^-9
+
+int SignalToSQI(const cChannel *Channel, int Signal, int Ber, int FeModulation, int FeCoderateH, int FeFec)
+{
+#if IGNORE_BER
+  Ber = BER_ERROR_FREE; // assume/pretend to be biterror free
+#endif
+  // Signal in 0.001dB (dB x 1000)
+  cDvbTransponderParameters dtp(Channel->Parameters());
+  int sqi = 0; // 0-100
+  int mod = (FeModulation >= 0) ? FeModulation : dtp.Modulation();
+  int cod = (FeCoderateH >= 0) ? FeCoderateH : dtp.CoderateH(); // DVB-T
+  int fec = (FeFec >= 0) ? FeFec : dtp.CoderateH();
+  if (Channel->IsTerr()) { // QEF: BER 10^-6
+     int cnref = 0;
+     // NorDig Unified Ver. 2.6 - 3.4.4.7 Page 45 ff.
+     // reference values for QEF (BER 10^-11 at MPEG2 demux input)
+     if (dtp.System() == DVB_SYSTEM_1) { // DVB-T
+        fec = cod; // adjustment for DVB-T
+        if (mod == QAM_AUTO) mod = QPSK;
+        switch (fec) {  // 0.1 dB      Q4  Q16  Q64 (Hierarchy=None)
+          case FEC_1_2: cnref = REF_T1(51, 108, 165); break;
+          default:
+          case FEC_2_3: cnref = REF_T1(69, 131, 187); break;
+          case FEC_3_4: cnref = REF_T1(79, 146, 202); break;
+          case FEC_5_6: cnref = REF_T1(89, 156, 216); break;
+          case FEC_7_8: cnref = REF_T1(97, 160, 225); break;
+          }
+        }
+     else { // DVB-T2
+        if (mod == QAM_AUTO) mod = QAM_64;
+        switch (fec) {  // 0.1 dB      Q4  Q16  Q64 Q256
+          case FEC_1_2: cnref = REF_T2(35,  87, 130, 170); break;
+          default:
+          case FEC_3_5: cnref = REF_T2(47, 101, 148, 194); break;
+          case FEC_2_3: cnref = REF_T2(56, 114, 162, 208); break;
+          case FEC_3_4: cnref = REF_T2(66, 125, 177, 229); break;
+          case FEC_4_5: cnref = REF_T2(72, 133, 187, 243); break;
+          case FEC_5_6: cnref = REF_T2(77, 138, 194, 251); break;
+          }
+        }
+     if (cnref) {
+        int cnrel = (Signal/100) - cnref; // 0.1 dB
+        int ber_sqi = 100; // 100%
+        int cnr_sqi = 0;   // 0%
+        if (dtp.System() == DVB_SYSTEM_1) { // DVB-T
+            ber_sqi = (Ber < 1000) ? 0 :           // > 10^-3
+                      (Ber >= 10000000) ? 100 :    // <= 10^-7
+                      (int)(20 * log10(Ber)) - 40; // 20*log10(1/BER)-40 -> 20% .. 100%
+            // scale: -7dB/+3dB to reference-value
+            cnr_sqi = (cnrel < -70) ? 0 :
+                      (cnrel < +30) ? (100 + (cnrel - 30)) :
+                      100;
+            sqi = (cnr_sqi * ber_sqi) / 100;
+            // alternative: stretched scale: cnref-7dB = 0%, 30dB = 100%
+            // sqi = dB1000toPercent(Signal, (100*cnref)-7000, 30000);
+            }
+        else { // DVB-T2
+            ber_sqi = (Ber < 10000)     ? 0 :             // > 10^-4
+                      (Ber >= 10000000) ? 100 * 100 / 6 : // <= 10^-7 : 16.67% -> SQI 0% .. 100%
+                      (100 * 100 / 15);                   //             6.67% -> SQI 0% .. 40% || 100%
+            // scale: -3dB/+3dB to reference-value
+            sqi = (cnrel <  -30) ? 0 :
+                  (cnrel <= +30) ? (cnrel + 30) * ber_sqi / 1000 : // (0 .. 6) * 16,67 || 6.67
+                  100;
+            // alternative: stretched scale: cnref-3dB = 0%, 32dB = 100%
+            // sqi = dB1000toPercent(Signal, (100*cnref)-3000, 32000);
+            }
+#ifdef DEBUG_SIGNALQUALITY
+        fprintf(stderr, "SQI-T: SIG:%d, BER:%d, CNref:%d, CNrel:%d, bersqi:%d, sqi:%d%%(sys:%d, mod:%d, fec:%d)\n", Signal, Ber, cnref, cnrel, ber_sqi, sqi, dtp.System(), mod, fec);
+#endif
+        }
+     }
+  else if (Channel->IsCable()) { // ! COMPLETELY UNTESTED !
+     if (mod == QAM_AUTO) mod = QAM_256;
+         // 0.1 dB      Q16  Q32  Q64 Q128 Q256
+     int cnref = REF_C1(200, 230, 260, 290, 320); // minimum for BER<10^-4
+     if (cnref) {
+        int cnrel = (Signal / 100) - cnref; // 0.1 dB
+        int ber_sqi = (Ber < 1000)      ? 0 :      // > 10^-3
+                      (Ber >= 10000000) ? 100 :    // <= 10^-7
+                      (int)(20 * log10(Ber)) - 40; // 20*log10(1/BER)-40 -> 20% .. 100%
+        // scale: -7dB/+3dB to reference-value
+        int cnr_sqi = (cnrel < -70) ? 0 :
+                      (cnrel < +30) ? (100 + (cnrel - 30)) :
+                      100;
+        sqi = (cnr_sqi * ber_sqi) / 100;
+        // alternative: stretched scale: cnref-7dB = 0%, 40dB = 100%
+        // sqi = dB1000toPercent(Signal, (100*cnref)-7000, 40000);
+#ifdef DEBUG_SIGNALQUALITY
+        dsyslog("SQI-C: SIG:%d, BER:%d, CNref:%d, CNrel:%d, bersqi:%d, sqi:%d%%(sys:%d, mod:%d, fec:%d)\n", Signal, Ber, cnref, cnrel, ber_sqi, sqi, dtp.System(), mod, fec);
+#endif
+        }
+     }
+  else if (Channel->IsSat()) {
+    int cnref = 0;
+    if (dtp.System() == DVB_SYSTEM_1) { // DVB-S
+        if (mod == QAM_AUTO) mod = QPSK;
+        switch (fec) {  // 0.1 dB:     Q4 : 10^-7
+          case FEC_1_2: cnref = REF_S1(38); break;
+          default:
+          case FEC_2_3: cnref = REF_S1(56); break;
+          case FEC_3_4: cnref = REF_S1(67); break;
+          case FEC_5_6: cnref = REF_S1(77); break;
+          case FEC_7_8: cnref = REF_S1(84); break;
+          }
+        if (cnref) {
+           //cnrel = (Signal/100) - cnref; // 0.1 dB
+           // scale: cnref-4dB = 0%, 15dB = 100%
+           sqi = dB1000toPercent(Signal, (100*cnref)-4000, 15000);
+#ifdef DEBUG_SIGNALQUALITY
+           dsyslog("SQI-S1: SIG:%d, BER:%d, CNref:%d, sqi:%d%%(mod:%d, fec:%d)\n", Signal, Ber, cnref, sqi, mod, fec);
+#endif
+           }
+        }
+    else { // DVB-S2
+        if (mod == QAM_AUTO) mod = QAM_64;
+        switch (fec) {   // 0.1 dB       Q4   Q8 16A* 32A*
+        //case FEC_1_4:  cnref = REF_S2(-14,  65,  90, 126); break;
+        //case FEC_1_3:  cnref = REF_S2( -2,  65,  90, 126); break;
+          case FEC_2_5:  cnref = REF_S2(  7,  65,  90, 126); break;
+          case FEC_1_2:  cnref = REF_S2( 20,  65,  90, 126); break;
+          case FEC_3_5:  cnref = REF_S2( 32,  65,  90, 126); break;
+          default:
+          case FEC_2_3:  cnref = REF_S2( 41,  76,  90, 126); break;
+          case FEC_3_4:  cnref = REF_S2( 50,  66, 102, 126); break;
+          case FEC_4_5:  cnref = REF_S2( 57,  89, 110, 136); break;
+          case FEC_5_6:  cnref = REF_S2( 62, 104, 116, 143); break;
+          case FEC_8_9:  cnref = REF_S2( 72, 117, 129, 157); break;
+          case FEC_9_10: cnref = REF_S2( 74, 120, 131, 161); break;
+          }
+        if (cnref) {
+           // cnrel = (Signal/100) - cnref; // 0.1 dB
+           // scale: cnref-4dB = 0%, 20dB = 100%
+           sqi = dB1000toPercent(Signal, (100*cnref)-4000, 20000);
+#ifdef DEBUG_SIGNALQUALITY
+           dsyslog("SQI-S2: SIG:%d, BER:%d, CNref:%d, sqi:%d%%(mod:%d, fec:%d)\n", Signal, Ber, cnref, sqi, mod, fec);
+#endif
+           }
+        }
+  }
+  return sqi;
+}
+
 int cDvbTuner::GetSignalStrength(void) const
 {
   ClearEventQueue();
@@ -710,14 +935,20 @@
       memset(&CmdSeq, 0, sizeof(CmdSeq));
       CmdSeq.props = Props;
       SETCMD(DTV_STAT_SIGNAL_STRENGTH, 0);
+      SETCMD(DTV_MODULATION, 0);
+      SETCMD(DTV_CODE_RATE_HP, 0); // DVB-T only
+      SETCMD(DTV_INNER_FEC, 0);
       if (ioctl(fd_frontend, FE_GET_PROPERTY, &CmdSeq) != 0) {
          esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
          return -1;
          }
-      int Signal = 0;;
+      int FeMod = (Props[1].u.st.len > 0) ? (int)Props[1].u.data : -1;
+      int FeCod = (Props[2].u.st.len > 0) ? (int)Props[2].u.data : -1;
+      int FeFec = (Props[3].u.st.len > 0) ? (int)Props[3].u.data : -1;
+      int Signal = 0;
       if (Props[0].u.st.len > 0) {
          switch (Props[0].u.st.stat[0].scale) {
-           case FE_SCALE_DECIBEL:  Signal = dB1000toPercent(Props[0].u.st.stat[0].svalue, -95000, -20000); // TODO use different values for DVB-S, -T, -C?
+           case FE_SCALE_DECIBEL:  Signal = StrengthToSSI(&channel, Props[0].u.st.stat[0].svalue, FeMod, FeCod, FeFec);
                                    break;
            case FE_SCALE_RELATIVE: Signal = 100 * Props[0].u.st.stat[0].uvalue / 0xFFFF;
                                    break;
@@ -768,14 +999,32 @@
       memset(&CmdSeq, 0, sizeof(CmdSeq));
       CmdSeq.props = Props;
       SETCMD(DTV_STAT_CNR, 0);
+      SETCMD(DTV_MODULATION, 0);
+      SETCMD(DTV_CODE_RATE_HP, 0); // DVB-T only
+      SETCMD(DTV_INNER_FEC, 0);
+      SETCMD(DTV_STAT_POST_ERROR_BIT_COUNT, 0);
+      SETCMD(DTV_STAT_POST_TOTAL_BIT_COUNT, 0);
       if (ioctl(fd_frontend, FE_GET_PROPERTY, &CmdSeq) != 0) {
          esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
          return -1;
          }
-      int Cnr = 0;;
+      int FeMod = (Props[1].u.st.len > 0) ? (int)Props[1].u.data : -1;
+      int FeCod = (Props[2].u.st.len > 0) ? (int)Props[2].u.data : -1;
+      int FeFec = (Props[3].u.st.len > 0) ? (int)Props[3].u.data : -1;
+      int Ber = BER_ERROR_FREE; // 1/10^-9
+      if (Props[4].u.st.len > 0 && Props[4].u.st.stat[0].scale == FE_SCALE_COUNTER && Props[5].u.st.len > 0 && Props[5].u.st.stat[0].scale == FE_SCALE_COUNTER) {
+         uint64_t ebc = Props[4].u.st.stat[0].uvalue; // error bit count
+         uint64_t tbc = Props[5].u.st.stat[0].uvalue; // total bit count
+         if (ebc > 0) {
+            uint64_t BerRev = tbc / ebc; // reversed, for integer arithmetic
+            if (BerRev < BER_ERROR_FREE)
+               Ber = (int)BerRev;
+            }
+         }
+      int Cnr = 0;
       if (Props[0].u.st.len > 0) {
          switch (Props[0].u.st.stat[0].scale) {
-           case FE_SCALE_DECIBEL:  Cnr = dB1000toPercent(Props[0].u.st.stat[0].svalue, 5000, 20000); // TODO use different values for DVB-S, -T, -C?
+           case FE_SCALE_DECIBEL:  Cnr = SignalToSQI(&channel, Props[0].u.st.stat[0].svalue, Ber, FeMod, FeCod, FeFec);
                                    break;
            case FE_SCALE_RELATIVE: Cnr = 100 * Props[0].u.st.stat[0].uvalue / 0xFFFF;
                                    break;
@@ -1116,13 +1365,13 @@
      SETCMD(DTV_HIERARCHY, dtp.Hierarchy());
      if (frontendType == SYS_DVBT2) {
         // DVB-T2
+        SETCMD(DTV_INNER_FEC, dtp.CoderateH());
         if (DvbApiVersion >= 0x0508) {
            SETCMD(DTV_STREAM_ID, dtp.StreamId());
            }
         else if (DvbApiVersion >= 0x0503)
            SETCMD(DTV_DVBT2_PLP_ID_LEGACY, dtp.StreamId());
         }
-
      tuneTimeout = DVBT_TUNE_TIMEOUT;
      lockTimeout = DVBT_LOCK_TIMEOUT;
      }
diff -ruN vdr-2.3.8/dvbplayer.c vdr-2.3.9/dvbplayer.c
--- vdr-2.3.8/dvbplayer.c	2016-12-22 12:34:31.000000000 +0100
+++ vdr-2.3.9/dvbplayer.c	2017-11-26 15:55:03.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 4.4 2016/12/22 11:34:31 kls Exp $
+ * $Id: dvbplayer.c 4.5 2017/11/26 14:55:03 kls Exp $
  */
 
 #include "dvbplayer.h"
@@ -1003,6 +1003,7 @@
 
 void cDvbPlayerControl::Stop(void)
 {
+  cControl::player = NULL;
   delete player;
   player = NULL;
 }
diff -ruN vdr-2.3.8/eit.c vdr-2.3.9/eit.c
--- vdr-2.3.8/eit.c	2017-05-03 10:58:41.000000000 +0200
+++ vdr-2.3.9/eit.c	2017-10-11 11:19:42.000000000 +0200
@@ -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 4.3 2017/05/03 08:58:41 kls Exp $
+ * $Id: eit.c 4.4 2017/10/11 09:19:42 kls Exp $
  */
 
 #include "eit.h"
@@ -94,10 +94,10 @@
       // Drop bogus events - but keep NVOD reference events, where all bits of the start time field are set to 1, resulting in a negative number.
       if (StartTime == 0 || StartTime > 0 && Duration == 0)
          continue;
+      Empty = false;
       // Ignore events that ended before the "EPG linger time":
       if (StartTime + Duration < LingerLimit)
          continue;
-      Empty = false;
       if (!SegmentStart)
          SegmentStart = StartTime;
       SegmentEnd = StartTime + Duration;
diff -ruN vdr-2.3.8/font.h vdr-2.3.9/font.h
--- vdr-2.3.8/font.h	2016-12-22 13:43:24.000000000 +0100
+++ vdr-2.3.9/font.h	2017-11-11 15:05:07.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: font.h 4.2 2016/12/22 12:43:24 kls Exp $
+ * $Id: font.h 4.3 2017/11/11 14:05:07 kls Exp $
  */
 
 #ifndef __FONT_H
@@ -54,6 +54,8 @@
           ///< Returns the width of the given string in pixel.
   virtual int Height(void) const = 0;
           ///< Returns the height of this font in pixel (all characters have the same height).
+          ///< If the font contains descenders, its total height may be higher than the character
+          ///< height specified during creation.
   int Height(const char *s) const { return Height(); }
           ///< Returns the height of this font in pixel (obsolete, just for backwards compatibility).
   virtual void DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const = 0;
@@ -75,10 +77,12 @@
           ///< function that called GetFont() has returned.
   static cFont *CreateFont(const char *Name, int CharHeight, int CharWidth = 0);
           ///< Creates a new font object with the given Name and makes its characters
-          ///< CharHeight pixels high. If CharWidth is given, it overwrites the font's
-          ///< default width. Name is of the form "Family:Style", for instance
-          ///< "Verdana:Bold Italic" or "Times New Roman". See GetAvailableFontNames()
-          ///< for how to get a list of all available font names.
+          ///< CharHeight pixels high. The actual height of the required drawing area
+          ///< might be higher than CharHeight if the font contains descenders.
+          ///< If CharWidth is given, it overwrites the font's default width.
+          ///< Name is of the form "Family:Style", for instance "Verdana:Bold Italic" or
+          ///< "Times New Roman". See GetAvailableFontNames() for how to get a list of
+          ///< all available font names.
           ///< If the requested font can't be created, a dummy font is returned.
           ///< The caller must delete the font when it is no longer needed.
   static bool GetAvailableFontNames(cStringList *FontNames, bool Monospaced = false);
diff -ruN vdr-2.3.8/menu.c vdr-2.3.9/menu.c
--- vdr-2.3.8/menu.c	2017-06-30 11:49:39.000000000 +0200
+++ vdr-2.3.9/menu.c	2018-03-18 13:01:09.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 4.41 2017/06/30 09:49:39 kls Exp $
+ * $Id: menu.c 4.69 2018/03/18 12:01:09 kls Exp $
  */
 
 #include "menu.h"
@@ -656,6 +656,7 @@
 private:
   cNestedItem *folder;
 public:
+  virtual void Set(void);
   cMenuFolderItem(cNestedItem *Folder);
   cNestedItem *Folder(void) { return folder; }
   };
@@ -664,8 +665,15 @@
 :cOsdItem(Folder->Text())
 {
   folder = Folder;
-  if (folder->SubItems())
+  Set();
+}
+
+void cMenuFolderItem::Set(void)
+{
+  if (folder->SubItems() && folder->SubItems()->Count())
      SetText(cString::sprintf("%s...", folder->Text()));
+  else
+     SetText(folder->Text());
 }
 
 // --- cMenuEditFolder -------------------------------------------------------
@@ -675,7 +683,6 @@
   cList<cNestedItem> *list;
   cNestedItem *folder;
   char name[PATH_MAX];
-  int subFolder;
   eOSState Confirm(void);
 public:
   cMenuEditFolder(const char *Dir, cList<cNestedItem> *List, cNestedItem *Folder = NULL);
@@ -689,13 +696,10 @@
   SetMenuCategory(mcFolder);
   list = List;
   folder = Folder;
-  if (folder) {
+  if (folder)
      strn0cpy(name, folder->Text(), sizeof(name));
-     subFolder = folder->SubItems() != NULL;
-     }
   else {
      *name = 0;
-     subFolder = 0;
      cRemote::Put(kRight, true); // go right into string editing mode
      }
   if (!isempty(Dir)) {
@@ -704,7 +708,6 @@
      Add(DirItem);
      }
   Add(new cMenuEditStrItem( tr("Name"), name, sizeof(name)));
-  Add(new cMenuEditBoolItem(tr("Sub folder"), &subFolder));
 }
 
 cString cMenuEditFolder::GetFolder(void)
@@ -728,12 +731,10 @@
         return osContinue;
         }
      }
-  if (folder) {
+  if (folder)
      folder->SetText(name);
-     folder->SetSubItems(subFolder);
-     }
   else
-     list->Add(folder = new cNestedItem(name, subFolder));
+     list->Add(folder = new cNestedItem(name));
   return osEnd;
 }
 
@@ -791,12 +792,8 @@
   if (HasSubMenu())
      return;
   int NewHelpKeys = 0;
-  if (firstFolder) {
-     if (cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current())) {
-        if (Folder->Folder()->SubItems())
-           NewHelpKeys = 1;
-        }
-     }
+  if (firstFolder)
+     NewHelpKeys = 1;
   if (NewHelpKeys != helpKeys) {
      helpKeys = NewHelpKeys;
      SetHelp(NewHelpKeys > 0 ? tr("Button$Open") : NULL, tr("Button$New"), firstFolder ? tr("Button$Delete") : NULL, firstFolder ? tr("Button$Edit") : NULL);
@@ -885,8 +882,10 @@
   if (firstFolder) {
      cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current());
      if (Folder) {
-        if (Open && Folder->Folder()->SubItems())
+        if (Open) {
+           Folder->Folder()->SetSubItems(true);
            return AddSubMenu(new cMenuFolder(Title(), Folder->Folder()->SubItems(), nestedItemList, !isempty(dir) ? *cString::sprintf("%s%c%s", *dir, FOLDERDELIMCHAR, Folder->Folder()->Text()) : Folder->Folder()->Text()));
+           }
         else
            return osEnd;
         }
@@ -1123,6 +1122,8 @@
                               return osContinue;
                            if (timer->Local() && timer->Recording() && data.Remote())
                               cRecordControls::Stop(timer);
+                           if (timer->Remote() && data.Remote())
+                              Timers->SetSyncStateKey(StateKeySVDRPRemoteTimersPoll);
                            *timer = data;
                            }
                         LOCK_SCHEDULES_READ;
@@ -1296,11 +1297,13 @@
 {
   if (HasSubMenu())
      return osContinue;
-  cTimers::GetTimersWrite(timersStateKey);
+  cStateKey StateKey;
+  cTimers *Timers = cTimers::GetTimersWrite(StateKey);
   cTimer *Timer = GetTimer();
   if (Timer) {
      Timer->OnOff();
      if (Timer->Remote()) {
+        Timers->SetSyncStateKey(StateKeySVDRPRemoteTimersPoll);
         cStringList Response;
         if (!ExecSVDRPCommand(Timer->Remote(), cString::sprintf("MODT %d %s", Timer->Id(), *Timer->ToText(true)), &Response) || SVDRPCode(Response[0]) != 250)
            RemoteTimerError(Timer);
@@ -1314,7 +1317,7 @@
      else
         isyslog("%sactivated timer %s", Timer->HasFlags(tfActive) ? "" : "de", *Timer->ToDescr());
      }
-  timersStateKey.Remove(Timer != NULL);
+  StateKey.Remove(Timer != NULL);
   return osContinue;
 }
 
@@ -1341,27 +1344,27 @@
   // Check if this timer is active:
   cTimer *Timer = GetTimer();
   if (Timer) {
-     if (Interface->Confirm(tr("Delete timer?"))) {
-        if (Timer->Recording()) {
-           if (Interface->Confirm(tr("Timer still recording - really delete?"))) {
-              if (!Timer->Remote()) {
-                 Timer->Skip();
-                 cRecordControls::Process(Timers, time(NULL));
-                 }
-              }
-           else
-              Timer = NULL;
-           }
+     bool TimerRecording = Timer->Recording();
+     timersStateKey.Remove(false); // must release lock while prompting!
+     if (Interface->Confirm(tr("Delete timer?")) && (!TimerRecording || Interface->Confirm(tr("Timer still recording - really delete?")))) {
+        Timers = cTimers::GetTimersWrite(timersStateKey);
+        Timer = GetTimer();
         if (Timer) {
-           if (!HandleRemoteModifications(NULL, Timer)) {
-              timersStateKey.Remove();
-              return osContinue;
+           if (!Timer->Remote()) {
+              Timer->Skip();
+              cRecordControls::Process(Timers, time(NULL));
+              }
+           if (HandleRemoteModifications(NULL, Timer)) {
+              if (Timer->Remote())
+                 Timers->SetSyncStateKey(StateKeySVDRPRemoteTimersPoll);
+              Timers->Del(Timer);
+              cOsdMenu::Del(Current());
+              Display();
               }
-           Timers->Del(Timer);
-           cOsdMenu::Del(Current());
-           Display();
            }
         }
+     else
+        return osContinue;
      }
   timersStateKey.Remove(Timer != NULL);
   return osContinue;
@@ -1399,7 +1402,11 @@
   if (const cTimer *Timer = cMenuEditTimer::AddedTimer()) {
      // a newly created timer was confirmed with Ok and the proper item needs to be added:
      LOCK_TIMERS_READ;
-     Add(new cMenuTimerItem(Timer), true);
+     cMenuTimerItem *CurrentItem = new cMenuTimerItem(Timer);
+     Add(CurrentItem, true);
+     Sort();
+     SetCurrent(CurrentItem);
+     SetHelpKeys();
      Display();
      }
   if (Key != kNone)
@@ -1680,6 +1687,8 @@
         // must add the timer before HandleRemoteModifications to get proper log messages with timer ids
         Timers->Del(Timer);
         }
+     else if (Timer->Remote())
+        Timers->SetSyncStateKey(StateKeySVDRPRemoteTimersPoll);
      if (HasSubMenu())
         CloseSubMenu();
      if (Update())
@@ -1779,7 +1788,7 @@
   };
 
 cMenuSchedule::cMenuSchedule(void)
-:cOsdMenu("")
+:cOsdMenu(tr("Schedule"))
 {
   SetMenuCategory(mcSchedule);
   scheduleState = -1;
@@ -1986,6 +1995,8 @@
         // must add the timer before HandleRemoteModifications to get proper log messages with timer ids
         Timers->Del(Timer);
         }
+     else if (Timer->Remote())
+        Timers->SetSyncStateKey(StateKeySVDRPRemoteTimersPoll);
      if (HasSubMenu())
         CloseSubMenu();
      if (Update())
@@ -2407,9 +2418,15 @@
 
 // --- cMenuPathEdit ---------------------------------------------------------
 
+#define osUserRecRenamed osUser1
+#define osUserRecMoved   osUser2
+#define osUserRecRemoved osUser3
+#define osUserRecEmpty   osUser4
+
 class cMenuPathEdit : public cOsdMenu {
 private:
   cString path;
+  cString oldFolder;
   char folder[PATH_MAX];
   char name[NAME_MAX];
   cMenuEditStrItem *folderItem;
@@ -2441,6 +2458,7 @@
     LOCK_RECORDINGS_READ;
     pathIsInUse = Recordings->PathIsInUse(path);
   }
+  oldFolder = folder;
   cOsdItem *p;
   Add(p = folderItem = new cMenuEditStrItem(tr("Folder"), folder, sizeof(folder)));
   p->SetSelectable(!pathIsInUse);
@@ -2479,18 +2497,28 @@
   cString NewPath = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name;
   NewPath.CompactChars(FOLDERDELIMCHAR);
   if (strcmp(NewPath, path)) {
-     LOCK_RECORDINGS_WRITE;
-     Recordings->SetExplicitModify();
-     int NumRecordings = Recordings->GetNumRecordingsInPath(path);
+     int NumRecordings = 0;
+     {
+       LOCK_RECORDINGS_READ;
+       NumRecordings = Recordings->GetNumRecordingsInPath(path);
+     }
      if (NumRecordings > 1 && !Interface->Confirm(cString::sprintf(tr("Move entire folder containing %d recordings?"), NumRecordings)))
         return osContinue;
-     if (!Recordings->MoveRecordings(path, NewPath)) {
+     bool Error = false;
+     {
+       LOCK_RECORDINGS_WRITE;
+       Recordings->SetExplicitModify();
+       Error = !Recordings->MoveRecordings(path, NewPath);
+       if (!Error)
+          Recordings->SetModified();
+     }
+     if (Error) {
         Skins.Message(mtError, tr("Error while moving folder!"));
         return osContinue;
         }
-     cMenuRecordings::SetPath(NewPath); // makes sure the Recordings menu will reposition to the new path
-     Recordings->SetModified();
-     return osUser1;
+     if (strcmp(folder, oldFolder))
+        return osUserRecMoved;
+     return osUserRecRenamed;
      }
   return osBack;
 }
@@ -2678,8 +2706,15 @@
 eOSState cMenuRecordingEdit::DeleteMarks(void)
 {
   if (buttonDeleteMarks && Interface->Confirm(tr("Delete editing marks for this recording?"))) {
-     if (cMarks::DeleteMarksFile(recording))
+     if (cMarks::DeleteMarksFile(recording)) {
         SetHelpKeys();
+        if (cControl *Control = cControl::Control(true)) {
+           if (const cRecording *Recording = Control->GetRecording()) {
+              if (strcmp(recording->FileName(), Recording->FileName()) == 0)
+                 Control->ClearEditingMarks();
+              }
+           }
+        }
      else
         Skins.Message(mtError, tr("Error while deleting editing marks!"));
      }
@@ -2709,6 +2744,7 @@
      *name = ' '; // name must not be empty!
      name[1] = 0;
      }
+  cString OldFolder = Recording->Folder();
   cString NewName = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name;
   NewName.CompactChars(FOLDERDELIMCHAR);
   if (strcmp(NewName, Recording->Name())) {
@@ -2720,10 +2756,12 @@
      Modified = true;
      }
   if (Modified) {
-     cMenuRecordings::SetRecording(Recording->FileName()); // makes sure the Recordings menu will reposition to the renamed recording
+     eOSState  state = osUserRecRenamed;
+     if (strcmp(Recording->Folder(), OldFolder))
+        state = osUserRecMoved;
      Recordings->TouchUpdate();
      StateKey.Remove(Modified);
-     return osUser1;
+     return state;
      }
   StateKey.Remove(Modified);
   return osBack;
@@ -2858,10 +2896,10 @@
   cMenuRecordingItem(const cRecording *Recording, int Level);
   ~cMenuRecordingItem();
   void IncrementCounter(bool New);
-  const char *Name(void) { return name; }
-  int Level(void) { return level; }
-  const cRecording *Recording(void) { return recording; }
-  bool IsDirectory(void) { return name != NULL; }
+  const char *Name(void) const { return name; }
+  int Level(void) const { return level; }
+  const cRecording *Recording(void) const { return recording; }
+  bool IsDirectory(void) const { return name != NULL; }
   void SetRecording(const cRecording *Recording) { recording = Recording; }
   virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable);
   };
@@ -2873,8 +2911,13 @@
   name = NULL;
   totalEntries = newEntries = 0;
   SetText(Recording->Title('\t', true, Level));
-  if (*Text() == '\t')
+  if (*Text() == '\t') // this is a folder
      name = strdup(Text() + 2); // 'Text() + 2' to skip the two '\t'
+  else { // this is an actual recording
+     int Usage = Recording->IsInUse();
+     if ((Usage & ruDst) != 0 && (Usage & (ruMove | ruCopy)) != 0)
+        SetSelectable(false);
+     }
 }
 
 cMenuRecordingItem::~cMenuRecordingItem()
@@ -2957,15 +3000,17 @@
 {
   if (cRecordings::GetRecordingsRead(recordingsStateKey)) {
      recordingsStateKey.Remove();
-     const char *CurrentRecording = *fileName ? *fileName : cReplayControl::LastReplayed();
      cRecordings *Recordings = cRecordings::GetRecordingsWrite(recordingsStateKey); // write access is necessary for sorting!
-     cMenuRecordingItem *LastItem = NULL;
+     const char *CurrentRecording = NULL;
      if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()))
         CurrentRecording = ri->Recording()->FileName();
+     if (!CurrentRecording)
+        CurrentRecording = *fileName ? *fileName : cReplayControl::LastReplayed();
      int current = Current();
      Clear();
      GetRecordingsSortMode(DirectoryName());
      Recordings->Sort();
+     cMenuRecordingItem *LastItem = NULL;
      for (const cRecording *Recording = Recordings->First(); Recording; Recording = Recordings->Next(Recording)) {
          if ((!filter || filter->Filter(Recording)) && (!base || (strstr(Recording->Name(), base) == Recording->Name() && Recording->Name()[strlen(base)] == FOLDERDELIMCHAR))) {
             cMenuRecordingItem *Item = new cMenuRecordingItem(Recording, level);
@@ -3073,28 +3118,67 @@
   return osContinue;
 }
 
-eOSState cMenuRecordings::Delete(void)
+static bool TimerStillRecording(const char *FileName)
 {
-  if (HasSubMenu() || Count() == 0)
-     return osContinue;
-  cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
-  if (ri && !ri->IsDirectory()) {
-     if (Interface->Confirm(tr("Delete recording?"))) {
-        if (cRecordControl *rc = cRecordControls::GetRecordControl(ri->Recording()->FileName())) {
+  if (cRecordControl *rc = cRecordControls::GetRecordControl(FileName)) {
+     // local timer
+     if (Interface->Confirm(tr("Timer still recording - really delete?"))) {
+        LOCK_TIMERS_WRITE;
+        if (cTimer *Timer = rc->Timer()) {
+           Timer->Skip();
+           cRecordControls::Process(Timers, time(NULL));
+           if (Timer->IsSingleEvent()) {
+              Timers->Del(Timer);
+              isyslog("deleted timer %s", *Timer->ToDescr());
+              }
+           }
+        }
+     else
+        return true; // user didn't confirm deletion
+     }
+  else {
+     // remote timer
+     cString TimerId = GetRecordingTimerId(FileName);
+     if (*TimerId) {
+        int Id;
+        char *RemoteBuf = NULL;
+        cString Remote;
+        if (2 == sscanf(TimerId, "%d@%m[^ \n]", &Id, &RemoteBuf)) {
+           Remote = RemoteBuf;
+           free(RemoteBuf);
            if (Interface->Confirm(tr("Timer still recording - really delete?"))) {
-              if (cTimer *Timer = rc->Timer()) {
-                 LOCK_TIMERS_WRITE;
+              LOCK_TIMERS_WRITE;
+              if (cTimer *Timer = Timers->GetById(Id, Remote)) {
+                 cTimer OldTimer = *Timer;
                  Timer->Skip();
-                 cRecordControls::Process(Timers, time(NULL));
+                 Timers->SetSyncStateKey(StateKeySVDRPRemoteTimersPoll);
                  if (Timer->IsSingleEvent()) {
-                    Timers->Del(Timer);
-                    isyslog("deleted timer %s", *Timer->ToDescr());
+                    if (HandleRemoteModifications(NULL, Timer))
+                       Timers->Del(Timer);
+                    else
+                       return true; // error while deleting remote timer
                     }
+                 else if (!HandleRemoteModifications(Timer, &OldTimer))
+                    return true; // error while modifying remote timer
                  }
               }
            else
-              return osContinue;
+              return true; // user didn't confirm deletion
            }
+        }
+     }
+  return false;
+}
+
+eOSState cMenuRecordings::Delete(void)
+{
+  if (HasSubMenu() || Count() == 0)
+     return osContinue;
+  cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
+  if (ri && !ri->IsDirectory()) {
+     if (Interface->Confirm(tr("Delete recording?"))) {
+        if (TimerStillRecording(ri->Recording()->FileName()))
+           return osContinue;
         cString FileName;
         {
           LOCK_RECORDINGS_READ;
@@ -3122,8 +3206,8 @@
            recordingsStateKey.Remove();
            Display();
            if (!Count())
-              return osBack;
-           return osUser2;
+              return osUserRecEmpty;
+           return osUserRecRemoved;
            }
         else
            Skins.Message(mtError, tr("Error while deleting recording!"));
@@ -3165,6 +3249,8 @@
 {
   if (HasSubMenu())
      return osContinue;
+  if (const cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()))
+     SetRecording(ri->Recording()->FileName()); // makes sure the Recordings menu will reposition to the current recording
   IncRecordingsSortMode(DirectoryName());
   recordingsStateKey.Reset();
   Set(true);
@@ -3173,7 +3259,6 @@
 
 eOSState cMenuRecordings::ProcessKey(eKeys Key)
 {
-  bool HadSubMenu = HasSubMenu();
   eOSState state = cOsdMenu::ProcessKey(Key);
 
   if (state == osUnknown) {
@@ -3191,37 +3276,46 @@
        default: break;
        }
      }
-  else if (state == osUser1) {
-     // a recording or path was renamed, so let's refresh the menu
-     CloseSubMenu(false);
-     if (base)
-        return state; // closes all recording menus except for the top one
-     Set(); // this is the top level menu, so we refresh it...
-     Open(true); // ...and open any necessary submenus to show the new name
-     if (!HasSubMenu()) {
-        LOCK_RECORDINGS_READ;
-        Display();
-        }
+  else if (state == osUserRecRenamed) {
+     // a recording was renamed (within the same folder), so let's refresh the menu
+     CloseSubMenu(false); // this is the cMenuRecordingEdit/cMenuPathEdit
      path = NULL;
      fileName = NULL;
+     state = osContinue;
      }
-  else if (state == osUser2) {
-     // a recording in a sub folder was deleted, so update the current item
-     cOsdMenu *m = HasSubMenu() ? SubMenu() : this;
-     if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current())) {
-        if (cMenuRecordingItem *riSub = (cMenuRecordingItem *)m->Get(m->Current()))
-           ri->SetRecording(riSub->Recording());
-        }
+  else if (state == osUserRecMoved) {
+     // a recording was moved to a different folder, so let's delete the old item
+     CloseSubMenu(false); // this is the cMenuRecordingEdit/cMenuPathEdit
+     path = NULL;
+     fileName = NULL;
+     cOsdMenu::Del(Current());
+     Set(); // the recording might have been moved into a new subfolder of this folder
+     if (!Count())
+        return osUserRecEmpty;
+     Display();
+     state = osUserRecRemoved;
+     }
+  else if (state == osUserRecRemoved) {
+     // a recording was removed from a sub folder, so update the current item
+     if (cOsdMenu *m = SubMenu()) {
+        if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current())) {
+           if (cMenuRecordingItem *riSub = (cMenuRecordingItem *)m->Get(m->Current()))
+              ri->SetRecording(riSub->Recording());
+           }
+        }
+     // no state change here, this report goes upstream!
+     }
+  else if (state == osUserRecEmpty) {
+     // a subfolder became empty, so let's go back up
+     CloseSubMenu(false); // this is the now empty submenu
+     cOsdMenu::Del(Current()); // the menu entry of the now empty subfolder
+     Set(); // in case a recording was moved into a new subfolder of this folder
+     if (base && !Count()) // base: don't go up beyond the top level Recordings menu
+        return state;
+     Display();
+     state = osContinue;
      }
   if (!HasSubMenu()) {
-     if (HadSubMenu) {
-        if (Key == kYellow) {
-           // the last recording in a subdirectory was deleted, so let's go back up
-           cOsdMenu::Del(Current());
-           if (!Count())
-              return osBack;
-           }
-        }
      Set(true);
      if (Key != kNone)
         SetHelpKeys();
@@ -3257,6 +3351,7 @@
 private:
   const char *useSmallFontTexts[3];
   const char *recSortModeTexts[2];
+  const char *recSortDirTexts[2];
   const char *keyColorTexts[4];
   int osdLanguageIndex;
   int numSkins;
@@ -3311,6 +3406,8 @@
   useSmallFontTexts[2] = tr("always");
   recSortModeTexts[0] = tr("by name");
   recSortModeTexts[1] = tr("by time");
+  recSortDirTexts[0] = tr("ascending");
+  recSortDirTexts[1] = tr("descending");
   keyColorTexts[0] = tr("Key$Red");
   keyColorTexts[1] = tr("Key$Green");
   keyColorTexts[2] = tr("Key$Yellow");
@@ -3345,6 +3442,7 @@
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Folders in timer menu"),  &data.FoldersInTimerMenu));
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Always sort folders first"), &data.AlwaysSortFoldersFirst));
   Add(new cMenuEditStraItem(tr("Setup.OSD$Default sort mode for recordings"), &data.DefaultSortModeRec, 2, recSortModeTexts));
+  Add(new cMenuEditStraItem(tr("Setup.OSD$Sorting direction for recordings"), &data.RecSortingDirection, 2, recSortDirTexts));
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Number keys for characters"), &data.NumberKeysForChars));
   Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 0"),            &data.ColorKey0, 4, keyColorTexts));
   Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 1"),            &data.ColorKey1, 4, keyColorTexts));
@@ -3386,7 +3484,7 @@
         ModifiedAppearance = true;
      if (strcmp(data.FontFix, Setup.FontFix) || !DoubleEqual(data.FontFixSizeP, Setup.FontFixSizeP))
         ModifiedAppearance = true;
-     if (data.AlwaysSortFoldersFirst != Setup.AlwaysSortFoldersFirst || data.RecordingDirs != Setup.RecordingDirs) {
+     if (data.AlwaysSortFoldersFirst != Setup.AlwaysSortFoldersFirst || data.RecordingDirs != Setup.RecordingDirs || data.RecSortingDirection != Setup.RecSortingDirection) {
         LOCK_RECORDINGS_WRITE;
         Recordings->ClearSortNames();
         }
@@ -3587,7 +3685,6 @@
 
 eOSState cMenuSetupDVB::ProcessKey(eKeys Key)
 {
-  int oldPrimaryDVB = ::Setup.PrimaryDVB;
   int oldVideoDisplayFormat = ::Setup.VideoDisplayFormat;
   bool oldVideoFormat = ::Setup.VideoFormat;
   bool newVideoFormat = data.VideoFormat;
@@ -3650,8 +3747,6 @@
        }
      }
   if (state == osBack && Key == kOk) {
-     if (::Setup.PrimaryDVB != oldPrimaryDVB)
-        state = osSwitchDvb;
      if (::Setup.VideoDisplayFormat != oldVideoDisplayFormat)
         cDevice::PrimaryDevice()->SetVideoDisplayFormat(eVideoDisplayFormat(::Setup.VideoDisplayFormat));
      if (::Setup.VideoFormat != oldVideoFormat)
@@ -3797,6 +3892,7 @@
 
 class cMenuSetupCAM : public cMenuSetupBase {
 private:
+  int currentChannel;
   const char *activationHelp;
   eOSState Menu(void);
   eOSState Reset(void);
@@ -3809,6 +3905,7 @@
 
 cMenuSetupCAM::cMenuSetupCAM(void)
 {
+  currentChannel = cDevice::CurrentChannel();
   activationHelp = NULL;
   SetMenuCategory(mcSetupCam);
   SetSection(tr("CAM"));
@@ -3934,6 +4031,8 @@
          }
      SetHelpKeys();
      }
+  if (currentChannel != cDevice::CurrentChannel())
+     state = osEnd;
   return state;
 }
 
@@ -4081,22 +4180,19 @@
   bool OldSVDRPPeering = data.SVDRPPeering;
   bool ModifiedSVDRPSettings = false;
   if (Key == kOk)
-     ModifiedSVDRPSettings = data.SVDRPPeering != Setup.SVDRPPeering | strcmp(data.SVDRPHostName, Setup.SVDRPHostName);
+     ModifiedSVDRPSettings = data.SVDRPPeering != Setup.SVDRPPeering || strcmp(data.SVDRPHostName, Setup.SVDRPHostName);
   eOSState state = cMenuSetupBase::ProcessKey(Key);
   if (data.SVDRPPeering != OldSVDRPPeering)
      Set();
   if (ModifiedSVDRPSettings) {
-     StopSVDRPClientHandler();
-     StopSVDRPServerHandler();
-     StartSVDRPServerHandler();
-     if (data.SVDRPPeering)
-        StartSVDRPClientHandler();
-     else {
-        LOCK_TIMERS_WRITE;
-        Timers->SetExplicitModify();
-        if (Timers->DelRemoteTimers())
-           Timers->SetModified();
-        }
+     StopSVDRPHandler();
+     {
+       LOCK_TIMERS_WRITE;
+       Timers->SetExplicitModify();
+       if (Timers->StoreRemoteTimers(NULL, NULL))
+          Timers->SetModified();
+     }
+     StartSVDRPHandler();
      }
   return state;
 }
@@ -4563,6 +4659,7 @@
   displayChannel->SetChannel(channel, number);
   cStatus::MsgOsdChannel(ChannelString(channel, number));
   lastPresent = lastFollowing = NULL;
+  lastTime.Set();
 }
 
 void cDisplayChannel::DisplayInfo(void)
@@ -4578,6 +4675,7 @@
            cStatus::MsgOsdProgramme(Present ? Present->StartTime() : 0, Present ? Present->Title() : NULL, Present ? Present->ShortText() : NULL, Following ? Following->StartTime() : 0, Following ? Following->Title() : NULL, Following ? Following->ShortText() : NULL);
            lastPresent = Present;
            lastFollowing = Following;
+           lastTime.Set();
            }
         }
      }
@@ -5133,15 +5231,16 @@
   cRecordingUserCommand::InvokeCommand(RUC_BEFORERECORDING, fileName);
   isyslog("record %s", fileName);
   if (MakeDirs(fileName, true)) {
+     Recording.WriteInfo(); // we write this *before* attaching the recorder to the device, to make sure the info file is present when the recorder needs to update the fps value!
      const cChannel *ch = timer->Channel();
      recorder = new cRecorder(fileName, ch, timer->Priority());
      if (device->AttachReceiver(recorder)) {
-        Recording.WriteInfo();
         cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true);
         if (!Timer && !LastReplayed) // an instant recording, maybe from cRecordControls::PauseLiveVideo()
            cReplayControl::SetRecording(fileName);
         SchedulesStateKey.Remove();
         LOCK_RECORDINGS_WRITE;
+        SetRecordingTimerId(fileName, cString::sprintf("%d@%s", timer->Id(), Setup.SVDRPHostName));
         Recordings->AddByName(fileName);
         return;
         }
@@ -5195,6 +5294,7 @@
      DELETENULL(recorder);
      timer->SetRecording(false);
      timer = NULL;
+     SetRecordingTimerId(fileName, NULL);
      cStatus::MsgRecording(device, NULL, fileName, false);
      if (ExecuteUserCommand)
         cRecordingUserCommand::InvokeCommand(RUC_AFTERRECORDING, fileName);
@@ -5208,7 +5308,6 @@
         timer->SetPending(false);
      return false;
      }
-  AssertFreeDiskSpace(timer->Priority());
   return true;
 }
 
@@ -5478,8 +5577,6 @@
 cReplayControl::~cReplayControl()
 {
   cDevice::PrimaryDevice()->SetKeepTracks(false);
-  Hide();
-  cStatus::MsgReplaying(this, NULL, fileName, false);
   Stop();
   if (currentReplayControl == this)
      currentReplayControl = NULL;
@@ -5487,6 +5584,8 @@
 
 void cReplayControl::Stop(void)
 {
+  Hide();
+  cStatus::MsgReplaying(this, NULL, fileName, false);
   if (Setup.DelTimeshiftRec && *fileName) {
      cRecordControl* rc = cRecordControls::GetRecordControl(fileName);
      if (rc && rc->InstantId()) {
@@ -5504,17 +5603,22 @@
                    }
               }
               cDvbPlayerControl::Stop();
-              LOCK_RECORDINGS_WRITE;
-              Recordings->SetExplicitModify();
-              if (cRecording *Recording = Recordings->GetByName(fileName)) {
-                 if (Recording->Delete()) {
-                    Recordings->DelByName(fileName);
-                    ClearLastReplayed(fileName);
-                    Recordings->SetModified();
-                    }
-                 else
-                    Skins.Message(mtError, tr("Error while deleting recording!"));
-                 }
+              bool Error = false;
+              {
+                LOCK_RECORDINGS_WRITE;
+                Recordings->SetExplicitModify();
+                if (cRecording *Recording = Recordings->GetByName(fileName)) {
+                   if (Recording->Delete()) {
+                      Recordings->DelByName(fileName);
+                      ClearLastReplayed(fileName);
+                      Recordings->SetModified();
+                      }
+                   else
+                      Error = true;
+                   }
+              }
+              if (Error)
+                 Skins.Message(mtError, tr("Error while deleting recording!"));
               return;
               }
            }
@@ -5524,6 +5628,16 @@
   cMenuRecordings::SetRecording(NULL); // make sure opening the Recordings menu navigates to the last replayed recording
 }
 
+void cReplayControl::ClearEditingMarks(void)
+{
+  cStateKey StateKey;
+  marks.Lock(StateKey);
+  while (cMark *m = marks.First())
+        marks.Del(m);
+  StateKey.Remove();
+  cStatus::MsgMarksModified(NULL);
+}
+
 void cReplayControl::SetRecording(const char *FileName)
 {
   fileName = FileName;
@@ -5764,6 +5878,7 @@
      StateKey.Remove();
      ShowTimed(2);
      marksModified = true;
+     cStatus::MsgMarksModified(&marks);
      }
 }
 
@@ -5823,6 +5938,7 @@
         m->SetPosition(p);
         Goto(m->Position(), true);
         marksModified = true;
+        cStatus::MsgMarksModified(&marks);
         }
      else if (!MarkRequired)
         Goto(SkipFrames(Frames), !Play);
@@ -5942,8 +6058,7 @@
                    SkipSeconds(Setup.SkipSecondsRepeat); break;
     case kYellow:  SkipSeconds(Setup.SkipSeconds); break;
     case kStop:
-    case kBlue:    Hide();
-                   Stop();
+    case kBlue:    Stop();
                    return osEnd;
     default: {
       DoShowMode = false;
@@ -5987,8 +6102,7 @@
                            else
                               Show();
                            break;
-            case kBack:    Hide();
-                           Stop();
+            case kBack:    Stop();
                            return osRecordings;
             default:       return osUnknown;
             }
diff -ruN vdr-2.3.8/menu.h vdr-2.3.9/menu.h
--- vdr-2.3.8/menu.h	2016-12-22 11:55:36.000000000 +0100
+++ vdr-2.3.9/menu.h	2018-02-01 16:35:48.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 4.5 2016/12/22 10:55:36 kls Exp $
+ * $Id: menu.h 4.6 2018/02/01 15:35:48 kls Exp $
  */
 
 #ifndef __MENU_H
@@ -326,6 +326,7 @@
   virtual void Show(void);
   virtual void Hide(void);
   bool Visible(void) { return visible; }
+  virtual void ClearEditingMarks(void);
   static void SetRecording(const char *FileName);
   static const char *NowReplaying(void);
   static const char *LastReplayed(void);
diff -ruN vdr-2.3.8/mtd.c vdr-2.3.9/mtd.c
--- vdr-2.3.8/mtd.c	2017-05-01 11:19:52.000000000 +0200
+++ vdr-2.3.9/mtd.c	2017-10-31 13:16:39.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: mtd.c 1.11 2017/05/01 09:19:52 kls Exp $
+ * $Id: mtd.c 1.12 2017/10/31 12:16:39 kls Exp $
  */
 
 #include "mtd.h"
@@ -148,6 +148,7 @@
 private:
   int number;
   int masterCamSlotNumber;
+  int nextUniqPid;
   uint16_t uniqPids[MAX_REAL_PIDS]; // maps a real PID to a unique PID
   uint16_t realPids[MAX_UNIQ_PIDS]; // maps a unique PID to a real PID
   cVector<uint16_t> uniqSids;
@@ -165,6 +166,7 @@
 {
   number = Number;
   masterCamSlotNumber = MasterCamSlotNumber;
+  nextUniqPid = 0;
   Clear();
 }
 
@@ -179,11 +181,15 @@
   DBGMTD("CAM %d/%d: mapped PID %d (%04X) to %d (%04X)", masterCamSlotNumber, number, RealPid, RealPid, uniqPids[RealPid], uniqPids[RealPid]);
   return uniqPids[RealPid];
 #else
-  for (int i = 0; i < MAX_UNIQ_PIDS; i++) {
+  for (int p = 0; p < MAX_UNIQ_PIDS; p++) {
+      int i = nextUniqPid + p;
+      if (i >= MAX_UNIQ_PIDS)
+         i -= MAX_UNIQ_PIDS;
       if (realPids[i] == MTD_INVALID_PID) { // 0x0000 is a valid PID (PAT)!
          realPids[i] = RealPid;
          uniqPids[RealPid] = (number << UNIQ_PID_SHIFT) | i;
          DBGMTD("CAM %d/%d: mapped PID %d (%04X) to %d (%04X)", masterCamSlotNumber, number, RealPid, RealPid, uniqPids[RealPid], uniqPids[RealPid]);
+         nextUniqPid = i + 1;
          return uniqPids[RealPid];
          }
       }
@@ -212,6 +218,7 @@
   DBGMTD("CAM %d/%d: MTD mapper cleared", masterCamSlotNumber, number);
   memset(uniqPids, 0, sizeof(uniqPids));
   memset(realPids, MTD_INVALID_PID, sizeof(realPids));
+  // do not reset nextUniqPid here!
   uniqSids.Clear();
 }
 
diff -ruN vdr-2.3.8/nit.c vdr-2.3.9/nit.c
--- vdr-2.3.8/nit.c	2016-12-23 15:16:59.000000000 +0100
+++ vdr-2.3.9/nit.c	2018-03-18 11:52:21.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: nit.c 4.4 2016/12/23 14:16:59 kls Exp $
+ * $Id: nit.c 4.5 2018/03/18 10:52:21 kls Exp $
  */
 
 #include "nit.h"
@@ -120,6 +120,10 @@
                     bool forceTransponderUpdate = false;
                     for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
                         if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
+                           // Preserve parameters set separately in S2SatelliteDeliverySystemDescriptor:
+                           cDvbTransponderParameters dtpc(Channel->Parameters());
+                           dtp.SetStreamId(dtpc.StreamId());
+                           //
                            int transponder = Channel->Transponder();
                            found = true;
                            if (!ISTRANSPONDER(cChannel::Transponder(Frequency, dtp.Polarization()), transponder)) {
@@ -157,8 +161,8 @@
                         if (!Channel->GroupSep() && cSource::IsSat(Channel->Source()) && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
                            SI::S2SatelliteDeliverySystemDescriptor *sd = (SI::S2SatelliteDeliverySystemDescriptor *)d;
                            cDvbTransponderParameters dtp(Channel->Parameters());
-                           dtp.SetSystem(DVB_SYSTEM_2);
                            dtp.SetStreamId(sd->getInputStreamIdentifier());
+                           dbgnit("    stream id = %d\n", dtp.StreamId());
                            ChannelsModified |= Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), Channel->Srate(), dtp.ToString('S'));
                            break;
                            }
@@ -239,6 +243,16 @@
                     bool forceTransponderUpdate = false;
                     for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
                         if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
+                           // Preserve parameters set separately in T2DeliverySystemDescripto:
+                           cDvbTransponderParameters dtpc(Channel->Parameters());
+                           dtp.SetSystem(dtpc.System());
+                           dtp.SetStreamId(dtpc.StreamId());
+                           dtp.SetT2SystemId(dtp.T2SystemId());
+                           dtp.SetSisoMiso(dtpc.SisoMiso());
+                           dtp.SetBandwidth(dtpc.Bandwidth());
+                           dtp.SetGuard(dtpc.Guard());
+                           dtp.SetTransmission(dtpc.Transmission());
+                           //
                            int transponder = Channel->Transponder();
                            found = true;
                            if (!ISTRANSPONDER(Frequency / 1000000, transponder)) {
@@ -295,6 +309,7 @@
                                      dtp.SetTransmission(T2TransmissionModes[td->getTransmissionMode()]);
                                      //TODO add parsing of frequencies
                                      }
+                                  dbgnit("    stream id = %d\n", dtp.StreamId());
                                   ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('T'));
                                   }
                                }
diff -ruN vdr-2.3.8/osd.c vdr-2.3.9/osd.c
--- vdr-2.3.8/osd.c	2015-09-10 16:12:06.000000000 +0200
+++ vdr-2.3.9/osd.c	2018-01-25 16:09:09.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 4.3 2015/09/10 14:12:06 kls Exp $
+ * $Id: osd.c 4.4 2018/01/25 15:09:09 kls Exp $
  */
 
 #include "osd.h"
@@ -1183,7 +1183,7 @@
            t0.Shift(-Pixmap->DrawPort().Width(), 0);
      while (t0.Y() > Pixmap->ViewPort().Top())
            t0.Shift(0, -Pixmap->DrawPort().Height());
-     cPoint t = t0;;
+     cPoint t = t0;
      while (t.Y() <= Pixmap->ViewPort().Bottom()) {
            while (t.X() <= Pixmap->ViewPort().Right()) {
                  cRect Source = Pixmap->DrawPort(); // assume the entire pixmap needs to be rendered
diff -ruN vdr-2.3.8/osd.h vdr-2.3.9/osd.h
--- vdr-2.3.8/osd.h	2015-04-19 14:18:25.000000000 +0200
+++ vdr-2.3.9/osd.h	2017-11-02 15:59:19.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 4.4 2015/04/19 12:18:25 kls Exp $
+ * $Id: osd.h 4.5 2017/11/02 14:59:19 kls Exp $
  */
 
 #ifndef __OSD_H
@@ -785,8 +785,10 @@
        ///< If there are no dirty pixmaps, or if this is not a true color OSD,
        ///< this function returns NULL.
        ///< The caller must call DestroyPixmap() for the returned pixmap after use.
-//#define DEPRECATED_GETBITMAP
-#ifdef DEPRECATED_GETBITMAP
+#ifndef DEPRECATED_GETBITMAP
+#define DEPRECATED_GETBITMAP 0
+#endif
+#if DEPRECATED_GETBITMAP
 public:
 #endif
   cBitmap *GetBitmap(int Area);
diff -ruN vdr-2.3.8/osdbase.c vdr-2.3.9/osdbase.c
--- vdr-2.3.8/osdbase.c	2017-04-03 14:30:52.000000000 +0200
+++ vdr-2.3.9/osdbase.c	2018-03-06 11:38:18.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 4.2 2017/04/03 12:30:52 kls Exp $
+ * $Id: osdbase.c 4.4 2018/03/06 10:38:18 kls Exp $
  */
 
 #include "osdbase.h"
@@ -92,6 +92,7 @@
   SetTitle(Title);
   SetCols(c0, c1, c2, c3, c4);
   first = 0;
+  lastOffset = -1;
   current = marked = -1;
   subMenu = NULL;
   helpRed = helpGreen = helpYellow = helpBlue = NULL;
@@ -253,7 +254,7 @@
          }
      if (current < 0)
         current = 0; // just for safety - there HAS to be a current item!
-     first = min(first, max(0, count - displayMenuItems)); // in case the menu size has changed
+     first = max(0, min(first, max(0, count - displayMenuItems))); // in case the menu size has changed
      if (current - first >= displayMenuItems || current < first) {
         first = current - displayMenuItems / 2;
         if (first + displayMenuItems > count)
@@ -281,6 +282,9 @@
 void cOsdMenu::SetCurrent(cOsdItem *Item)
 {
   current = Item ? Item->Index() : -1;
+  if (current >= 0 && lastOffset >= 0)
+     first = max(0, current - lastOffset);
+  lastOffset = -1;
 }
 
 void cOsdMenu::RefreshCurrent(void)
@@ -326,6 +330,7 @@
 {
   if (marked >= 0)
      SetStatus(NULL);
+  lastOffset = (current > first) ? current - first : 0;
   first = 0;
   current = marked = -1;
   cList<cOsdItem>::Clear();
diff -ruN vdr-2.3.8/osdbase.h vdr-2.3.9/osdbase.h
--- vdr-2.3.8/osdbase.h	2017-04-03 14:02:16.000000000 +0200
+++ vdr-2.3.9/osdbase.h	2018-01-25 16:09:23.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 4.2 2017/04/03 12:02:16 kls Exp $
+ * $Id: osdbase.h 4.5 2018/01/25 15:09:23 kls Exp $
  */
 
 #ifndef __OSDBASE_H
@@ -30,7 +30,6 @@
                 osStopRecord,
                 osStopReplay,
                 osCancelEdit,
-                osSwitchDvb,
                 osBack,
                 osEnd,
                 os_User, // the following values can be used locally
@@ -92,6 +91,7 @@
   char *title;
   int cols[cSkinDisplayMenu::MaxTabs];
   int first, current, marked;
+  int lastOffset;
   eMenuCategory menuCategory;
   eMenuSortMode menuSortMode;
   eMenuOrientation menuOrientation;
diff -ruN vdr-2.3.8/peerdemo vdr-2.3.9/peerdemo
--- vdr-2.3.8/peerdemo	1970-01-01 01:00:00.000000000 +0100
+++ vdr-2.3.9/peerdemo	2018-03-09 11:49:48.000000000 +0100
@@ -0,0 +1,136 @@
+#!/usr/bin/perl
+
+# VDR SVDRP Peer Demo
+#
+# This script broadcasts an SVDRP discover datagram on the SVDRP UDP port and
+# then listens for replies  from peer VDRs on both the UDP and TCP port.
+# It reacts properly to the SVDRP commands CONN, LSTT, POLL, PING and QUIT,
+# and thus seems like a regular VDR to other VDRs.
+#
+# (C) 2018 by Klaus Schmidinger <Klaus.Schmidinger@tvdr.de>
+
+use Getopt::Std;
+use IO::Socket;
+use IO::Select;
+
+$DefaultSvdrpPort = 6419;
+$DefaultSvdrpName = "peerdemo";
+
+$Usage = qq{
+Usage: $0 options
+
+Options: -n name  use the given VDR name (default: $DefaultSvdrpName)
+         -p port  use the given TCP port (default: $DefaultSvdrpPort)
+         -v       be verbose
+};
+
+die $Usage if (!getopts("n:p:v"));
+
+$Name    = $opt_n || $DefaultSvdrpName;
+$Port    = $opt_p || $DefaultSvdrpPort;
+$Verbose = $opt_v || 0;
+
+# Open TCP and UDP sockets:
+
+$TcpPort = $Port;
+$UdpPort = $DefaultSvdrpPort;
+
+$TcpSocket = new IO::Socket::INET(Listen => 5, LocalPort => $TcpPort, Proto => "tcp", ReusePort => 1) || die "$!";
+$UdpSocket = new IO::Socket::INET(             LocalPort => $UdpPort, Proto => "udp", ReusePort => 1) || die "$!";
+$SvdrpSelect = new IO::Select($TcpSocket);
+setsockopt($UdpSocket, SOL_SOCKET, SO_RCVTIMEO, pack('L!L!', 0, 1000)); # 1ms timeout on UDP socket
+
+# Send UDP broadcast:
+
+$BcastSocket = new IO::Socket::INET(PeerAddr => '255.255.255.255', PeerPort => $UdpPort, Proto => "udp", Broadcast => 1) || die "$!";
+$BcastMsg = "SVDRP:discover name:$Name port:$TcpPort vdrversion:20309 apiversion:20309 timeout:300";
+Log('>', $BcastSocket, $BcastMsg);
+print($BcastSocket $BcastMsg);
+$BcastSocket->close();
+
+# Listen on UDP and TCP socket:
+
+while (1) {
+      if ($UdpSocket->recv($Request, 1024)) {
+         if (Extract($Request, "name") ne $Name) {
+            Log('<', $UdpSocket, $Request);
+            ReportVDR($Request, $UdpSocket->peerhost());
+            }
+         }
+      if (my @Ready = $SvdrpSelect->can_read(0.01)) {
+         for my $fh (@Ready) {
+             if ($fh == $TcpSocket) {
+                # accept connection:
+                my $new = $TcpSocket->accept();
+                Log('<', $new, "incoming TCP connection");
+                # send mandatory response to simulate an SVDRP host:
+                my $Prompt = "220 $Name SVDRP VideoDiskRecorder 2.3.9; Wed Nov 29 17:00:29 2017; ISO-8859-1";
+                Log('>', $new, $Prompt);
+                print($new "$Prompt\n");
+                # add incoming connection to select:
+                $SvdrpSelect->add($new);
+                }
+             else {
+                # process connection:
+                my $Request = "";
+                $fh->recv($Request, 1024);
+                chomp($Request);
+                Log('<', $fh, $Request) if ($Request);
+                if ($Request =~ /^CONN/i) {
+                   Reply($fh, "250 OK");
+                   ReportVDR($Request, $fh->peerhost());
+                   }
+                elsif ($Request =~ /^LSTT/i) {
+                   Reply($fh, "550 No timers defined");
+                   }
+                elsif ($Request =~ /^POLL/i) {
+                   Reply($fh, "250 OK");
+                   }
+                elsif ($Request =~ /^PING/i) {
+                   Reply($fh, "250 $Name is alive");
+                   }
+                elsif ($Request =~ /^QUIT/i || !$Request) {
+                   # close connection:
+                   Log('<', $fh, "connection closed");
+                   $SvdrpSelect->remove($fh);
+                   $fh->close;
+                   }
+                }
+             }
+         }
+      }
+
+# Tools:
+
+sub Reply
+{
+  my ($fh, $s) = @_;
+  Log('>', $fh, $s);
+  print($fh "$s\n");
+}
+
+sub ReportVDR
+{
+  my $s = shift;
+  my $PeerHost = shift;
+  $s .= " "; # for easier parsing
+  my $Name       = Extract($s, "name");
+  my $Port       = Extract($s, "port");
+  my $VdrVersion = Extract($s, "vdrversion");
+  my $ApiVersion = Extract($s, "apiversion");
+  my $Timeout    = Extract($s, "timeout");
+  print("found VDR '$Name' at $PeerHost with SVDRP port '$Port'\n");
+}
+
+sub Extract
+{
+  my ($s, $n) = @_;
+  return ($s =~ / $n:([^ ]*) /)[0];
+}
+
+sub Log
+{
+  return unless ($Verbose);
+  my ($Dir, $Socket, $Msg) = @_;
+  printf("SVDRP %s [%s:%s] %s\n", $Dir, $Socket->peerhost(), $Socket->peerport(), $Msg);
+}
diff -ruN vdr-2.3.8/player.h vdr-2.3.9/player.h
--- vdr-2.3.8/player.h	2016-12-22 11:38:11.000000000 +0100
+++ vdr-2.3.9/player.h	2018-02-01 16:34:51.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 4.2 2016/12/22 10:38:11 kls Exp $
+ * $Id: player.h 4.4 2018/02/01 15:34:51 kls Exp $
  */
 
 #ifndef __PLAYER_H
@@ -102,10 +102,15 @@
          ///< skins as a last resort, in case they want to display the state of the
          ///< current player. The return value is expected to be a short, single line
          ///< string. The default implementation returns an empty string.
-  double FramesPerSecond(void) const { return player->FramesPerSecond(); }
-  bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) const { return player->GetIndex(Current, Total, SnapToIFrame); }
-  bool GetFrameNumber(int &Current, int &Total) const { return player->GetFrameNumber(Current, Total); }
-  bool GetReplayMode(bool &Play, bool &Forward, int &Speed) const { return player->GetReplayMode(Play, Forward, Speed); }
+  virtual void ClearEditingMarks(void) {}
+         ///< Clears any editing marks this player might be showing.
+         ///< Deletion of the marks themselves is handled separately, calling
+         ///< this function merely tells the player to no longer display the
+         ///< marks, if it has any.
+  double FramesPerSecond(void) const { return player ? player->FramesPerSecond() : DEFAULTFRAMESPERSECOND; }
+  bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) const { return player ? player->GetIndex(Current, Total, SnapToIFrame) : false; }
+  bool GetFrameNumber(int &Current, int &Total) const { return player ? player->GetFrameNumber(Current, Total) : false; }
+  bool GetReplayMode(bool &Play, bool &Forward, int &Speed) const { return player ? player->GetReplayMode(Play, Forward, Speed) : false; }
   static void Launch(cControl *Control);
   static void Attach(void);
   static void Shutdown(void);
diff -ruN vdr-2.3.8/po/ar.po vdr-2.3.9/po/ar.po
--- vdr-2.3.8/po/ar.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/ar.po	2018-03-14 23:46:32.653355389 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2008-10-16 11:16-0400\n"
 "Last-Translator: Osama Alrawab <alrawab@hotmail.com>\n"
 "Language-Team: Arabic <ar@li.org>\n"
@@ -615,9 +615,6 @@
 msgid "New folder"
 msgstr "مجلد جديد"
 
-msgid "Sub folder"
-msgstr "مجلد فرعى"
-
 msgid "Folder name already exists!"
 msgstr "المجلد موجود بالفعل"
 
@@ -733,6 +730,9 @@
 msgid "Can't switch channel!"
 msgstr "لا يمكن تبديل القناة"
 
+msgid "Schedule"
+msgstr "جدولة"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "%sجدولة"
@@ -866,6 +866,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "عرص على الشاشة"
 
@@ -956,6 +962,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Number keys for characters"
 
@@ -1390,9 +1399,6 @@
 msgid " Stop recording "
 msgstr " ايقاف التسجيل "
 
-msgid "Schedule"
-msgstr "جدولة"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " ايقاف الاعادة"
@@ -1600,15 +1606,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "اضغط اى زر لالغاء اقفال البرنامج"
 
-msgid "Switching primary DVB..."
-msgstr "تم الانتقال لكرت الستالايت الافتراضى"
-
 msgid "Editing process failed!"
 msgstr "تم فشل عملية التعديل"
 
 msgid "Editing process finished"
 msgstr "تم الانتهاء من التعديل"
 
+msgid "Switching primary DVB..."
+msgstr "تم الانتقال لكرت الستالايت الافتراضى"
+
 msgid "Press any key to cancel restart"
 msgstr "اضغط اى زر لالغاء اعادة التشغيل"
 
diff -ruN vdr-2.3.8/po/ca_ES.po vdr-2.3.9/po/ca_ES.po
--- vdr-2.3.8/po/ca_ES.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/ca_ES.po	2018-03-14 23:46:32.777353452 +0100
@@ -10,7 +10,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2008-03-02 19:02+0100\n"
 "Last-Translator: Luca Olivetti <luca@ventoso.org>\n"
 "Language-Team: Catalan <vdr@linuxtv.org>\n"
@@ -614,9 +614,6 @@
 msgid "New folder"
 msgstr "Nova carpeta"
 
-msgid "Sub folder"
-msgstr "Sub carpeta"
-
 msgid "Folder name already exists!"
 msgstr "La carpeta ja existeix!"
 
@@ -732,6 +729,9 @@
 msgid "Can't switch channel!"
 msgstr "No puc canviar de canal!"
 
+msgid "Schedule"
+msgstr "Guia de Programaci�"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Guia de Programaci� - %s"
@@ -865,6 +865,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "Informaci� en pantalla"
 
@@ -955,6 +961,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Tecles num�riques per a car�cters"
 
@@ -1389,9 +1398,6 @@
 msgid " Stop recording "
 msgstr " Aturar la gravaci� "
 
-msgid "Schedule"
-msgstr "Guia de Programaci�"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Aturar la reproducci�"
@@ -1599,15 +1605,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Prem qualsevol tecla per cancel�lar l'aturada"
 
-msgid "Switching primary DVB..."
-msgstr "Canviant a la interf�cie DVB prim�ria..."
-
 msgid "Editing process failed!"
 msgstr "Proc�s d'edici� fallit!"
 
 msgid "Editing process finished"
 msgstr "Proc�s d'edici� finalitzat"
 
+msgid "Switching primary DVB..."
+msgstr "Canviant a la interf�cie DVB prim�ria..."
+
 msgid "Press any key to cancel restart"
 msgstr "Prem qualsevol tecla per cancel�lar el reinici"
 
diff -ruN vdr-2.3.8/po/cs_CZ.po vdr-2.3.9/po/cs_CZ.po
--- vdr-2.3.8/po/cs_CZ.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/cs_CZ.po	2018-03-14 23:46:32.093364135 +0100
@@ -10,7 +10,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2010-05-06 11:00+0200\n"
 "Last-Translator: Aleš Juřík <ajurik@quick.cz>\n"
 "Language-Team: Czech <vdr@linuxtv.org>\n"
@@ -614,9 +614,6 @@
 msgid "New folder"
 msgstr "Nová složka"
 
-msgid "Sub folder"
-msgstr "Podsložka"
-
 msgid "Folder name already exists!"
 msgstr "Složka již existuje!"
 
@@ -732,6 +729,9 @@
 msgid "Can't switch channel!"
 msgstr "Kanál nelze přepnout!"
 
+msgid "Schedule"
+msgstr "Program (EPG)"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Program - %s"
@@ -865,6 +865,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -955,6 +961,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Psát písmena pomocí číselných kláves"
 
@@ -1389,9 +1398,6 @@
 msgid " Stop recording "
 msgstr " Zastavit nahrávání "
 
-msgid "Schedule"
-msgstr "Program (EPG)"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Zastavit přehrávání"
@@ -1599,15 +1605,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Jakákoliv klávesa zruší vypnutí"
 
-msgid "Switching primary DVB..."
-msgstr "Přepnout primární DVB..."
-
 msgid "Editing process failed!"
 msgstr "Editační proces selhal!"
 
 msgid "Editing process finished"
 msgstr "Editační proces skončil"
 
+msgid "Switching primary DVB..."
+msgstr "Přepnout primární DVB..."
+
 msgid "Press any key to cancel restart"
 msgstr "Jakákoliv klávesa zruší restart"
 
diff -ruN vdr-2.3.8/po/da_DK.po vdr-2.3.9/po/da_DK.po
--- vdr-2.3.8/po/da_DK.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/da_DK.po	2018-03-14 23:46:32.197362511 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Mogens Elneff <mogens@elneff.dk>\n"
 "Language-Team: Danish <vdr@linuxtv.org>\n"
@@ -611,9 +611,6 @@
 msgid "New folder"
 msgstr ""
 
-msgid "Sub folder"
-msgstr ""
-
 msgid "Folder name already exists!"
 msgstr ""
 
@@ -729,6 +726,9 @@
 msgid "Can't switch channel!"
 msgstr "Kan ikke skifte kanal!"
 
+msgid "Schedule"
+msgstr "Programoversigt"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Program - %s"
@@ -862,6 +862,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -952,6 +958,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr ""
 
@@ -1386,9 +1395,6 @@
 msgid " Stop recording "
 msgstr " Stop optagelse "
 
-msgid "Schedule"
-msgstr "Programoversigt"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Stop afspilning"
@@ -1596,15 +1602,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Tryk vilk�rlig tast for at annullere sluk"
 
-msgid "Switching primary DVB..."
-msgstr "Skifter prim�r DVB enhed..."
-
 msgid "Editing process failed!"
 msgstr "Redigeringsproces fejlede!"
 
 msgid "Editing process finished"
 msgstr "Redigeringsproces afsluttet"
 
+msgid "Switching primary DVB..."
+msgstr "Skifter prim�r DVB enhed..."
+
 msgid "Press any key to cancel restart"
 msgstr "Tryk vilk�rlig knap for at annullere genstart"
 
diff -ruN vdr-2.3.8/po/de_DE.po vdr-2.3.9/po/de_DE.po
--- vdr-2.3.8/po/de_DE.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/de_DE.po	2018-03-14 23:46:32.813352890 +0100
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2015-02-10 13:45+0100\n"
 "Last-Translator: Klaus Schmidinger <vdr@tvdr.de>\n"
 "Language-Team: German <vdr@linuxtv.org>\n"
@@ -612,9 +612,6 @@
 msgid "New folder"
 msgstr "Neues Verzeichnis"
 
-msgid "Sub folder"
-msgstr "Unterverzeichnis"
-
 msgid "Folder name already exists!"
 msgstr "Verzeichnisname existiert bereits!"
 
@@ -730,6 +727,9 @@
 msgid "Can't switch channel!"
 msgstr "Kanal kann nicht umgeschaltet werden!"
 
+msgid "Schedule"
+msgstr "Programm"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Programm - %s"
@@ -863,6 +863,12 @@
 msgid "by time"
 msgstr "nach Zeit"
 
+msgid "ascending"
+msgstr "aufsteigend"
+
+msgid "descending"
+msgstr "absteigend"
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -953,6 +959,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr "Standard Sortierreihenfolge f�r Aufnahmen"
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr "Sortierreihenfolge f�r Aufnahmen"
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Nummerntasten f�r Zeichen"
 
@@ -1387,9 +1396,6 @@
 msgid " Stop recording "
 msgstr " Aufzeichnung beenden "
 
-msgid "Schedule"
-msgstr "Programm"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Wiedergabe beenden"
@@ -1597,15 +1603,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Taste dr�cken, um Ausschalten abzubrechen"
 
-msgid "Switching primary DVB..."
-msgstr "Prim�res Interface wird umgeschaltet..."
-
 msgid "Editing process failed!"
 msgstr "Bearbeitung gescheitert!"
 
 msgid "Editing process finished"
 msgstr "Bearbeitung beendet"
 
+msgid "Switching primary DVB..."
+msgstr "Prim�res Interface wird umgeschaltet..."
+
 msgid "Press any key to cancel restart"
 msgstr "Taste dr�cken, um Neustart abzubrechen"
 
diff -ruN vdr-2.3.8/po/el_GR.po vdr-2.3.9/po/el_GR.po
--- vdr-2.3.8/po/el_GR.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/el_GR.po	2018-03-14 23:46:32.405359263 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Dimitrios Dimitrakos <mail@dimitrios.de>\n"
 "Language-Team: Greek <vdr@linuxtv.org>\n"
@@ -611,9 +611,6 @@
 msgid "New folder"
 msgstr ""
 
-msgid "Sub folder"
-msgstr ""
-
 msgid "Folder name already exists!"
 msgstr ""
 
@@ -729,6 +726,9 @@
 msgid "Can't switch channel!"
 msgstr "������ �������� �������!"
 
+msgid "Schedule"
+msgstr "���������"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "��������� - %s"
@@ -862,6 +862,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -952,6 +958,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr ""
 
@@ -1386,9 +1395,6 @@
 msgid " Stop recording "
 msgstr " ����� ������� "
 
-msgid "Schedule"
-msgstr "���������"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " ����� ������������"
@@ -1596,15 +1602,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "����� ��� ������� ��� ������� �����������"
 
-msgid "Switching primary DVB..."
-msgstr "� ����� DVB ����� �������..."
-
 msgid "Editing process failed!"
 msgstr "� ����������� �������!"
 
 msgid "Editing process finished"
 msgstr "� ����������� ��������"
 
+msgid "Switching primary DVB..."
+msgstr "� ����� DVB ����� �������..."
+
 msgid "Press any key to cancel restart"
 msgstr ""
 
diff -ruN vdr-2.3.8/po/es_ES.po vdr-2.3.9/po/es_ES.po
--- vdr-2.3.8/po/es_ES.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/es_ES.po	2018-03-14 23:46:32.257361574 +0100
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2015-02-19 23:00+0100\n"
 "Last-Translator: Gabriel Bonich <gbonich@gmail.com>\n"
 "Language-Team: Spanish <vdr@linuxtv.org>\n"
@@ -612,9 +612,6 @@
 msgid "New folder"
 msgstr "Nueva carpeta"
 
-msgid "Sub folder"
-msgstr "Sub carpeta"
-
 msgid "Folder name already exists!"
 msgstr "�La carpeta ya existe!"
 
@@ -730,6 +727,9 @@
 msgid "Can't switch channel!"
 msgstr "�No se puede cambiar de canal!"
 
+msgid "Schedule"
+msgstr "Gu�a de Programaci�n"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Gu�a de programaci�n - %s"
@@ -863,6 +863,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "Men�s en pantalla"
 
@@ -953,6 +959,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Teclas num�ricas para caracteres"
 
@@ -1387,9 +1396,6 @@
 msgid " Stop recording "
 msgstr " Parar grabaci�n "
 
-msgid "Schedule"
-msgstr "Gu�a de Programaci�n"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Parar reproduci�n"
@@ -1597,15 +1603,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Pulse una tecla para interrumpir apagar"
 
-msgid "Switching primary DVB..."
-msgstr "Cambiando el interfaz DVB primario..."
-
 msgid "Editing process failed!"
 msgstr "�Proceso de edici�n fallido!"
 
 msgid "Editing process finished"
 msgstr "Proceso de edici�n terminado"
 
+msgid "Switching primary DVB..."
+msgstr "Cambiando el interfaz DVB primario..."
+
 msgid "Press any key to cancel restart"
 msgstr "Pulse cualquier tecla para cancelar reinicio"
 
diff -ruN vdr-2.3.8/po/et_EE.po vdr-2.3.9/po/et_EE.po
--- vdr-2.3.8/po/et_EE.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/et_EE.po	2018-03-14 23:46:32.061364635 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Arthur Konovalov <artlov@gmail.com>\n"
 "Language-Team: Estonian <vdr@linuxtv.org>\n"
@@ -611,9 +611,6 @@
 msgid "New folder"
 msgstr "Uus kaust"
 
-msgid "Sub folder"
-msgstr "Uus alamkaust"
-
 msgid "Folder name already exists!"
 msgstr "Sellenimeline kaust juba olemas!"
 
@@ -729,6 +726,9 @@
 msgid "Can't switch channel!"
 msgstr "Kanali vahetus ei ole võimalik!"
 
+msgid "Schedule"
+msgstr "Ajakava"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Ajakava - %s"
@@ -862,6 +862,12 @@
 msgid "by time"
 msgstr "aja järgi"
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -952,6 +958,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr "Salvestiste vaikimisi järjestus"
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Teksti sisestamine numbriklahvidega"
 
@@ -1386,9 +1395,6 @@
 msgid " Stop recording "
 msgstr " Lõpeta salvestamine "
 
-msgid "Schedule"
-msgstr "Ajakava"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Lõpeta taasesitus"
@@ -1596,15 +1602,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Vajuta suvalist klahvi väljalülitamise tühistamiseks"
 
-msgid "Switching primary DVB..."
-msgstr "Esmase DVB seadme ümberlülitus..."
-
 msgid "Editing process failed!"
 msgstr "Töötlemine ebaõnnestus!"
 
 msgid "Editing process finished"
 msgstr "Töötlemine lõpetatud"
 
+msgid "Switching primary DVB..."
+msgstr "Esmase DVB seadme ümberlülitus..."
+
 msgid "Press any key to cancel restart"
 msgstr "Taaskäivitamise katkestamiseks vajuta suvalist klahvi"
 
diff -ruN vdr-2.3.8/po/fi_FI.po vdr-2.3.9/po/fi_FI.po
--- vdr-2.3.8/po/fi_FI.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/fi_FI.po	2018-03-14 23:46:32.257361574 +0100
@@ -11,7 +11,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2007-08-15 15:52+0200\n"
 "Last-Translator: Matti Lehtimäki <matti.lehtimaki@gmail.com>\n"
 "Language-Team: Finnish <vdr@linuxtv.org>\n"
@@ -615,9 +615,6 @@
 msgid "New folder"
 msgstr "Uusi kansio"
 
-msgid "Sub folder"
-msgstr "Alikansio"
-
 msgid "Folder name already exists!"
 msgstr "Kansio on jo olemassa!"
 
@@ -733,6 +730,9 @@
 msgid "Can't switch channel!"
 msgstr "Kanavan vaihtaminen ei mahdollista!"
 
+msgid "Schedule"
+msgstr "Ohjelmisto"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Ohjelmisto - %s"
@@ -866,6 +866,12 @@
 msgid "by time"
 msgstr "ajan mukaan"
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "Kuvaruutunäyttö"
 
@@ -956,6 +962,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr "Järjestä tallenteet oletuksena"
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Käytä numeronäppäimiä tekstisyötteessä"
 
@@ -1303,16 +1312,16 @@
 msgstr "Tallenteen paluutunniste"
 
 msgid "any hosts"
-msgstr ""
+msgstr "mihin tahansa palvelimeen"
 
 msgid "only default host"
-msgstr ""
+msgstr "vain oletuspalvelimeen"
 
 msgid "type"
-msgstr ""
+msgstr "tyyppi"
 
 msgid "full"
-msgstr ""
+msgstr "kokonaan"
 
 msgid "Miscellaneous"
 msgstr "Sekalaiset"
@@ -1390,9 +1399,6 @@
 msgid " Stop recording "
 msgstr " Lopeta tallennus "
 
-msgid "Schedule"
-msgstr "Ohjelmisto"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Lopeta toisto"
@@ -1600,15 +1606,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Peru sammutus painamalla mitä tahansa näppäintä"
 
-msgid "Switching primary DVB..."
-msgstr "Vaihdetaan ensisijaista DVB-sovitinta..."
-
 msgid "Editing process failed!"
 msgstr "Muokkaus epäonnistui!"
 
 msgid "Editing process finished"
 msgstr "Muokkaus valmis"
 
+msgid "Switching primary DVB..."
+msgstr "Vaihdetaan ensisijaista DVB-sovitinta..."
+
 msgid "Press any key to cancel restart"
 msgstr "Peru uudelleenkäynnistys painamalla mitä tahansa näppäintä"
 
diff -ruN vdr-2.3.8/po/fr_FR.po vdr-2.3.9/po/fr_FR.po
--- vdr-2.3.8/po/fr_FR.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/fr_FR.po	2018-03-14 23:46:32.509357638 +0100
@@ -18,7 +18,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2015-02-18 20:16+0100\n"
 "Last-Translator: Bernard Jaulin <bernard.jaulin@gmail.com>\n"
 "Language-Team: French <vdr@linuxtv.org>\n"
@@ -622,9 +622,6 @@
 msgid "New folder"
 msgstr "Nouveau dossier"
 
-msgid "Sub folder"
-msgstr "Sous-dossier"
-
 msgid "Folder name already exists!"
 msgstr "Ce nom de dossier existe déjà !"
 
@@ -740,6 +737,9 @@
 msgid "Can't switch channel!"
 msgstr "Impossible de changer de chaîne !"
 
+msgid "Schedule"
+msgstr "Programme"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Programme - %s"
@@ -873,6 +873,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "Affichage à l'écran"
 
@@ -963,6 +969,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Touches numériques pour caractères"
 
@@ -1397,9 +1406,6 @@
 msgid " Stop recording "
 msgstr " Arrêter l'enregistrement "
 
-msgid "Schedule"
-msgstr "Programme"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Arrêter la lecture"
@@ -1607,15 +1613,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Appuyer sur une touche pour annuler l'arrêt"
 
-msgid "Switching primary DVB..."
-msgstr "Changement de périphérique DVB primaire..."
-
 msgid "Editing process failed!"
 msgstr "Échec du montage !"
 
 msgid "Editing process finished"
 msgstr "Montage terminé"
 
+msgid "Switching primary DVB..."
+msgstr "Changement de périphérique DVB primaire..."
+
 msgid "Press any key to cancel restart"
 msgstr "Appuyer sur une touche pour annuler le redémarrage"
 
diff -ruN vdr-2.3.8/po/hr_HR.po vdr-2.3.9/po/hr_HR.po
--- vdr-2.3.8/po/hr_HR.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/hr_HR.po	2018-03-14 23:46:32.721354327 +0100
@@ -9,7 +9,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2008-03-17 19:00+0100\n"
 "Last-Translator: Adrian Caval <anrxc@sysphere.org>\n"
 "Language-Team: Croatian <vdr@linuxtv.org>\n"
@@ -613,9 +613,6 @@
 msgid "New folder"
 msgstr ""
 
-msgid "Sub folder"
-msgstr ""
-
 msgid "Folder name already exists!"
 msgstr ""
 
@@ -731,6 +728,9 @@
 msgid "Can't switch channel!"
 msgstr "Nemogu�e prebaciti program!"
 
+msgid "Schedule"
+msgstr "Raspored"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Raspored - %s"
@@ -864,6 +864,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -954,6 +960,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr ""
 
@@ -1388,9 +1397,6 @@
 msgid " Stop recording "
 msgstr " Prekini snimanje "
 
-msgid "Schedule"
-msgstr "Raspored"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Prekini reprodukciju"
@@ -1598,15 +1604,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Pritisnite tipku za prekid isklju�ivanja"
 
-msgid "Switching primary DVB..."
-msgstr "Prebacivanje primarnog DVB ure�aja..."
-
 msgid "Editing process failed!"
 msgstr "Rezanje neuspje�no!"
 
 msgid "Editing process finished"
 msgstr "Rezanje zavr�eno"
 
+msgid "Switching primary DVB..."
+msgstr "Prebacivanje primarnog DVB ure�aja..."
+
 msgid "Press any key to cancel restart"
 msgstr "Pritisnite jednu tipku za poni�tenje ponovnog podizanja"
 
diff -ruN vdr-2.3.8/po/hu_HU.po vdr-2.3.9/po/hu_HU.po
--- vdr-2.3.8/po/hu_HU.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/hu_HU.po	2018-03-14 23:46:32.653355389 +0100
@@ -11,7 +11,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2015-02-13 09:36+0200\n"
 "Last-Translator: István Füley <ifuley@tigercomp.ro>\n"
 "Language-Team: Hungarian <vdr@linuxtv.org>\n"
@@ -616,9 +616,6 @@
 msgid "New folder"
 msgstr "Új könyvtár"
 
-msgid "Sub folder"
-msgstr "Alkönyvtár"
-
 msgid "Folder name already exists!"
 msgstr "Ez a könyvtárnév már létezik!"
 
@@ -734,6 +731,9 @@
 msgid "Can't switch channel!"
 msgstr "Csatornaváltás nem lehetséges!"
 
+msgid "Schedule"
+msgstr "Műsorújság"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Műsorújság - %s"
@@ -867,6 +867,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -957,6 +963,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Betűk a számgombokon"
 
@@ -1391,9 +1400,6 @@
 msgid " Stop recording "
 msgstr " Felvételt befejezni "
 
-msgid "Schedule"
-msgstr "Műsorújság"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Lejátszást befejzni"
@@ -1601,15 +1607,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Nyomj egy gombot a leállás megszakításához"
 
-msgid "Switching primary DVB..."
-msgstr "Elsődleges DVB eszköz váltása folyamatban..."
-
 msgid "Editing process failed!"
 msgstr "Vágás sikertelen!"
 
 msgid "Editing process finished"
 msgstr "Vágás befejezve"
 
+msgid "Switching primary DVB..."
+msgstr "Elsődleges DVB eszköz váltása folyamatban..."
+
 msgid "Press any key to cancel restart"
 msgstr "Nyomj egy gombot az újraindítás megszakításához"
 
diff -ruN vdr-2.3.8/po/it_IT.po vdr-2.3.9/po/it_IT.po
--- vdr-2.3.8/po/it_IT.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/it_IT.po	2018-03-14 23:46:32.409359200 +0100
@@ -11,8 +11,8 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
-"PO-Revision-Date: 2017-06-23 00:00+0100\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
+"PO-Revision-Date: 2017-07-02 23:49+0100\n"
 "Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n"
 "Language-Team: Italian <vdr@linuxtv.org>\n"
 "Language: it\n"
@@ -617,9 +617,6 @@
 msgid "New folder"
 msgstr "Nuova cartella"
 
-msgid "Sub folder"
-msgstr "Sotto cartella"
-
 msgid "Folder name already exists!"
 msgstr "Nome cartella già esistente!"
 
@@ -735,6 +732,9 @@
 msgid "Can't switch channel!"
 msgstr "Impossibile cambiare canale!"
 
+msgid "Schedule"
+msgstr "Programmi"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Programmi - %s"
@@ -868,6 +868,12 @@
 msgid "by time"
 msgstr "per ora"
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -958,6 +964,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr "Modalità ordinamento predefinito per registrazioni"
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Tasti numerici per i caratteri"
 
@@ -1305,16 +1314,16 @@
 msgstr "ID di ripristino"
 
 msgid "any hosts"
-msgstr ""
+msgstr "qualsiasi sistema"
 
 msgid "only default host"
-msgstr ""
+msgstr "solo sistema predefinito"
 
 msgid "type"
 msgstr "tipo"
 
 msgid "full"
-msgstr "completo"
+msgstr "tutto"
 
 msgid "Miscellaneous"
 msgstr "Generici"
@@ -1392,9 +1401,6 @@
 msgid " Stop recording "
 msgstr " Ferma registrazione "
 
-msgid "Schedule"
-msgstr "Programmi"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Ferma riproduzione"
@@ -1602,15 +1608,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Premi un tasto per annullare lo spegnimento"
 
-msgid "Switching primary DVB..."
-msgstr "Cambio della scheda DVB primaria..."
-
 msgid "Editing process failed!"
 msgstr "Processo di modifica fallito!"
 
 msgid "Editing process finished"
 msgstr "Processo di modifica terminato"
 
+msgid "Switching primary DVB..."
+msgstr "Cambio della scheda DVB primaria..."
+
 msgid "Press any key to cancel restart"
 msgstr "Premi un tasto per annullare il riavvio"
 
diff -ruN vdr-2.3.8/po/lt_LT.po vdr-2.3.9/po/lt_LT.po
--- vdr-2.3.8/po/lt_LT.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/lt_LT.po	2018-03-14 23:46:32.457358450 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2015-02-11 14:02+0200\n"
 "Last-Translator: Valdemaras Pipiras <varas@ambernet.lt>\n"
 "Language-Team: Lithuanian <vdr@linuxtv.org>\n"
@@ -611,9 +611,6 @@
 msgid "New folder"
 msgstr "Naujas katalogas"
 
-msgid "Sub folder"
-msgstr "Pakatalogis"
-
 msgid "Folder name already exists!"
 msgstr "Toks katalogo vardas jau egzistuoja!"
 
@@ -729,6 +726,9 @@
 msgid "Can't switch channel!"
 msgstr "Negali perjunti kanalo!"
 
+msgid "Schedule"
+msgstr "Programa"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Programa - %s"
@@ -862,6 +862,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD (ekrano užsklanda)"
 
@@ -952,6 +958,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Skaičių mygtukai simboliams"
 
@@ -1386,9 +1395,6 @@
 msgid " Stop recording "
 msgstr " Sustabdyti įrašinėjimą "
 
-msgid "Schedule"
-msgstr "Programa"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Sustabdyti kartojimą"
@@ -1596,15 +1602,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Paspauskit bet kurį mygtuką kad sustabdytumėte sistemos išjungimą"
 
-msgid "Switching primary DVB..."
-msgstr "Sukeičiamas pirminis DVB įrenginys..."
-
 msgid "Editing process failed!"
 msgstr "Koregavimas nepavyko!"
 
 msgid "Editing process finished"
 msgstr "Koregavimas baigtas"
 
+msgid "Switching primary DVB..."
+msgstr "Sukeičiamas pirminis DVB įrenginys..."
+
 msgid "Press any key to cancel restart"
 msgstr "Paspauskit bet kurį mygtuką kad sustabdytumėte perkrovimą"
 
diff -ruN vdr-2.3.8/po/mk_MK.po vdr-2.3.9/po/mk_MK.po
--- vdr-2.3.8/po/mk_MK.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/mk_MK.po	2018-03-14 23:46:32.569356701 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2015-02-08 15:18+0100\n"
 "Last-Translator: Dimitar Petrovski <dimeptr@gmail.com>\n"
 "Language-Team: Macedonian <en@li.org>\n"
@@ -612,9 +612,6 @@
 msgid "New folder"
 msgstr "Нов директориум"
 
-msgid "Sub folder"
-msgstr "Поддиректориум"
-
 msgid "Folder name already exists!"
 msgstr "Името веќе постои"
 
@@ -730,6 +727,9 @@
 msgid "Can't switch channel!"
 msgstr "Невозможно е да се смени канал!"
 
+msgid "Schedule"
+msgstr "Распоред"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Распоред - %s"
@@ -863,6 +863,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -953,6 +959,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Нумерички копчиња за букви"
 
@@ -1387,9 +1396,6 @@
 msgid " Stop recording "
 msgstr " Запри снимање "
 
-msgid "Schedule"
-msgstr "Распоред"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Запри пуштање"
@@ -1597,15 +1603,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Притиснете копче за отказ на исклучување"
 
-msgid "Switching primary DVB..."
-msgstr "Промена на примарниот DVB уред..."
-
 msgid "Editing process failed!"
 msgstr "Сечењето е неуспешно!"
 
 msgid "Editing process finished"
 msgstr "Сечењето заврши"
 
+msgid "Switching primary DVB..."
+msgstr "Промена на примарниот DVB уред..."
+
 msgid "Press any key to cancel restart"
 msgstr "Притиснете копче за откажување на рестарт"
 
diff -ruN vdr-2.3.8/po/nl_NL.po vdr-2.3.9/po/nl_NL.po
--- vdr-2.3.8/po/nl_NL.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/nl_NL.po	2018-03-14 23:46:32.333360387 +0100
@@ -13,7 +13,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2015-02-10 19:43+0100\n"
 "Last-Translator: Erik Oomen <oomen.e@gmail.com>\n"
 "Language-Team: Dutch <vdr@linuxtv.org>\n"
@@ -617,9 +617,6 @@
 msgid "New folder"
 msgstr "Nieuwe map"
 
-msgid "Sub folder"
-msgstr "Submap"
-
 msgid "Folder name already exists!"
 msgstr "Map bestaat al"
 
@@ -735,6 +732,9 @@
 msgid "Can't switch channel!"
 msgstr "Kan geen kanaal wisselen!"
 
+msgid "Schedule"
+msgstr "Programmagids"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Programma - %s"
@@ -868,6 +868,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -958,6 +964,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Nummertoetsen voor karakters"
 
@@ -1392,9 +1401,6 @@
 msgid " Stop recording "
 msgstr " Stop opnemen "
 
-msgid "Schedule"
-msgstr "Programmagids"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Stop afspelen"
@@ -1602,15 +1608,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Druk een toets om shutdown af te breken"
 
-msgid "Switching primary DVB..."
-msgstr "Eerste DVB-kaart wordt omgeschakeld..."
-
 msgid "Editing process failed!"
 msgstr "Bewerken is mislukt!"
 
 msgid "Editing process finished"
 msgstr "Bewerken is klaar"
 
+msgid "Switching primary DVB..."
+msgstr "Eerste DVB-kaart wordt omgeschakeld..."
+
 msgid "Press any key to cancel restart"
 msgstr "Druk een willekeurige toets om herstarten af te breken"
 
diff -ruN vdr-2.3.8/po/nn_NO.po vdr-2.3.9/po/nn_NO.po
--- vdr-2.3.8/po/nn_NO.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/nn_NO.po	2018-03-14 23:46:32.173362886 +0100
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Truls Slevigen <truls@slevigen.no>\n"
 "Language-Team: Norwegian Nynorsk <vdr@linuxtv.org>\n"
@@ -612,9 +612,6 @@
 msgid "New folder"
 msgstr ""
 
-msgid "Sub folder"
-msgstr ""
-
 msgid "Folder name already exists!"
 msgstr ""
 
@@ -730,6 +727,9 @@
 msgid "Can't switch channel!"
 msgstr "Ikke mulig � skifte kanal!"
 
+msgid "Schedule"
+msgstr "Programmer"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Program Guide - %s"
@@ -863,6 +863,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -953,6 +959,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr ""
 
@@ -1387,9 +1396,6 @@
 msgid " Stop recording "
 msgstr " Stopp opptak fra "
 
-msgid "Schedule"
-msgstr "Programmer"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Stopp avspilling"
@@ -1597,15 +1603,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Trykk en tast for ikke � sl� av"
 
-msgid "Switching primary DVB..."
-msgstr "Bytter f�rste DVB-enhet..."
-
 msgid "Editing process failed!"
 msgstr "Feil under redigering!"
 
 msgid "Editing process finished"
 msgstr "Redigeringsprosess avsluttet"
 
+msgid "Switching primary DVB..."
+msgstr "Bytter f�rste DVB-enhet..."
+
 msgid "Press any key to cancel restart"
 msgstr ""
 
diff -ruN vdr-2.3.8/po/pl_PL.po vdr-2.3.9/po/pl_PL.po
--- vdr-2.3.8/po/pl_PL.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/pl_PL.po	2018-03-14 23:46:31.941366509 +0100
@@ -4,38 +4,40 @@
 # Michael Rakowski <mrak@gmx.de>, 2002, 2003, 2008
 # Jaroslaw Swierczynski <swiergot@gmail.com>, 2006
 # Marek Nazarko <mnazarko@gmail.com>, 2013
-# Tomasz Maciej Nowak <tmn505@gmail.com>, 2015
+# Tomasz Maciej Nowak <tmn505@gmail.com>, 2015, 2018
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
-"PO-Revision-Date: 2015-02-12 00:59+0100\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
+"PO-Revision-Date: 2018-02-19 00:42+0100\n"
 "Last-Translator: Tomasz Maciej Nowak <tmn505@gmail.com>\n"
 "Language-Team: Polish <vdr@linuxtv.org>\n"
-"Language: pl\n"
+"Language: pl_PL\n"
 "MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=ISO-8859-2\n"
+"Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Poedit 2.0.6\n"
 
 msgid "*** Invalid Channel ***"
-msgstr "*** Niepoprawny kana� ***"
+msgstr "*** Niepoprawny kanał ***"
 
 msgid "CAM activated!"
 msgstr "CAM aktywowany!"
 
 msgid "Channel not available!"
-msgstr "Kana� nie jest dost�pny!"
+msgstr "Kanał nie jest dostępny!"
 
 msgid "Can't start Transfer Mode!"
-msgstr "Nie mo�na uruchomi� trybu transferu!"
+msgstr "Nie można uruchomić trybu transferu!"
 
 msgid "off"
-msgstr "wy��cz"
+msgstr "wyłącz"
 
 msgid "on"
-msgstr "w��cz"
+msgstr "włącz"
 
 msgid "auto"
 msgstr "auto"
@@ -98,7 +100,7 @@
 msgstr "Film/Dramat"
 
 msgid "Content$Detective/Thriller"
-msgstr "Krymina�/Thriller"
+msgstr "Kryminał/Thriller"
 
 msgid "Content$Adventure/Western/War"
 msgstr "Przygodowy/Western/Wojenny"
@@ -116,13 +118,13 @@
 msgstr "Romans"
 
 msgid "Content$Serious/Classical/Religious/Historical Movie/Drama"
-msgstr "Powa�ne/Klasyczne/Religijne/Historyczne/Dramaty"
+msgstr "Poważne/Klasyczne/Religijne/Historyczne/Dramaty"
 
 msgid "Content$Adult Movie/Drama"
-msgstr "Filmy dla doros�ych/Dramaty"
+msgstr "Filmy dla dorosłych/Dramaty"
 
 msgid "Content$News/Current Affairs"
-msgstr "Wiadomo�ci/Informacje"
+msgstr "Wiadomości/Informacje"
 
 msgid "Content$News/Weather Report"
 msgstr "Prognoza pogody"
@@ -158,13 +160,13 @@
 msgstr "Magazyny sportowe"
 
 msgid "Content$Football/Soccer"
-msgstr "Pi�ka no�na"
+msgstr "Piłka nożna"
 
 msgid "Content$Tennis/Squash"
 msgstr "Squash"
 
 msgid "Content$Team Sports"
-msgstr "Sporty zespo�owe"
+msgstr "Sporty zespołowe"
 
 msgid "Content$Athletics"
 msgstr "Lekkoatletyka"
@@ -185,7 +187,7 @@
 msgstr "Sporty walki"
 
 msgid "Content$Children's/Youth Programme"
-msgstr "Dzieci�ce"
+msgstr "Dziecięce"
 
 msgid "Content$Pre-school Children's Programme"
 msgstr "Przedszkolne"
@@ -200,7 +202,7 @@
 msgstr "Infromacyjne/Edukacyjne/Szkolne"
 
 msgid "Content$Cartoons/Puppets"
-msgstr "Kresk�wki/Kukie�kowe"
+msgstr "Kreskówki/Kukiełkowe"
 
 msgid "Content$Music/Ballet/Dance"
 msgstr "Muzyka/Balet/Taniec"
@@ -227,10 +229,10 @@
 msgstr "Sztuka/Kultura"
 
 msgid "Content$Performing Arts"
-msgstr "Rzemios�o"
+msgstr "Rzemiosło"
 
 msgid "Content$Fine Arts"
-msgstr "Dzie�a sztuki"
+msgstr "Dzieła sztuki"
 
 msgid "Content$Religion"
 msgstr "Religia"
@@ -260,22 +262,22 @@
 msgstr "Moda"
 
 msgid "Content$Social/Political/Economics"
-msgstr "Spo�eczne/Polityka/Ekonomia"
+msgstr "Społeczne/Polityka/Ekonomia"
 
 msgid "Content$Magazine/Report/Documentary"
-msgstr "Reporta�e/Dokumentalne"
+msgstr "Reportaże/Dokumentalne"
 
 msgid "Content$Economics/Social Advisory"
 msgstr "Ekonomia"
 
 msgid "Content$Remarkable People"
-msgstr "S�awni ludzie"
+msgstr "Sławni ludzie"
 
 msgid "Content$Education/Science/Factual"
 msgstr "Edukacja/Nauka"
 
 msgid "Content$Nature/Animals/Environment"
-msgstr "Natura/Zwierz�ta/�rodowisko"
+msgstr "Natura/Zwierzęta/Środowisko"
 
 msgid "Content$Technology/Natural Sciences"
 msgstr "Technologia/Nauki przyrodnicze"
@@ -287,22 +289,22 @@
 msgstr "Obce kraje/Wyprawy"
 
 msgid "Content$Social/Spiritual Sciences"
-msgstr "Spo�eczne/Duchowe"
+msgstr "Społeczne/Duchowe"
 
 msgid "Content$Further Education"
 msgstr "Dalsza edukacja"
 
 msgid "Content$Languages"
-msgstr "J�zyki"
+msgstr "Języki"
 
 msgid "Content$Leisure/Hobbies"
 msgstr "Czas wolny/Hobby"
 
 msgid "Content$Tourism/Travel"
-msgstr "Turystyka/Podr�e"
+msgstr "Turystyka/Podróże"
 
 msgid "Content$Handicraft"
-msgstr "R�kodzie�o"
+msgstr "Rękodzieło"
 
 msgid "Content$Motoring"
 msgstr "Automobilizm"
@@ -320,23 +322,23 @@
 msgstr "Ogrody"
 
 msgid "Content$Original Language"
-msgstr "Oryginalny j�zyk"
+msgstr "Oryginalny język"
 
 msgid "Content$Black & White"
-msgstr "Czarne i bia�e"
+msgstr "Czarne i białe"
 
 msgid "Content$Unpublished"
 msgstr "Niepublikowane"
 
 msgid "Content$Live Broadcast"
-msgstr "Transmisje na �ywo"
+msgstr "Transmisje na żywo"
 
 #, c-format
 msgid "ParentalRating$from %d"
 msgstr "od %d"
 
 msgid "No title"
-msgstr "Bez tytu�u"
+msgstr "Bez tytułu"
 
 #. TRANSLATORS: The name of the language, as written natively
 msgid "LanguageName$English"
@@ -350,50 +352,50 @@
 msgstr "Etap 1: Wykrywanie rodzaju kodu pilota"
 
 msgid "Press any key on the RC unit"
-msgstr "Naci�nij dowolny klawisz na pilocie"
+msgstr "Naciśnij dowolny klawisz na pilocie"
 
 msgid "RC code detected!"
 msgstr "Wykryto kod pilota!"
 
 msgid "Do not press any key..."
-msgstr "Nie naciskaj teraz �adnego klawisza..."
+msgstr "Nie naciskaj teraz żadnego klawisza..."
 
 msgid "Phase 2: Learning specific key codes"
-msgstr "Etap 2: Nauka konkretnych kod�w klawiszy"
+msgstr "Etap 2: Nauka konkretnych kodów klawiszy"
 
 #, c-format
 msgid "Press key for '%s'"
-msgstr "Naci�nij klawisz dla '%s'"
+msgstr "Naciśnij klawisz dla '%s'"
 
 msgid "Press 'Up' to confirm"
-msgstr "Naci�nij 'Do g�ry' aby potwierdzi�"
+msgstr "Naciśnij 'Do góry' aby potwierdzić"
 
 msgid "Press 'Down' to continue"
-msgstr "Naci�nij 'Na d�' aby kontynuowa�"
+msgstr "Naciśnij 'Na dół' aby kontynuować"
 
 msgid "(press 'Up' to go back)"
-msgstr "(naci�nij 'Do g�ry' aby si� cofn��)"
+msgstr "(naciśnij 'Do góry' aby się cofnąć)"
 
 msgid "(press 'Down' to end key definition)"
-msgstr "(naci�nij 'Na d�' aby zako�czy� definiowanie)"
+msgstr "(naciśnij 'Na dół' aby zakończyć definiowanie)"
 
 msgid "(press 'Menu' to skip this key)"
-msgstr "(naci�nij 'Menu' aby pomin�� ten klawisz)"
+msgstr "(naciśnij 'Menu' aby pominąć ten klawisz)"
 
 msgid "Learning Remote Control Keys"
 msgstr "Nauka klawiszy pilota"
 
 msgid "Phase 3: Saving key codes"
-msgstr "Etap 3: Zapisywanie kod�w klawiszy"
+msgstr "Etap 3: Zapisywanie kodów klawiszy"
 
 msgid "Press 'Up' to save, 'Down' to cancel"
-msgstr "Naci�nij 'Do g�ry' aby zapisa�, 'Na d�' - aby anulowa�"
+msgstr "Naciśnij 'Do góry' aby zapisać, 'Na dół' - aby anulować"
 
 msgid "Key$Up"
-msgstr "Do g�ry"
+msgstr "Do góry"
 
 msgid "Key$Down"
-msgstr "Na d�"
+msgstr "Na dół"
 
 msgid "Key$Menu"
 msgstr "Menu"
@@ -417,7 +419,7 @@
 msgstr "Zielony"
 
 msgid "Key$Yellow"
-msgstr "��ty"
+msgstr "Żółty"
 
 msgid "Key$Blue"
 msgstr "Niebieski"
@@ -426,10 +428,10 @@
 msgstr "Info"
 
 msgid "Key$Play/Pause"
-msgstr "Odtw�rz/Pauza"
+msgstr "Odtwórz/Pauza"
 
 msgid "Key$Play"
-msgstr "Odtw�rz"
+msgstr "Odtwórz"
 
 msgid "Key$Pause"
 msgstr "Pauza"
@@ -444,7 +446,7 @@
 msgstr "Do przodu"
 
 msgid "Key$FastRew"
-msgstr "Do ty�u"
+msgstr "Do tyłu"
 
 msgid "Key$Next"
 msgstr "Dalej"
@@ -453,19 +455,19 @@
 msgstr "Wstecz"
 
 msgid "Key$Power"
-msgstr "Wy��cz"
+msgstr "Wyłącz"
 
 msgid "Key$Channel+"
-msgstr "Kana�+"
+msgstr "Kanał+"
 
 msgid "Key$Channel-"
-msgstr "Kana�-"
+msgstr "Kanał-"
 
 msgid "Key$PrevChannel"
-msgstr "Poprzedni kana�"
+msgstr "Poprzedni kanał"
 
 msgid "Key$Volume+"
-msgstr "G�o�niej"
+msgstr "Głośniej"
 
 msgid "Key$Volume-"
 msgstr "Ciszej"
@@ -474,16 +476,16 @@
 msgstr "Wycisz"
 
 msgid "Key$Audio"
-msgstr "D�wi�k"
+msgstr "Dźwięk"
 
 msgid "Key$Subtitles"
-msgstr "Podtytu�y"
+msgstr "Podtytuły"
 
 msgid "Key$Schedule"
 msgstr "Program"
 
 msgid "Key$Channels"
-msgstr "Kana�y"
+msgstr "Kanały"
 
 msgid "Key$Timers"
 msgstr "Timery"
@@ -498,34 +500,34 @@
 msgstr "Polecenia"
 
 msgid "Key$User0"
-msgstr "U�ytkownik 0"
+msgstr "Użytkownik 0"
 
 msgid "Key$User1"
-msgstr "U�ytkownik 1"
+msgstr "Użytkownik 1"
 
 msgid "Key$User2"
-msgstr "U�ytkownik 2"
+msgstr "Użytkownik 2"
 
 msgid "Key$User3"
-msgstr "U�ytkownik 3"
+msgstr "Użytkownik 3"
 
 msgid "Key$User4"
-msgstr "U�ytkownik 4"
+msgstr "Użytkownik 4"
 
 msgid "Key$User5"
-msgstr "U�ytkownik 5"
+msgstr "Użytkownik 5"
 
 msgid "Key$User6"
-msgstr "U�ytkownik 6"
+msgstr "Użytkownik 6"
 
 msgid "Key$User7"
-msgstr "U�ytkownik 7"
+msgstr "Użytkownik 7"
 
 msgid "Key$User8"
-msgstr "U�ytkownik 8"
+msgstr "Użytkownik 8"
 
 msgid "Key$User9"
-msgstr "U�ytkownik 9"
+msgstr "Użytkownik 9"
 
 msgid "Free To Air"
 msgstr "nieszyfrowany"
@@ -534,16 +536,16 @@
 msgstr "szyfrowany"
 
 msgid "Edit channel"
-msgstr "Edycja kana�u"
+msgstr "Edycja kanału"
 
 msgid "Name"
 msgstr "Nazwa"
 
 msgid "Source"
-msgstr "�r�d�o"
+msgstr "Źródło"
 
 msgid "Frequency"
-msgstr "Cz�stotliwo��"
+msgstr "Częstotliwość"
 
 msgid "Vpid"
 msgstr "Vpid"
@@ -585,10 +587,10 @@
 msgstr "Tid"
 
 msgid "Channel settings are not unique!"
-msgstr "Ustawienia kana�u nie s� unikalne!"
+msgstr "Ustawienia kanału nie są unikalne!"
 
 msgid "Channels"
-msgstr "Kana�y"
+msgstr "Kanały"
 
 msgid "Button$Edit"
 msgstr "Edytuj"
@@ -597,16 +599,16 @@
 msgstr "Nowy"
 
 msgid "Button$Delete"
-msgstr "Usu�"
+msgstr "Usuń"
 
 msgid "Button$Mark"
 msgstr "Zaznacz"
 
 msgid "Channel is being used by a timer!"
-msgstr "Kana� jest u�ywany przez timer!"
+msgstr "Kanał jest używany przez timer!"
 
 msgid "Delete channel?"
-msgstr "Usun�� kana�?"
+msgstr "Usunąć kanał?"
 
 msgid "Edit folder"
 msgstr "Edutuj katalog"
@@ -614,24 +616,21 @@
 msgid "New folder"
 msgstr "Nowy katalog"
 
-msgid "Sub folder"
-msgstr "Podkatalog"
-
 msgid "Folder name already exists!"
 msgstr "Taka nazwa katalogu juz istnieje"
 
 #, c-format
 msgid "Folder name must not contain '%c'!"
-msgstr "Folder nie mo�e zawiera� '%c'!"
+msgstr "Folder nie może zawierać '%c'!"
 
 msgid "Button$Open"
-msgstr "Otw�rz"
+msgstr "Otwórz"
 
 msgid "Delete folder and all sub folders?"
-msgstr "Czy skasowa� katalog i wszystkie podkatalogi?"
+msgstr "Czy skasować katalog i wszystkie podkatalogi?"
 
 msgid "Delete folder?"
-msgstr "Czy skasowa� katalog?"
+msgstr "Czy skasować katalog?"
 
 msgid "Edit timer"
 msgstr "Edycja timera"
@@ -640,13 +639,13 @@
 msgstr "Aktywny"
 
 msgid "Channel"
-msgstr "Kana�"
+msgstr "Kanał"
 
 msgid "Day"
-msgstr "Dzie�"
+msgstr "Dzień"
 
 msgid "Start"
-msgstr "Pocz�tek"
+msgstr "Początek"
 
 msgid "Stop"
 msgstr "Koniec"
@@ -658,13 +657,13 @@
 msgstr "Priorytet"
 
 msgid "Lifetime"
-msgstr "Czas �ycia"
+msgstr "Czas życia"
 
 msgid "File"
 msgstr "Plik"
 
 msgid "Record on"
-msgstr ""
+msgstr "Nagrywanie włączone"
 
 msgid "Button$Folder"
 msgstr "Katalog"
@@ -676,13 +675,13 @@
 msgstr "Powtarzanie"
 
 msgid "First day"
-msgstr "Pierwszy dzie�"
+msgstr "Pierwszy dzień"
 
 msgid "Error while accessing remote timer"
-msgstr ""
+msgstr "Błąd podczas dostępu do zdalnego timera"
 
 msgid "Timer has been deleted!"
-msgstr ""
+msgstr "Timer został usunięty!"
 
 msgid "Select folder"
 msgstr "Wybierz katalog"
@@ -691,16 +690,16 @@
 msgstr "Timery"
 
 msgid "Button$On/Off"
-msgstr "W�/Wy�"
+msgstr "Wł/Wył"
 
 msgid "Button$Info"
 msgstr "Info"
 
 msgid "Delete timer?"
-msgstr "Usun�� timer?"
+msgstr "Usunąć timer?"
 
 msgid "Timer still recording - really delete?"
-msgstr "Trwa nagrywanie - na pewno usun��?"
+msgstr "Trwa nagrywanie - na pewno usunąć?"
 
 msgid "Event"
 msgstr "Audycja"
@@ -712,16 +711,16 @@
 msgstr "Nagraj"
 
 msgid "Button$Switch"
-msgstr "Prze��cz"
+msgstr "Przełącz"
 
 msgid "What's on now?"
-msgstr "Program bie��cy"
+msgstr "Program bieżący"
 
 msgid "What's on next?"
-msgstr "Nast�pne audycje"
+msgstr "Następne audycje"
 
 msgid "Button$Next"
-msgstr "Nast�pnie"
+msgstr "Następnie"
 
 msgid "Button$Now"
 msgstr "Teraz"
@@ -730,7 +729,10 @@
 msgstr "Program"
 
 msgid "Can't switch channel!"
-msgstr "Nie mo�na prze��czy� kana�u!"
+msgstr "Nie można przełączyć kanału!"
+
+msgid "Schedule"
+msgstr "Program"
 
 #, c-format
 msgid "Schedule - %s"
@@ -741,39 +743,39 @@
 msgstr "Ta audycja - %s"
 
 msgid "This event - all channels"
-msgstr "Ta audycja - wszystkie kana�y"
+msgstr "Ta audycja - wszystkie kanały"
 
 msgid "All events - all channels"
-msgstr "Wszystkie audycje - wszystkie kana�y"
+msgstr "Wszystkie audycje - wszystkie kanały"
 
 #, c-format
 msgid "Please enter %d digits!"
-msgstr "Prosz� poda� %d cyfr!"
+msgstr "Proszę podać %d cyfr!"
 
 msgid "CAM not responding!"
 msgstr "CAM nie reaguje!"
 
 msgid "Edit path"
-msgstr "Edytuj �cie�k�"
+msgstr "Edytuj ścieżkę"
 
 msgid "Folder"
 msgstr "Katalog"
 
 msgid "This folder is currently in use - no changes are possible!"
-msgstr "Ten katalog jest w u�yciu - zmiany nie s� mo�liwe!"
+msgstr "Ten katalog jest w użyciu - zmiany nie są możliwe!"
 
 #, c-format
 msgid "Move entire folder containing %d recordings?"
-msgstr "Przenie�� ca�y katalog zawiraj�cy %d nagra�?"
+msgstr "Przenieść cały katalog zawirający %d nagrań?"
 
 msgid "Error while moving folder!"
-msgstr "B��d podczas przenoszenia katalogu!"
+msgstr "Błąd podczas przenoszenia katalogu!"
 
 msgid "Edit recording"
 msgstr "Edytuj nagranie"
 
 msgid "This recording is currently in use - no changes are possible!"
-msgstr "To nagranie jest w u�yciu - zmiany nie s� mo�liwe!"
+msgstr "To nagranie jest w użyciu - zmiany nie są możliwe!"
 
 msgid "Button$Cancel cutting"
 msgstr "Anuluj przycinanie"
@@ -797,40 +799,40 @@
 msgstr "Wytnij"
 
 msgid "Button$Delete marks"
-msgstr "Usu� zaznaczone"
+msgstr "Usuń zaznaczone"
 
 msgid "Recording vanished!"
-msgstr "Nagranie znikn�o!"
+msgstr "Nagranie zniknęło!"
 
 msgid "Edited version already exists - overwrite?"
-msgstr "Edytowana wersja ju� istnieje - nadpisa�?"
+msgstr "Edytowana wersja już istnieje - nadpisać?"
 
 msgid "Error while queueing recording for cutting!"
-msgstr "B��d podczas zakolejkowania nagrania do przycinania!"
+msgstr "Błąd podczas zakolejkowania nagrania do przycinania!"
 
 msgid "Rename recording to folder name?"
-msgstr "Zmieni� nazw� nagrania na nazw� katalogu?"
+msgstr "Zmienić nazwę nagrania na nazwę katalogu?"
 
 msgid "Delete editing marks for this recording?"
-msgstr "Usun�� zaznaczenia z edycji dla tego nagrania?"
+msgstr "Usunąć zaznaczenia z edycji dla tego nagrania?"
 
 msgid "Error while deleting editing marks!"
-msgstr "B��d podczas usuwania zaznacze� edycji!"
+msgstr "Błąd podczas usuwania zaznaczeń edycji!"
 
 msgid "Error while changing priority/lifetime!"
-msgstr "B��d podczas zmiany priorytetu/czasu �ycia!"
+msgstr "Błąd podczas zmiany priorytetu/czasu życia!"
 
 msgid "Error while changing folder/name!"
-msgstr "B��d podczas zmiany katalogu/nazwy!"
+msgstr "Błąd podczas zmiany katalogu/nazwy!"
 
 msgid "Recording info"
 msgstr "Informacje o nagraniu"
 
 msgid "Button$Play"
-msgstr "Odtw�rz"
+msgstr "Odtwórz"
 
 msgid "Button$Rewind"
-msgstr "Pocz�tek"
+msgstr "Początek"
 
 msgid "Recordings"
 msgstr "Nagrania"
@@ -839,40 +841,46 @@
 msgstr "Polecenia"
 
 msgid "Delete recording?"
-msgstr "Usun�� nagranie?"
+msgstr "Usunąć nagranie?"
 
 msgid "Recording is being edited - really delete?"
-msgstr "Nagranie jest edytowane - jeste� pewien �e chcesz je usun��?"
+msgstr "Nagranie jest edytowane - jesteś pewien że chcesz je usunąć?"
 
 msgid "Error while deleting recording!"
-msgstr "B��d podczas usuwania nagrania!"
+msgstr "Błąd podczas usuwania nagrania!"
 
 msgid "Recording commands"
-msgstr "Polecenia nagra�"
+msgstr "Polecenia nagrań"
 
 msgid "never"
 msgstr "nigdy"
 
 msgid "skin dependent"
-msgstr "zal. od sk�rki"
+msgstr "zal. od skórki"
 
 msgid "always"
 msgstr "zawsze"
 
 msgid "by name"
-msgstr ""
+msgstr "wg nazwy"
 
 msgid "by time"
-msgstr ""
+msgstr "wg czasu"
+
+msgid "ascending"
+msgstr "rosnąco"
+
+msgid "descending"
+msgstr "malejąco"
 
 msgid "OSD"
 msgstr "OSD"
 
 msgid "Setup.OSD$Language"
-msgstr "J�zyk"
+msgstr "Język"
 
 msgid "Setup.OSD$Skin"
-msgstr "Sk�rka"
+msgstr "Skórka"
 
 msgid "Setup.OSD$Theme"
 msgstr "Motyw"
@@ -881,55 +889,55 @@
 msgstr "Od lewej (%)"
 
 msgid "Setup.OSD$Top (%)"
-msgstr "Od g�ry (%)"
+msgstr "Od góry (%)"
 
 msgid "Setup.OSD$Width (%)"
-msgstr "Szeroko�� (%)"
+msgstr "Szerokość (%)"
 
 msgid "Setup.OSD$Height (%)"
-msgstr "Wysoko�� (%)"
+msgstr "Wysokość (%)"
 
 msgid "Setup.OSD$Message time (s)"
-msgstr "Czas trwania wiadomo�ci (s)"
+msgstr "Czas trwania wiadomości (s)"
 
 msgid "Setup.OSD$Use small font"
-msgstr "U�ywaj ma�ej czcionki"
+msgstr "Używaj małej czcionki"
 
 msgid "Setup.OSD$Anti-alias"
-msgstr "Wyg�adzanie"
+msgstr "Wygładzanie"
 
 msgid "Setup.OSD$Default font"
 msgstr "Standardowa czcionka"
 
 msgid "Setup.OSD$Small font"
-msgstr "Ma�a czcionka"
+msgstr "Mała czcionka"
 
 msgid "Setup.OSD$Fixed font"
-msgstr "Sta�a czcionka"
+msgstr "Stała czcionka"
 
 msgid "Setup.OSD$Default font size (%)"
-msgstr "Standardowa czcionka wyj�ciowa (%)"
+msgstr "Standardowa czcionka wyjściowa (%)"
 
 msgid "Setup.OSD$Small font size (%)"
-msgstr "Ma�a czcionka wyj�ciowa (%)"
+msgstr "Mała czcionka wyjściowa (%)"
 
 msgid "Setup.OSD$Fixed font size (%)"
-msgstr "Ustalona czcionka wyj�ciowa (%)"
+msgstr "Ustalona czcionka wyjściowa (%)"
 
 msgid "Setup.OSD$Channel info position"
 msgstr "Pozycja informacji o kanale"
 
 msgid "bottom"
-msgstr "d�"
+msgstr "dół"
 
 msgid "top"
-msgstr "g�ra"
+msgstr "góra"
 
 msgid "Setup.OSD$Channel info time (s)"
 msgstr "Czas trwania informacji o kanale (s)"
 
 msgid "Setup.OSD$Info on channel switch"
-msgstr "Informacja przy zmianie kana�u"
+msgstr "Informacja przy zmianie kanału"
 
 msgid "Setup.OSD$Timeout requested channel info"
 msgstr "Czas oczekiwania na informacje"
@@ -938,13 +946,13 @@
 msgstr "Przesuwaj stronami"
 
 msgid "Setup.OSD$Scroll wraps"
-msgstr "Skrajne pozycje s�siaduj�"
+msgstr "Skrajne pozycje sąsiadują"
 
 msgid "Setup.OSD$Menu key closes"
 msgstr "Przycisk Menu zamyka"
 
 msgid "Setup.OSD$Recording directories"
-msgstr "Katalogi nagra�"
+msgstr "Katalogi nagrań"
 
 msgid "Setup.OSD$Folders in timer menu"
 msgstr "Katalogi w menu timera"
@@ -953,7 +961,10 @@
 msgstr "Sortuj najpierw katalogi"
 
 msgid "Setup.OSD$Default sort mode for recordings"
-msgstr ""
+msgstr "Domyślny tryb sortowania dla nagrań"
+
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr "Kierunek sortowania nagrań"
 
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Klawisze numeryczne dla liter"
@@ -980,7 +991,7 @@
 msgstr "Czas skanowania EPG (h)"
 
 msgid "Setup.EPG$EPG bugfix level"
-msgstr "Stopie� poprawek b��d�w EPG"
+msgstr "Stopień poprawek błędów EPG"
 
 msgid "Setup.EPG$EPG linger time (min)"
 msgstr "Czas przechowywania EPG (min)"
@@ -989,15 +1000,15 @@
 msgstr "Ustawiaj czas systemowy"
 
 msgid "Setup.EPG$Use time from transponder"
-msgstr "U�yj czasu z transpondera"
+msgstr "Użyj czasu z transpondera"
 
 #. TRANSLATORS: note the plural!
 msgid "Setup.EPG$Preferred languages"
-msgstr "Preferowanych j�zyk�w"
+msgstr "Preferowanych języków"
 
 #. TRANSLATORS: note the singular!
 msgid "Setup.EPG$Preferred language"
-msgstr "Preferowany j�zyk"
+msgstr "Preferowany język"
 
 msgid "pan&scan"
 msgstr "pan&scan"
@@ -1021,7 +1032,7 @@
 msgstr "nazwy i PID-y"
 
 msgid "add new channels"
-msgstr "dodawaj nowe kana�y"
+msgstr "dodawaj nowe kanały"
 
 msgid "add new transponders"
 msgstr "dodawaj nowe transpondery"
@@ -1030,7 +1041,7 @@
 msgstr "DVB"
 
 msgid "Button$Audio"
-msgstr "D�wi�k"
+msgstr "Dźwięk"
 
 msgid "Button$Subtitles"
 msgstr "Napisy"
@@ -1039,92 +1050,92 @@
 msgstr "Pierwszy interfejs DVB"
 
 msgid "Setup.DVB$Standard compliance"
-msgstr "Zgodno�� ze standardem"
+msgstr "Zgodność ze standardem"
 
 msgid "Setup.DVB$Video format"
 msgstr "Format obrazu"
 
 msgid "Setup.DVB$Video display format"
-msgstr "Format wy�wietlania obrazu"
+msgstr "Format wyświetlania obrazu"
 
 msgid "Setup.DVB$Use Dolby Digital"
-msgstr "U�ywaj Dolby Digital"
+msgstr "Używaj Dolby Digital"
 
 msgid "Setup.DVB$Update channels"
-msgstr "Aktualizuj kana�y"
+msgstr "Aktualizuj kanały"
 
 msgid "Setup.DVB$Audio languages"
-msgstr "J�zyk�w d�wi�ku"
+msgstr "Języków dźwięku"
 
 msgid "Setup.DVB$Audio language"
-msgstr "J�zyk d�wi�ku"
+msgstr "Język dźwięku"
 
 msgid "Setup.DVB$Display subtitles"
-msgstr "Obraz napis�w"
+msgstr "Obraz napisów"
 
 msgid "Setup.DVB$Subtitle languages"
-msgstr "J�zyki napisu"
+msgstr "Języki napisu"
 
 msgid "Setup.DVB$Subtitle language"
-msgstr "J�zyk napisu"
+msgstr "Język napisu"
 
 msgid "Setup.DVB$Subtitle offset"
-msgstr "Zr�wnowa�y� napis"
+msgstr "Zrównoważyć napis"
 
 msgid "Setup.DVB$Subtitle foreground transparency"
-msgstr "Prze�rocze podtytu��w: Prz�d"
+msgstr "Przeźrocze podtytułów: Przód"
 
 msgid "Setup.DVB$Subtitle background transparency"
-msgstr "Prze�rocze podtytu��w: T�o"
+msgstr "Przeźrocze podtytułów: Tło"
 
 msgid "LNB"
 msgstr "LNB"
 
 msgid "Setup.LNB$Use DiSEqC"
-msgstr "U�ywaj DiSEqC"
+msgstr "Używaj DiSEqC"
 
 msgid "Setup.LNB$SLOF (MHz)"
 msgstr "SLOF (MHz)"
 
 msgid "Setup.LNB$Low LNB frequency (MHz)"
-msgstr "Dolna cz�stotliwo�� LNB (MHz)"
+msgstr "Dolna częstotliwość LNB (MHz)"
 
 msgid "Setup.LNB$High LNB frequency (MHz)"
-msgstr "G�rna cz�stotliwo�� LNB (MHz)"
+msgstr "Górna częstotliwość LNB (MHz)"
 
 #, c-format
 msgid "Setup.LNB$Device %d connected to sat cable"
-msgstr "Urz�dzenie %d pod��czone do kabla satelitarnego"
+msgstr "Urządzenie %d podłączone do kabla satelitarnego"
 
 msgid "Setup.LNB$own"
-msgstr "W�asny"
+msgstr "Własny"
 
 msgid "Setup.LNB$Use dish positioner"
-msgstr "U�ywaj obrotnicy"
+msgstr "Używaj obrotnicy"
 
 msgid "Setup.LNB$Site latitude (degrees)"
-msgstr "Szeroko�� geograficzna stopnie"
+msgstr "Szerokość geograficzna stopnie"
 
 msgid "South"
-msgstr "Po�udnie"
+msgstr "Południe"
 
 msgid "North"
-msgstr "P�noc"
+msgstr "Północ"
 
 msgid "Setup.LNB$Site longitude (degrees)"
-msgstr "D�ugo�� geograficzna (stopnie)"
+msgstr "Długość geograficzna (stopnie)"
 
 msgid "West"
-msgstr "Zach�d"
+msgstr "Zachód"
 
 msgid "East"
-msgstr "Wsch�d"
+msgstr "Wschód"
 
 msgid "Setup.LNB$Max. positioner swing (degrees)"
-msgstr "Max. obr�t pozycjonera (stopnie)"
+msgstr "Max. obrót pozycjonera (stopnie)"
 
 msgid "Setup.LNB$Positioner speed (degrees/s)"
-msgstr "Pr�dko�� pozycjonera (stopnie/s)"
+msgstr "Prędkość pozycjonera (stopnie/s)"
 
 msgid "CAM reset"
 msgstr "CAM zresetowany"
@@ -1137,16 +1148,16 @@
 
 #. TRANSLATORS: note the leading blank!
 msgid " (activating)"
-msgstr " (aktywuj�)"
+msgstr " (aktywuję)"
 
 msgid "@ device"
-msgstr ""
+msgstr "@ urządzenie"
 
 msgid "CAM"
 msgstr "CAM"
 
 msgid "Button$Cancel activation"
-msgstr "Anuluj aktywacj�"
+msgstr "Anuluj aktywację"
 
 msgid "Button$Activate"
 msgstr "Aktywuj"
@@ -1161,37 +1172,37 @@
 msgstr "Otwieram menu CAM..."
 
 msgid "Can't open CAM menu!"
-msgstr "Nie mo�na otworzy� menu CAM!"
+msgstr "Nie można otworzyć menu CAM!"
 
 msgid "Can't activate CAM!"
-msgstr "Nie mo�na aktywowa� CAM!"
+msgstr "Nie można aktywować CAM!"
 
 msgid "CAM is in use - really reset?"
-msgstr "CAM jest w u�yciu - naprawd� zresetowa�?"
+msgstr "CAM jest w użyciu - naprawdę zresetować?"
 
 msgid "Can't reset CAM!"
-msgstr "Nie mo�na zresetowa� CAM!"
+msgstr "Nie można zresetować CAM!"
 
 msgid "no instant recording"
-msgstr ""
+msgstr "bez natychmiastowego nagrywania"
 
 msgid "confirm instant recording"
-msgstr ""
+msgstr "potwierdź natychmiastowe nagrywanie"
 
 msgid "record instantly"
-msgstr ""
+msgstr "nagraj natychmiast"
 
 msgid "do not pause live video"
-msgstr "nie wstrzymuj transmisji na �ywo"
+msgstr "nie wstrzymuj transmisji na żywo"
 
 msgid "confirm pause live video"
-msgstr "potwierd� wstrzymanie transmisji na �ywo"
+msgstr "potwierdź wstrzymanie transmisji na żywo"
 
 msgid "pause live video"
-msgstr "wstrzymaj transmisj� na �ywo"
+msgstr "wstrzymaj transmisję na żywo"
 
 msgid "confirm"
-msgstr "potwierd�"
+msgstr "potwierdź"
 
 msgid "yes"
 msgstr "tak"
@@ -1200,34 +1211,34 @@
 msgstr "Nagranie"
 
 msgid "Setup.Recording$Margin at start (min)"
-msgstr "Margines na pocz�tku (min)"
+msgstr "Margines na początku (min)"
 
 msgid "Setup.Recording$Margin at stop (min)"
-msgstr "Margines na ko�cu (min)"
+msgstr "Margines na końcu (min)"
 
 msgid "Setup.Recording$Default priority"
-msgstr "Domy�lny priorytet"
+msgstr "Domyślny priorytet"
 
 msgid "Setup.Recording$Default lifetime (d)"
-msgstr "Domy�lny czas �ycia (d)"
+msgstr "Domyślny czas życia (d)"
 
 msgid "Setup.Recording$Record key handling"
-msgstr ""
+msgstr "Obsługa klawisza nagrywania"
 
 msgid "Setup.Recording$Pause key handling"
-msgstr "Obs�uga klawisza pauzy"
+msgstr "Obsługa klawisza pauzy"
 
 msgid "Setup.Recording$Pause priority"
 msgstr "Priorytet pauzy"
 
 msgid "Setup.Recording$Pause lifetime (d)"
-msgstr "Czas �ycia pauzy (d)"
+msgstr "Czas życia pauzy (d)"
 
 msgid "Setup.Recording$Use episode name"
-msgstr "U�ywaj nazwy epizodu"
+msgstr "Używaj nazwy epizodu"
 
 msgid "Setup.Recording$Use VPS"
-msgstr "U�ywaj VPS"
+msgstr "Używaj VPS"
 
 msgid "Setup.Recording$VPS margin (s)"
 msgstr "Margines VPS (s)"
@@ -1251,22 +1262,22 @@
 msgstr "Dziel edytowane pliki"
 
 msgid "Setup.Recording$Delete timeshift recording"
-msgstr "Usu� nagranie timeshift"
+msgstr "Usuń nagranie timeshift"
 
 msgid "Replay"
 msgstr "Odtwarzanie"
 
 msgid "Setup.Replay$Multi speed mode"
-msgstr "Tryb wielopr�dko�ciowy"
+msgstr "Tryb wieloprędkościowy"
 
 msgid "Setup.Replay$Show replay mode"
 msgstr "Pokazuj tryb odtwarzania"
 
 msgid "Setup.Replay$Show remaining time"
-msgstr "Poka� pozosta�y czas"
+msgstr "Pokaż pozostały czas"
 
 msgid "Setup.Replay$Progress display time (s)"
-msgstr "Wy�wietl czas odtwarzania (s)"
+msgstr "Wyświetl czas odtwarzania (s)"
 
 msgid "Setup.Replay$Pause replay when setting mark"
 msgstr "Wstrzymaj odtwarzanie podczas ustawiania zaznaczania"
@@ -1275,106 +1286,106 @@
 msgstr "Wstrzymaj odtwarzanie przy przeskoku do zaznaczenia"
 
 msgid "Setup.Replay$Skip edited parts"
-msgstr "Przeskocz edytowan� cz��"
+msgstr "Przeskocz edytowaną część"
 
 msgid "Setup.Replay$Pause replay at last mark"
 msgstr "Wstrzymaj odtwarzanie przy ostatnim zaznaczeniu"
 
 msgid "Setup.Replay$Initial duration for adaptive skipping (s)"
-msgstr "Pocz�tkowy czas trwania dla przeskoku adaptacyjnego (s)"
+msgstr "Początkowy czas trwania dla przeskoku adaptacyjnego (s)"
 
 msgid "Setup.Replay$Reset timeout for adaptive skipping (s)"
 msgstr "Czas oczekiwania na wyzerowanie dla przeskoku adaptacyjnego (s)"
 
 msgid "Setup.Replay$Alternate behavior for adaptive skipping"
-msgstr "Zmie� zachowanie dla przeskoku adaptacyjnego"
+msgstr "Zmień zachowanie dla przeskoku adaptacyjnego"
 
 msgid "Setup.Replay$Use Prev/Next keys for adaptive skipping"
-msgstr "U�yj klawiszy Poprzedni/Nast�pny dla przeskoku adaptacyjnego"
+msgstr "Użyj klawiszy Poprzedni/Następny dla przeskoku adaptacyjnego"
 
 msgid "Setup.Replay$Skip distance with Green/Yellow keys (s)"
-msgstr "Pomi� dystans klawiszami Zielony/��ty (s)"
+msgstr "Pomiń dystans klawiszami Zielony/Żółty (s)"
 
 msgid "Setup.Replay$Skip distance with Green/Yellow keys in repeat (s)"
-msgstr "Pomi� dystans klawiszami Zielony/��ty w powt�rce (s)"
+msgstr "Pomiń dystans klawiszami Zielony/Żółty w powtórce (s)"
 
 msgid "Setup.Replay$Resume ID"
 msgstr "ID wznowienia"
 
 msgid "any hosts"
-msgstr ""
+msgstr "każdy host"
 
 msgid "only default host"
-msgstr ""
+msgstr "tylko domyślny host"
 
 msgid "type"
-msgstr ""
+msgstr "typ"
 
 msgid "full"
-msgstr ""
+msgstr "pełny"
 
 msgid "Miscellaneous"
-msgstr "R�ne"
+msgstr "Różne"
 
 msgid "Setup.Miscellaneous$Min. event timeout (min)"
 msgstr "Minimalny czas audycji (min)"
 
 msgid "Setup.Miscellaneous$Min. user inactivity (min)"
-msgstr "Minimalny czas nieaktywno�ci (min)"
+msgstr "Minimalny czas nieaktywności (min)"
 
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "Czas oczekiwania na SVDRP (s)"
 
 msgid "Setup.Miscellaneous$SVDRP peering"
-msgstr ""
+msgstr "Dzielenie zadań przez SVDRP"
 
 msgid "Setup.Miscellaneous$SVDRP host name"
-msgstr ""
+msgstr "Nazwa hosta SVDRP"
 
 msgid "Setup.Miscellaneous$SVDRP default host"
-msgstr ""
+msgstr "Domyślny host SVDRP"
 
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Czas oczekiwania na zap (s)"
 
 msgid "Setup.Miscellaneous$Channel entry timeout (ms)"
-msgstr "Min�� czas wej�cia do kana�u"
+msgstr "Minął czas wejścia do kanału"
 
 msgid "Setup.Miscellaneous$Remote control repeat delay (ms)"
-msgstr "Czas op�nienia powtarzania dla pilota (ms)"
+msgstr "Czas opóźnienia powtarzania dla pilota (ms)"
 
 msgid "Setup.Miscellaneous$Remote control repeat delta (ms)"
-msgstr "Delta op�nienia powtarzania dla pilota (ms)"
+msgstr "Delta opóźnienia powtarzania dla pilota (ms)"
 
 msgid "Setup.Miscellaneous$Initial channel"
-msgstr "Pocz�tkowy kana�"
+msgstr "Początkowy kanał"
 
 msgid "Setup.Miscellaneous$as before"
 msgstr "jak ostatnio"
 
 msgid "Setup.Miscellaneous$Initial volume"
-msgstr "Pocz�tkowa g�o�no��"
+msgstr "Początkowa głośność"
 
 msgid "Setup.Miscellaneous$Volume steps"
-msgstr "Przeskok g�o�no�ci"
+msgstr "Przeskok głośności"
 
 msgid "Setup.Miscellaneous$Volume linearize"
-msgstr "Linearyzacja g�o�no�ci"
+msgstr "Linearyzacja głośności"
 
 msgid "Setup.Miscellaneous$Channels wrap"
-msgstr "Zawijanie kana��w"
+msgstr "Zawijanie kanałów"
 
 msgid "Setup.Miscellaneous$Show channel names with source"
-msgstr "Poka� nazwy kana��w ze �r�d�em"
+msgstr "Pokaż nazwy kanałów ze źródłem"
 
 msgid "Setup.Miscellaneous$Emergency exit"
-msgstr "Wyj�cie awaryjne"
+msgstr "Wyjście awaryjne"
 
 msgid "Plugins"
 msgstr "Wtyczki"
 
 msgid "This plugin has no setup parameters!"
-msgstr "Ta wtyczka nie ma �adnych ustawie�!"
+msgstr "Ta wtyczka nie ma żadnych ustawień!"
 
 msgid "Setup"
 msgstr "Ustawienia"
@@ -1383,15 +1394,12 @@
 msgstr "Restart"
 
 msgid "Really restart?"
-msgstr "Na pewno zrestartowa�?"
+msgstr "Na pewno zrestartować?"
 
 #. TRANSLATORS: note the leading and trailing blanks!
 msgid " Stop recording "
 msgstr " Zatrzymaj nagrywanie "
 
-msgid "Schedule"
-msgstr "Program"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Zatrzymaj odtwarzanie"
@@ -1403,26 +1411,26 @@
 msgstr "Zatrzymaj"
 
 msgid "Button$Resume"
-msgstr "Wzn�w"
+msgstr "Wznów"
 
 #. TRANSLATORS: note the leading blank!
 msgid " Cancel editing"
-msgstr " Anuluj edycj�"
+msgstr " Anuluj edycję"
 
 msgid "Stop recording?"
-msgstr "Zatrzyma� nagrywanie?"
+msgstr "Zatrzymać nagrywanie?"
 
 msgid "Cancel editing?"
-msgstr "Anulowa� monta�?"
+msgstr "Anulować montaż?"
 
 msgid "No audio available!"
-msgstr "D�wi�k nie jest dost�pny!"
+msgstr "Dźwięk nie jest dostępny!"
 
 msgid "No subtitles"
-msgstr "Brak napis�w"
+msgstr "Brak napisów"
 
 msgid "No subtitles available!"
-msgstr "Napisy nie s� dost�pne"
+msgstr "Napisy nie są dostępne"
 
 msgid "Not enough disk space to start recording!"
 msgstr "Brak miejsca na dysku do nagrywania!"
@@ -1431,35 +1439,35 @@
 msgstr "Brak wolnej karty DVB do nagrywania!"
 
 msgid "Pausing live video..."
-msgstr "Pauzuj� program na �ywo..."
+msgstr "Pauzuję program na żywo..."
 
 msgid "Delete timeshift recording?"
-msgstr "Czy usun�� nagranie timeshift?"
+msgstr "Czy usunąć nagranie timeshift?"
 
 #. TRANSLATORS: note the trailing blank!
 msgid "Jump: "
 msgstr "Skok: "
 
 msgid "No editing marks defined!"
-msgstr "Nie zdefiniowano znacznik�w monta�u!"
+msgstr "Nie zdefiniowano znaczników montażu!"
 
 msgid "No editing sequences defined!"
 msgstr "Nie zdefiniowano sekwencji edycji!"
 
 msgid "Can't start editing process!"
-msgstr "Nie mo�na uruchomi� procesu edycji!"
+msgstr "Nie można uruchomić procesu edycji!"
 
 msgid "Editing process started"
-msgstr "Proces edycji rozpocz�ty"
+msgstr "Proces edycji rozpoczęty"
 
 msgid "Editing process already active!"
-msgstr "Proces edycji jest ju� aktywny!"
+msgstr "Proces edycji jest już aktywny!"
 
 msgid "FileNameChars$ abcdefghijklmnopqrstuvwxyz0123456789-.,#~\\^$[]|()*+?{}/:%@&"
-msgstr " a�bc�de�fghijkl�mn�o�pqrs�tuvwxyz��0123456789-.,#~\\^$[]|()*+?{}/:%@&"
+msgstr " aąbcćdeęfghijklłmnńoópqrsśtuvwxyzźż0123456789-.,#~\\^$[]|()*+?{}/:%@&"
 
 msgid "CharMap$ 0\t-.,1#~\\^$[]|()*+?{}/:%@&\tabc2\tdef3\tghi4\tjkl5\tmno6\tpqrs7\ttuv8\twxyz9"
-msgstr " 0\t-.,1#~\\^$[]|()*+?{}/:%@&\tabc2��\tdef3�\tghi4\tjkl5�\tmno6��\tpqrs7�\ttuv8\twxyz9��"
+msgstr " 0\t-.,1#~\\^$[]|()*+?{}/:%@&\tabc2ąć\tdef3ę\tghi4\tjkl5ł\tmno6ńó\tpqrs7ś\ttuv8\twxyz9źż"
 
 msgid "Button$ABC/abc"
 msgstr "ABC/abc"
@@ -1474,55 +1482,55 @@
 msgstr "Wtyczka"
 
 msgid "Up/Dn for new location - OK to move"
-msgstr "Do g�ry/Na d� na now� pozycj� - Ok zatwierdza"
+msgstr "Do góry/Na dół na nową pozycję - Ok zatwierdza"
 
 msgid "Channel locked (recording)!"
-msgstr "Kana� zablokowany (trwa nagrywanie)!"
+msgstr "Kanał zablokowany (trwa nagrywanie)!"
 
 msgid "Low disk space!"
-msgstr "Ma�o miejsca na dysku!"
+msgstr "Mało miejsca na dysku!"
 
 msgid "Regenerating index file"
 msgstr "Odtwarzanie pliku z indeksem"
 
 msgid "Index file regeneration complete"
-msgstr "Odtwarzanie pliku z indeksem zako�czone"
+msgstr "Odtwarzanie pliku z indeksem zakończone"
 
 msgid "Index file regeneration failed!"
 msgstr "Odtwarzanie pliku z indeksem nieudane!"
 
 msgid "Can't shutdown - option '-s' not given!"
-msgstr "Nie mo�na wy��czy� - nie podano opcji '-s'!"
+msgstr "Nie można wyłączyć - nie podano opcji '-s'!"
 
 msgid "Editing - shut down anyway?"
-msgstr "Monta� w trakcie - Wy��czy� mimo to?"
+msgstr "Montaż w trakcie - Wyłączyć mimo to?"
 
 msgid "Recording - shut down anyway?"
-msgstr "Trwa nagrywanie - wy��czy� mimo to?"
+msgstr "Trwa nagrywanie - wyłączyć mimo to?"
 
 #, c-format
 msgid "Recording in %ld minutes, shut down anyway?"
-msgstr "Nagrywanie za %ld minut - wy��czy� mimo to?"
+msgstr "Nagrywanie za %ld minut - wyłączyć mimo to?"
 
 msgid "shut down anyway?"
-msgstr "wy��czy� mimo to?"
+msgstr "wyłączyć mimo to?"
 
 #, c-format
 msgid "Plugin %s wakes up in %ld min, continue?"
-msgstr "Wtyczka %s obudzi si� za %ld min, kontynuowa�?"
+msgstr "Wtyczka %s obudzi się za %ld min, kontynuować?"
 
 msgid "Editing - restart anyway?"
-msgstr "Monta� w trakcie - Zrestartowa� mimo to?"
+msgstr "Montaż w trakcie - Zrestartować mimo to?"
 
 msgid "Recording - restart anyway?"
-msgstr "Trwa nagrywanie - zrestartowa� mimo to?"
+msgstr "Trwa nagrywanie - zrestartować mimo to?"
 
 msgid "restart anyway?"
-msgstr "zrestartowa� mimo to?"
+msgstr "zrestartować mimo to?"
 
 #. TRANSLATORS: note the trailing blank!
 msgid "Volume "
-msgstr "G�o�no�� "
+msgstr "Głośność "
 
 msgid "Classic VDR"
 msgstr "Klasyczny VDR"
@@ -1531,16 +1539,16 @@
 msgstr "DYSK"
 
 msgid "LOAD"
-msgstr "OBCI��ENIE"
+msgstr "OBCIĄŻENIE"
 
 msgid "TIMERS"
 msgstr "TIMERY"
 
 msgid "DEVICES"
-msgstr "URZ�DZENIA"
+msgstr "URZĄDZENIA"
 
 msgid "LIVE"
-msgstr "NA �YWO"
+msgstr "NA ŻYWO"
 
 msgid "PLAY"
 msgstr "ODTWARZA"
@@ -1554,26 +1562,26 @@
 
 #. TRANSLATORS: the first character of each weekday, beginning with monday
 msgid "MTWTFSS"
-msgstr "PW�CPSN"
+msgstr "PWŚCPSN"
 
 #. TRANSLATORS: abbreviated weekdays, beginning with monday (must all be 3 letters!)
 msgid "MonTueWedThuFriSatSun"
-msgstr "PonWto�roCzwPi�SobNie"
+msgstr "PonWtoŚroCzwPiąSobNie"
 
 msgid "Monday"
-msgstr "poniedzia�ek"
+msgstr "poniedziałek"
 
 msgid "Tuesday"
 msgstr "Wtorek"
 
 msgid "Wednesday"
-msgstr "�roda"
+msgstr "środa"
 
 msgid "Thursday"
 msgstr "Czwartek"
 
 msgid "Friday"
-msgstr "Pi�tek"
+msgstr "Piątek"
 
 msgid "Saturday"
 msgstr "Sobota"
@@ -1582,38 +1590,38 @@
 msgstr "Niedziela"
 
 msgid "Upcoming recording!"
-msgstr "Wkr�tce nagranie!"
+msgstr "Wkrótce nagranie!"
 
 msgid "Pause live video?"
-msgstr "Zatrzyma� transmisj� na �ywo?"
+msgstr "Zatrzymać transmisję na żywo?"
 
 msgid "Start recording?"
-msgstr ""
+msgstr "Rozpocząć nagrywanie?"
 
 msgid "Recording started"
-msgstr "Rozpocz�to nagrywanie"
+msgstr "Rozpoczęto nagrywanie"
 
 msgid "VDR will shut down later - press Power to force"
-msgstr "VDR zostanie wy��czony p�niej - Naci�nij Wy��cz (Power) aby wymusi�"
+msgstr "VDR zostanie wyłączony później - Naciśnij Wyłącz (Power) aby wymusić"
 
 msgid "Press any key to cancel shutdown"
-msgstr "Naci�nij dowolny klawisz aby nie wy��cza�"
-
-msgid "Switching primary DVB..."
-msgstr "Prz곱czam na pierwszy interfejs DVB..."
+msgstr "Naciśnij dowolny klawisz aby nie wyłączać"
 
 msgid "Editing process failed!"
-msgstr "Proces edycji nie powi�d� si�!"
+msgstr "Proces edycji nie powiódł się!"
 
 msgid "Editing process finished"
-msgstr "Proces edycji zako�czony"
+msgstr "Proces edycji zakończony"
+
+msgid "Switching primary DVB..."
+msgstr "Przęłączam na pierwszy interfejs DVB..."
 
 msgid "Press any key to cancel restart"
-msgstr "Naci�nij dowolny klawisz aby nie zrestartowa�"
+msgstr "Naciśnij dowolny klawisz aby nie zrestartować"
 
 #, c-format
 msgid "VDR will shut down in %s minutes"
-msgstr "VDR zostanie wy��czony za %s minut"
+msgstr "VDR zostanie wyłączony za %s minut"
 
 msgid "Disk"
 msgstr "Dysk"
diff -ruN vdr-2.3.8/po/pt_PT.po vdr-2.3.9/po/pt_PT.po
--- vdr-2.3.8/po/pt_PT.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/pt_PT.po	2018-03-14 23:46:31.901367134 +0100
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2010-03-28 22:49+0100\n"
 "Last-Translator: Cris Silva <hudokkow@gmail.com>\n"
 "Language-Team: Portuguese <vdr@linuxtv.org>\n"
@@ -612,9 +612,6 @@
 msgid "New folder"
 msgstr "Nova pasta"
 
-msgid "Sub folder"
-msgstr "Subpasta"
-
 msgid "Folder name already exists!"
 msgstr "Nome da pasta j� existe!"
 
@@ -730,6 +727,9 @@
 msgid "Can't switch channel!"
 msgstr "Imposs�vel mudar de canal!"
 
+msgid "Schedule"
+msgstr "Programa��o"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Programa��o - %s"
@@ -863,6 +863,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -953,6 +959,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Teclas num�ricas para caracteres"
 
@@ -1387,9 +1396,6 @@
 msgid " Stop recording "
 msgstr " Parar grava��o "
 
-msgid "Schedule"
-msgstr "Programa��o"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Parar reprodu��o"
@@ -1597,15 +1603,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Pressione qualquer tecla para cancelar o encerramento"
 
-msgid "Switching primary DVB..."
-msgstr "A mudar placa de DVB prim�ria..."
-
 msgid "Editing process failed!"
 msgstr "Processo de edi��o falhou!"
 
 msgid "Editing process finished"
 msgstr "Processo de edi��o terminou"
 
+msgid "Switching primary DVB..."
+msgstr "A mudar placa de DVB prim�ria..."
+
 msgid "Press any key to cancel restart"
 msgstr "Pressione qualquer tecla para cancelar o reinicio"
 
diff -ruN vdr-2.3.8/po/ro_RO.po vdr-2.3.9/po/ro_RO.po
--- vdr-2.3.8/po/ro_RO.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/ro_RO.po	2018-03-14 23:46:32.721354327 +0100
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2015-02-11 22:26+0100\n"
 "Last-Translator: Lucian Muresan <lucianm@users.sourceforge.net>\n"
 "Language-Team: Romanian <vdr@linuxtv.org>\n"
@@ -613,9 +613,6 @@
 msgid "New folder"
 msgstr "Director nou"
 
-msgid "Sub folder"
-msgstr "Sub-director"
-
 msgid "Folder name already exists!"
 msgstr "Un director cu acelaşi nume există!"
 
@@ -731,6 +728,9 @@
 msgid "Can't switch channel!"
 msgstr "Nu pot comuta canalul!"
 
+msgid "Schedule"
+msgstr "Program (EPG)"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Programul canalului %s"
@@ -864,6 +864,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -954,6 +960,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Caractere pe tastele numerice"
 
@@ -1388,9 +1397,6 @@
 msgid " Stop recording "
 msgstr " Opreşte înregistrarea "
 
-msgid "Schedule"
-msgstr "Program (EPG)"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Opreşte redarea"
@@ -1598,15 +1604,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Apasă orice tastă pentru a anula închiderea"
 
-msgid "Switching primary DVB..."
-msgstr "Comut dispozitiv DVB primar..."
-
 msgid "Editing process failed!"
 msgstr "Editarea înregistrării a eşuat"
 
 msgid "Editing process finished"
 msgstr "Editarea înregistrării s-a încheiat"
 
+msgid "Switching primary DVB..."
+msgstr "Comut dispozitiv DVB primar..."
+
 msgid "Press any key to cancel restart"
 msgstr "Apăsaţi orice tastă pentru a anula repornirea"
 
diff -ruN vdr-2.3.8/po/ru_RU.po vdr-2.3.9/po/ru_RU.po
--- vdr-2.3.8/po/ru_RU.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/ru_RU.po	2018-03-14 23:46:32.021365260 +0100
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2016-12-27 17:13+0100\n"
 "Last-Translator: Pridvorov Andrey <ua0lnj@bk.ru>\n"
 "Language-Team: Russian <vdr@linuxtv.org>\n"
@@ -612,9 +612,6 @@
 msgid "New folder"
 msgstr "Новая директория"
 
-msgid "Sub folder"
-msgstr "поддиректория"
-
 msgid "Folder name already exists!"
 msgstr "Директория уже существует!"
 
@@ -730,6 +727,9 @@
 msgid "Can't switch channel!"
 msgstr "Невозможно переключить канал!"
 
+msgid "Schedule"
+msgstr "Телегид"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Программа - %s"
@@ -863,6 +863,12 @@
 msgid "by time"
 msgstr "по времени"
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "Меню"
 
@@ -953,6 +959,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr "Сортировка по умолчанию"
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Количество кнопок для символов"
 
@@ -1387,9 +1396,6 @@
 msgid " Stop recording "
 msgstr " Прекратить запись "
 
-msgid "Schedule"
-msgstr "Телегид"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Прекратить воспроизведение"
@@ -1597,15 +1603,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Нажмите любую кнопку чтобы отменить выключение"
 
-msgid "Switching primary DVB..."
-msgstr "Смена основного DVB-устройства..."
-
 msgid "Editing process failed!"
 msgstr "Ошибка во время монтажа записи!"
 
 msgid "Editing process finished"
 msgstr "Монтаж окончен"
 
+msgid "Switching primary DVB..."
+msgstr "Смена основного DVB-устройства..."
+
 msgid "Press any key to cancel restart"
 msgstr "Нажмите любую кнопку для отмены перезагрузки"
 
diff -ruN vdr-2.3.8/po/sk_SK.po vdr-2.3.9/po/sk_SK.po
--- vdr-2.3.8/po/sk_SK.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/sk_SK.po	2018-03-14 23:46:32.569356701 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2015-02-17 18:59+0100\n"
 "Last-Translator: Milan Hrala <hrala.milan@gmail.com>\n"
 "Language-Team: Slovak <vdr@linuxtv.org>\n"
@@ -612,9 +612,6 @@
 msgid "New folder"
 msgstr "Nov� zlo�ka"
 
-msgid "Sub folder"
-msgstr "Pod zlo�ka"
-
 msgid "Folder name already exists!"
 msgstr "N�zov zlo�ky u� existuje!"
 
@@ -730,6 +727,9 @@
 msgid "Can't switch channel!"
 msgstr "Kan�l nejde prepn��!"
 
+msgid "Schedule"
+msgstr "TV program"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "TV program - %s"
@@ -863,6 +863,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD (Menu na obrazovke)"
 
@@ -953,6 +959,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "P�sa� znaky ��seln�mi tla�idlami"
 
@@ -1387,9 +1396,6 @@
 msgid " Stop recording "
 msgstr " Zastavi� nahr�vanie "
 
-msgid "Schedule"
-msgstr "TV program"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Zastavi� prehr�vanie"
@@ -1597,15 +1603,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Ktor�ko�vek kl�vesa zru�� vypnutie"
 
-msgid "Switching primary DVB..."
-msgstr "Prep�na hlavn� DVB..."
-
 msgid "Editing process failed!"
 msgstr "Zostrih�vanie zlyhalo!"
 
 msgid "Editing process finished"
 msgstr "Proces zostrih�vania dokon�en�"
 
+msgid "Switching primary DVB..."
+msgstr "Prep�na hlavn� DVB..."
+
 msgid "Press any key to cancel restart"
 msgstr "ktor�ko�vek kl�vesa zru�� re�tart"
 
diff -ruN vdr-2.3.8/po/sl_SI.po vdr-2.3.9/po/sl_SI.po
--- vdr-2.3.8/po/sl_SI.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/sl_SI.po	2018-03-14 23:46:32.777353452 +0100
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2013-03-04 12:46+0100\n"
 "Last-Translator: Matjaz Thaler <matjaz.thaler@guest.arnes.si>\n"
 "Language-Team: Slovenian <vdr@linuxtv.org>\n"
@@ -612,9 +612,6 @@
 msgid "New folder"
 msgstr "Nov direktorij"
 
-msgid "Sub folder"
-msgstr "Pod direktorij"
-
 msgid "Folder name already exists!"
 msgstr "Ime direktorija �e obstaja"
 
@@ -730,6 +727,9 @@
 msgid "Can't switch channel!"
 msgstr "Ne morem preklopiti kanala!"
 
+msgid "Schedule"
+msgstr "Program"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Program - %s"
@@ -863,6 +863,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -953,6 +959,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "�tevilo tipk za znake"
 
@@ -1387,9 +1396,6 @@
 msgid " Stop recording "
 msgstr " Prekini snemanje "
 
-msgid "Schedule"
-msgstr "Program"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Prekini predvajanje"
@@ -1597,15 +1603,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Pritisnite katerikoli gumb za preklic izklopa"
 
-msgid "Switching primary DVB..."
-msgstr "Preklapljanje primarne DVB naprave..."
-
 msgid "Editing process failed!"
 msgstr "Napaka pri procesu urejanja!"
 
 msgid "Editing process finished"
 msgstr "Proces urejanja je kon�an"
 
+msgid "Switching primary DVB..."
+msgstr "Preklapljanje primarne DVB naprave..."
+
 msgid "Press any key to cancel restart"
 msgstr "Pritisni katerokoli tipko za preklic ponovnega zagona"
 
diff -ruN vdr-2.3.8/po/sr_RS.po vdr-2.3.9/po/sr_RS.po
--- vdr-2.3.8/po/sr_RS.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/sr_RS.po	2018-03-14 23:46:31.981365884 +0100
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2013-03-16 15:05+0100\n"
 "Last-Translator: Zoran Turalija <zoran.turalija@gmail.com>\n"
 "Language-Team: Serbian <vdr@linuxtv.org>\n"
@@ -612,9 +612,6 @@
 msgid "New folder"
 msgstr "Novi direktorijum"
 
-msgid "Sub folder"
-msgstr "Poddirektorijum"
-
 msgid "Folder name already exists!"
 msgstr "Naziv direktorijuma ve� postoji!"
 
@@ -730,6 +727,9 @@
 msgid "Can't switch channel!"
 msgstr "Nemogu�e promeniti kanal!"
 
+msgid "Schedule"
+msgstr "Raspored"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Raspored - %s"
@@ -863,6 +863,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -953,6 +959,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Pi�ite pomo�u numeri�kih dugmi�a (kao SMS)"
 
@@ -1387,9 +1396,6 @@
 msgid " Stop recording "
 msgstr " Zaustavi snimanje "
 
-msgid "Schedule"
-msgstr "Raspored"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Zaustavi reprodukciju"
@@ -1597,15 +1603,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Pritisnite bilo koje dugme da otka�ete isklju�enje"
 
-msgid "Switching primary DVB..."
-msgstr "Promena primarnog DVB ure�aja..."
-
 msgid "Editing process failed!"
 msgstr "Ure�ivanje neuspe�eno!"
 
 msgid "Editing process finished"
 msgstr "Ure�ivanje zavr�eno"
 
+msgid "Switching primary DVB..."
+msgstr "Promena primarnog DVB ure�aja..."
+
 msgid "Press any key to cancel restart"
 msgstr "Pritisnite bilo koje dugme da otka�ete ponovno pokretanje"
 
diff -ruN vdr-2.3.8/po/sv_SE.po vdr-2.3.9/po/sv_SE.po
--- vdr-2.3.8/po/sv_SE.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/sv_SE.po	2018-03-14 23:46:32.457358450 +0100
@@ -12,7 +12,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2015-02-12 21:58+0100\n"
 "Last-Translator: Magnus Sirvi� <sirwio@hotmail.com>\n"
 "Language-Team: Swedish <vdr@linuxtv.org>\n"
@@ -616,9 +616,6 @@
 msgid "New folder"
 msgstr "Ny mapp"
 
-msgid "Sub folder"
-msgstr "Undermapp"
-
 msgid "Folder name already exists!"
 msgstr "Mappnamnet finns redan!"
 
@@ -734,6 +731,9 @@
 msgid "Can't switch channel!"
 msgstr "Om�jligt att byta kanal!"
 
+msgid "Schedule"
+msgstr "Program"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Program - %s"
@@ -867,6 +867,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -957,6 +963,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Anv�nd sifferknappar f�r bokstavsinmatning"
 
@@ -1391,9 +1400,6 @@
 msgid " Stop recording "
 msgstr " Avsluta inspelning "
 
-msgid "Schedule"
-msgstr "Program"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Avsluta uppspelning"
@@ -1601,15 +1607,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Tryck valfri knapp f�r att �terkalla avst�ngningen"
 
-msgid "Switching primary DVB..."
-msgstr "Byter prim�r DVB enhet..."
-
 msgid "Editing process failed!"
 msgstr "Redigeringsprocessen misslyckades"
 
 msgid "Editing process finished"
 msgstr "Redigering avslutad"
 
+msgid "Switching primary DVB..."
+msgstr "Byter prim�r DVB enhet..."
+
 msgid "Press any key to cancel restart"
 msgstr "Tryck valfri knapp f�r att avbryta omstart"
 
diff -ruN vdr-2.3.8/po/tr_TR.po vdr-2.3.9/po/tr_TR.po
--- vdr-2.3.8/po/tr_TR.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/tr_TR.po	2018-03-14 23:46:32.509357638 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2008-02-28 00:33+0100\n"
 "Last-Translator: Oktay Yolge�en <oktay_73@yahoo.de>\n"
 "Language-Team: Turkish <vdr@linuxtv.org>\n"
@@ -611,9 +611,6 @@
 msgid "New folder"
 msgstr ""
 
-msgid "Sub folder"
-msgstr ""
-
 msgid "Folder name already exists!"
 msgstr ""
 
@@ -729,6 +726,9 @@
 msgid "Can't switch channel!"
 msgstr "Kanala de�i�tirelemiyor!"
 
+msgid "Schedule"
+msgstr "Program"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Program - %s"
@@ -862,6 +862,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -952,6 +958,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr ""
 
@@ -1386,9 +1395,6 @@
 msgid " Stop recording "
 msgstr " �ekimi bitir "
 
-msgid "Schedule"
-msgstr "Program"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " G�steri�i bitir"
@@ -1596,15 +1602,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Kapatmay� iptal etmek i�in herhangi bir tu�a bas"
 
-msgid "Switching primary DVB..."
-msgstr "Primer DVB aray�z� de�i�tiriliyor..."
-
 msgid "Editing process failed!"
 msgstr "Kesim ba�ar�s�z!"
 
 msgid "Editing process finished"
 msgstr "Kesim bitti"
 
+msgid "Switching primary DVB..."
+msgstr "Primer DVB aray�z� de�i�tiriliyor..."
+
 msgid "Press any key to cancel restart"
 msgstr "Yeniden ba�latmay� iptal etmek i�in herhangi bir tu�a bas"
 
diff -ruN vdr-2.3.8/po/uk_UA.po vdr-2.3.9/po/uk_UA.po
--- vdr-2.3.8/po/uk_UA.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/uk_UA.po	2018-03-14 23:46:32.333360387 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2015-02-13 18:14+0100\n"
 "Last-Translator: Yarema aka Knedlyk <yupadmin@gmail.com>\n"
 "Language-Team: Ukrainian <vdr@linuxtv.org>\n"
@@ -612,9 +612,6 @@
 msgid "New folder"
 msgstr "Нова тека"
 
-msgid "Sub folder"
-msgstr "Підтека"
-
 msgid "Folder name already exists!"
 msgstr "Назва теки вже існує!"
 
@@ -730,6 +727,9 @@
 msgid "Can't switch channel!"
 msgstr "Неможливо перемкнути канал!"
 
+msgid "Schedule"
+msgstr "Телегід"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "Програма - %s"
@@ -863,6 +863,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "Меню"
 
@@ -953,6 +959,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Кількість клавіш для символів"
 
@@ -1387,9 +1396,6 @@
 msgid " Stop recording "
 msgstr " Зупинити запис "
 
-msgid "Schedule"
-msgstr "Телегід"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " Зупинити програвання"
@@ -1597,15 +1603,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "Натисніть будь-яку кнопку для відміни виключення."
 
-msgid "Switching primary DVB..."
-msgstr "Зміна основного DVB-пристрою..."
-
 msgid "Editing process failed!"
 msgstr "Помилка під час монтажу запису!"
 
 msgid "Editing process finished"
 msgstr "Монтаж закінчено"
 
+msgid "Switching primary DVB..."
+msgstr "Зміна основного DVB-пристрою..."
+
 msgid "Press any key to cancel restart"
 msgstr "Натисніть будь-яку кнопку для відміни перезавантаження"
 
diff -ruN vdr-2.3.8/po/zh_CN.po vdr-2.3.9/po/zh_CN.po
--- vdr-2.3.8/po/zh_CN.po	2017-06-30 11:50:44.000000000 +0200
+++ vdr-2.3.9/po/zh_CN.po	2018-03-14 23:46:32.133363510 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2017-06-30 11:46+0200\n"
+"POT-Creation-Date: 2018-03-09 16:02+0100\n"
 "PO-Revision-Date: 2013-03-04 14:52+0800\n"
 "Last-Translator: NFVDR <nfvdr@live.com>\n"
 "Language-Team: Chinese (simplified) <nfvdr@live.com>\n"
@@ -613,9 +613,6 @@
 msgid "New folder"
 msgstr "新文件夹"
 
-msgid "Sub folder"
-msgstr "子文件夹"
-
 msgid "Folder name already exists!"
 msgstr "文件夹名称已经存在！"
 
@@ -731,6 +728,9 @@
 msgid "Can't switch channel!"
 msgstr "不能切换频道"
 
+msgid "Schedule"
+msgstr "任务列表"
+
 #, c-format
 msgid "Schedule - %s"
 msgstr "任务列表 - %s"
@@ -864,6 +864,12 @@
 msgid "by time"
 msgstr ""
 
+msgid "ascending"
+msgstr ""
+
+msgid "descending"
+msgstr ""
+
 msgid "OSD"
 msgstr "系统菜单设置"
 
@@ -954,6 +960,9 @@
 msgid "Setup.OSD$Default sort mode for recordings"
 msgstr ""
 
+msgid "Setup.OSD$Sorting direction for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "数字键的字符"
 
@@ -1388,9 +1397,6 @@
 msgid " Stop recording "
 msgstr " 是否停止录像? "
 
-msgid "Schedule"
-msgstr "任务列表"
-
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
 msgstr " 停止回放"
@@ -1598,15 +1604,15 @@
 msgid "Press any key to cancel shutdown"
 msgstr "按下任何键即可取消关机"
 
-msgid "Switching primary DVB..."
-msgstr "切换主要的DVB设备."
-
 msgid "Editing process failed!"
 msgstr "编辑过程失败"
 
 msgid "Editing process finished"
 msgstr "编辑过程完毕"
 
+msgid "Switching primary DVB..."
+msgstr "切换主要的DVB设备."
+
 msgid "Press any key to cancel restart"
 msgstr "请按任意键重启"
 
diff -ruN vdr-2.3.8/recording.c vdr-2.3.9/recording.c
--- vdr-2.3.8/recording.c	2017-06-25 14:31:46.000000000 +0200
+++ vdr-2.3.9/recording.c	2018-03-17 11:56: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 4.10 2017/06/25 12:31:46 kls Exp $
+ * $Id: recording.c 4.22 2018/03/17 10:56:13 kls Exp $
  */
 
 #include "recording.h"
@@ -57,6 +57,7 @@
 #define MARKSFILESUFFIX   "/marks"
 
 #define SORTMODEFILE      ".sort"
+#define TIMERRECFILE      ".timer"
 
 #define MINDISKSPACE 1024 // MB
 
@@ -117,7 +118,7 @@
          r = DeletedRecordings->Next(r);
          }
      if (deleted) {
-        const char *IgnoreFiles[] = { SORTMODEFILE, NULL };
+        const char *IgnoreFiles[] = { SORTMODEFILE, TIMERRECFILE, NULL };
         cVideoDirectory::RemoveEmptyVideoDirectories(IgnoreFiles);
         }
      }
@@ -962,8 +963,8 @@
      // To have folders sorted before plain recordings, the '/' s1 points to
      // is replaced by the character '1'. All other slashes will be replaced
      // by '0' in SortName() (see below), which will result in the desired
-     // sequence:
-     *s1 = '1';
+     // sequence ('0' and '1' are reversed in case of rsdDescending):
+     *s1 = (Setup.RecSortingDirection == rsdAscending) ? '1' : '0';
      if (Strip) {
         s1++;
         memmove(s1, s2, t - s2 + 1);
@@ -986,7 +987,7 @@
         char *s = strdup(FileName() + strlen(cVideoDirectory::Name()));
         if (RecordingsSortMode != rsmName || Setup.AlwaysSortFoldersFirst)
            s = StripEpisodeName(s, RecordingsSortMode != rsmName);
-        strreplace(s, '/', '0'); // some locales ignore '/' when sorting
+        strreplace(s, '/', (Setup.RecSortingDirection == rsdAscending) ? '0' : '1'); // some locales ignore '/' when sorting
         int l = strxfrm(NULL, s, 0) + 1;
         *sb = MALLOC(char, l);
         strxfrm(*sb, s, l);
@@ -1020,7 +1021,10 @@
 int cRecording::Compare(const cListObject &ListObject) const
 {
   cRecording *r = (cRecording *)&ListObject;
-  return strcasecmp(SortName(), r->SortName());
+  if (Setup.RecSortingDirection == rsdAscending)
+     return strcasecmp(SortName(), r->SortName());
+  else
+     return strcasecmp(r->SortName(), SortName());
 }
 
 bool cRecording::IsInPath(const char *Path) const
@@ -1234,7 +1238,10 @@
      free(name);
      name = strdup(NewName);
      cString NewFileName = FileName();
-     if (!(MakeDirs(NewFileName, true) && cVideoDirectory::MoveVideoFile(OldFileName, NewFileName))) {
+     bool Exists = access(NewFileName, F_OK) == 0;
+     if (Exists)
+        esyslog("ERROR: recording '%s' already exists", NewName);
+     if (Exists || !(MakeDirs(NewFileName, true) && cVideoDirectory::MoveVideoFile(OldFileName, NewFileName))) {
         free(name);
         name = strdup(OldName);
         free(fileName);
@@ -1494,7 +1501,6 @@
   TouchFile(UpdateFileName());
   if (!needsUpdate)
      lastUpdate = time(NULL); // make sure we don't trigger ourselves
-  BroadcastSVDRPCommand("UPDR");
 }
 
 bool cRecordings::NeedsUpdate(void)
@@ -1679,7 +1685,6 @@
 public:
   cDirCopier(const char *DirNameSrc, const char *DirNameDst);
   virtual ~cDirCopier();
-  void Stop(void);
   bool Error(void) { return error; }
   };
 
@@ -1694,7 +1699,7 @@
 
 cDirCopier::~cDirCopier()
 {
-  Stop();
+  Cancel(3);
 }
 
 bool cDirCopier::Throttled(void)
@@ -1725,6 +1730,7 @@
         int From = -1;
         int To = -1;
         size_t BufferSize = BUFSIZ;
+        uchar *Buffer = NULL;
         while (Running()) {
               // Suspend copying if we have severe throughput problems:
               if (Throttled()) {
@@ -1734,8 +1740,11 @@
               // Copy all files in the source directory to the destination directory:
               if (e) {
                  // We're currently copying a file:
-                 uchar Buffer[BufferSize];
-                 size_t Read = safe_read(From, Buffer, sizeof(Buffer));
+                 if (!Buffer) {
+                    esyslog("ERROR: no buffer");
+                    break;
+                    }
+                 size_t Read = safe_read(From, Buffer, BufferSize);
                  if (Read > 0) {
                     size_t Written = safe_write(To, Buffer, Read);
                     if (Written != Read) {
@@ -1784,7 +1793,14 @@
                     break;
                     }
                  dsyslog("copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
-                 BufferSize = max(size_t(st.st_blksize * 10), size_t(BUFSIZ));
+                 if (!Buffer) {
+                    BufferSize = max(size_t(st.st_blksize * 10), size_t(BUFSIZ));
+                    Buffer = MALLOC(uchar, BufferSize);
+                    if (!Buffer) {
+                       esyslog("ERROR: out of memory");
+                       break;
+                       }
+                    }
                  if (access(FileNameDst, F_OK) == 0) {
                     esyslog("ERROR: destination file '%s' already exists", *FileNameDst);
                     break;
@@ -1801,14 +1817,16 @@
                  }
               else {
                  // We're done:
+                 free(Buffer);
                  dsyslog("done copying directory '%s' to '%s'", *dirNameSrc, *dirNameDst);
                  error = false;
                  return;
                  }
               }
+        free(Buffer);
         close(From); // just to be absolutely sure
         close(To);
-        esyslog("ERROR: copying directory '%s' to '%s' ended prematurely", *dirNameSrc, *dirNameDst);
+        isyslog("copying directory '%s' to '%s' ended prematurely", *dirNameSrc, *dirNameDst);
         }
      else
         esyslog("ERROR: can't open '%s'", *dirNameSrc);
@@ -1817,17 +1835,6 @@
      esyslog("ERROR: can't access '%s'", *dirNameDst);
 }
 
-void cDirCopier::Stop(void)
-{
-  Cancel(3);
-  if (error) {
-     cVideoDirectory::RemoveVideoFile(dirNameDst);
-     LOCK_RECORDINGS_WRITE;
-     Recordings->AddByName(dirNameSrc);
-     Recordings->DelByName(dirNameDst);
-     }
-}
-
 // --- cRecordingsHandlerEntry -----------------------------------------------
 
 class cRecordingsHandlerEntry : public cListObject {
@@ -1837,14 +1844,18 @@
   cString fileNameDst;
   cCutter *cutter;
   cDirCopier *copier;
+  bool error;
   void ClearPending(void) { usage &= ~ruPending; }
 public:
   cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst);
   ~cRecordingsHandlerEntry();
   int Usage(const char *FileName = NULL) const;
+  bool Error(void) const { return error; }
+  void SetCanceled(void) { usage |= ruCanceled; }
   const char *FileNameSrc(void) const { return fileNameSrc; }
   const char *FileNameDst(void) const { return fileNameDst; }
-  bool Active(bool &Error);
+  bool Active(cRecordings *Recordings);
+  void Cleanup(cRecordings *Recordings);
   };
 
 cRecordingsHandlerEntry::cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
@@ -1854,6 +1865,7 @@
   fileNameDst = FileNameDst;
   cutter = NULL;
   copier = NULL;
+  error = false;
 }
 
 cRecordingsHandlerEntry::~cRecordingsHandlerEntry()
@@ -1874,22 +1886,22 @@
   return u;
 }
 
-bool cRecordingsHandlerEntry::Active(bool &Error)
+bool cRecordingsHandlerEntry::Active(cRecordings *Recordings)
 {
-  bool CopierFinishedOk = false;
+  if ((usage & ruCanceled) != 0)
+     return false;
   // First test whether there is an ongoing operation:
   if (cutter) {
      if (cutter->Active())
         return true;
-     Error |= cutter->Error();
+     error = cutter->Error();
      delete cutter;
      cutter = NULL;
      }
   else if (copier) {
      if (copier->Active())
         return true;
-     Error |= copier->Error();
-     CopierFinishedOk = !copier->Error();
+     error = copier->Error();
      delete copier;
      copier = NULL;
      }
@@ -1898,28 +1910,52 @@
      if ((Usage() & ruCut) != 0) {
         cutter = new cCutter(FileNameSrc());
         cutter->Start();
+        Recordings->AddByName(FileNameDst(), false);
         }
      else if ((Usage() & (ruMove | ruCopy)) != 0) {
         copier = new cDirCopier(FileNameSrc(), FileNameDst());
         copier->Start();
         }
      ClearPending();
-     LOCK_RECORDINGS_WRITE; // to trigger a state change
+     Recordings->SetModified(); // to trigger a state change
      return true;
      }
-  // Clean up:
-  if (CopierFinishedOk && (Usage() & ruMove) != 0) {
+  // We're done:
+  if (!error && (usage & ruMove) != 0) {
      cRecording Recording(FileNameSrc());
-     if (Recording.Delete()) {
-        LOCK_RECORDINGS_WRITE;
+     if (Recording.Delete())
         Recordings->DelByName(Recording.FileName());
-        }
      }
-  LOCK_RECORDINGS_WRITE; // to trigger a state change
+  Recordings->SetModified(); // to trigger a state change
   Recordings->TouchUpdate();
   return false;
 }
 
+void cRecordingsHandlerEntry::Cleanup(cRecordings *Recordings)
+{
+  if ((usage & ruCut)) {          // this was a cut operation...
+     if (cutter) {                // ...which had not yet ended
+        delete cutter;
+        cutter = NULL;
+        cVideoDirectory::RemoveVideoFile(fileNameDst);
+        Recordings->DelByName(fileNameDst);
+        }
+     }
+  if ((usage & (ruMove | ruCopy)) // this was a move/copy operation...
+     && ((usage & ruPending)      // ...which had not yet started...
+        || copier                 // ...or not yet finished...
+        || error)) {              // ...or finished with error
+     if (copier) {
+        delete copier;
+        copier = NULL;
+        }
+     cVideoDirectory::RemoveVideoFile(fileNameDst);
+     if ((usage & ruMove) != 0)
+        Recordings->AddByName(fileNameSrc);
+     Recordings->DelByName(fileNameDst);
+     }
+}
+
 // --- cRecordingsHandler ----------------------------------------------------
 
 cRecordingsHandler RecordingsHandler;
@@ -1941,10 +1977,15 @@
   while (Running()) {
         bool Sleep = false;
         {
+          LOCK_RECORDINGS_WRITE;
+          Recordings->SetExplicitModify();
           cMutexLock MutexLock(&mutex);
           if (cRecordingsHandlerEntry *r = operations.First()) {
-             if (!r->Active(error))
+             if (!r->Active(Recordings)) {
+                error |= r->Error();
+                r->Cleanup(Recordings);
                 operations.Del(r);
+                }
              else
                 Sleep = true;
              }
@@ -1960,6 +2001,8 @@
 {
   if (FileName && *FileName) {
      for (cRecordingsHandlerEntry *r = operations.First(); r; r = operations.Next(r)) {
+         if ((r->Usage() & ruCanceled) != 0)
+            continue;
          if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
             return r;
          }
@@ -2002,16 +2045,14 @@
 {
   cMutexLock MutexLock(&mutex);
   if (cRecordingsHandlerEntry *r = Get(FileName))
-     operations.Del(r);
+     r->SetCanceled();
 }
 
 void cRecordingsHandler::DelAll(void)
 {
-  {
-    cMutexLock MutexLock(&mutex);
-    operations.Clear();
-  }
-  Cancel(3);
+  cMutexLock MutexLock(&mutex);
+  for (cRecordingsHandlerEntry *r = operations.First(); r; r = operations.Next(r))
+      r->SetCanceled();
 }
 
 int cRecordingsHandler::GetUsage(const char *FileName)
@@ -2149,8 +2190,8 @@
   cIndexFile IndexFile(recordingFileName, false, isPesRecording);
   for (cMark *m = First(); m; m = Next(m)) {
       int p = IndexFile.GetClosestIFrame(m->Position());
-      if (int d = m->Position() - p) {
-         isyslog("aligned editing mark %s to %s (off by %d frame%s)", *IndexToHMSF(m->Position(), true, framesPerSecond), *IndexToHMSF(p, true, framesPerSecond), d, abs(d) > 1 ? "s" : "");
+      if (m->Position() - p) {
+         //isyslog("aligned editing mark %s to %s (off by %d frame%s)", *IndexToHMSF(m->Position(), true, framesPerSecond), *IndexToHMSF(p, true, framesPerSecond), m->Position() - p, abs(m->Position() - p) > 1 ? "s" : "");
          m->SetPosition(p);
          }
       }
@@ -3081,3 +3122,38 @@
      RecordingsSortMode = eRecordingsSortMode(0);
   SetRecordingsSortMode(Directory, RecordingsSortMode);
 }
+
+// --- Recording Timer Indicator ---------------------------------------------
+
+void SetRecordingTimerId(const char *Directory, const char *TimerId)
+{
+  cString FileName = AddDirectory(Directory, TIMERRECFILE);
+  if (TimerId) {
+     dsyslog("writing timer id '%s' to %s", TimerId, *FileName);
+     if (FILE *f = fopen(FileName, "w")) {
+        fprintf(f, "%s\n", TimerId);
+        fclose(f);
+        }
+     else
+        LOG_ERROR_STR(*FileName);
+     }
+  else {
+     dsyslog("removing %s", *FileName);
+     unlink(FileName);
+     }
+}
+
+cString GetRecordingTimerId(const char *Directory)
+{
+  cString FileName = AddDirectory(Directory, TIMERRECFILE);
+  const char *Id = NULL;
+  if (FILE *f = fopen(FileName, "r")) {
+     char buf[HOST_NAME_MAX + 10]; // +10 for numeric timer id and '@'
+     if (fgets(buf, sizeof(buf), f)) {
+        stripspace(buf);
+        Id = buf;
+        }
+     fclose(f);
+     }
+  return Id;
+}
diff -ruN vdr-2.3.8/recording.h vdr-2.3.9/recording.h
--- vdr-2.3.8/recording.h	2017-04-03 15:31:16.000000000 +0200
+++ vdr-2.3.9/recording.h	2018-02-12 13:28:24.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 4.5 2017/04/03 13:31:16 kls Exp $
+ * $Id: recording.h 4.8 2018/02/12 12:28:24 kls Exp $
  */
 
 #ifndef __RECORDING_H
@@ -38,6 +38,7 @@
   ruDst      = 0x0040, // the recording is the destination of a cut, move or copy process
   //
   ruPending  = 0x0080, // the recording is pending a cut, move or copy process
+  ruCanceled = 0x8000, // the operation has been canceled, waiting for cleanup
   };
 
 void RemoveDeletedRecordings(void);
@@ -526,6 +527,7 @@
        ///< complete, and will be updated if it isn't. Otherwise an existing index
        ///< file will be removed before a new one is generated.
 
+enum eRecordingsSortDir { rsdAscending, rsdDescending };
 enum eRecordingsSortMode { rsmName, rsmTime };
 extern eRecordingsSortMode RecordingsSortMode;
 bool HasRecordingsSortMode(const char *Directory);
@@ -533,4 +535,7 @@
 void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode);
 void IncRecordingsSortMode(const char *Directory);
 
+void SetRecordingTimerId(const char *Directory, const char *TimerId);
+cString GetRecordingTimerId(const char *Directory);
+
 #endif //__RECORDING_H
diff -ruN vdr-2.3.8/skinlcars.c vdr-2.3.9/skinlcars.c
--- vdr-2.3.8/skinlcars.c	2017-06-23 17:52:03.000000000 +0200
+++ vdr-2.3.9/skinlcars.c	2017-11-08 11:10:30.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skinlcars.c 4.5 2017/06/23 15:52:03 kls Exp $
+ * $Id: skinlcars.c 4.6 2017/11/08 10:10:30 kls Exp $
  */
 
 // "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures,
@@ -715,6 +715,7 @@
   int lastLiveIndicatorY;
   bool lastLiveIndicatorTransferring;
   const cChannel *lastChannel;
+  cString lastChannelName;
   const cEvent *lastEvent;
   const cRecording *lastRecording;
   cString lastHeader;
@@ -1426,12 +1427,13 @@
      }
   if (!Channel)
      return;
-  if (initial || Channel != lastChannel) {
+  if (initial || Channel != lastChannel || strcmp(Channel->Name(), lastChannelName)) {
      osd->DrawText(xa00, yt00, itoa(Channel->Number()), Theme.Color(clrChannelFrameFg), Theme.Color(clrChannelFrameBg), tallFont, xa02 - xa00, yt02 - yt00, taTop | taRight | taBorder);
      osd->DrawText(xa03, yt00, Channel->Name(), Theme.Color(clrChannelName), Theme.Color(clrBackground), tallFont, xd00 - xa03, yd01 - yd00, taTop | taLeft);
      int x = xa00 + (yc03 - yc02); // compensate for the arc
      osd->DrawText(x, yc00, cSource::ToString(Channel->Source()), Theme.Color(clrChannelFrameFg), Theme.Color(clrChannelFrameBg), cFont::GetFont(fontOsd), xa02 - x, yc03 - yc00, taTop | taRight | taBorder);
      lastChannel = Channel;
+     lastChannelName = Channel->Name();
      DrawSeen(0, 0);
      }
   // The current programme:
diff -ruN vdr-2.3.8/skins.h vdr-2.3.9/skins.h
--- vdr-2.3.8/skins.h	2017-06-25 12:02:09.000000000 +0200
+++ vdr-2.3.9/skins.h	2017-11-02 16:04:56.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 4.4 2017/06/25 10:02:09 kls Exp $
+ * $Id: skins.h 4.5 2017/11/02 15:04:56 kls Exp $
  */
 
 #ifndef __SKINS_H
@@ -244,8 +244,10 @@
        ///< If the skin displays the Event item in its own way, it shall return true.
        ///< The default implementation does nothing and returns false, which results in
        ///< a call to SetItem() with a proper text.
-#define DEPRECATED_SKIN_SETITEMEVENT
-#ifdef DEPRECATED_SKIN_SETITEMEVENT
+#ifndef DEPRECATED_SKIN_SETITEMEVENT
+#define DEPRECATED_SKIN_SETITEMEVENT 1
+#endif
+#if DEPRECATED_SKIN_SETITEMEVENT
   virtual bool SetItemEvent(const cEvent *Event, int Index, bool Current, bool Selectable, const cChannel *Channel, bool WithDate, eTimerMatch TimerMatch) { return SetItemEvent(Event, Index, Current, Selectable, Channel, WithDate, TimerMatch, true); }
        ///< This function is here for comaptibility with older plugins and may be removed
        ///< in a future version. Use the above version of SetItemEvent() with the TimerActive
diff -ruN vdr-2.3.8/status.c vdr-2.3.9/status.c
--- vdr-2.3.8/status.c	2014-01-25 11:47:39.000000000 +0100
+++ vdr-2.3.9/status.c	2018-01-29 14:36:53.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 4.0 2014/01/25 10:47:39 kls Exp $
+ * $Id: status.c 4.1 2018/01/29 13:36:53 kls Exp $
  */
 
 #include "status.h"
@@ -53,6 +53,12 @@
       sm->Replaying(Control, Name, FileName, On);
 }
 
+void cStatus::MsgMarksModified(const cMarks* Marks)
+{
+  for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
+      sm->MarksModified(Marks);
+}
+
 void cStatus::MsgSetVolume(int Volume, bool Absolute)
 {
   for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
diff -ruN vdr-2.3.8/status.h vdr-2.3.9/status.h
--- vdr-2.3.8/status.h	2017-06-23 11:08:24.000000000 +0200
+++ vdr-2.3.9/status.h	2018-01-29 14:42:17.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 4.3 2017/06/23 09:08:24 kls Exp $
+ * $Id: status.h 4.4 2018/01/29 13:42:17 kls Exp $
  */
 
 #ifndef __STATUS_H
@@ -59,6 +59,11 @@
                // a name, Name can be a string that identifies the player type (like, e.g., "DVD").
                // 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 MarksModified(const cMarks *Marks) {}
+               // If the editing marks of the recording that is currently being played
+               // are modified in any way, this function is called with the list of
+               // Marks. If Marks is NULL, the editing marks for the currently played
+               // recording have been deleted entirely.
   virtual void SetVolume(int Volume, bool Absolute) {}
                // The volume has been set to the given value, either
                // absolutely or relative to the current volume.
@@ -103,6 +108,7 @@
   static void MsgChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView);
   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 MsgMarksModified(const cMarks* Marks);
   static void MsgSetVolume(int Volume, bool Absolute);
   static void MsgSetAudioTrack(int Index, const char * const *Tracks);
   static void MsgSetAudioChannel(int AudioChannel);
diff -ruN vdr-2.3.8/svdrp.c vdr-2.3.9/svdrp.c
--- vdr-2.3.8/svdrp.c	2017-06-30 11:49:39.000000000 +0200
+++ vdr-2.3.9/svdrp.c	2018-03-18 11:43:53.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 4.22 2017/06/30 09:49:39 kls Exp $
+ * $Id: svdrp.c 4.36 2018/03/18 10:43:53 kls Exp $
  */
 
 #include "svdrp.h"
@@ -37,7 +37,6 @@
 #include "recording.h"
 #include "remote.h"
 #include "skins.h"
-#include "thread.h"
 #include "timers.h"
 #include "videodir.h"
 
@@ -48,6 +47,13 @@
 static int SVDRPTcpPort = 0;
 static int SVDRPUdpPort = 0;
 
+enum eSvdrpFetchFlags {
+  sffNone   = 0b00000000,
+  sffConn   = 0b00000001,
+  sffPing   = 0b00000010,
+  sffTimers = 0b00000100,
+  };
+
 // --- cIpAddress ------------------------------------------------------------
 
 class cIpAddress {
@@ -98,7 +104,6 @@
   bool tcp;
   int sock;
   cIpAddress lastIpAddress;
-  bool IsOwnInterface(sockaddr_in *Addr);
 public:
   cSocket(int Port, bool Tcp);
   ~cSocket();
@@ -107,7 +112,7 @@
   void Close(void);
   int Port(void) const { return port; }
   int Socket(void) const { return sock; }
-  static bool SendDgram(const char *Dgram, int Port, const char *Address = NULL);
+  static bool SendDgram(const char *Dgram, int Port);
   int Accept(void);
   cString Discover(void);
   const cIpAddress *LastIpAddress(void) const { return &lastIpAddress; }
@@ -125,30 +130,6 @@
   Close();
 }
 
-bool cSocket::IsOwnInterface(sockaddr_in *Addr)
-{
-  ifaddrs *ifaddr;
-  if (getifaddrs(&ifaddr) >= 0) {
-     bool Own = false;
-     for (ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
-         if (ifa->ifa_addr) {
-            if (ifa->ifa_addr->sa_family == AF_INET) {
-               sockaddr_in *addr = (sockaddr_in *)ifa->ifa_addr;
-               if (addr->sin_addr.s_addr == Addr->sin_addr.s_addr) {
-                  Own = true;
-                  break;
-                  }
-               }
-            }
-         }
-     freeifaddrs(ifaddr);
-     return Own;
-     }
-  else
-     LOG_ERROR;
-  return false;
-}
-
 void cSocket::Close(void)
 {
   if (sock >= 0) {
@@ -160,6 +141,7 @@
 bool cSocket::Listen(void)
 {
   if (sock < 0) {
+     isyslog("SVDRP %s opening port %d/%s", Setup.SVDRPHostName, port, tcp ? "tcp" : "udp");
      // create socket:
      sock = tcp ? socket(PF_INET, SOCK_STREAM, IPPROTO_IP) : socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
      if (sock < 0) {
@@ -198,7 +180,7 @@
            return false;
            }
         }
-     isyslog("SVDRP listening on port %d/%s", port, tcp ? "tcp" : "udp");
+     isyslog("SVDRP %s listening on port %d/%s", Setup.SVDRPHostName, port, tcp ? "tcp" : "udp");
      }
   return true;
 }
@@ -234,13 +216,14 @@
         LOG_ERROR;
         return false;
         }
-     isyslog("SVDRP > %s:%d server connection established", Address, port);
+     dbgsvdrp("> %s:%d server connection established\n", Address, port);
+     isyslog("SVDRP %s > %s:%d server connection established", Setup.SVDRPHostName, Address, port);
      return true;
      }
   return false;
 }
 
-bool cSocket::SendDgram(const char *Dgram, int Port, const char *Address)
+bool cSocket::SendDgram(const char *Dgram, int Port)
 {
   // Create a socket:
   int Socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
@@ -248,23 +231,22 @@
      LOG_ERROR;
      return false;
      }
-  if (!Address) {
-     // Enable broadcast:
-     int One = 1;
-     if (setsockopt(Socket, SOL_SOCKET, SO_BROADCAST, &One, sizeof(One)) < 0) {
-        LOG_ERROR;
-        close(Socket);
-        return false;
-        }
+  // Enable broadcast:
+  int One = 1;
+  if (setsockopt(Socket, SOL_SOCKET, SO_BROADCAST, &One, sizeof(One)) < 0) {
+     LOG_ERROR;
+     close(Socket);
+     return false;
      }
   // Configure port and ip:
   sockaddr_in Addr;
   memset(&Addr, 0, sizeof(Addr));
   Addr.sin_family = AF_INET;
-  Addr.sin_addr.s_addr = Address ? inet_addr(Address) : htonl(INADDR_BROADCAST);
+  Addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
   Addr.sin_port = htons(Port);
   // Send datagram:
-  dsyslog("SVDRP > %s:%d send dgram '%s'", inet_ntoa(Addr.sin_addr), Port, Dgram);
+  dbgsvdrp("> %s:%d %s\n", inet_ntoa(Addr.sin_addr), Port, Dgram);
+  dsyslog("SVDRP %s > %s:%d send dgram '%s'", Setup.SVDRPHostName, inet_ntoa(Addr.sin_addr), Port, Dgram);
   int Length = strlen(Dgram);
   int Sent = sendto(Socket, Dgram, Length, 0, (sockaddr *)&Addr, sizeof(Addr));
   if (Sent < 0)
@@ -289,7 +271,8 @@
            NewSock = -1;
            }
         lastIpAddress.Set((sockaddr *)&Addr);
-        isyslog("SVDRP < %s client connection %s", lastIpAddress.Connection(), Accepted ? "accepted" : "DENIED");
+        dbgsvdrp("< %s client connection %s\n", lastIpAddress.Connection(), Accepted ? "accepted" : "DENIED");
+        isyslog("SVDRP %s < %s client connection %s", Setup.SVDRPHostName, lastIpAddress.Connection(), Accepted ? "accepted" : "DENIED");
         }
      else if (FATALERRNO)
         LOG_ERROR;
@@ -307,17 +290,18 @@
      int NumBytes = recvfrom(sock, buf, sizeof(buf), 0, (sockaddr *)&Addr, &Size);
      if (NumBytes >= 0) {
         buf[NumBytes] = 0;
-        if (!IsOwnInterface(&Addr)) {
-           lastIpAddress.Set((sockaddr *)&Addr);
-           if (!SVDRPhosts.Acceptable(Addr.sin_addr.s_addr)) {
-              dsyslog("SVDRP < %s discovery ignored (%s)", lastIpAddress.Connection(), buf);
-              return NULL;
-              }
-           if (!startswith(buf, "SVDRP:discover")) {
-              dsyslog("SVDRP < %s discovery unrecognized (%s)", lastIpAddress.Connection(), buf);
-              return NULL;
-              }
-           isyslog("SVDRP < %s discovery received (%s)", lastIpAddress.Connection(), buf);
+        lastIpAddress.Set((sockaddr *)&Addr);
+        if (!SVDRPhosts.Acceptable(Addr.sin_addr.s_addr)) {
+           dsyslog("SVDRP %s < %s discovery ignored (%s)", Setup.SVDRPHostName, lastIpAddress.Connection(), buf);
+           return NULL;
+           }
+        if (!startswith(buf, "SVDRP:discover")) {
+           dsyslog("SVDRP %s < %s discovery unrecognized (%s)", Setup.SVDRPHostName, lastIpAddress.Connection(), buf);
+           return NULL;
+           }
+        if (strcmp(strgetval(buf, "name", ':'), Setup.SVDRPHostName) != 0) { // ignore our own broadcast
+           dbgsvdrp("< %s discovery received (%s)\n", lastIpAddress.Connection(), buf);
+           isyslog("SVDRP %s < %s discovery received (%s)", Setup.SVDRPHostName, lastIpAddress.Connection(), buf);
            return buf;
            }
         }
@@ -331,51 +315,60 @@
 
 class cSVDRPClient {
 private:
-  cIpAddress ipAddress;
+  cIpAddress serverIpAddress;
   cSocket socket;
   cString serverName;
+  int length;
+  char *input;
   int timeout;
   cTimeMs pingTime;
   cFile file;
   int fetchFlags;
+  bool connected;
+  bool Send(const char *Command);
   void Close(void);
 public:
   cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout);
   ~cSVDRPClient();
   const char *ServerName(void) const { return serverName; }
-  const char *Connection(void) const { return ipAddress.Connection(); }
+  const char *Connection(void) const { return serverIpAddress.Connection(); }
   bool HasAddress(const char *Address, int Port) const;
-  bool Send(const char *Command);
   bool Process(cStringList *Response = NULL);
   bool Execute(const char *Command, cStringList *Response = NULL);
-  void SetFetchFlag(eSvdrpFetchFlags Flag);
-  bool HasFetchFlag(eSvdrpFetchFlags Flag);
+  bool Connected(void) const { return connected; }
+  void SetFetchFlag(int Flag);
+  bool HasFetchFlag(int Flag);
+  bool GetRemoteTimers(cStringList &Response);
   };
 
 static cPoller SVDRPClientPoller;
 
 cSVDRPClient::cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout)
-:ipAddress(Address, Port)
+:serverIpAddress(Address, Port)
 ,socket(Port, true)
 {
   serverName = ServerName;
+  length = BUFSIZ;
+  input = MALLOC(char, length);
   timeout = Timeout * 1000 * 9 / 10; // ping after 90% of timeout
   pingTime.Set(timeout);
-  fetchFlags = sffTimers;
+  fetchFlags = sffNone;
+  connected = false;
   if (socket.Connect(Address)) {
      if (file.Open(socket.Socket())) {
         SVDRPClientPoller.Add(file, false);
-        dsyslog("SVDRP > %s client created for '%s'", ipAddress.Connection(), *serverName);
+        dsyslog("SVDRP %s > %s client created for '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
         return;
         }
      }
-  esyslog("SVDRP > %s ERROR: failed to create client for '%s'", ipAddress.Connection(), *serverName);
+  esyslog("SVDRP %s > %s ERROR: failed to create client for '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
 }
 
 cSVDRPClient::~cSVDRPClient()
 {
   Close();
-  dsyslog("SVDRP > %s client destroyed for '%s'", ipAddress.Connection(), *serverName);
+  free(input);
+  dsyslog("SVDRP %s > %s client destroyed for '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
 }
 
 void cSVDRPClient::Close(void)
@@ -384,21 +377,18 @@
      SVDRPClientPoller.Del(file, false);
      file.Close();
      socket.Close();
-     LOCK_TIMERS_WRITE;
-     if (Timers)
-        Timers->DelRemoteTimers(serverName);
      }
 }
 
 bool cSVDRPClient::HasAddress(const char *Address, int Port) const
 {
-  return strcmp(ipAddress.Address(), Address) == 0 && ipAddress.Port() == Port;
+  return strcmp(serverIpAddress.Address(), Address) == 0 && serverIpAddress.Port() == Port;
 }
 
 bool cSVDRPClient::Send(const char *Command)
 {
   pingTime.Set(timeout);
-  dbgsvdrp("> %s: %s\n", *serverName, Command);
+  dbgsvdrp("> C %s: %s\n", *serverName, Command);
   if (safe_write(file, Command, strlen(Command) + 1) < 0) {
      LOG_ERROR;
      return false;
@@ -409,7 +399,6 @@
 bool cSVDRPClient::Process(cStringList *Response)
 {
   if (file.IsOpen()) {
-     char input[BUFSIZ];
      int numChars = 0;
 #define SVDRPResonseTimeout 5000 // ms
      cTimeMs Timeout(SVDRPResonseTimeout);
@@ -424,12 +413,9 @@
                         input[--numChars] = 0;
                   // make sure the string is terminated:
                   input[numChars] = 0;
-                  dbgsvdrp("< %s: %s\n", *serverName, input);
-                  if (Response) {
+                  dbgsvdrp("< C %s: %s\n", *serverName, input);
+                  if (Response)
                      Response->Append(strdup(input));
-                     if (numChars >= 4 && input[3] != '-') // no more lines will follow
-                        break;
-                     }
                   else {
                      switch (atoi(input)) {
                        case 220: if (numChars > 4) {
@@ -438,23 +424,35 @@
                                        *t = 0;
                                        if (strcmp(n, serverName) != 0) {
                                           serverName = n;
-                                          dsyslog("SVDRP < %s remote server name is '%s'", ipAddress.Connection(), *serverName);
+                                          dsyslog("SVDRP %s < %s remote server name is '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
                                           }
+                                       SetFetchFlag(sffConn | sffTimers);
+                                       connected = true;
                                        }
                                     }
                                  break;
-                       case 221: dsyslog("SVDRP < %s remote server closed connection to '%s'", ipAddress.Connection(), *serverName);
+                       case 221: dsyslog("SVDRP %s < %s remote server closed connection to '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
+                                 connected = false;
                                  Close();
                                  break;
                        }
                      }
+                  if (numChars >= 4 && input[3] != '-') // no more lines will follow
+                     break;
                   numChars = 0;
                   }
                else {
-                  if (numChars >= int(sizeof(input))) {
-                     esyslog("SVDRP < %s ERROR: out of memory", ipAddress.Connection());
-                     Close();
-                     break;
+                  if (numChars >= length - 1) {
+                     int NewLength = length + BUFSIZ;
+                     if (char *NewBuffer = (char *)realloc(input, NewLength)) {
+                        length = NewLength;
+                        input = NewBuffer;
+                        }
+                     else {
+                        esyslog("SVDRP %s < %s ERROR: out of memory", Setup.SVDRPHostName, serverIpAddress.Connection());
+                        Close();
+                        break;
+                        }
                      }
                   input[numChars++] = c;
                   input[numChars] = 0;
@@ -462,20 +460,20 @@
                Timeout.Set(SVDRPResonseTimeout);
                }
             else if (r <= 0) {
-               isyslog("SVDRP < %s lost connection to remote server '%s'", ipAddress.Connection(), *serverName);
+               isyslog("SVDRP %s < %s lost connection to remote server '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
                Close();
                return false;
                }
             }
-         else if (!Response)
-            break;
          else if (Timeout.TimedOut()) {
-            esyslog("SVDRP < %s timeout while waiting for response from '%s'", ipAddress.Connection(), *serverName);
+            esyslog("SVDRP %s < %s timeout while waiting for response from '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
             return false;
             }
+         else if (!Response && numChars == 0)
+            break; // we read all or nothing!
          }
      if (pingTime.TimedOut())
-        Execute("PING");
+        SetFetchFlag(sffPing);
      }
   return file.IsOpen();
 }
@@ -487,26 +485,114 @@
   return Send(Command) && Process(Response);
 }
 
-void cSVDRPClient::SetFetchFlag(eSvdrpFetchFlags Flags)
+void cSVDRPClient::SetFetchFlag(int Flags)
 {
   fetchFlags |= Flags;
 }
 
-bool cSVDRPClient::HasFetchFlag(eSvdrpFetchFlags Flag)
+bool cSVDRPClient::HasFetchFlag(int Flag)
 {
   bool Result = (fetchFlags & Flag);
   fetchFlags &= ~Flag;
   return Result;
 }
 
+bool cSVDRPClient::GetRemoteTimers(cStringList &Response)
+{
+  if (Execute("LSTT ID", &Response)) {
+     for (int i = 0; i < Response.Size(); i++) {
+         char *s = Response[i];
+         int Code = SVDRPCode(s);
+         if (Code == 250)
+            strshift(s, 4);
+         else {
+            if (Code != 550)
+               esyslog("ERROR: %s: %s", ServerName(), s);
+            return false;
+            }
+         }
+     Response.SortNumerically();
+     return true;
+     }
+  return false;
+}
+
+
+// --- cSVDRPServerParams ----------------------------------------------------
+
+class cSVDRPServerParams {
+private:
+  cString name;
+  int port;
+  cString vdrversion;
+  cString apiversion;
+  int timeout;
+  cString host;
+  cString error;
+public:
+  cSVDRPServerParams(const char *Params);
+  const char *Name(void) const { return name; }
+  const int Port(void) const { return port; }
+  const char *VdrVersion(void) const { return vdrversion; }
+  const char *ApiVersion(void) const { return apiversion; }
+  const int Timeout(void) const { return timeout; }
+  const char *Host(void) const { return host; }
+  bool Ok(void) const { return !*error; }
+  const char *Error(void) const { return error; }
+  };
+
+cSVDRPServerParams::cSVDRPServerParams(const char *Params)
+{
+  if (Params && *Params) {
+     name = strgetval(Params, "name", ':');
+     if (*name) {
+        cString p = strgetval(Params, "port", ':');
+        if (*p) {
+           port = atoi(p);
+           vdrversion = strgetval(Params, "vdrversion", ':');
+           if (*vdrversion) {
+              apiversion = strgetval(Params, "apiversion", ':');
+              if (*apiversion) {
+                 cString t = strgetval(Params, "timeout", ':');
+                 if (*t) {
+                    timeout = atoi(t);
+                    if (timeout > 10) { // don't let it get too small
+                       host = strgetval(Params, "host", ':');
+                       // no error if missing - this parameter is optional!
+                       }
+                    else
+                       error = "invalid timeout";
+                    }
+                 else
+                    error = "missing server timeout";
+                 }
+              else
+                 error = "missing server apiversion";
+              }
+           else
+              error = "missing server vdrversion";
+           }
+        else
+           error = "missing server port";
+        }
+     else
+        error = "missing server name";
+     }
+  else
+     error = "missing server parameters";
+}
+
 // --- cSVDRPClientHandler ---------------------------------------------------
 
+cStateKey StateKeySVDRPRemoteTimersPoll(true);
+
 class cSVDRPClientHandler : public cThread {
 private:
   cMutex mutex;
   int tcpPort;
   cSocket udpSocket;
   cVector<cSVDRPClient *> clientConnections;
+  void SendDiscover(void);
   void HandleClientConnection(void);
   void ProcessConnections(void);
   cSVDRPClient *GetClientForServer(const char *ServerName);
@@ -515,9 +601,11 @@
 public:
   cSVDRPClientHandler(int TcpPort, int UdpPort);
   virtual ~cSVDRPClientHandler();
-  void SendDiscover(const char *Address = NULL);
+  void Lock(void) { mutex.Lock(); }
+  void Unlock(void) { mutex.Unlock(); }
+  void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress);
   bool Execute(const char *ServerName, const char *Command, cStringList *Response = NULL);
-  bool GetServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlags = sffNone);
+  bool GetServerNames(cStringList *ServerNames);
   bool TriggerFetchingTimers(const char *ServerName);
   };
 
@@ -546,64 +634,78 @@
   return NULL;
 }
 
-void cSVDRPClientHandler::SendDiscover(const char *Address)
+void cSVDRPClientHandler::SendDiscover(void)
 {
-  cMutexLock MutexLock(&mutex);
   cString Dgram = cString::sprintf("SVDRP:discover name:%s port:%d vdrversion:%d apiversion:%d timeout:%d%s", Setup.SVDRPHostName, tcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout, (Setup.SVDRPPeering == spmOnly && *Setup.SVDRPDefaultHost) ? *cString::sprintf(" host:%s", Setup.SVDRPDefaultHost) : "");
-  udpSocket.SendDgram(Dgram, udpSocket.Port(), Address);
+  udpSocket.SendDgram(Dgram, udpSocket.Port());
 }
 
 void cSVDRPClientHandler::ProcessConnections(void)
 {
-  cMutexLock MutexLock(&mutex);
+  cString PollTimersCmd;
+  if (cTimers::GetTimersRead(StateKeySVDRPRemoteTimersPoll, 100)) {
+     PollTimersCmd = cString::sprintf("POLL %s TIMERS", Setup.SVDRPHostName);
+     StateKeySVDRPRemoteTimersPoll.Remove();
+     }
+  else if (StateKeySVDRPRemoteTimersPoll.TimedOut())
+     return; // try again next time
   for (int i = 0; i < clientConnections.Size(); i++) {
-      if (!clientConnections[i]->Process()) {
-         delete clientConnections[i];
+      cSVDRPClient *Client = clientConnections[i];
+      if (Client->Process()) {
+         if (Client->HasFetchFlag(sffConn))
+            Client->Execute(cString::sprintf("CONN name:%s port:%d vdrversion:%d apiversion:%d timeout:%d", Setup.SVDRPHostName, SVDRPTcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout));
+         if (Client->HasFetchFlag(sffPing))
+            Client->Execute("PING");
+         if (Client->HasFetchFlag(sffTimers)) {
+            cStringList RemoteTimers;
+            if (Client->GetRemoteTimers(RemoteTimers)) {
+               if (cTimers *Timers = cTimers::GetTimersWrite(StateKeySVDRPRemoteTimersPoll, 100)) {
+                  bool TimersModified = Timers->StoreRemoteTimers(Client->ServerName(), &RemoteTimers);
+                  StateKeySVDRPRemoteTimersPoll.Remove(TimersModified);
+                  }
+               else
+                  Client->SetFetchFlag(sffTimers); // try again next time
+               }
+            }
+         if (*PollTimersCmd) {
+            if (!Client->Execute(PollTimersCmd))
+               esyslog("ERROR: can't send '%s' to '%s'", *PollTimersCmd, Client->ServerName());
+            }
+         }
+      else {
+         cTimers *Timers = cTimers::GetTimersWrite(StateKeySVDRPRemoteTimersPoll);
+         bool TimersModified = Timers->StoreRemoteTimers(Client->ServerName(), NULL);
+         StateKeySVDRPRemoteTimersPoll.Remove(TimersModified);
+         delete Client;
          clientConnections.Remove(i);
          i--;
          }
       }
 }
 
+void cSVDRPClientHandler::AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
+{
+  cMutexLock MutexLock(&mutex);
+  for (int i = 0; i < clientConnections.Size(); i++) {
+      if (clientConnections[i]->HasAddress(IpAddress, ServerParams.Port()))
+         return;
+      }
+  if (Setup.SVDRPPeering == spmOnly && strcmp(ServerParams.Name(), Setup.SVDRPDefaultHost) != 0)
+     return; // we only want to peer with the default host, but this isn't the default host
+  if (ServerParams.Host() && strcmp(ServerParams.Host(), Setup.SVDRPHostName) != 0)
+     return; // the remote VDR requests a specific host, but it's not us
+  clientConnections.Append(new cSVDRPClient(IpAddress, ServerParams.Port(), ServerParams.Name(), ServerParams.Timeout()));
+}
+
 void cSVDRPClientHandler::HandleClientConnection(void)
 {
   cString NewDiscover = udpSocket.Discover();
   if (*NewDiscover) {
-     cString p = strgetval(NewDiscover, "port", ':');
-     if (*p) {
-        int Port = atoi(p);
-        for (int i = 0; i < clientConnections.Size(); i++) {
-            if (clientConnections[i]->HasAddress(udpSocket.LastIpAddress()->Address(), Port)) {
-               dsyslog("SVDRP < %s connection to '%s' confirmed", clientConnections[i]->Connection(), clientConnections[i]->ServerName());
-               return;
-               }
-            }
-        cString ServerName = strgetval(NewDiscover, "name", ':');
-        if (*ServerName) {
-           if (Setup.SVDRPPeering == spmOnly && strcmp(ServerName, Setup.SVDRPDefaultHost) != 0)
-              return; // we only want to peer with the default host, but this isn't the default host
-           cString HostName = strgetval(NewDiscover, "host", ':');
-           if (*HostName && strcmp(HostName, Setup.SVDRPHostName) != 0)
-              return; // the remote VDR requests a specific host, but it's not us
-           cString t = strgetval(NewDiscover, "timeout", ':');
-           if (*t) {
-              int Timeout = atoi(t);
-              if (Timeout > 10) { // don't let it get too small
-                 const char *Address = udpSocket.LastIpAddress()->Address();
-                 clientConnections.Append(new cSVDRPClient(Address, Port, ServerName, Timeout));
-                 SendDiscover(Address);
-                 }
-              else
-                 esyslog("SVDRP < %s ERROR: invalid timeout (%d)", udpSocket.LastIpAddress()->Connection(), Timeout);
-              }
-           else
-              esyslog("SVDRP < %s ERROR: missing timeout", udpSocket.LastIpAddress()->Connection());
-           }
-        else
-           esyslog("SVDRP < %s ERROR: missing server name", udpSocket.LastIpAddress()->Connection());
-        }
+     cSVDRPServerParams ServerParams(NewDiscover);
+     if (ServerParams.Ok())
+        AddClient(ServerParams, udpSocket.LastIpAddress()->Address());
      else
-        esyslog("SVDRP < %s ERROR: missing port number", udpSocket.LastIpAddress()->Connection());
+        esyslog("SVDRP %s < %s ERROR: %s", Setup.SVDRPHostName, udpSocket.LastIpAddress()->Connection(), ServerParams.Error());
      }
 }
 
@@ -631,13 +733,13 @@
   return false;
 }
 
-bool cSVDRPClientHandler::GetServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag)
+bool cSVDRPClientHandler::GetServerNames(cStringList *ServerNames)
 {
   cMutexLock MutexLock(&mutex);
   ServerNames->Clear();
   for (int i = 0; i < clientConnections.Size(); i++) {
       cSVDRPClient *Client = clientConnections[i];
-      if (FetchFlag == sffNone || Client->HasFetchFlag(FetchFlag))
+      if (Client->Connected())
          ServerNames->Append(strdup(Client->ServerName()));
       }
   return ServerNames->Size() > 0;
@@ -730,6 +832,10 @@
   "    After a CLRE command, no further EPG processing is done for 10\n"
   "    seconds, so that data sent with subsequent PUTE commands doesn't\n"
   "    interfere with data from the broadcasters.",
+  "CONN name:<name> port:<port> vdrversion:<vdrversion> apiversion:<apiversion> timeout:<timeout>\n"
+  "    Used by peer-to-peer connections between VDRs to tell the other VDR\n"
+  "    to establish a connection to this VDR. The name is the SVDRP host name\n"
+  "    of this VDR, which may differ from its DNS name.",
   "DELC <number>\n"
   "    Delete channel.",
   "DELR <id>\n"
@@ -771,6 +877,10 @@
   "    separators. The channel number of a group separator is always 0.\n"
   "    With ':ids' the channel ids are listed following the channel numbers.\n"
   "    The special number 0 can be given to list the current channel.",
+  "LSTD\n"
+  "    List all available devices. Each device is listed with its name and\n"
+  "    whether it is currently the primary device ('P') or it implements a\n"
+  "    decoder ('D') and can be used as output device.",
   "LSTE [ <channel> ] [ now | next | at <time> ]\n"
   "    List EPG data. Without any parameters all data of all channels is\n"
   "    listed. If a channel is given (either by number or by channel ID),\n"
@@ -847,10 +957,15 @@
   "    If 'help' is followed by a command, the detailed help for that command is\n"
   "    given. The keyword 'main' initiates a call to the main menu function of the\n"
   "    given plugin.\n",
-  "POLL timers\n"
+  "POLL <name> timers\n"
   "    Used by peer-to-peer connections between VDRs to inform other machines\n"
   "    about changes to timers. The receiving VDR shall use LSTT to query the\n"
-  "    remote machine's timers and update its list of timers accordingly.\n",
+  "    remote machine with the given name about its timers and update its list\n"
+  "    of timers accordingly.\n",
+  "PRIM [ <number> ]\n"
+  "    Make the device with the given number the primary device.\n"
+  "    Without option it returns the currently active primary device in the same\n"
+  "    format as used by the LSTD command.",
   "PUTE [ file ]\n"
   "    Put data into the EPG list. The data entered has to strictly follow the\n"
   "    format defined in vdr(5) for the 'epg.data' file.  A '.' on a line\n"
@@ -943,7 +1058,8 @@
 class cSVDRPServer {
 private:
   int socket;
-  cString connection;
+  cIpAddress clientIpAddress;
+  cString clientName;
   cFile file;
   cPUTEhandler *PUTEhandler;
   int numChars;
@@ -951,11 +1067,12 @@
   char *cmdLine;
   time_t lastActivity;
   void Close(bool SendReply = false, bool Timeout = false);
-  bool Send(const char *s, int length = -1);
+  bool Send(const char *s);
   void Reply(int Code, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
   void PrintHelpTopics(const char **hp);
   void CmdCHAN(const char *Option);
   void CmdCLRE(const char *Option);
+  void CmdCONN(const char *Option);
   void CmdDELC(const char *Option);
   void CmdDELR(const char *Option);
   void CmdDELT(const char *Option);
@@ -964,6 +1081,7 @@
   void CmdHELP(const char *Option);
   void CmdHITK(const char *Option);
   void CmdLSTC(const char *Option);
+  void CmdLSTD(const char *Option);
   void CmdLSTE(const char *Option);
   void CmdLSTR(const char *Option);
   void CmdLSTT(const char *Option);
@@ -979,6 +1097,7 @@
   void CmdPLAY(const char *Option);
   void CmdPLUG(const char *Option);
   void CmdPOLL(const char *Option);
+  void CmdPRIM(const char *Option);
   void CmdPUTE(const char *Option);
   void CmdREMO(const char *Option);
   void CmdSCAN(const char *Option);
@@ -988,18 +1107,20 @@
   void CmdVOLU(const char *Option);
   void Execute(char *Cmd);
 public:
-  cSVDRPServer(int Socket, const char *Connection);
+  cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress);
   ~cSVDRPServer();
+  const char *ClientName(void) const { return clientName; }
   bool HasConnection(void) { return file.IsOpen(); }
   bool Process(void);
   };
 
 static cPoller SVDRPServerPoller;
 
-cSVDRPServer::cSVDRPServer(int Socket, const char *Connection)
+cSVDRPServer::cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
 {
   socket = Socket;
-  connection = Connection;
+  clientIpAddress = *ClientIpAddress;
+  clientName = clientIpAddress.Connection(); // will be set to actual name by a CONN command
   PUTEhandler = NULL;
   numChars = 0;
   length = BUFSIZ;
@@ -1010,14 +1131,14 @@
      Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", Setup.SVDRPHostName, VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8");
      SVDRPServerPoller.Add(file, false);
      }
-  dsyslog("SVDRP < %s server created", *connection);
+  dsyslog("SVDRP %s > %s server created", Setup.SVDRPHostName, *clientName);
 }
 
 cSVDRPServer::~cSVDRPServer()
 {
   Close(true);
   free(cmdLine);
-  dsyslog("SVDRP < %s server destroyed", *connection);
+  dsyslog("SVDRP %s < %s server destroyed", Setup.SVDRPHostName, *clientName);
 }
 
 void cSVDRPServer::Close(bool SendReply, bool Timeout)
@@ -1026,7 +1147,7 @@
      if (SendReply) {
         Reply(221, "%s closing connection%s", Setup.SVDRPHostName, Timeout ? " (timeout)" : "");
         }
-     isyslog("SVDRP < %s connection closed", *connection);
+     isyslog("SVDRP %s < %s connection closed", Setup.SVDRPHostName, *clientName);
      SVDRPServerPoller.Del(file, false);
      file.Close();
      DELETENULL(PUTEhandler);
@@ -1034,11 +1155,10 @@
   close(socket);
 }
 
-bool cSVDRPServer::Send(const char *s, int length)
+bool cSVDRPServer::Send(const char *s)
 {
-  if (length < 0)
-     length = strlen(s);
-  if (safe_write(file, s, length) < 0) {
+  dbgsvdrp("> S %s: %s", *clientName, s); // terminating newline is already in the string!
+  if (safe_write(file, s, strlen(s)) < 0) {
      LOG_ERROR;
      Close();
      return false;
@@ -1050,26 +1170,33 @@
 {
   if (file.IsOpen()) {
      if (Code != 0) {
+        char *buffer = NULL;
         va_list ap;
         va_start(ap, fmt);
-        cString buffer = cString::vsprintf(fmt, ap);
+        if (vasprintf(&buffer, fmt, ap) >= 0) {
+           char *s = buffer;
+           while (s && *s) {
+                 char *n = strchr(s, '\n');
+                 if (n)
+                    *n = 0;
+                 char cont = ' ';
+                 if (Code < 0 || n && *(n + 1)) // trailing newlines don't count!
+                    cont = '-';
+                 if (!Send(cString::sprintf("%03d%c%s\r\n", abs(Code), cont, s)))
+                    break;
+                 s = n ? n + 1 : NULL;
+                 }
+           }
+        else {
+           Reply(451, "Bad format - looks like a programming error!");
+           esyslog("SVDRP %s < %s bad format!", Setup.SVDRPHostName, *clientName);
+           }
         va_end(ap);
-        const char *s = buffer;
-        while (s && *s) {
-              const char *n = strchr(s, '\n');
-              char cont = ' ';
-              if (Code < 0 || n && *(n + 1)) // trailing newlines don't count!
-                 cont = '-';
-              char number[16];
-              sprintf(number, "%03d%c", abs(Code), cont);
-              if (!(Send(number) && Send(s, n ? n - s : -1) && Send("\r\n")))
-                 break;
-              s = n ? n + 1 : NULL;
-              }
+        free(buffer);
         }
      else {
         Reply(451, "Zero return code - looks like a programming error!");
-        esyslog("SVDRP < %s zero return code!", *connection);
+        esyslog("SVDRP %s < %s zero return code!", Setup.SVDRPHostName, *clientName);
         }
      }
 }
@@ -1225,6 +1352,26 @@
      }
 }
 
+void cSVDRPServer::CmdCONN(const char *Option)
+{
+  if (*Option) {
+     if (SVDRPClientHandler) {
+        cSVDRPServerParams ServerParams(Option);
+        if (ServerParams.Ok()) {
+           clientName = ServerParams.Name();
+           Reply(250, "OK"); // must finish this transaction before creating the new client
+           SVDRPClientHandler->AddClient(ServerParams, clientIpAddress.Address());
+           }
+        else
+           Reply(501, "Error in server parameters: %s", ServerParams.Error());
+        }
+     else
+        Reply(451, "No SVDRP client handler");
+     }
+  else
+     Reply(501, "Missing server parameters");
+}
+
 void cSVDRPServer::CmdDELC(const char *Option)
 {
   if (*Option) {
@@ -1254,7 +1401,7 @@
            Channels->ReNumber();
            Channels->SetModifiedByUser();
            Channels->SetModified();
-           isyslog("SVDRP < %s channel %s deleted", *connection, Option);
+           isyslog("SVDRP %s < %s deleted channel %s", Setup.SVDRPHostName, *clientName, Option);
            if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
               if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
                  Channels->SwitchTo(CurrentChannel->Number());
@@ -1302,6 +1449,7 @@
               if (Recording->Delete()) {
                  Recordings->DelByName(Recording->FileName());
                  Recordings->SetModified();
+                 isyslog("SVDRP %s < %s deleted recording %s", Setup.SVDRPHostName, *clientName, Option);
                  Reply(250, "Recording \"%s\" deleted", Option);
                  }
               else
@@ -1325,11 +1473,13 @@
         LOCK_TIMERS_WRITE;
         Timers->SetExplicitModify();
         if (cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
-           if (Timer->Recording())
+           if (Timer->Recording()) {
               Timer->Skip();
+              cRecordControls::Process(Timers, time(NULL));
+              }
            Timers->Del(Timer);
            Timers->SetModified();
-           isyslog("SVDRP < %s deleted timer %s", *connection, *Timer->ToDescr());
+           isyslog("SVDRP %s < %s deleted timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
            Reply(250, "Timer \"%s\" deleted", Option);
            }
         else
@@ -1475,7 +1625,7 @@
            int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
            if (fd >= 0) {
               if (safe_write(fd, Image, ImageSize) == ImageSize) {
-                 dsyslog("SVDRP < %s grabbed image to %s", *connection, FileName);
+                 dsyslog("SVDRP %s < %s grabbed image to %s", Setup.SVDRPHostName, *clientName, FileName);
                  Reply(250, "Grabbed image %s", Option);
                  }
               else {
@@ -1620,6 +1770,18 @@
      Reply(550, "No channels defined");
 }
 
+void cSVDRPServer::CmdLSTD(const char *Option)
+{
+  if (cDevice::NumDevices()) {
+     for (int i = 0; i < cDevice::NumDevices(); i++) {
+         if (const cDevice *d = cDevice::GetDevice(i))
+            Reply(d->DeviceNumber() + 1 == cDevice::NumDevices() ? 250 : -250, "%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ? "D" : "-", d->DeviceNumber() + 1 == Setup.PrimaryDVB ? "P" : "-", *d->DeviceName());
+         }
+     }
+  else
+     Reply(550, "No devices found");
+}
+
 void cSVDRPServer::CmdLSTE(const char *Option)
 {
   LOCK_CHANNELS_READ;
@@ -1817,7 +1979,7 @@
 void cSVDRPServer::CmdMESG(const char *Option)
 {
   if (*Option) {
-     isyslog("SVDRP < %s message '%s'", *connection, Option);
+     isyslog("SVDRP %s < %s message '%s'", Setup.SVDRPHostName, *clientName, Option);
      Skins.QueueMessage(mtInfo, Option);
      Reply(250, "Message queued");
      }
@@ -1842,7 +2004,7 @@
                  Channels->ReNumber();
                  Channels->SetModifiedByUser();
                  Channels->SetModified();
-                 isyslog("SVDRP < %s modifed channel %d %s", *connection, Channel->Number(), *Channel->ToText());
+                 isyslog("SVDRP %s < %s modifed channel %d %s", Setup.SVDRPHostName, *clientName, Channel->Number(), *Channel->ToText());
                  Reply(250, "%d %s", Channel->Number(), *Channel->ToText());
                  }
               else
@@ -1882,7 +2044,7 @@
               }
            *Timer = t;
            Timers->SetModified();
-           isyslog("SVDRP < %s modified timer %s (%s)", *connection, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive");
+           isyslog("SVDRP %s < %s modified timer %s (%s)", Setup.SVDRPHostName, *clientName, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive");
            Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
            }
         else
@@ -1926,7 +2088,7 @@
                        else
                           cDevice::SetCurrentChannel(CurrentChannel->Number());
                        }
-                    isyslog("SVDRP < %s channel %d moved to %d", *connection, FromNumber, ToNumber);
+                    isyslog("SVDRP %s < %s moved channel %d to %d", Setup.SVDRPHostName, *clientName, FromNumber, ToNumber);
                     Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
                     }
                  else
@@ -2006,7 +2168,7 @@
            Channels->ReNumber();
            Channels->SetModifiedByUser();
            Channels->SetModified();
-           isyslog("SVDRP < %s new channel %d %s", *connection, channel->Number(), *channel->ToText());
+           isyslog("SVDRP %s < %s new channel %d %s", Setup.SVDRPHostName, *clientName, channel->Number(), *channel->ToText());
            Reply(250, "%d %s", channel->Number(), *channel->ToText());
            }
         else
@@ -2027,7 +2189,7 @@
         LOCK_TIMERS_WRITE;
         Timer->ClrFlags(tfRecording);
         Timers->Add(Timer);
-        isyslog("SVDRP < %s added timer %s", *connection, *Timer->ToDescr());
+        isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
         Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
         return;
         }
@@ -2216,6 +2378,32 @@
      Reply(501, "Missing parameters");
 }
 
+void cSVDRPServer::CmdPRIM(const char *Option)
+{
+  int n = -1;
+  if (*Option) {
+     if (isnumber(Option)) {
+        int o = strtol(Option, NULL, 10);
+        if (o > 0 && o <= cDevice::NumDevices())
+           n = o;
+        else
+           Reply(501, "Invalid device number \"%s\"", Option);
+        }
+     else
+        Reply(501, "Invalid parameter \"%s\"", Option);
+     if (n >= 0) {
+        Setup.PrimaryDVB = n;
+        Reply(250, "Primary device set to %d", n);
+        }
+     }
+  else {
+    if (const cDevice *d = cDevice::PrimaryDevice())
+       Reply(250, "%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ? "D" : "-", d->DeviceNumber() + 1 == Setup.PrimaryDVB ? "P" : "-", *d->DeviceName());
+    else
+       Reply(501, "Failed to get primary device");
+    }
+}
+
 void cSVDRPServer::CmdPUTE(const char *Option)
 {
   if (*Option) {
@@ -2290,11 +2478,11 @@
            t->Parse(Option);
            delete Timer;
            Timer = t;
-           isyslog("SVDRP < %s updated timer %s", *connection, *Timer->ToDescr());
+           isyslog("SVDRP %s < %s updated timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
            }
         else {
            Timers->Add(Timer);
-           isyslog("SVDRP < %s added timer %s", *connection, *Timer->ToDescr());
+           isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
            }
         Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
         return;
@@ -2360,6 +2548,7 @@
   s = skipspace(s);
   if      (CMD("CHAN"))  CmdCHAN(s);
   else if (CMD("CLRE"))  CmdCLRE(s);
+  else if (CMD("CONN"))  CmdCONN(s);
   else if (CMD("DELC"))  CmdDELC(s);
   else if (CMD("DELR"))  CmdDELR(s);
   else if (CMD("DELT"))  CmdDELT(s);
@@ -2368,6 +2557,7 @@
   else if (CMD("HELP"))  CmdHELP(s);
   else if (CMD("HITK"))  CmdHITK(s);
   else if (CMD("LSTC"))  CmdLSTC(s);
+  else if (CMD("LSTD"))  CmdLSTD(s);
   else if (CMD("LSTE"))  CmdLSTE(s);
   else if (CMD("LSTR"))  CmdLSTR(s);
   else if (CMD("LSTT"))  CmdLSTT(s);
@@ -2383,6 +2573,7 @@
   else if (CMD("PLAY"))  CmdPLAY(s);
   else if (CMD("PLUG"))  CmdPLUG(s);
   else if (CMD("POLL"))  CmdPOLL(s);
+  else if (CMD("PRIM"))  CmdPRIM(s);
   else if (CMD("PUTE"))  CmdPUTE(s);
   else if (CMD("REMO"))  CmdREMO(s);
   else if (CMD("SCAN"))  CmdSCAN(s);
@@ -2408,6 +2599,7 @@
                  // make sure the string is terminated:
                  cmdLine[numChars] = 0;
                  // showtime!
+                 dbgsvdrp("< S %s: %s\n", *clientName, cmdLine);
                  Execute(cmdLine);
                  numChars = 0;
                  if (length > BUFSIZ) {
@@ -2436,7 +2628,7 @@
                        cmdLine = NewBuffer;
                        }
                     else {
-                       esyslog("SVDRP < %s ERROR: out of memory", *connection);
+                       esyslog("SVDRP %s < %s ERROR: out of memory", Setup.SVDRPHostName, *clientName);
                        Close();
                        break;
                        }
@@ -2447,12 +2639,12 @@
               lastActivity = time(NULL);
               }
            else if (r <= 0) {
-              isyslog("SVDRP < %s lost connection to client", *connection);
+              isyslog("SVDRP %s < %s lost connection to client", Setup.SVDRPHostName, *clientName);
               Close();
               }
            }
      if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
-        isyslog("SVDRP < %s timeout on connection", *connection);
+        isyslog("SVDRP %s < %s timeout on connection", Setup.SVDRPHostName, *clientName);
         Close(true, true);
         }
      }
@@ -2474,7 +2666,6 @@
 
 class cSVDRPServerHandler : public cThread {
 private:
-  cMutex mutex;
   bool ready;
   cSocket tcpSocket;
   cVector<cSVDRPServer *> serverConnections;
@@ -2513,7 +2704,6 @@
 
 void cSVDRPServerHandler::ProcessConnections(void)
 {
-  cMutexLock MutexLock(&mutex);
   for (int i = 0; i < serverConnections.Size(); i++) {
       if (!serverConnections[i]->Process()) {
          delete serverConnections[i];
@@ -2527,7 +2717,7 @@
 {
   int NewSocket = tcpSocket.Accept();
   if (NewSocket >= 0)
-     serverConnections.Append(new cSVDRPServer(NewSocket, tcpSocket.LastIpAddress()->Connection()));
+     serverConnections.Append(new cSVDRPServer(NewSocket, tcpSocket.LastIpAddress()));
 }
 
 void cSVDRPServerHandler::Action(void)
@@ -2537,7 +2727,6 @@
      ready = true;
      while (Running()) {
            SVDRPServerPoller.Poll(1000);
-           cMutexLock MutexLock(&mutex);
            HandleServerConnection();
            ProcessConnections();
            }
@@ -2550,60 +2739,53 @@
 
 static cMutex SVDRPHandlerMutex;
 
-void StartSVDRPServerHandler(void)
-{
-  cMutexLock MutexLock(&SVDRPHandlerMutex);
-  if (SVDRPTcpPort && !SVDRPServerHandler) {
-     SVDRPServerHandler = new cSVDRPServerHandler(SVDRPTcpPort);
-     SVDRPServerHandler->Start();
-     SVDRPServerHandler->WaitUntilReady();
-     }
-}
-
-void StartSVDRPClientHandler(void)
+void StartSVDRPHandler(void)
 {
   cMutexLock MutexLock(&SVDRPHandlerMutex);
-  if (SVDRPTcpPort && SVDRPUdpPort && !SVDRPClientHandler) {
-     SVDRPClientHandler = new cSVDRPClientHandler(SVDRPTcpPort, SVDRPUdpPort);
-     SVDRPClientHandler->Start();
+  if (SVDRPTcpPort) {
+     if (!SVDRPServerHandler) {
+        SVDRPServerHandler = new cSVDRPServerHandler(SVDRPTcpPort);
+        SVDRPServerHandler->Start();
+        SVDRPServerHandler->WaitUntilReady();
+        }
+     if (Setup.SVDRPPeering && SVDRPUdpPort && !SVDRPClientHandler) {
+        SVDRPClientHandler = new cSVDRPClientHandler(SVDRPTcpPort, SVDRPUdpPort);
+        SVDRPClientHandler->Start();
+        }
      }
 }
 
-void StopSVDRPServerHandler(void)
-{
-  cMutexLock MutexLock(&SVDRPHandlerMutex);
-  delete SVDRPServerHandler;
-  SVDRPServerHandler = NULL;
-}
-
-void StopSVDRPClientHandler(void)
+void StopSVDRPHandler(void)
 {
   cMutexLock MutexLock(&SVDRPHandlerMutex);
   delete SVDRPClientHandler;
   SVDRPClientHandler = NULL;
+  delete SVDRPServerHandler;
+  SVDRPServerHandler = NULL;
 }
 
-void SendSVDRPDiscover(const char *Address)
-{
-  cMutexLock MutexLock(&SVDRPHandlerMutex);
-  if (SVDRPClientHandler)
-     SVDRPClientHandler->SendDiscover(Address);
-}
-
-bool GetSVDRPServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag)
+bool GetSVDRPServerNames(cStringList *ServerNames)
 {
+  bool Result = false;
   cMutexLock MutexLock(&SVDRPHandlerMutex);
-  if (SVDRPClientHandler)
-     return SVDRPClientHandler->GetServerNames(ServerNames, FetchFlag);
-  return false;
+  if (SVDRPClientHandler) {
+     SVDRPClientHandler->Lock();
+     Result = SVDRPClientHandler->GetServerNames(ServerNames);
+     SVDRPClientHandler->Unlock();
+     }
+  return Result;
 }
 
 bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
 {
+  bool Result = false;
   cMutexLock MutexLock(&SVDRPHandlerMutex);
-  if (SVDRPClientHandler)
-     return SVDRPClientHandler->Execute(ServerName, Command, Response);
-  return false;
+  if (SVDRPClientHandler) {
+     SVDRPClientHandler->Lock();
+     Result = SVDRPClientHandler->Execute(ServerName, Command, Response);
+     SVDRPClientHandler->Unlock();
+     }
+  return Result;
 }
 
 void BroadcastSVDRPCommand(const char *Command)
@@ -2611,9 +2793,11 @@
   cMutexLock MutexLock(&SVDRPHandlerMutex);
   cStringList ServerNames;
   if (SVDRPClientHandler) {
+     SVDRPClientHandler->Lock();
      if (SVDRPClientHandler->GetServerNames(&ServerNames)) {
         for (int i = 0; i < ServerNames.Size(); i++)
             ExecSVDRPCommand(ServerNames[i], Command);
         }
+     SVDRPClientHandler->Unlock();
      }
 }
diff -ruN vdr-2.3.8/svdrp.h vdr-2.3.9/svdrp.h
--- vdr-2.3.8/svdrp.h	2017-06-30 11:49:39.000000000 +0200
+++ vdr-2.3.9/svdrp.h	2018-03-15 17:30:29.000000000 +0100
@@ -4,12 +4,13 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: svdrp.h 4.7 2017/06/30 09:49:39 kls Exp $
+ * $Id: svdrp.h 4.11 2018/03/15 16:30:29 kls Exp $
  */
 
 #ifndef __SVDRP_H
 #define __SVDRP_H
 
+#include "thread.h"
 #include "tools.h"
 
 enum eSvdrpPeerModes {
@@ -18,25 +19,18 @@
   spmOnly = 2,
   };
 
-enum eSvdrpFetchFlags {
-  sffNone   = 0b0000,
-  sffTimers = 0b0001,
-  };
+extern cStateKey StateKeySVDRPRemoteTimersPoll;
+     ///< Controls whether a change to the local list of timers needs to result in
+     ///< sending a POLL to the remote clients.
 
 void SetSVDRPPorts(int TcpPort, int UdpPort);
 void SetSVDRPGrabImageDir(const char *GrabImageDir);
-void StartSVDRPServerHandler(void);
-void StartSVDRPClientHandler(void);
-void StopSVDRPServerHandler(void);
-void StopSVDRPClientHandler(void);
-void SendSVDRPDiscover(const char *Address = NULL);
-bool GetSVDRPServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag = sffNone);
+void StartSVDRPHandler(void);
+void StopSVDRPHandler(void);
+bool GetSVDRPServerNames(cStringList *ServerNames);
      ///< Gets a list of all available VDRs this VDR is connected to via SVDRP,
      ///< and stores it in the given ServerNames list. The list is cleared
      ///< before getting the server names.
-     ///< If FetchFlag is given, only the server names for which the local
-     ///< client has this flag set will be returned, and the client's flag
-     ///< will be cleared.
      ///< Returns true if the resulting list is not empty.
 bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response = NULL);
      ///< Sends the given SVDRP Command string to the remote VDR identified
diff -ruN vdr-2.3.8/thread.c vdr-2.3.9/thread.c
--- vdr-2.3.8/thread.c	2017-06-25 14:08:16.000000000 +0200
+++ vdr-2.3.9/thread.c	2018-03-05 23:38:10.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 4.11 2017/06/25 12:08:16 kls Exp $
+ * $Id: thread.c 4.14 2018/03/05 22:38:10 kls Exp $
  */
 
 #include "thread.h"
@@ -716,7 +716,8 @@
   name = Name;
   threadId = 0;
   state = 0;
-  explicitModify = false;
+  explicitModify = emDisabled;
+  syncStateKey = NULL;
 }
 
 bool cStateLock::Lock(cStateKey &StateKey, bool Write, int TimeoutMs)
@@ -764,30 +765,71 @@
      return;
      }
   if (StateKey.write && threadId != cThread::ThreadId()) {
-     esyslog("ERROR: cStateLock::Unlock() called without holding a lock (tid=%d, lock=%s)", threadId, name);
+     esyslog("ERROR: cStateLock::Unlock() called without holding a write lock (tid=%d, lock=%s)", threadId, name);
      ABORT;
      return;
      }
-  if (StateKey.write && IncState && !explicitModify)
+  if (StateKey.write && (IncState && explicitModify != emArmed || explicitModify == emEnabled)) {
+     if (syncStateKey && syncStateKey->state == state)
+        syncStateKey->state++;
      state++;
+     }
   StateKey.state = state;
+  StateKey.stateLock = NULL;
   if (StateKey.write) {
      StateKey.write = false;
      threadId = 0;
-     explicitModify = false;
+     explicitModify = emDisabled;
+     syncStateKey = NULL;
      }
   dbglockseq(name, false, false);
   rwLock.Unlock();
 }
 
-void cStateLock::IncState(void)
+void cStateLock::SetSyncStateKey(cStateKey &StateKey)
 {
+  dbglocking("%5d %-12s %10p SetSyncStateKey\n", cThread::ThreadId(), name, &StateKey);
   if (threadId != cThread::ThreadId()) {
-     esyslog("ERROR: cStateLock::IncState() called without holding a lock (tid=%d, lock=%s)", threadId, name);
+     esyslog("ERROR: cStateLock::SetSyncStateKey() called without holding a write lock (tid=%d, lock=%s)", threadId, name);
      ABORT;
+     return;
      }
-  else
-     state++;
+  if (StateKey.stateLock == this) {
+     esyslog("ERROR: cStateLock::SetSyncStateKey() called with locked key (tid=%d, lock=%s)", threadId, name);
+     ABORT;
+     return;
+     }
+  if (syncStateKey) {
+     esyslog("ERROR: cStateLock::SetSyncStateKey() called twice (tid=%d, lock=%s)", threadId, name);
+     ABORT;
+     return;
+     }
+  syncStateKey = &StateKey;
+}
+
+void cStateLock::SetExplicitModify(void)
+{
+  if (threadId != cThread::ThreadId()) {
+     esyslog("ERROR: cStateLock::SetExplicitModify() called without holding a write lock (tid=%d, lock=%s)", threadId, name);
+     ABORT;
+     return;
+     }
+  if (explicitModify != emDisabled) {
+     esyslog("ERROR: cStateLock::SetExplicitModify() called twice (tid=%d, lock=%s)", threadId, name);
+     ABORT;
+     return;
+     }
+  explicitModify = emArmed;
+}
+
+void cStateLock::SetModified(void)
+{
+  if (threadId != cThread::ThreadId()) {
+     esyslog("ERROR: cStateLock::SetModified() called without holding a write lock (tid=%d, lock=%s)", threadId, name);
+     ABORT;
+     return;
+     }
+  explicitModify = emEnabled;
 }
 
 // --- cStateKey -------------------------------------------------------------
@@ -816,10 +858,8 @@
 
 void cStateKey::Remove(bool IncState)
 {
-  if (stateLock) {
+  if (stateLock)
      stateLock->Unlock(*this, IncState);
-     stateLock = NULL;
-     }
   else {
      esyslog("ERROR: cStateKey::Remove() called without holding a lock (key=%p)", this);
      ABORT;
@@ -836,7 +876,6 @@
      return state != stateLock->state;
   else
      return true;
-  return false;
 }
 
 // --- cIoThrottle -----------------------------------------------------------
diff -ruN vdr-2.3.8/thread.h vdr-2.3.9/thread.h
--- vdr-2.3.8/thread.h	2017-05-31 13:39:11.000000000 +0200
+++ vdr-2.3.9/thread.h	2018-03-04 12:27:55.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 4.3 2017/05/31 11:39:11 kls Exp $
+ * $Id: thread.h 4.4 2018/03/04 11:27:55 kls Exp $
  */
 
 #ifndef __THREAD_H
@@ -171,11 +171,13 @@
 class cStateLock {
   friend class cStateKey;
 private:
+  enum { emDisabled = 0, emArmed, emEnabled };
   const char *name;
   tThreadId threadId;
   cRwLock rwLock;
   int state;
-  bool explicitModify;
+  int explicitModify;
+  cStateKey *syncStateKey;
   void Unlock(cStateKey &StateKey, bool IncState = true);
        ///< Releases a lock that has been obtained by a previous call to Lock()
        ///< with the given StateKey. If this was a write-lock, and IncState is true,
@@ -211,13 +213,21 @@
        ///< If Write is true (i.e. a write-lock is requested), the states of the
        ///< lock and the given StateKey don't matter, it will always try to obtain
        ///< a write lock.
-  void SetExplicitModify(void) { explicitModify = true; }
+  void SetSyncStateKey(cStateKey &StateKey);
+       ///< Sets the given StateKey to be synchronized to the state of this lock.
+       ///< The caller must currenty hold a write lock on this lock, with a cStateKey
+       ///< that is different from the given StateKey. If, when removing the key that
+       ///< is holding the write lock, the StateKey's current state is the same as that
+       ///< of the lock, it will be increased together with the lock's state.
+  void SetExplicitModify(void);
        ///< If you have obtained a write lock on this lock, and you don't want its
        ///< state to be automatically incremented when the lock is released, a call to
-       ///< this function will disable this, and you can explicitly call IncState()
+       ///< this function will disable this, and you can explicitly call SetModified()
        ///< to increment the state.
-  void IncState(void);
-       ///< Increments the state of this lock.
+  void SetModified(void);
+       ///< Sets this lock to have its state incremented when the current write lock
+       ///< state key is removed. Must have called SetExplicitModify() before calling
+       ///< this function.
   };
 
 class cStateKey {
diff -ruN vdr-2.3.8/timers.c vdr-2.3.9/timers.c
--- vdr-2.3.8/timers.c	2017-06-25 12:02:09.000000000 +0200
+++ vdr-2.3.9/timers.c	2018-03-17 11:07:19.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 4.11 2017/06/25 10:02:09 kls Exp $
+ * $Id: timers.c 4.18 2018/03/17 10:07:19 kls Exp $
  */
 
 #include "timers.h"
@@ -419,20 +419,25 @@
      t = time(NULL);
 
   int begin  = TimeToInt(start); // seconds from midnight
-  int length = TimeToInt(stop) - begin;
-  if (length < 0)
-     length += SECSINDAY;
+  int end    = TimeToInt(stop);
+  int length = end - begin;
 
   if (IsSingleEvent()) {
-     startTime = SetTime(day, begin);
-     stopTime = startTime + length;
+     time_t t0 = day;
+     startTime = SetTime(t0, begin);
+     if (length < 0)
+        t0 = IncDay(day, 1);
+     stopTime  = SetTime(t0, end);
      }
   else {
+     time_t d = day ? max(day, t) : t;
      for (int i = -1; i <= 7; i++) {
-         time_t t0 = IncDay(day ? max(day, t) : t, i);
+         time_t t0 = IncDay(d, i);
          if (DayMatches(t0)) {
             time_t a = SetTime(t0, begin);
-            time_t b = a + length;
+            if (length < 0)
+               t0 = IncDay(d, i + 1);
+            time_t b = SetTime(t0, end);
             if ((!day || a >= day) && t < b) {
                startTime = a;
                stopTime = b;
@@ -480,9 +485,16 @@
      bool UseVps = HasFlags(tfVps) && Event->Vps();
      Matches(UseVps ? Event->Vps() : Event->StartTime(), true);
      int overlap = 0;
-     if (UseVps)
-        overlap = (startTime == Event->Vps()) ? FULLMATCH + (Event->IsRunning() ? 200 : 100) : 0;
-     if (!overlap) {
+     if (UseVps) {
+        if (startTime == Event->Vps()) {
+           overlap = FULLMATCH;
+           if (Event->IsRunning())
+              overlap += 200;
+           else if (Event->RunningStatus() != SI::RunningStatusNotRunning)
+              overlap += 100;
+           }
+        }
+     else {
         if (startTime <= Event->StartTime() && Event->EndTime() <= stopTime)
            overlap = FULLMATCH;
         else if (stopTime <= Event->StartTime() || Event->EndTime() <= startTime)
@@ -493,8 +505,6 @@
      startTime = stopTime = 0;
      if (Overlap)
         *Overlap = overlap;
-     if (UseVps)
-        return overlap > FULLMATCH ? tmFull : tmNone;
      return overlap >= FULLMATCH ? tmFull : overlap > 0 ? tmPartial : tmNone;
      }
   return tmNone;
@@ -504,7 +514,10 @@
 
 bool cTimer::Expired(void) const
 {
-  return IsSingleEvent() && !Recording() && StopTime() + EXPIRELATENCY <= time(NULL) && (!HasFlags(tfVps) || !event || !event->Vps());
+  return IsSingleEvent()
+      && !Recording()
+      && StopTime() + EXPIRELATENCY <= time(NULL)
+      && (!HasFlags(tfVps) || !event || !event->Vps() || event->EndTime() + EXPIRELATENCY <= time(NULL));
 }
 
 time_t cTimer::StartTime(void) const
@@ -538,12 +551,12 @@
         if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) {
            // VPS timers only match if their start time exactly matches the event's VPS time:
            for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
-               if (e->StartTime() && e->RunningStatus() != SI::RunningStatusNotRunning) { // skip outdated events
+               if (e->StartTime()) {
                   int overlap = 0;
-                  Matches(e, &overlap);
-                  if (overlap > FULLMATCH) {
+                  if (Matches(e, &overlap) == tmFull) {
                      Event = e;
-                     break; // take the first matching event
+                     if (overlap > FULLMATCH)
+                        break; // take the first matching event
                      }
                   }
                }
@@ -792,10 +805,10 @@
   eTimerMatch m = tmNone;
   for (const cTimer *ti = First(); ti; ti = Next(ti)) {
       eTimerMatch tm = ti->Matches(Event);
-      if (tm > m) {
+      if (tm > m || tm == tmFull && ti->Local()) {
          t = ti;
          m = tm;
-         if (m == tmFull)
+         if (m == tmFull && ti->Local())
             break;
          }
       }
@@ -804,6 +817,16 @@
   return t;
 }
 
+int cTimers::GetMaxPriority(void) const
+{
+  int n = 0;
+  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
+      if (!ti->Remote() && ti->Recording())
+         n = max(n, ti->Priority());
+      }
+  return n;
+}
+
 const cTimer *cTimers::GetNextActiveTimer(void) const
 {
   const cTimer *t0 = NULL;
@@ -883,80 +906,117 @@
   return TimersModified;
 }
 
-bool cTimers::GetRemoteTimers(const char *ServerName)
+bool cTimers::StoreRemoteTimers(const char *ServerName, const cStringList *RemoteTimers)
 {
   bool Result = false;
-  if (ServerName) {
-     Result = DelRemoteTimers(ServerName);
-     cStringList Response;
-     if (ExecSVDRPCommand(ServerName, "LSTT ID", &Response)) {
-        for (int i = 0; i < Response.Size(); i++) {
-            const char *s = Response[i];
-            int Code = SVDRPCode(s);
-            if (Code == 250) {
-               if (const char *v = SVDRPValue(s)) {
-                  int Id = atoi(v);
-                  while (*v && *v != ' ')
-                        v++; // skip id
-                  cTimer *Timer = new cTimer;
-                  if (Timer->Parse(v)) {
-                     Timer->SetRemote(ServerName);
-                     Timer->SetId(Id);
-                     Add(Timer);
-                     Result = true;
-                     }
-                  else {
-                     esyslog("ERROR: %s: error in timer settings: %s", ServerName, v);
-                     delete Timer;
-                     }
+  if (!ServerName || !RemoteTimers || RemoteTimers->Size() == 0) {
+     // Remove remote timers from this list:
+     cTimer *Timer = First();
+     while (Timer) {
+           cTimer *t = Next(Timer);
+           if (Timer->Remote() && (!ServerName || strcmp(Timer->Remote(), ServerName) == 0)) {
+              Del(Timer);
+              Result = true;
+              }
+           Timer = t;
+           }
+     return Result;
+     }
+  // Collect all locally stored remote timers from ServerName:
+  cStringList tl;
+  for (cTimer *ti = First(); ti; ti = Next(ti)) {
+      if (ti->Remote() && strcmp(ti->Remote(), ServerName) == 0)
+         tl.Append(strdup(cString::sprintf("%d %s", ti->Id(), *ti->ToText(true))));
+      }
+  tl.SortNumerically(); // RemoteTimers is also sorted numerically!
+  // Compare the two lists and react accordingly:
+  int il = 0; // index into the local ("left") list of remote timers
+  int ir = 0; // index into the remote ("right") list of timers
+  int sl = tl.Size();
+  int sr = RemoteTimers->Size();
+  for (;;) {
+      int AddTimer = 0;
+      int DelTimer = 0;
+      if (il < sl) { // still have left entries
+         int nl = atoi(tl[il]);
+         if (ir < sr) { // still have right entries
+            // Compare timers:
+            int nr = atoi((*RemoteTimers)[ir]);
+            if (nl == nr) // same timer id
+               AddTimer = DelTimer = nl;
+            else if (nl < nr) // left entry not in right list
+               DelTimer = nl;
+            else // right entry not in left list
+               AddTimer = nr;
+            }
+         else // processed all right entries
+            DelTimer = nl;
+         }
+      else if (ir < sr) { // still have right entries
+         AddTimer = atoi((*RemoteTimers)[ir]);
+         if (!AddTimer) {
+            esyslog("ERROR: %s: error in timer settings: %s", ServerName, (*RemoteTimers)[ir]);
+            ir++;
+            continue; // let's see if we can process the rest
+            }
+         }
+      else // processed all left and right entries
+         break;
+      if (AddTimer && DelTimer) {
+         if (strcmp(tl[il], (*RemoteTimers)[ir]) != 0) {
+            // Overwrite timer:
+            char *v = (*RemoteTimers)[ir];
+            while (*v && *v != ' ')
+                  v++; // skip id
+            if (cTimer *l = GetById(DelTimer, ServerName)) {
+               cTimer r;
+               if (r.Parse(v)) {
+                  r.SetRemote(ServerName);
+                  r.SetId(AddTimer);
+                  *l = r;
+                  Result = true;
                   }
+               else
+                  esyslog("ERROR: %d@%s: error in timer settings: %s", DelTimer, ServerName, v);
                }
-            else if (Code != 550)
-               esyslog("ERROR: %s: %s", ServerName, s);
             }
-        return Result;
-        }
-     }
-  else {
-     cStringList ServerNames;
-     if (GetSVDRPServerNames(&ServerNames, sffTimers)) {
-        for (int i = 0; i < ServerNames.Size(); i++)
-            Result |= GetRemoteTimers(ServerNames[i]);
-        }
-     }
+         else // identical timer, nothing to do
+            ;
+         il++;
+         ir++;
+         }
+      else if (AddTimer) {
+         char *v = (*RemoteTimers)[ir];
+         while (*v && *v != ' ')
+               v++; // skip id
+         cTimer *Timer = new cTimer;
+         if (Timer->Parse(v)) {
+            Timer->SetRemote(ServerName);
+            Timer->SetId(AddTimer);
+            Add(Timer);
+            Result = true;
+            }
+         else {
+            esyslog("ERROR: %s: error in timer settings: %s", ServerName, v);
+            delete Timer;
+            }
+         ir++;
+         }
+      else if (DelTimer) {
+         if (cTimer *t = GetById(DelTimer, ServerName)) {
+            Del(t);
+            Result = true;
+            }
+         il++;
+         }
+      else {
+         esyslog("ERROR: oops while storing remote timers!");
+         break; // let's not get stuck here!
+         }
+      }
   return Result;
 }
 
-bool cTimers::DelRemoteTimers(const char *ServerName)
-{
-  bool Deleted = false;
-  cTimer *Timer = First();
-  while (Timer) {
-        cTimer *t = Next(Timer);
-        if (Timer->Remote() && (!ServerName || strcmp(Timer->Remote(), ServerName) == 0)) {
-           Del(Timer);
-           Deleted = true;
-           }
-        Timer = t;
-        }
-  return Deleted;
-}
-
-void cTimers::TriggerRemoteTimerPoll(const char *ServerName)
-{
-  if (ServerName) {
-     if (!ExecSVDRPCommand(ServerName, cString::sprintf("POLL %s TIMERS", Setup.SVDRPHostName)))
-        esyslog("ERROR: can't send 'POLL %s TIMERS' to '%s'", Setup.SVDRPHostName, ServerName);
-     }
-  else {
-     cStringList ServerNames;
-     if (GetSVDRPServerNames(&ServerNames)) {
-        for (int i = 0; i < ServerNames.Size(); i++)
-            TriggerRemoteTimerPoll(ServerNames[i]);
-        }
-     }
-}
-
 static bool RemoteTimerError(const cTimer *Timer, cString *Msg)
 {
   if (Msg)
diff -ruN vdr-2.3.8/timers.h vdr-2.3.9/timers.h
--- vdr-2.3.8/timers.h	2017-04-20 11:09:45.000000000 +0200
+++ vdr-2.3.9/timers.h	2018-02-27 14:57:26.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 4.8 2017/04/20 09:09:45 kls Exp $
+ * $Id: timers.h 4.11 2018/02/27 13:57:26 kls Exp $
  */
 
 #ifndef __TIMERS_H
@@ -176,6 +176,8 @@
   cTimer *GetMatch(time_t t) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(t)); };
   const cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) const;
   cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(Event, Match)); }
+  int GetMaxPriority(void) const;
+      ///< Returns the maximum priority of all local timers that are currently recording.
   const cTimer *GetNextActiveTimer(void) const;
   const cTimer *UsesChannel(const cChannel *Channel) const;
   bool SetEvents(const cSchedules *Schedules);
@@ -183,21 +185,14 @@
   void Add(cTimer *Timer, cTimer *After = NULL);
   void Ins(cTimer *Timer, cTimer *Before = NULL);
   void Del(cTimer *Timer, bool DeleteObject = true);
-  bool GetRemoteTimers(const char *ServerName = NULL);
-      ///< Gets the timers from the given remote machine and adds them to this
-      ///< list. If no ServerName is given, all timers from all known remote
-      ///< machines will be fetched. This function calls DelRemoteTimers() with
-      ///< the given ServerName first.
-      ///< Returns true if any remote timers have been added or deleted
-  bool DelRemoteTimers(const char *ServerName = NULL);
-      ///< Deletes all timers of the given remote machine from this list (leaves
-      ///< them untouched on the remote machine). If no ServerName is given, the
-      ///< timers of all remote machines will be deleted from the list.
-      ///< Returns true if any remote timers have been deleted.
-  void TriggerRemoteTimerPoll(const char *ServerName = NULL);
-      ///< Sends an SVDRP POLL command to the given remote machine.
-      ///< If no ServerName is given, the POLL command will be sent to all
-      ///< known remote machines.
+  bool StoreRemoteTimers(const char *ServerName = NULL, const cStringList *RemoteTimers = NULL);
+      ///< Stores the given list of RemoteTimers, which come from the VDR ServerName, in
+      ///< this list. If no ServerName is given, all remote timers from all peer machines
+      ///< will be removed from this list. If no RemoteTimers are given, only the remote
+      ///< timers from ServerName will be removed from this list.
+      ///< The given list of RemoteTimers must be sorted numerically (by a call to its
+      ///< SortNumerically() function).
+      ///< Returns true if any remote timers have been added, deleted or modified.
   };
 
 bool HandleRemoteTimerModifications(cTimer *NewTimer, cTimer *OldTimer = NULL, cString *Msg = NULL);
diff -ruN vdr-2.3.8/tools.c vdr-2.3.9/tools.c
--- vdr-2.3.8/tools.c	2017-06-25 13:45:39.000000000 +0200
+++ vdr-2.3.9/tools.c	2018-03-04 11:28:04.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: tools.c 4.8 2017/06/25 11:45:39 kls Exp $
+ * $Id: tools.c 4.11 2018/03/04 10:28:04 kls Exp $
  */
 
 #include "tools.h"
@@ -296,6 +296,18 @@
   return NULL;
 }
 
+char *strshift(char *s, int n)
+{
+  if (s && n > 0) {
+     int l = strlen(s);
+     if (n < l)
+        memmove(s, s + n, l - n + 1); // we also copy the terminating 0!
+     else
+        *s = 0;
+     }
+  return s;
+}
+
 bool startswith(const char *s, const char *p)
 {
   while (*p) {
@@ -1762,7 +1774,12 @@
 
 // --- cUnbufferedFile -------------------------------------------------------
 
-#define USE_FADVISE
+#ifndef USE_FADVISE_READ
+#define USE_FADVISE_READ 0
+#endif
+#ifndef USE_FADVISE_WRITE
+#define USE_FADVISE_WRITE 1
+#endif
 
 #define WRITE_BUFFER KILOBYTE(800)
 
@@ -1781,7 +1798,7 @@
   Close();
   fd = open(FileName, Flags, Mode);
   curpos = 0;
-#ifdef USE_FADVISE
+#if USE_FADVISE_READ || USE_FADVISE_WRITE
   begin = lastpos = ahead = 0;
   cachedstart = 0;
   cachedend = 0;
@@ -1797,7 +1814,7 @@
 int cUnbufferedFile::Close(void)
 {
   if (fd >= 0) {
-#ifdef USE_FADVISE
+#if USE_FADVISE_READ || USE_FADVISE_WRITE
      if (totwritten)    // if we wrote anything make sure the data has hit the disk before
         fdatasync(fd);  // calling fadvise, as this is our last chance to un-cache it.
      posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
@@ -1840,7 +1857,7 @@
 ssize_t cUnbufferedFile::Read(void *Data, size_t Size)
 {
   if (fd >= 0) {
-#ifdef USE_FADVISE
+#if USE_FADVISE_READ
      off_t jumped = curpos-lastpos; // nonzero means we're not at the last offset
      if ((cachedstart < cachedend) && (curpos < cachedstart || curpos > cachedend)) {
         // current position is outside the cached window -- invalidate it.
@@ -1853,7 +1870,7 @@
      ssize_t bytesRead = safe_read(fd, Data, Size);
      if (bytesRead > 0) {
         curpos += bytesRead;
-#ifdef USE_FADVISE
+#if USE_FADVISE_READ
         cachedend = max(cachedend, curpos);
 
         // Read ahead:
@@ -1875,7 +1892,7 @@
            ahead = curpos; // jumped -> we really don't want any readahead, otherwise e.g. fast-rewind gets in trouble.
 #endif
         }
-#ifdef USE_FADVISE
+#if USE_FADVISE_READ
      if (cachedstart < cachedend) {
         if (curpos - cachedstart > READCHUNK * 2) {
            // current position has moved forward enough, shrink tail window.
@@ -1899,7 +1916,7 @@
 {
   if (fd >=0) {
      ssize_t bytesWritten = safe_write(fd, Data, Size);
-#ifdef USE_FADVISE
+#if USE_FADVISE_WRITE
      if (bytesWritten > 0) {
         begin = min(begin, curpos);
         curpos += bytesWritten;
@@ -2236,7 +2253,7 @@
 
 void cListBase::SetModified(void)
 {
-  stateLock.IncState();
+  stateLock.SetModified();
 }
 
 const cListObject *cListBase::Get(int Index) const
diff -ruN vdr-2.3.8/tools.h vdr-2.3.9/tools.h
--- vdr-2.3.8/tools.h	2017-06-25 13:45:38.000000000 +0200
+++ vdr-2.3.9/tools.h	2018-03-04 15:06:36.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 4.13 2017/06/25 11:45:38 kls Exp $
+ * $Id: tools.h 4.16 2018/03/04 14:06:36 kls Exp $
  */
 
 #ifndef __TOOLS_H
@@ -226,6 +226,12 @@
     ///< If an other delimiter shall be used (like, e.g., ':'), it can be given
     ///< as the third parameter.
     ///< If name occurs more than once in s, only the first occurrence is taken.
+char *strshift(char *s, int n);
+    ///< Shifts the given string to the left by the given number of bytes, thus
+    ///< removing the first n bytes from s.
+    ///< If n is greater than the length of s, the resulting string will be empty.
+    ///< If n is <= 0 s will be unchanged.
+    ///< Returns s.
 bool startswith(const char *s, const char *p);
 bool endswith(const char *s, const char *p);
 bool isempty(const char *s);
@@ -553,6 +559,11 @@
        ///< Contains() function to check whether an object you are holding a pointer
        ///< to is still in the list. Note that the garbage collector is purged when
        ///< the usual housekeeping is done.
+  void SetSyncStateKey(cStateKey &StateKey) { stateLock.SetSyncStateKey(StateKey); }
+       ///< When making changes to this list (while holding a write lock) that shall
+       ///< not affect some other code that reacts to such changes, this function can
+       ///< be called with the StateKey used by that other code.
+       ///< See cStateLock::SetSyncStateKey() for details.
   void SetUseGarbageCollector(void) { useGarbageCollector = true; }
   void SetExplicitModify(void);
        ///< If you have obtained a write lock on this list, and you don't want it to
@@ -768,7 +779,7 @@
 
 inline int CompareInts(const void *a, const void *b)
 {
-  return *(const int *)a > *(const int *)b;
+  return *(const int *)a - *(const int *)b;
 }
 
 inline int CompareStrings(const void *a, const void *b)
@@ -781,6 +792,12 @@
   return strcasecmp(*(const char **)a, *(const char **)b);
 }
 
+inline int CompareStringsNumerically(const void *a, const void *b)
+{
+  int d = atoi(*(const char **)a) - atoi(*(const char **)b);
+  return d ? d : CompareStrings(a, b);
+}
+
 class cStringList : public cVector<char *> {
 public:
   cStringList(int Allocated = 10): cVector<char *>(Allocated) {}
@@ -793,6 +810,10 @@
     else
        cVector<char *>::Sort(CompareStrings);
   }
+  void SortNumerically(void)
+  {
+    cVector<char *>::Sort(CompareStringsNumerically);
+  }
   virtual void Clear(void);
   };
 
diff -ruN vdr-2.3.8/transfer.c vdr-2.3.9/transfer.c
--- vdr-2.3.8/transfer.c	2015-09-05 13:43:58.000000000 +0200
+++ vdr-2.3.9/transfer.c	2017-12-07 16:00:33.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: transfer.c 4.1 2015/09/05 11:43:58 kls Exp $
+ * $Id: transfer.c 4.2 2017/12/07 15:00:33 kls Exp $
  */
 
 #include "transfer.h"
@@ -14,6 +14,8 @@
 cTransfer::cTransfer(const cChannel *Channel)
 :cReceiver(Channel, TRANSFERPRIORITY)
 {
+  lastErrorReport = 0;
+  numLostPackets = 0;
   patPmtGenerator.SetChannel(Channel);
 }
 
@@ -37,6 +39,7 @@
 
 #define MAXRETRIES    20 // max. number of retries for a single TS packet
 #define RETRYWAIT      5 // time (in ms) between two retries
+#define ERRORDELTA    60 // seconds before reporting lost TS packets again
 
 void cTransfer::Receive(const uchar *Data, int Length)
 {
@@ -51,7 +54,12 @@
          cCondWait::SleepMs(RETRYWAIT);
          }
      DeviceClear();
-     esyslog("ERROR: TS packet not accepted in Transfer Mode");
+     numLostPackets++;
+     if (time(NULL) - lastErrorReport > ERRORDELTA) {
+        esyslog("ERROR: %d TS packet(s) not accepted in Transfer Mode", numLostPackets);
+        numLostPackets = 0;
+        lastErrorReport = time(NULL);
+        }
      }
 }
 
diff -ruN vdr-2.3.8/transfer.h vdr-2.3.9/transfer.h
--- vdr-2.3.8/transfer.h	2015-09-05 13:43:08.000000000 +0200
+++ vdr-2.3.9/transfer.h	2017-12-07 15:56:22.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: transfer.h 4.1 2015/09/05 11:43:08 kls Exp $
+ * $Id: transfer.h 4.2 2017/12/07 14:56:22 kls Exp $
  */
 
 #ifndef __TRANSFER_H
@@ -16,6 +16,8 @@
 
 class cTransfer : public cReceiver, public cPlayer {
 private:
+  time_t lastErrorReport;
+  int numLostPackets;
   cPatPmtGenerator patPmtGenerator;
 protected:
   virtual void Activate(bool On);
diff -ruN vdr-2.3.8/vdr.1 vdr-2.3.9/vdr.1
--- vdr-2.3.8/vdr.1	2015-04-19 14:39:13.000000000 +0200
+++ vdr-2.3.9/vdr.1	2018-02-09 16:09:07.000000000 +0100
@@ -8,7 +8,7 @@
 .\" License as specified in the file COPYING that comes with the
 .\" vdr distribution.
 .\"
-.\" $Id: vdr.1 4.2 2015/04/19 12:39:13 kls Exp $
+.\" $Id: vdr.1 4.3 2018/02/09 15:09:07 kls Exp $
 .\"
 .TH vdr 1 "19 Feb 2015" "2.2" "Video Disk Recorder"
 .SH NAME
@@ -158,7 +158,10 @@
 Use \fIport\fR for SVDRP. A value of \fB0\fR turns off SVDRP.
 The default SVDRP port is \fB6419\fR.
 You need to edit the file \fIsvdrphosts.conf\fR in order to enable
-access to the SVDRP port.
+access to the SVDRP port from hosts other than the localhosts.
+Note that this option only changes the TCP port used for SVDRP commands.
+The UDP port for discovering peer VDRs in the same network is always set
+to 6419 and can't be changed.
 .TP
 .BI \-P\  options ,\ \-\-plugin= options
 Load a plugin, defined by the given \fIoptions\fR.
diff -ruN vdr-2.3.8/vdr.5 vdr-2.3.9/vdr.5
--- vdr-2.3.8/vdr.5	2017-06-10 13:47:27.000000000 +0200
+++ vdr-2.3.9/vdr.5	2018-02-15 17:00:42.000000000 +0100
@@ -8,7 +8,7 @@
 .\" License as specified in the file COPYING that comes with the
 .\" vdr distribution.
 .\"
-.\" $Id: vdr.5 4.3 2017/06/10 11:47:27 kls Exp $
+.\" $Id: vdr.5 4.7 2018/02/15 16:00:42 kls Exp $
 .\"
 .TH vdr 5 "19 Feb 2015" "2.2" "Video Disk Recorder Files"
 .SH NAME
@@ -522,23 +522,18 @@
 actual values are device specific and can be found in the SCR device's manual.
 
 Examples:
-
+.PP
+.EX
 0 1284
-.br
 1 1400
-.br
 2 1516
-.br
 3 1632
-.br
 4 1748
-.br
 5 1864
-.br
 6 1980
-.br
 7 2096
-
+.EE
+.PP
 By default it is assumed that the SCR configurations apply to all devices, and
 each device will pick one. If you have several SCR sat cables connected to one
 VDR machine, or if you want to explicitly assign the SCR channels to your devices,
@@ -596,35 +591,24 @@
 consisting of only '}' ends the definition of a sub folder.
 
 Example:
-
+.PP
+.EX
 Daily {
-.br
   News
-.br
   Soaps
-.br
   }
-.br
 Archive {
-.br
   Movies
-.br
   Sports
-.br
   Sci-Fi {
-.br
     Star Trek
-.br
     U.F.O.
-.br
     }
-.br
   }
-.br
 Comedy
-.br
 Science
-
+.EE
+.PP
 Note that these folder definitions are only used to set the file name under which
 a timer will store its recording. Changing these definitions in any way has no
 effect on existing timers or recordings.
@@ -647,9 +631,9 @@
 
 You can have nested layers of command menus by surrounding a sequence of
 commands with '{'...'}' and giving it a title, as in
-
+.PP
+.EX
 My Commands {
-.br
   First list {
     Do something: some command
     Do something else: another command
@@ -658,9 +642,9 @@
     Even more: yet another command
     So much more: and yet another one
     }
-.br
   }
-
+.EE
+.PP
 Command lists can be nested to any depth.
 
 By default the menu entries in the "Commands" menu will be numbered '1'...'9'
@@ -675,15 +659,14 @@
 \fIstdout\fR will be displayed in a result window, with \fBtitle\fR as its title.
 
 Examples:
-
+.PP
+.EX
 Check for new mail?: /usr/local/bin/checkmail 2>&1
-.br
 CPU status: /usr/local/bin/cpustatus 2>&1
-.br
 Disk space: df \-h | grep '/video' | awk '{ print 100 \- $5 "% free"; }'
-.br
 Calendar: date;echo;cal
-
+.EE
+.PP
 Note that the commands 'checkmail' and 'cpustatus' are only \fBexamples\fR!
 Don't send emails to the author asking where to find these ;\-)
 .br
@@ -713,14 +696,13 @@
 Everything following (and including) a '#' character is considered to be comment.
 
 Examples:
-
+.PP
+.EX
 127.0.0.1        # always accept localhost
-.br
 192.168.100.0/24 # any host on the local net
-.br
 204.152.189.113  # a specific host
-.br
 0.0.0.0/0        # any host on any net (\fBUSE WITH CARE!\fR)
+.EE
 .SS SETUP
 The file \fIsetup.conf\fR contains the basic configuration options for \fBvdr\fR.
 Each line contains one option in the format "Name = Value".
@@ -839,6 +821,26 @@
 \fBCURRENT RESTRICTIONS:\fR
 
 -\ the comment is currently not used by VDR
+.SS SORT MODE
+The file \fI.sort\fR (if present in a directory) contains an integer number
+defining the mode by which this directory shall be sorted when presented in a menu.
+
+The following values are defined:
+.TS
+tab (@);
+l l.
+\fB0\fR@sort by name
+\fB1\fR@sort by time
+.TE
+.SS RECORDING TIMER
+The file \fI.timer\fR (if present in a recording directory) contains
+the full id of the timer that is currently recording into this directory.
+Timer ids are of the form
+
+\fBid@hostname\fR
+
+where \fBid\fR is the timer's numerical id on the VDR with the name \fBhostname\fR.
+This file is created when the timer starts recording, and is deleted when it ends.
 .SS EPG DATA
 The file \fIepg.data\fR contains the EPG data in an easily parsable format.
 The first character of each line defines what kind of data this line contains.
@@ -918,6 +920,7 @@
 file is to speed up channel switching in systems with more than one CAM.
 
 This file will be read at program startup and saved when the program ends.
+If the file is read-only, it will not be overwritten.
 .SS CAM AUTO RESPONSE
 If your CAM keeps popping up annoying messages or you want to make sure VDR
 can record programmes with parental rating without having to enter the PIN
@@ -961,20 +964,16 @@
 If started without any options, vdr tries to read any files in the directory
 /etc/vdr/conf.d with names that do not begin with a '.' and that end with '.conf'.
 These files are read in alphabetical order. The format of these files is
-
+.PP
+.EX
 # comment
-.br
 [name]
-.br
 -a
-.br
 -b 123
-.br
 --long
-.br
 --longarg=123
-.br
-
+.EE
+.PP
 Any lines that begin with '#' as the first non-whitespace character are considered
 comments and are ignored.
 A command line option file consists of one or more sections, indicated by '[name]',
diff -ruN vdr-2.3.8/vdr.c vdr-2.3.9/vdr.c
--- vdr-2.3.8/vdr.c	2017-06-08 18:10:34.000000000 +0200
+++ vdr-2.3.9/vdr.c	2018-03-04 15:00:29.000000000 +0100
@@ -22,7 +22,7 @@
  *
  * The project's page is at http://www.tvdr.de
  *
- * $Id: vdr.c 4.18 2017/06/08 16:10:34 kls Exp $
+ * $Id: vdr.c 4.24 2018/03/04 14:00:29 kls Exp $
  */
 
 #include <getopt.h>
@@ -221,8 +221,10 @@
   int WatchdogTimeout = DEFAULTWATCHDOG;
   const char *Terminal = NULL;
   const char *OverrideCharacterTable = NULL;
-#define DEPRECATED_VDR_CHARSET_OVERRIDE
-#ifdef DEPRECATED_VDR_CHARSET_OVERRIDE
+#ifndef DEPRECATED_VDR_CHARSET_OVERRIDE
+#define DEPRECATED_VDR_CHARSET_OVERRIDE 0
+#endif
+#if DEPRECATED_VDR_CHARSET_OVERRIDE
   OverrideCharacterTable = getenv("VDR_CHARSET_OVERRIDE");
   const char *DeprecatedVdrCharsetOverride = OverrideCharacterTable;
 #endif
@@ -703,7 +705,7 @@
      isyslog("codeset is '%s' - %s", CodeSet, known ? "known" : "unknown");
      cCharSetConv::SetSystemCharacterTable(CodeSet);
      }
-#ifdef DEPRECATED_VDR_CHARSET_OVERRIDE
+#if DEPRECATED_VDR_CHARSET_OVERRIDE
   if (DeprecatedVdrCharsetOverride)
      isyslog("use of environment variable VDR_CHARSET_OVERRIDE (%s) is deprecated!", DeprecatedVdrCharsetOverride);
 #endif
@@ -730,6 +732,7 @@
   bool InhibitEpgScan = false;
   bool IsInfoMenu = false;
   cSkin *CurrentSkin = NULL;
+  int OldPrimaryDVB = 0;
 
   // Load plugins:
 
@@ -832,6 +835,7 @@
            }
         }
      }
+  OldPrimaryDVB = Setup.PrimaryDVB;
 
   // Check for timers in automatic start time window:
 
@@ -942,9 +946,7 @@
   // SVDRP:
 
   SetSVDRPPorts(SVDRPport, DEFAULTSVDRPPORT);
-  StartSVDRPServerHandler();
-  if (Setup.SVDRPPeering)
-     StartSVDRPClientHandler();
+  StartSVDRPHandler();
 
   // Main program loop:
 
@@ -1076,32 +1078,28 @@
            PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel;
         {
           // Timers and Recordings:
-          bool TimersModified = false;
-          bool TriggerRemoteTimerPoll = false;
-          static cStateKey TimersStateKey(true);
-          if (cTimers::GetTimersRead(TimersStateKey)) {
-             TriggerRemoteTimerPoll = true;
-             TimersStateKey.Remove();
-             }
+          static cStateKey TimersStateKey;
           cTimers *Timers = cTimers::GetTimersWrite(TimersStateKey);
-          // Get remote timers:
-          TimersModified |= Timers->GetRemoteTimers();
-          // Assign events to timers:
-          static cStateKey SchedulesStateKey;
-          if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(SchedulesStateKey))
-             TimersModified |= Timers->SetEvents(Schedules);
+          {
+            // Assign events to timers:
+            static cStateKey SchedulesStateKey;
+            if (TimersStateKey.StateChanged())
+               SchedulesStateKey.Reset(); // we assign events if either the Timers or the Schedules have changed
+            bool TimersModified = false;
+            if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(SchedulesStateKey)) {
+               Timers->SetSyncStateKey(StateKeySVDRPRemoteTimersPoll);
+               if (Timers->SetEvents(Schedules))
+                  TimersModified = true;
+               SchedulesStateKey.Remove();
+               }
+            TimersStateKey.Remove(TimersModified); // we need to remove the key here, so that syncing StateKeySVDRPRemoteTimersPoll takes effect!
+          }
           // Must do all following calls with the exact same time!
           // Process ongoing recordings:
-          if (cRecordControls::Process(Timers, Now)) {
+          Timers = cTimers::GetTimersWrite(TimersStateKey);
+          bool TimersModified = false;
+          if (cRecordControls::Process(Timers, Now))
              TimersModified = true;
-             TriggerRemoteTimerPoll = true;
-             }
-          // Must keep the lock on the schedules until after processing the record
-          // controls, in order to avoid short interrupts in case the current event
-          // is replaced by a new one (which some broadcasters do, instead of just
-          // modifying the current event's data):
-          if (SchedulesStateKey.InLock())
-             SchedulesStateKey.Remove();
           // Start new recordings:
           if (cTimer *Timer = Timers->GetMatch(Now)) {
              if (!cRecordControls::Start(Timers, Timer))
@@ -1109,7 +1107,6 @@
              else
                 LastTimerChannel = Timer->Channel()->Number();
              TimersModified = true;
-             TriggerRemoteTimerPoll = true;
              }
           // Make sure timers "see" their channel early enough:
           static time_t LastTimerCheck = 0;
@@ -1164,13 +1161,10 @@
              LastTimerCheck = Now;
              }
           // Delete expired timers:
-          if (Timers->DeleteExpired()) {
+          if (Timers->DeleteExpired())
              TimersModified = true;
-             TriggerRemoteTimerPoll = true;
-             }
-          // Trigger remote timer polls:
-          if (TriggerRemoteTimerPoll)
-             Timers->TriggerRemoteTimerPoll();
+          // Make sure there is enough free disk space for ongoing recordings:
+          AssertFreeDiskSpace(Timers->GetMaxPriority());
           TimersStateKey.Remove(TimersModified);
         }
         // Recordings:
@@ -1422,12 +1416,6 @@
                             DELETE_MENU;
                             cControl::Shutdown();
                             break;
-             case osSwitchDvb:
-                            DELETE_MENU;
-                            cControl::Shutdown();
-                            Skins.QueueMessage(mtInfo, tr("Switching primary DVB..."));
-                            cDevice::SetPrimaryDevice(Setup.PrimaryDVB);
-                            break;
              case osPlugin: DELETE_MENU;
                             Menu = cMenuMain::PluginOsdObject();
                             if (Menu)
@@ -1503,6 +1491,17 @@
               }
            }
 
+        // Change primary device:
+        int NewPrimaryDVB = Setup.PrimaryDVB;
+        if (NewPrimaryDVB != OldPrimaryDVB) {
+           DELETE_MENU;
+           cControl::Shutdown();
+           Skins.QueueMessage(mtInfo, tr("Switching primary DVB..."));
+           cOsdProvider::Shutdown();
+           cDevice::SetPrimaryDevice(NewPrimaryDVB);
+           OldPrimaryDVB = NewPrimaryDVB;
+           }
+
         // SIGHUP shall cause a restart:
         if (LastSignal == SIGHUP) {
            if (ShutdownHandler.ConfirmRestart(true) && Interface->Confirm(tr("Press any key to cancel restart"), RESTARTCANCELPROMPT, true))
@@ -1564,8 +1563,7 @@
   signal(SIGPIPE, SIG_DFL);
   signal(SIGALRM, SIG_DFL);
 
-  StopSVDRPClientHandler();
-  StopSVDRPServerHandler();
+  StopSVDRPHandler();
   ChannelCamRelations.Save();
   cRecordControls::Shutdown();
   PluginManager.StopPlugins();
