diff -ruN vdr-1.4.5/HISTORY vdr-1.5.0/HISTORY
--- vdr-1.4.5/HISTORY	2007-01-07 14:47:19.000000000 +0100
+++ vdr-1.5.0/HISTORY	2007-01-07 15:08:19.000000000 +0100
@@ -5028,3 +5028,56 @@
 2007-01-07: Version 1.4.5
 
 - Official release.
+
+2007-01-07: Version 1.5.0
+
+- The CAM handling has been refactored. Instead of a cCiHandler per device there
+  is now an abstract cCiAdapter and a cCamSlot. This allows each slot to be
+  accessed individually.
+- The general 15 seconds workaround time before opening the CAM menu has been
+  removed. If the CAM menu doesn't open within a timeout, the enter menu command
+  is now sent again.
+- If a CAM is reset or pulled and reinserted, it now automatically starts
+  decrypting the current channel again.
+- The Setup/CAM menu now dynamically refreshes its items and displays whether
+  a CAM is present or ready. The 'Reset' function no longer leaves the menu.
+- The CAM menu will now be openend when pressing the Ok key on a slot entry.
+- The CAM menu now stays within the current menu context and doesn't close and
+  reopen the menu every time an option is selected.
+- When an encrypted channel is switched to for the first time, VDR now checks
+  explicitly whether a CAM can actually decrypt that channel. If there is more
+  than one CAM in the system that claims to be able to decrypt the channel,
+  they are all tried in turn.
+  To make this possible, an encrypted channel needs to be received in Transfer
+  Mode when it is switched to for the first time, so that VDR can determine
+  whether the TS packets are actually decrypted. Once a channel is known to
+  be decrypted by a particular CAM, the next time it is switched to it will
+  be shown in normal live viewing mode.
+- A cDevice now automatically detaches all cReceiver objects that receive PIDs
+  that can't be decrypted with the current CAM. A plugin that attaches a cReceiver
+  to a device should therefore watch the receiver's IsAttached() function to
+  see if it is still attached to the device.
+- The cReceiver constructor no longer takes an 'int Ca' as its first parameter,
+  but rather a 'tChannelID ChannelID'. This is necessary for the device to be
+  able to determine which CAM a particular channel can be decrypted with. If the
+  channel is known to be unencrypted, or a plugin doesn't want to provide the
+  channel id for other reasons, an invalid tChannelID() can be given.
+- The cThread::Start() function now waits until a previous incarnation of this
+  thread has actually stopped. Before this it could happen that a thread's
+  Cancel(-1) function was called and immediately after that it was started again,
+  but the Start() function still found it to be 'active'.
+- The parameter NeedsDetachReceivers in cDevice::GetDevice(const cChannel *Channel, ...)
+  has been removed. A call to this function will automatically detach all receivers
+  from the device if it returns a non-NULL pointer.
+- The cTimeMs class now accepts an initial timeout value in its constructor.
+- A CAM is now explicitly instructed to stop decrypting when switching away from
+  an encrypted channel.
+- If the CAM in use can decrypt several channels at the same time, VDR can
+  now make use if this capability. Whether or not a CAM can decrypt more
+  than one channel is determined by sending it an initial empty QUERY command
+  and testing whether it replies to it.
+- Ca values in the range 0...F in channels.conf can still be used to assign a channel
+  to a particular device, but this will no longer work with encrypted channels because
+  without valid CA ids VDR can't decide which CAM slot to use. However, since VDR now
+  automatically determines which CAM can decrypt which channel, setting fixed
+  channel/device relations should no longer be necessary.
diff -ruN vdr-1.4.5/MANUAL vdr-1.5.0/MANUAL
--- vdr-1.4.5/MANUAL	2006-04-28 15:07:08.000000000 +0200
+++ vdr-1.5.0/MANUAL	2006-11-18 15:50:35.000000000 +0100
@@ -667,13 +667,15 @@
 
   Use DiSEqC = no        Generally turns DiSEqC support on or off.
 
-  CICAM:
+  CAM:
 
-  CICAM DVBn m           Shows the CAMs that each device contains, where 'n' is
-                         the number of the device, and 'm' is the number of the
-                         Common Interface slot of that device. The "Red" key
-                         can be pressed to enter the CAM menu, and the "Green" key
-                         triggers a reset of the selected CAM.
+  n CAM Name             Shows the CAM slots that are present in this system, where
+                         'n' is the number of the slot, followed by the name of the
+                         CAM. If a CAM slot is empty, '-' is displayed as name, and
+                         if it is in the process of being reset, its current status
+                         is displayed.  The "Red" key can be pressed to enter the CAM
+                         menu, and the "Green" key triggers a reset of the selected
+                         slot. The "Ok" key also opens the CAM menu.
 
   Recording:
 
diff -ruN vdr-1.4.5/Makefile vdr-1.5.0/Makefile
--- vdr-1.4.5/Makefile	2006-08-20 12:44:22.000000000 +0200
+++ vdr-1.5.0/Makefile	2006-11-11 14:55:32.000000000 +0100
@@ -4,7 +4,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: Makefile 1.95 2006/08/20 10:44:22 kls Exp $
+# $Id: Makefile 1.96 2006/11/11 13:55:32 kls Exp $
 
 .DELETE_ON_ERROR:
 
@@ -32,7 +32,7 @@
 
 SILIB    = $(LSIDIR)/libsi.a
 
-OBJS = audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdevice.o dvbosd.o\
+OBJS = audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdevice.o dvbci.o dvbosd.o\
        dvbplayer.o dvbspu.o eit.o eitscan.o epg.o filter.o font.o i18n.o interface.o keys.o\
        lirc.o menu.o menuitems.o nit.o osdbase.o osd.o pat.o player.o plugin.o rcu.o\
        receiver.o recorder.o recording.o remote.o remux.o ringbuffer.o sdt.o sections.o\
diff -ruN vdr-1.4.5/PLUGINS.html vdr-1.5.0/PLUGINS.html
--- vdr-1.4.5/PLUGINS.html	2006-05-19 14:11:36.000000000 +0200
+++ vdr-1.5.0/PLUGINS.html	2007-01-07 15:08:55.000000000 +0100
@@ -6,7 +6,7 @@
 
 <center><h1>The VDR Plugin System</h1></center>
 
-<center><b>Version 1.4.1</b></center>
+<center><b>Version 1.5.0</b></center>
 <p>
 <center>
 Copyright &copy; 2006 Klaus Schmidinger<br>
@@ -14,6 +14,10 @@
 <a href="http://www.cadsoft.de/vdr">www.cadsoft.de/vdr</a>
 </center>
 <p>
+<!--X1.5.0--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+Important modifications introduced in version 1.5.0 are marked like this.
+<!--X1.5.0--></td></tr></table>
+<p>
 VDR provides an easy to use plugin interface that allows additional functionality
 to be added to the program by implementing a dynamically loadable library file.
 This interface allows programmers to develop additional functionality for VDR completely
@@ -72,6 +76,9 @@
 <li><a href="#Devices">Devices</a>
 <li><a href="#Audio">Audio</a>
 <li><a href="#Remote Control">Remote Control</a>
+<!--X1.5.0--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+<li><a href="#Conditional Access">Conditional Access</a>
+<!--X1.5.0--></td></tr></table>
 </ul>
 </ul>
 
@@ -1727,6 +1734,7 @@
 
 <p><table><tr><td bgcolor=#F0F0F0><pre>
 virtual bool ProvidesSource(int Source) const;
+virtual bool ProvidesTransponder(const cChannel *Channel) const;
 virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL);
 virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
 </pre></td></tr></table><p>
@@ -2038,5 +2046,42 @@
 
 The other parameters have the same meaning as in the first version of this function.
 
+<!--X1.5.0--><table width=100%><tr><td bgcolor=#FF0000>&nbsp;</td><td width=100%>
+<a name="Conditional Access"><hr><h2>Conditional Access</h2>
+
+<center><i><b>Members only!</b></i></center><p>
+
+Pay TV providers usually encrypt their broadcasts, so that only viewers who
+have the proper smart card can watch them. Such a smart card needs to be inserted
+into a CAM (Conditional Access Module), which in turn goes into a CI (Common
+Interface) slot.
+<p>
+VDR's mechanisms for supporting Conditional Access are mainly the two classes
+<tt>cCiAdapter</tt> and <tt>cCamSlot</tt>. A <tt>cCiAdapter</tt> handles exactly
+one CI, and can provide several CAM slots, represented by the appropriate
+number of <tt>cCamSlot</tt> objects.
+<p>
+In order to decrypt a particular channel, a <tt>cCiAdapter</tt> with a <tt>cCamSlot</tt>
+that contains the necessary CAM will be assigned to a <tt>cDevice</tt>, and exactly
+one of its CAM slots will be activated. Whether or not a <tt>cCiAdapter</tt> can
+be assigned to a particular device depends on the hardware implementation.
+Some devices (like the Siemens/Technotrend DVB cards) are hardwired with their
+CI adapters, so the <tt>cCiAdapter</tt> for these can only be used with one device.
+Other hardware implementations may allow CI adapters and devices to be connected
+through some sort of matrix switch. When trying to decrypt an encrypted channel,
+VDR will automatically select a useful combination of device and CAM slot.
+<p>
+If a plugin implements a derived <tt>cCiAdapter</tt>, it has to implement
+several low level functions that handle the actual data transfer (see <tt>dvbci.c</tt>
+for example). The decision whether the adapter can actually be assigned to different
+devices is made in the function
+
+<p><table><tr><td bgcolor=#F0F0F0><pre>
+virtual bool Assign(cDevice *Device, bool Query = false);
+</pre></td></tr></table><p>
+
+See the description of this function in <tt>ci.h</tt> for details.
+<!--X1.5.0--></td></tr></table>
+
 </body>
 </html>
diff -ruN vdr-1.4.5/channels.h vdr-1.5.0/channels.h
--- vdr-1.4.5/channels.h	2006-05-28 17:03:56.000000000 +0200
+++ vdr-1.5.0/channels.h	2007-01-05 11:37:35.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: channels.h 1.42 2006/05/28 15:03:56 kls Exp $
+ * $Id: channels.h 1.43 2007/01/05 10:37:35 kls Exp $
  */
 
 #ifndef __CHANNELS_H
@@ -179,6 +179,7 @@
   const char *Dlang(int i) const { return (0 <= i && i < MAXDPIDS) ? dlangs[i] : ""; }
   const char *Slang(int i) const { return (0 <= i && i < MAXSPIDS) ? slangs[i] : ""; }
   int Tpid(void) const { return tpid; }
+  const int *Caids(void) const { return caids; }
   int Ca(int Index = 0) const { return Index < MAXCAIDS ? caids[Index] : 0; }
   int Nid(void) const { return nid; }
   int Tid(void) const { return tid; }
diff -ruN vdr-1.4.5/ci.c vdr-1.5.0/ci.c
--- vdr-1.4.5/ci.c	2007-01-05 11:08:46.000000000 +0100
+++ vdr-1.5.0/ci.c	2007-01-05 11:38: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 1.46 2007/01/05 10:08:46 kls Exp $
+ * $Id: ci.c 1.47 2007/01/05 10:38:03 kls Exp $
  */
 
 #include "ci.h"
@@ -17,38 +17,18 @@
 #include <sys/ioctl.h>
 #include <time.h>
 #include <unistd.h>
+#include "device.h"
 #include "pat.h"
 #include "tools.h"
 
-/* these might come in handy in case you want to use this code without VDR's other files:
-#ifndef MALLOC
-#define MALLOC(type, size)  (type *)malloc(sizeof(type) * (size))
-#endif
-
-#ifndef esyslog
-static int SysLogLevel = 3;
-#define esyslog(a...) void( (SysLogLevel > 0) ? void(fprintf(stderr, a)), void(fprintf(stderr, "\n")) : void() )
-#define isyslog(a...) void( (SysLogLevel > 1) ? void(fprintf(stderr, a)), void(fprintf(stderr, "\n")) : void() )
-#define dsyslog(a...) void( (SysLogLevel > 2) ? void(fprintf(stderr, a)), void(fprintf(stderr, "\n")) : void() )
-#endif
-*/
-
 // Set these to 'true' for debug output:
 static bool DumpTPDUDataTransfer = false;
 static bool DebugProtocol = false;
+static bool DumpPolls = false;
+static bool DumpDateTime = false;
 
 #define dbgprotocol(a...) if (DebugProtocol) fprintf(stderr, a)
 
-#define OK       0
-#define TIMEOUT -1
-#define ERROR   -2
-
-// --- Workarounds -----------------------------------------------------------
-
-// The Irdeto AllCAM 4.7 (and maybe others, too) does not react on AOT_ENTER_MENU
-// during the first few seconds of a newly established connection
-#define WRKRND_TIME_BEFORE_ENTER_MENU  15 // seconds
-
 // --- Helper functions ------------------------------------------------------
 
 #define SIZE_INDICATOR 0x80
@@ -144,27 +124,29 @@
 class cTPDU {
 private:
   int size;
-  uint8_t data[MAX_TPDU_SIZE];
+  uint8_t buffer[MAX_TPDU_SIZE];
   const uint8_t *GetData(const uint8_t *Data, int &Length);
 public:
   cTPDU(void) { size = 0; }
   cTPDU(uint8_t Slot, uint8_t Tcid, uint8_t Tag, int Length = 0, const uint8_t *Data = NULL);
-  uint8_t Slot(void) { return data[0]; }
-  uint8_t Tcid(void) { return data[1]; }
-  uint8_t Tag(void)  { return data[2]; }
-  const uint8_t *Data(int &Length) { return GetData(data + 3, Length); }
+  uint8_t Slot(void) { return buffer[0]; }
+  uint8_t Tcid(void) { return buffer[1]; }
+  uint8_t Tag(void)  { return buffer[2]; }
+  const uint8_t *Data(int &Length) { return GetData(buffer + 3, Length); }
   uint8_t Status(void);
-  int Write(int fd);
-  int Read(int fd);
-  void Dump(int fd, bool Outgoing);
+  uint8_t *Buffer(void) { return buffer; }
+  int Size(void) { return size; }
+  void SetSize(int Size) { size = Size; }
+  int MaxSize(void) { return sizeof(buffer); }
+  void Dump(int SlotNumber, bool Outgoing);
   };
 
 cTPDU::cTPDU(uint8_t Slot, uint8_t Tcid, uint8_t Tag, int Length, const uint8_t *Data)
 {
   size = 0;
-  data[0] = Slot;
-  data[1] = Tcid;
-  data[2] = Tag;
+  buffer[0] = Slot;
+  buffer[1] = Tcid;
+  buffer[2] = Tag;
   switch (Tag) {
     case T_RCV:
     case T_CREATE_TC:
@@ -172,72 +154,51 @@
     case T_DELETE_TC:
     case T_DTC_REPLY:
     case T_REQUEST_TC:
-         data[3] = 1; // length
-         data[4] = Tcid;
+         buffer[3] = 1; // length
+         buffer[4] = Tcid;
          size = 5;
          break;
     case T_NEW_TC:
     case T_TC_ERROR:
          if (Length == 1) {
-            data[3] = 2; // length
-            data[4] = Tcid;
-            data[5] = Data[0];
+            buffer[3] = 2; // length
+            buffer[4] = Tcid;
+            buffer[5] = Data[0];
             size = 6;
             }
          else
-            esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d", Tag, Length);
+            esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d (%d/%d)", Tag, Length, Slot, Tcid);
          break;
     case T_DATA_LAST:
     case T_DATA_MORE:
          if (Length <= MAX_TPDU_DATA) {
-            uint8_t *p = data + 3;
+            uint8_t *p = buffer + 3;
             p = SetLength(p, Length + 1);
             *p++ = Tcid;
             if (Length)
                memcpy(p, Data, Length);
-            size = Length + (p - data);
+            size = Length + (p - buffer);
             }
          else
-            esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d", Tag, Length);
+            esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d (%d/%d)", Tag, Length, Slot, Tcid);
          break;
     default:
-         esyslog("ERROR: unknown TPDU tag: 0x%02X", Tag);
+         esyslog("ERROR: unknown TPDU tag: 0x%02X (%d/%d)", Tag, Slot, Tcid);
     }
  }
 
-int cTPDU::Write(int fd)
-{
-  Dump(fd, true);
-  if (size)
-     return safe_write(fd, data, size) == size ? OK : ERROR;
-  esyslog("ERROR: attemp to write TPDU with zero size");
-  return ERROR;
-}
-
-int cTPDU::Read(int fd)
-{
-  size = safe_read(fd, data, sizeof(data));
-  if (size < 0) {
-     esyslog("ERROR: %m");
-     size = 0;
-     return ERROR;
-     }
-  Dump(fd, false);
-  return OK;
-}
-
-void cTPDU::Dump(int fd, bool Outgoing)
+void cTPDU::Dump(int SlotNumber, bool Outgoing)
 {
-  if (DumpTPDUDataTransfer) {
+  if (DumpTPDUDataTransfer && (DumpPolls || Tag() != T_SB)) {
 #define MAX_DUMP 256
-     fprintf(stderr, "%2d %s ", fd, Outgoing ? "-->" : "<--");
+     fprintf(stderr, "     %d: %s ", SlotNumber, Outgoing ? "-->" : "<--");
      for (int i = 0; i < size && i < MAX_DUMP; i++)
-         fprintf(stderr, "%02X ", data[i]);
+         fprintf(stderr, "%02X ", buffer[i]);
      fprintf(stderr, "%s\n", size >= MAX_DUMP ? "..." : "");
      if (!Outgoing) {
-        fprintf(stderr, "      ");
+        fprintf(stderr, "           ");
         for (int i = 0; i < size && i < MAX_DUMP; i++)
-            fprintf(stderr, "%2c ", isprint(data[i]) ? data[i] : '.');
+            fprintf(stderr, "%2c ", isprint(buffer[i]) ? buffer[i] : '.');
         fprintf(stderr, "%s\n", size >= MAX_DUMP ? "..." : "");
         }
      }
@@ -257,301 +218,51 @@
 
 uint8_t cTPDU::Status(void)
 {
-  if (size >= 4 && data[size - 4] == T_SB && data[size - 3] == 2) {
-     //XXX test tcid???
-     return data[size - 1];
-     }
+  if (size >= 4 && buffer[size - 4] == T_SB && buffer[size - 3] == 2)
+     return buffer[size - 1];
   return 0;
 }
 
 // --- cCiTransportConnection ------------------------------------------------
 
-enum eState { stIDLE, stCREATION, stACTIVE, stDELETION };
+#define MAX_SESSIONS_PER_TC  16
 
 class cCiTransportConnection {
-  friend class cCiTransportLayer;
 private:
-  int fd;
-  uint8_t slot;
+  enum eState { stIDLE, stCREATION, stACTIVE, stDELETION };
+  cCamSlot *camSlot;
   uint8_t tcid;
   eState state;
-  cTPDU *tpdu;
-  int lastResponse;
-  bool dataAvailable;
-  void Init(int Fd, uint8_t Slot, uint8_t Tcid);
-  int SendTPDU(uint8_t Tag, int Length = 0, const uint8_t *Data = NULL);
-  int RecvTPDU(void);
-  int CreateConnection(void);
-  int Poll(void);
-  eState State(void) { return state; }
-  int LastResponse(void) { return lastResponse; }
-  bool DataAvailable(void) { return dataAvailable; }
+  bool createConnectionRequested;
+  bool deleteConnectionRequested;
+  bool hasUserIO;
+  cTimeMs alive;
+  cTimeMs timer;
+  cCiSession *sessions[MAX_SESSIONS_PER_TC + 1]; // session numbering starts with 1
+  void SendTPDU(uint8_t Tag, int Length = 0, const uint8_t *Data = NULL);
+  void SendTag(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId = 0, int Status = -1);
+  void Poll(void);
+  uint32_t ResourceIdToInt(const uint8_t *Data);
+  cCiSession *GetSessionBySessionId(uint16_t SessionId);
+  void OpenSession(int Length, const uint8_t *Data);
+  void CloseSession(uint16_t SessionId);
+  void HandleSessions(cTPDU *TPDU);
 public:
-  cCiTransportConnection(void);
-  ~cCiTransportConnection();
-  int Slot(void) const { return slot; }
-  int SendData(int Length, const uint8_t *Data);
-  int RecvData(void);
-  const uint8_t *Data(int &Length);
-  //XXX Close()
-  void Reset(void);
+  cCiTransportConnection(cCamSlot *CamSlot, uint8_t Tcid);
+  virtual ~cCiTransportConnection();
+  cCamSlot *CamSlot(void) { return camSlot; }
+  uint8_t Tcid(void) const { return tcid; }
+  void CreateConnection(void) { createConnectionRequested = true; }
+  void DeleteConnection(void) { deleteConnectionRequested = true; }
+  const char *GetCamName(void);
+  bool Ready(void);
+  bool HasUserIO(void) { return hasUserIO; }
+  void SendData(int Length, const uint8_t *Data);
+  bool Process(cTPDU *TPDU = NULL);
+  cCiSession *GetSessionByResourceId(uint32_t ResourceId);
   };
 
-cCiTransportConnection::cCiTransportConnection(void)
-{
-  tpdu = NULL;
-  Init(-1, 0, 0);
-}
-
-cCiTransportConnection::~cCiTransportConnection()
-{
-  delete tpdu;
-}
-
-void cCiTransportConnection::Init(int Fd, uint8_t Slot, uint8_t Tcid)
-{
-  fd = Fd;
-  slot = Slot;
-  tcid = Tcid;
-  state = stIDLE;
-  if (fd >= 0 && !tpdu)
-     tpdu = new cTPDU;
-  lastResponse = ERROR;
-  dataAvailable = false;
-//XXX Clear()???
-}
-
-int cCiTransportConnection::SendTPDU(uint8_t Tag, int Length, const uint8_t *Data)
-{
-  cTPDU TPDU(slot, tcid, Tag, Length, Data);
-  return TPDU.Write(fd);
-}
-
-#define CAM_READ_TIMEOUT  3500 // ms
-
-int cCiTransportConnection::RecvTPDU(void)
-{
-  struct pollfd pfd[1];
-  pfd[0].fd = fd;
-  pfd[0].events = POLLIN;
-  lastResponse = ERROR;
-  if (poll(pfd, 1, CAM_READ_TIMEOUT) > 0 && (pfd[0].revents & POLLIN) && tpdu->Read(fd) == OK && tpdu->Tcid() == tcid) {
-     switch (state) {
-       case stIDLE:     break;
-       case stCREATION: if (tpdu->Tag() == T_CTC_REPLY) {
-                           dataAvailable = tpdu->Status() & DATA_INDICATOR;
-                           state = stACTIVE;
-                           lastResponse = tpdu->Tag();
-                           }
-                        break;
-       case stACTIVE:   switch (tpdu->Tag()) {
-                          case T_SB:
-                          case T_DATA_LAST:
-                          case T_DATA_MORE:
-                          case T_REQUEST_TC: break;
-                          case T_DELETE_TC:  if (SendTPDU(T_DTC_REPLY) != OK)
-                                                return ERROR;
-                                             Init(fd, slot, tcid);
-                                             break;
-                          default: return ERROR;
-                          }
-                        dataAvailable = tpdu->Status() & DATA_INDICATOR;
-                        lastResponse = tpdu->Tag();
-                        break;
-       case stDELETION: if (tpdu->Tag() == T_DTC_REPLY) {
-                           Init(fd, slot, tcid);
-                           //XXX Status()???
-                           lastResponse = tpdu->Tag();
-                           }
-                        break;
-       }
-     }
-  else if (FATALERRNO) {
-     esyslog("ERROR: CAM: Read failed: slot %d, tcid %d - %m", slot, tcid);
-     Init(-1, slot, tcid);
-     }
-  return lastResponse;
-}
-
-int cCiTransportConnection::SendData(int Length, const uint8_t *Data)
-{
-  while (state == stACTIVE && Length > 0) {
-        uint8_t Tag = T_DATA_LAST;
-        int l = Length;
-        if (l > MAX_TPDU_DATA) {
-           Tag = T_DATA_MORE;
-           l = MAX_TPDU_DATA;
-           }
-        if (SendTPDU(Tag, l, Data) != OK || RecvTPDU() != T_SB)
-           break;
-        Length -= l;
-        Data += l;
-        }
-  return Length ? ERROR : OK;
-}
-
-int cCiTransportConnection::RecvData(void)
-{
-  if (SendTPDU(T_RCV) == OK)
-     return RecvTPDU();
-  return ERROR;
-}
-
-const uint8_t *cCiTransportConnection::Data(int &Length)
-{
-  return tpdu->Data(Length);
-}
-
-#define MAX_CONNECT_RETRIES  2
-
-int cCiTransportConnection::CreateConnection(void)
-{
-  if (state == stIDLE) {
-     if (SendTPDU(T_CREATE_TC) == OK) {
-        state = stCREATION;
-        if (RecvTPDU() == T_CTC_REPLY)
-           return OK;
-        // the following is a workaround for CAMs that don't quite follow the specs...
-        else {
-           for (int i = 0; i < MAX_CONNECT_RETRIES; i++) {
-               dsyslog("CAM: retrying to establish connection");
-               if (RecvTPDU() == T_CTC_REPLY) {
-                  dsyslog("CAM: connection established");
-                  return OK;
-                  }
-               }
-           return ERROR;
-           }
-        }
-     }
-  return ERROR;
-}
-
-int cCiTransportConnection::Poll(void)
-{
-  if (state == stACTIVE) {
-     if (SendTPDU(T_DATA_LAST) == OK)
-        return RecvTPDU();
-     }
-  return ERROR;
-}
-
-void cCiTransportConnection::Reset(void)
-{
-  Init(-1, 0, 0);
-}
-
-// --- cCiTransportLayer -----------------------------------------------------
-
-#define MAX_CI_CONNECT  16 // maximum possible value is 254
-
-class cCiTransportLayer {
-private:
-  int fd;
-  int numSlots;
-  cCiTransportConnection tc[MAX_CI_CONNECT];
-public:
-  cCiTransportLayer(int Fd, int NumSlots);
-  cCiTransportConnection *NewConnection(int Slot);
-  bool ResetSlot(int Slot, bool Wait = false);
-  bool ModuleReady(int Slot);
-  cCiTransportConnection *Process(int Slot);
-  };
-
-cCiTransportLayer::cCiTransportLayer(int Fd, int NumSlots)
-{
-  fd = Fd;
-  numSlots = NumSlots;
-  for (int s = 0; s < numSlots; s++)
-      ResetSlot(s);
-  cCondWait::SleepMs(2000);
-}
-
-cCiTransportConnection *cCiTransportLayer::NewConnection(int Slot)
-{
-  for (int i = 0; i < MAX_CI_CONNECT; i++) {
-      if (tc[i].State() == stIDLE) {
-         dbgprotocol("Creating connection: slot %d, tcid %d\n", Slot, i + 1);
-         tc[i].Init(fd, Slot, i + 1);
-         if (tc[i].CreateConnection() == OK)
-            return &tc[i];
-         break;
-         }
-      }
-  return NULL;
-}
-
-bool cCiTransportLayer::ResetSlot(int Slot, bool Wait)
-{
-  for (int i = 0; i < MAX_CI_CONNECT; i++) {
-      if (tc[i].State() != stIDLE && tc[i].Slot() == Slot)
-         tc[i].Reset();
-      }
-  dbgprotocol("Resetting slot %d...", Slot);
-  if (ioctl(fd, CA_RESET, 1 << Slot) != -1) {
-     if (Wait)
-        cCondWait::SleepMs(2000);
-     dbgprotocol("ok.\n");
-     return true;
-     }
-  else
-     esyslog("ERROR: can't reset CAM slot %d: %m", Slot);
-  dbgprotocol("failed!\n");
-  return false;
-}
-
-bool cCiTransportLayer::ModuleReady(int Slot)
-{
-  ca_slot_info_t sinfo;
-  sinfo.num = Slot;
-  if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1)
-     return sinfo.flags & CA_CI_MODULE_READY;
-  else
-     esyslog("ERROR: can't get info on CAM slot %d: %m", Slot);
-  return false;
-}
-
-cCiTransportConnection *cCiTransportLayer::Process(int Slot)
-{
-  for (int i = 0; i < MAX_CI_CONNECT; i++) {
-      cCiTransportConnection *Tc = &tc[i];
-      if (Tc->Slot() == Slot) {
-         switch (Tc->State()) {
-           case stCREATION:
-           case stACTIVE:
-                if (!Tc->DataAvailable()) {
-                   if (Tc->Poll() != OK)
-                      ;//XXX continue;
-                   }
-                switch (Tc->LastResponse()) {
-                  case T_REQUEST_TC:
-                       //XXX
-                       break;
-                  case T_DATA_MORE:
-                  case T_DATA_LAST:
-                  case T_CTC_REPLY:
-                  case T_SB:
-                       if (Tc->DataAvailable())
-                          Tc->RecvData();
-                       break;
-                  case TIMEOUT:
-                  case ERROR:
-                  default:
-                       //XXX Tc->state = stIDLE;//XXX Init()???
-                       return NULL;
-                       break;
-                  }
-                //XXX this will only work with _one_ transport connection per slot!
-                return Tc;
-                break;
-           default: ;
-           }
-         }
-      }
-  return NULL;
-}
-
-// -- cCiSession -------------------------------------------------------------
+// --- cCiSession ------------------------------------------------------------
 
 // Session Tags:
 
@@ -636,15 +347,15 @@
 protected:
   int GetTag(int &Length, const uint8_t **Data);
   const uint8_t *GetData(const uint8_t *Data, int &Length);
-  int SendData(int Tag, int Length = 0, const uint8_t *Data = NULL);
+  void SendData(int Tag, int Length = 0, const uint8_t *Data = NULL);
+  cCiTransportConnection *Tc(void) { return tc; }
 public:
   cCiSession(uint16_t SessionId, uint32_t ResourceId, cCiTransportConnection *Tc);
   virtual ~cCiSession();
-  const cCiTransportConnection *Tc(void) { return tc; }
   uint16_t SessionId(void) { return sessionId; }
   uint32_t ResourceId(void) { return resourceId; }
   virtual bool HasUserIO(void) { return false; }
-  virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
+  virtual void Process(int Length = 0, const uint8_t *Data = NULL);
   };
 
 cCiSession::cCiSession(uint16_t SessionId, uint32_t ResourceId, cCiTransportConnection *Tc)
@@ -679,7 +390,7 @@
   return Length ? Data : NULL;
 }
 
-int cCiSession::SendData(int Tag, int Length, const uint8_t *Data)
+void cCiSession::SendData(int Tag, int Length, const uint8_t *Data)
 {
   uint8_t buffer[2048];
   uint8_t *p = buffer;
@@ -694,78 +405,75 @@
   if (p - buffer + Length < int(sizeof(buffer))) {
      memcpy(p, Data, Length);
      p += Length;
-     return tc->SendData(p - buffer, buffer);
+     tc->SendData(p - buffer, buffer);
      }
-  esyslog("ERROR: CAM: data length (%d) exceeds buffer size", Length);
-  return ERROR;
+  else
+     esyslog("ERROR: CAM %d: data length (%d) exceeds buffer size", Tc()->CamSlot()->SlotNumber(), Length);
 }
 
-bool cCiSession::Process(int Length, const uint8_t *Data)
+void cCiSession::Process(int Length, const uint8_t *Data)
 {
-  return true;
 }
 
-// -- cCiResourceManager -----------------------------------------------------
+// --- cCiResourceManager ----------------------------------------------------
 
 class cCiResourceManager : public cCiSession {
 private:
   int state;
 public:
   cCiResourceManager(uint16_t SessionId, cCiTransportConnection *Tc);
-  virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
+  virtual void Process(int Length = 0, const uint8_t *Data = NULL);
   };
 
 cCiResourceManager::cCiResourceManager(uint16_t SessionId, cCiTransportConnection *Tc)
 :cCiSession(SessionId, RI_RESOURCE_MANAGER, Tc)
 {
-  dbgprotocol("New Resource Manager (session id %d)\n", SessionId);
+  dbgprotocol("Slot %d: new Resource Manager (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId);
   state = 0;
 }
 
-bool cCiResourceManager::Process(int Length, const uint8_t *Data)
+void cCiResourceManager::Process(int Length, const uint8_t *Data)
 {
   if (Data) {
      int Tag = GetTag(Length, &Data);
      switch (Tag) {
        case AOT_PROFILE_ENQ: {
-            dbgprotocol("%d: <== Profile Enquiry\n", SessionId());
+            dbgprotocol("Slot %d: <== Profile Enquiry (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
             uint32_t resources[] = { htonl(RI_RESOURCE_MANAGER),
                                      htonl(RI_APPLICATION_INFORMATION),
                                      htonl(RI_CONDITIONAL_ACCESS_SUPPORT),
                                      htonl(RI_DATE_TIME),
                                      htonl(RI_MMI)
                                    };
-            dbgprotocol("%d: ==> Profile\n", SessionId());
+            dbgprotocol("Slot %d: ==> Profile (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
             SendData(AOT_PROFILE, sizeof(resources), (uint8_t*)resources);
             state = 3;
             }
             break;
        case AOT_PROFILE: {
-            dbgprotocol("%d: <== Profile\n", SessionId());
+            dbgprotocol("Slot %d: <== Profile (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
             if (state == 1) {
                int l = 0;
                const uint8_t *d = GetData(Data, l);
                if (l > 0 && d)
-                  esyslog("CI resource manager: unexpected data");
-               dbgprotocol("%d: ==> Profile Change\n", SessionId());
+                  esyslog("ERROR: CAM %d: resource manager: unexpected data", Tc()->CamSlot()->SlotNumber());
+               dbgprotocol("Slot %d: ==> Profile Change (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
                SendData(AOT_PROFILE_CHANGE);
                state = 2;
                }
             else {
-               esyslog("ERROR: CI resource manager: unexpected tag %06X in state %d", Tag, state);
+               esyslog("ERROR: CAM %d: resource manager: unexpected tag %06X in state %d", Tc()->CamSlot()->SlotNumber(), Tag, state);
                }
             }
             break;
-       default: esyslog("ERROR: CI resource manager: unknown tag %06X", Tag);
-                return false;
+       default: esyslog("ERROR: CAM %d: resource manager: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag);
        }
      }
   else if (state == 0) {
-     dbgprotocol("%d: ==> Profile Enq\n", SessionId());
+     dbgprotocol("Slot %d: ==> Profile Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
      SendData(AOT_PROFILE_ENQ);
      state = 1;
      }
-  return true;
 }
 
 // --- cCiApplicationInformation ---------------------------------------------
@@ -773,7 +481,6 @@
 class cCiApplicationInformation : public cCiSession {
 private:
   int state;
-  time_t creationTime;
   uint8_t applicationType;
   uint16_t applicationManufacturer;
   uint16_t manufacturerCode;
@@ -781,7 +488,7 @@
 public:
   cCiApplicationInformation(uint16_t SessionId, cCiTransportConnection *Tc);
   virtual ~cCiApplicationInformation();
-  virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
+  virtual void Process(int Length = 0, const uint8_t *Data = NULL);
   bool EnterMenu(void);
   const char *GetMenuString(void) { return menuString; }
   };
@@ -789,9 +496,8 @@
 cCiApplicationInformation::cCiApplicationInformation(uint16_t SessionId, cCiTransportConnection *Tc)
 :cCiSession(SessionId, RI_APPLICATION_INFORMATION, Tc)
 {
-  dbgprotocol("New Application Information (session id %d)\n", SessionId);
+  dbgprotocol("Slot %d: new Application Information (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId);
   state = 0;
-  creationTime = time(NULL);
   menuString = NULL;
 }
 
@@ -800,13 +506,13 @@
   free(menuString);
 }
 
-bool cCiApplicationInformation::Process(int Length, const uint8_t *Data)
+void cCiApplicationInformation::Process(int Length, const uint8_t *Data)
 {
   if (Data) {
      int Tag = GetTag(Length, &Data);
      switch (Tag) {
        case AOT_APPLICATION_INFO: {
-            dbgprotocol("%d: <== Application Info\n", SessionId());
+            dbgprotocol("Slot %d: <== Application Info (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
             int l = 0;
             const uint8_t *d = GetData(Data, l);
             if ((l -= 1) < 0) break;
@@ -819,28 +525,26 @@
             d += 2;
             free(menuString);
             menuString = GetString(l, &d);
-            isyslog("CAM: %s, %02X, %04X, %04X", menuString, applicationType, applicationManufacturer, manufacturerCode);//XXX make externally accessible!
-            }
+            isyslog("CAM %d: %s, %02X, %04X, %04X", Tc()->CamSlot()->SlotNumber(), menuString, applicationType, applicationManufacturer, manufacturerCode);
             state = 2;
+            }
             break;
-       default: esyslog("ERROR: CI application information: unknown tag %06X", Tag);
-                return false;
+       default: esyslog("ERROR: CAM %d: application information: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag);
        }
      }
   else if (state == 0) {
-     dbgprotocol("%d: ==> Application Info Enq\n", SessionId());
+     dbgprotocol("Slot %d: ==> Application Info Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
      SendData(AOT_APPLICATION_INFO_ENQ);
      state = 1;
      }
-  return true;
 }
 
 bool cCiApplicationInformation::EnterMenu(void)
 {
-  if (state == 2 && time(NULL) - creationTime > WRKRND_TIME_BEFORE_ENTER_MENU) {
-     dbgprotocol("%d: ==> Enter Menu\n", SessionId());
+  if (state == 2) {
+     dbgprotocol("Slot %d: ==> Enter Menu (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
      SendData(AOT_ENTER_MENU);
-     return true;//XXX
+     return true;
      }
   return false;
 }
@@ -875,13 +579,14 @@
   bool streamFlag;
   void AddCaDescriptors(int Length, const uint8_t *Data);
 public:
-  cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const unsigned short *CaSystemIds);
+  cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds);
+  uint8_t CmdId(void) { return cmdId; }
   void SetListManagement(uint8_t ListManagement);
-  bool Valid(void);
+  uint8_t ListManagement(void) { return capmt[0]; }
   void AddPid(int Pid, uint8_t StreamType);
   };
 
-cCiCaPmt::cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const unsigned short *CaSystemIds)
+cCiCaPmt::cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds)
 {
   cmdId = CmdId;
   caDescriptorsLength = GetCaDescriptors(Source, Transponder, ProgramNumber, CaSystemIds, sizeof(caDescriptors), caDescriptors, streamFlag);
@@ -902,11 +607,6 @@
   capmt[0] = ListManagement;
 }
 
-bool cCiCaPmt::Valid(void)
-{
-  return caDescriptorsLength > 0;
-}
-
 void cCiCaPmt::AddPid(int Pid, uint8_t StreamType)
 {
   if (Pid) {
@@ -957,61 +657,77 @@
 
 #define CA_ENABLE(x) (((x) & CA_ENABLE_FLAG) ? (x) & ~CA_ENABLE_FLAG : 0)
 
+#define QUERY_WAIT_TIME      1000 // ms to wait before sending a query
+#define QUERY_REPLY_TIMEOUT  2000 // ms to wait for a reply to a query
+
 class cCiConditionalAccessSupport : public cCiSession {
 private:
   int state;
   int numCaSystemIds;
-  unsigned short caSystemIds[MAXCASYSTEMIDS + 1]; // list is zero terminated!
+  int caSystemIds[MAXCASYSTEMIDS + 1]; // list is zero terminated!
+  bool repliesToQuery;
+  cTimeMs timer;
 public:
   cCiConditionalAccessSupport(uint16_t SessionId, cCiTransportConnection *Tc);
-  virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
-  const unsigned short *GetCaSystemIds(void) { return caSystemIds; }
-  bool SendPMT(cCiCaPmt *CaPmt);
-  bool ReceivedReply(bool CanDescramble = false);
+  virtual void Process(int Length = 0, const uint8_t *Data = NULL);
+  const int *GetCaSystemIds(void) { return caSystemIds; }
+  void SendPMT(cCiCaPmt *CaPmt);
+  bool RepliesToQuery(void) { return repliesToQuery; }
+  bool Ready(void) { return state >= 4; }
+  bool ReceivedReply(void) { return state >= 5; }
+  bool CanDecrypt(void) { return state == 6; }
   };
 
 cCiConditionalAccessSupport::cCiConditionalAccessSupport(uint16_t SessionId, cCiTransportConnection *Tc)
 :cCiSession(SessionId, RI_CONDITIONAL_ACCESS_SUPPORT, Tc)
 {
-  dbgprotocol("New Conditional Access Support (session id %d)\n", SessionId);
+  dbgprotocol("Slot %d: new Conditional Access Support (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId);
   state = 0; // inactive
   caSystemIds[numCaSystemIds = 0] = 0;
+  repliesToQuery = false;
 }
 
-bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data)
+void cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data)
 {
   if (Data) {
      int Tag = GetTag(Length, &Data);
      switch (Tag) {
        case AOT_CA_INFO: {
-            dbgprotocol("%d: <== Ca Info", SessionId());
+            dbgprotocol("Slot %d: <== Ca Info (%d)", Tc()->CamSlot()->SlotNumber(), SessionId());
             numCaSystemIds = 0;
             int l = 0;
             const uint8_t *d = GetData(Data, l);
             while (l > 1) {
-                  unsigned short id = ((unsigned short)(*d) << 8) | *(d + 1);
+                  uint16_t id = ((uint16_t)(*d) << 8) | *(d + 1);
                   dbgprotocol(" %04X", id);
                   d += 2;
                   l -= 2;
                   if (numCaSystemIds < MAXCASYSTEMIDS)
                      caSystemIds[numCaSystemIds++] = id;
                   else {
-                     esyslog("ERROR: too many CA system IDs!");
+                     esyslog("ERROR: CAM %d: too many CA system IDs!", Tc()->CamSlot()->SlotNumber());
                      break;
                      }
                   }
             caSystemIds[numCaSystemIds] = 0;
             dbgprotocol("\n");
+            if (state == 1) {
+               timer.Set(QUERY_WAIT_TIME); // WORKAROUND: Alphacrypt 3.09 doesn't reply to QUERY immediately after reset
+               state = 2; // got ca info
+               }
             }
-            state = 2; // got ca info
             break;
        case AOT_CA_PMT_REPLY: {
-            dbgprotocol("%d: <== Ca Pmt Reply", SessionId());
-            state = 4; // got ca pmt reply
+            dbgprotocol("Slot %d: <== Ca Pmt Reply (%d)", Tc()->CamSlot()->SlotNumber(), SessionId());
+            if (!repliesToQuery) {
+               dsyslog("CAM %d: replies to QUERY - multi channel decryption possible", Tc()->CamSlot()->SlotNumber());
+               repliesToQuery = true;
+               }
+            state = 5; // got ca pmt reply
             int l = 0;
             const uint8_t *d = GetData(Data, l);
             if (l > 1) {
-               unsigned short pnr = ((unsigned short)(*d) << 8) | *(d + 1);
+               uint16_t pnr = ((uint16_t)(*d) << 8) | *(d + 1);
                dbgprotocol(" %d", pnr);
                d += 2;
                l -= 2;
@@ -1026,7 +742,7 @@
                         // CAMs (for instance the AlphaCrypt with firmware <= 3.05) that
                         // insert a two byte length field here.
                         // This is a workaround to skip this length field:
-                        unsigned short len = ((unsigned short)(*d) << 8) | *(d + 1);
+                        uint16_t len = ((uint16_t)(*d) << 8) | *(d + 1);
                         if (len == l - 2) {
                            d += 2;
                            l -= 2;
@@ -1040,7 +756,7 @@
                      if (l <= 2)
                         ok = CA_ENABLE(caepl) == CAEI_POSSIBLE;
                      while (l > 2) {
-                           unsigned short pid = ((unsigned short)(*d) << 8) | *(d + 1);
+                           uint16_t pid = ((uint16_t)(*d) << 8) | *(d + 1);
                            unsigned char caees = *(d + 2);
                            dbgprotocol(" %d=%02X", pid, caees);
                            d += 3;
@@ -1049,39 +765,40 @@
                               ok = false;
                            }
                      if (ok)
-                        state = 5; // descrambling possible
+                        state = 6; // descrambling possible
                      }
                   }
                }
             dbgprotocol("\n");
             }
             break;
-       default: esyslog("ERROR: CI conditional access support: unknown tag %06X", Tag);
-                return false;
+       default: esyslog("ERROR: CAM %d: conditional access support: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag);
        }
      }
   else if (state == 0) {
-     dbgprotocol("%d: ==> Ca Info Enq\n", SessionId());
+     dbgprotocol("Slot %d: ==> Ca Info Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
      SendData(AOT_CA_INFO_ENQ);
      state = 1; // enquired ca info
      }
-  return true;
+  else if (state == 2 && timer.TimedOut()) {
+     cCiCaPmt CaPmt(CPCI_QUERY, 0, 0, 0, NULL);
+     SendPMT(&CaPmt);
+     timer.Set(QUERY_REPLY_TIMEOUT);
+     state = 3; // waiting for reply
+     }
+  else if (state == 3 && timer.TimedOut()) {
+     dsyslog("CAM %d: doesn't reply to QUERY - only a single channel can be decrypted", Tc()->CamSlot()->SlotNumber());
+     state = 4; // normal operation
+     }
 }
 
-bool cCiConditionalAccessSupport::SendPMT(cCiCaPmt *CaPmt)
+void cCiConditionalAccessSupport::SendPMT(cCiCaPmt *CaPmt)
 {
   if (CaPmt && state >= 2) {
-     dbgprotocol("%d: ==> Ca Pmt\n", SessionId());
+     dbgprotocol("Slot %d: ==> Ca Pmt (%d) %d %d\n", Tc()->CamSlot()->SlotNumber(), SessionId(), CaPmt->ListManagement(), CaPmt->CmdId());
      SendData(AOT_CA_PMT, CaPmt->length, CaPmt->capmt);
-     state = 3; // sent ca pmt
-     return true;
+     state = 4; // sent ca pmt
      }
-  return false;
-}
-
-bool cCiConditionalAccessSupport::ReceivedReply(bool CanDescramble)
-{
-  return state >= (CanDescramble ? 5 : 4);
 }
 
 // --- cCiDateTime -----------------------------------------------------------
@@ -1090,10 +807,10 @@
 private:
   int interval;
   time_t lastTime;
-  bool SendDateTime(void);
+  void SendDateTime(void);
 public:
   cCiDateTime(uint16_t SessionId, cCiTransportConnection *Tc);
-  virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
+  virtual void Process(int Length = 0, const uint8_t *Data = NULL);
   };
 
 cCiDateTime::cCiDateTime(uint16_t SessionId, cCiTransportConnection *Tc)
@@ -1101,10 +818,10 @@
 {
   interval = 0;
   lastTime = 0;
-  dbgprotocol("New Date Time (session id %d)\n", SessionId);
+  dbgprotocol("Slot %d: new Date Time (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId);
 }
 
-bool cCiDateTime::SendDateTime(void)
+void cCiDateTime::SendDateTime(void)
 {
   time_t t = time(NULL);
   struct tm tm_gmt;
@@ -1116,17 +833,18 @@
      int L = (M == 1 || M == 2) ? 1 : 0;
      int MJD = 14956 + D + int((Y - L) * 365.25) + int((M + 1 + L * 12) * 30.6001);
 #define DEC2BCD(d) (((d / 10) << 4) + (d % 10))
-     struct tTime { unsigned short mjd; uint8_t h, m, s; short offset; };
+     struct tTime { uint16_t mjd; uint8_t h, m, s; short offset; };
      tTime T = { mjd : htons(MJD), h : DEC2BCD(tm_gmt.tm_hour), m : DEC2BCD(tm_gmt.tm_min), s : DEC2BCD(tm_gmt.tm_sec), offset : htons(tm_loc.tm_gmtoff / 60) };
-     dbgprotocol("%d: ==> Date Time\n", SessionId());
+     bool OldDumpTPDUDataTransfer = DumpTPDUDataTransfer;
+     DumpTPDUDataTransfer &= DumpDateTime;
+     if (DumpDateTime)
+        dbgprotocol("Slot %d: ==> Date Time (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
      SendData(AOT_DATE_TIME, 7, (uint8_t*)&T);
-     //XXX return value of all SendData() calls???
-     return true;
+     DumpTPDUDataTransfer = OldDumpTPDUDataTransfer;
      }
-  return false;
 }
 
-bool cCiDateTime::Process(int Length, const uint8_t *Data)
+void cCiDateTime::Process(int Length, const uint8_t *Data)
 {
   if (Data) {
      int Tag = GetTag(Length, &Data);
@@ -1137,20 +855,18 @@
             const uint8_t *d = GetData(Data, l);
             if (l > 0)
                interval = *d;
-            dbgprotocol("%d: <== Date Time Enq, interval = %d\n", SessionId(), interval);
+            dbgprotocol("Slot %d: <== Date Time Enq (%d), interval = %d\n", Tc()->CamSlot()->SlotNumber(), SessionId(), interval);
             lastTime = time(NULL);
-            return SendDateTime();
+            SendDateTime();
             }
             break;
-       default: esyslog("ERROR: CI date time: unknown tag %06X", Tag);
-                return false;
+       default: esyslog("ERROR: CAM %d: date time: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag);
        }
      }
   else if (interval && time(NULL) - lastTime > interval) {
      lastTime = time(NULL);
-     return SendDateTime();
+     SendDateTime();
      }
-  return true;
 }
 
 // --- cCiMMI ----------------------------------------------------------------
@@ -1197,11 +913,11 @@
 public:
   cCiMMI(uint16_t SessionId, cCiTransportConnection *Tc);
   virtual ~cCiMMI();
-  virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
+  virtual void Process(int Length = 0, const uint8_t *Data = NULL);
   virtual bool HasUserIO(void) { return menu || enquiry; }
   cCiMenu *Menu(bool Clear = false);
   cCiEnquiry *Enquiry(bool Clear = false);
-  bool SendMenuAnswer(uint8_t Selection);
+  void SendMenuAnswer(uint8_t Selection);
   bool SendAnswer(const char *Text);
   bool SendCloseMMI(void);
   };
@@ -1209,7 +925,7 @@
 cCiMMI::cCiMMI(uint16_t SessionId, cCiTransportConnection *Tc)
 :cCiSession(SessionId, RI_MMI, Tc)
 {
-  dbgprotocol("New MMI (session id %d)\n", SessionId);
+  dbgprotocol("Slot %d: new MMI (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId);
   menu = fetchedMenu = NULL;
   enquiry = fetchedEnquiry = NULL;
 }
@@ -1236,21 +952,21 @@
   int Tag = GetTag(Length, Data);
   if (Tag == AOT_TEXT_LAST) {
      char *s = GetString(Length, Data);
-     dbgprotocol("%d: <== Text Last '%s'\n", SessionId(), s);
+     dbgprotocol("Slot %d: <== Text Last (%d) '%s'\n", Tc()->CamSlot()->SlotNumber(), SessionId(), s);
      return s;
      }
   else
-     esyslog("CI MMI: unexpected text tag: %06X", Tag);
+     esyslog("ERROR: CAM %d: MMI: unexpected text tag: %06X", Tc()->CamSlot()->SlotNumber(), Tag);
   return NULL;
 }
 
-bool cCiMMI::Process(int Length, const uint8_t *Data)
+void cCiMMI::Process(int Length, const uint8_t *Data)
 {
   if (Data) {
      int Tag = GetTag(Length, &Data);
      switch (Tag) {
        case AOT_DISPLAY_CONTROL: {
-            dbgprotocol("%d: <== Display Control\n", SessionId());
+            dbgprotocol("Slot %d: <== Display Control (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
             int l = 0;
             const uint8_t *d = GetData(Data, l);
             if (l > 0) {
@@ -1259,19 +975,18 @@
                       if (l == 2 && *++d == MM_HIGH_LEVEL) {
                          struct tDisplayReply { uint8_t id; uint8_t mode; };
                          tDisplayReply dr = { id : DRI_MMI_MODE_ACK, mode : MM_HIGH_LEVEL };
-                         dbgprotocol("%d: ==> Display Reply\n", SessionId());
+                         dbgprotocol("Slot %d: ==> Display Reply (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
                          SendData(AOT_DISPLAY_REPLY, 2, (uint8_t *)&dr);
                          }
                       break;
-                 default: esyslog("CI MMI: unsupported display control command %02X", *d);
-                          return false;
+                 default: esyslog("ERROR: CAM %d: MMI: unsupported display control command %02X", Tc()->CamSlot()->SlotNumber(), *d);
                  }
                }
             }
             break;
        case AOT_LIST_LAST:
        case AOT_MENU_LAST: {
-            dbgprotocol("%d: <== Menu Last\n", SessionId());
+            dbgprotocol("Slot %d: <== Menu Last (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
             delete menu;
             menu = new cCiMenu(this, Tag == AOT_MENU_LAST);
             int l = 0;
@@ -1296,7 +1011,7 @@
             }
             break;
        case AOT_ENQ: {
-            dbgprotocol("%d: <== Enq\n", SessionId());
+            dbgprotocol("Slot %d: <== Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
             delete enquiry;
             enquiry = new cCiEnquiry(this);
             int l = 0;
@@ -1323,14 +1038,12 @@
                if (l > 1)
                   delay = *d;
                }
-            dbgprotocol("%d: <== Close MMI  id = %02X  delay = %d\n", SessionId(), id, delay);
+            dbgprotocol("Slot %d: <== Close MMI (%d)  id = %02X  delay = %d\n", Tc()->CamSlot()->SlotNumber(), SessionId(), id, delay);
             }
             break;
-       default: esyslog("ERROR: CI MMI: unknown tag %06X", Tag);
-                return false;
+       default: esyslog("ERROR: CAM %d: MMI: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag);
        }
      }
-  return true;
 }
 
 cCiMenu *cCiMMI::Menu(bool Clear)
@@ -1355,32 +1068,28 @@
   return fetchedEnquiry;
 }
 
-bool cCiMMI::SendMenuAnswer(uint8_t Selection)
+void cCiMMI::SendMenuAnswer(uint8_t Selection)
 {
-  dbgprotocol("%d: ==> Menu Answ\n", SessionId());
+  dbgprotocol("Slot %d: ==> Menu Answ (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
   SendData(AOT_MENU_ANSW, 1, &Selection);
-  //XXX return value of all SendData() calls???
-  return true;
 }
 
 bool cCiMMI::SendAnswer(const char *Text)
 {
-  dbgprotocol("%d: ==> Answ\n", SessionId());
+  dbgprotocol("Slot %d: ==> Answ (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
   struct tAnswer { uint8_t id; char text[256]; };//XXX
   tAnswer answer;
   answer.id = Text ? AI_ANSWER : AI_CANCEL;
   if (Text)
      strncpy(answer.text, Text, sizeof(answer.text));
   SendData(AOT_ANSW, Text ? strlen(Text) + 1 : 1, (uint8_t *)&answer);
-  //XXX return value of all SendData() calls???
   return true;
 }
 
 bool cCiMMI::SendCloseMMI(void)
 {
-  dbgprotocol("%d: ==> Close MMI\n", SessionId());
+  dbgprotocol("Slot %d: ==> Close MMI (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId());
   SendData(AOT_CLOSE_MMI, 0);
-  //XXX return value of all SendData() calls???
   return true;
 }
 
@@ -1422,24 +1131,23 @@
   return !mmi || mmi->HasUserIO();
 }
 
-bool cCiMenu::Select(int Index)
+void cCiMenu::Select(int Index)
 {
   cMutexLock MutexLock(mutex);
-  dbgprotocol("%d: ==> Select %d\n", mmi ? mmi->SessionId() : -1, Index);
   if (mmi && -1 <= Index && Index < numEntries)
-     return mmi->SendMenuAnswer(Index + 1);
-  return false;
+     mmi->SendMenuAnswer(Index + 1);
 }
 
-bool cCiMenu::Cancel(void)
+void cCiMenu::Cancel(void)
 {
-  return Select(-1);
+  Select(-1);
 }
 
-bool cCiMenu::Abort(void)
+void cCiMenu::Abort(void)
 {
   cMutexLock MutexLock(mutex);
-  return mmi && mmi->SendCloseMMI();
+  if (mmi)
+     mmi->SendCloseMMI();
 }
 
 // --- cCiEnquiry ------------------------------------------------------------
@@ -1460,78 +1168,77 @@
   free(text);
 }
 
-bool cCiEnquiry::Reply(const char *s)
+void cCiEnquiry::Reply(const char *s)
 {
   cMutexLock MutexLock(mutex);
-  return mmi ? mmi->SendAnswer(s) : false;
+  if (mmi)
+     mmi->SendAnswer(s);
 }
 
-bool cCiEnquiry::Cancel(void)
+void cCiEnquiry::Cancel(void)
 {
-  return Reply(NULL);
+  Reply(NULL);
 }
 
-bool cCiEnquiry::Abort(void)
+void cCiEnquiry::Abort(void)
 {
   cMutexLock MutexLock(mutex);
-  return mmi && mmi->SendCloseMMI();
+  if (mmi)
+     mmi->SendCloseMMI();
 }
 
-// -- cCiHandler -------------------------------------------------------------
+// --- cCiTransportConnection (cont'd) ---------------------------------------
 
-cCiHandler::cCiHandler(int Fd, int NumSlots)
+#define TC_POLL_TIMEOUT   300 // ms WORKAROUND: TC_POLL_TIMEOUT < 300ms doesn't work with DragonCAM
+#define TC_ALIVE_TIMEOUT 2000 // ms after which a transport connection is assumed dead
+
+cCiTransportConnection::cCiTransportConnection(cCamSlot *CamSlot, uint8_t Tcid)
 {
-  fd = Fd;
-  numSlots = NumSlots;
-  newCaSupport = false;
+  dbgprotocol("Slot %d: creating connection %d/%d\n", CamSlot->SlotNumber(), CamSlot->SlotIndex(), Tcid);
+  camSlot = CamSlot;
+  tcid = Tcid;
+  state = stIDLE;
+  createConnectionRequested = false;
+  deleteConnectionRequested = false;
   hasUserIO = false;
-  for (int i = 0; i < MAX_CI_SESSION; i++)
+  alive.Set(TC_ALIVE_TIMEOUT);
+  for (int i = 0; i <= MAX_SESSIONS_PER_TC; i++) // sessions[0] is not used, but initialized anyway
       sessions[i] = NULL;
-  for (int i = 0; i < MAX_CI_SLOT; i++)
-      moduleReady[i] = false;
-  tpl = new cCiTransportLayer(Fd, numSlots);
-  tc = NULL;
-  source = transponder = 0;
 }
 
-cCiHandler::~cCiHandler()
+cCiTransportConnection::~cCiTransportConnection()
 {
-  for (int i = 0; i < MAX_CI_SESSION; i++)
+  for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++)
       delete sessions[i];
-  delete tpl;
-  close(fd);
 }
 
-cCiHandler *cCiHandler::CreateCiHandler(const char *FileName)
+bool cCiTransportConnection::Ready(void)
 {
-  int fd_ca = open(FileName, O_RDWR);
-  if (fd_ca >= 0) {
-     ca_caps_t Caps;
-     if (ioctl(fd_ca, CA_GET_CAP, &Caps) == 0) {
-        int NumSlots = Caps.slot_num;
-        if (NumSlots > 0) {
-           //XXX dsyslog("CAM: found %d CAM slots", NumSlots); // TODO let's do this only once we can be sure that there _really_ is a CAM adapter!
-           if ((Caps.slot_type & CA_CI_LINK) != 0)
-              return new cCiHandler(fd_ca, NumSlots);
-           else
-              isyslog("CAM doesn't support link layer interface");
-           }
-        else
-           esyslog("ERROR: no CAM slots found");
-        }
-     else
-        LOG_ERROR_STR(FileName);
-     close(fd_ca);
-     }
-  return NULL;
+  cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT);
+  return cas && cas->Ready();
 }
 
-uint32_t cCiHandler::ResourceIdToInt(const uint8_t *Data)
+const char *cCiTransportConnection::GetCamName(void)
 {
-  return (ntohl(get_unaligned((uint32_t *)Data)));
+  cCiApplicationInformation *ai = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION);
+  return ai ? ai->GetMenuString() : NULL;
 }
 
-bool cCiHandler::Send(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId, int Status)
+void cCiTransportConnection::SendTPDU(uint8_t Tag, int Length, const uint8_t *Data)
+{
+  cTPDU TPDU(camSlot->SlotIndex(), tcid, Tag, Length, Data);
+  camSlot->Write(&TPDU);
+  timer.Set(TC_POLL_TIMEOUT);
+}
+
+void cCiTransportConnection::SendData(int Length, const uint8_t *Data)
+{
+  // if Length ever exceeds MAX_TPDU_DATA this needs to be handled differently
+  if (state == stACTIVE && Length > 0)
+     SendTPDU(T_DATA_LAST, Length, Data);
+}
+
+void cCiTransportConnection::SendTag(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId, int Status)
 {
   uint8_t buffer[16];
   uint8_t *p = buffer;
@@ -1546,297 +1253,587 @@
   put_unaligned(htons(SessionId), (uint16_t *)p);
   p += 2;
   buffer[1] = p - buffer - 2; // length
-  return tc && tc->SendData(p - buffer, buffer) == OK;
+  SendData(p - buffer, buffer);
 }
 
-cCiSession *cCiHandler::GetSessionBySessionId(uint16_t SessionId)
+void cCiTransportConnection::Poll(void)
 {
-  for (int i = 0; i < MAX_CI_SESSION; i++) {
-      if (sessions[i] && sessions[i]->SessionId() == SessionId)
-         return sessions[i];
-      }
-  return NULL;
+  bool OldDumpTPDUDataTransfer = DumpTPDUDataTransfer;
+  DumpTPDUDataTransfer &= DumpPolls;
+  if (DumpPolls)
+     dbgprotocol("Slot %d: ==> Poll\n", camSlot->SlotNumber());
+  SendTPDU(T_DATA_LAST);
+  DumpTPDUDataTransfer = OldDumpTPDUDataTransfer;
 }
 
-cCiSession *cCiHandler::GetSessionByResourceId(uint32_t ResourceId, int Slot)
+uint32_t cCiTransportConnection::ResourceIdToInt(const uint8_t *Data)
 {
-  for (int i = 0; i < MAX_CI_SESSION; i++) {
-      if (sessions[i] && sessions[i]->Tc()->Slot() == Slot && sessions[i]->ResourceId() == ResourceId)
-         return sessions[i];
-      }
-  return NULL;
+  return (ntohl(get_unaligned((uint32_t *)Data)));
 }
 
-cCiSession *cCiHandler::CreateSession(uint32_t ResourceId)
+cCiSession *cCiTransportConnection::GetSessionBySessionId(uint16_t SessionId)
 {
-  if (!GetSessionByResourceId(ResourceId, tc->Slot())) {
-     for (int i = 0; i < MAX_CI_SESSION; i++) {
-         if (!sessions[i]) {
-            switch (ResourceId) {
-              case RI_RESOURCE_MANAGER:           return sessions[i] = new cCiResourceManager(i + 1, tc);
-              case RI_APPLICATION_INFORMATION:    return sessions[i] = new cCiApplicationInformation(i + 1, tc);
-              case RI_CONDITIONAL_ACCESS_SUPPORT: newCaSupport = true;
-                                                  return sessions[i] = new cCiConditionalAccessSupport(i + 1, tc);
-              case RI_HOST_CONTROL:               break; //XXX
-              case RI_DATE_TIME:                  return sessions[i] = new cCiDateTime(i + 1, tc);
-              case RI_MMI:                        return sessions[i] = new cCiMMI(i + 1, tc);
-              }
-            }
-         }
-     }
+  return (SessionId <= MAX_SESSIONS_PER_TC) ? sessions[SessionId] : NULL;
+}
+
+cCiSession *cCiTransportConnection::GetSessionByResourceId(uint32_t ResourceId)
+{
+  for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++) {
+      if (sessions[i] && sessions[i]->ResourceId() == ResourceId)
+         return sessions[i];
+      }
   return NULL;
 }
 
-bool cCiHandler::OpenSession(int Length, const uint8_t *Data)
+void cCiTransportConnection::OpenSession(int Length, const uint8_t *Data)
 {
   if (Length == 6 && *(Data + 1) == 0x04) {
      uint32_t ResourceId = ResourceIdToInt(Data + 2);
-     dbgprotocol("OpenSession %08X\n", ResourceId);
-     switch (ResourceId) {
-       case RI_RESOURCE_MANAGER:
-       case RI_APPLICATION_INFORMATION:
-       case RI_CONDITIONAL_ACCESS_SUPPORT:
-       case RI_HOST_CONTROL:
-       case RI_DATE_TIME:
-       case RI_MMI:
-            {
-              cCiSession *Session = CreateSession(ResourceId);
-              if (Session) {
-                 Send(ST_OPEN_SESSION_RESPONSE, Session->SessionId(), Session->ResourceId(), SS_OK);
-                 return true;
+     dbgprotocol("Slot %d: open session %08X\n", camSlot->SlotNumber(), ResourceId);
+     if (!GetSessionByResourceId(ResourceId)) {
+        for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++) {
+            if (!sessions[i]) {
+               switch (ResourceId) {
+                 case RI_RESOURCE_MANAGER:           sessions[i] = new cCiResourceManager(i, this); break;
+                 case RI_APPLICATION_INFORMATION:    sessions[i] = new cCiApplicationInformation(i, this); break;
+                 case RI_CONDITIONAL_ACCESS_SUPPORT: sessions[i] = new cCiConditionalAccessSupport(i, this); break;
+                 case RI_DATE_TIME:                  sessions[i] = new cCiDateTime(i, this); break;
+                 case RI_MMI:                        sessions[i] = new cCiMMI(i, this); break;
+                 case RI_HOST_CONTROL:               // not implemented
+                 default: esyslog("ERROR: CAM %d: unknown resource identifier: %08X (%d/%d)", camSlot->SlotNumber(), ResourceId, camSlot->SlotIndex(), tcid);
                  }
-              esyslog("ERROR: can't create session for resource identifier: %08X", ResourceId);
+               if (sessions[i])
+                  SendTag(ST_OPEN_SESSION_RESPONSE, sessions[i]->SessionId(), sessions[i]->ResourceId(), SS_OK);
+               return;
+               }
             }
-            break;
-       default: esyslog("ERROR: unknown resource identifier: %08X", ResourceId);
-       }
+        esyslog("ERROR: CAM %d: no free session slot for resource identifier %08X (%d/%d)", camSlot->SlotNumber(), ResourceId, camSlot->SlotIndex(), tcid);
+        }
+     else
+        esyslog("ERROR: CAM %d: session for resource identifier %08X already exists (%d/%d)", camSlot->SlotNumber(), ResourceId, camSlot->SlotIndex(), tcid);
      }
-  return false;
 }
 
-bool cCiHandler::CloseSession(uint16_t SessionId)
+void cCiTransportConnection::CloseSession(uint16_t SessionId)
 {
-  dbgprotocol("CloseSession %d\n", SessionId);
+  dbgprotocol("Slot %d: close session %d\n", camSlot->SlotNumber(), SessionId);
   cCiSession *Session = GetSessionBySessionId(SessionId);
-  if (Session && sessions[SessionId - 1] == Session) {
+  if (Session && sessions[SessionId] == Session) {
      delete Session;
-     sessions[SessionId - 1] = NULL;
-     Send(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_OK);
-     return true;
+     sessions[SessionId] = NULL;
+     SendTag(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_OK);
      }
   else {
-     esyslog("ERROR: unknown session id: %d", SessionId);
-     Send(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_NOT_ALLOCATED);
+     esyslog("ERROR: CAM %d: unknown session id: %d (%d/%d)", camSlot->SlotNumber(), SessionId, camSlot->SlotIndex(), tcid);
+     SendTag(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_NOT_ALLOCATED);
      }
-  return false;
 }
 
-int cCiHandler::CloseAllSessions(int Slot)
+void cCiTransportConnection::HandleSessions(cTPDU *TPDU)
 {
-  int result = 0;
-  for (int i = 0; i < MAX_CI_SESSION; i++) {
-      if (sessions[i] && sessions[i]->Tc()->Slot() == Slot) {
-         CloseSession(sessions[i]->SessionId());
-         result++;
-         }
-      }
-  return result;
+  int Length;
+  const uint8_t *Data = TPDU->Data(Length);
+  if (Data && Length > 1) {
+     switch (*Data) {
+       case ST_SESSION_NUMBER:          if (Length > 4) {
+                                           uint16_t SessionId = ntohs(get_unaligned((uint16_t *)&Data[2]));
+                                           cCiSession *Session = GetSessionBySessionId(SessionId);
+                                           if (Session)
+                                              Session->Process(Length - 4, Data + 4);
+                                           else
+                                              esyslog("ERROR: CAM %d: unknown session id: %d (%d/%d)", camSlot->SlotNumber(), SessionId, camSlot->SlotIndex(), tcid);
+                                           }
+                                        break;
+       case ST_OPEN_SESSION_REQUEST:    OpenSession(Length, Data);
+                                        break;
+       case ST_CLOSE_SESSION_REQUEST:   if (Length == 4)
+                                           CloseSession(ntohs(get_unaligned((uint16_t *)&Data[2])));
+                                        break;
+       case ST_CREATE_SESSION_RESPONSE: // not implemented
+       case ST_CLOSE_SESSION_RESPONSE:  // not implemented
+       default: esyslog("ERROR: CAM %d: unknown session tag: %02X (%d/%d)", camSlot->SlotNumber(), *Data, camSlot->SlotIndex(), tcid);
+       }
+     }
 }
 
-int cCiHandler::NumCams(void)
+bool cCiTransportConnection::Process(cTPDU *TPDU)
 {
-  int result = 0;
-  for (int i = 0; i < MAX_CI_SLOT; i++)
-      if (moduleReady[i])
-         result++;
-  return result;
+  if (TPDU)
+     alive.Set(TC_ALIVE_TIMEOUT);
+  else if (alive.TimedOut())
+     return false;
+  switch (state) {
+    case stIDLE:
+         if (createConnectionRequested) {
+            dbgprotocol("Slot %d: create connection %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid);
+            createConnectionRequested = false;
+            SendTPDU(T_CREATE_TC);
+            state = stCREATION;
+            }
+         return true;
+    case stCREATION:
+         if (TPDU && TPDU->Tag() == T_CTC_REPLY) {
+            dbgprotocol("Slot %d: connection created %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid);
+            Poll();
+            state = stACTIVE;
+            }
+         else if (timer.TimedOut()) {
+            dbgprotocol("Slot %d: timeout while creating connection %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid);
+            state = stIDLE;
+            }
+         return true;
+    case stACTIVE:
+         if (deleteConnectionRequested) {
+            dbgprotocol("Slot %d: delete connection requested %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid);
+            deleteConnectionRequested = false;
+            SendTPDU(T_DELETE_TC);
+            state = stDELETION;
+            return true;
+            }
+         if (TPDU) {
+            switch (TPDU->Tag()) {
+              case T_REQUEST_TC:
+                   esyslog("ERROR: CAM %d: T_REQUEST_TC not implemented (%d/%d)", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid);
+                   break;
+              case T_DATA_MORE:
+              case T_DATA_LAST:
+                   HandleSessions(TPDU);
+                   // continue with T_SB
+              case T_SB:
+                   if ((TPDU->Status() & DATA_INDICATOR) != 0) {
+                      dbgprotocol("Slot %d: receive data %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid);
+                      SendTPDU(T_RCV);
+                      }
+                   break;
+              case T_DELETE_TC:
+                   dbgprotocol("Slot %d: delete connection %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid);
+                   SendTPDU(T_DTC_REPLY);
+                   state = stIDLE;
+                   return true;
+              }
+            }
+         else if (timer.TimedOut())
+            Poll();
+         hasUserIO = false;
+         for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++) {
+             if (sessions[i]) {
+                sessions[i]->Process();
+                if (sessions[i]->HasUserIO())
+                   hasUserIO = true;
+                }
+             }
+         break;
+    case stDELETION:
+         if (TPDU && TPDU->Tag() == T_DTC_REPLY || timer.TimedOut()) {
+            dbgprotocol("Slot %d: connection deleted %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid);
+            state = stIDLE;
+            }
+         return true;
+    }
+  return true;
 }
 
-bool cCiHandler::Ready(void)
+// --- cCiCaPidData ----------------------------------------------------------
+
+class cCiCaPidData : public cListObject {
+public:
+  bool active;
+  int pid;
+  int streamType;
+  cCiCaPidData(int Pid, int StreamType)
+  {
+    active = false;
+    pid = Pid;
+    streamType = StreamType;
+  }
+  };
+
+// --- cCiCaProgramData ------------------------------------------------------
+
+class cCiCaProgramData : public cListObject {
+public:
+  int programNumber;
+  bool modified;
+  cList<cCiCaPidData> pidList;
+  cCiCaProgramData(int ProgramNumber)
+  {
+    programNumber = ProgramNumber;
+    modified = false;
+  }
+  };
+
+// --- cCiAdapter ------------------------------------------------------------
+
+cCiAdapter::cCiAdapter(void)
+:cThread("CI adapter")
 {
-  cMutexLock MutexLock(&mutex);
-  for (int Slot = 0; Slot < numSlots; Slot++) {
-      if (moduleReady[Slot]) {
-         cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot);
-         if (!cas || !*cas->GetCaSystemIds())
-            return false;
-         }
+  assignedDevice = NULL;
+  for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++)
+      camSlots[i] = NULL;
+}
+
+cCiAdapter::~cCiAdapter()
+{
+  Cancel(3);
+  for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++)
+      delete camSlots[i];
+}
+
+void cCiAdapter::AddCamSlot(cCamSlot *CamSlot)
+{
+  if (CamSlot) {
+     for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) {
+         if (!camSlots[i]) {
+            CamSlot->slotIndex = i;
+            camSlots[i] = CamSlot;
+            return;
+            }
+        }
+     esyslog("ERROR: no free CAM slot in CI adapter");
+     }
+}
+
+bool cCiAdapter::Ready(void)
+{
+  for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) {
+      if (camSlots[i] && !camSlots[i]->Ready())
+         return false;
       }
   return true;
 }
 
-bool cCiHandler::Process(int Slot)
+void cCiAdapter::Action(void)
+{
+  cTPDU TPDU;
+  while (Running()) {
+        int n = Read(TPDU.Buffer(), TPDU.MaxSize());
+        if (n > 0 && TPDU.Slot() < MAX_CAM_SLOTS_PER_ADAPTER) {
+           TPDU.SetSize(n);
+           cCamSlot *cs = camSlots[TPDU.Slot()];
+           TPDU.Dump(cs ? cs->SlotNumber() : 0, false);
+           if (cs)
+              cs->Process(&TPDU);
+           }
+        for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) {
+            if (camSlots[i])
+               camSlots[i]->Process();
+            }
+        }
+}
+
+// --- cCamSlot --------------------------------------------------------------
+
+cCamSlots CamSlots;
+
+#define MODULE_CHECK_INTERVAL 100 // ms
+#define MODULE_RESET_TIMEOUT    2 // s
+
+cCamSlot::cCamSlot(cCiAdapter *CiAdapter)
+{
+  ciAdapter = CiAdapter;
+  slotIndex = -1;
+  lastModuleStatus = msReset; // avoids initial reset log message
+  resetTime = 0;
+  resendPmt = false;
+  source = transponder = 0;
+  for (int i = 0; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) // tc[0] is not used, but initialized anyway
+      tc[i] = NULL;
+  CamSlots.Add(this);
+  slotNumber = Index() + 1;
+  if (ciAdapter)
+     ciAdapter->AddCamSlot(this);
+  Reset();
+}
+
+cCamSlot::~cCamSlot()
+{
+  CamSlots.Del(this, false);
+  DeleteAllConnections();
+}
+
+bool cCamSlot::Assign(cDevice *Device, bool Query)
 {
-  bool result = true;
   cMutexLock MutexLock(&mutex);
-  for (int slot = 0; slot < numSlots; slot++) {
-      if (Slot < 0 || slot == Slot) {
-         tc = tpl->Process(slot);
-         if (tc) {
-            int Length;
-            const uint8_t *Data = tc->Data(Length);
-            if (Data && Length > 1) {
-               switch (*Data) {
-                 case ST_SESSION_NUMBER:          if (Length > 4) {
-                                                     uint16_t SessionId = ntohs(get_unaligned((uint16_t *)&Data[2]));
-                                                     cCiSession *Session = GetSessionBySessionId(SessionId);
-                                                     if (Session)
-                                                        Session->Process(Length - 4, Data + 4);
-                                                     else
-                                                        esyslog("ERROR: unknown session id: %d", SessionId);
-                                                     }
-                                                  break;
-                 case ST_OPEN_SESSION_REQUEST:    OpenSession(Length, Data);
-                                                  break;
-                 case ST_CLOSE_SESSION_REQUEST:   if (Length == 4)
-                                                     CloseSession(ntohs(get_unaligned((uint16_t *)&Data[2])));
-                                                  break;
-                 case ST_CREATE_SESSION_RESPONSE: //XXX fall through to default
-                 case ST_CLOSE_SESSION_RESPONSE:  //XXX fall through to default
-                 default: esyslog("ERROR: unknown session tag: %02X", *Data);
+  if (ciAdapter) {
+     if (ciAdapter->Assign(Device, true)) {
+        if (!Device && ciAdapter->assignedDevice)
+           ciAdapter->assignedDevice->SetCamSlot(NULL);
+        if (!Query) {
+           StopDecrypting();
+           source = transponder = 0;
+           if (ciAdapter->Assign(Device)) {
+              ciAdapter->assignedDevice = Device;
+              if (Device) {
+                 Device->SetCamSlot(this);
+                 dsyslog("CAM %d: assigned to device %d", slotNumber, Device->DeviceNumber() + 1);
                  }
-               }
-            }
-         else if (CloseAllSessions(slot)) {
-            tpl->ResetSlot(slot);
-            result = false;
-            }
-         else if (tpl->ModuleReady(slot)) {
-            dbgprotocol("Module ready in slot %d\n", slot);
-            moduleReady[slot] = true;
-            tpl->NewConnection(slot);
-            }
+              else
+                 dsyslog("CAM %d: unassigned", slotNumber);
+              }
+           else
+              return false;
+           }
+        return true;
+        }
+     }
+  return false;
+}
+
+cDevice *cCamSlot::Device(void)
+{
+  cMutexLock MutexLock(&mutex);
+  if (ciAdapter) {
+     cDevice *d = ciAdapter->assignedDevice;
+     if (d && d->CamSlot() == this)
+        return d;
+     }
+  return NULL;
+}
+
+void cCamSlot::NewConnection(void)
+{
+  cMutexLock MutexLock(&mutex);
+  for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) {
+      if (!tc[i]) {
+         tc[i] = new cCiTransportConnection(this, i);
+         tc[i]->CreateConnection();
+         return;
          }
       }
-  SendCaPmt();
-  bool UserIO = false;
-  for (int i = 0; i < MAX_CI_SESSION; i++) {
-      if (sessions[i] && sessions[i]->Process())
-         UserIO |= sessions[i]->HasUserIO();
+  esyslog("ERROR: CAM %d: can't create new transport connection!", slotNumber);
+}
+
+void cCamSlot::DeleteAllConnections(void)
+{
+  cMutexLock MutexLock(&mutex);
+  for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) {
+      delete tc[i];
+      tc[i] = NULL;
       }
-  hasUserIO = UserIO;
-  return result;
 }
 
-void cCiHandler::SendCaPmt(void)
+void cCamSlot::Process(cTPDU *TPDU)
 {
   cMutexLock MutexLock(&mutex);
-  if (newCaSupport) {
-     newCaSupport = false;
-     for (int Slot = 0; Slot < numSlots; Slot++) {
-         cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot);
-         if (cas) {
-            // build the list of CA_PMT data:
-            cList<cCiCaPmt> CaPmtList;
-            for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
-                bool Active = false;
-                cCiCaPmt *CaPmt = new cCiCaPmt(CPCI_OK_DESCRAMBLING, source, transponder, p->programNumber, GetCaSystemIds(Slot));
-                if (CaPmt->Valid()) {
-                   for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) {
-                       if (q->active) {
-                          CaPmt->AddPid(q->pid, q->streamType);
-                          Active = true;
-                          }
-                       }
-                   }
-                if (Active)
-                   CaPmtList.Add(CaPmt);
-                else
-                   delete CaPmt;
-                }
-            // send the CA_PMT data:
-            uint8_t ListManagement = CaPmtList.Count() > 1 ? CPLM_FIRST : CPLM_ONLY;
-            for (cCiCaPmt *CaPmt = CaPmtList.First(); CaPmt; CaPmt = CaPmtList.Next(CaPmt)) {
-                CaPmt->SetListManagement(ListManagement);
-                if (!cas->SendPMT(CaPmt))
-                   newCaSupport = true;
-                ListManagement = CaPmt->Next() && CaPmt->Next()->Next() ? CPLM_MORE : CPLM_LAST;
-                }
-            }
+  if (TPDU) {
+     int n = TPDU->Tcid();
+     if (1 <= n && n <= MAX_CONNECTIONS_PER_CAM_SLOT) {
+        if (tc[n])
+           tc[n]->Process(TPDU);
+        }
+     }
+  for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) {
+      if (tc[i]) {
+         if (!tc[i]->Process()) {
+           Reset();
+           return;
+           }
          }
+      }
+  if (moduleCheckTimer.TimedOut()) {
+     eModuleStatus ms = ModuleStatus();
+     if (ms != lastModuleStatus) {
+        switch (ms) {
+          case msNone:
+               dbgprotocol("Slot %d: no module present\n", slotNumber);
+               isyslog("CAM %d: no module present", slotNumber);
+               DeleteAllConnections();
+               break;
+          case msReset:
+               dbgprotocol("Slot %d: module reset\n", slotNumber);
+               isyslog("CAM %d: module reset", slotNumber);
+               DeleteAllConnections();
+               break;
+          case msPresent:
+               dbgprotocol("Slot %d: module present\n", slotNumber);
+               isyslog("CAM %d: module present", slotNumber);
+               break;
+          case msReady:
+               dbgprotocol("Slot %d: module ready\n", slotNumber);
+               isyslog("CAM %d: module ready", slotNumber);
+               NewConnection();
+               resendPmt = caProgramList.Count() > 0;
+               break;
+          }
+        lastModuleStatus = ms;
+        }
+     moduleCheckTimer.Set(MODULE_CHECK_INTERVAL);
+     }
+  if (resendPmt)
+     SendCaPmt(CPCI_OK_DESCRAMBLING);
+  processed.Broadcast();
+}
+
+cCiSession *cCamSlot::GetSessionByResourceId(uint32_t ResourceId)
+{
+  cMutexLock MutexLock(&mutex);
+  return tc[1] ? tc[1]->GetSessionByResourceId(ResourceId) : NULL;
+}
+
+void cCamSlot::Write(cTPDU *TPDU)
+{
+  cMutexLock MutexLock(&mutex);
+  if (ciAdapter && TPDU->Size()) {
+     TPDU->Dump(SlotNumber(), true);
+     ciAdapter->Write(TPDU->Buffer(), TPDU->Size());
+     }
+}
+
+bool cCamSlot::Reset(void)
+{
+  cMutexLock MutexLock(&mutex);
+  ChannelCamRelations.Reset(slotNumber);
+  DeleteAllConnections();
+  if (ciAdapter) {
+     dbgprotocol("Slot %d: reset...", slotNumber);
+     if (ciAdapter->Reset(slotIndex)) {
+        resetTime = time(NULL);
+        dbgprotocol("ok.\n");
+        return true;
+        }
+     dbgprotocol("failed!\n");
+     }
+  return false;
+}
+
+eModuleStatus cCamSlot::ModuleStatus(void)
+{
+  cMutexLock MutexLock(&mutex);
+  eModuleStatus ms = ciAdapter ? ciAdapter->ModuleStatus(slotIndex) : msNone;
+  if (resetTime) {
+     if (ms <= msReset) {
+        if (time(NULL) - resetTime < MODULE_RESET_TIMEOUT)
+           return msReset;
+        }
+     resetTime = 0;
      }
+  return ms;
 }
 
-bool cCiHandler::EnterMenu(int Slot)
+const char *cCamSlot::GetCamName(void)
 {
   cMutexLock MutexLock(&mutex);
-  cCiApplicationInformation *api = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION, Slot);
+  return tc[1] ? tc[1]->GetCamName() : NULL;
+}
+
+bool cCamSlot::Ready(void)
+{
+  cMutexLock MutexLock(&mutex);
+  return ModuleStatus() == msNone || tc[1] && tc[1]->Ready();
+}
+
+bool cCamSlot::HasMMI(void)
+{
+  return GetSessionByResourceId(RI_MMI);
+}
+
+bool cCamSlot::HasUserIO(void)
+{
+  cMutexLock MutexLock(&mutex);
+  return tc[1] && tc[1]->HasUserIO();
+}
+
+bool cCamSlot::EnterMenu(void)
+{
+  cMutexLock MutexLock(&mutex);
+  cCiApplicationInformation *api = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION);
   return api ? api->EnterMenu() : false;
 }
 
-cCiMenu *cCiHandler::GetMenu(void)
+cCiMenu *cCamSlot::GetMenu(void)
 {
   cMutexLock MutexLock(&mutex);
-  for (int Slot = 0; Slot < numSlots; Slot++) {
-      cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI, Slot);
-      if (mmi) {
-         cCiMenu *Menu = mmi->Menu();
-         if (Menu)
-            Menu->mutex = &mutex;
-         return Menu;
-         }
-      }
+  cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI);
+  if (mmi) {
+     cCiMenu *Menu = mmi->Menu();
+     if (Menu)
+        Menu->mutex = &mutex;
+     return Menu;
+     }
   return NULL;
 }
 
-cCiEnquiry *cCiHandler::GetEnquiry(void)
+cCiEnquiry *cCamSlot::GetEnquiry(void)
 {
   cMutexLock MutexLock(&mutex);
-  for (int Slot = 0; Slot < numSlots; Slot++) {
-      cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI, Slot);
-      if (mmi) {
-         cCiEnquiry *Enquiry = mmi->Enquiry();
-         if (Enquiry)
-            Enquiry->mutex = &mutex;
-         return Enquiry;
-         }
-      }
+  cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI);
+  if (mmi) {
+     cCiEnquiry *Enquiry = mmi->Enquiry();
+     if (Enquiry)
+        Enquiry->mutex = &mutex;
+     return Enquiry;
+     }
   return NULL;
 }
 
-const char *cCiHandler::GetCamName(int Slot)
+void cCamSlot::SendCaPmt(uint8_t CmdId)
 {
   cMutexLock MutexLock(&mutex);
-  cCiApplicationInformation *ai = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION, Slot);
-  return ai ? ai->GetMenuString() : NULL;
+  cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT);
+  if (cas) {
+     const int *CaSystemIds = cas->GetCaSystemIds();
+     if (CaSystemIds && *CaSystemIds) {
+        if (caProgramList.Count()) {
+           for (int Loop = 1; Loop <= 2; Loop++) {
+               for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
+                   if (p->modified || resendPmt) {
+                      bool Active = false;
+                      cCiCaPmt CaPmt(CmdId, source, transponder, p->programNumber, CaSystemIds);
+                      for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) {
+                          if (q->active) {
+                             CaPmt.AddPid(q->pid, q->streamType);
+                             Active = true;
+                             }
+                          }
+                      if ((Loop == 1) != Active) { // first remove, then add
+                         if (cas->RepliesToQuery())
+                            CaPmt.SetListManagement(Active ? CPLM_ADD : CPLM_UPDATE);
+                         if (Active || cas->RepliesToQuery())
+                            cas->SendPMT(&CaPmt);
+                         p->modified = false;
+                         }
+                      }
+                   }
+               }
+           resendPmt = false;
+           }
+        else {
+           cCiCaPmt CaPmt(CmdId, 0, 0, 0, NULL);
+           cas->SendPMT(&CaPmt);
+           }
+        }
+     }
 }
 
-const unsigned short *cCiHandler::GetCaSystemIds(int Slot)
+const int *cCamSlot::GetCaSystemIds(void)
 {
   cMutexLock MutexLock(&mutex);
-  cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot);
+  cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT);
   return cas ? cas->GetCaSystemIds() : NULL;
 }
 
-bool cCiHandler::ProvidesCa(const unsigned short *CaSystemIds)
+int cCamSlot::Priority(void)
 {
-  cMutexLock MutexLock(&mutex);
-  for (int Slot = 0; Slot < numSlots; Slot++) {
-      cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot);
-      if (cas) {
-         for (const unsigned short *ids = cas->GetCaSystemIds(); ids && *ids; ids++) {
-             for (const unsigned short *id = CaSystemIds; *id; id++) {
-                 if (*id == *ids)
-                    return true;
-                 }
-             }
-         }
-      }
-  return false;
+  cDevice *d = Device();
+  return d ? d->Priority() : -1;
 }
 
-void cCiHandler::SetSource(int Source, int Transponder)
+bool cCamSlot::ProvidesCa(const int *CaSystemIds)
 {
   cMutexLock MutexLock(&mutex);
-  if (source != Source || transponder != Transponder) {
-     //XXX if there are active entries, send an empty CA_PMT
-     caProgramList.Clear();
+  cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT);
+  if (cas) {
+     for (const int *ids = cas->GetCaSystemIds(); ids && *ids; ids++) {
+         for (const int *id = CaSystemIds; *id; id++) {
+             if (*id == *ids)
+                return true;
+             }
+         }
      }
-  source = Source;
-  transponder = Transponder;
+  return false;
 }
 
-void cCiHandler::AddPid(int ProgramNumber, int Pid, int StreamType)
+void cCamSlot::AddPid(int ProgramNumber, int Pid, int StreamType)
 {
   cMutexLock MutexLock(&mutex);
   cCiCaProgramData *ProgramData = NULL;
@@ -1854,65 +1851,276 @@
   ProgramData->pidList.Add(new cCiCaPidData(Pid, StreamType));
 }
 
-void cCiHandler::SetPid(int Pid, bool Active)
+void cCamSlot::SetPid(int Pid, bool Active)
 {
   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)) {
           if (q->pid == Pid) {
-             q->active = Active;
+             if (q->active != Active) {
+                q->active = Active;
+                p->modified = true;
+                }
              return;
              }
          }
       }
 }
 
-bool cCiHandler::CanDecrypt(int ProgramNumber)
+// see ISO/IEC 13818-1
+#define STREAM_TYPE_VIDEO  0x02
+#define STREAM_TYPE_AUDIO  0x04
+#define STREAM_TYPE_DOLBY  0x06
+
+void cCamSlot::AddChannel(const cChannel *Channel)
 {
   cMutexLock MutexLock(&mutex);
-  for (int Slot = 0; Slot < numSlots; Slot++) {
-      cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot);
-      if (cas) {
-         for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
-             if (p->programNumber == ProgramNumber) {
-                cCiCaPmt CaPmt(CPCI_QUERY, source, transponder, p->programNumber, GetCaSystemIds(Slot));//XXX???
-                if (CaPmt.Valid()) {
-                   for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) {
-//XXX                       if (q->active)
-                          CaPmt.AddPid(q->pid, q->streamType);
-                       }
-                   }
-                if (!cas->SendPMT(&CaPmt))
-                   return false;//XXX
-                //XXX
-                time_t timeout = time(NULL) + 3;//XXX
-                while (time(NULL) <= timeout) {
-                      Process(Slot);
-                      cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot);
-                      if (!cas)
-                         return false;//XXX
-                      if (cas->ReceivedReply(true))
-                         return true;
-                      //XXX remember if a slot doesn't receive a reply
-                      }
-                break;
-                }
+  if (source != Channel->Source() || transponder != Channel->Transponder())
+     StopDecrypting();
+  source = Channel->Source();
+  transponder = Channel->Transponder();
+  if (Channel->Ca() >= CA_ENCRYPTED_MIN) {
+     AddPid(Channel->Sid(), Channel->Vpid(), STREAM_TYPE_VIDEO);
+     for (const int *Apid = Channel->Apids(); *Apid; Apid++)
+         AddPid(Channel->Sid(), *Apid, STREAM_TYPE_AUDIO);
+     for (const int *Dpid = Channel->Dpids(); *Dpid; Dpid++)
+         AddPid(Channel->Sid(), *Dpid, STREAM_TYPE_DOLBY);
+     }
+}
+
+#define QUERY_REPLY_WAIT  100 // ms to wait between checks for a reply
+
+bool cCamSlot::CanDecrypt(const cChannel *Channel)
+{
+  if (Channel->Ca() < CA_ENCRYPTED_MIN)
+     return true; // channel not encrypted
+  if (!IsDecrypting())
+     return true; // any CAM can decrypt at least one channel
+  cMutexLock MutexLock(&mutex);
+  cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT);
+  if (cas && cas->RepliesToQuery()) {
+     cCiCaPmt CaPmt(CPCI_QUERY, Channel->Source(), Channel->Transponder(), Channel->Sid(), GetCaSystemIds());
+     CaPmt.SetListManagement(CPLM_ADD); // WORKAROUND: CPLM_ONLY doesn't work with Alphacrypt 3.09 (deletes existing CA_PMTs)
+     CaPmt.AddPid(Channel->Vpid(), STREAM_TYPE_VIDEO);
+     for (const int *Apid = Channel->Apids(); *Apid; Apid++)
+         CaPmt.AddPid(*Apid, STREAM_TYPE_AUDIO);
+     for (const int *Dpid = Channel->Dpids(); *Dpid; Dpid++)
+         CaPmt.AddPid(*Dpid, STREAM_TYPE_DOLBY);
+     cas->SendPMT(&CaPmt);
+     cTimeMs Timeout(QUERY_REPLY_TIMEOUT);
+     do {
+        processed.TimedWait(mutex, QUERY_REPLY_WAIT);
+        if ((cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT)) != NULL) { // must re-fetch it, there might have been a reset
+           if (cas->ReceivedReply())
+              return cas->CanDecrypt();
+           }
+        else
+           return false;
+        } while (!Timeout.TimedOut());
+     dsyslog("CAM %d: didn't reply to QUERY", SlotNumber());
+     }
+  return false;
+}
+
+void cCamSlot::StartDecrypting(void)
+{
+  SendCaPmt(CPCI_OK_DESCRAMBLING);
+}
+
+void cCamSlot::StopDecrypting(void)
+{
+  cMutexLock MutexLock(&mutex);
+  if (caProgramList.Count()) {
+     caProgramList.Clear();
+     SendCaPmt(CPCI_NOT_SELECTED);
+     }
+}
+
+bool cCamSlot::IsDecrypting(void)
+{
+  cMutexLock MutexLock(&mutex);
+  if (caProgramList.Count()) {
+     for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
+         if (p->modified)
+            return true; // any modifications need to be processed before we can assume it's no longer decrypting
+         for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) {
+             if (q->active)
+                return true;
              }
          }
-      }
+     }
   return false;
 }
 
-void cCiHandler::StartDecrypting(void)
+// --- cChannelCamRelation ---------------------------------------------------
+
+#define CAM_CHECKED_TIMEOUT  15 // seconds before a CAM that has been checked for a particular channel will be checked again
+
+class cChannelCamRelation : public cListObject {
+private:
+  tChannelID channelID;
+  uint32_t camSlotsChecked;
+  uint32_t camSlotsDecrypt;
+  time_t lastChecked;
+public:
+  cChannelCamRelation(tChannelID ChannelID);
+  bool TimedOut(void);
+  tChannelID ChannelID(void) { return channelID; }
+  bool CamChecked(int CamSlotNumber);
+  bool CamDecrypt(int CamSlotNumber);
+  void SetChecked(int CamSlotNumber);
+  void SetDecrypt(int CamSlotNumber);
+  void ClrChecked(int CamSlotNumber);
+  void ClrDecrypt(int CamSlotNumber);
+  };
+
+cChannelCamRelation::cChannelCamRelation(tChannelID ChannelID)
+{
+  channelID = ChannelID;
+  camSlotsChecked = 0;
+  camSlotsDecrypt = 0;
+  lastChecked = 0;
+}
+
+bool cChannelCamRelation::TimedOut(void)
+{
+  return !camSlotsDecrypt && time(NULL) - lastChecked > CAM_CHECKED_TIMEOUT;
+}
+
+bool cChannelCamRelation::CamChecked(int CamSlotNumber)
+{
+  if (lastChecked && time(NULL) - lastChecked > CAM_CHECKED_TIMEOUT) {
+     lastChecked = 0;
+     camSlotsChecked = 0;
+     }
+  return camSlotsChecked & (1 << (CamSlotNumber - 1));
+}
+
+bool cChannelCamRelation::CamDecrypt(int CamSlotNumber)
+{
+  return camSlotsDecrypt & (1 << (CamSlotNumber - 1));
+}
+
+void cChannelCamRelation::SetChecked(int CamSlotNumber)
+{
+  camSlotsChecked |= (1 << (CamSlotNumber - 1));
+  lastChecked = time(NULL);
+  ClrDecrypt(CamSlotNumber);
+}
+
+void cChannelCamRelation::SetDecrypt(int CamSlotNumber)
+{
+  camSlotsDecrypt |= (1 << (CamSlotNumber - 1));
+  ClrChecked(CamSlotNumber);
+}
+
+void cChannelCamRelation::ClrChecked(int CamSlotNumber)
+{
+  camSlotsChecked &= ~(1 << (CamSlotNumber - 1));
+  lastChecked = 0;
+}
+
+void cChannelCamRelation::ClrDecrypt(int CamSlotNumber)
+{
+  camSlotsDecrypt &= ~(1 << (CamSlotNumber - 1));
+}
+
+// --- cChannelCamRelations --------------------------------------------------
+
+#define CHANNEL_CAM_RELATIONS_CLEANUP_INTERVAL 3600 // seconds between cleanups
+
+cChannelCamRelations ChannelCamRelations;
+
+cChannelCamRelations::cChannelCamRelations(void)
+{
+  lastCleanup = time(NULL);
+}
+
+void cChannelCamRelations::Cleanup(void)
+{
+  cMutexLock MutexLock(&mutex);
+  if (time(NULL) - lastCleanup > CHANNEL_CAM_RELATIONS_CLEANUP_INTERVAL) {
+     for (cChannelCamRelation *ccr = First(); ccr; ) {
+         cChannelCamRelation *c = ccr;
+         ccr = Next(ccr);
+         if (c->TimedOut())
+            Del(c);
+         }
+     lastCleanup = time(NULL);
+     }
+}
+
+cChannelCamRelation *cChannelCamRelations::GetEntry(tChannelID ChannelID)
+{
+  cMutexLock MutexLock(&mutex);
+  Cleanup();
+  for (cChannelCamRelation *ccr = First(); ccr; ccr = Next(ccr)) {
+      if (ccr->ChannelID() == ChannelID)
+         return ccr;
+      }
+  return NULL;
+}
+
+cChannelCamRelation *cChannelCamRelations::AddEntry(tChannelID ChannelID)
+{
+  cMutexLock MutexLock(&mutex);
+  cChannelCamRelation *ccr = GetEntry(ChannelID);
+  if (!ccr)
+     Add(ccr = new cChannelCamRelation(ChannelID));
+  return ccr;
+}
+
+void cChannelCamRelations::Reset(int CamSlotNumber)
+{
+  cMutexLock MutexLock(&mutex);
+  for (cChannelCamRelation *ccr = First(); ccr; ccr = Next(ccr)) {
+      ccr->ClrChecked(CamSlotNumber);
+      ccr->ClrDecrypt(CamSlotNumber);
+      }
+}
+
+bool cChannelCamRelations::CamChecked(tChannelID ChannelID, int CamSlotNumber)
+{
+  cMutexLock MutexLock(&mutex);
+  cChannelCamRelation *ccr = GetEntry(ChannelID);
+  return ccr ? ccr->CamChecked(CamSlotNumber) : false;
+}
+
+bool cChannelCamRelations::CamDecrypt(tChannelID ChannelID, int CamSlotNumber)
+{
+  cMutexLock MutexLock(&mutex);
+  cChannelCamRelation *ccr = GetEntry(ChannelID);
+  return ccr ? ccr->CamDecrypt(CamSlotNumber) : false;
+}
+
+void cChannelCamRelations::SetChecked(tChannelID ChannelID, int CamSlotNumber)
+{
+  cMutexLock MutexLock(&mutex);
+  cChannelCamRelation *ccr = AddEntry(ChannelID);
+  if (ccr)
+     ccr->SetChecked(CamSlotNumber);
+}
+
+void cChannelCamRelations::SetDecrypt(tChannelID ChannelID, int CamSlotNumber)
+{
+  cMutexLock MutexLock(&mutex);
+  cChannelCamRelation *ccr = AddEntry(ChannelID);
+  if (ccr)
+     ccr->SetDecrypt(CamSlotNumber);
+}
+
+void cChannelCamRelations::ClrChecked(tChannelID ChannelID, int CamSlotNumber)
 {
   cMutexLock MutexLock(&mutex);
-  newCaSupport = true;
-  SendCaPmt();
+  cChannelCamRelation *ccr = GetEntry(ChannelID);
+  if (ccr)
+     ccr->ClrChecked(CamSlotNumber);
 }
 
-bool cCiHandler::Reset(int Slot)
+void cChannelCamRelations::ClrDecrypt(tChannelID ChannelID, int CamSlotNumber)
 {
   cMutexLock MutexLock(&mutex);
-  CloseAllSessions(Slot);
-  return tpl->ResetSlot(Slot, true);
+  cChannelCamRelation *ccr = GetEntry(ChannelID);
+  if (ccr)
+     ccr->ClrDecrypt(CamSlotNumber);
 }
diff -ruN vdr-1.4.5/ci.h vdr-1.5.0/ci.h
--- vdr-1.4.5/ci.h	2006-08-12 11:43:31.000000000 +0200
+++ vdr-1.5.0/ci.h	2007-01-03 13:49:10.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: ci.h 1.22 2006/08/12 09:43:31 kls Exp $
+ * $Id: ci.h 1.23 2007/01/03 12:49:10 kls Exp $
  */
 
 #ifndef __CI_H
@@ -12,13 +12,18 @@
 
 #include <stdint.h>
 #include <stdio.h>
+#include "channels.h"
 #include "thread.h"
 #include "tools.h"
 
+#define MAX_CAM_SLOTS_PER_ADAPTER     8 // maximum possible value is 255
+#define MAX_CONNECTIONS_PER_CAM_SLOT  8 // maximum possible value is 254
+#define CAM_READ_TIMEOUT  50 // ms
+
 class cCiMMI;
 
 class cCiMenu {
-  friend class cCiHandler;
+  friend class cCamSlot;
   friend class cCiMMI;
 private:
   enum { MAX_CIMENU_ENTRIES = 64 }; ///< XXX is there a specified maximum?
@@ -40,14 +45,14 @@
   const char *Entry(int n) { return n < numEntries ? entries[n] : NULL; }
   int NumEntries(void) { return numEntries; }
   bool Selectable(void) { return selectable; }
-  bool Select(int Index);
-  bool Cancel(void);
-  bool Abort(void);
+  void Select(int Index);
+  void Cancel(void);
+  void Abort(void);
   bool HasUpdate(void);
   };
 
 class cCiEnquiry {
-  friend class cCiHandler;
+  friend class cCamSlot;
   friend class cCiMMI;
 private:
   cCiMMI *mmi;
@@ -61,103 +66,140 @@
   const char *Text(void) { return text; }
   bool Blind(void) { return blind; }
   int ExpectedLength(void) { return expectedLength; }
-  bool Reply(const char *s);
-  bool Cancel(void);
-  bool Abort(void);
+  void Reply(const char *s);
+  void Cancel(void);
+  void Abort(void);
   };
 
-#define MAX_CI_SESSION  16 //XXX
-#define MAX_CI_SLOT     16
+class cDevice;
+class cCamSlot;
 
-class cCiCaPidData : public cListObject {
-public:
-  bool active;
-  int pid;
-  int streamType;
-  cCiCaPidData(int Pid, int StreamType)
-  {
-    active = false;
-    pid = Pid;
-    streamType = StreamType;
-  }
-  };
+enum eModuleStatus { msNone, msReset, msPresent, msReady };
 
-class cCiCaProgramData : public cListObject {
+class cCiAdapter : public cThread {
+  friend class cCamSlot;
+private:
+  cDevice *assignedDevice;
+  cCamSlot *camSlots[MAX_CAM_SLOTS_PER_ADAPTER];
+  void AddCamSlot(cCamSlot *CamSlot);
+       ///< Adds the given CamSlot to this CI adapter.
+protected:
+  virtual void Action(void);
+       ///< Handles the attached CAM slots in a separate thread.
+       ///< The derived class must call the Start() function to
+       ///< actually start CAM handling.
+  virtual int Read(uint8_t *Buffer, int MaxLength) = 0;
+       ///< Reads one chunk of data into the given Buffer, up to MaxLength bytes.
+       ///< If no data is available immediately, wait for up to CAM_READ_TIMEOUT.
+       ///< Returns the number of bytes read (in case of an error it will also
+       ///< return 0).
+  virtual void Write(const uint8_t *Buffer, int Length) = 0;
+       ///< Writes Length bytes of the given Buffer.
+  virtual bool Reset(int Slot) = 0;
+       ///< Resets the CAM in the given Slot.
+       ///< Returns true if the operation was successful.
+  virtual eModuleStatus ModuleStatus(int Slot) = 0;
+       ///< Returns the status of the CAM in the given Slot.
+  virtual bool Assign(cDevice *Device, bool Query = false) = 0;
+       ///< Assigns this adapter to the given Device, if this is possible.
+       ///< If Query is 'true', the adapter only checks whether it can be
+       ///< assigned to the Device, but doesn't actually assign itself to it.
+       ///< Returns true if the adapter can be assigned to the Device.
+       ///< If Device is NULL, the adapter will be unassigned from any
+       ///< device it was previously assigned to. The value of Query
+       ///< is ignored in that case, and this function always returns
+       ///< 'true'.
 public:
-  int programNumber;
-  cList<cCiCaPidData> pidList;
-  cCiCaProgramData(int ProgramNumber)
-  {
-    programNumber = ProgramNumber;
-  }
+  cCiAdapter(void);
+  virtual ~cCiAdapter();
+       ///< The derived class must call Cancel(3) in its destructor.
+  virtual bool Ready(void);
+       ///< Returns 'true' if all present CAMs in this adapter are ready.
   };
 
-class cCiSession;
-class cCiTransportLayer;
+class cTPDU;
 class cCiTransportConnection;
+class cCiSession;
+class cCiCaProgramData;
 
-class cCiHandler {
+class cCamSlot : public cListObject {
+  friend class cCiAdapter;
+  friend class cCiTransportConnection;
 private:
   cMutex mutex;
-  int fd;
-  int numSlots;
-  bool newCaSupport;
-  bool hasUserIO;
-  bool moduleReady[MAX_CI_SLOT];
-  cCiSession *sessions[MAX_CI_SESSION];
-  cCiTransportLayer *tpl;
-  cCiTransportConnection *tc;
+  cCondVar processed;
+  cCiAdapter *ciAdapter;
+  int slotIndex;
+  int slotNumber;
+  cCiTransportConnection *tc[MAX_CONNECTIONS_PER_CAM_SLOT + 1];  // connection numbering starts with 1
+  eModuleStatus lastModuleStatus;
+  time_t resetTime;
+  cTimeMs moduleCheckTimer;
+  bool resendPmt;
   int source;
   int transponder;
   cList<cCiCaProgramData> caProgramList;
-  uint32_t ResourceIdToInt(const uint8_t *Data);
-  bool Send(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId = 0, int Status = -1);
-  const unsigned short *GetCaSystemIds(int Slot);
-  cCiSession *GetSessionBySessionId(uint16_t SessionId);
-  cCiSession *GetSessionByResourceId(uint32_t ResourceId, int Slot);
-  cCiSession *CreateSession(uint32_t ResourceId);
-  bool OpenSession(int Length, const uint8_t *Data);
-  bool CloseSession(uint16_t SessionId);
-  int CloseAllSessions(int Slot);
-  cCiHandler(int Fd, int NumSlots);
-  void SendCaPmt(void);
+  const int *GetCaSystemIds(void);
+  void SendCaPmt(uint8_t CmdId);
+  void NewConnection(void);
+  void DeleteAllConnections(void);
+  void Process(cTPDU *TPDU = NULL);
+  void Write(cTPDU *TPDU);
+  cCiSession *GetSessionByResourceId(uint32_t ResourceId);
 public:
-  ~cCiHandler();
-  static cCiHandler *CreateCiHandler(const char *FileName);
-       ///< Creates a new cCiHandler for the given CA device.
-  int NumSlots(void) { return numSlots; }
-       ///< Returns the number of CAM slots provided by this CA device.
-  int NumCams(void);
-       ///< Returns the number of actual CAMs inserted into this CA device.
+  cCamSlot(cCiAdapter *CiAdapter);
+       ///< Creates a new CAM slot for the given CiAdapter.
+       ///< The CiAdapter will take care of deleting the CAM slot,
+       ///< so the caller must not delete it!
+  virtual ~cCamSlot();
+  bool Assign(cDevice *Device, bool Query = false);
+       ///< Assigns this CAM slot to the given Device, if this is possible.
+       ///< If Query is 'true', the CI adapter of this slot only checks whether
+       ///< it can be assigned to the Device, but doesn't actually assign itself to it.
+       ///< Returns true if this slot can be assigned to the Device.
+       ///< If Device is NULL, the slot will be unassigned from any
+       ///< device it was previously assigned to. The value of Query
+       ///< is ignored in that case, and this function always returns
+       ///< 'true'.
+  cDevice *Device(void);
+       ///< Returns the device this CAM slot is currently assigned to.
+  int SlotIndex(void) { return slotIndex; }
+       ///< Returns the index of this CAM slot within its CI adapter.
+       ///< The first slot has an index of 0.
+  int SlotNumber(void) { return slotNumber; }
+       ///< Returns the number of this CAM slot within the whole system.
+       ///< The first slot has the number 1.
+  bool Reset(void);
+       ///< Resets the CAM in this slot.
+       ///< Returns true if the operation was successful.
+  eModuleStatus ModuleStatus(void);
+       ///< Returns the status of the CAM in this slot.
+  const char *GetCamName(void);
+       ///< Returns the name of the CAM in this slot, or NULL if there is
+       ///< no ready CAM in this slot.
   bool Ready(void);
-       ///< Returns true if all CAMs in this CA device are ready.
-  bool Process(int Slot = -1);
-       ///< Processes the given Slot. If Slot is -1, all slots are processed.
-       ///< Returns false in case of an error.
-  bool HasUserIO(void) { return hasUserIO; }
+       ///< Returns 'true' if the CAM in this slot is ready to decrypt.
+  bool HasMMI(void);
+       ///< Returns 'true' if the CAM in this slot has an active MMI.
+  bool HasUserIO(void);
        ///< Returns true if there is a pending user interaction, which shall
        ///< be retrieved via GetMenu() or GetEnquiry().
-  bool EnterMenu(int Slot);
-       ///< Requests the CAM in the given Slot to start its menu.
+  bool EnterMenu(void);
+       ///< Requests the CAM in this slot to start its menu.
   cCiMenu *GetMenu(void);
        ///< Gets a pending menu, or NULL if there is no menu.
   cCiEnquiry *GetEnquiry(void);
        ///< Gets a pending enquiry, or NULL if there is no enquiry.
-  const char *GetCamName(int Slot);
-       ///< Returns the name of the CAM in the given Slot, or NULL if there
-       ///< is no CAM in that slot.
-  bool ProvidesCa(const unsigned short *CaSystemIds); //XXX Slot???
-       ///< Returns true if any of the CAMs can provide one of the given
+  int Priority(void);
+       ///< Returns the priority if the device this slot is currently assigned
+       ///< to, or -1 if it is not assigned to any device.
+  bool ProvidesCa(const int *CaSystemIds);
+       ///< Returns true if the CAM in this slot provides one of the given
        ///< CaSystemIds. This doesn't necessarily mean that it will be
        ///< possible to actually decrypt such a programme, since CAMs
        ///< usually advertise several CA system ids, while the actual
        ///< decryption is controlled by the smart card inserted into
        ///< the CAM.
-  void SetSource(int Source, int Transponder);
-       ///< Sets the Source and Transponder of the device this cCiHandler is
-       ///< currently tuned to. If Source or Transponder are different than
-       ///< what was given in a previous call to SetSource(), any previously
-       ///< added PIDs will be cleared.
   void AddPid(int ProgramNumber, int Pid, int StreamType);
        ///< Adds the given PID information to the list of PIDs. A later call
        ///< to SetPid() will (de)activate one of these entries.
@@ -165,16 +207,55 @@
        ///< Sets the given Pid (which has previously been added through a
        ///< call to AddPid()) to Active. A later call to StartDecrypting() will
        ///< send the full list of currently active CA_PMT entries to the CAM.
-  bool CanDecrypt(int ProgramNumber);
-       ///< XXX
-       ///< Returns true if there is a CAM in this CA device that is able
-       ///< to decrypt the programme with the given ProgramNumber. The PIDs
-       ///< for this ProgramNumber must have been set through previous calls
-       ///< to SetPid().
+  void AddChannel(const cChannel *Channel);
+       ///< Adds all PIDs if 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.
+  bool CanDecrypt(const cChannel *Channel);
+       ///< Returns true if there is a CAM in this slot that is able to decrypt
+       ///< the given Channel (or at least claims to be able to do so).
+       ///< Since the QUERY/REPLY mechanism for CAMs is pretty unreliable (some
+       ///< CAMs don't reply to queries at all), we always return true if the
+       ///< CAM is currently not decrypting anything. If there is already a
+       ///< channel being decrypted, a call to CanDecrypt() checks whether the
+       ///< CAM can also decrypt the given channel. Only CAMs that have replied
+       ///< to the inital QUERY will perform this check at all. CAMs that never
+       ///< replied to the initial QUERY are assumed not to be able to handle
+       ///< more than one channel at a time.
   void StartDecrypting(void);
        ///< Triggers sending all currently active CA_PMT entries to the CAM,
        ///< so that it will start decrypting.
-  bool Reset(int Slot);
+  void StopDecrypting(void);
+       ///< Clears the list of CA_PMT entries and tells the CAM to stop decrypting.
+  bool IsDecrypting(void);
+       ///< Returns true if the CAM in this slot is currently used for decrypting.
   };
 
+class cCamSlots : public cList<cCamSlot> {};
+
+extern cCamSlots CamSlots;
+
+class cChannelCamRelation;
+
+class cChannelCamRelations : public cList<cChannelCamRelation> {
+private:
+  cMutex mutex;
+  cChannelCamRelation *GetEntry(tChannelID ChannelID);
+  cChannelCamRelation *AddEntry(tChannelID ChannelID);
+  time_t lastCleanup;
+  void Cleanup(void);
+public:
+  cChannelCamRelations(void);
+  void Reset(int CamSlotNumber);
+  bool CamChecked(tChannelID ChannelID, int CamSlotNumber);
+  bool CamDecrypt(tChannelID ChannelID, int CamSlotNumber);
+  void SetChecked(tChannelID ChannelID, int CamSlotNumber);
+  void SetDecrypt(tChannelID ChannelID, int CamSlotNumber);
+  void ClrChecked(tChannelID ChannelID, int CamSlotNumber);
+  void ClrDecrypt(tChannelID ChannelID, int CamSlotNumber);
+  };
+
+extern cChannelCamRelations ChannelCamRelations;
+
 #endif //__CI_H
diff -ruN vdr-1.4.5/config.h vdr-1.5.0/config.h
--- vdr-1.4.5/config.h	2007-01-07 14:45:19.000000000 +0100
+++ vdr-1.5.0/config.h	2007-01-07 15:09:31.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: config.h 1.282 2007/01/07 13:45:19 kls Exp $
+ * $Id: config.h 1.283 2007/01/07 14:09:31 kls Exp $
  */
 
 #ifndef __CONFIG_H
@@ -21,13 +21,13 @@
 
 // VDR's own version number:
 
-#define VDRVERSION  "1.4.5"
-#define VDRVERSNUM   10405  // Version * 10000 + Major * 100 + Minor
+#define VDRVERSION  "1.5.0"
+#define VDRVERSNUM   10500  // Version * 10000 + Major * 100 + Minor
 
 // The plugin API's version number:
 
-#define APIVERSION  "1.4.5"
-#define APIVERSNUM   10405  // Version * 10000 + Major * 100 + Minor
+#define APIVERSION  "1.5.0"
+#define APIVERSNUM   10500  // 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
diff -ruN vdr-1.4.5/device.c vdr-1.5.0/device.c
--- vdr-1.4.5/device.c	2006-09-03 12:13:25.000000000 +0200
+++ vdr-1.5.0/device.c	2007-01-07 14:41:07.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: device.c 1.137 2006/09/03 10:13:25 kls Exp $
+ * $Id: device.c 1.138 2007/01/07 13:41:07 kls Exp $
  */
 
 #include "device.h"
@@ -164,7 +164,9 @@
   sdtFilter = NULL;
   nitFilter = NULL;
 
-  ciHandler = NULL;
+  camSlot = NULL;
+  startScrambleDetection = 0;
+
   player = NULL;
   pesAssembler = new cPesAssembler;
   ClrAvailableTracks();
@@ -183,9 +185,7 @@
 cDevice::~cDevice()
 {
   Detach(player);
-  for (int i = 0; i < MAXRECEIVERS; i++)
-      Detach(receiver[i]);
-  delete ciHandler;
+  DetachAllReceivers();
   delete nitFilter;
   delete sdtFilter;
   delete patFilter;
@@ -199,8 +199,10 @@
   for (time_t t0 = time(NULL); time(NULL) - t0 < Timeout; ) {
       bool ready = true;
       for (int i = 0; i < numDevices; i++) {
-          if (device[i] && !device[i]->Ready())
+          if (device[i] && !device[i]->Ready()) {
              ready = false;
+             cCondWait::SleepMs(100);
+             }
           }
       if (ready)
          return true;
@@ -278,39 +280,98 @@
   return (0 <= Index && Index < numDevices) ? device[Index] : NULL;
 }
 
-cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers)
+cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView)
 {
-  cDevice *d = NULL;
-  uint Impact = 0xFFFFFFFF; // we're looking for a device with the least impact
-  for (int i = 0; i < numDevices; i++) {
-      bool ndr;
-      if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job
-         // Put together an integer number that reflects the "impact" using
-         // this device would have on the overall system. Each condition is represented
-         // by one bit in the number (or several bits, if the condition is actually
-         // a numeric value). The sequence in which the conditions are listed corresponds
-         // to their individual severity, where the one listed first will make the most
-         // difference, because it results in the most significant bit of the result.
-         uint imp = 0;
-         imp <<= 1; imp |= !device[i]->Receiving() || ndr;                         // use receiving devices if we don't need to detach existing receivers
-         imp <<= 1; imp |= device[i]->Receiving();                                 // avoid devices that are receiving
-         imp <<= 1; imp |= device[i] == cTransferControl::ReceiverDevice();        // avoid the Transfer Mode receiver device
-         imp <<= 8; imp |= min(max(device[i]->Priority() + MAXPRIORITY, 0), 0xFF); // use the device with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used)
-         imp <<= 8; imp |= min(max(device[i]->ProvidesCa(Channel), 0), 0xFF);      // use the device that provides the lowest number of conditional access methods
-         imp <<= 1; imp |= device[i]->IsPrimaryDevice();                           // avoid the primary device
-         imp <<= 1; imp |= device[i]->HasDecoder();                                // avoid full featured cards
-         if (imp < Impact) {
-            // This device has less impact than any previous one, so we take it.
-            Impact = imp;
-            d = device[i];
-            if (NeedsDetachReceivers)
-               *NeedsDetachReceivers = ndr;
+  // Collect the current priorities of all CAM slots that can decrypt the channel:
+  int NumCamSlots = CamSlots.Count();
+  int SlotPriority[NumCamSlots];
+  int NumUsableSlots = 0;
+  if (Channel->Ca() >= CA_ENCRYPTED_MIN) {
+     for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
+         SlotPriority[CamSlot->Index()] = MAXPRIORITY + 1; // assumes it can't be used
+         if (CamSlot->ModuleStatus() == msReady) {
+            if (CamSlot->ProvidesCa(Channel->Caids())) {
+               if (!ChannelCamRelations.CamChecked(Channel->GetChannelID(), CamSlot->SlotNumber())) {
+                  SlotPriority[CamSlot->Index()] = CamSlot->Priority();
+                  NumUsableSlots++;
+                  }
+               }
             }
          }
+     if (!NumUsableSlots)
+        return NULL; // no CAM is able to decrypt this channel
+     }
+
+  bool NeedsDetachReceivers = false;
+  cDevice *d = NULL;
+  cCamSlot *s = NULL;
+
+  uint32_t Impact = 0xFFFFFFFF; // we're looking for a device with the least impact
+  for (int j = 0; j < NumCamSlots || !NumUsableSlots; j++) {
+      if (NumUsableSlots && SlotPriority[j] > MAXPRIORITY)
+         continue; // there is no CAM available in this slot
+      for (int i = 0; i < numDevices; i++) {
+          if (Channel->Ca() && Channel->Ca() <= CA_DVB_MAX && Channel->Ca() != device[i]->CardIndex() + 1)
+             continue; // a specific card was requested, but not this one
+          if (NumUsableSlots && !CamSlots.Get(j)->Assign(device[i], true))
+             continue; // CAM slot can't be used with this device
+          bool ndr;
+          if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job
+             if (NumUsableSlots && device[i]->CamSlot() && device[i]->CamSlot() != CamSlots.Get(j))
+                ndr = true; // using a different CAM slot requires detaching receivers
+             // Put together an integer number that reflects the "impact" using
+             // this device would have on the overall system. Each condition is represented
+             // by one bit in the number (or several bits, if the condition is actually
+             // a numeric value). The sequence in which the conditions are listed corresponds
+             // to their individual severity, where the one listed first will make the most
+             // difference, because it results in the most significant bit of the result.
+             uint32_t imp = 0;
+             imp <<= 1; imp |= LiveView ? !device[i]->IsPrimaryDevice() || ndr : 0;                                  // prefer the primary device for live viewing if we don't need to detach existing receivers
+             imp <<= 1; imp |= !device[i]->Receiving() || ndr;                                                       // use receiving devices if we don't need to detach existing receivers
+             imp <<= 1; imp |= device[i]->Receiving();                                                               // avoid devices that are receiving
+             imp <<= 1; imp |= device[i] == cTransferControl::ReceiverDevice();                                      // avoid the Transfer Mode receiver device
+             imp <<= 8; imp |= min(max(device[i]->Priority() + MAXPRIORITY, 0), 0xFF);                               // use the device with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used)
+             imp <<= 8; imp |= min(max((NumUsableSlots ? SlotPriority[j] : 0) + MAXPRIORITY, 0), 0xFF);              // use the CAM slot with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used)
+             imp <<= 1; imp |= ndr;                                                                                  // avoid devices if we need to detach existing receivers
+             imp <<= 1; imp |= device[i]->IsPrimaryDevice();                                                         // avoid the primary device
+             imp <<= 1; imp |= device[i]->HasDecoder();                                                              // avoid full featured cards
+             imp <<= 1; imp |= NumUsableSlots ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), j + 1) : 0; // prefer CAMs that are known to decrypt this channel
+             if (imp < Impact) {
+                // This device has less impact than any previous one, so we take it.
+                Impact = imp;
+                d = device[i];
+                NeedsDetachReceivers = ndr;
+                if (NumUsableSlots)
+                   s = CamSlots.Get(j);
+                }
+             }
+          }
+      if (!NumUsableSlots)
+         break; // no CAM necessary, so just one loop over the devices
       }
+  if (d) {
+     if (NeedsDetachReceivers)
+        d->DetachAllReceivers();
+     if (s) {
+        if (s->Device() != d) {
+           if (s->Device())
+              s->Device()->DetachAllReceivers();
+           if (d->CamSlot())
+              d->CamSlot()->Assign(NULL);
+           s->Assign(d);
+           }
+        }
+     else if (d->CamSlot() && !d->CamSlot()->IsDecrypting())
+        d->CamSlot()->Assign(NULL);
+     }
   return d;
 }
 
+void cDevice::SetCamSlot(cCamSlot *CamSlot)
+{
+  camSlot = CamSlot;
+}
+
 void cDevice::Shutdown(void)
 {
   primaryDevice = NULL;
@@ -422,8 +483,8 @@
               DelPid(Pid, PidType);
               return false;
               }
-           if (ciHandler)
-              ciHandler->SetPid(Pid, true);
+           if (camSlot)
+              camSlot->SetPid(Pid, true);
            }
         PRINTPIDS("a");
         return true;
@@ -451,8 +512,8 @@
            DelPid(Pid, PidType);
            return false;
            }
-        if (ciHandler)
-           ciHandler->SetPid(Pid, true);
+        if (camSlot)
+           camSlot->SetPid(Pid, true);
         }
      }
   return true;
@@ -479,8 +540,8 @@
            if (pidHandles[n].used == 0) {
               pidHandles[n].handle = -1;
               pidHandles[n].pid = 0;
-              if (ciHandler)
-                 ciHandler->SetPid(Pid, false);
+              if (camSlot)
+                 camSlot->SetPid(Pid, false);
               }
            }
         PRINTPIDS("E");
@@ -557,8 +618,10 @@
 
 bool cDevice::SwitchChannel(const cChannel *Channel, bool LiveView)
 {
-  if (LiveView)
+  if (LiveView) {
      isyslog("switching to channel %d", Channel->Number());
+     cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer
+     }
   for (int i = 3; i--;) {
       switch (SetChannel(Channel, LiveView)) {
         case scrOk:           return true;
@@ -578,12 +641,13 @@
   bool result = false;
   Direction = sgn(Direction);
   if (Direction) {
+     cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer
      int n = CurrentChannel() + Direction;
      int first = n;
      cChannel *channel;
      while ((channel = Channels.GetByNumber(n, Direction)) != NULL) {
            // try only channels which are currently available
-           if (PrimaryDevice()->ProvidesChannel(channel, Setup.PrimaryLimit) || PrimaryDevice()->CanReplay() && GetDevice(channel, 0))
+           if (GetDevice(channel, 0, true))
               break;
            n = channel->Number() + Direction;
            }
@@ -607,14 +671,9 @@
   if (LiveView)
      StopReplay();
 
-  // If this card is switched to an other transponder, any receivers still
-  // attached to it need to be automatically detached:
-  bool NeedsDetachReceivers = false;
+  cDevice *Device = (LiveView && IsPrimaryDevice()) ? GetDevice(Channel, 0, LiveView) : this;
 
-  // If this card can't receive this channel, we must not actually switch
-  // the channel here, because that would irritate the driver when we
-  // start replaying in Transfer Mode immediately after switching the channel:
-  bool NeedsTransferMode = (LiveView && IsPrimaryDevice() && !ProvidesChannel(Channel, Setup.PrimaryLimit, &NeedsDetachReceivers));
+  bool NeedsTransferMode = Device != this;
 
   eSetChannelResult Result = scrOk;
 
@@ -622,14 +681,10 @@
   // use the card that actually can receive it and transfer data from there:
 
   if (NeedsTransferMode) {
-     cDevice *CaDevice = GetDevice(Channel, 0, &NeedsDetachReceivers);
-     if (CaDevice && CanReplay()) {
+     if (Device && CanReplay()) {
         cStatus::MsgChannelSwitch(this, 0); // only report status if we are actually going to switch the channel
-        if (CaDevice->SetChannel(Channel, false) == scrOk) { // calling SetChannel() directly, not SwitchChannel()!
-           if (NeedsDetachReceivers)
-              CaDevice->DetachAllReceivers();
-           cControl::Launch(new cTransferControl(CaDevice, Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids()));
-           }
+        if (Device->SetChannel(Channel, false) == scrOk) // calling SetChannel() directly, not SwitchChannel()!
+           cControl::Launch(new cTransferControl(Device, Channel->GetChannelID(), Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids()));
         else
            Result = scrNoTransfer;
         }
@@ -644,27 +699,10 @@
         sectionHandler->SetStatus(false);
         sectionHandler->SetChannel(NULL);
         }
-     // Tell the ciHandler about the channel switch and add all PIDs of this
+     // Tell the camSlot about the channel switch and add all PIDs of this
      // channel to it, for possible later decryption:
-     if (ciHandler) {
-        ciHandler->SetSource(Channel->Source(), Channel->Transponder());
-// Men at work - please stand clear! ;-)
-#ifdef XXX_DO_MULTIPLE_CA_CHANNELS
-        if (Channel->Ca() >= CA_ENCRYPTED_MIN) {
-#endif
-           ciHandler->AddPid(Channel->Sid(), Channel->Vpid(), 2);
-           for (const int *Apid = Channel->Apids(); *Apid; Apid++)
-               ciHandler->AddPid(Channel->Sid(), *Apid, 4);
-           for (const int *Dpid = Channel->Dpids(); *Dpid; Dpid++)
-               ciHandler->AddPid(Channel->Sid(), *Dpid, 0);
-#ifdef XXX_DO_MULTIPLE_CA_CHANNELS
-           bool CanDecrypt = ciHandler->CanDecrypt(Channel->Sid());//XXX
-           dsyslog("CanDecrypt %d %d %d %s", CardIndex() + 1, CanDecrypt, Channel->Number(), Channel->Name());//XXX
-           }
-#endif
-        }
-     if (NeedsDetachReceivers)
-        DetachAllReceivers();
+     if (camSlot)
+        camSlot->AddChannel(Channel);
      if (SetChannelDevice(Channel, LiveView)) {
         // Start section handling:
         if (sectionHandler) {
@@ -672,8 +710,8 @@
            sectionHandler->SetStatus(true);
            }
         // Start decrypting any PIDs that might have been set in SetChannelDevice():
-        if (ciHandler)
-           ciHandler->StartDecrypting();
+        if (camSlot)
+           camSlot->StartDecrypting();
         }
      else
         Result = scrFailed;
@@ -968,9 +1006,10 @@
 void cDevice::Detach(cPlayer *Player)
 {
   if (Player && player == Player) {
-     player->Activate(false);
-     player->device = NULL;
-     player = NULL;
+     cPlayer *p = player;
+     player = NULL; // avoids recursive calls to Detach()
+     p->Activate(false);
+     p->device = NULL;
      SetPlayMode(pmNone);
      SetVideoDisplayFormat(eVideoDisplayFormat(Setup.VideoDisplayFormat));
      Audios.ClearAudio();
@@ -1143,16 +1182,6 @@
   return Length;
 }
 
-int cDevice::Ca(void) const
-{
-  int ca = 0;
-  for (int i = 0; i < MAXRECEIVERS; i++) {
-      if (receiver[i] && (ca = receiver[i]->ca) != 0)
-         break; // all receivers have the same ca
-      }
-  return ca;
-}
-
 int cDevice::Priority(void) const
 {
   int priority = IsPrimaryDevice() ? Setup.PrimaryLimit - 1 : DEFAULTPRIORITY;
@@ -1168,16 +1197,6 @@
   return true;
 }
 
-int cDevice::ProvidesCa(const cChannel *Channel) const
-{
-  int Ca = Channel->Ca();
-  if (Ca == CardIndex() + 1)
-     return 1; // exactly _this_ card was requested
-  if (Ca && Ca <= CA_DVB_MAX)
-     return 0; // a specific card was requested, but not _this_ one
-  return !Ca; // by default every card can provide FTA
-}
-
 bool cDevice::Receiving(bool CheckAny) const
 {
   for (int i = 0; i < MAXRECEIVERS; i++) {
@@ -1187,6 +1206,10 @@
   return false;
 }
 
+#define TS_SCRAMBLING_CONTROL  0xC0
+#define TS_SCRAMBLING_TIMEOUT     3 // seconds to wait until a TS becomes unscrambled
+#define TS_SCRAMBLING_TIME_OK    10 // seconds before a Channel/CAM combination is marked a known to decrypt
+
 void cDevice::Action(void)
 {
   if (Running() && OpenDvr()) {
@@ -1196,11 +1219,39 @@
            if (GetTSPacket(b)) {
               if (b) {
                  int Pid = (((uint16_t)b[1] & PID_MASK_HI) << 8) | b[2];
+                 // Check whether the TS packets are scrambled:
+                 bool DetachReceivers = false;
+                 bool DescramblingOk = false;
+                 int CamSlotNumber = 0;
+                 if (startScrambleDetection) {
+                    cCamSlot *cs = CamSlot();
+                    CamSlotNumber = cs ? cs->SlotNumber() : 0;
+                    if (CamSlotNumber) {
+                       bool Scrambled = b[3] & TS_SCRAMBLING_CONTROL;
+                       int t = time(NULL) - startScrambleDetection;
+                       if (Scrambled) {
+                          if (t > TS_SCRAMBLING_TIMEOUT)
+                             DetachReceivers = true;
+                          }
+                       else if (t > TS_SCRAMBLING_TIME_OK) {
+                          DescramblingOk = true;
+                          startScrambleDetection = 0;
+                          }
+                       }
+                    }
                  // Distribute the packet to all attached receivers:
                  Lock();
                  for (int i = 0; i < MAXRECEIVERS; i++) {
-                     if (receiver[i] && receiver[i]->WantsPid(Pid))
-                        receiver[i]->Receive(b, TS_SIZE);
+                     if (receiver[i] && receiver[i]->WantsPid(Pid)) {
+                        if (DetachReceivers) {
+                           ChannelCamRelations.SetChecked(receiver[i]->ChannelID(), CamSlotNumber);
+                           Detach(receiver[i]);
+                           }
+                        else
+                           receiver[i]->Receive(b, TS_SIZE);
+                        if (DescramblingOk)
+                           ChannelCamRelations.SetDecrypt(receiver[i]->ChannelID(), CamSlotNumber);
+                        }
                      }
                  Unlock();
                  }
@@ -1256,10 +1307,11 @@
          Receiver->device = this;
          receiver[i] = Receiver;
          Unlock();
-         if (!Running())
-            Start();
-         if (ciHandler)
-            ciHandler->StartDecrypting();
+         if (camSlot) {
+            camSlot->StartDecrypting();
+            startScrambleDetection = time(NULL);
+            }
+         Start();
          return true;
          }
       }
@@ -1286,10 +1338,10 @@
       else if (receiver[i])
          receiversLeft = true;
       }
-  if (ciHandler)
-     ciHandler->StartDecrypting();
+  if (camSlot)
+     camSlot->StartDecrypting();
   if (!receiversLeft)
-     Cancel(3);
+     Cancel(-1);
 }
 
 void cDevice::DetachAll(int Pid)
@@ -1307,10 +1359,8 @@
 void cDevice::DetachAllReceivers(void)
 {
   cMutexLock MutexLock(&mutexReceiver);
-  for (int i = 0; i < MAXRECEIVERS; i++) {
-      if (receiver[i])
-         Detach(receiver[i]);
-      }
+  for (int i = 0; i < MAXRECEIVERS; i++)
+      Detach(receiver[i]);
 }
 
 // --- cTSBuffer -------------------------------------------------------------
diff -ruN vdr-1.4.5/device.h vdr-1.5.0/device.h
--- vdr-1.4.5/device.h	2006-06-15 11:32:48.000000000 +0200
+++ vdr-1.5.0/device.h	2007-01-03 15:14:29.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: device.h 1.79 2006/06/15 09:32:48 kls Exp $
+ * $Id: device.h 1.80 2007/01/03 14:14:29 kls Exp $
  */
 
 #ifndef __DEVICE_H
@@ -128,12 +128,21 @@
          ///< Gets the device with the given Index.
          ///< \param Index must be in the range 0..numDevices-1.
          ///< \return A pointer to the device, or NULL if the Index was invalid.
-  static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL);
+  static cDevice *GetDevice(const cChannel *Channel, int Priority, bool LiveView);
          ///< Returns a device that is able to receive the given Channel at the
          ///< given Priority, with the least impact on active recordings and
-         ///< live viewing.
-         ///< See ProvidesChannel() for more information on how
-         ///< priorities are handled, and the meaning of NeedsDetachReceivers.
+         ///< live viewing. The LiveView parameter tells whether the device will
+         ///< be used for live viewing or a recording.
+         ///< If the Channel is encrypted, a CAM slot that claims to be able to
+         ///< decrypt the channel is automatically selected and assigned to the
+         ///< returned device. Whether or not this combination of device and CAM
+         ///< slot is actually able to decrypt the channel can only be determined
+         ///< by checking the "scrambling control" bits of the received TS packets.
+         ///< The Action() function automatically does this and takes care that
+         ///< after detaching any receivers because the channel can't be decrypted,
+         ///< this device/CAM combination will be skipped in the next call to
+         ///< GetDevice().
+         ///< See also ProvidesChannel().
   static void Shutdown(void);
          ///< Closes down all devices.
          ///< Must be called at the end of the program.
@@ -171,16 +180,6 @@
          ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
   int DeviceNumber(void) const;
          ///< Returns the number of this device (0 ... numDevices).
-  virtual int ProvidesCa(const cChannel *Channel) const;
-         ///< Checks whether this device provides the conditional access
-         ///< facilities to decrypt the given Channel.
-         ///< Returns 0 if the Channel can't be decrypted, 1 if this is a
-         ///< Free To Air channel or only exactly this device can decrypt it,
-         ///< and > 1 if this device can decrypt the Channel.
-         ///< If the result is greater than 1 and the device has more than one
-         ///< CAM, the value will be increased by the number of CAMs, which
-         ///< allows to select the device with the smallest number of CAMs
-         ///< in order to preserve resources for other recordings.
   virtual bool HasDecoder(void) const;
          ///< Tells whether this device has an MPEG decoder.
 
@@ -199,7 +198,9 @@
   virtual bool ProvidesSource(int Source) const;
          ///< Returns true if this device can provide the given source.
   virtual bool ProvidesTransponder(const cChannel *Channel) const;
-         ///< XXX -> PLUGINS.html!
+         ///< Returns true if this device can provide the transponder of the
+         ///< given Channel (which implies that it can provide the Channel's
+         ///< source).
   virtual bool ProvidesTransponderExclusively(const cChannel *Channel) const;
          ///< Returns true if this is the only device that is able to provide
          ///< the given channel's transponder.
@@ -246,7 +247,7 @@
          ///< channel number while replaying.
   void ForceTransferMode(void);
          ///< Forces the device into transfermode for the current channel.
-  virtual bool HasLock(int TimeoutMs = 0);//XXX PLUGINS.html
+  virtual bool HasLock(int TimeoutMs = 0);
          ///< Returns true if the device has a lock on the requested transponder.
          ///< Default is true, a specific device implementation may return false
          ///< to indicate that it is not ready yet.
@@ -309,10 +310,15 @@
 
 // Common Interface facilities:
 
-protected:
-  cCiHandler *ciHandler;
+private:
+  time_t startScrambleDetection;
+  cCamSlot *camSlot;
 public:
-  cCiHandler *CiHandler(void) { return ciHandler; }
+  void SetCamSlot(cCamSlot *CamSlot);
+         ///< Sets the given CamSlot to be used with this device.
+  cCamSlot *CamSlot(void) const { return camSlot; }
+         ///< Returns the CAM slot that is currently used with this device,
+         ///< or NULL if no CAM slot is in use.
 
 // Image Grab facilities
 
@@ -512,11 +518,12 @@
 private:
   cMutex mutexReceiver;
   cReceiver *receiver[MAXRECEIVERS];
-protected:
+public:
   int Priority(void) const;
       ///< Returns the priority of the current receiving session (0..MAXPRIORITY),
       ///< or -1 if no receiver is currently active. The primary device will
       ///< always return at least Setup.PrimaryLimit-1.
+protected:
   virtual bool OpenDvr(void);
       ///< Opens the DVR of this device and prepares it to deliver a Transport
       ///< Stream for use in a cReceiver.
@@ -530,8 +537,6 @@
       ///< false in case of a non recoverable error, otherwise it returns true,
       ///< even if Data is NULL.
 public:
-  int  Ca(void) const;
-       ///< Returns the ca of the current receiving session(s).
   bool Receiving(bool CheckAny = false) const;
        ///< Returns true if we are currently receiving.
   bool AttachReceiver(cReceiver *Receiver);
diff -ruN vdr-1.4.5/dvbci.c vdr-1.5.0/dvbci.c
--- vdr-1.4.5/dvbci.c	1970-01-01 01:00:00.000000000 +0100
+++ vdr-1.5.0/dvbci.c	2007-01-04 13:49:10.000000000 +0100
@@ -0,0 +1,108 @@
+/*
+ * dvbci.h: Common Interface for DVB devices
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: dvbci.c 1.1 2007/01/04 12:49:10 kls Exp $
+ */
+
+#include "dvbci.h"
+#include <linux/dvb/ca.h>
+#include <sys/ioctl.h>
+#include "device.h"
+
+// --- cDvbCiAdapter ---------------------------------------------------------
+
+cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
+{
+  device = Device;
+  SetDescription("CI adapter on device %d", device->DeviceNumber());
+  fd = Fd;
+  ca_caps_t Caps;
+  if (ioctl(fd, CA_GET_CAP, &Caps) == 0) {
+     if ((Caps.slot_type & CA_CI_LINK) != 0) {
+        int NumSlots = Caps.slot_num;
+        if (NumSlots > 0) {
+           for (int i = 0; i < NumSlots; i++)
+               new cCamSlot(this);
+           Start();
+           }
+        else
+           esyslog("ERROR: no CAM slots found on device %d", device->DeviceNumber());
+        }
+     else
+        isyslog("device %d doesn't support CI link layer interface", device->DeviceNumber());
+     }
+  else
+     esyslog("ERROR: can't get CA capabilities on device %d", device->DeviceNumber());
+}
+
+cDvbCiAdapter::~cDvbCiAdapter()
+{
+  Cancel(3);
+}
+
+int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+{
+  if (Buffer && MaxLength > 0) {
+     struct pollfd pfd[1];
+     pfd[0].fd = fd;
+     pfd[0].events = POLLIN;
+     if (poll(pfd, 1, CAM_READ_TIMEOUT) > 0 && (pfd[0].revents & POLLIN)) {
+        int n = safe_read(fd, Buffer, MaxLength);
+        if (n >= 0)
+           return n;
+        esyslog("ERROR: can't read from CI adapter on device %d: %m", device->DeviceNumber());
+        }
+     }
+  return 0;
+}
+
+void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+{
+  if (Buffer && Length > 0) {
+     if (safe_write(fd, Buffer, Length) != Length)
+        esyslog("ERROR: can't write to CI adapter on device %d: %m", device->DeviceNumber());
+     }
+}
+
+bool cDvbCiAdapter::Reset(int Slot)
+{
+  if (ioctl(fd, CA_RESET, 1 << Slot) != -1)
+     return true;
+  else
+     esyslog("ERROR: can't reset CAM slot %d on device %d: %m", Slot, device->DeviceNumber());
+  return false;
+}
+
+eModuleStatus cDvbCiAdapter::ModuleStatus(int Slot)
+{
+  ca_slot_info_t sinfo;
+  sinfo.num = Slot;
+  if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) {
+     if ((sinfo.flags & CA_CI_MODULE_READY) != 0)
+        return msReady;
+     else if ((sinfo.flags & CA_CI_MODULE_PRESENT) != 0)
+        return msPresent;
+     }
+  else
+     esyslog("ERROR: can't get info of CAM slot %d on device %d: %m", Slot, device->DeviceNumber());
+  return msNone;
+}
+
+bool cDvbCiAdapter::Assign(cDevice *Device, bool Query)
+{
+  // The CI is hardwired to its device, so there's not really much to do here
+  if (Device)
+     return Device == device;
+  return true;
+}
+
+cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd)
+{
+  // TODO check whether a CI is actually present?
+  if (Device)
+     return new cDvbCiAdapter(Device, Fd);
+  return NULL;
+}
diff -ruN vdr-1.4.5/dvbci.h vdr-1.5.0/dvbci.h
--- vdr-1.4.5/dvbci.h	1970-01-01 01:00:00.000000000 +0100
+++ vdr-1.5.0/dvbci.h	2006-11-26 12:19:42.000000000 +0100
@@ -0,0 +1,31 @@
+/*
+ * dvbci.h: Common Interface for DVB devices
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: dvbci.h 1.1 2006/11/26 11:19:42 kls Exp $
+ */
+
+#ifndef __DVBCI_H
+#define __DVBCI_H
+
+#include "ci.h"
+
+class cDvbCiAdapter : public cCiAdapter {
+private:
+  cDevice *device;
+  int fd;
+protected:
+  virtual int Read(uint8_t *Buffer, int MaxLength);
+  virtual void Write(const uint8_t *Buffer, int Length);
+  virtual bool Reset(int Slot);
+  virtual eModuleStatus ModuleStatus(int Slot);
+  virtual bool Assign(cDevice *Device, bool Query = false);
+  cDvbCiAdapter(cDevice *Device, int Fd);
+public:
+  virtual ~cDvbCiAdapter();
+  static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd);
+  };
+
+#endif //__DVBCI_H
diff -ruN vdr-1.4.5/dvbdevice.c vdr-1.5.0/dvbdevice.c
--- vdr-1.4.5/dvbdevice.c	2006-08-14 11:38:32.000000000 +0200
+++ vdr-1.5.0/dvbdevice.c	2007-01-05 12:09:51.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 1.160 2006/08/14 09:38:32 kls Exp $
+ * $Id: dvbdevice.c 1.161 2007/01/05 11:09:51 kls Exp $
  */
 
 #include "dvbdevice.h"
@@ -19,6 +19,7 @@
 #include <sys/mman.h>
 #include "channels.h"
 #include "diseqc.h"
+#include "dvbci.h"
 #include "dvbosd.h"
 #include "eitscan.h"
 #include "player.h"
@@ -28,7 +29,6 @@
 
 #define DO_REC_AND_PLAY_ON_PRIMARY_DEVICE 1
 #define DO_MULTIPLE_RECORDINGS 1
-//#define DO_MULTIPLE_CA_CHANNELS
 
 #define DEV_VIDEO         "/dev/video"
 #define DEV_DVB_ADAPTER   "/dev/dvb/adapter"
@@ -77,7 +77,6 @@
   int lockTimeout;
   time_t lastTimeoutReport;
   fe_type_t frontendType;
-  cCiHandler *ciHandler;
   cChannel channel;
   const char *diseqcCommands;
   eTunerStatus tunerStatus;
@@ -88,19 +87,18 @@
   bool SetFrontend(void);
   virtual void Action(void);
 public:
-  cDvbTuner(int Fd_Frontend, int CardIndex, fe_type_t FrontendType, cCiHandler *CiHandler);
+  cDvbTuner(int Fd_Frontend, int CardIndex, fe_type_t FrontendType);
   virtual ~cDvbTuner();
   bool IsTunedTo(const cChannel *Channel) const;
   void Set(const cChannel *Channel, bool Tune);
   bool Locked(int TimeoutMs = 0);
   };
 
-cDvbTuner::cDvbTuner(int Fd_Frontend, int CardIndex, fe_type_t FrontendType, cCiHandler *CiHandler)
+cDvbTuner::cDvbTuner(int Fd_Frontend, int CardIndex, fe_type_t FrontendType)
 {
   fd_frontend = Fd_Frontend;
   cardIndex = CardIndex;
   frontendType = FrontendType;
-  ciHandler = CiHandler;
   tuneTimeout = 0;
   lockTimeout = 0;
   lastTimeoutReport = 0;
@@ -346,8 +344,6 @@
                   }
           }
 
-        if (ciHandler)
-           ciHandler->Process();
         if (tunerStatus != tsTuned)
            newSet.TimedWait(mutex, 1000);
         }
@@ -360,6 +356,7 @@
 
 cDvbDevice::cDvbDevice(int n)
 {
+  ciAdapter = NULL;
   dvbTuner = NULL;
   frontendType = fe_type_t(-1); // don't know how else to initialize this - there is no FE_UNKNOWN
   spuDecoder = NULL;
@@ -377,6 +374,12 @@
   fd_audio    = DvbOpen(DEV_DVB_AUDIO,  n, O_RDWR | O_NONBLOCK);
   fd_stc      = DvbOpen(DEV_DVB_DEMUX,  n, O_RDWR);
 
+  // Common Interface:
+
+  fd_ca       = DvbOpen(DEV_DVB_CA,     n, O_RDWR);
+  if (fd_ca >= 0)
+     ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca);
+
   // The DVR device (will be opened and closed as needed):
 
   fd_dvr = -1;
@@ -420,8 +423,7 @@
      dvb_frontend_info feinfo;
      if (ioctl(fd_frontend, FE_GET_INFO, &feinfo) >= 0) {
         frontendType = feinfo.type;
-        ciHandler = cCiHandler::CreateCiHandler(*cDvbName(DEV_DVB_CA, n));
-        dvbTuner = new cDvbTuner(fd_frontend, CardIndex(), frontendType, ciHandler);
+        dvbTuner = new cDvbTuner(fd_frontend, CardIndex(), frontendType);
         }
      else
         LOG_ERROR;
@@ -436,6 +438,7 @@
 {
   delete spuDecoder;
   delete dvbTuner;
+  delete ciAdapter;
   // We're not explicitly closing any device files here, since this sometimes
   // caused segfaults. Besides, the program is about to terminate anyway...
 }
@@ -494,32 +497,11 @@
 
 bool cDvbDevice::Ready(void)
 {
-  if (ciHandler) {
-     ciHandler->Process();
-     return ciHandler->Ready();
-     }
+  if (ciAdapter)
+     return ciAdapter->Ready();
   return true;
 }
 
-int cDvbDevice::ProvidesCa(const cChannel *Channel) const
-{
-  int NumCams = 0;
-  if (ciHandler) {
-     NumCams = ciHandler->NumCams();
-     if (Channel->Ca() >= CA_ENCRYPTED_MIN) {
-        unsigned short ids[MAXCAIDS + 1];
-        for (int i = 0; i <= MAXCAIDS; i++) // '<=' copies the terminating 0!
-            ids[i] = Channel->Ca(i);
-        if (ciHandler->ProvidesCa(ids))
-           return NumCams + 1;
-        }
-     }
-  int result = cDevice::ProvidesCa(Channel);
-  if (result > 0)
-     result += NumCams;
-  return result;
-}
-
 cSpuDecoder *cDvbDevice::GetSpuDecoder(void)
 {
   if (!spuDecoder && IsPrimaryDevice())
@@ -770,18 +752,19 @@
   bool hasPriority = Priority < 0 || Priority > this->Priority();
   bool needsDetachReceivers = false;
 
-  if (ProvidesSource(Channel->Source()) && ProvidesCa(Channel)) {
+  if (ProvidesSource(Channel->Source())) {
      result = hasPriority;
      if (Priority >= 0 && Receiving(true)) {
         if (dvbTuner->IsTunedTo(Channel)) {
            if (Channel->Vpid() && !HasPid(Channel->Vpid()) || Channel->Apid(0) && !HasPid(Channel->Apid(0))) {
 #ifdef DO_MULTIPLE_RECORDINGS
-#ifndef DO_MULTIPLE_CA_CHANNELS
-              if (Ca() >= CA_ENCRYPTED_MIN || Channel->Ca() >= CA_ENCRYPTED_MIN)
-                 needsDetachReceivers = Ca() != Channel->Ca();
-              else
-#endif
-              if (!IsPrimaryDevice())
+              if (CamSlot() && Channel->Ca() >= CA_ENCRYPTED_MIN) {
+                 if (CamSlot()->CanDecrypt(Channel))
+                    result = true;
+                 else
+                    needsDetachReceivers = true;
+                 }
+              else if (!IsPrimaryDevice())
                  result = true;
 #ifdef DO_REC_AND_PLAY_ON_PRIMARY_DEVICE
               else
@@ -821,6 +804,8 @@
                            && (LiveView && HasPid(Channel->Vpid() ? Channel->Vpid() : Channel->Apid(0)) && (pidHandles[ptVideo].pid != Channel->Vpid() || (pidHandles[ptAudio].pid != Channel->Apid(0) && (Channel->Dpid(0) ? pidHandles[ptAudio].pid != Channel->Dpid(0) : true)))// the PID is already set as DMX_PES_OTHER
                               || !LiveView && (pidHandles[ptVideo].pid == Channel->Vpid() || pidHandles[ptAudio].pid == Channel->Apid(0)) // a recording is going to shift the PIDs from DMX_PES_AUDIO/VIDEO to DMX_PES_OTHER
                               );
+  if (CamSlot() && !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), CamSlot()->SlotNumber()))
+     StartTransferMode |= LiveView && IsPrimaryDevice() && Channel->Ca() >= CA_ENCRYPTED_MIN;
 
   bool TurnOnLivePIDs = HasDecoder() && !StartTransferMode && LiveView;
 
@@ -861,7 +846,7 @@
      CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true));
      }
   else if (StartTransferMode)
-     cControl::Launch(new cTransferControl(this, Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids()));
+     cControl::Launch(new cTransferControl(this, Channel->GetChannelID(), Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids()));
 
   return true;
 }
@@ -922,13 +907,13 @@
      if (IS_AUDIO_TRACK(Type) || (IS_DOLBY_TRACK(Type) && SetAudioBypass(true))) {
         if (pidHandles[ptAudio].pid && pidHandles[ptAudio].pid != TrackId->id) {
            DetachAll(pidHandles[ptAudio].pid);
-           if (ciHandler)
-              ciHandler->SetPid(pidHandles[ptAudio].pid, false);
+           if (CamSlot())
+              CamSlot()->SetPid(pidHandles[ptAudio].pid, false);
            pidHandles[ptAudio].pid = TrackId->id;
            SetPid(&pidHandles[ptAudio], ptAudio, true);
-           if (ciHandler) {
-              ciHandler->SetPid(pidHandles[ptAudio].pid, true);
-              ciHandler->StartDecrypting();
+           if (CamSlot()) {
+              CamSlot()->SetPid(pidHandles[ptAudio].pid, true);
+              CamSlot()->StartDecrypting();
               }
            }
         }
diff -ruN vdr-1.4.5/dvbdevice.h vdr-1.5.0/dvbdevice.h
--- vdr-1.4.5/dvbdevice.h	2006-05-28 17:05:19.000000000 +0200
+++ vdr-1.5.0/dvbdevice.h	2007-01-05 11:39:52.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbdevice.h 1.41 2006/05/28 15:05:19 kls Exp $
+ * $Id: dvbdevice.h 1.42 2007/01/05 10:39:52 kls Exp $
  */
 
 #ifndef __DVBDEVICE_H
@@ -36,16 +36,20 @@
          ///< \return True if any devices are available.
 private:
   fe_type_t frontendType;
-  int fd_osd, fd_audio, fd_video, fd_dvr, fd_stc;
+  int fd_osd, fd_audio, fd_video, fd_dvr, fd_stc, fd_ca;
 protected:
   virtual void MakePrimaryDevice(bool On);
 public:
   cDvbDevice(int n);
   virtual ~cDvbDevice();
   virtual bool Ready(void);
-  virtual int ProvidesCa(const cChannel *Channel) const;
   virtual bool HasDecoder(void) const;
 
+// Common Interface facilities:
+
+private:
+  cCiAdapter *ciAdapter;
+
 // SPU facilities
 
 private:
diff -ruN vdr-1.4.5/i18n.c vdr-1.5.0/i18n.c
--- vdr-1.4.5/i18n.c	2006-10-14 11:26:41.000000000 +0200
+++ vdr-1.5.0/i18n.c	2007-01-07 13:19:15.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: i18n.c 1.285 2006/10/14 09:26:41 kls Exp $
+ * $Id: i18n.c 1.286 2007/01/07 12:19:15 kls Exp $
  *
  * Translations provided by:
  *
@@ -2598,27 +2598,27 @@
     "Kan ikke åbne CAM menuen!",
     "Menu CAM není dostupné",
   },
-  { "Resetting CAM...",
-    "CAM wird zurückgesetzt...",
-    "Resetiram CAM...",
-    "Reimpostazione modulo CAM...",
-    "CAM wordt herstart...",
-    "",//TODO
-    "Réinitialisation du CAM",
-    "",//TODO
-    "CA-moduuli palautetaan alkutilaan...",
-    "Resetujê CAM...",
-    "Reiniciando CAM...",
-    "",//TODO
-    "Återställer CAM ...",
-    "Se reseteazã CAM...",
-    "A CAM újra indul...",
-    "",//TODO
-    "¿ÕàÕÓàã×ÚÐ CAM...",
-    "",//TODO
-    "CAM mooduli taaskäivitus...",
-    "Nulstiller CAM...",
-    "Restartuje se CAM...",
+  { "CAM is in use - really reset?"
+    "CAM wird benutzt - wirklich zurücksetzen?",
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
   },
   { "Can't reset CAM!",
     "Zurücksetzen des CAM fehlgeschlagen!",
@@ -2642,27 +2642,93 @@
     "Kan ikke nulstille CAM!",
     "CAM modul nelze restartovat!",
   },
-  { "CAM has been reset",
-    "CAM wurde zurückgesetzt",
-    "CAM je resetiran",
-    "Modulo CAM reimpostato",
-    "CAM is herstart",
-    "",//TODO
-    "La CAM a été réinitialisée",
-    "",//TODO
-    "CA-moduuli palautettu alkutilaan",
-    "CAM zosta³ zresetowany",
-    "CAM reiniciado",
-    "¸ãéíå åðáíáöïñÜ óôï CAM",
-    "CA modulen har återställts",
-    "CAM-ul a fost resetat",
-    "A CAM vissza lett állítva",
-    "CAM reiniciada",
-    "CAM-ÜÞÔãÛì ßÕàÕ×ÐßãéÕÝ",
-    "CAM je resetiran",
-    "CAM mooduli taaskäivitus tehtud",
-    "CAM er blevet nulstillet",
-    "CAM byl restartován",
+  { "CAM reset",
+    "CAM zurückgesetzt",
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+  },
+  { "CAM present",
+    "CAM vorhanden",
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+  },
+  { "CAM ready",
+    "CAM bereit",
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+  },
+  { "CAM not responding!",
+    "CAM antwortet nicht!",
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
+    "",//TODO
   },
   { "Please enter %d digits!",
     "Bitte geben Sie %d Ziffern ein!",
@@ -2797,27 +2863,27 @@
     "LNB",
     "LNB",
   },
-  { "CICAM",
-    "CICAM",
-    "CICAM",
-    "Accesso condizionato CICAM",
-    "CICAM",
-    "CICAM",
+  { "CAM",
+    "CAM",
+    "CAM",
+    "Accesso condizionato CAM",
+    "CAM",
+    "CAM",
     "Accès conditionnel",
-    "CICAM",
-    "CICAM",
-    "CICAM",
-    "CICAM",
-    "CICAM",
-    "CICAM",
-    "CICAM",
-    "CICAM",
+    "CAM",
+    "CAM",
+    "CAM",
+    "CAM",
+    "CAM",
+    "CAM",
+    "CAM",
+    "CAM",
     "CI Accés condicional",
     "ÃáÛÞÒÝëÙ ÔÞáâãß",
-    "CICAM",
-    "CICAM",
-    "CICAM",
-    "CICAM",
+    "CAM",
+    "CAM",
+    "CAM",
+    "CAM",
   },
   { "Recording",
     "Aufnahme",
@@ -3964,28 +4030,6 @@
     "Anvend DiSEqC",
     "Pou¾ívat DiSEqC",
   },
-  { "Setup.CICAM$CICAM DVB",
-    "CICAM-DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-    "Accès conditionnel",
-    "CICAM DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-    "Accés condicional CICAM",
-    "CAM-ÜÞÔãÛì DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-    "CICAM DVB",
-  },
   { "Setup.Recording$Margin at start (min)",
     "Vorlauf zum Timer-Beginn (min)",
     "Premik zaèetka snemanja (min)",
diff -ruN vdr-1.4.5/menu.c vdr-1.5.0/menu.c
--- vdr-1.4.5/menu.c	2006-12-02 12:12:02.000000000 +0100
+++ vdr-1.5.0/menu.c	2007-01-07 13:19: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.c 1.446 2006/12/02 11:12:02 kls Exp $
+ * $Id: menu.c 1.447 2007/01/07 12:19:48 kls Exp $
  */
 
 #include "menu.h"
@@ -38,7 +38,9 @@
 
 #define MAXRECORDCONTROLS (MAXDEVICES * MAXRECEIVERS)
 #define MAXINSTANTRECTIME (24 * 60 - 1) // 23:59 hours
-#define MAXWAITFORCAMMENU 4 // seconds to wait for the CAM menu to open
+#define MAXWAITFORCAMMENU  10 // seconds to wait for the CAM menu to open
+#define CAMMENURETYTIMEOUT  3 // seconds after which opening the CAM menu is retried
+#define CAMRESPONSETIMEOUT  5 // seconds to wait for a response from a CAM
 #define MINFREEDISK       300 // minimum free disk space (in MB) required to start recording
 #define NODISKSPACEDELTA  300 // seconds between "Not enough disk space to start recording!" messages
 
@@ -1579,139 +1581,176 @@
 
 // --- cMenuCam --------------------------------------------------------------
 
-cMenuCam::cMenuCam(cCiMenu *CiMenu)
-:cOsdMenu("")
+class cMenuCam : public cOsdMenu {
+private:
+  cCamSlot *camSlot;
+  cCiMenu *ciMenu;
+  cCiEnquiry *ciEnquiry;
+  char *input;
+  int offset;
+  time_t lastCamExchange;
+  void GenerateTitle(const char *s = NULL);
+  void QueryCam(void);
+  void AddMultiLineItem(const char *s);
+  void Set(void);
+  eOSState Select(void);
+public:
+  cMenuCam(cCamSlot *CamSlot);
+  virtual ~cMenuCam();
+  virtual eOSState ProcessKey(eKeys Key);
+  };
+
+cMenuCam::cMenuCam(cCamSlot *CamSlot)
+:cOsdMenu("", 1) // tab necessary for enquiry!
 {
-  dsyslog("CAM: Menu ------------------");
-  ciMenu = CiMenu;
-  selected = false;
+  camSlot = CamSlot;
+  ciMenu = NULL;
+  ciEnquiry = NULL;
+  input = NULL;
   offset = 0;
-  if (ciMenu->Selectable())
-     SetHasHotkeys();
-  SetTitle(*ciMenu->TitleText() ? ciMenu->TitleText() : "CAM");
-  dsyslog("CAM: '%s'", ciMenu->TitleText());
-  if (*ciMenu->SubTitleText()) {
-     dsyslog("CAM: '%s'", ciMenu->SubTitleText());
-     AddMultiLineItem(ciMenu->SubTitleText());
-     offset = Count();
-     }
-  for (int i = 0; i < ciMenu->NumEntries(); i++) {
-      Add(new cOsdItem(hk(ciMenu->Entry(i)), osUnknown, ciMenu->Selectable()));
-      dsyslog("CAM: '%s'", ciMenu->Entry(i));
-      }
-  if (*ciMenu->BottomText()) {
-     AddMultiLineItem(ciMenu->BottomText());
-     dsyslog("CAM: '%s'", ciMenu->BottomText());
-     }
-  Display();
+  lastCamExchange = time(NULL);
+  SetNeedsFastResponse(true);
+  QueryCam();
 }
 
 cMenuCam::~cMenuCam()
 {
-  if (!selected)
+  if (ciMenu)
      ciMenu->Abort();
   delete ciMenu;
+  if (ciEnquiry)
+     ciEnquiry->Abort();
+  delete ciEnquiry;
+  free(input);
 }
 
-void cMenuCam::AddMultiLineItem(const char *s)
+void cMenuCam::GenerateTitle(const char *s)
 {
-  while (s && *s) {
-        const char *p = strchr(s, '\n');
-        int l = p ? p - s : strlen(s);
-        cOsdItem *item = new cOsdItem;
-        item->SetSelectable(false);
-        item->SetText(strndup(s, l), false);
-        Add(item);
-        s = p ? p + 1 : p;
-        }
+  SetTitle(cString::sprintf("CAM %d - %s", camSlot->SlotNumber(), (s && *s) ? s : camSlot->GetCamName()));
 }
 
-eOSState cMenuCam::Select(void)
+void cMenuCam::QueryCam(void)
 {
-  if (ciMenu->Selectable()) {
-     ciMenu->Select(Current() - offset);
-     dsyslog("CAM: select %d", Current() - offset);
+  delete ciMenu;
+  ciMenu = NULL;
+  delete ciEnquiry;
+  ciEnquiry = NULL;
+  if (camSlot->HasUserIO()) {
+     ciMenu = camSlot->GetMenu();
+     ciEnquiry = camSlot->GetEnquiry();
      }
-  else
-     ciMenu->Cancel();
-  selected = true;
-  return osEnd;
+  Set();
 }
 
-eOSState cMenuCam::ProcessKey(eKeys Key)
+void cMenuCam::Set(void)
 {
-  eOSState state = cOsdMenu::ProcessKey(Key);
-
-  if (state == osUnknown) {
-     switch (Key) {
-       case kOk:     return Select();
-       default: break;
-       }
-     }
-  else if (state == osBack) {
-     ciMenu->Cancel();
-     selected = true;
-     return osEnd;
+  if (ciMenu) {
+     Clear();
+     free(input);
+     input = NULL;
+     dsyslog("CAM %d: Menu ------------------", camSlot->SlotNumber());
+     offset = 0;
+     SetHasHotkeys(ciMenu->Selectable());
+     GenerateTitle(ciMenu->TitleText());
+     dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->TitleText());
+     if (*ciMenu->SubTitleText()) {
+        dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->SubTitleText());
+        AddMultiLineItem(ciMenu->SubTitleText());
+        offset = Count();
+        }
+     for (int i = 0; i < ciMenu->NumEntries(); i++) {
+         Add(new cOsdItem(hk(ciMenu->Entry(i)), osUnknown, ciMenu->Selectable()));
+         dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->Entry(i));
+         }
+     if (*ciMenu->BottomText()) {
+        AddMultiLineItem(ciMenu->BottomText());
+        dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->BottomText());
+        }
      }
-  if (ciMenu->HasUpdate()) {
-     selected = true;
-     return osEnd;
+  else if (ciEnquiry) {
+     Clear();
+     int Length = ciEnquiry->ExpectedLength();
+     free(input);
+     input = MALLOC(char, Length + 1);
+     *input = 0;
+     GenerateTitle();
+     Add(new cOsdItem(ciEnquiry->Text(), osUnknown, false));
+     Add(new cOsdItem("", osUnknown, false));
+     Add(new cMenuEditNumItem("", input, Length, ciEnquiry->Blind()));
      }
-  return state;
-}
-
-// --- cMenuCamEnquiry -------------------------------------------------------
-
-cMenuCamEnquiry::cMenuCamEnquiry(cCiEnquiry *CiEnquiry)
-:cOsdMenu("", 1)
-{
-  ciEnquiry = CiEnquiry;
-  int Length = ciEnquiry->ExpectedLength();
-  input = MALLOC(char, Length + 1);
-  *input = 0;
-  replied = false;
-  SetTitle("CAM");
-  Add(new cOsdItem(ciEnquiry->Text(), osUnknown, false));
-  Add(new cOsdItem("", osUnknown, false));
-  Add(new cMenuEditNumItem("", input, Length, ciEnquiry->Blind()));
   Display();
 }
 
-cMenuCamEnquiry::~cMenuCamEnquiry()
+void cMenuCam::AddMultiLineItem(const char *s)
 {
-  if (!replied)
-     ciEnquiry->Abort();
-  free(input);
-  delete ciEnquiry;
+  while (s && *s) {
+        const char *p = strchr(s, '\n');
+        int l = p ? p - s : strlen(s);
+        cOsdItem *item = new cOsdItem;
+        item->SetSelectable(false);
+        item->SetText(strndup(s, l), false);
+        Add(item);
+        s = p ? p + 1 : p;
+        }
 }
 
-eOSState cMenuCamEnquiry::Reply(void)
+eOSState cMenuCam::Select(void)
 {
-  if (ciEnquiry->ExpectedLength() < 0xFF && int(strlen(input)) != ciEnquiry->ExpectedLength()) {
-     char buffer[64];
-     snprintf(buffer, sizeof(buffer), tr("Please enter %d digits!"), ciEnquiry->ExpectedLength());
-     Skins.Message(mtError, buffer);
-     return osContinue;
+  if (ciMenu) {
+     if (ciMenu->Selectable()) {
+        ciMenu->Select(Current() - offset);
+        dsyslog("CAM %d: select %d", camSlot->SlotNumber(), Current() - offset);
+        }
+     else
+        ciMenu->Cancel();
      }
-  ciEnquiry->Reply(input);
-  replied = true;
-  return osEnd;
+  else if (ciEnquiry) {
+     if (ciEnquiry->ExpectedLength() < 0xFF && int(strlen(input)) != ciEnquiry->ExpectedLength()) {
+        char buffer[64];
+        snprintf(buffer, sizeof(buffer), tr("Please enter %d digits!"), ciEnquiry->ExpectedLength());
+        Skins.Message(mtError, buffer);
+        return osContinue;
+        }
+     ciEnquiry->Reply(input);
+     dsyslog("CAM %d: entered '%s'", camSlot->SlotNumber(), ciEnquiry->Blind() ? "****" : input);
+     }
+  QueryCam();
+  return osContinue;
 }
 
-eOSState cMenuCamEnquiry::ProcessKey(eKeys Key)
+eOSState cMenuCam::ProcessKey(eKeys Key)
 {
+  if (!camSlot->HasMMI())
+     return osBack;
+
   eOSState state = cOsdMenu::ProcessKey(Key);
 
-  if (state == osUnknown) {
-     switch (Key) {
-       case kOk:     return Reply();
-       default: break;
-       }
+  if (ciMenu || ciEnquiry) {
+     lastCamExchange = time(NULL);
+     if (state == osUnknown) {
+        switch (Key) {
+          case kOk: return Select();
+          default: break;
+          }
+        }
+     else if (state == osBack) {
+        if (ciMenu)
+           ciMenu->Cancel();
+        if (ciEnquiry)
+           ciEnquiry->Cancel();
+        QueryCam();
+        return osContinue;
+        }
+     if (ciMenu && ciMenu->HasUpdate()) {
+        QueryCam();
+        return osContinue;
+        }
      }
-  else if (state == osBack) {
-     ciEnquiry->Cancel();
-     replied = true;
-     return osEnd;
+  else if (time(NULL) - lastCamExchange < CAMRESPONSETIMEOUT)
+     QueryCam();
+  else {
+     Skins.Message(mtError, tr("CAM not responding!"));
+     return osBack;
      }
   return state;
 }
@@ -1720,21 +1759,9 @@
 
 cOsdObject *CamControl(void)
 {
-  for (int d = 0; d < cDevice::NumDevices(); d++) {
-      cDevice *Device = cDevice::GetDevice(d);
-      if (Device) {
-         cCiHandler *CiHandler = Device->CiHandler();
-         if (CiHandler && CiHandler->HasUserIO()) {
-            cCiMenu *CiMenu = CiHandler->GetMenu();
-            if (CiMenu)
-               return new cMenuCam(CiMenu);
-            else {
-               cCiEnquiry *CiEnquiry = CiHandler->GetEnquiry();
-               if (CiEnquiry)
-                  return new cMenuCamEnquiry(CiEnquiry);
-               }
-            }
-         }
+  for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
+      if (CamSlot->HasUserIO())
+         return new cMenuCam(CamSlot);
       }
   return NULL;
 }
@@ -2454,95 +2481,117 @@
   return state;
 }
 
-// --- cMenuSetupCICAM -------------------------------------------------------
+// --- cMenuSetupCAM ---------------------------------------------------------
 
-class cMenuSetupCICAMItem : public cOsdItem {
+class cMenuSetupCAMItem : public cOsdItem {
 private:
-  cCiHandler *ciHandler;
-  int slot;
+  cCamSlot *camSlot;
 public:
-  cMenuSetupCICAMItem(int Device, cCiHandler *CiHandler, int Slot);
-  cCiHandler *CiHandler(void) { return ciHandler; }
-  int Slot(void) { return slot; }
+  cMenuSetupCAMItem(cCamSlot *CamSlot);
+  cCamSlot *CamSlot(void) { return camSlot; }
+  bool Changed(void);
   };
 
-cMenuSetupCICAMItem::cMenuSetupCICAMItem(int Device, cCiHandler *CiHandler, int Slot)
+cMenuSetupCAMItem::cMenuSetupCAMItem(cCamSlot *CamSlot)
+{
+  camSlot = CamSlot;
+  SetText("");
+  Changed();
+}
+
+bool cMenuSetupCAMItem::Changed(void)
 {
-  ciHandler = CiHandler;
-  slot = Slot;
   char buffer[32];
-  const char *CamName = CiHandler->GetCamName(slot);
-  snprintf(buffer, sizeof(buffer), "%s%d %d\t%s", tr("Setup.CICAM$CICAM DVB"), Device + 1, slot + 1, CamName ? CamName : "-");
-  SetText(buffer);
+  const char *CamName = camSlot->GetCamName();
+  if (!CamName) {
+     switch (camSlot->ModuleStatus()) {
+       case msReset:   CamName = tr("CAM reset"); break;
+       case msPresent: CamName = tr("CAM present"); break;
+       case msReady:   CamName = tr("CAM ready"); break;
+       default:        CamName = "-"; break;
+       }
+     }
+  snprintf(buffer, sizeof(buffer), " %d %s", camSlot->SlotNumber(), CamName);
+  if (strcmp(buffer, Text()) != 0) {
+     SetText(buffer);
+     return true;
+     }
+  return false;
 }
 
-class cMenuSetupCICAM : public cMenuSetupBase {
+class cMenuSetupCAM : public cMenuSetupBase {
 private:
   eOSState Menu(void);
   eOSState Reset(void);
 public:
-  cMenuSetupCICAM(void);
+  cMenuSetupCAM(void);
   virtual eOSState ProcessKey(eKeys Key);
   };
 
-cMenuSetupCICAM::cMenuSetupCICAM(void)
+cMenuSetupCAM::cMenuSetupCAM(void)
 {
-  SetSection(tr("CICAM"));
-  for (int d = 0; d < cDevice::NumDevices(); d++) {
-      cDevice *Device = cDevice::GetDevice(d);
-      if (Device) {
-         cCiHandler *CiHandler = Device->CiHandler();
-         if (CiHandler) {
-            for (int Slot = 0; Slot < CiHandler->NumSlots(); Slot++)
-                Add(new cMenuSetupCICAMItem(Device->CardIndex(), CiHandler, Slot));
-            }
-         }
-      }
+  SetSection(tr("CAM"));
+  SetCols(15);
+  SetHasHotkeys();
+  for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot))
+      Add(new cMenuSetupCAMItem(CamSlot));
   SetHelp(tr("Button$Menu"), tr("Button$Reset"));
 }
 
-eOSState cMenuSetupCICAM::Menu(void)
+eOSState cMenuSetupCAM::Menu(void)
 {
-  cMenuSetupCICAMItem *item = (cMenuSetupCICAMItem *)Get(Current());
+  cMenuSetupCAMItem *item = (cMenuSetupCAMItem *)Get(Current());
   if (item) {
-     if (item->CiHandler()->EnterMenu(item->Slot())) {
-        Skins.Message(mtWarning, tr("Opening CAM menu..."));
-        time_t t = time(NULL);
-        while (time(NULL) - t < MAXWAITFORCAMMENU && !item->CiHandler()->HasUserIO())
-              item->CiHandler()->Process();
-        return osEnd; // the CAM menu will be executed explicitly from the main loop
+     if (item->CamSlot()->EnterMenu()) {
+        Skins.Message(mtStatus, tr("Opening CAM menu..."));
+        time_t t0 = time(NULL);
+        time_t t1 = t0;
+        while (time(NULL) - t0 <= MAXWAITFORCAMMENU) {
+              if (item->CamSlot()->HasUserIO())
+                 break;
+              if (time(NULL) - t1 >= CAMMENURETYTIMEOUT) {
+                 dsyslog("CAM %d: retrying to enter CAM menu...", item->CamSlot()->SlotNumber());
+                 item->CamSlot()->EnterMenu();
+                 t1 = time(NULL);
+                 }
+              cCondWait::SleepMs(100);
+              }
+        Skins.Message(mtStatus, NULL);
+        if (item->CamSlot()->HasUserIO())
+           return AddSubMenu(new cMenuCam(item->CamSlot()));
         }
-     else
-        Skins.Message(mtError, tr("Can't open CAM menu!"));
+     Skins.Message(mtError, tr("Can't open CAM menu!"));
      }
   return osContinue;
 }
 
-eOSState cMenuSetupCICAM::Reset(void)
+eOSState cMenuSetupCAM::Reset(void)
 {
-  cMenuSetupCICAMItem *item = (cMenuSetupCICAMItem *)Get(Current());
+  cMenuSetupCAMItem *item = (cMenuSetupCAMItem *)Get(Current());
   if (item) {
-     Skins.Message(mtWarning, tr("Resetting CAM..."));
-     if (item->CiHandler()->Reset(item->Slot())) {
-        Skins.Message(mtInfo, tr("CAM has been reset"));
-        return osEnd;
+     if (!item->CamSlot()->Device() || Interface->Confirm(tr("CAM is in use - really reset?"))) {
+        if (!item->CamSlot()->Reset())
+           Skins.Message(mtError, tr("Can't reset CAM!"));
         }
-     else
-        Skins.Message(mtError, tr("Can't reset CAM!"));
      }
   return osContinue;
 }
 
-eOSState cMenuSetupCICAM::ProcessKey(eKeys Key)
+eOSState cMenuSetupCAM::ProcessKey(eKeys Key)
 {
-  eOSState state = cMenuSetupBase::ProcessKey(Key);
+  eOSState state = HasSubMenu() ? cMenuSetupBase::ProcessKey(Key) : cOsdMenu::ProcessKey(Key);
 
-  if (state == osUnknown) {
+  if (!HasSubMenu()) {
      switch (Key) {
+       case kOk:
        case kRed:    return Menu();
-       case kGreen:  return Reset();
+       case kGreen:  state = Reset(); break;
        default: break;
        }
+     for (cMenuSetupCAMItem *ci = (cMenuSetupCAMItem *)First(); ci; ci = (cMenuSetupCAMItem *)ci->Next()) {
+         if (ci->Changed())
+            DisplayItem(ci);
+         }
      }
   return state;
 }
@@ -2710,7 +2759,7 @@
   Add(new cOsdItem(hk(tr("EPG")),           osUser2));
   Add(new cOsdItem(hk(tr("DVB")),           osUser3));
   Add(new cOsdItem(hk(tr("LNB")),           osUser4));
-  Add(new cOsdItem(hk(tr("CICAM")),         osUser5));
+  Add(new cOsdItem(hk(tr("CAM")),           osUser5));
   Add(new cOsdItem(hk(tr("Recording")),     osUser6));
   Add(new cOsdItem(hk(tr("Replay")),        osUser7));
   Add(new cOsdItem(hk(tr("Miscellaneous")), osUser8));
@@ -2740,7 +2789,7 @@
     case osUser2: return AddSubMenu(new cMenuSetupEPG);
     case osUser3: return AddSubMenu(new cMenuSetupDVB);
     case osUser4: return AddSubMenu(new cMenuSetupLNB);
-    case osUser5: return AddSubMenu(new cMenuSetupCICAM);
+    case osUser5: return AddSubMenu(new cMenuSetupCAM);
     case osUser6: return AddSubMenu(new cMenuSetupRecord);
     case osUser7: return AddSubMenu(new cMenuSetupReplay);
     case osUser8: return AddSubMenu(new cMenuSetupMisc);
@@ -3126,7 +3175,7 @@
   if (Direction) {
      while (Channel) {
            Channel = Direction > 0 ? Channels.Next(Channel) : Channels.Prev(Channel);
-           if (Channel && !Channel->GroupSep() && (cDevice::PrimaryDevice()->ProvidesChannel(Channel, Setup.PrimaryLimit) || cDevice::GetDevice(Channel, 0)))
+           if (Channel && !Channel->GroupSep() && cDevice::GetDevice(Channel, 0, true))
               return Channel;
            }
      }
@@ -3541,7 +3590,7 @@
   isyslog("record %s", fileName);
   if (MakeDirs(fileName, true)) {
      const cChannel *ch = timer->Channel();
-     recorder = new cRecorder(fileName, ch->Ca(), timer->Priority(), ch->Vpid(), ch->Apids(), ch->Dpids(), ch->Spids());
+     recorder = new cRecorder(fileName, ch->GetChannelID(), timer->Priority(), ch->Vpid(), ch->Apids(), ch->Dpids(), ch->Spids());
      if (device->AttachReceiver(recorder)) {
         Recording.WriteInfo();
         cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true);
@@ -3610,7 +3659,7 @@
 
 bool cRecordControl::Process(time_t t)
 {
-  if (!recorder || !timer || !timer->Matches(t))
+  if (!recorder || !recorder->IsAttached() || !timer || !timer->Matches(t))
      return false;
   AssertFreeDiskSpace(timer->Priority());
   return true;
@@ -3645,15 +3694,9 @@
   cChannel *channel = Channels.GetByNumber(ch);
 
   if (channel) {
-     bool NeedsDetachReceivers = false;
      int Priority = Timer ? Timer->Priority() : Pause ? Setup.PausePriority : Setup.DefaultPriority;
-     cDevice *device = cDevice::GetDevice(channel, Priority, &NeedsDetachReceivers);
+     cDevice *device = cDevice::GetDevice(channel, Priority, false);
      if (device) {
-        if (NeedsDetachReceivers) {
-           Stop(device);
-           if (device == cTransferControl::ReceiverDevice())
-              cControl::Shutdown(); // in case this device was used for Transfer Mode
-           }
         dsyslog("switching device %d to channel %d", device->DeviceNumber() + 1, channel->Number());
         if (!device->SwitchChannel(channel, false)) {
            cThread::EmergencyExit(true);
@@ -3698,19 +3741,6 @@
       }
 }
 
-void cRecordControls::Stop(cDevice *Device)
-{
-  ChangeState();
-  for (int i = 0; i < MAXRECORDCONTROLS; i++) {
-      if (RecordControls[i]) {
-         if (RecordControls[i]->Device() == Device) {
-            isyslog("stopping recording on DVB device %d due to higher priority", Device->CardIndex() + 1);
-            RecordControls[i]->Stop();
-            }
-         }
-      }
-}
-
 bool cRecordControls::PauseLiveVideo(void)
 {
   Skins.Message(mtStatus, tr("Pausing live video..."));
@@ -3882,7 +3912,8 @@
   if (visible) {
      delete displayReplay;
      displayReplay = NULL;
-     needsFastResponse = visible = false;
+     SetNeedsFastResponse(false);
+     visible = false;
      modeOnly = false;
      lastPlay = lastForward = false;
      lastSpeed = -2; // an invalid value
@@ -3923,7 +3954,8 @@
      if (!visible) {
         displayReplay = Skins.Current()->DisplayReplay(modeOnly);
         displayReplay->SetMarks(&marks);
-        needsFastResponse = visible = true;
+        SetNeedsFastResponse(true);
+        visible = true;
         }
      if (Initial) {
         if (title)
diff -ruN vdr-1.4.5/menu.h vdr-1.5.0/menu.h
--- vdr-1.4.5/menu.h	2006-10-20 15:09:57.000000000 +0200
+++ vdr-1.5.0/menu.h	2007-01-05 11:40:54.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menu.h 1.86 2006/10/20 13:09:57 kls Exp $
+ * $Id: menu.h 1.87 2007/01/05 10:40:54 kls Exp $
  */
 
 #ifndef __MENU_H
@@ -128,31 +128,6 @@
   eOSState ProcessKey(eKeys Key);
   };
 
-class cMenuCam : public cOsdMenu {
-private:
-  cCiMenu *ciMenu;
-  bool selected;
-  int offset;
-  void AddMultiLineItem(const char *s);
-  eOSState Select(void);
-public:
-  cMenuCam(cCiMenu *CiMenu);
-  virtual ~cMenuCam();
-  virtual eOSState ProcessKey(eKeys Key);
-  };
-
-class cMenuCamEnquiry : public cOsdMenu {
-private:
-  cCiEnquiry *ciEnquiry;
-  char *input;
-  bool replied;
-  eOSState Reply(void);
-public:
-  cMenuCamEnquiry(cCiEnquiry *CiEnquiry);
-  virtual ~cMenuCamEnquiry();
-  virtual eOSState ProcessKey(eKeys Key);
-  };
-
 cOsdObject *CamControl(void);
 
 class cMenuRecordingItem;
@@ -206,7 +181,6 @@
 public:
   static bool Start(cTimer *Timer = NULL, bool Pause = false);
   static void Stop(const char *InstantId);
-  static void Stop(cDevice *Device);
   static bool PauseLiveVideo(void);
   static const char *GetInstantId(const char *LastInstantId);
   static cRecordControl *GetRecordControl(const char *FileName);
diff -ruN vdr-1.4.5/osdbase.c vdr-1.5.0/osdbase.c
--- vdr-1.4.5/osdbase.c	2006-02-05 15:37:03.000000000 +0100
+++ vdr-1.5.0/osdbase.c	2007-01-05 11:41:16.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osdbase.c 1.29 2006/02/05 14:37:03 kls Exp $
+ * $Id: osdbase.c 1.30 2007/01/05 10:41:16 kls Exp $
  */
 
 #include "osdbase.h"
@@ -127,9 +127,9 @@
   cols[4] = c4;
 }
 
-void cOsdMenu::SetHasHotkeys(void)
+void cOsdMenu::SetHasHotkeys(bool HasHotkeys)
 {
-  hasHotkeys = true;
+  hasHotkeys = HasHotkeys;
   digit = 0;
 }
 
@@ -256,6 +256,20 @@
      }
 }
 
+void cOsdMenu::DisplayItem(cOsdItem *Item)
+{
+  if (Item) {
+     int Index = Item->Index();
+     int Offset = Index - first;
+     if (Offset >= 0 && Offset < first + displayMenuItems) {
+        bool Current = Index == current;
+        displayMenu->SetItem(Item->Text(), Offset, Current && Item->Selectable(), Item->Selectable());
+        if (Current && Item->Selectable())
+           cStatus::MsgOsdCurrentItem(Item->Text());
+        }
+     }
+}
+
 void cOsdMenu::Clear(void)
 {
   if (marked >= 0)
@@ -432,6 +446,8 @@
       if (s && (s = skipspace(s)) != NULL) {
          if (*s == Key - k1 + '1') {
             current = item->Index();
+            RefreshCurrent();
+            Display();
             cRemote::Put(kOk, true);
             break;
             }
@@ -499,4 +515,3 @@
     }
   return osContinue;
 }
-
diff -ruN vdr-1.4.5/osdbase.h vdr-1.5.0/osdbase.h
--- vdr-1.4.5/osdbase.h	2006-01-06 12:55:30.000000000 +0100
+++ vdr-1.5.0/osdbase.h	2007-01-05 11:41:32.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osdbase.h 1.14 2006/01/06 11:55:30 kls Exp $
+ * $Id: osdbase.h 1.15 2007/01/05 10:41:32 kls Exp $
  */
 
 #ifndef __OSDBASE_H
@@ -70,12 +70,13 @@
   friend class cOsdMenu;
 private:
   bool isMenu;
-protected:
   bool needsFastResponse;
+protected:
+  void SetNeedsFastResponse(bool NeedsFastResponse) { needsFastResponse = NeedsFastResponse; }
 public:
   cOsdObject(bool FastResponse = false) { isMenu = false; needsFastResponse = FastResponse; }
   virtual ~cOsdObject() {}
-  bool NeedsFastResponse(void) { return needsFastResponse; }
+  virtual bool NeedsFastResponse(void) { return needsFastResponse; }
   bool IsMenu(void) { return isMenu; }
   virtual void Show(void);
   virtual eOSState ProcessKey(eKeys Key) { return osUnknown; }
@@ -98,12 +99,13 @@
   cSkinDisplayMenu *DisplayMenu(void) { return displayMenu; }
   const char *hk(const char *s);
   void SetCols(int c0, int c1 = 0, int c2 = 0, int c3 = 0, int c4 = 0);
-  void SetHasHotkeys(void);
+  void SetHasHotkeys(bool HasHotkeys = true);
   virtual void Clear(void);
   bool SelectableItem(int idx);
   void SetCurrent(cOsdItem *Item);
   void RefreshCurrent(void);
   void DisplayCurrent(bool Current);
+  void DisplayItem(cOsdItem *Item);
   void CursorUp(void);
   void CursorDown(void);
   void PageUp(void);
@@ -120,6 +122,7 @@
 public:
   cOsdMenu(const char *Title, int c0 = 0, int c1 = 0, int c2 = 0, int c3 = 0, int c4 = 0);
   virtual ~cOsdMenu();
+  virtual bool NeedsFastResponse(void) { return subMenu ? subMenu->NeedsFastResponse() : cOsdObject::NeedsFastResponse(); }
   int Current(void) { return current; }
   void Add(cOsdItem *Item, bool Current = false, cOsdItem *After = NULL);
   void Ins(cOsdItem *Item, bool Current = false, cOsdItem *Before = NULL);
diff -ruN vdr-1.4.5/pat.c vdr-1.5.0/pat.c
--- vdr-1.4.5/pat.c	2006-03-31 14:39:34.000000000 +0200
+++ vdr-1.5.0/pat.c	2007-01-05 11:41:55.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: pat.c 1.16 2006/03/31 12:39:34 kls Exp $
+ * $Id: pat.c 1.17 2007/01/05 10:41:55 kls Exp $
  */
 
 #include "pat.h"
@@ -78,7 +78,7 @@
   bool Is(cCaDescriptors * CaDescriptors);
   bool Empty(void) { return caDescriptors.Count() == 0; }
   void AddCaDescriptor(SI::CaDescriptor *d, bool Stream);
-  int GetCaDescriptors(const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag);
+  int GetCaDescriptors(const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag);
   const int *CaIds(void) { return caIds; }
   };
 
@@ -148,7 +148,7 @@
 #endif
 }
 
-int cCaDescriptors::GetCaDescriptors(const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag)
+int cCaDescriptors::GetCaDescriptors(const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag)
 {
   if (!CaSystemIds || !*CaSystemIds)
      return 0;
@@ -156,7 +156,7 @@
      int length = 0;
      int IsStream = -1;
      for (cCaDescriptor *d = caDescriptors.First(); d; d = caDescriptors.Next(d)) {
-         const unsigned short *caids = CaSystemIds;
+         const int *caids = CaSystemIds;
          do {
             if (d->CaSystem() == *caids) {
                if (length + d->Length() <= BufSize) {
@@ -187,7 +187,7 @@
       // Returns 0 if this is an already known descriptor,
       // 1 if it is an all new descriptor with actual contents,
       // and 2 if an existing descriptor was changed.
-  int GetCaDescriptors(int Source, int Transponder, int ServiceId, const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag);
+  int GetCaDescriptors(int Source, int Transponder, int ServiceId, const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag);
   };
 
 int cCaDescriptorHandler::AddCaDescriptors(cCaDescriptors *CaDescriptors)
@@ -208,7 +208,7 @@
   return CaDescriptors->Empty() ? 0 : 1;
 }
 
-int cCaDescriptorHandler::GetCaDescriptors(int Source, int Transponder, int ServiceId, const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag)
+int cCaDescriptorHandler::GetCaDescriptors(int Source, int Transponder, int ServiceId, const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag)
 {
   cMutexLock MutexLock(&mutex);
   StreamFlag = false;
@@ -221,7 +221,7 @@
 
 cCaDescriptorHandler CaDescriptorHandler;
 
-int GetCaDescriptors(int Source, int Transponder, int ServiceId, const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag)
+int GetCaDescriptors(int Source, int Transponder, int ServiceId, const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag)
 {
   return CaDescriptorHandler.GetCaDescriptors(Source, Transponder, ServiceId, CaSystemIds, BufSize, Data, StreamFlag);
 }
diff -ruN vdr-1.4.5/pat.h vdr-1.5.0/pat.h
--- vdr-1.4.5/pat.h	2006-03-29 17:18:38.000000000 +0200
+++ vdr-1.5.0/pat.h	2007-01-05 11:42:11.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: pat.h 1.6 2006/03/29 15:18:38 kls Exp $
+ * $Id: pat.h 1.7 2007/01/05 10:42:11 kls Exp $
  */
 
 #ifndef __PAT_H
@@ -32,7 +32,7 @@
   void Trigger(void);
   };
 
-int GetCaDescriptors(int Source, int Transponder, int ServiceId, const unsigned short *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag);
+int GetCaDescriptors(int Source, int Transponder, int ServiceId, const int *CaSystemIds, int BufSize, uchar *Data, bool &StreamFlag);
          ///< Gets all CA descriptors for a given channel.
          ///< Copies all available CA descriptors for the given Source, Transponder and ServiceId
          ///< into the provided buffer at Data (at most BufSize bytes). Only those CA descriptors
diff -ruN vdr-1.4.5/receiver.c vdr-1.5.0/receiver.c
--- vdr-1.4.5/receiver.c	2006-03-26 16:07:21.000000000 +0200
+++ vdr-1.5.0/receiver.c	2007-01-05 11:42:29.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: receiver.c 1.5 2006/03/26 14:07:21 kls Exp $
+ * $Id: receiver.c 1.6 2007/01/05 10:42:29 kls Exp $
  */
 
 #include "receiver.h"
@@ -12,10 +12,10 @@
 #include <stdio.h>
 #include "tools.h"
 
-cReceiver::cReceiver(int Ca, int Priority, int Pid, const int *Pids1, const int *Pids2, const int *Pids3)
+cReceiver::cReceiver(tChannelID ChannelID, int Priority, int Pid, const int *Pids1, const int *Pids2, const int *Pids3)
 {
   device = NULL;
-  ca = Ca;
+  channelID = ChannelID;
   priority = Priority;
   numPids = 0;
   if (Pid)
diff -ruN vdr-1.4.5/receiver.h vdr-1.5.0/receiver.h
--- vdr-1.4.5/receiver.h	2006-05-27 11:04:22.000000000 +0200
+++ vdr-1.5.0/receiver.h	2007-01-05 12:00:36.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: receiver.h 1.4 2006/05/27 09:04:22 kls Exp $
+ * $Id: receiver.h 1.5 2007/01/05 11:00:36 kls Exp $
  */
 
 #ifndef __RECEIVER_H
@@ -18,7 +18,7 @@
   friend class cDevice;
 private:
   cDevice *device;
-  int ca;
+  tChannelID channelID;
   int priority;
   int pids[MAXRECEIVEPIDS];
   int numPids;
@@ -38,8 +38,8 @@
                ///< will be delivered only ONCE, so the cReceiver must make sure that
                ///< it will be able to buffer the data if necessary.
 public:
-  cReceiver(int Ca, int Priority, int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL);
-               ///< Creates a new receiver that requires conditional access Ca and has
+  cReceiver(tChannelID ChannelID, int Priority, int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL);
+               ///< Creates a new receiver for the channel with the given ChannelID with
                ///< the given Priority. Pid is a single PID (typically the video PID), while
                ///< Pids1...Pids3 are pointers to zero terminated lists of PIDs.
                ///< If any of these PIDs are 0, they will be silently ignored.
@@ -47,7 +47,18 @@
                ///< Priority may be any value in the range -99..99. Negative values indicate
                ///< that this cReceiver may be detached at any time (without blocking the
                ///< cDevice it is attached to).
+               ///< The ChannelID is necessary to allow the device that will be used for this
+               ///< receiver to detect and store whether the channel can be decrypted in case
+               ///< this is an encrypted channel. If the channel is not encrypted or this
+               ///< detection is not wanted, an invalid tChannelID may be given.
   virtual ~cReceiver();
+  tChannelID ChannelID(void) { return channelID; }
+  bool IsAttached(void) { return device != NULL; }
+               ///< Returns true if this receiver is (still) attached to a device.
+               ///< A receiver may be automatically detached from its device in
+               ///< case the device is needed otherwise, so code that uses a cReceiver
+               ///< should repeatedly check whether it is still attached, and if
+               ///< it isn't, delete it (or take any other appropriate measures).
   };
 
 #endif //__RECEIVER_H
diff -ruN vdr-1.4.5/recorder.c vdr-1.5.0/recorder.c
--- vdr-1.4.5/recorder.c	2006-01-08 12:01:25.000000000 +0100
+++ vdr-1.5.0/recorder.c	2007-01-05 11:43:09.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recorder.c 1.17 2006/01/08 11:01:25 kls Exp $
+ * $Id: recorder.c 1.18 2007/01/05 10:43:09 kls Exp $
  */
 
 #include "recorder.h"
@@ -21,6 +21,8 @@
 #define MINFREEDISKSPACE    (512) // MB
 #define DISKCHECKINTERVAL   100 // seconds
 
+// --- cFileWriter -----------------------------------------------------------
+
 class cFileWriter : public cThread {
 private:
   cRemux *remux;
@@ -121,8 +123,10 @@
         }
 }
 
-cRecorder::cRecorder(const char *FileName, int Ca, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids)
-:cReceiver(Ca, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids)
+// --- cRecorder -------------------------------------------------------------
+
+cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids)
+:cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids)
 ,cThread("recording")
 {
   // Make sure the disk is up and running:
diff -ruN vdr-1.4.5/recorder.h vdr-1.5.0/recorder.h
--- vdr-1.4.5/recorder.h	2005-08-13 13:31:18.000000000 +0200
+++ vdr-1.5.0/recorder.h	2007-01-05 11:44:05.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recorder.h 1.4 2005/08/13 11:31:18 kls Exp $
+ * $Id: recorder.h 1.5 2007/01/05 10:44:05 kls Exp $
  */
 
 #ifndef __RECORDER_H
@@ -28,9 +28,9 @@
   virtual void Receive(uchar *Data, int Length);
   virtual void Action(void);
 public:
-  cRecorder(const char *FileName, int Ca, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids);
-               // Creates a new recorder that requires conditional access Ca, has
-               // the given Priority and will record the given PIDs into the file FileName.
+  cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids);
+               // Creates a new recorder for the channel with the given ChannelID and
+               // the given Priority that will record the given PIDs into the file FileName.
   virtual ~cRecorder();
   };
 
diff -ruN vdr-1.4.5/thread.c vdr-1.5.0/thread.c
--- vdr-1.4.5/thread.c	2006-09-24 14:54:47.000000000 +0200
+++ vdr-1.5.0/thread.c	2007-01-05 11:44:22.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: thread.c 1.58 2006/09/24 12:54:47 kls Exp $
+ * $Id: thread.c 1.59 2007/01/05 10:44:22 kls Exp $
  */
 
 #include "thread.h"
@@ -249,17 +249,29 @@
   return NULL;
 }
 
+#define THREAD_STOP_TIMEOUT  3000 // ms to wait for a thread to stop before newly starting it
+#define THREAD_STOP_SLEEP      30 // ms to sleep while waiting for a thread to stop
+
 bool cThread::Start(void)
 {
-  if (!active) {
-     active = running = true;
-     if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0) {
-        pthread_detach(childTid); // auto-reap
-        }
-     else {
-        LOG_ERROR;
-        active = running = false;
-        return false;
+  if (!running) {
+     if (active) {
+        // Wait until the previous incarnation of this thread has completely ended
+        // before starting it newly:
+        cTimeMs RestartTimeout;
+        while (!running && active && RestartTimeout.Elapsed() < THREAD_STOP_TIMEOUT)
+              cCondWait::SleepMs(THREAD_STOP_SLEEP);
+        }
+     if (!active) {
+        active = running = true;
+        if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0) {
+           pthread_detach(childTid); // auto-reap
+           }
+        else {
+           LOG_ERROR;
+           active = running = false;
+           return false;
+           }
         }
      }
   return true;
diff -ruN vdr-1.4.5/thread.h vdr-1.5.0/thread.h
--- vdr-1.4.5/thread.h	2006-09-24 12:10:37.000000000 +0200
+++ vdr-1.5.0/thread.h	2007-01-05 11:44:38.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: thread.h 1.37 2006/09/24 10:10:37 kls Exp $
+ * $Id: thread.h 1.38 2007/01/05 10:44:38 kls Exp $
  */
 
 #ifndef __THREAD_H
@@ -115,6 +115,7 @@
   void SetDescription(const char *Description, ...) __attribute__ ((format (printf, 2, 3)));
   bool Start(void);
        ///< Actually starts the thread.
+       ///< If the thread is already running, nothing happens.
   bool Active(void);
        ///< Checks whether the thread is still alive.
   static bool EmergencyExit(bool Request = false);
diff -ruN vdr-1.4.5/tools.c vdr-1.5.0/tools.c
--- vdr-1.4.5/tools.c	2006-12-02 12:12:59.000000000 +0100
+++ vdr-1.5.0/tools.c	2007-01-05 11:44:57.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 1.121 2006/12/02 11:12:59 kls Exp $
+ * $Id: tools.c 1.122 2007/01/05 10:44:57 kls Exp $
  */
 
 #include "tools.h"
@@ -542,9 +542,9 @@
 
 // --- cTimeMs ---------------------------------------------------------------
 
-cTimeMs::cTimeMs(void)
+cTimeMs::cTimeMs(int Ms)
 {
-  Set();
+  Set(Ms);
 }
 
 uint64_t cTimeMs::Now(void)
diff -ruN vdr-1.4.5/tools.h vdr-1.5.0/tools.h
--- vdr-1.4.5/tools.h	2006-12-03 18:38:38.000000000 +0100
+++ vdr-1.5.0/tools.h	2007-01-05 11:45:11.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: tools.h 1.96 2006/12/03 17:38:38 kls Exp $
+ * $Id: tools.h 1.97 2007/01/05 10:45:11 kls Exp $
  */
 
 #ifndef __TOOLS_H
@@ -23,7 +23,6 @@
 #include <sys/types.h>
 
 typedef unsigned char uchar;
-#define uint64 uint64_t // for compatibility - TODO remove in version 1.5
 
 extern int SysLogLevel;
 
@@ -162,7 +161,8 @@
 private:
   uint64_t begin;
 public:
-  cTimeMs(void);
+  cTimeMs(int Ms = 0);
+      ///< Creates a timer with ms resolution and an initial timeout of Ms.
   static uint64_t Now(void);
   void Set(int Ms = 0);
   bool TimedOut(void);
diff -ruN vdr-1.4.5/transfer.c vdr-1.5.0/transfer.c
--- vdr-1.4.5/transfer.c	2006-01-29 18:24:39.000000000 +0100
+++ vdr-1.5.0/transfer.c	2007-01-05 11:45:28.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 1.33 2006/01/29 17:24:39 kls Exp $
+ * $Id: transfer.c 1.34 2007/01/05 10:45:28 kls Exp $
  */
 
 #include "transfer.h"
@@ -14,8 +14,8 @@
 
 // --- cTransfer -------------------------------------------------------------
 
-cTransfer::cTransfer(int VPid, const int *APids, const int *DPids, const int *SPids)
-:cReceiver(0, -1, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids)
+cTransfer::cTransfer(tChannelID ChannelID, int VPid, const int *APids, const int *DPids, const int *SPids)
+:cReceiver(ChannelID, -1, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids)
 ,cThread("transfer")
 {
   ringBuffer = new cRingBufferLinear(TRANSFERBUFSIZE, TS_SIZE * 2, true, "Transfer");
@@ -34,17 +34,18 @@
 {
   if (On)
      Start();
-  else
+  else {
      Cancel(3);
+     cPlayer::Detach();
+     }
 }
 
 void cTransfer::Receive(uchar *Data, int Length)
 {
-  if (IsAttached() && Running()) {
+  if (cPlayer::IsAttached() && Running()) {
      int p = ringBuffer->Put(Data, Length);
      if (p != Length && Running())
         ringBuffer->ReportOverflow(Length - p);
-     return;
      }
 }
 
@@ -110,8 +111,8 @@
 
 cDevice *cTransferControl::receiverDevice = NULL;
 
-cTransferControl::cTransferControl(cDevice *ReceiverDevice, int VPid, const int *APids, const int *DPids, const int *SPids)
-:cControl(transfer = new cTransfer(VPid, APids, DPids, SPids), true)
+cTransferControl::cTransferControl(cDevice *ReceiverDevice, tChannelID ChannelID, int VPid, const int *APids, const int *DPids, const int *SPids)
+:cControl(transfer = new cTransfer(ChannelID, VPid, APids, DPids, SPids), true)
 {
   ReceiverDevice->AttachReceiver(transfer);
   receiverDevice = ReceiverDevice;
diff -ruN vdr-1.4.5/transfer.h vdr-1.5.0/transfer.h
--- vdr-1.4.5/transfer.h	2006-01-29 18:24:43.000000000 +0100
+++ vdr-1.5.0/transfer.h	2007-01-05 11:45:45.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 1.11 2006/01/29 17:24:43 kls Exp $
+ * $Id: transfer.h 1.12 2007/01/05 10:45:45 kls Exp $
  */
 
 #ifndef __TRANSFER_H
@@ -25,7 +25,7 @@
   virtual void Receive(uchar *Data, int Length);
   virtual void Action(void);
 public:
-  cTransfer(int VPid, const int *APids, const int *DPids, const int *SPids);
+  cTransfer(tChannelID ChannelID, int VPid, const int *APids, const int *DPids, const int *SPids);
   virtual ~cTransfer();
   };
 
@@ -34,7 +34,7 @@
   cTransfer *transfer;
   static cDevice *receiverDevice;
 public:
-  cTransferControl(cDevice *ReceiverDevice, int VPid, const int *APids, const int *DPids, const int *SPids);
+  cTransferControl(cDevice *ReceiverDevice, tChannelID ChannelID, int VPid, const int *APids, const int *DPids, const int *SPids);
   ~cTransferControl();
   virtual void Hide(void) {}
   static cDevice *ReceiverDevice(void) { return receiverDevice; }
diff -ruN vdr-1.4.5/vdr.c vdr-1.5.0/vdr.c
--- vdr-1.4.5/vdr.c	2006-12-02 17:22:12.000000000 +0100
+++ vdr-1.5.0/vdr.c	2007-01-05 11:46:14.000000000 +0100
@@ -22,7 +22,7 @@
  *
  * The project's page is at http://www.cadsoft.de/vdr
  *
- * $Id: vdr.c 1.282 2006/12/02 16:22:12 kls Exp $
+ * $Id: vdr.c 1.283 2007/01/05 10:46:14 kls Exp $
  */
 
 #include <getopt.h>
@@ -68,8 +68,6 @@
 #define SHUTDOWNWAIT      300 // seconds to wait in user prompt before automatic shutdown
 #define MANUALSTART       600 // seconds the next timer must be in the future to assume manual start
 #define CHANNELSAVEDELTA  600 // seconds before saving channels.conf after automatic modifications
-#define LASTCAMMENUTIMEOUT  3 // seconds to run the main loop 'fast' after a CAM menu has been closed
-                              // in order to react on a possible new CAM menu as soon as possible
 #define DEVICEREADYTIMEOUT 30 // seconds to wait until all devices are ready
 #define MENUTIMEOUT       120 // seconds of user inactivity after which an OSD display is closed
 #define SHUTDOWNRETRY     300 // seconds before trying again to shut down
@@ -501,7 +499,6 @@
   int PreviousChannelIndex = 0;
   time_t LastChannelChanged = time(NULL);
   time_t LastActivity = 0;
-  time_t LastCamMenu = 0;
   int MaxLatencyTime = 0;
   bool ForceShutdown = false;
   bool UserShutdown = false;
@@ -851,19 +848,14 @@
            DeletedRecordings.Update();
            }
         // CAM control:
-        if (!Menu && !cOsd::IsOpen()) {
+        if (!Menu && !cOsd::IsOpen())
            Menu = CamControl();
-           if (Menu)
-              LastCamMenu = 0;
-           else if (!LastCamMenu)
-              LastCamMenu = time(NULL);
-           }
         // Queued messages:
         if (!Skins.IsOpen())
            Skins.ProcessQueuedMessages();
         // User Input:
         cOsdObject *Interact = Menu ? Menu : cControl::Control();
-        eKeys key = Interface->GetKey((!Interact || !Interact->NeedsFastResponse()) && time(NULL) - LastCamMenu > LASTCAMMENUTIMEOUT);
+        eKeys key = Interface->GetKey(!Interact || !Interact->NeedsFastResponse());
         if (NORMALKEY(key) != kNone) {
            EITScanner.Activity();
            LastActivity = time(NULL);
@@ -1052,9 +1044,6 @@
               else if (time(NULL) - LastActivity > MENUTIMEOUT)
                  state = osEnd;
               }
-           // TODO make the CAM menu stay open in case of automatic updates and have it return osContinue; then the following two lines can be removed again
-           else if (state == osEnd && LastActivity > 1)
-              LastActivity = time(NULL);
            switch (state) {
              case osPause:  DELETE_MENU;
                             cControl::Shutdown(); // just in case
