summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Hanisch <dvb@flensrocker.de>2013-04-03 21:22:25 +0200
committerLars Hanisch <dvb@flensrocker.de>2013-04-03 21:22:25 +0200
commit1500458aeac572780b2045bdff8581d692451772 (patch)
treeec08354dd4c1e95fec352e12f981d5f62431cbd9
parent749bac14d91a45f1147d8294011ba2a15e80732a (diff)
downloadvdr-plugin-dynamite-1500458aeac572780b2045bdff8581d692451772.tar.gz
vdr-plugin-dynamite-1500458aeac572780b2045bdff8581d692451772.tar.bz2
add patches for vdr 2.0.0
-rw-r--r--patches/vdr-1.7.42-dynamite+externalci+rotorng.patch1070
-rw-r--r--patches/vdr-1.7.42-dynamite+externalci.patch988
-rw-r--r--patches/vdr-1.7.42-dynamite.patch818
-rw-r--r--patches/vdr-2.0.0-dynamite+externalci+rotorng.patch1070
-rw-r--r--patches/vdr-2.0.0-dynamite+externalci.patch988
-rw-r--r--patches/vdr-2.0.0-dynamite.patch818
6 files changed, 5752 insertions, 0 deletions
diff --git a/patches/vdr-1.7.42-dynamite+externalci+rotorng.patch b/patches/vdr-1.7.42-dynamite+externalci+rotorng.patch
new file mode 100644
index 0000000..ea92508
--- /dev/null
+++ b/patches/vdr-1.7.42-dynamite+externalci+rotorng.patch
@@ -0,0 +1,1070 @@
+diff --git a/ci.c b/ci.c
+index 9a4a829..88b50e7 100644
+--- a/ci.c
++++ b/ci.c
+@@ -1571,6 +1571,8 @@ cCamSlot::cCamSlot(cCiAdapter *CiAdapter)
+
+ cCamSlot::~cCamSlot()
+ {
++ if (ciAdapter && ciAdapter->assignedDevice)
++ ciAdapter->assignedDevice->SetCamSlot(NULL);
+ CamSlots.Del(this, false);
+ DeleteAllConnections();
+ }
+diff --git a/ci.h b/ci.h
+index 74e0270..818ea29 100644
+--- a/ci.h
++++ b/ci.h
+@@ -72,6 +72,7 @@ public:
+ };
+
+ class cDevice;
++class cTSBufferBase;
+ class cCamSlot;
+
+ enum eModuleStatus { msNone, msReset, msPresent, msReady };
+@@ -115,6 +116,13 @@ public:
+ ///< 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.
++#define EXTERNALCI_PATCH
++ virtual cTSBufferBase *GetTSBuffer(int FdDvr) { return NULL; }
++ ///< Derived classes can return a special TS buffer with features
++ ///< like rerouting the stream through an external ci.
++ ///< The caller must delete the buffer.
++ virtual bool SetIdle(bool Idle, bool TestOnly) { return false; }
++ virtual bool IsIdle(void) const { return false; }
+ };
+
+ class cTPDU;
+diff --git a/device.c b/device.c
+index ff51a11..dfdc081 100644
+--- a/device.c
++++ b/device.c
+@@ -69,12 +69,22 @@ int cDevice::currentChannel = 1;
+ cDevice *cDevice::device[MAXDEVICES] = { NULL };
+ cDevice *cDevice::primaryDevice = NULL;
+ cList<cDeviceHook> cDevice::deviceHooks;
+-
+-cDevice::cDevice(void)
++cDevice *cDevice::nextParentDevice = NULL;
++
++cDevice::cDevice(cDevice *ParentDevice)
+ :patPmtParser(true)
+-{
+- cardIndex = nextCardIndex++;
+- dsyslog("new device number %d", CardIndex() + 1);
++,isIdle(false)
++,parentDevice(ParentDevice)
++,subDevice(NULL)
++{
++ if (!ParentDevice)
++ parentDevice = nextParentDevice;
++ cDevice::nextParentDevice = NULL;
++ if (parentDevice)
++ cardIndex = parentDevice->cardIndex;
++ else
++ cardIndex = nextCardIndex++;
++ dsyslog("new %sdevice number %d", parentDevice ? "sub-" : "", CardIndex() + 1);
+
+ SetDescription("receiver on device %d", CardIndex() + 1);
+
+@@ -106,10 +116,14 @@ cDevice::cDevice(void)
+ for (int i = 0; i < MAXRECEIVERS; i++)
+ receiver[i] = NULL;
+
+- if (numDevices < MAXDEVICES)
+- device[numDevices++] = this;
++ if (!parentDevice) {
++ if (numDevices < MAXDEVICES)
++ device[numDevices++] = this;
++ else
++ esyslog("ERROR: too many devices or \"dynamite\"-unpatched device creator!");
++ }
+ else
+- esyslog("ERROR: too many devices!");
++ parentDevice->subDevice = this;
+ }
+
+ cDevice::~cDevice()
+@@ -118,6 +132,29 @@ cDevice::~cDevice()
+ DetachAllReceivers();
+ delete liveSubtitle;
+ delete dvbSubtitleConverter;
++ if (parentDevice && (parentDevice->subDevice == this))
++ parentDevice->subDevice = NULL;
++}
++
++bool cDevice::SetIdle(bool Idle)
++{
++ if (parentDevice)
++ return parentDevice->SetIdle(Idle);
++ if (isIdle == Idle)
++ return true;
++ if (Receiving(false))
++ return false;
++ if (Idle) {
++ Detach(player);
++ DetachAllReceivers();
++ }
++ if (!SetIdleDevice(Idle, true))
++ return false;
++ isIdle = Idle;
++ if (SetIdleDevice(Idle, false))
++ return true;
++ isIdle = !Idle;
++ return false;
+ }
+
+ bool cDevice::WaitForAllDevicesReady(int Timeout)
+@@ -156,6 +193,8 @@ int cDevice::NextCardIndex(int n)
+
+ int cDevice::DeviceNumber(void) const
+ {
++ if (parentDevice)
++ return parentDevice->DeviceNumber();
+ for (int i = 0; i < numDevices; i++) {
+ if (device[i] == this)
+ return i;
+@@ -356,6 +395,8 @@ bool cDevice::HasCi(void)
+
+ void cDevice::SetCamSlot(cCamSlot *CamSlot)
+ {
++ if (parentDevice)
++ return parentDevice->SetCamSlot(CamSlot);
+ camSlot = CamSlot;
+ }
+
+@@ -568,6 +609,10 @@ void cDevice::DelLivePids(void)
+
+ void cDevice::StartSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StartSectionHandler();
++ return;
++ }
+ if (!sectionHandler) {
+ sectionHandler = new cSectionHandler(this);
+ AttachFilter(eitFilter = new cEitFilter);
+@@ -579,6 +624,10 @@ void cDevice::StartSectionHandler(void)
+
+ void cDevice::StopSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StopSectionHandler();
++ return;
++ }
+ if (sectionHandler) {
+ delete nitFilter;
+ delete sdtFilter;
+@@ -610,12 +659,17 @@ void cDevice::CloseFilter(int Handle)
+
+ void cDevice::AttachFilter(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->AttachFilter(Filter);
++ SetIdle(false);
+ if (sectionHandler)
+ sectionHandler->Attach(Filter);
+ }
+
+ void cDevice::Detach(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Filter);
+ if (sectionHandler)
+ sectionHandler->Detach(Filter);
+ }
+@@ -777,6 +831,7 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
+ sectionHandler->SetStatus(false);
+ sectionHandler->SetChannel(NULL);
+ }
++ SetIdle(false);
+ // Tell the camSlot about the channel switch and add all PIDs of this
+ // channel to it, for possible later decryption:
+ if (camSlot)
+@@ -823,19 +878,27 @@ void cDevice::ForceTransferMode(void)
+ {
+ if (!cTransferControl::ReceiverDevice()) {
+ cChannel *Channel = Channels.GetByNumber(CurrentChannel());
+- if (Channel)
++ if (Channel) {
++ SetIdle(false);
+ SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
++ }
+ }
+ }
+
+ int cDevice::Occupied(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Occupied();
+ int Seconds = occupiedTimeout - time(NULL);
+ return Seconds > 0 ? Seconds : 0;
+ }
+
+ void cDevice::SetOccupied(int Seconds)
+ {
++ if (parentDevice) {
++ parentDevice->SetOccupied(Seconds);
++ return;
++ }
+ if (Seconds >= 0)
+ occupiedTimeout = time(NULL) + min(Seconds, MAXOCCUPIEDTIMEOUT);
+ }
+@@ -1216,7 +1279,10 @@ bool cDevice::Transferring(void) const
+
+ bool cDevice::AttachPlayer(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->AttachPlayer(Player);
+ if (CanReplay()) {
++ SetIdle(false);
+ if (player)
+ Detach(player);
+ DELETENULL(liveSubtitle);
+@@ -1235,6 +1301,8 @@ bool cDevice::AttachPlayer(cPlayer *Player)
+
+ void cDevice::Detach(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Player);
+ if (Player && player == Player) {
+ cPlayer *p = player;
+ player = NULL; // avoids recursive calls to Detach()
+@@ -1254,6 +1322,8 @@ void cDevice::Detach(cPlayer *Player)
+
+ void cDevice::StopReplay(void)
+ {
++ if (parentDevice)
++ return parentDevice->StopReplay();
+ if (player) {
+ Detach(player);
+ if (IsPrimaryDevice())
+@@ -1534,6 +1604,8 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
+
+ int cDevice::Priority(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Priority();
+ int priority = IDLEPRIORITY;
+ if (IsPrimaryDevice() && !Replaying() && HasProgramme())
+ priority = TRANSFERPRIORITY; // we use the same value here, no matter whether it's actual Transfer Mode or real live viewing
+@@ -1552,6 +1624,8 @@ bool cDevice::Ready(void)
+
+ bool cDevice::Receiving(bool Dummy) const
+ {
++ if (parentDevice)
++ return parentDevice->Receiving(Dummy);
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+ if (receiver[i])
+@@ -1632,10 +1706,13 @@ bool cDevice::GetTSPacket(uchar *&Data)
+
+ bool cDevice::AttachReceiver(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->AttachReceiver(Receiver);
+ if (!Receiver)
+ return false;
+ if (Receiver->device == this)
+ return true;
++ SetIdle(false);
+ // activate the following line if you need it - actually the driver should be fixed!
+ //#define WAIT_FOR_TUNER_LOCK
+ #ifdef WAIT_FOR_TUNER_LOCK
+@@ -1674,6 +1751,8 @@ bool cDevice::AttachReceiver(cReceiver *Receiver)
+
+ void cDevice::Detach(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Receiver);
+ if (!Receiver || Receiver->device != this)
+ return;
+ bool receiversLeft = false;
+@@ -1699,6 +1778,8 @@ void cDevice::Detach(cReceiver *Receiver)
+
+ void cDevice::DetachAll(int Pid)
+ {
++ if (parentDevice)
++ return parentDevice->DetachAll(Pid);
+ if (Pid) {
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+@@ -1783,3 +1864,25 @@ uchar *cTSBuffer::Get(void)
+ }
+ return NULL;
+ }
++
++// --- cDynamicDeviceProbe -------------------------------------------------------
++
++cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
++cList<cDynamicDeviceProbe::cDynamicDeviceProbeItem> cDynamicDeviceProbe::commandQueue;
++
++void cDynamicDeviceProbe::QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath)
++{
++ if (DevPath)
++ commandQueue.Add(new cDynamicDeviceProbeItem(Cmd, new cString(DevPath)));
++}
++
++cDynamicDeviceProbe::cDynamicDeviceProbe(void)
++{
++ DynamicDeviceProbes.Add(this);
++}
++
++cDynamicDeviceProbe::~cDynamicDeviceProbe()
++{
++ DynamicDeviceProbes.Del(this, false);
++}
+diff --git a/device.h b/device.h
+index fd010d4..ed3b1da 100644
+--- a/device.h
++++ b/device.h
+@@ -24,6 +24,7 @@
+ #include "spu.h"
+ #include "thread.h"
+ #include "tools.h"
++#include <linux/dvb/frontend.h>
+
+ #define MAXDEVICES 16 // the maximum number of devices in the system
+ #define MAXPIDHANDLES 64 // the maximum number of different PIDs per device
+@@ -169,7 +170,6 @@ private:
+ static int nextCardIndex;
+ int cardIndex;
+ protected:
+- cDevice(void);
+ virtual ~cDevice();
+ virtual bool Ready(void);
+ ///< Returns true if this device is ready. Devices with conditional
+@@ -196,9 +196,6 @@ protected:
+ ///< A derived class must call the MakePrimaryDevice() function of its
+ ///< base class.
+ public:
+- bool IsPrimaryDevice(void) const { return this == primaryDevice; }
+- int CardIndex(void) const { return cardIndex; }
+- ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
+ int DeviceNumber(void) const;
+ ///< Returns the number of this device (0 ... numDevices).
+ virtual cString DeviceType(void) const;
+@@ -338,6 +335,7 @@ public:
+ ///< Returns true if the device is currently showing any programme to
+ ///< the user, either through replaying or live.
+
++ virtual bool SendDiseqcCmd(dvb_diseqc_master_cmd cmd) {return false;}
+ // PID handle facilities
+
+ private:
+@@ -423,9 +421,6 @@ public:
+ ///< shall check whether the channel can be decrypted.
+ 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
+
+@@ -586,9 +581,6 @@ private:
+ cTsToPes tsToPesSubtitle;
+ bool isPlayingVideo;
+ protected:
+- const cPatPmtParser *PatPmtParser(void) const { return &patPmtParser; }
+- ///< Returns a pointer to the patPmtParser, so that a derived device
+- ///< can use the stream information from it.
+ virtual bool CanReplay(void) const;
+ ///< Returns true if this device can currently start a replay session.
+ virtual bool SetPlayMode(ePlayMode PlayMode);
+@@ -802,6 +794,38 @@ public:
+ ///< Detaches all receivers from this device for this pid.
+ virtual void DetachAllReceivers(void);
+ ///< Detaches all receivers from this device.
++
++// --- dynamite subdevice patch start ---
++ friend class cDynamicDevice;
++private:
++ static cDevice *nextParentDevice;
++ ///< Holds the parent device for the next subdevice
++ ///< so the dynamite-plugin can work with unpatched plugins
++ bool isIdle;
++protected:
++ cDevice *parentDevice;
++ cDevice *subDevice;
++ cDevice(cDevice *ParentDevice = NULL);
++ const cPatPmtParser *PatPmtParser(void) const { if (parentDevice) return parentDevice->PatPmtParser(); return &patPmtParser; }
++ ///< Returns a pointer to the patPmtParser, so that a derived device
++ ///< can use the stream information from it.
++public:
++ bool IsPrimaryDevice(void) const { if (parentDevice) return parentDevice->IsPrimaryDevice(); return this == primaryDevice; }
++ int CardIndex(void) const { if (parentDevice) return parentDevice->cardIndex; return cardIndex; }
++ ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
++ cCamSlot *CamSlot(void) const { if (parentDevice) return parentDevice->CamSlot(); return camSlot; }
++ ///< Returns the CAM slot that is currently used with this device,
++ ///< or NULL if no CAM slot is in use.
++ bool IsSubDevice(void) const { return (parentDevice != NULL); }
++ bool HasSubDevice(void) const { return (subDevice != NULL); }
++ cDevice *SubDevice(void) const { return subDevice; }
++ bool IsIdle(void) const { if (parentDevice) return parentDevice->IsIdle(); return isIdle; }
++ bool SetIdle(bool Idle);
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly) { return false; }
++ ///< Called by SetIdle
++ ///< if TestOnly, don't do anything, just return, if the device
++ ///< can be set to the new idle state
++ // --- dynamite subdevice patch end ---
+ };
+
+ /// Derived cDevice classes that can receive channels will have to provide
+@@ -812,7 +836,14 @@ public:
+ /// sure the returned data points to a TS packet and automatically
+ /// re-synchronizes after broken packets.
+
+-class cTSBuffer : public cThread {
++class cTSBufferBase {
++public:
++ cTSBufferBase() {}
++ virtual ~cTSBufferBase() {}
++ virtual uchar *Get(void) = 0;
++ };
++
++class cTSBuffer : public cTSBufferBase, public cThread {
+ private:
+ int f;
+ int cardIndex;
+@@ -821,8 +852,51 @@ private:
+ virtual void Action(void);
+ public:
+ cTSBuffer(int File, int Size, int CardIndex);
+- ~cTSBuffer();
+- uchar *Get(void);
++ virtual ~cTSBuffer();
++ virtual uchar *Get(void);
++ };
++
++/// A plugin that want to create devices handled by the dynamite-plugin needs to create
++/// a cDynamicDeviceProbe derived object on the heap in order to have its Probe()
++/// function called, where it can actually create the appropriate device.
++/// The cDynamicDeviceProbe object must be created in the plugin's constructor,
++/// and deleted in its destructor.
++/// The "DevPath" hasn't to be a physical device or a path in the filesystem.
++/// It can be any string a plugin may react on.
++
++#define __DYNAMIC_DEVICE_PROBE
++
++enum eDynamicDeviceProbeCommand { ddpcAttach, ddpcDetach, ddpcService };
++
++class cDynamicDeviceProbe : public cListObject {
++ friend class cDynamicDevice;
++private:
++ class cDynamicDeviceProbeItem : public cListObject {
++ public:
++ eDynamicDeviceProbeCommand cmd;
++ cString *devpath;
++ cDynamicDeviceProbeItem(eDynamicDeviceProbeCommand Cmd, cString *DevPath):cmd(Cmd),devpath(DevPath) {}
++ virtual ~cDynamicDeviceProbeItem() { if (devpath) delete devpath; }
++ };
++ static cList<cDynamicDeviceProbeItem> commandQueue;
++ ///< A list where all attach/detach commands are queued
++ ///< so they can be processed in the MainThreadHook of
++ ///< the dynamite plugin.
++public:
++ static void QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath);
++ ///< Plugins which support cDynamicDeviceProbe must use this function
++ ///< to queue the devices they normally create in their Initialize method.
++ ///< These devices are created as subdevices in the Start-method of the dynamite-plugin.
++ cDynamicDeviceProbe(void);
++ virtual ~cDynamicDeviceProbe();
++ virtual cDevice *Attach(cDevice *ParentDevice, const char *DevPath) = 0;
++ ///< Probes for a device at the given device-path like /dev/dvb/adapter0/frontend0
++ ///< or /dev/video0 etc. and creates the appropriate
++ ///< object derived from cDevice if applicable.
++ ///< Returns the device that has been created or NULL if not.
++ ///< The dynamite-plugin will delete the device if it is detached.
+ };
+
++extern cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
+ #endif //__DEVICE_H
+diff --git a/dvbci.c b/dvbci.c
+index 5289bbd..baa70bc 100644
+--- a/dvbci.c
++++ b/dvbci.c
+@@ -10,41 +10,70 @@
+ #include "dvbci.h"
+ #include <linux/dvb/ca.h>
+ #include <sys/ioctl.h>
+-#include "device.h"
++#include "dvbdevice.h"
+
+ // --- cDvbCiAdapter ---------------------------------------------------------
+
+-cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
++cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
+ {
+ 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());
++ adapter = Adapter;
++ frontend = Frontend;
++ idle = false;
++ GetNumCamSlots(Device, Fd, this);
++ Start();
+ }
+
+ cDvbCiAdapter::~cDvbCiAdapter()
+ {
+ Cancel(3);
++ if (device->IsSubDevice() || device->HasSubDevice())
++ CloseCa();
++}
++
++bool cDvbCiAdapter::OpenCa(void)
++{
++ if (fd >= 0)
++ return true;
++ fd = cDvbDevice::DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
++ return (fd >= 0);
++}
++
++void cDvbCiAdapter::CloseCa(void)
++{
++ if (fd < 0)
++ return;
++ close(fd);
++ fd = -1;
++}
++
++bool cDvbCiAdapter::SetIdle(bool Idle, bool TestOnly)
++{
++ if ((adapter < 0) || (frontend < 0))
++ return false;
++ if (TestOnly || (idle == Idle))
++ return true;
++ if (Idle)
++ CloseCa();
++ else
++ OpenCa();
++ idle = Idle;
++ return true;
++}
++
++cTSBufferBase *cDvbCiAdapter::GetTSBuffer(int FdDvr)
++{
++ if (device)
++ return new cTSBuffer(FdDvr, MEGABYTE(5), device->CardIndex() + 1);
++ return NULL;
+ }
+
+ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+ {
++ if (idle || (fd < 0))
++ return 0;
+ if (Buffer && MaxLength > 0) {
+ struct pollfd pfd[1];
+ pfd[0].fd = fd;
+@@ -61,6 +90,8 @@ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+
+ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+ {
++ if (idle || (fd < 0))
++ return;
+ 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());
+@@ -69,6 +100,8 @@ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+
+ bool cDvbCiAdapter::Reset(int Slot)
+ {
++ if (idle || (fd < 0))
++ return false;
+ if (ioctl(fd, CA_RESET, 1 << Slot) != -1)
+ return true;
+ else
+@@ -78,6 +111,8 @@ bool cDvbCiAdapter::Reset(int Slot)
+
+ eModuleStatus cDvbCiAdapter::ModuleStatus(int Slot)
+ {
++ if (idle || (fd < 0))
++ return msNone;
+ ca_slot_info_t sinfo;
+ sinfo.num = Slot;
+ if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) {
+@@ -99,10 +134,60 @@ bool cDvbCiAdapter::Assign(cDevice *Device, bool Query)
+ return true;
+ }
+
+-cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd)
++int cDvbCiAdapter::GetNumCamSlots(cDevice *Device, int Fd, cCiAdapter *CiAdapter)
+ {
+- // TODO check whether a CI is actually present?
+- if (Device)
+- return new cDvbCiAdapter(Device, Fd);
+- return NULL;
++ int NumSlots = -1;
++ if (Fd >= 0) {
++ ca_caps_t Caps;
++ if (ioctl(Fd, CA_GET_CAP, &Caps) == 0) {
++ if ((Caps.slot_type & CA_CI_LINK) != 0) {
++ NumSlots = Caps.slot_num;
++ if (NumSlots == 0)
++ esyslog("ERROR: no CAM slots found on device %d", Device->DeviceNumber());
++ else if (CiAdapter != NULL) {
++ for (int i = 0; i < NumSlots; i++)
++ new cCamSlot(CiAdapter);
++ }
++ else
++ return NumSlots;
++ }
++ 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());
++ }
++ return -1;
++}
++
++cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
++{
++ // don't create a ci-adapter if it's not useable
++ if (Device && (Fd >= 0) && (GetNumCamSlots(Device, Fd, NULL) > 0))
++ return new cDvbCiAdapter(Device, Fd, Adapter, Frontend);
++
++ if (Fd >= 0)
++ close(Fd);
++
++ // try to find an external ci-adapter
++ for (cDvbCiAdapterProbe *cp = DvbCiAdapterProbes.First(); cp; cp = DvbCiAdapterProbes.Next(cp)) {
++ cDvbCiAdapter *ca = cp->Probe(Device);
++ if (ca)
++ return ca;
++ }
++ return NULL;
++}
++
++// --- cDvbCiAdapterProbe -------------------------------------------------------
++
++cList<cDvbCiAdapterProbe> DvbCiAdapterProbes;
++
++cDvbCiAdapterProbe::cDvbCiAdapterProbe(void)
++{
++ DvbCiAdapterProbes.Add(this);
++}
++
++cDvbCiAdapterProbe::~cDvbCiAdapterProbe()
++{
++ DvbCiAdapterProbes.Del(this, false);
+ }
+diff --git a/dvbci.h b/dvbci.h
+index adbe40d..d908b2f 100644
+--- a/dvbci.h
++++ b/dvbci.h
+@@ -16,16 +16,48 @@ class cDvbCiAdapter : public cCiAdapter {
+ private:
+ cDevice *device;
+ int fd;
++ int adapter;
++ int frontend;
++ bool idle;
++
++ bool OpenCa(void);
++ void CloseCa(void);
+ 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);
++ cDvbCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
+ public:
+ virtual ~cDvbCiAdapter();
+- static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd);
++ virtual cTSBufferBase *GetTSBuffer(int FdDvr);
++ static int GetNumCamSlots(cDevice *Device, int Fd, cCiAdapter *CiAdapter);
++ ///< Tests if the CA device is usable for vdr.
++ ///< If CiAdapter is not NULL it will create the CamSlots for the given ci-adapter.
++ virtual bool SetIdle(bool Idle, bool TestOnly);
++ virtual bool IsIdle(void) const { return idle; }
++ static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
++ };
++
++// A plugin that implements an external DVB ci-adapter derived from cDvbCiAdapter needs to create
++// a cDvbCiAdapterProbe derived object on the heap in order to have its Probe()
++// function called, where it can actually create the appropriate ci-adapter.
++// The cDvbCiAdapterProbe object must be created in the plugin's constructor,
++// and deleted in its destructor.
++// Every plugin has to track its own list of already used device nodes.
++// The Probes are always called if the base cDvbCiAdapter can't create a ci-adapter on its own.
++
++class cDvbCiAdapterProbe : public cListObject {
++public:
++ cDvbCiAdapterProbe(void);
++ virtual ~cDvbCiAdapterProbe();
++ virtual cDvbCiAdapter *Probe(cDevice *Device) = 0;
++ ///< Probes for a DVB ci-adapter for the given Device and creates the appropriate
++ ///< object derived from cDvbCiAdapter if applicable.
++ ///< Returns NULL if no adapter has been created.
+ };
+
++extern cList<cDvbCiAdapterProbe> DvbCiAdapterProbes;
++
+ #endif //__DVBCI_H
+diff --git a/dvbdevice.c b/dvbdevice.c
+index ab6149f..351f1ed 100644
+--- a/dvbdevice.c
++++ b/dvbdevice.c
+@@ -288,9 +288,10 @@ class cDvbTuner : public cThread {
+ private:
+ static cMutex bondMutex;
+ enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked };
++ bool SendDiseqc;
+ int frontendType;
+ const cDvbDevice *device;
+- int fd_frontend;
++ mutable int fd_frontend;
+ int adapter, frontend;
+ uint32_t subsystemId;
+ int tuneTimeout;
+@@ -301,7 +302,7 @@ private:
+ const cScr *scr;
+ bool lnbPowerTurnedOn;
+ eTunerStatus tunerStatus;
+- cMutex mutex;
++ mutable cMutex mutex;
+ cCondVar locked;
+ cCondVar newSet;
+ cDvbTuner *bondedTuner;
+@@ -311,11 +312,16 @@ private:
+ cDvbTuner *GetBondedMaster(void);
+ bool IsBondedMaster(void) const { return !bondedTuner || bondedMaster; }
+ void ClearEventQueue(void) const;
++ dvb_diseqc_master_cmd diseqc_cmd;
+ bool GetFrontendStatus(fe_status_t &Status) const;
+ void ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency);
+ void ResetToneAndVoltage(void);
+ bool SetFrontend(void);
+ virtual void Action(void);
++
++ mutable bool isIdle;
++ bool OpenFrontend(void) const;
++ bool CloseFrontend(void);
+ public:
+ cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend);
+ virtual ~cDvbTuner();
+@@ -327,9 +333,13 @@ public:
+ uint32_t SubsystemId(void) const { return subsystemId; }
+ bool IsTunedTo(const cChannel *Channel) const;
+ void SetChannel(const cChannel *Channel);
++ bool SendDiseqcCmd(dvb_diseqc_master_cmd cmd);
+ bool Locked(int TimeoutMs = 0);
+ int GetSignalStrength(void) const;
+ int GetSignalQuality(void) const;
++
++ bool SetIdle(bool Idle);
++ bool IsIdle(void) const { return isIdle; }
+ };
+
+ cMutex cDvbTuner::bondMutex;
+@@ -339,6 +349,7 @@ cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int
+ frontendType = SYS_UNDEFINED;
+ device = Device;
+ fd_frontend = Fd_Frontend;
++ SendDiseqc=false;
+ adapter = Adapter;
+ frontend = Frontend;
+ subsystemId = cDvbDeviceProbe::GetSubsystemId(adapter, frontend);
+@@ -351,6 +362,7 @@ cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int
+ tunerStatus = tsIdle;
+ bondedTuner = NULL;
+ bondedMaster = false;
++ isIdle = false;
+ SetDescription("tuner on frontend %d/%d", adapter, frontend);
+ Start();
+ }
+@@ -368,6 +380,8 @@ cDvbTuner::~cDvbTuner()
+ ExecuteDiseqc(lastDiseqc, &Frequency);
+ }
+ */
++ if (device && device->IsSubDevice())
++ CloseFrontend();
+ }
+
+ bool cDvbTuner::Bond(cDvbTuner *Tuner)
+@@ -512,6 +526,8 @@ bool cDvbTuner::Locked(int TimeoutMs)
+
+ void cDvbTuner::ClearEventQueue(void) const
+ {
++ if (!OpenFrontend())
++ return;
+ cPoller Poller(fd_frontend);
+ if (Poller.Poll(TUNER_POLL_TIMEOUT)) {
+ dvb_frontend_event Event;
+@@ -720,8 +736,28 @@ static int GetRequiredDeliverySystem(const cChannel *Channel, const cDvbTranspon
+ return ds;
+ }
+
++bool cDvbTuner::SendDiseqcCmd(dvb_diseqc_master_cmd cmd)
++{
++ cMutexLock MutexLock(&mutex);
++ cDvbTransponderParameters dtp(channel.Parameters());
++
++ // Determine the required frontend type:
++ int frontendType = GetRequiredDeliverySystem(&channel, &dtp);
++
++ if ((frontendType!=SYS_DVBS2 && frontendType!=SYS_DVBS) || SendDiseqc)
++ return false;
++ if (!OpenFrontend())
++ return false;
++ diseqc_cmd=cmd;
++ SendDiseqc=true;
++ newSet.Broadcast();
++ return true;
++}
++
+ bool cDvbTuner::SetFrontend(void)
+ {
++ if (!OpenFrontend())
++ return false;
+ #define MAXFRONTENDCMDS 16
+ #define SETCMD(c, d) { Frontend[CmdSeq.num].cmd = (c);\
+ Frontend[CmdSeq.num].u.data = (d);\
+@@ -868,10 +904,16 @@ void cDvbTuner::Action(void)
+ bool LostLock = false;
+ fe_status_t Status = (fe_status_t)0;
+ while (Running()) {
+- fe_status_t NewStatus;
+- if (GetFrontendStatus(NewStatus))
+- Status = NewStatus;
++ if (!isIdle) {
++ fe_status_t NewStatus;
++ if (GetFrontendStatus(NewStatus))
++ Status = NewStatus;
++ }
+ cMutexLock MutexLock(&mutex);
++ if (SendDiseqc) {
++ CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &diseqc_cmd));
++ SendDiseqc=false;
++ }
+ int WaitTime = 1000;
+ switch (tunerStatus) {
+ case tsIdle:
+@@ -923,6 +965,40 @@ void cDvbTuner::Action(void)
+ }
+ }
+
++bool cDvbTuner::SetIdle(bool Idle)
++{
++ if (isIdle == Idle)
++ return true;
++ isIdle = Idle;
++ if (Idle)
++ return CloseFrontend();
++ return OpenFrontend();
++}
++
++bool cDvbTuner::OpenFrontend(void) const
++{
++ if (fd_frontend >= 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ fd_frontend = cDvbDevice::DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK);
++ if (fd_frontend < 0)
++ return false;
++ isIdle = false;
++ return true;
++}
++
++bool cDvbTuner::CloseFrontend(void)
++{
++ if (fd_frontend < 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ tunerStatus = tsIdle;
++ newSet.Broadcast();
++ close(fd_frontend);
++ fd_frontend = -1;
++ return true;
++}
++
+ // --- cDvbSourceParam -------------------------------------------------------
+
+ class cDvbSourceParam : public cSourceParam {
+@@ -1008,7 +1084,8 @@ const char *DeliverySystemNames[] = {
+ NULL
+ };
+
+-cDvbDevice::cDvbDevice(int Adapter, int Frontend)
++cDvbDevice::cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice)
++:cDevice(ParentDevice)
+ {
+ adapter = Adapter;
+ frontend = Frontend;
+@@ -1026,9 +1103,8 @@ cDvbDevice::cDvbDevice(int Adapter, int Frontend)
+
+ // Common Interface:
+
+- fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
+- if (fd_ca >= 0)
+- ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca);
++ int fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
++ ciAdapter = cDvbCiAdapter::CreateCiAdapter(parentDevice ? parentDevice : this, fd_ca, adapter, frontend);
+
+ // The DVR device (will be opened and closed as needed):
+
+@@ -1256,7 +1332,11 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ if (d >= 0) {
+ int ErrorDevice = 0;
+ if (cDevice *Device1 = cDevice::GetDevice(i)) {
++ if (Device1->HasSubDevice())
++ Device1 = Device1->SubDevice();
+ if (cDevice *Device2 = cDevice::GetDevice(d)) {
++ if (Device2->HasSubDevice())
++ Device2 = Device2->SubDevice();
+ if (cDvbDevice *DvbDevice1 = dynamic_cast<cDvbDevice *>(Device1)) {
+ if (cDvbDevice *DvbDevice2 = dynamic_cast<cDvbDevice *>(Device2)) {
+ if (!DvbDevice1->Bond(DvbDevice2))
+@@ -1290,7 +1370,10 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ void cDvbDevice::UnBondDevices(void)
+ {
+ for (int i = 0; i < cDevice::NumDevices(); i++) {
+- if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(cDevice::GetDevice(i)))
++ cDevice *dev = cDevice::GetDevice(i);
++ if (dev && dev->HasSubDevice())
++ dev = dev->SubDevice();
++ if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(dev))
+ d->UnBond();
+ }
+ }
+@@ -1344,6 +1427,26 @@ bool cDvbDevice::BondingOk(const cChannel *Channel, bool ConsiderOccupied) const
+ return true;
+ }
+
++bool cDvbDevice::SetIdleDevice(bool Idle, bool TestOnly)
++{
++ if (TestOnly) {
++ if (ciAdapter)
++ return ciAdapter->SetIdle(Idle, true);
++ return true;
++ }
++ if (!dvbTuner->SetIdle(Idle))
++ return false;
++ if (ciAdapter && !ciAdapter->SetIdle(Idle, false)) {
++ dvbTuner->SetIdle(!Idle);
++ return false;
++ }
++ if (Idle)
++ StopSectionHandler();
++ else
++ StartSectionHandler();
++ return true;
++}
++
+ bool cDvbDevice::HasCi(void)
+ {
+ return ciAdapter;
+@@ -1513,7 +1616,7 @@ bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne
+
+ bool cDvbDevice::ProvidesEIT(void) const
+ {
+- return dvbTuner != NULL;
++ return !IsIdle() && (dvbTuner != NULL) && !dvbTuner->IsIdle() && ((ciAdapter == NULL) || !ciAdapter->IsIdle());
+ }
+
+ int cDvbDevice::NumProvidedSystems(void) const
+@@ -1558,6 +1661,11 @@ bool cDvbDevice::HasLock(int TimeoutMs) const
+ return dvbTuner ? dvbTuner->Locked(TimeoutMs) : false;
+ }
+
++bool cDvbDevice::SendDiseqcCmd(dvb_diseqc_master_cmd cmd)
++{
++ return dvbTuner->SendDiseqcCmd(cmd);
++}
++
+ void cDvbDevice::SetTransferModeForDolbyDigital(int Mode)
+ {
+ setTransferModeForDolbyDigital = Mode;
+@@ -1567,8 +1675,12 @@ bool cDvbDevice::OpenDvr(void)
+ {
+ CloseDvr();
+ fd_dvr = DvbOpen(DEV_DVB_DVR, adapter, frontend, O_RDONLY | O_NONBLOCK, true);
+- if (fd_dvr >= 0)
+- tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(5), CardIndex() + 1);
++ if (fd_dvr >= 0) {
++ if (ciAdapter)
++ tsBuffer = ciAdapter->GetTSBuffer(fd_dvr);
++ if (tsBuffer == NULL)
++ tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(5), CardIndex() + 1);
++ }
+ return fd_dvr >= 0;
+ }
+
+diff --git a/dvbdevice.h b/dvbdevice.h
+index b4d07f5..339c52b 100644
+--- a/dvbdevice.h
++++ b/dvbdevice.h
+@@ -107,7 +107,7 @@ class cDvbTuner;
+ /// The cDvbDevice implements a DVB device which can be accessed through the Linux DVB driver API.
+
+ class cDvbDevice : public cDevice {
+-protected:
++public:
+ static cString DvbName(const char *Name, int Adapter, int Frontend);
+ static int DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError = false);
+ private:
+@@ -127,19 +127,20 @@ private:
+ int deliverySystems[MAXDELIVERYSYSTEMS];
+ int numDeliverySystems;
+ int numModulations;
+- int fd_dvr, fd_ca;
++ int fd_dvr;
+ static cMutex bondMutex;
+ cDvbDevice *bondedDevice;
+ mutable bool needsDetachBondedReceivers;
+ bool QueryDeliverySystems(int fd_frontend);
+ public:
+- cDvbDevice(int Adapter, int Frontend);
++ cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice = NULL);
+ virtual ~cDvbDevice();
+ int Adapter(void) const { return adapter; }
+ int Frontend(void) const { return frontend; }
+ virtual bool Ready(void);
+ virtual cString DeviceType(void) const;
+ virtual cString DeviceName(void) const;
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly);
+ static bool BondDevices(const char *Bondings);
+ ///< Bonds the devices as defined in the given Bondings string.
+ ///< A bonding is a sequence of device numbers (starting at 1),
+@@ -193,6 +194,7 @@ protected:
+ virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
+ public:
+ virtual bool HasLock(int TimeoutMs = 0) const;
++ virtual bool SendDiseqcCmd(dvb_diseqc_master_cmd cmd);
+
+ // PID handle facilities
+
+@@ -225,7 +227,7 @@ public:
+ // Receiver facilities
+
+ private:
+- cTSBuffer *tsBuffer;
++ cTSBufferBase *tsBuffer;
+ protected:
+ virtual bool OpenDvr(void);
+ virtual void CloseDvr(void);
diff --git a/patches/vdr-1.7.42-dynamite+externalci.patch b/patches/vdr-1.7.42-dynamite+externalci.patch
new file mode 100644
index 0000000..dd421af
--- /dev/null
+++ b/patches/vdr-1.7.42-dynamite+externalci.patch
@@ -0,0 +1,988 @@
+diff --git a/ci.c b/ci.c
+index 9a4a829..88b50e7 100644
+--- a/ci.c
++++ b/ci.c
+@@ -1571,6 +1571,8 @@ cCamSlot::cCamSlot(cCiAdapter *CiAdapter)
+
+ cCamSlot::~cCamSlot()
+ {
++ if (ciAdapter && ciAdapter->assignedDevice)
++ ciAdapter->assignedDevice->SetCamSlot(NULL);
+ CamSlots.Del(this, false);
+ DeleteAllConnections();
+ }
+diff --git a/ci.h b/ci.h
+index 74e0270..818ea29 100644
+--- a/ci.h
++++ b/ci.h
+@@ -72,6 +72,7 @@ public:
+ };
+
+ class cDevice;
++class cTSBufferBase;
+ class cCamSlot;
+
+ enum eModuleStatus { msNone, msReset, msPresent, msReady };
+@@ -115,6 +116,13 @@ public:
+ ///< 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.
++#define EXTERNALCI_PATCH
++ virtual cTSBufferBase *GetTSBuffer(int FdDvr) { return NULL; }
++ ///< Derived classes can return a special TS buffer with features
++ ///< like rerouting the stream through an external ci.
++ ///< The caller must delete the buffer.
++ virtual bool SetIdle(bool Idle, bool TestOnly) { return false; }
++ virtual bool IsIdle(void) const { return false; }
+ };
+
+ class cTPDU;
+diff --git a/device.c b/device.c
+index ff51a11..dfdc081 100644
+--- a/device.c
++++ b/device.c
+@@ -69,12 +69,22 @@ int cDevice::currentChannel = 1;
+ cDevice *cDevice::device[MAXDEVICES] = { NULL };
+ cDevice *cDevice::primaryDevice = NULL;
+ cList<cDeviceHook> cDevice::deviceHooks;
+-
+-cDevice::cDevice(void)
++cDevice *cDevice::nextParentDevice = NULL;
++
++cDevice::cDevice(cDevice *ParentDevice)
+ :patPmtParser(true)
+-{
+- cardIndex = nextCardIndex++;
+- dsyslog("new device number %d", CardIndex() + 1);
++,isIdle(false)
++,parentDevice(ParentDevice)
++,subDevice(NULL)
++{
++ if (!ParentDevice)
++ parentDevice = nextParentDevice;
++ cDevice::nextParentDevice = NULL;
++ if (parentDevice)
++ cardIndex = parentDevice->cardIndex;
++ else
++ cardIndex = nextCardIndex++;
++ dsyslog("new %sdevice number %d", parentDevice ? "sub-" : "", CardIndex() + 1);
+
+ SetDescription("receiver on device %d", CardIndex() + 1);
+
+@@ -106,10 +116,14 @@ cDevice::cDevice(void)
+ for (int i = 0; i < MAXRECEIVERS; i++)
+ receiver[i] = NULL;
+
+- if (numDevices < MAXDEVICES)
+- device[numDevices++] = this;
++ if (!parentDevice) {
++ if (numDevices < MAXDEVICES)
++ device[numDevices++] = this;
++ else
++ esyslog("ERROR: too many devices or \"dynamite\"-unpatched device creator!");
++ }
+ else
+- esyslog("ERROR: too many devices!");
++ parentDevice->subDevice = this;
+ }
+
+ cDevice::~cDevice()
+@@ -118,6 +132,29 @@ cDevice::~cDevice()
+ DetachAllReceivers();
+ delete liveSubtitle;
+ delete dvbSubtitleConverter;
++ if (parentDevice && (parentDevice->subDevice == this))
++ parentDevice->subDevice = NULL;
++}
++
++bool cDevice::SetIdle(bool Idle)
++{
++ if (parentDevice)
++ return parentDevice->SetIdle(Idle);
++ if (isIdle == Idle)
++ return true;
++ if (Receiving(false))
++ return false;
++ if (Idle) {
++ Detach(player);
++ DetachAllReceivers();
++ }
++ if (!SetIdleDevice(Idle, true))
++ return false;
++ isIdle = Idle;
++ if (SetIdleDevice(Idle, false))
++ return true;
++ isIdle = !Idle;
++ return false;
+ }
+
+ bool cDevice::WaitForAllDevicesReady(int Timeout)
+@@ -156,6 +193,8 @@ int cDevice::NextCardIndex(int n)
+
+ int cDevice::DeviceNumber(void) const
+ {
++ if (parentDevice)
++ return parentDevice->DeviceNumber();
+ for (int i = 0; i < numDevices; i++) {
+ if (device[i] == this)
+ return i;
+@@ -356,6 +395,8 @@ bool cDevice::HasCi(void)
+
+ void cDevice::SetCamSlot(cCamSlot *CamSlot)
+ {
++ if (parentDevice)
++ return parentDevice->SetCamSlot(CamSlot);
+ camSlot = CamSlot;
+ }
+
+@@ -568,6 +609,10 @@ void cDevice::DelLivePids(void)
+
+ void cDevice::StartSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StartSectionHandler();
++ return;
++ }
+ if (!sectionHandler) {
+ sectionHandler = new cSectionHandler(this);
+ AttachFilter(eitFilter = new cEitFilter);
+@@ -579,6 +624,10 @@ void cDevice::StartSectionHandler(void)
+
+ void cDevice::StopSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StopSectionHandler();
++ return;
++ }
+ if (sectionHandler) {
+ delete nitFilter;
+ delete sdtFilter;
+@@ -610,12 +659,17 @@ void cDevice::CloseFilter(int Handle)
+
+ void cDevice::AttachFilter(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->AttachFilter(Filter);
++ SetIdle(false);
+ if (sectionHandler)
+ sectionHandler->Attach(Filter);
+ }
+
+ void cDevice::Detach(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Filter);
+ if (sectionHandler)
+ sectionHandler->Detach(Filter);
+ }
+@@ -777,6 +831,7 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
+ sectionHandler->SetStatus(false);
+ sectionHandler->SetChannel(NULL);
+ }
++ SetIdle(false);
+ // Tell the camSlot about the channel switch and add all PIDs of this
+ // channel to it, for possible later decryption:
+ if (camSlot)
+@@ -823,19 +878,27 @@ void cDevice::ForceTransferMode(void)
+ {
+ if (!cTransferControl::ReceiverDevice()) {
+ cChannel *Channel = Channels.GetByNumber(CurrentChannel());
+- if (Channel)
++ if (Channel) {
++ SetIdle(false);
+ SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
++ }
+ }
+ }
+
+ int cDevice::Occupied(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Occupied();
+ int Seconds = occupiedTimeout - time(NULL);
+ return Seconds > 0 ? Seconds : 0;
+ }
+
+ void cDevice::SetOccupied(int Seconds)
+ {
++ if (parentDevice) {
++ parentDevice->SetOccupied(Seconds);
++ return;
++ }
+ if (Seconds >= 0)
+ occupiedTimeout = time(NULL) + min(Seconds, MAXOCCUPIEDTIMEOUT);
+ }
+@@ -1216,7 +1279,10 @@ bool cDevice::Transferring(void) const
+
+ bool cDevice::AttachPlayer(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->AttachPlayer(Player);
+ if (CanReplay()) {
++ SetIdle(false);
+ if (player)
+ Detach(player);
+ DELETENULL(liveSubtitle);
+@@ -1235,6 +1301,8 @@ bool cDevice::AttachPlayer(cPlayer *Player)
+
+ void cDevice::Detach(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Player);
+ if (Player && player == Player) {
+ cPlayer *p = player;
+ player = NULL; // avoids recursive calls to Detach()
+@@ -1254,6 +1322,8 @@ void cDevice::Detach(cPlayer *Player)
+
+ void cDevice::StopReplay(void)
+ {
++ if (parentDevice)
++ return parentDevice->StopReplay();
+ if (player) {
+ Detach(player);
+ if (IsPrimaryDevice())
+@@ -1534,6 +1604,8 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
+
+ int cDevice::Priority(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Priority();
+ int priority = IDLEPRIORITY;
+ if (IsPrimaryDevice() && !Replaying() && HasProgramme())
+ priority = TRANSFERPRIORITY; // we use the same value here, no matter whether it's actual Transfer Mode or real live viewing
+@@ -1552,6 +1624,8 @@ bool cDevice::Ready(void)
+
+ bool cDevice::Receiving(bool Dummy) const
+ {
++ if (parentDevice)
++ return parentDevice->Receiving(Dummy);
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+ if (receiver[i])
+@@ -1632,10 +1706,13 @@ bool cDevice::GetTSPacket(uchar *&Data)
+
+ bool cDevice::AttachReceiver(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->AttachReceiver(Receiver);
+ if (!Receiver)
+ return false;
+ if (Receiver->device == this)
+ return true;
++ SetIdle(false);
+ // activate the following line if you need it - actually the driver should be fixed!
+ //#define WAIT_FOR_TUNER_LOCK
+ #ifdef WAIT_FOR_TUNER_LOCK
+@@ -1674,6 +1751,8 @@ bool cDevice::AttachReceiver(cReceiver *Receiver)
+
+ void cDevice::Detach(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Receiver);
+ if (!Receiver || Receiver->device != this)
+ return;
+ bool receiversLeft = false;
+@@ -1699,6 +1778,8 @@ void cDevice::Detach(cReceiver *Receiver)
+
+ void cDevice::DetachAll(int Pid)
+ {
++ if (parentDevice)
++ return parentDevice->DetachAll(Pid);
+ if (Pid) {
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+@@ -1783,3 +1864,25 @@ uchar *cTSBuffer::Get(void)
+ }
+ return NULL;
+ }
++
++// --- cDynamicDeviceProbe -------------------------------------------------------
++
++cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
++cList<cDynamicDeviceProbe::cDynamicDeviceProbeItem> cDynamicDeviceProbe::commandQueue;
++
++void cDynamicDeviceProbe::QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath)
++{
++ if (DevPath)
++ commandQueue.Add(new cDynamicDeviceProbeItem(Cmd, new cString(DevPath)));
++}
++
++cDynamicDeviceProbe::cDynamicDeviceProbe(void)
++{
++ DynamicDeviceProbes.Add(this);
++}
++
++cDynamicDeviceProbe::~cDynamicDeviceProbe()
++{
++ DynamicDeviceProbes.Del(this, false);
++}
+diff --git a/device.h b/device.h
+index fd010d4..95acccd 100644
+--- a/device.h
++++ b/device.h
+@@ -169,7 +169,6 @@ private:
+ static int nextCardIndex;
+ int cardIndex;
+ protected:
+- cDevice(void);
+ virtual ~cDevice();
+ virtual bool Ready(void);
+ ///< Returns true if this device is ready. Devices with conditional
+@@ -196,9 +195,6 @@ protected:
+ ///< A derived class must call the MakePrimaryDevice() function of its
+ ///< base class.
+ public:
+- bool IsPrimaryDevice(void) const { return this == primaryDevice; }
+- int CardIndex(void) const { return cardIndex; }
+- ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
+ int DeviceNumber(void) const;
+ ///< Returns the number of this device (0 ... numDevices).
+ virtual cString DeviceType(void) const;
+@@ -423,9 +419,6 @@ public:
+ ///< shall check whether the channel can be decrypted.
+ 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
+
+@@ -586,9 +579,6 @@ private:
+ cTsToPes tsToPesSubtitle;
+ bool isPlayingVideo;
+ protected:
+- const cPatPmtParser *PatPmtParser(void) const { return &patPmtParser; }
+- ///< Returns a pointer to the patPmtParser, so that a derived device
+- ///< can use the stream information from it.
+ virtual bool CanReplay(void) const;
+ ///< Returns true if this device can currently start a replay session.
+ virtual bool SetPlayMode(ePlayMode PlayMode);
+@@ -802,6 +792,38 @@ public:
+ ///< Detaches all receivers from this device for this pid.
+ virtual void DetachAllReceivers(void);
+ ///< Detaches all receivers from this device.
++
++// --- dynamite subdevice patch start ---
++ friend class cDynamicDevice;
++private:
++ static cDevice *nextParentDevice;
++ ///< Holds the parent device for the next subdevice
++ ///< so the dynamite-plugin can work with unpatched plugins
++ bool isIdle;
++protected:
++ cDevice *parentDevice;
++ cDevice *subDevice;
++ cDevice(cDevice *ParentDevice = NULL);
++ const cPatPmtParser *PatPmtParser(void) const { if (parentDevice) return parentDevice->PatPmtParser(); return &patPmtParser; }
++ ///< Returns a pointer to the patPmtParser, so that a derived device
++ ///< can use the stream information from it.
++public:
++ bool IsPrimaryDevice(void) const { if (parentDevice) return parentDevice->IsPrimaryDevice(); return this == primaryDevice; }
++ int CardIndex(void) const { if (parentDevice) return parentDevice->cardIndex; return cardIndex; }
++ ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
++ cCamSlot *CamSlot(void) const { if (parentDevice) return parentDevice->CamSlot(); return camSlot; }
++ ///< Returns the CAM slot that is currently used with this device,
++ ///< or NULL if no CAM slot is in use.
++ bool IsSubDevice(void) const { return (parentDevice != NULL); }
++ bool HasSubDevice(void) const { return (subDevice != NULL); }
++ cDevice *SubDevice(void) const { return subDevice; }
++ bool IsIdle(void) const { if (parentDevice) return parentDevice->IsIdle(); return isIdle; }
++ bool SetIdle(bool Idle);
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly) { return false; }
++ ///< Called by SetIdle
++ ///< if TestOnly, don't do anything, just return, if the device
++ ///< can be set to the new idle state
++ // --- dynamite subdevice patch end ---
+ };
+
+ /// Derived cDevice classes that can receive channels will have to provide
+@@ -812,7 +834,14 @@ public:
+ /// sure the returned data points to a TS packet and automatically
+ /// re-synchronizes after broken packets.
+
+-class cTSBuffer : public cThread {
++class cTSBufferBase {
++public:
++ cTSBufferBase() {}
++ virtual ~cTSBufferBase() {}
++ virtual uchar *Get(void) = 0;
++ };
++
++class cTSBuffer : public cTSBufferBase, public cThread {
+ private:
+ int f;
+ int cardIndex;
+@@ -821,8 +850,51 @@ private:
+ virtual void Action(void);
+ public:
+ cTSBuffer(int File, int Size, int CardIndex);
+- ~cTSBuffer();
+- uchar *Get(void);
++ virtual ~cTSBuffer();
++ virtual uchar *Get(void);
++ };
++
++/// A plugin that want to create devices handled by the dynamite-plugin needs to create
++/// a cDynamicDeviceProbe derived object on the heap in order to have its Probe()
++/// function called, where it can actually create the appropriate device.
++/// The cDynamicDeviceProbe object must be created in the plugin's constructor,
++/// and deleted in its destructor.
++/// The "DevPath" hasn't to be a physical device or a path in the filesystem.
++/// It can be any string a plugin may react on.
++
++#define __DYNAMIC_DEVICE_PROBE
++
++enum eDynamicDeviceProbeCommand { ddpcAttach, ddpcDetach, ddpcService };
++
++class cDynamicDeviceProbe : public cListObject {
++ friend class cDynamicDevice;
++private:
++ class cDynamicDeviceProbeItem : public cListObject {
++ public:
++ eDynamicDeviceProbeCommand cmd;
++ cString *devpath;
++ cDynamicDeviceProbeItem(eDynamicDeviceProbeCommand Cmd, cString *DevPath):cmd(Cmd),devpath(DevPath) {}
++ virtual ~cDynamicDeviceProbeItem() { if (devpath) delete devpath; }
++ };
++ static cList<cDynamicDeviceProbeItem> commandQueue;
++ ///< A list where all attach/detach commands are queued
++ ///< so they can be processed in the MainThreadHook of
++ ///< the dynamite plugin.
++public:
++ static void QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath);
++ ///< Plugins which support cDynamicDeviceProbe must use this function
++ ///< to queue the devices they normally create in their Initialize method.
++ ///< These devices are created as subdevices in the Start-method of the dynamite-plugin.
++ cDynamicDeviceProbe(void);
++ virtual ~cDynamicDeviceProbe();
++ virtual cDevice *Attach(cDevice *ParentDevice, const char *DevPath) = 0;
++ ///< Probes for a device at the given device-path like /dev/dvb/adapter0/frontend0
++ ///< or /dev/video0 etc. and creates the appropriate
++ ///< object derived from cDevice if applicable.
++ ///< Returns the device that has been created or NULL if not.
++ ///< The dynamite-plugin will delete the device if it is detached.
+ };
+
++extern cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
+ #endif //__DEVICE_H
+diff --git a/dvbci.c b/dvbci.c
+index 5289bbd..baa70bc 100644
+--- a/dvbci.c
++++ b/dvbci.c
+@@ -10,41 +10,70 @@
+ #include "dvbci.h"
+ #include <linux/dvb/ca.h>
+ #include <sys/ioctl.h>
+-#include "device.h"
++#include "dvbdevice.h"
+
+ // --- cDvbCiAdapter ---------------------------------------------------------
+
+-cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
++cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
+ {
+ 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());
++ adapter = Adapter;
++ frontend = Frontend;
++ idle = false;
++ GetNumCamSlots(Device, Fd, this);
++ Start();
+ }
+
+ cDvbCiAdapter::~cDvbCiAdapter()
+ {
+ Cancel(3);
++ if (device->IsSubDevice() || device->HasSubDevice())
++ CloseCa();
++}
++
++bool cDvbCiAdapter::OpenCa(void)
++{
++ if (fd >= 0)
++ return true;
++ fd = cDvbDevice::DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
++ return (fd >= 0);
++}
++
++void cDvbCiAdapter::CloseCa(void)
++{
++ if (fd < 0)
++ return;
++ close(fd);
++ fd = -1;
++}
++
++bool cDvbCiAdapter::SetIdle(bool Idle, bool TestOnly)
++{
++ if ((adapter < 0) || (frontend < 0))
++ return false;
++ if (TestOnly || (idle == Idle))
++ return true;
++ if (Idle)
++ CloseCa();
++ else
++ OpenCa();
++ idle = Idle;
++ return true;
++}
++
++cTSBufferBase *cDvbCiAdapter::GetTSBuffer(int FdDvr)
++{
++ if (device)
++ return new cTSBuffer(FdDvr, MEGABYTE(5), device->CardIndex() + 1);
++ return NULL;
+ }
+
+ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+ {
++ if (idle || (fd < 0))
++ return 0;
+ if (Buffer && MaxLength > 0) {
+ struct pollfd pfd[1];
+ pfd[0].fd = fd;
+@@ -61,6 +90,8 @@ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+
+ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+ {
++ if (idle || (fd < 0))
++ return;
+ 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());
+@@ -69,6 +100,8 @@ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+
+ bool cDvbCiAdapter::Reset(int Slot)
+ {
++ if (idle || (fd < 0))
++ return false;
+ if (ioctl(fd, CA_RESET, 1 << Slot) != -1)
+ return true;
+ else
+@@ -78,6 +111,8 @@ bool cDvbCiAdapter::Reset(int Slot)
+
+ eModuleStatus cDvbCiAdapter::ModuleStatus(int Slot)
+ {
++ if (idle || (fd < 0))
++ return msNone;
+ ca_slot_info_t sinfo;
+ sinfo.num = Slot;
+ if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) {
+@@ -99,10 +134,60 @@ bool cDvbCiAdapter::Assign(cDevice *Device, bool Query)
+ return true;
+ }
+
+-cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd)
++int cDvbCiAdapter::GetNumCamSlots(cDevice *Device, int Fd, cCiAdapter *CiAdapter)
+ {
+- // TODO check whether a CI is actually present?
+- if (Device)
+- return new cDvbCiAdapter(Device, Fd);
+- return NULL;
++ int NumSlots = -1;
++ if (Fd >= 0) {
++ ca_caps_t Caps;
++ if (ioctl(Fd, CA_GET_CAP, &Caps) == 0) {
++ if ((Caps.slot_type & CA_CI_LINK) != 0) {
++ NumSlots = Caps.slot_num;
++ if (NumSlots == 0)
++ esyslog("ERROR: no CAM slots found on device %d", Device->DeviceNumber());
++ else if (CiAdapter != NULL) {
++ for (int i = 0; i < NumSlots; i++)
++ new cCamSlot(CiAdapter);
++ }
++ else
++ return NumSlots;
++ }
++ 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());
++ }
++ return -1;
++}
++
++cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
++{
++ // don't create a ci-adapter if it's not useable
++ if (Device && (Fd >= 0) && (GetNumCamSlots(Device, Fd, NULL) > 0))
++ return new cDvbCiAdapter(Device, Fd, Adapter, Frontend);
++
++ if (Fd >= 0)
++ close(Fd);
++
++ // try to find an external ci-adapter
++ for (cDvbCiAdapterProbe *cp = DvbCiAdapterProbes.First(); cp; cp = DvbCiAdapterProbes.Next(cp)) {
++ cDvbCiAdapter *ca = cp->Probe(Device);
++ if (ca)
++ return ca;
++ }
++ return NULL;
++}
++
++// --- cDvbCiAdapterProbe -------------------------------------------------------
++
++cList<cDvbCiAdapterProbe> DvbCiAdapterProbes;
++
++cDvbCiAdapterProbe::cDvbCiAdapterProbe(void)
++{
++ DvbCiAdapterProbes.Add(this);
++}
++
++cDvbCiAdapterProbe::~cDvbCiAdapterProbe()
++{
++ DvbCiAdapterProbes.Del(this, false);
+ }
+diff --git a/dvbci.h b/dvbci.h
+index adbe40d..d908b2f 100644
+--- a/dvbci.h
++++ b/dvbci.h
+@@ -16,16 +16,48 @@ class cDvbCiAdapter : public cCiAdapter {
+ private:
+ cDevice *device;
+ int fd;
++ int adapter;
++ int frontend;
++ bool idle;
++
++ bool OpenCa(void);
++ void CloseCa(void);
+ 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);
++ cDvbCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
+ public:
+ virtual ~cDvbCiAdapter();
+- static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd);
++ virtual cTSBufferBase *GetTSBuffer(int FdDvr);
++ static int GetNumCamSlots(cDevice *Device, int Fd, cCiAdapter *CiAdapter);
++ ///< Tests if the CA device is usable for vdr.
++ ///< If CiAdapter is not NULL it will create the CamSlots for the given ci-adapter.
++ virtual bool SetIdle(bool Idle, bool TestOnly);
++ virtual bool IsIdle(void) const { return idle; }
++ static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
++ };
++
++// A plugin that implements an external DVB ci-adapter derived from cDvbCiAdapter needs to create
++// a cDvbCiAdapterProbe derived object on the heap in order to have its Probe()
++// function called, where it can actually create the appropriate ci-adapter.
++// The cDvbCiAdapterProbe object must be created in the plugin's constructor,
++// and deleted in its destructor.
++// Every plugin has to track its own list of already used device nodes.
++// The Probes are always called if the base cDvbCiAdapter can't create a ci-adapter on its own.
++
++class cDvbCiAdapterProbe : public cListObject {
++public:
++ cDvbCiAdapterProbe(void);
++ virtual ~cDvbCiAdapterProbe();
++ virtual cDvbCiAdapter *Probe(cDevice *Device) = 0;
++ ///< Probes for a DVB ci-adapter for the given Device and creates the appropriate
++ ///< object derived from cDvbCiAdapter if applicable.
++ ///< Returns NULL if no adapter has been created.
+ };
+
++extern cList<cDvbCiAdapterProbe> DvbCiAdapterProbes;
++
+ #endif //__DVBCI_H
+diff --git a/dvbdevice.c b/dvbdevice.c
+index ab6149f..11e9fd8 100644
+--- a/dvbdevice.c
++++ b/dvbdevice.c
+@@ -290,7 +290,7 @@ private:
+ enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked };
+ int frontendType;
+ const cDvbDevice *device;
+- int fd_frontend;
++ mutable int fd_frontend;
+ int adapter, frontend;
+ uint32_t subsystemId;
+ int tuneTimeout;
+@@ -301,7 +301,7 @@ private:
+ const cScr *scr;
+ bool lnbPowerTurnedOn;
+ eTunerStatus tunerStatus;
+- cMutex mutex;
++ mutable cMutex mutex;
+ cCondVar locked;
+ cCondVar newSet;
+ cDvbTuner *bondedTuner;
+@@ -316,6 +316,10 @@ private:
+ void ResetToneAndVoltage(void);
+ bool SetFrontend(void);
+ virtual void Action(void);
++
++ mutable bool isIdle;
++ bool OpenFrontend(void) const;
++ bool CloseFrontend(void);
+ public:
+ cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend);
+ virtual ~cDvbTuner();
+@@ -330,6 +334,9 @@ public:
+ bool Locked(int TimeoutMs = 0);
+ int GetSignalStrength(void) const;
+ int GetSignalQuality(void) const;
++
++ bool SetIdle(bool Idle);
++ bool IsIdle(void) const { return isIdle; }
+ };
+
+ cMutex cDvbTuner::bondMutex;
+@@ -351,6 +358,7 @@ cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int
+ tunerStatus = tsIdle;
+ bondedTuner = NULL;
+ bondedMaster = false;
++ isIdle = false;
+ SetDescription("tuner on frontend %d/%d", adapter, frontend);
+ Start();
+ }
+@@ -368,6 +376,8 @@ cDvbTuner::~cDvbTuner()
+ ExecuteDiseqc(lastDiseqc, &Frequency);
+ }
+ */
++ if (device && device->IsSubDevice())
++ CloseFrontend();
+ }
+
+ bool cDvbTuner::Bond(cDvbTuner *Tuner)
+@@ -512,6 +522,8 @@ bool cDvbTuner::Locked(int TimeoutMs)
+
+ void cDvbTuner::ClearEventQueue(void) const
+ {
++ if (!OpenFrontend())
++ return;
+ cPoller Poller(fd_frontend);
+ if (Poller.Poll(TUNER_POLL_TIMEOUT)) {
+ dvb_frontend_event Event;
+@@ -722,6 +734,8 @@ static int GetRequiredDeliverySystem(const cChannel *Channel, const cDvbTranspon
+
+ bool cDvbTuner::SetFrontend(void)
+ {
++ if (!OpenFrontend())
++ return false;
+ #define MAXFRONTENDCMDS 16
+ #define SETCMD(c, d) { Frontend[CmdSeq.num].cmd = (c);\
+ Frontend[CmdSeq.num].u.data = (d);\
+@@ -868,9 +882,11 @@ void cDvbTuner::Action(void)
+ bool LostLock = false;
+ fe_status_t Status = (fe_status_t)0;
+ while (Running()) {
+- fe_status_t NewStatus;
+- if (GetFrontendStatus(NewStatus))
+- Status = NewStatus;
++ if (!isIdle) {
++ fe_status_t NewStatus;
++ if (GetFrontendStatus(NewStatus))
++ Status = NewStatus;
++ }
+ cMutexLock MutexLock(&mutex);
+ int WaitTime = 1000;
+ switch (tunerStatus) {
+@@ -923,6 +939,40 @@ void cDvbTuner::Action(void)
+ }
+ }
+
++bool cDvbTuner::SetIdle(bool Idle)
++{
++ if (isIdle == Idle)
++ return true;
++ isIdle = Idle;
++ if (Idle)
++ return CloseFrontend();
++ return OpenFrontend();
++}
++
++bool cDvbTuner::OpenFrontend(void) const
++{
++ if (fd_frontend >= 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ fd_frontend = cDvbDevice::DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK);
++ if (fd_frontend < 0)
++ return false;
++ isIdle = false;
++ return true;
++}
++
++bool cDvbTuner::CloseFrontend(void)
++{
++ if (fd_frontend < 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ tunerStatus = tsIdle;
++ newSet.Broadcast();
++ close(fd_frontend);
++ fd_frontend = -1;
++ return true;
++}
++
+ // --- cDvbSourceParam -------------------------------------------------------
+
+ class cDvbSourceParam : public cSourceParam {
+@@ -1008,7 +1058,8 @@ const char *DeliverySystemNames[] = {
+ NULL
+ };
+
+-cDvbDevice::cDvbDevice(int Adapter, int Frontend)
++cDvbDevice::cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice)
++:cDevice(ParentDevice)
+ {
+ adapter = Adapter;
+ frontend = Frontend;
+@@ -1026,9 +1077,8 @@ cDvbDevice::cDvbDevice(int Adapter, int Frontend)
+
+ // Common Interface:
+
+- fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
+- if (fd_ca >= 0)
+- ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca);
++ int fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
++ ciAdapter = cDvbCiAdapter::CreateCiAdapter(parentDevice ? parentDevice : this, fd_ca, adapter, frontend);
+
+ // The DVR device (will be opened and closed as needed):
+
+@@ -1256,7 +1306,11 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ if (d >= 0) {
+ int ErrorDevice = 0;
+ if (cDevice *Device1 = cDevice::GetDevice(i)) {
++ if (Device1->HasSubDevice())
++ Device1 = Device1->SubDevice();
+ if (cDevice *Device2 = cDevice::GetDevice(d)) {
++ if (Device2->HasSubDevice())
++ Device2 = Device2->SubDevice();
+ if (cDvbDevice *DvbDevice1 = dynamic_cast<cDvbDevice *>(Device1)) {
+ if (cDvbDevice *DvbDevice2 = dynamic_cast<cDvbDevice *>(Device2)) {
+ if (!DvbDevice1->Bond(DvbDevice2))
+@@ -1290,7 +1344,10 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ void cDvbDevice::UnBondDevices(void)
+ {
+ for (int i = 0; i < cDevice::NumDevices(); i++) {
+- if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(cDevice::GetDevice(i)))
++ cDevice *dev = cDevice::GetDevice(i);
++ if (dev && dev->HasSubDevice())
++ dev = dev->SubDevice();
++ if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(dev))
+ d->UnBond();
+ }
+ }
+@@ -1344,6 +1401,26 @@ bool cDvbDevice::BondingOk(const cChannel *Channel, bool ConsiderOccupied) const
+ return true;
+ }
+
++bool cDvbDevice::SetIdleDevice(bool Idle, bool TestOnly)
++{
++ if (TestOnly) {
++ if (ciAdapter)
++ return ciAdapter->SetIdle(Idle, true);
++ return true;
++ }
++ if (!dvbTuner->SetIdle(Idle))
++ return false;
++ if (ciAdapter && !ciAdapter->SetIdle(Idle, false)) {
++ dvbTuner->SetIdle(!Idle);
++ return false;
++ }
++ if (Idle)
++ StopSectionHandler();
++ else
++ StartSectionHandler();
++ return true;
++}
++
+ bool cDvbDevice::HasCi(void)
+ {
+ return ciAdapter;
+@@ -1513,7 +1590,7 @@ bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne
+
+ bool cDvbDevice::ProvidesEIT(void) const
+ {
+- return dvbTuner != NULL;
++ return !IsIdle() && (dvbTuner != NULL) && !dvbTuner->IsIdle() && ((ciAdapter == NULL) || !ciAdapter->IsIdle());
+ }
+
+ int cDvbDevice::NumProvidedSystems(void) const
+@@ -1567,8 +1644,12 @@ bool cDvbDevice::OpenDvr(void)
+ {
+ CloseDvr();
+ fd_dvr = DvbOpen(DEV_DVB_DVR, adapter, frontend, O_RDONLY | O_NONBLOCK, true);
+- if (fd_dvr >= 0)
+- tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(5), CardIndex() + 1);
++ if (fd_dvr >= 0) {
++ if (ciAdapter)
++ tsBuffer = ciAdapter->GetTSBuffer(fd_dvr);
++ if (tsBuffer == NULL)
++ tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(5), CardIndex() + 1);
++ }
+ return fd_dvr >= 0;
+ }
+
+diff --git a/dvbdevice.h b/dvbdevice.h
+index b4d07f5..f66720d 100644
+--- a/dvbdevice.h
++++ b/dvbdevice.h
+@@ -107,7 +107,7 @@ class cDvbTuner;
+ /// The cDvbDevice implements a DVB device which can be accessed through the Linux DVB driver API.
+
+ class cDvbDevice : public cDevice {
+-protected:
++public:
+ static cString DvbName(const char *Name, int Adapter, int Frontend);
+ static int DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError = false);
+ private:
+@@ -127,19 +127,20 @@ private:
+ int deliverySystems[MAXDELIVERYSYSTEMS];
+ int numDeliverySystems;
+ int numModulations;
+- int fd_dvr, fd_ca;
++ int fd_dvr;
+ static cMutex bondMutex;
+ cDvbDevice *bondedDevice;
+ mutable bool needsDetachBondedReceivers;
+ bool QueryDeliverySystems(int fd_frontend);
+ public:
+- cDvbDevice(int Adapter, int Frontend);
++ cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice = NULL);
+ virtual ~cDvbDevice();
+ int Adapter(void) const { return adapter; }
+ int Frontend(void) const { return frontend; }
+ virtual bool Ready(void);
+ virtual cString DeviceType(void) const;
+ virtual cString DeviceName(void) const;
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly);
+ static bool BondDevices(const char *Bondings);
+ ///< Bonds the devices as defined in the given Bondings string.
+ ///< A bonding is a sequence of device numbers (starting at 1),
+@@ -225,7 +226,7 @@ public:
+ // Receiver facilities
+
+ private:
+- cTSBuffer *tsBuffer;
++ cTSBufferBase *tsBuffer;
+ protected:
+ virtual bool OpenDvr(void);
+ virtual void CloseDvr(void);
diff --git a/patches/vdr-1.7.42-dynamite.patch b/patches/vdr-1.7.42-dynamite.patch
new file mode 100644
index 0000000..3782e05
--- /dev/null
+++ b/patches/vdr-1.7.42-dynamite.patch
@@ -0,0 +1,818 @@
+diff --git a/ci.c b/ci.c
+index 9a4a829..88b50e7 100644
+--- a/ci.c
++++ b/ci.c
+@@ -1571,6 +1571,8 @@ cCamSlot::cCamSlot(cCiAdapter *CiAdapter)
+
+ cCamSlot::~cCamSlot()
+ {
++ if (ciAdapter && ciAdapter->assignedDevice)
++ ciAdapter->assignedDevice->SetCamSlot(NULL);
+ CamSlots.Del(this, false);
+ DeleteAllConnections();
+ }
+diff --git a/ci.h b/ci.h
+index 74e0270..d38e2dd 100644
+--- a/ci.h
++++ b/ci.h
+@@ -115,6 +115,8 @@ public:
+ ///< 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.
++ virtual bool SetIdle(bool Idle, bool TestOnly) { return false; }
++ virtual bool IsIdle(void) const { return false; }
+ };
+
+ class cTPDU;
+diff --git a/device.c b/device.c
+index ff51a11..dfdc081 100644
+--- a/device.c
++++ b/device.c
+@@ -69,12 +69,22 @@ int cDevice::currentChannel = 1;
+ cDevice *cDevice::device[MAXDEVICES] = { NULL };
+ cDevice *cDevice::primaryDevice = NULL;
+ cList<cDeviceHook> cDevice::deviceHooks;
+-
+-cDevice::cDevice(void)
++cDevice *cDevice::nextParentDevice = NULL;
++
++cDevice::cDevice(cDevice *ParentDevice)
+ :patPmtParser(true)
+-{
+- cardIndex = nextCardIndex++;
+- dsyslog("new device number %d", CardIndex() + 1);
++,isIdle(false)
++,parentDevice(ParentDevice)
++,subDevice(NULL)
++{
++ if (!ParentDevice)
++ parentDevice = nextParentDevice;
++ cDevice::nextParentDevice = NULL;
++ if (parentDevice)
++ cardIndex = parentDevice->cardIndex;
++ else
++ cardIndex = nextCardIndex++;
++ dsyslog("new %sdevice number %d", parentDevice ? "sub-" : "", CardIndex() + 1);
+
+ SetDescription("receiver on device %d", CardIndex() + 1);
+
+@@ -106,10 +116,14 @@ cDevice::cDevice(void)
+ for (int i = 0; i < MAXRECEIVERS; i++)
+ receiver[i] = NULL;
+
+- if (numDevices < MAXDEVICES)
+- device[numDevices++] = this;
++ if (!parentDevice) {
++ if (numDevices < MAXDEVICES)
++ device[numDevices++] = this;
++ else
++ esyslog("ERROR: too many devices or \"dynamite\"-unpatched device creator!");
++ }
+ else
+- esyslog("ERROR: too many devices!");
++ parentDevice->subDevice = this;
+ }
+
+ cDevice::~cDevice()
+@@ -118,6 +132,29 @@ cDevice::~cDevice()
+ DetachAllReceivers();
+ delete liveSubtitle;
+ delete dvbSubtitleConverter;
++ if (parentDevice && (parentDevice->subDevice == this))
++ parentDevice->subDevice = NULL;
++}
++
++bool cDevice::SetIdle(bool Idle)
++{
++ if (parentDevice)
++ return parentDevice->SetIdle(Idle);
++ if (isIdle == Idle)
++ return true;
++ if (Receiving(false))
++ return false;
++ if (Idle) {
++ Detach(player);
++ DetachAllReceivers();
++ }
++ if (!SetIdleDevice(Idle, true))
++ return false;
++ isIdle = Idle;
++ if (SetIdleDevice(Idle, false))
++ return true;
++ isIdle = !Idle;
++ return false;
+ }
+
+ bool cDevice::WaitForAllDevicesReady(int Timeout)
+@@ -156,6 +193,8 @@ int cDevice::NextCardIndex(int n)
+
+ int cDevice::DeviceNumber(void) const
+ {
++ if (parentDevice)
++ return parentDevice->DeviceNumber();
+ for (int i = 0; i < numDevices; i++) {
+ if (device[i] == this)
+ return i;
+@@ -356,6 +395,8 @@ bool cDevice::HasCi(void)
+
+ void cDevice::SetCamSlot(cCamSlot *CamSlot)
+ {
++ if (parentDevice)
++ return parentDevice->SetCamSlot(CamSlot);
+ camSlot = CamSlot;
+ }
+
+@@ -568,6 +609,10 @@ void cDevice::DelLivePids(void)
+
+ void cDevice::StartSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StartSectionHandler();
++ return;
++ }
+ if (!sectionHandler) {
+ sectionHandler = new cSectionHandler(this);
+ AttachFilter(eitFilter = new cEitFilter);
+@@ -579,6 +624,10 @@ void cDevice::StartSectionHandler(void)
+
+ void cDevice::StopSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StopSectionHandler();
++ return;
++ }
+ if (sectionHandler) {
+ delete nitFilter;
+ delete sdtFilter;
+@@ -610,12 +659,17 @@ void cDevice::CloseFilter(int Handle)
+
+ void cDevice::AttachFilter(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->AttachFilter(Filter);
++ SetIdle(false);
+ if (sectionHandler)
+ sectionHandler->Attach(Filter);
+ }
+
+ void cDevice::Detach(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Filter);
+ if (sectionHandler)
+ sectionHandler->Detach(Filter);
+ }
+@@ -777,6 +831,7 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
+ sectionHandler->SetStatus(false);
+ sectionHandler->SetChannel(NULL);
+ }
++ SetIdle(false);
+ // Tell the camSlot about the channel switch and add all PIDs of this
+ // channel to it, for possible later decryption:
+ if (camSlot)
+@@ -823,19 +878,27 @@ void cDevice::ForceTransferMode(void)
+ {
+ if (!cTransferControl::ReceiverDevice()) {
+ cChannel *Channel = Channels.GetByNumber(CurrentChannel());
+- if (Channel)
++ if (Channel) {
++ SetIdle(false);
+ SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
++ }
+ }
+ }
+
+ int cDevice::Occupied(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Occupied();
+ int Seconds = occupiedTimeout - time(NULL);
+ return Seconds > 0 ? Seconds : 0;
+ }
+
+ void cDevice::SetOccupied(int Seconds)
+ {
++ if (parentDevice) {
++ parentDevice->SetOccupied(Seconds);
++ return;
++ }
+ if (Seconds >= 0)
+ occupiedTimeout = time(NULL) + min(Seconds, MAXOCCUPIEDTIMEOUT);
+ }
+@@ -1216,7 +1279,10 @@ bool cDevice::Transferring(void) const
+
+ bool cDevice::AttachPlayer(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->AttachPlayer(Player);
+ if (CanReplay()) {
++ SetIdle(false);
+ if (player)
+ Detach(player);
+ DELETENULL(liveSubtitle);
+@@ -1235,6 +1301,8 @@ bool cDevice::AttachPlayer(cPlayer *Player)
+
+ void cDevice::Detach(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Player);
+ if (Player && player == Player) {
+ cPlayer *p = player;
+ player = NULL; // avoids recursive calls to Detach()
+@@ -1254,6 +1322,8 @@ void cDevice::Detach(cPlayer *Player)
+
+ void cDevice::StopReplay(void)
+ {
++ if (parentDevice)
++ return parentDevice->StopReplay();
+ if (player) {
+ Detach(player);
+ if (IsPrimaryDevice())
+@@ -1534,6 +1604,8 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
+
+ int cDevice::Priority(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Priority();
+ int priority = IDLEPRIORITY;
+ if (IsPrimaryDevice() && !Replaying() && HasProgramme())
+ priority = TRANSFERPRIORITY; // we use the same value here, no matter whether it's actual Transfer Mode or real live viewing
+@@ -1552,6 +1624,8 @@ bool cDevice::Ready(void)
+
+ bool cDevice::Receiving(bool Dummy) const
+ {
++ if (parentDevice)
++ return parentDevice->Receiving(Dummy);
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+ if (receiver[i])
+@@ -1632,10 +1706,13 @@ bool cDevice::GetTSPacket(uchar *&Data)
+
+ bool cDevice::AttachReceiver(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->AttachReceiver(Receiver);
+ if (!Receiver)
+ return false;
+ if (Receiver->device == this)
+ return true;
++ SetIdle(false);
+ // activate the following line if you need it - actually the driver should be fixed!
+ //#define WAIT_FOR_TUNER_LOCK
+ #ifdef WAIT_FOR_TUNER_LOCK
+@@ -1674,6 +1751,8 @@ bool cDevice::AttachReceiver(cReceiver *Receiver)
+
+ void cDevice::Detach(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Receiver);
+ if (!Receiver || Receiver->device != this)
+ return;
+ bool receiversLeft = false;
+@@ -1699,6 +1778,8 @@ void cDevice::Detach(cReceiver *Receiver)
+
+ void cDevice::DetachAll(int Pid)
+ {
++ if (parentDevice)
++ return parentDevice->DetachAll(Pid);
+ if (Pid) {
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+@@ -1783,3 +1864,25 @@ uchar *cTSBuffer::Get(void)
+ }
+ return NULL;
+ }
++
++// --- cDynamicDeviceProbe -------------------------------------------------------
++
++cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
++cList<cDynamicDeviceProbe::cDynamicDeviceProbeItem> cDynamicDeviceProbe::commandQueue;
++
++void cDynamicDeviceProbe::QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath)
++{
++ if (DevPath)
++ commandQueue.Add(new cDynamicDeviceProbeItem(Cmd, new cString(DevPath)));
++}
++
++cDynamicDeviceProbe::cDynamicDeviceProbe(void)
++{
++ DynamicDeviceProbes.Add(this);
++}
++
++cDynamicDeviceProbe::~cDynamicDeviceProbe()
++{
++ DynamicDeviceProbes.Del(this, false);
++}
+diff --git a/device.h b/device.h
+index fd010d4..3217e9d 100644
+--- a/device.h
++++ b/device.h
+@@ -169,7 +169,6 @@ private:
+ static int nextCardIndex;
+ int cardIndex;
+ protected:
+- cDevice(void);
+ virtual ~cDevice();
+ virtual bool Ready(void);
+ ///< Returns true if this device is ready. Devices with conditional
+@@ -196,9 +195,6 @@ protected:
+ ///< A derived class must call the MakePrimaryDevice() function of its
+ ///< base class.
+ public:
+- bool IsPrimaryDevice(void) const { return this == primaryDevice; }
+- int CardIndex(void) const { return cardIndex; }
+- ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
+ int DeviceNumber(void) const;
+ ///< Returns the number of this device (0 ... numDevices).
+ virtual cString DeviceType(void) const;
+@@ -423,9 +419,6 @@ public:
+ ///< shall check whether the channel can be decrypted.
+ 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
+
+@@ -586,9 +579,6 @@ private:
+ cTsToPes tsToPesSubtitle;
+ bool isPlayingVideo;
+ protected:
+- const cPatPmtParser *PatPmtParser(void) const { return &patPmtParser; }
+- ///< Returns a pointer to the patPmtParser, so that a derived device
+- ///< can use the stream information from it.
+ virtual bool CanReplay(void) const;
+ ///< Returns true if this device can currently start a replay session.
+ virtual bool SetPlayMode(ePlayMode PlayMode);
+@@ -802,6 +792,38 @@ public:
+ ///< Detaches all receivers from this device for this pid.
+ virtual void DetachAllReceivers(void);
+ ///< Detaches all receivers from this device.
++
++// --- dynamite subdevice patch start ---
++ friend class cDynamicDevice;
++private:
++ static cDevice *nextParentDevice;
++ ///< Holds the parent device for the next subdevice
++ ///< so the dynamite-plugin can work with unpatched plugins
++ bool isIdle;
++protected:
++ cDevice *parentDevice;
++ cDevice *subDevice;
++ cDevice(cDevice *ParentDevice = NULL);
++ const cPatPmtParser *PatPmtParser(void) const { if (parentDevice) return parentDevice->PatPmtParser(); return &patPmtParser; }
++ ///< Returns a pointer to the patPmtParser, so that a derived device
++ ///< can use the stream information from it.
++public:
++ bool IsPrimaryDevice(void) const { if (parentDevice) return parentDevice->IsPrimaryDevice(); return this == primaryDevice; }
++ int CardIndex(void) const { if (parentDevice) return parentDevice->cardIndex; return cardIndex; }
++ ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
++ cCamSlot *CamSlot(void) const { if (parentDevice) return parentDevice->CamSlot(); return camSlot; }
++ ///< Returns the CAM slot that is currently used with this device,
++ ///< or NULL if no CAM slot is in use.
++ bool IsSubDevice(void) const { return (parentDevice != NULL); }
++ bool HasSubDevice(void) const { return (subDevice != NULL); }
++ cDevice *SubDevice(void) const { return subDevice; }
++ bool IsIdle(void) const { if (parentDevice) return parentDevice->IsIdle(); return isIdle; }
++ bool SetIdle(bool Idle);
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly) { return false; }
++ ///< Called by SetIdle
++ ///< if TestOnly, don't do anything, just return, if the device
++ ///< can be set to the new idle state
++ // --- dynamite subdevice patch end ---
+ };
+
+ /// Derived cDevice classes that can receive channels will have to provide
+@@ -825,4 +847,47 @@ public:
+ uchar *Get(void);
+ };
+
++/// A plugin that want to create devices handled by the dynamite-plugin needs to create
++/// a cDynamicDeviceProbe derived object on the heap in order to have its Probe()
++/// function called, where it can actually create the appropriate device.
++/// The cDynamicDeviceProbe object must be created in the plugin's constructor,
++/// and deleted in its destructor.
++/// The "DevPath" hasn't to be a physical device or a path in the filesystem.
++/// It can be any string a plugin may react on.
++
++#define __DYNAMIC_DEVICE_PROBE
++
++enum eDynamicDeviceProbeCommand { ddpcAttach, ddpcDetach, ddpcService };
++
++class cDynamicDeviceProbe : public cListObject {
++ friend class cDynamicDevice;
++private:
++ class cDynamicDeviceProbeItem : public cListObject {
++ public:
++ eDynamicDeviceProbeCommand cmd;
++ cString *devpath;
++ cDynamicDeviceProbeItem(eDynamicDeviceProbeCommand Cmd, cString *DevPath):cmd(Cmd),devpath(DevPath) {}
++ virtual ~cDynamicDeviceProbeItem() { if (devpath) delete devpath; }
++ };
++ static cList<cDynamicDeviceProbeItem> commandQueue;
++ ///< A list where all attach/detach commands are queued
++ ///< so they can be processed in the MainThreadHook of
++ ///< the dynamite plugin.
++public:
++ static void QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath);
++ ///< Plugins which support cDynamicDeviceProbe must use this function
++ ///< to queue the devices they normally create in their Initialize method.
++ ///< These devices are created as subdevices in the Start-method of the dynamite-plugin.
++ cDynamicDeviceProbe(void);
++ virtual ~cDynamicDeviceProbe();
++ virtual cDevice *Attach(cDevice *ParentDevice, const char *DevPath) = 0;
++ ///< Probes for a device at the given device-path like /dev/dvb/adapter0/frontend0
++ ///< or /dev/video0 etc. and creates the appropriate
++ ///< object derived from cDevice if applicable.
++ ///< Returns the device that has been created or NULL if not.
++ ///< The dynamite-plugin will delete the device if it is detached.
++ };
++
++extern cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
+ #endif //__DEVICE_H
+diff --git a/dvbci.c b/dvbci.c
+index 5289bbd..fea3a83 100644
+--- a/dvbci.c
++++ b/dvbci.c
+@@ -10,15 +10,18 @@
+ #include "dvbci.h"
+ #include <linux/dvb/ca.h>
+ #include <sys/ioctl.h>
+-#include "device.h"
++#include "dvbdevice.h"
+
+ // --- cDvbCiAdapter ---------------------------------------------------------
+
+-cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
++cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
+ {
+ device = Device;
+ SetDescription("CI adapter on device %d", device->DeviceNumber());
+ fd = Fd;
++ adapter = Adapter;
++ frontend = Frontend;
++ idle = false;
+ ca_caps_t Caps;
+ if (ioctl(fd, CA_GET_CAP, &Caps) == 0) {
+ if ((Caps.slot_type & CA_CI_LINK) != 0) {
+@@ -41,10 +44,44 @@ cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
+ cDvbCiAdapter::~cDvbCiAdapter()
+ {
+ Cancel(3);
++ if (device->IsSubDevice() || device->HasSubDevice())
++ CloseCa();
++}
++
++bool cDvbCiAdapter::OpenCa(void)
++{
++ if (fd >= 0)
++ return true;
++ fd = cDvbDevice::DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
++ return (fd >= 0);
++}
++
++void cDvbCiAdapter::CloseCa(void)
++{
++ if (fd < 0)
++ return;
++ close(fd);
++ fd = -1;
++}
++
++bool cDvbCiAdapter::SetIdle(bool Idle, bool TestOnly)
++{
++ if ((adapter < 0) || (frontend < 0))
++ return false;
++ if (TestOnly || (idle == Idle))
++ return true;
++ if (Idle)
++ CloseCa();
++ else
++ OpenCa();
++ idle = Idle;
++ return true;
+ }
+
+ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+ {
++ if (idle || (fd < 0))
++ return 0;
+ if (Buffer && MaxLength > 0) {
+ struct pollfd pfd[1];
+ pfd[0].fd = fd;
+@@ -61,6 +98,8 @@ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+
+ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+ {
++ if (idle || (fd < 0))
++ return;
+ 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());
+@@ -69,6 +108,8 @@ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+
+ bool cDvbCiAdapter::Reset(int Slot)
+ {
++ if (idle || (fd < 0))
++ return false;
+ if (ioctl(fd, CA_RESET, 1 << Slot) != -1)
+ return true;
+ else
+@@ -78,6 +119,8 @@ bool cDvbCiAdapter::Reset(int Slot)
+
+ eModuleStatus cDvbCiAdapter::ModuleStatus(int Slot)
+ {
++ if (idle || (fd < 0))
++ return msNone;
+ ca_slot_info_t sinfo;
+ sinfo.num = Slot;
+ if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) {
+@@ -99,10 +142,10 @@ bool cDvbCiAdapter::Assign(cDevice *Device, bool Query)
+ return true;
+ }
+
+-cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd)
++cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
+ {
+ // TODO check whether a CI is actually present?
+ if (Device)
+- return new cDvbCiAdapter(Device, Fd);
++ return new cDvbCiAdapter(Device, Fd, Adapter, Frontend);
+ return NULL;
+ }
+diff --git a/dvbci.h b/dvbci.h
+index adbe40d..6d117b2 100644
+--- a/dvbci.h
++++ b/dvbci.h
+@@ -16,16 +16,24 @@ class cDvbCiAdapter : public cCiAdapter {
+ private:
+ cDevice *device;
+ int fd;
++ int adapter;
++ int frontend;
++ bool idle;
++
++ bool OpenCa(void);
++ void CloseCa(void);
+ 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);
++ cDvbCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
+ public:
+ virtual ~cDvbCiAdapter();
+- static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd);
++ virtual bool SetIdle(bool Idle, bool TestOnly);
++ virtual bool IsIdle(void) const { return idle; }
++ static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
+ };
+
+ #endif //__DVBCI_H
+diff --git a/dvbdevice.c b/dvbdevice.c
+index ab6149f..47cf585 100644
+--- a/dvbdevice.c
++++ b/dvbdevice.c
+@@ -290,7 +290,7 @@ private:
+ enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked };
+ int frontendType;
+ const cDvbDevice *device;
+- int fd_frontend;
++ mutable int fd_frontend;
+ int adapter, frontend;
+ uint32_t subsystemId;
+ int tuneTimeout;
+@@ -301,7 +301,7 @@ private:
+ const cScr *scr;
+ bool lnbPowerTurnedOn;
+ eTunerStatus tunerStatus;
+- cMutex mutex;
++ mutable cMutex mutex;
+ cCondVar locked;
+ cCondVar newSet;
+ cDvbTuner *bondedTuner;
+@@ -316,6 +316,10 @@ private:
+ void ResetToneAndVoltage(void);
+ bool SetFrontend(void);
+ virtual void Action(void);
++
++ mutable bool isIdle;
++ bool OpenFrontend(void) const;
++ bool CloseFrontend(void);
+ public:
+ cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend);
+ virtual ~cDvbTuner();
+@@ -330,6 +334,9 @@ public:
+ bool Locked(int TimeoutMs = 0);
+ int GetSignalStrength(void) const;
+ int GetSignalQuality(void) const;
++
++ bool SetIdle(bool Idle);
++ bool IsIdle(void) const { return isIdle; }
+ };
+
+ cMutex cDvbTuner::bondMutex;
+@@ -351,6 +358,7 @@ cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int
+ tunerStatus = tsIdle;
+ bondedTuner = NULL;
+ bondedMaster = false;
++ isIdle = false;
+ SetDescription("tuner on frontend %d/%d", adapter, frontend);
+ Start();
+ }
+@@ -368,6 +376,8 @@ cDvbTuner::~cDvbTuner()
+ ExecuteDiseqc(lastDiseqc, &Frequency);
+ }
+ */
++ if (device && device->IsSubDevice())
++ CloseFrontend();
+ }
+
+ bool cDvbTuner::Bond(cDvbTuner *Tuner)
+@@ -512,6 +522,8 @@ bool cDvbTuner::Locked(int TimeoutMs)
+
+ void cDvbTuner::ClearEventQueue(void) const
+ {
++ if (!OpenFrontend())
++ return;
+ cPoller Poller(fd_frontend);
+ if (Poller.Poll(TUNER_POLL_TIMEOUT)) {
+ dvb_frontend_event Event;
+@@ -722,6 +734,8 @@ static int GetRequiredDeliverySystem(const cChannel *Channel, const cDvbTranspon
+
+ bool cDvbTuner::SetFrontend(void)
+ {
++ if (!OpenFrontend())
++ return false;
+ #define MAXFRONTENDCMDS 16
+ #define SETCMD(c, d) { Frontend[CmdSeq.num].cmd = (c);\
+ Frontend[CmdSeq.num].u.data = (d);\
+@@ -868,9 +882,11 @@ void cDvbTuner::Action(void)
+ bool LostLock = false;
+ fe_status_t Status = (fe_status_t)0;
+ while (Running()) {
+- fe_status_t NewStatus;
+- if (GetFrontendStatus(NewStatus))
+- Status = NewStatus;
++ if (!isIdle) {
++ fe_status_t NewStatus;
++ if (GetFrontendStatus(NewStatus))
++ Status = NewStatus;
++ }
+ cMutexLock MutexLock(&mutex);
+ int WaitTime = 1000;
+ switch (tunerStatus) {
+@@ -923,6 +939,40 @@ void cDvbTuner::Action(void)
+ }
+ }
+
++bool cDvbTuner::SetIdle(bool Idle)
++{
++ if (isIdle == Idle)
++ return true;
++ isIdle = Idle;
++ if (Idle)
++ return CloseFrontend();
++ return OpenFrontend();
++}
++
++bool cDvbTuner::OpenFrontend(void) const
++{
++ if (fd_frontend >= 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ fd_frontend = cDvbDevice::DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK);
++ if (fd_frontend < 0)
++ return false;
++ isIdle = false;
++ return true;
++}
++
++bool cDvbTuner::CloseFrontend(void)
++{
++ if (fd_frontend < 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ tunerStatus = tsIdle;
++ newSet.Broadcast();
++ close(fd_frontend);
++ fd_frontend = -1;
++ return true;
++}
++
+ // --- cDvbSourceParam -------------------------------------------------------
+
+ class cDvbSourceParam : public cSourceParam {
+@@ -1008,7 +1058,8 @@ const char *DeliverySystemNames[] = {
+ NULL
+ };
+
+-cDvbDevice::cDvbDevice(int Adapter, int Frontend)
++cDvbDevice::cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice)
++:cDevice(ParentDevice)
+ {
+ adapter = Adapter;
+ frontend = Frontend;
+@@ -1028,7 +1079,7 @@ cDvbDevice::cDvbDevice(int Adapter, int Frontend)
+
+ fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
+ if (fd_ca >= 0)
+- ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca);
++ ciAdapter = cDvbCiAdapter::CreateCiAdapter(parentDevice ? parentDevice : this, fd_ca, adapter, frontend);
+
+ // The DVR device (will be opened and closed as needed):
+
+@@ -1256,7 +1307,11 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ if (d >= 0) {
+ int ErrorDevice = 0;
+ if (cDevice *Device1 = cDevice::GetDevice(i)) {
++ if (Device1->HasSubDevice())
++ Device1 = Device1->SubDevice();
+ if (cDevice *Device2 = cDevice::GetDevice(d)) {
++ if (Device2->HasSubDevice())
++ Device2 = Device2->SubDevice();
+ if (cDvbDevice *DvbDevice1 = dynamic_cast<cDvbDevice *>(Device1)) {
+ if (cDvbDevice *DvbDevice2 = dynamic_cast<cDvbDevice *>(Device2)) {
+ if (!DvbDevice1->Bond(DvbDevice2))
+@@ -1290,7 +1345,10 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ void cDvbDevice::UnBondDevices(void)
+ {
+ for (int i = 0; i < cDevice::NumDevices(); i++) {
+- if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(cDevice::GetDevice(i)))
++ cDevice *dev = cDevice::GetDevice(i);
++ if (dev && dev->HasSubDevice())
++ dev = dev->SubDevice();
++ if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(dev))
+ d->UnBond();
+ }
+ }
+@@ -1344,6 +1402,26 @@ bool cDvbDevice::BondingOk(const cChannel *Channel, bool ConsiderOccupied) const
+ return true;
+ }
+
++bool cDvbDevice::SetIdleDevice(bool Idle, bool TestOnly)
++{
++ if (TestOnly) {
++ if (ciAdapter)
++ return ciAdapter->SetIdle(Idle, true);
++ return true;
++ }
++ if (!dvbTuner->SetIdle(Idle))
++ return false;
++ if (ciAdapter && !ciAdapter->SetIdle(Idle, false)) {
++ dvbTuner->SetIdle(!Idle);
++ return false;
++ }
++ if (Idle)
++ StopSectionHandler();
++ else
++ StartSectionHandler();
++ return true;
++}
++
+ bool cDvbDevice::HasCi(void)
+ {
+ return ciAdapter;
+@@ -1513,7 +1591,7 @@ bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne
+
+ bool cDvbDevice::ProvidesEIT(void) const
+ {
+- return dvbTuner != NULL;
++ return !IsIdle() && (dvbTuner != NULL) && !dvbTuner->IsIdle() && ((ciAdapter == NULL) || !ciAdapter->IsIdle());
+ }
+
+ int cDvbDevice::NumProvidedSystems(void) const
+diff --git a/dvbdevice.h b/dvbdevice.h
+index b4d07f5..6f785ec 100644
+--- a/dvbdevice.h
++++ b/dvbdevice.h
+@@ -107,7 +107,7 @@ class cDvbTuner;
+ /// The cDvbDevice implements a DVB device which can be accessed through the Linux DVB driver API.
+
+ class cDvbDevice : public cDevice {
+-protected:
++public:
+ static cString DvbName(const char *Name, int Adapter, int Frontend);
+ static int DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError = false);
+ private:
+@@ -133,13 +133,14 @@ private:
+ mutable bool needsDetachBondedReceivers;
+ bool QueryDeliverySystems(int fd_frontend);
+ public:
+- cDvbDevice(int Adapter, int Frontend);
++ cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice = NULL);
+ virtual ~cDvbDevice();
+ int Adapter(void) const { return adapter; }
+ int Frontend(void) const { return frontend; }
+ virtual bool Ready(void);
+ virtual cString DeviceType(void) const;
+ virtual cString DeviceName(void) const;
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly);
+ static bool BondDevices(const char *Bondings);
+ ///< Bonds the devices as defined in the given Bondings string.
+ ///< A bonding is a sequence of device numbers (starting at 1),
diff --git a/patches/vdr-2.0.0-dynamite+externalci+rotorng.patch b/patches/vdr-2.0.0-dynamite+externalci+rotorng.patch
new file mode 100644
index 0000000..ea92508
--- /dev/null
+++ b/patches/vdr-2.0.0-dynamite+externalci+rotorng.patch
@@ -0,0 +1,1070 @@
+diff --git a/ci.c b/ci.c
+index 9a4a829..88b50e7 100644
+--- a/ci.c
++++ b/ci.c
+@@ -1571,6 +1571,8 @@ cCamSlot::cCamSlot(cCiAdapter *CiAdapter)
+
+ cCamSlot::~cCamSlot()
+ {
++ if (ciAdapter && ciAdapter->assignedDevice)
++ ciAdapter->assignedDevice->SetCamSlot(NULL);
+ CamSlots.Del(this, false);
+ DeleteAllConnections();
+ }
+diff --git a/ci.h b/ci.h
+index 74e0270..818ea29 100644
+--- a/ci.h
++++ b/ci.h
+@@ -72,6 +72,7 @@ public:
+ };
+
+ class cDevice;
++class cTSBufferBase;
+ class cCamSlot;
+
+ enum eModuleStatus { msNone, msReset, msPresent, msReady };
+@@ -115,6 +116,13 @@ public:
+ ///< 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.
++#define EXTERNALCI_PATCH
++ virtual cTSBufferBase *GetTSBuffer(int FdDvr) { return NULL; }
++ ///< Derived classes can return a special TS buffer with features
++ ///< like rerouting the stream through an external ci.
++ ///< The caller must delete the buffer.
++ virtual bool SetIdle(bool Idle, bool TestOnly) { return false; }
++ virtual bool IsIdle(void) const { return false; }
+ };
+
+ class cTPDU;
+diff --git a/device.c b/device.c
+index ff51a11..dfdc081 100644
+--- a/device.c
++++ b/device.c
+@@ -69,12 +69,22 @@ int cDevice::currentChannel = 1;
+ cDevice *cDevice::device[MAXDEVICES] = { NULL };
+ cDevice *cDevice::primaryDevice = NULL;
+ cList<cDeviceHook> cDevice::deviceHooks;
+-
+-cDevice::cDevice(void)
++cDevice *cDevice::nextParentDevice = NULL;
++
++cDevice::cDevice(cDevice *ParentDevice)
+ :patPmtParser(true)
+-{
+- cardIndex = nextCardIndex++;
+- dsyslog("new device number %d", CardIndex() + 1);
++,isIdle(false)
++,parentDevice(ParentDevice)
++,subDevice(NULL)
++{
++ if (!ParentDevice)
++ parentDevice = nextParentDevice;
++ cDevice::nextParentDevice = NULL;
++ if (parentDevice)
++ cardIndex = parentDevice->cardIndex;
++ else
++ cardIndex = nextCardIndex++;
++ dsyslog("new %sdevice number %d", parentDevice ? "sub-" : "", CardIndex() + 1);
+
+ SetDescription("receiver on device %d", CardIndex() + 1);
+
+@@ -106,10 +116,14 @@ cDevice::cDevice(void)
+ for (int i = 0; i < MAXRECEIVERS; i++)
+ receiver[i] = NULL;
+
+- if (numDevices < MAXDEVICES)
+- device[numDevices++] = this;
++ if (!parentDevice) {
++ if (numDevices < MAXDEVICES)
++ device[numDevices++] = this;
++ else
++ esyslog("ERROR: too many devices or \"dynamite\"-unpatched device creator!");
++ }
+ else
+- esyslog("ERROR: too many devices!");
++ parentDevice->subDevice = this;
+ }
+
+ cDevice::~cDevice()
+@@ -118,6 +132,29 @@ cDevice::~cDevice()
+ DetachAllReceivers();
+ delete liveSubtitle;
+ delete dvbSubtitleConverter;
++ if (parentDevice && (parentDevice->subDevice == this))
++ parentDevice->subDevice = NULL;
++}
++
++bool cDevice::SetIdle(bool Idle)
++{
++ if (parentDevice)
++ return parentDevice->SetIdle(Idle);
++ if (isIdle == Idle)
++ return true;
++ if (Receiving(false))
++ return false;
++ if (Idle) {
++ Detach(player);
++ DetachAllReceivers();
++ }
++ if (!SetIdleDevice(Idle, true))
++ return false;
++ isIdle = Idle;
++ if (SetIdleDevice(Idle, false))
++ return true;
++ isIdle = !Idle;
++ return false;
+ }
+
+ bool cDevice::WaitForAllDevicesReady(int Timeout)
+@@ -156,6 +193,8 @@ int cDevice::NextCardIndex(int n)
+
+ int cDevice::DeviceNumber(void) const
+ {
++ if (parentDevice)
++ return parentDevice->DeviceNumber();
+ for (int i = 0; i < numDevices; i++) {
+ if (device[i] == this)
+ return i;
+@@ -356,6 +395,8 @@ bool cDevice::HasCi(void)
+
+ void cDevice::SetCamSlot(cCamSlot *CamSlot)
+ {
++ if (parentDevice)
++ return parentDevice->SetCamSlot(CamSlot);
+ camSlot = CamSlot;
+ }
+
+@@ -568,6 +609,10 @@ void cDevice::DelLivePids(void)
+
+ void cDevice::StartSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StartSectionHandler();
++ return;
++ }
+ if (!sectionHandler) {
+ sectionHandler = new cSectionHandler(this);
+ AttachFilter(eitFilter = new cEitFilter);
+@@ -579,6 +624,10 @@ void cDevice::StartSectionHandler(void)
+
+ void cDevice::StopSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StopSectionHandler();
++ return;
++ }
+ if (sectionHandler) {
+ delete nitFilter;
+ delete sdtFilter;
+@@ -610,12 +659,17 @@ void cDevice::CloseFilter(int Handle)
+
+ void cDevice::AttachFilter(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->AttachFilter(Filter);
++ SetIdle(false);
+ if (sectionHandler)
+ sectionHandler->Attach(Filter);
+ }
+
+ void cDevice::Detach(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Filter);
+ if (sectionHandler)
+ sectionHandler->Detach(Filter);
+ }
+@@ -777,6 +831,7 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
+ sectionHandler->SetStatus(false);
+ sectionHandler->SetChannel(NULL);
+ }
++ SetIdle(false);
+ // Tell the camSlot about the channel switch and add all PIDs of this
+ // channel to it, for possible later decryption:
+ if (camSlot)
+@@ -823,19 +878,27 @@ void cDevice::ForceTransferMode(void)
+ {
+ if (!cTransferControl::ReceiverDevice()) {
+ cChannel *Channel = Channels.GetByNumber(CurrentChannel());
+- if (Channel)
++ if (Channel) {
++ SetIdle(false);
+ SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
++ }
+ }
+ }
+
+ int cDevice::Occupied(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Occupied();
+ int Seconds = occupiedTimeout - time(NULL);
+ return Seconds > 0 ? Seconds : 0;
+ }
+
+ void cDevice::SetOccupied(int Seconds)
+ {
++ if (parentDevice) {
++ parentDevice->SetOccupied(Seconds);
++ return;
++ }
+ if (Seconds >= 0)
+ occupiedTimeout = time(NULL) + min(Seconds, MAXOCCUPIEDTIMEOUT);
+ }
+@@ -1216,7 +1279,10 @@ bool cDevice::Transferring(void) const
+
+ bool cDevice::AttachPlayer(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->AttachPlayer(Player);
+ if (CanReplay()) {
++ SetIdle(false);
+ if (player)
+ Detach(player);
+ DELETENULL(liveSubtitle);
+@@ -1235,6 +1301,8 @@ bool cDevice::AttachPlayer(cPlayer *Player)
+
+ void cDevice::Detach(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Player);
+ if (Player && player == Player) {
+ cPlayer *p = player;
+ player = NULL; // avoids recursive calls to Detach()
+@@ -1254,6 +1322,8 @@ void cDevice::Detach(cPlayer *Player)
+
+ void cDevice::StopReplay(void)
+ {
++ if (parentDevice)
++ return parentDevice->StopReplay();
+ if (player) {
+ Detach(player);
+ if (IsPrimaryDevice())
+@@ -1534,6 +1604,8 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
+
+ int cDevice::Priority(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Priority();
+ int priority = IDLEPRIORITY;
+ if (IsPrimaryDevice() && !Replaying() && HasProgramme())
+ priority = TRANSFERPRIORITY; // we use the same value here, no matter whether it's actual Transfer Mode or real live viewing
+@@ -1552,6 +1624,8 @@ bool cDevice::Ready(void)
+
+ bool cDevice::Receiving(bool Dummy) const
+ {
++ if (parentDevice)
++ return parentDevice->Receiving(Dummy);
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+ if (receiver[i])
+@@ -1632,10 +1706,13 @@ bool cDevice::GetTSPacket(uchar *&Data)
+
+ bool cDevice::AttachReceiver(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->AttachReceiver(Receiver);
+ if (!Receiver)
+ return false;
+ if (Receiver->device == this)
+ return true;
++ SetIdle(false);
+ // activate the following line if you need it - actually the driver should be fixed!
+ //#define WAIT_FOR_TUNER_LOCK
+ #ifdef WAIT_FOR_TUNER_LOCK
+@@ -1674,6 +1751,8 @@ bool cDevice::AttachReceiver(cReceiver *Receiver)
+
+ void cDevice::Detach(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Receiver);
+ if (!Receiver || Receiver->device != this)
+ return;
+ bool receiversLeft = false;
+@@ -1699,6 +1778,8 @@ void cDevice::Detach(cReceiver *Receiver)
+
+ void cDevice::DetachAll(int Pid)
+ {
++ if (parentDevice)
++ return parentDevice->DetachAll(Pid);
+ if (Pid) {
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+@@ -1783,3 +1864,25 @@ uchar *cTSBuffer::Get(void)
+ }
+ return NULL;
+ }
++
++// --- cDynamicDeviceProbe -------------------------------------------------------
++
++cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
++cList<cDynamicDeviceProbe::cDynamicDeviceProbeItem> cDynamicDeviceProbe::commandQueue;
++
++void cDynamicDeviceProbe::QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath)
++{
++ if (DevPath)
++ commandQueue.Add(new cDynamicDeviceProbeItem(Cmd, new cString(DevPath)));
++}
++
++cDynamicDeviceProbe::cDynamicDeviceProbe(void)
++{
++ DynamicDeviceProbes.Add(this);
++}
++
++cDynamicDeviceProbe::~cDynamicDeviceProbe()
++{
++ DynamicDeviceProbes.Del(this, false);
++}
+diff --git a/device.h b/device.h
+index fd010d4..ed3b1da 100644
+--- a/device.h
++++ b/device.h
+@@ -24,6 +24,7 @@
+ #include "spu.h"
+ #include "thread.h"
+ #include "tools.h"
++#include <linux/dvb/frontend.h>
+
+ #define MAXDEVICES 16 // the maximum number of devices in the system
+ #define MAXPIDHANDLES 64 // the maximum number of different PIDs per device
+@@ -169,7 +170,6 @@ private:
+ static int nextCardIndex;
+ int cardIndex;
+ protected:
+- cDevice(void);
+ virtual ~cDevice();
+ virtual bool Ready(void);
+ ///< Returns true if this device is ready. Devices with conditional
+@@ -196,9 +196,6 @@ protected:
+ ///< A derived class must call the MakePrimaryDevice() function of its
+ ///< base class.
+ public:
+- bool IsPrimaryDevice(void) const { return this == primaryDevice; }
+- int CardIndex(void) const { return cardIndex; }
+- ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
+ int DeviceNumber(void) const;
+ ///< Returns the number of this device (0 ... numDevices).
+ virtual cString DeviceType(void) const;
+@@ -338,6 +335,7 @@ public:
+ ///< Returns true if the device is currently showing any programme to
+ ///< the user, either through replaying or live.
+
++ virtual bool SendDiseqcCmd(dvb_diseqc_master_cmd cmd) {return false;}
+ // PID handle facilities
+
+ private:
+@@ -423,9 +421,6 @@ public:
+ ///< shall check whether the channel can be decrypted.
+ 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
+
+@@ -586,9 +581,6 @@ private:
+ cTsToPes tsToPesSubtitle;
+ bool isPlayingVideo;
+ protected:
+- const cPatPmtParser *PatPmtParser(void) const { return &patPmtParser; }
+- ///< Returns a pointer to the patPmtParser, so that a derived device
+- ///< can use the stream information from it.
+ virtual bool CanReplay(void) const;
+ ///< Returns true if this device can currently start a replay session.
+ virtual bool SetPlayMode(ePlayMode PlayMode);
+@@ -802,6 +794,38 @@ public:
+ ///< Detaches all receivers from this device for this pid.
+ virtual void DetachAllReceivers(void);
+ ///< Detaches all receivers from this device.
++
++// --- dynamite subdevice patch start ---
++ friend class cDynamicDevice;
++private:
++ static cDevice *nextParentDevice;
++ ///< Holds the parent device for the next subdevice
++ ///< so the dynamite-plugin can work with unpatched plugins
++ bool isIdle;
++protected:
++ cDevice *parentDevice;
++ cDevice *subDevice;
++ cDevice(cDevice *ParentDevice = NULL);
++ const cPatPmtParser *PatPmtParser(void) const { if (parentDevice) return parentDevice->PatPmtParser(); return &patPmtParser; }
++ ///< Returns a pointer to the patPmtParser, so that a derived device
++ ///< can use the stream information from it.
++public:
++ bool IsPrimaryDevice(void) const { if (parentDevice) return parentDevice->IsPrimaryDevice(); return this == primaryDevice; }
++ int CardIndex(void) const { if (parentDevice) return parentDevice->cardIndex; return cardIndex; }
++ ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
++ cCamSlot *CamSlot(void) const { if (parentDevice) return parentDevice->CamSlot(); return camSlot; }
++ ///< Returns the CAM slot that is currently used with this device,
++ ///< or NULL if no CAM slot is in use.
++ bool IsSubDevice(void) const { return (parentDevice != NULL); }
++ bool HasSubDevice(void) const { return (subDevice != NULL); }
++ cDevice *SubDevice(void) const { return subDevice; }
++ bool IsIdle(void) const { if (parentDevice) return parentDevice->IsIdle(); return isIdle; }
++ bool SetIdle(bool Idle);
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly) { return false; }
++ ///< Called by SetIdle
++ ///< if TestOnly, don't do anything, just return, if the device
++ ///< can be set to the new idle state
++ // --- dynamite subdevice patch end ---
+ };
+
+ /// Derived cDevice classes that can receive channels will have to provide
+@@ -812,7 +836,14 @@ public:
+ /// sure the returned data points to a TS packet and automatically
+ /// re-synchronizes after broken packets.
+
+-class cTSBuffer : public cThread {
++class cTSBufferBase {
++public:
++ cTSBufferBase() {}
++ virtual ~cTSBufferBase() {}
++ virtual uchar *Get(void) = 0;
++ };
++
++class cTSBuffer : public cTSBufferBase, public cThread {
+ private:
+ int f;
+ int cardIndex;
+@@ -821,8 +852,51 @@ private:
+ virtual void Action(void);
+ public:
+ cTSBuffer(int File, int Size, int CardIndex);
+- ~cTSBuffer();
+- uchar *Get(void);
++ virtual ~cTSBuffer();
++ virtual uchar *Get(void);
++ };
++
++/// A plugin that want to create devices handled by the dynamite-plugin needs to create
++/// a cDynamicDeviceProbe derived object on the heap in order to have its Probe()
++/// function called, where it can actually create the appropriate device.
++/// The cDynamicDeviceProbe object must be created in the plugin's constructor,
++/// and deleted in its destructor.
++/// The "DevPath" hasn't to be a physical device or a path in the filesystem.
++/// It can be any string a plugin may react on.
++
++#define __DYNAMIC_DEVICE_PROBE
++
++enum eDynamicDeviceProbeCommand { ddpcAttach, ddpcDetach, ddpcService };
++
++class cDynamicDeviceProbe : public cListObject {
++ friend class cDynamicDevice;
++private:
++ class cDynamicDeviceProbeItem : public cListObject {
++ public:
++ eDynamicDeviceProbeCommand cmd;
++ cString *devpath;
++ cDynamicDeviceProbeItem(eDynamicDeviceProbeCommand Cmd, cString *DevPath):cmd(Cmd),devpath(DevPath) {}
++ virtual ~cDynamicDeviceProbeItem() { if (devpath) delete devpath; }
++ };
++ static cList<cDynamicDeviceProbeItem> commandQueue;
++ ///< A list where all attach/detach commands are queued
++ ///< so they can be processed in the MainThreadHook of
++ ///< the dynamite plugin.
++public:
++ static void QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath);
++ ///< Plugins which support cDynamicDeviceProbe must use this function
++ ///< to queue the devices they normally create in their Initialize method.
++ ///< These devices are created as subdevices in the Start-method of the dynamite-plugin.
++ cDynamicDeviceProbe(void);
++ virtual ~cDynamicDeviceProbe();
++ virtual cDevice *Attach(cDevice *ParentDevice, const char *DevPath) = 0;
++ ///< Probes for a device at the given device-path like /dev/dvb/adapter0/frontend0
++ ///< or /dev/video0 etc. and creates the appropriate
++ ///< object derived from cDevice if applicable.
++ ///< Returns the device that has been created or NULL if not.
++ ///< The dynamite-plugin will delete the device if it is detached.
+ };
+
++extern cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
+ #endif //__DEVICE_H
+diff --git a/dvbci.c b/dvbci.c
+index 5289bbd..baa70bc 100644
+--- a/dvbci.c
++++ b/dvbci.c
+@@ -10,41 +10,70 @@
+ #include "dvbci.h"
+ #include <linux/dvb/ca.h>
+ #include <sys/ioctl.h>
+-#include "device.h"
++#include "dvbdevice.h"
+
+ // --- cDvbCiAdapter ---------------------------------------------------------
+
+-cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
++cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
+ {
+ 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());
++ adapter = Adapter;
++ frontend = Frontend;
++ idle = false;
++ GetNumCamSlots(Device, Fd, this);
++ Start();
+ }
+
+ cDvbCiAdapter::~cDvbCiAdapter()
+ {
+ Cancel(3);
++ if (device->IsSubDevice() || device->HasSubDevice())
++ CloseCa();
++}
++
++bool cDvbCiAdapter::OpenCa(void)
++{
++ if (fd >= 0)
++ return true;
++ fd = cDvbDevice::DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
++ return (fd >= 0);
++}
++
++void cDvbCiAdapter::CloseCa(void)
++{
++ if (fd < 0)
++ return;
++ close(fd);
++ fd = -1;
++}
++
++bool cDvbCiAdapter::SetIdle(bool Idle, bool TestOnly)
++{
++ if ((adapter < 0) || (frontend < 0))
++ return false;
++ if (TestOnly || (idle == Idle))
++ return true;
++ if (Idle)
++ CloseCa();
++ else
++ OpenCa();
++ idle = Idle;
++ return true;
++}
++
++cTSBufferBase *cDvbCiAdapter::GetTSBuffer(int FdDvr)
++{
++ if (device)
++ return new cTSBuffer(FdDvr, MEGABYTE(5), device->CardIndex() + 1);
++ return NULL;
+ }
+
+ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+ {
++ if (idle || (fd < 0))
++ return 0;
+ if (Buffer && MaxLength > 0) {
+ struct pollfd pfd[1];
+ pfd[0].fd = fd;
+@@ -61,6 +90,8 @@ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+
+ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+ {
++ if (idle || (fd < 0))
++ return;
+ 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());
+@@ -69,6 +100,8 @@ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+
+ bool cDvbCiAdapter::Reset(int Slot)
+ {
++ if (idle || (fd < 0))
++ return false;
+ if (ioctl(fd, CA_RESET, 1 << Slot) != -1)
+ return true;
+ else
+@@ -78,6 +111,8 @@ bool cDvbCiAdapter::Reset(int Slot)
+
+ eModuleStatus cDvbCiAdapter::ModuleStatus(int Slot)
+ {
++ if (idle || (fd < 0))
++ return msNone;
+ ca_slot_info_t sinfo;
+ sinfo.num = Slot;
+ if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) {
+@@ -99,10 +134,60 @@ bool cDvbCiAdapter::Assign(cDevice *Device, bool Query)
+ return true;
+ }
+
+-cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd)
++int cDvbCiAdapter::GetNumCamSlots(cDevice *Device, int Fd, cCiAdapter *CiAdapter)
+ {
+- // TODO check whether a CI is actually present?
+- if (Device)
+- return new cDvbCiAdapter(Device, Fd);
+- return NULL;
++ int NumSlots = -1;
++ if (Fd >= 0) {
++ ca_caps_t Caps;
++ if (ioctl(Fd, CA_GET_CAP, &Caps) == 0) {
++ if ((Caps.slot_type & CA_CI_LINK) != 0) {
++ NumSlots = Caps.slot_num;
++ if (NumSlots == 0)
++ esyslog("ERROR: no CAM slots found on device %d", Device->DeviceNumber());
++ else if (CiAdapter != NULL) {
++ for (int i = 0; i < NumSlots; i++)
++ new cCamSlot(CiAdapter);
++ }
++ else
++ return NumSlots;
++ }
++ 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());
++ }
++ return -1;
++}
++
++cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
++{
++ // don't create a ci-adapter if it's not useable
++ if (Device && (Fd >= 0) && (GetNumCamSlots(Device, Fd, NULL) > 0))
++ return new cDvbCiAdapter(Device, Fd, Adapter, Frontend);
++
++ if (Fd >= 0)
++ close(Fd);
++
++ // try to find an external ci-adapter
++ for (cDvbCiAdapterProbe *cp = DvbCiAdapterProbes.First(); cp; cp = DvbCiAdapterProbes.Next(cp)) {
++ cDvbCiAdapter *ca = cp->Probe(Device);
++ if (ca)
++ return ca;
++ }
++ return NULL;
++}
++
++// --- cDvbCiAdapterProbe -------------------------------------------------------
++
++cList<cDvbCiAdapterProbe> DvbCiAdapterProbes;
++
++cDvbCiAdapterProbe::cDvbCiAdapterProbe(void)
++{
++ DvbCiAdapterProbes.Add(this);
++}
++
++cDvbCiAdapterProbe::~cDvbCiAdapterProbe()
++{
++ DvbCiAdapterProbes.Del(this, false);
+ }
+diff --git a/dvbci.h b/dvbci.h
+index adbe40d..d908b2f 100644
+--- a/dvbci.h
++++ b/dvbci.h
+@@ -16,16 +16,48 @@ class cDvbCiAdapter : public cCiAdapter {
+ private:
+ cDevice *device;
+ int fd;
++ int adapter;
++ int frontend;
++ bool idle;
++
++ bool OpenCa(void);
++ void CloseCa(void);
+ 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);
++ cDvbCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
+ public:
+ virtual ~cDvbCiAdapter();
+- static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd);
++ virtual cTSBufferBase *GetTSBuffer(int FdDvr);
++ static int GetNumCamSlots(cDevice *Device, int Fd, cCiAdapter *CiAdapter);
++ ///< Tests if the CA device is usable for vdr.
++ ///< If CiAdapter is not NULL it will create the CamSlots for the given ci-adapter.
++ virtual bool SetIdle(bool Idle, bool TestOnly);
++ virtual bool IsIdle(void) const { return idle; }
++ static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
++ };
++
++// A plugin that implements an external DVB ci-adapter derived from cDvbCiAdapter needs to create
++// a cDvbCiAdapterProbe derived object on the heap in order to have its Probe()
++// function called, where it can actually create the appropriate ci-adapter.
++// The cDvbCiAdapterProbe object must be created in the plugin's constructor,
++// and deleted in its destructor.
++// Every plugin has to track its own list of already used device nodes.
++// The Probes are always called if the base cDvbCiAdapter can't create a ci-adapter on its own.
++
++class cDvbCiAdapterProbe : public cListObject {
++public:
++ cDvbCiAdapterProbe(void);
++ virtual ~cDvbCiAdapterProbe();
++ virtual cDvbCiAdapter *Probe(cDevice *Device) = 0;
++ ///< Probes for a DVB ci-adapter for the given Device and creates the appropriate
++ ///< object derived from cDvbCiAdapter if applicable.
++ ///< Returns NULL if no adapter has been created.
+ };
+
++extern cList<cDvbCiAdapterProbe> DvbCiAdapterProbes;
++
+ #endif //__DVBCI_H
+diff --git a/dvbdevice.c b/dvbdevice.c
+index ab6149f..351f1ed 100644
+--- a/dvbdevice.c
++++ b/dvbdevice.c
+@@ -288,9 +288,10 @@ class cDvbTuner : public cThread {
+ private:
+ static cMutex bondMutex;
+ enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked };
++ bool SendDiseqc;
+ int frontendType;
+ const cDvbDevice *device;
+- int fd_frontend;
++ mutable int fd_frontend;
+ int adapter, frontend;
+ uint32_t subsystemId;
+ int tuneTimeout;
+@@ -301,7 +302,7 @@ private:
+ const cScr *scr;
+ bool lnbPowerTurnedOn;
+ eTunerStatus tunerStatus;
+- cMutex mutex;
++ mutable cMutex mutex;
+ cCondVar locked;
+ cCondVar newSet;
+ cDvbTuner *bondedTuner;
+@@ -311,11 +312,16 @@ private:
+ cDvbTuner *GetBondedMaster(void);
+ bool IsBondedMaster(void) const { return !bondedTuner || bondedMaster; }
+ void ClearEventQueue(void) const;
++ dvb_diseqc_master_cmd diseqc_cmd;
+ bool GetFrontendStatus(fe_status_t &Status) const;
+ void ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency);
+ void ResetToneAndVoltage(void);
+ bool SetFrontend(void);
+ virtual void Action(void);
++
++ mutable bool isIdle;
++ bool OpenFrontend(void) const;
++ bool CloseFrontend(void);
+ public:
+ cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend);
+ virtual ~cDvbTuner();
+@@ -327,9 +333,13 @@ public:
+ uint32_t SubsystemId(void) const { return subsystemId; }
+ bool IsTunedTo(const cChannel *Channel) const;
+ void SetChannel(const cChannel *Channel);
++ bool SendDiseqcCmd(dvb_diseqc_master_cmd cmd);
+ bool Locked(int TimeoutMs = 0);
+ int GetSignalStrength(void) const;
+ int GetSignalQuality(void) const;
++
++ bool SetIdle(bool Idle);
++ bool IsIdle(void) const { return isIdle; }
+ };
+
+ cMutex cDvbTuner::bondMutex;
+@@ -339,6 +349,7 @@ cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int
+ frontendType = SYS_UNDEFINED;
+ device = Device;
+ fd_frontend = Fd_Frontend;
++ SendDiseqc=false;
+ adapter = Adapter;
+ frontend = Frontend;
+ subsystemId = cDvbDeviceProbe::GetSubsystemId(adapter, frontend);
+@@ -351,6 +362,7 @@ cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int
+ tunerStatus = tsIdle;
+ bondedTuner = NULL;
+ bondedMaster = false;
++ isIdle = false;
+ SetDescription("tuner on frontend %d/%d", adapter, frontend);
+ Start();
+ }
+@@ -368,6 +380,8 @@ cDvbTuner::~cDvbTuner()
+ ExecuteDiseqc(lastDiseqc, &Frequency);
+ }
+ */
++ if (device && device->IsSubDevice())
++ CloseFrontend();
+ }
+
+ bool cDvbTuner::Bond(cDvbTuner *Tuner)
+@@ -512,6 +526,8 @@ bool cDvbTuner::Locked(int TimeoutMs)
+
+ void cDvbTuner::ClearEventQueue(void) const
+ {
++ if (!OpenFrontend())
++ return;
+ cPoller Poller(fd_frontend);
+ if (Poller.Poll(TUNER_POLL_TIMEOUT)) {
+ dvb_frontend_event Event;
+@@ -720,8 +736,28 @@ static int GetRequiredDeliverySystem(const cChannel *Channel, const cDvbTranspon
+ return ds;
+ }
+
++bool cDvbTuner::SendDiseqcCmd(dvb_diseqc_master_cmd cmd)
++{
++ cMutexLock MutexLock(&mutex);
++ cDvbTransponderParameters dtp(channel.Parameters());
++
++ // Determine the required frontend type:
++ int frontendType = GetRequiredDeliverySystem(&channel, &dtp);
++
++ if ((frontendType!=SYS_DVBS2 && frontendType!=SYS_DVBS) || SendDiseqc)
++ return false;
++ if (!OpenFrontend())
++ return false;
++ diseqc_cmd=cmd;
++ SendDiseqc=true;
++ newSet.Broadcast();
++ return true;
++}
++
+ bool cDvbTuner::SetFrontend(void)
+ {
++ if (!OpenFrontend())
++ return false;
+ #define MAXFRONTENDCMDS 16
+ #define SETCMD(c, d) { Frontend[CmdSeq.num].cmd = (c);\
+ Frontend[CmdSeq.num].u.data = (d);\
+@@ -868,10 +904,16 @@ void cDvbTuner::Action(void)
+ bool LostLock = false;
+ fe_status_t Status = (fe_status_t)0;
+ while (Running()) {
+- fe_status_t NewStatus;
+- if (GetFrontendStatus(NewStatus))
+- Status = NewStatus;
++ if (!isIdle) {
++ fe_status_t NewStatus;
++ if (GetFrontendStatus(NewStatus))
++ Status = NewStatus;
++ }
+ cMutexLock MutexLock(&mutex);
++ if (SendDiseqc) {
++ CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &diseqc_cmd));
++ SendDiseqc=false;
++ }
+ int WaitTime = 1000;
+ switch (tunerStatus) {
+ case tsIdle:
+@@ -923,6 +965,40 @@ void cDvbTuner::Action(void)
+ }
+ }
+
++bool cDvbTuner::SetIdle(bool Idle)
++{
++ if (isIdle == Idle)
++ return true;
++ isIdle = Idle;
++ if (Idle)
++ return CloseFrontend();
++ return OpenFrontend();
++}
++
++bool cDvbTuner::OpenFrontend(void) const
++{
++ if (fd_frontend >= 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ fd_frontend = cDvbDevice::DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK);
++ if (fd_frontend < 0)
++ return false;
++ isIdle = false;
++ return true;
++}
++
++bool cDvbTuner::CloseFrontend(void)
++{
++ if (fd_frontend < 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ tunerStatus = tsIdle;
++ newSet.Broadcast();
++ close(fd_frontend);
++ fd_frontend = -1;
++ return true;
++}
++
+ // --- cDvbSourceParam -------------------------------------------------------
+
+ class cDvbSourceParam : public cSourceParam {
+@@ -1008,7 +1084,8 @@ const char *DeliverySystemNames[] = {
+ NULL
+ };
+
+-cDvbDevice::cDvbDevice(int Adapter, int Frontend)
++cDvbDevice::cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice)
++:cDevice(ParentDevice)
+ {
+ adapter = Adapter;
+ frontend = Frontend;
+@@ -1026,9 +1103,8 @@ cDvbDevice::cDvbDevice(int Adapter, int Frontend)
+
+ // Common Interface:
+
+- fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
+- if (fd_ca >= 0)
+- ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca);
++ int fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
++ ciAdapter = cDvbCiAdapter::CreateCiAdapter(parentDevice ? parentDevice : this, fd_ca, adapter, frontend);
+
+ // The DVR device (will be opened and closed as needed):
+
+@@ -1256,7 +1332,11 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ if (d >= 0) {
+ int ErrorDevice = 0;
+ if (cDevice *Device1 = cDevice::GetDevice(i)) {
++ if (Device1->HasSubDevice())
++ Device1 = Device1->SubDevice();
+ if (cDevice *Device2 = cDevice::GetDevice(d)) {
++ if (Device2->HasSubDevice())
++ Device2 = Device2->SubDevice();
+ if (cDvbDevice *DvbDevice1 = dynamic_cast<cDvbDevice *>(Device1)) {
+ if (cDvbDevice *DvbDevice2 = dynamic_cast<cDvbDevice *>(Device2)) {
+ if (!DvbDevice1->Bond(DvbDevice2))
+@@ -1290,7 +1370,10 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ void cDvbDevice::UnBondDevices(void)
+ {
+ for (int i = 0; i < cDevice::NumDevices(); i++) {
+- if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(cDevice::GetDevice(i)))
++ cDevice *dev = cDevice::GetDevice(i);
++ if (dev && dev->HasSubDevice())
++ dev = dev->SubDevice();
++ if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(dev))
+ d->UnBond();
+ }
+ }
+@@ -1344,6 +1427,26 @@ bool cDvbDevice::BondingOk(const cChannel *Channel, bool ConsiderOccupied) const
+ return true;
+ }
+
++bool cDvbDevice::SetIdleDevice(bool Idle, bool TestOnly)
++{
++ if (TestOnly) {
++ if (ciAdapter)
++ return ciAdapter->SetIdle(Idle, true);
++ return true;
++ }
++ if (!dvbTuner->SetIdle(Idle))
++ return false;
++ if (ciAdapter && !ciAdapter->SetIdle(Idle, false)) {
++ dvbTuner->SetIdle(!Idle);
++ return false;
++ }
++ if (Idle)
++ StopSectionHandler();
++ else
++ StartSectionHandler();
++ return true;
++}
++
+ bool cDvbDevice::HasCi(void)
+ {
+ return ciAdapter;
+@@ -1513,7 +1616,7 @@ bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne
+
+ bool cDvbDevice::ProvidesEIT(void) const
+ {
+- return dvbTuner != NULL;
++ return !IsIdle() && (dvbTuner != NULL) && !dvbTuner->IsIdle() && ((ciAdapter == NULL) || !ciAdapter->IsIdle());
+ }
+
+ int cDvbDevice::NumProvidedSystems(void) const
+@@ -1558,6 +1661,11 @@ bool cDvbDevice::HasLock(int TimeoutMs) const
+ return dvbTuner ? dvbTuner->Locked(TimeoutMs) : false;
+ }
+
++bool cDvbDevice::SendDiseqcCmd(dvb_diseqc_master_cmd cmd)
++{
++ return dvbTuner->SendDiseqcCmd(cmd);
++}
++
+ void cDvbDevice::SetTransferModeForDolbyDigital(int Mode)
+ {
+ setTransferModeForDolbyDigital = Mode;
+@@ -1567,8 +1675,12 @@ bool cDvbDevice::OpenDvr(void)
+ {
+ CloseDvr();
+ fd_dvr = DvbOpen(DEV_DVB_DVR, adapter, frontend, O_RDONLY | O_NONBLOCK, true);
+- if (fd_dvr >= 0)
+- tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(5), CardIndex() + 1);
++ if (fd_dvr >= 0) {
++ if (ciAdapter)
++ tsBuffer = ciAdapter->GetTSBuffer(fd_dvr);
++ if (tsBuffer == NULL)
++ tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(5), CardIndex() + 1);
++ }
+ return fd_dvr >= 0;
+ }
+
+diff --git a/dvbdevice.h b/dvbdevice.h
+index b4d07f5..339c52b 100644
+--- a/dvbdevice.h
++++ b/dvbdevice.h
+@@ -107,7 +107,7 @@ class cDvbTuner;
+ /// The cDvbDevice implements a DVB device which can be accessed through the Linux DVB driver API.
+
+ class cDvbDevice : public cDevice {
+-protected:
++public:
+ static cString DvbName(const char *Name, int Adapter, int Frontend);
+ static int DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError = false);
+ private:
+@@ -127,19 +127,20 @@ private:
+ int deliverySystems[MAXDELIVERYSYSTEMS];
+ int numDeliverySystems;
+ int numModulations;
+- int fd_dvr, fd_ca;
++ int fd_dvr;
+ static cMutex bondMutex;
+ cDvbDevice *bondedDevice;
+ mutable bool needsDetachBondedReceivers;
+ bool QueryDeliverySystems(int fd_frontend);
+ public:
+- cDvbDevice(int Adapter, int Frontend);
++ cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice = NULL);
+ virtual ~cDvbDevice();
+ int Adapter(void) const { return adapter; }
+ int Frontend(void) const { return frontend; }
+ virtual bool Ready(void);
+ virtual cString DeviceType(void) const;
+ virtual cString DeviceName(void) const;
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly);
+ static bool BondDevices(const char *Bondings);
+ ///< Bonds the devices as defined in the given Bondings string.
+ ///< A bonding is a sequence of device numbers (starting at 1),
+@@ -193,6 +194,7 @@ protected:
+ virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
+ public:
+ virtual bool HasLock(int TimeoutMs = 0) const;
++ virtual bool SendDiseqcCmd(dvb_diseqc_master_cmd cmd);
+
+ // PID handle facilities
+
+@@ -225,7 +227,7 @@ public:
+ // Receiver facilities
+
+ private:
+- cTSBuffer *tsBuffer;
++ cTSBufferBase *tsBuffer;
+ protected:
+ virtual bool OpenDvr(void);
+ virtual void CloseDvr(void);
diff --git a/patches/vdr-2.0.0-dynamite+externalci.patch b/patches/vdr-2.0.0-dynamite+externalci.patch
new file mode 100644
index 0000000..dd421af
--- /dev/null
+++ b/patches/vdr-2.0.0-dynamite+externalci.patch
@@ -0,0 +1,988 @@
+diff --git a/ci.c b/ci.c
+index 9a4a829..88b50e7 100644
+--- a/ci.c
++++ b/ci.c
+@@ -1571,6 +1571,8 @@ cCamSlot::cCamSlot(cCiAdapter *CiAdapter)
+
+ cCamSlot::~cCamSlot()
+ {
++ if (ciAdapter && ciAdapter->assignedDevice)
++ ciAdapter->assignedDevice->SetCamSlot(NULL);
+ CamSlots.Del(this, false);
+ DeleteAllConnections();
+ }
+diff --git a/ci.h b/ci.h
+index 74e0270..818ea29 100644
+--- a/ci.h
++++ b/ci.h
+@@ -72,6 +72,7 @@ public:
+ };
+
+ class cDevice;
++class cTSBufferBase;
+ class cCamSlot;
+
+ enum eModuleStatus { msNone, msReset, msPresent, msReady };
+@@ -115,6 +116,13 @@ public:
+ ///< 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.
++#define EXTERNALCI_PATCH
++ virtual cTSBufferBase *GetTSBuffer(int FdDvr) { return NULL; }
++ ///< Derived classes can return a special TS buffer with features
++ ///< like rerouting the stream through an external ci.
++ ///< The caller must delete the buffer.
++ virtual bool SetIdle(bool Idle, bool TestOnly) { return false; }
++ virtual bool IsIdle(void) const { return false; }
+ };
+
+ class cTPDU;
+diff --git a/device.c b/device.c
+index ff51a11..dfdc081 100644
+--- a/device.c
++++ b/device.c
+@@ -69,12 +69,22 @@ int cDevice::currentChannel = 1;
+ cDevice *cDevice::device[MAXDEVICES] = { NULL };
+ cDevice *cDevice::primaryDevice = NULL;
+ cList<cDeviceHook> cDevice::deviceHooks;
+-
+-cDevice::cDevice(void)
++cDevice *cDevice::nextParentDevice = NULL;
++
++cDevice::cDevice(cDevice *ParentDevice)
+ :patPmtParser(true)
+-{
+- cardIndex = nextCardIndex++;
+- dsyslog("new device number %d", CardIndex() + 1);
++,isIdle(false)
++,parentDevice(ParentDevice)
++,subDevice(NULL)
++{
++ if (!ParentDevice)
++ parentDevice = nextParentDevice;
++ cDevice::nextParentDevice = NULL;
++ if (parentDevice)
++ cardIndex = parentDevice->cardIndex;
++ else
++ cardIndex = nextCardIndex++;
++ dsyslog("new %sdevice number %d", parentDevice ? "sub-" : "", CardIndex() + 1);
+
+ SetDescription("receiver on device %d", CardIndex() + 1);
+
+@@ -106,10 +116,14 @@ cDevice::cDevice(void)
+ for (int i = 0; i < MAXRECEIVERS; i++)
+ receiver[i] = NULL;
+
+- if (numDevices < MAXDEVICES)
+- device[numDevices++] = this;
++ if (!parentDevice) {
++ if (numDevices < MAXDEVICES)
++ device[numDevices++] = this;
++ else
++ esyslog("ERROR: too many devices or \"dynamite\"-unpatched device creator!");
++ }
+ else
+- esyslog("ERROR: too many devices!");
++ parentDevice->subDevice = this;
+ }
+
+ cDevice::~cDevice()
+@@ -118,6 +132,29 @@ cDevice::~cDevice()
+ DetachAllReceivers();
+ delete liveSubtitle;
+ delete dvbSubtitleConverter;
++ if (parentDevice && (parentDevice->subDevice == this))
++ parentDevice->subDevice = NULL;
++}
++
++bool cDevice::SetIdle(bool Idle)
++{
++ if (parentDevice)
++ return parentDevice->SetIdle(Idle);
++ if (isIdle == Idle)
++ return true;
++ if (Receiving(false))
++ return false;
++ if (Idle) {
++ Detach(player);
++ DetachAllReceivers();
++ }
++ if (!SetIdleDevice(Idle, true))
++ return false;
++ isIdle = Idle;
++ if (SetIdleDevice(Idle, false))
++ return true;
++ isIdle = !Idle;
++ return false;
+ }
+
+ bool cDevice::WaitForAllDevicesReady(int Timeout)
+@@ -156,6 +193,8 @@ int cDevice::NextCardIndex(int n)
+
+ int cDevice::DeviceNumber(void) const
+ {
++ if (parentDevice)
++ return parentDevice->DeviceNumber();
+ for (int i = 0; i < numDevices; i++) {
+ if (device[i] == this)
+ return i;
+@@ -356,6 +395,8 @@ bool cDevice::HasCi(void)
+
+ void cDevice::SetCamSlot(cCamSlot *CamSlot)
+ {
++ if (parentDevice)
++ return parentDevice->SetCamSlot(CamSlot);
+ camSlot = CamSlot;
+ }
+
+@@ -568,6 +609,10 @@ void cDevice::DelLivePids(void)
+
+ void cDevice::StartSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StartSectionHandler();
++ return;
++ }
+ if (!sectionHandler) {
+ sectionHandler = new cSectionHandler(this);
+ AttachFilter(eitFilter = new cEitFilter);
+@@ -579,6 +624,10 @@ void cDevice::StartSectionHandler(void)
+
+ void cDevice::StopSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StopSectionHandler();
++ return;
++ }
+ if (sectionHandler) {
+ delete nitFilter;
+ delete sdtFilter;
+@@ -610,12 +659,17 @@ void cDevice::CloseFilter(int Handle)
+
+ void cDevice::AttachFilter(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->AttachFilter(Filter);
++ SetIdle(false);
+ if (sectionHandler)
+ sectionHandler->Attach(Filter);
+ }
+
+ void cDevice::Detach(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Filter);
+ if (sectionHandler)
+ sectionHandler->Detach(Filter);
+ }
+@@ -777,6 +831,7 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
+ sectionHandler->SetStatus(false);
+ sectionHandler->SetChannel(NULL);
+ }
++ SetIdle(false);
+ // Tell the camSlot about the channel switch and add all PIDs of this
+ // channel to it, for possible later decryption:
+ if (camSlot)
+@@ -823,19 +878,27 @@ void cDevice::ForceTransferMode(void)
+ {
+ if (!cTransferControl::ReceiverDevice()) {
+ cChannel *Channel = Channels.GetByNumber(CurrentChannel());
+- if (Channel)
++ if (Channel) {
++ SetIdle(false);
+ SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
++ }
+ }
+ }
+
+ int cDevice::Occupied(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Occupied();
+ int Seconds = occupiedTimeout - time(NULL);
+ return Seconds > 0 ? Seconds : 0;
+ }
+
+ void cDevice::SetOccupied(int Seconds)
+ {
++ if (parentDevice) {
++ parentDevice->SetOccupied(Seconds);
++ return;
++ }
+ if (Seconds >= 0)
+ occupiedTimeout = time(NULL) + min(Seconds, MAXOCCUPIEDTIMEOUT);
+ }
+@@ -1216,7 +1279,10 @@ bool cDevice::Transferring(void) const
+
+ bool cDevice::AttachPlayer(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->AttachPlayer(Player);
+ if (CanReplay()) {
++ SetIdle(false);
+ if (player)
+ Detach(player);
+ DELETENULL(liveSubtitle);
+@@ -1235,6 +1301,8 @@ bool cDevice::AttachPlayer(cPlayer *Player)
+
+ void cDevice::Detach(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Player);
+ if (Player && player == Player) {
+ cPlayer *p = player;
+ player = NULL; // avoids recursive calls to Detach()
+@@ -1254,6 +1322,8 @@ void cDevice::Detach(cPlayer *Player)
+
+ void cDevice::StopReplay(void)
+ {
++ if (parentDevice)
++ return parentDevice->StopReplay();
+ if (player) {
+ Detach(player);
+ if (IsPrimaryDevice())
+@@ -1534,6 +1604,8 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
+
+ int cDevice::Priority(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Priority();
+ int priority = IDLEPRIORITY;
+ if (IsPrimaryDevice() && !Replaying() && HasProgramme())
+ priority = TRANSFERPRIORITY; // we use the same value here, no matter whether it's actual Transfer Mode or real live viewing
+@@ -1552,6 +1624,8 @@ bool cDevice::Ready(void)
+
+ bool cDevice::Receiving(bool Dummy) const
+ {
++ if (parentDevice)
++ return parentDevice->Receiving(Dummy);
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+ if (receiver[i])
+@@ -1632,10 +1706,13 @@ bool cDevice::GetTSPacket(uchar *&Data)
+
+ bool cDevice::AttachReceiver(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->AttachReceiver(Receiver);
+ if (!Receiver)
+ return false;
+ if (Receiver->device == this)
+ return true;
++ SetIdle(false);
+ // activate the following line if you need it - actually the driver should be fixed!
+ //#define WAIT_FOR_TUNER_LOCK
+ #ifdef WAIT_FOR_TUNER_LOCK
+@@ -1674,6 +1751,8 @@ bool cDevice::AttachReceiver(cReceiver *Receiver)
+
+ void cDevice::Detach(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Receiver);
+ if (!Receiver || Receiver->device != this)
+ return;
+ bool receiversLeft = false;
+@@ -1699,6 +1778,8 @@ void cDevice::Detach(cReceiver *Receiver)
+
+ void cDevice::DetachAll(int Pid)
+ {
++ if (parentDevice)
++ return parentDevice->DetachAll(Pid);
+ if (Pid) {
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+@@ -1783,3 +1864,25 @@ uchar *cTSBuffer::Get(void)
+ }
+ return NULL;
+ }
++
++// --- cDynamicDeviceProbe -------------------------------------------------------
++
++cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
++cList<cDynamicDeviceProbe::cDynamicDeviceProbeItem> cDynamicDeviceProbe::commandQueue;
++
++void cDynamicDeviceProbe::QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath)
++{
++ if (DevPath)
++ commandQueue.Add(new cDynamicDeviceProbeItem(Cmd, new cString(DevPath)));
++}
++
++cDynamicDeviceProbe::cDynamicDeviceProbe(void)
++{
++ DynamicDeviceProbes.Add(this);
++}
++
++cDynamicDeviceProbe::~cDynamicDeviceProbe()
++{
++ DynamicDeviceProbes.Del(this, false);
++}
+diff --git a/device.h b/device.h
+index fd010d4..95acccd 100644
+--- a/device.h
++++ b/device.h
+@@ -169,7 +169,6 @@ private:
+ static int nextCardIndex;
+ int cardIndex;
+ protected:
+- cDevice(void);
+ virtual ~cDevice();
+ virtual bool Ready(void);
+ ///< Returns true if this device is ready. Devices with conditional
+@@ -196,9 +195,6 @@ protected:
+ ///< A derived class must call the MakePrimaryDevice() function of its
+ ///< base class.
+ public:
+- bool IsPrimaryDevice(void) const { return this == primaryDevice; }
+- int CardIndex(void) const { return cardIndex; }
+- ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
+ int DeviceNumber(void) const;
+ ///< Returns the number of this device (0 ... numDevices).
+ virtual cString DeviceType(void) const;
+@@ -423,9 +419,6 @@ public:
+ ///< shall check whether the channel can be decrypted.
+ 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
+
+@@ -586,9 +579,6 @@ private:
+ cTsToPes tsToPesSubtitle;
+ bool isPlayingVideo;
+ protected:
+- const cPatPmtParser *PatPmtParser(void) const { return &patPmtParser; }
+- ///< Returns a pointer to the patPmtParser, so that a derived device
+- ///< can use the stream information from it.
+ virtual bool CanReplay(void) const;
+ ///< Returns true if this device can currently start a replay session.
+ virtual bool SetPlayMode(ePlayMode PlayMode);
+@@ -802,6 +792,38 @@ public:
+ ///< Detaches all receivers from this device for this pid.
+ virtual void DetachAllReceivers(void);
+ ///< Detaches all receivers from this device.
++
++// --- dynamite subdevice patch start ---
++ friend class cDynamicDevice;
++private:
++ static cDevice *nextParentDevice;
++ ///< Holds the parent device for the next subdevice
++ ///< so the dynamite-plugin can work with unpatched plugins
++ bool isIdle;
++protected:
++ cDevice *parentDevice;
++ cDevice *subDevice;
++ cDevice(cDevice *ParentDevice = NULL);
++ const cPatPmtParser *PatPmtParser(void) const { if (parentDevice) return parentDevice->PatPmtParser(); return &patPmtParser; }
++ ///< Returns a pointer to the patPmtParser, so that a derived device
++ ///< can use the stream information from it.
++public:
++ bool IsPrimaryDevice(void) const { if (parentDevice) return parentDevice->IsPrimaryDevice(); return this == primaryDevice; }
++ int CardIndex(void) const { if (parentDevice) return parentDevice->cardIndex; return cardIndex; }
++ ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
++ cCamSlot *CamSlot(void) const { if (parentDevice) return parentDevice->CamSlot(); return camSlot; }
++ ///< Returns the CAM slot that is currently used with this device,
++ ///< or NULL if no CAM slot is in use.
++ bool IsSubDevice(void) const { return (parentDevice != NULL); }
++ bool HasSubDevice(void) const { return (subDevice != NULL); }
++ cDevice *SubDevice(void) const { return subDevice; }
++ bool IsIdle(void) const { if (parentDevice) return parentDevice->IsIdle(); return isIdle; }
++ bool SetIdle(bool Idle);
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly) { return false; }
++ ///< Called by SetIdle
++ ///< if TestOnly, don't do anything, just return, if the device
++ ///< can be set to the new idle state
++ // --- dynamite subdevice patch end ---
+ };
+
+ /// Derived cDevice classes that can receive channels will have to provide
+@@ -812,7 +834,14 @@ public:
+ /// sure the returned data points to a TS packet and automatically
+ /// re-synchronizes after broken packets.
+
+-class cTSBuffer : public cThread {
++class cTSBufferBase {
++public:
++ cTSBufferBase() {}
++ virtual ~cTSBufferBase() {}
++ virtual uchar *Get(void) = 0;
++ };
++
++class cTSBuffer : public cTSBufferBase, public cThread {
+ private:
+ int f;
+ int cardIndex;
+@@ -821,8 +850,51 @@ private:
+ virtual void Action(void);
+ public:
+ cTSBuffer(int File, int Size, int CardIndex);
+- ~cTSBuffer();
+- uchar *Get(void);
++ virtual ~cTSBuffer();
++ virtual uchar *Get(void);
++ };
++
++/// A plugin that want to create devices handled by the dynamite-plugin needs to create
++/// a cDynamicDeviceProbe derived object on the heap in order to have its Probe()
++/// function called, where it can actually create the appropriate device.
++/// The cDynamicDeviceProbe object must be created in the plugin's constructor,
++/// and deleted in its destructor.
++/// The "DevPath" hasn't to be a physical device or a path in the filesystem.
++/// It can be any string a plugin may react on.
++
++#define __DYNAMIC_DEVICE_PROBE
++
++enum eDynamicDeviceProbeCommand { ddpcAttach, ddpcDetach, ddpcService };
++
++class cDynamicDeviceProbe : public cListObject {
++ friend class cDynamicDevice;
++private:
++ class cDynamicDeviceProbeItem : public cListObject {
++ public:
++ eDynamicDeviceProbeCommand cmd;
++ cString *devpath;
++ cDynamicDeviceProbeItem(eDynamicDeviceProbeCommand Cmd, cString *DevPath):cmd(Cmd),devpath(DevPath) {}
++ virtual ~cDynamicDeviceProbeItem() { if (devpath) delete devpath; }
++ };
++ static cList<cDynamicDeviceProbeItem> commandQueue;
++ ///< A list where all attach/detach commands are queued
++ ///< so they can be processed in the MainThreadHook of
++ ///< the dynamite plugin.
++public:
++ static void QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath);
++ ///< Plugins which support cDynamicDeviceProbe must use this function
++ ///< to queue the devices they normally create in their Initialize method.
++ ///< These devices are created as subdevices in the Start-method of the dynamite-plugin.
++ cDynamicDeviceProbe(void);
++ virtual ~cDynamicDeviceProbe();
++ virtual cDevice *Attach(cDevice *ParentDevice, const char *DevPath) = 0;
++ ///< Probes for a device at the given device-path like /dev/dvb/adapter0/frontend0
++ ///< or /dev/video0 etc. and creates the appropriate
++ ///< object derived from cDevice if applicable.
++ ///< Returns the device that has been created or NULL if not.
++ ///< The dynamite-plugin will delete the device if it is detached.
+ };
+
++extern cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
+ #endif //__DEVICE_H
+diff --git a/dvbci.c b/dvbci.c
+index 5289bbd..baa70bc 100644
+--- a/dvbci.c
++++ b/dvbci.c
+@@ -10,41 +10,70 @@
+ #include "dvbci.h"
+ #include <linux/dvb/ca.h>
+ #include <sys/ioctl.h>
+-#include "device.h"
++#include "dvbdevice.h"
+
+ // --- cDvbCiAdapter ---------------------------------------------------------
+
+-cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
++cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
+ {
+ 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());
++ adapter = Adapter;
++ frontend = Frontend;
++ idle = false;
++ GetNumCamSlots(Device, Fd, this);
++ Start();
+ }
+
+ cDvbCiAdapter::~cDvbCiAdapter()
+ {
+ Cancel(3);
++ if (device->IsSubDevice() || device->HasSubDevice())
++ CloseCa();
++}
++
++bool cDvbCiAdapter::OpenCa(void)
++{
++ if (fd >= 0)
++ return true;
++ fd = cDvbDevice::DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
++ return (fd >= 0);
++}
++
++void cDvbCiAdapter::CloseCa(void)
++{
++ if (fd < 0)
++ return;
++ close(fd);
++ fd = -1;
++}
++
++bool cDvbCiAdapter::SetIdle(bool Idle, bool TestOnly)
++{
++ if ((adapter < 0) || (frontend < 0))
++ return false;
++ if (TestOnly || (idle == Idle))
++ return true;
++ if (Idle)
++ CloseCa();
++ else
++ OpenCa();
++ idle = Idle;
++ return true;
++}
++
++cTSBufferBase *cDvbCiAdapter::GetTSBuffer(int FdDvr)
++{
++ if (device)
++ return new cTSBuffer(FdDvr, MEGABYTE(5), device->CardIndex() + 1);
++ return NULL;
+ }
+
+ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+ {
++ if (idle || (fd < 0))
++ return 0;
+ if (Buffer && MaxLength > 0) {
+ struct pollfd pfd[1];
+ pfd[0].fd = fd;
+@@ -61,6 +90,8 @@ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+
+ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+ {
++ if (idle || (fd < 0))
++ return;
+ 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());
+@@ -69,6 +100,8 @@ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+
+ bool cDvbCiAdapter::Reset(int Slot)
+ {
++ if (idle || (fd < 0))
++ return false;
+ if (ioctl(fd, CA_RESET, 1 << Slot) != -1)
+ return true;
+ else
+@@ -78,6 +111,8 @@ bool cDvbCiAdapter::Reset(int Slot)
+
+ eModuleStatus cDvbCiAdapter::ModuleStatus(int Slot)
+ {
++ if (idle || (fd < 0))
++ return msNone;
+ ca_slot_info_t sinfo;
+ sinfo.num = Slot;
+ if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) {
+@@ -99,10 +134,60 @@ bool cDvbCiAdapter::Assign(cDevice *Device, bool Query)
+ return true;
+ }
+
+-cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd)
++int cDvbCiAdapter::GetNumCamSlots(cDevice *Device, int Fd, cCiAdapter *CiAdapter)
+ {
+- // TODO check whether a CI is actually present?
+- if (Device)
+- return new cDvbCiAdapter(Device, Fd);
+- return NULL;
++ int NumSlots = -1;
++ if (Fd >= 0) {
++ ca_caps_t Caps;
++ if (ioctl(Fd, CA_GET_CAP, &Caps) == 0) {
++ if ((Caps.slot_type & CA_CI_LINK) != 0) {
++ NumSlots = Caps.slot_num;
++ if (NumSlots == 0)
++ esyslog("ERROR: no CAM slots found on device %d", Device->DeviceNumber());
++ else if (CiAdapter != NULL) {
++ for (int i = 0; i < NumSlots; i++)
++ new cCamSlot(CiAdapter);
++ }
++ else
++ return NumSlots;
++ }
++ 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());
++ }
++ return -1;
++}
++
++cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
++{
++ // don't create a ci-adapter if it's not useable
++ if (Device && (Fd >= 0) && (GetNumCamSlots(Device, Fd, NULL) > 0))
++ return new cDvbCiAdapter(Device, Fd, Adapter, Frontend);
++
++ if (Fd >= 0)
++ close(Fd);
++
++ // try to find an external ci-adapter
++ for (cDvbCiAdapterProbe *cp = DvbCiAdapterProbes.First(); cp; cp = DvbCiAdapterProbes.Next(cp)) {
++ cDvbCiAdapter *ca = cp->Probe(Device);
++ if (ca)
++ return ca;
++ }
++ return NULL;
++}
++
++// --- cDvbCiAdapterProbe -------------------------------------------------------
++
++cList<cDvbCiAdapterProbe> DvbCiAdapterProbes;
++
++cDvbCiAdapterProbe::cDvbCiAdapterProbe(void)
++{
++ DvbCiAdapterProbes.Add(this);
++}
++
++cDvbCiAdapterProbe::~cDvbCiAdapterProbe()
++{
++ DvbCiAdapterProbes.Del(this, false);
+ }
+diff --git a/dvbci.h b/dvbci.h
+index adbe40d..d908b2f 100644
+--- a/dvbci.h
++++ b/dvbci.h
+@@ -16,16 +16,48 @@ class cDvbCiAdapter : public cCiAdapter {
+ private:
+ cDevice *device;
+ int fd;
++ int adapter;
++ int frontend;
++ bool idle;
++
++ bool OpenCa(void);
++ void CloseCa(void);
+ 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);
++ cDvbCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
+ public:
+ virtual ~cDvbCiAdapter();
+- static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd);
++ virtual cTSBufferBase *GetTSBuffer(int FdDvr);
++ static int GetNumCamSlots(cDevice *Device, int Fd, cCiAdapter *CiAdapter);
++ ///< Tests if the CA device is usable for vdr.
++ ///< If CiAdapter is not NULL it will create the CamSlots for the given ci-adapter.
++ virtual bool SetIdle(bool Idle, bool TestOnly);
++ virtual bool IsIdle(void) const { return idle; }
++ static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
++ };
++
++// A plugin that implements an external DVB ci-adapter derived from cDvbCiAdapter needs to create
++// a cDvbCiAdapterProbe derived object on the heap in order to have its Probe()
++// function called, where it can actually create the appropriate ci-adapter.
++// The cDvbCiAdapterProbe object must be created in the plugin's constructor,
++// and deleted in its destructor.
++// Every plugin has to track its own list of already used device nodes.
++// The Probes are always called if the base cDvbCiAdapter can't create a ci-adapter on its own.
++
++class cDvbCiAdapterProbe : public cListObject {
++public:
++ cDvbCiAdapterProbe(void);
++ virtual ~cDvbCiAdapterProbe();
++ virtual cDvbCiAdapter *Probe(cDevice *Device) = 0;
++ ///< Probes for a DVB ci-adapter for the given Device and creates the appropriate
++ ///< object derived from cDvbCiAdapter if applicable.
++ ///< Returns NULL if no adapter has been created.
+ };
+
++extern cList<cDvbCiAdapterProbe> DvbCiAdapterProbes;
++
+ #endif //__DVBCI_H
+diff --git a/dvbdevice.c b/dvbdevice.c
+index ab6149f..11e9fd8 100644
+--- a/dvbdevice.c
++++ b/dvbdevice.c
+@@ -290,7 +290,7 @@ private:
+ enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked };
+ int frontendType;
+ const cDvbDevice *device;
+- int fd_frontend;
++ mutable int fd_frontend;
+ int adapter, frontend;
+ uint32_t subsystemId;
+ int tuneTimeout;
+@@ -301,7 +301,7 @@ private:
+ const cScr *scr;
+ bool lnbPowerTurnedOn;
+ eTunerStatus tunerStatus;
+- cMutex mutex;
++ mutable cMutex mutex;
+ cCondVar locked;
+ cCondVar newSet;
+ cDvbTuner *bondedTuner;
+@@ -316,6 +316,10 @@ private:
+ void ResetToneAndVoltage(void);
+ bool SetFrontend(void);
+ virtual void Action(void);
++
++ mutable bool isIdle;
++ bool OpenFrontend(void) const;
++ bool CloseFrontend(void);
+ public:
+ cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend);
+ virtual ~cDvbTuner();
+@@ -330,6 +334,9 @@ public:
+ bool Locked(int TimeoutMs = 0);
+ int GetSignalStrength(void) const;
+ int GetSignalQuality(void) const;
++
++ bool SetIdle(bool Idle);
++ bool IsIdle(void) const { return isIdle; }
+ };
+
+ cMutex cDvbTuner::bondMutex;
+@@ -351,6 +358,7 @@ cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int
+ tunerStatus = tsIdle;
+ bondedTuner = NULL;
+ bondedMaster = false;
++ isIdle = false;
+ SetDescription("tuner on frontend %d/%d", adapter, frontend);
+ Start();
+ }
+@@ -368,6 +376,8 @@ cDvbTuner::~cDvbTuner()
+ ExecuteDiseqc(lastDiseqc, &Frequency);
+ }
+ */
++ if (device && device->IsSubDevice())
++ CloseFrontend();
+ }
+
+ bool cDvbTuner::Bond(cDvbTuner *Tuner)
+@@ -512,6 +522,8 @@ bool cDvbTuner::Locked(int TimeoutMs)
+
+ void cDvbTuner::ClearEventQueue(void) const
+ {
++ if (!OpenFrontend())
++ return;
+ cPoller Poller(fd_frontend);
+ if (Poller.Poll(TUNER_POLL_TIMEOUT)) {
+ dvb_frontend_event Event;
+@@ -722,6 +734,8 @@ static int GetRequiredDeliverySystem(const cChannel *Channel, const cDvbTranspon
+
+ bool cDvbTuner::SetFrontend(void)
+ {
++ if (!OpenFrontend())
++ return false;
+ #define MAXFRONTENDCMDS 16
+ #define SETCMD(c, d) { Frontend[CmdSeq.num].cmd = (c);\
+ Frontend[CmdSeq.num].u.data = (d);\
+@@ -868,9 +882,11 @@ void cDvbTuner::Action(void)
+ bool LostLock = false;
+ fe_status_t Status = (fe_status_t)0;
+ while (Running()) {
+- fe_status_t NewStatus;
+- if (GetFrontendStatus(NewStatus))
+- Status = NewStatus;
++ if (!isIdle) {
++ fe_status_t NewStatus;
++ if (GetFrontendStatus(NewStatus))
++ Status = NewStatus;
++ }
+ cMutexLock MutexLock(&mutex);
+ int WaitTime = 1000;
+ switch (tunerStatus) {
+@@ -923,6 +939,40 @@ void cDvbTuner::Action(void)
+ }
+ }
+
++bool cDvbTuner::SetIdle(bool Idle)
++{
++ if (isIdle == Idle)
++ return true;
++ isIdle = Idle;
++ if (Idle)
++ return CloseFrontend();
++ return OpenFrontend();
++}
++
++bool cDvbTuner::OpenFrontend(void) const
++{
++ if (fd_frontend >= 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ fd_frontend = cDvbDevice::DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK);
++ if (fd_frontend < 0)
++ return false;
++ isIdle = false;
++ return true;
++}
++
++bool cDvbTuner::CloseFrontend(void)
++{
++ if (fd_frontend < 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ tunerStatus = tsIdle;
++ newSet.Broadcast();
++ close(fd_frontend);
++ fd_frontend = -1;
++ return true;
++}
++
+ // --- cDvbSourceParam -------------------------------------------------------
+
+ class cDvbSourceParam : public cSourceParam {
+@@ -1008,7 +1058,8 @@ const char *DeliverySystemNames[] = {
+ NULL
+ };
+
+-cDvbDevice::cDvbDevice(int Adapter, int Frontend)
++cDvbDevice::cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice)
++:cDevice(ParentDevice)
+ {
+ adapter = Adapter;
+ frontend = Frontend;
+@@ -1026,9 +1077,8 @@ cDvbDevice::cDvbDevice(int Adapter, int Frontend)
+
+ // Common Interface:
+
+- fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
+- if (fd_ca >= 0)
+- ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca);
++ int fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
++ ciAdapter = cDvbCiAdapter::CreateCiAdapter(parentDevice ? parentDevice : this, fd_ca, adapter, frontend);
+
+ // The DVR device (will be opened and closed as needed):
+
+@@ -1256,7 +1306,11 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ if (d >= 0) {
+ int ErrorDevice = 0;
+ if (cDevice *Device1 = cDevice::GetDevice(i)) {
++ if (Device1->HasSubDevice())
++ Device1 = Device1->SubDevice();
+ if (cDevice *Device2 = cDevice::GetDevice(d)) {
++ if (Device2->HasSubDevice())
++ Device2 = Device2->SubDevice();
+ if (cDvbDevice *DvbDevice1 = dynamic_cast<cDvbDevice *>(Device1)) {
+ if (cDvbDevice *DvbDevice2 = dynamic_cast<cDvbDevice *>(Device2)) {
+ if (!DvbDevice1->Bond(DvbDevice2))
+@@ -1290,7 +1344,10 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ void cDvbDevice::UnBondDevices(void)
+ {
+ for (int i = 0; i < cDevice::NumDevices(); i++) {
+- if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(cDevice::GetDevice(i)))
++ cDevice *dev = cDevice::GetDevice(i);
++ if (dev && dev->HasSubDevice())
++ dev = dev->SubDevice();
++ if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(dev))
+ d->UnBond();
+ }
+ }
+@@ -1344,6 +1401,26 @@ bool cDvbDevice::BondingOk(const cChannel *Channel, bool ConsiderOccupied) const
+ return true;
+ }
+
++bool cDvbDevice::SetIdleDevice(bool Idle, bool TestOnly)
++{
++ if (TestOnly) {
++ if (ciAdapter)
++ return ciAdapter->SetIdle(Idle, true);
++ return true;
++ }
++ if (!dvbTuner->SetIdle(Idle))
++ return false;
++ if (ciAdapter && !ciAdapter->SetIdle(Idle, false)) {
++ dvbTuner->SetIdle(!Idle);
++ return false;
++ }
++ if (Idle)
++ StopSectionHandler();
++ else
++ StartSectionHandler();
++ return true;
++}
++
+ bool cDvbDevice::HasCi(void)
+ {
+ return ciAdapter;
+@@ -1513,7 +1590,7 @@ bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne
+
+ bool cDvbDevice::ProvidesEIT(void) const
+ {
+- return dvbTuner != NULL;
++ return !IsIdle() && (dvbTuner != NULL) && !dvbTuner->IsIdle() && ((ciAdapter == NULL) || !ciAdapter->IsIdle());
+ }
+
+ int cDvbDevice::NumProvidedSystems(void) const
+@@ -1567,8 +1644,12 @@ bool cDvbDevice::OpenDvr(void)
+ {
+ CloseDvr();
+ fd_dvr = DvbOpen(DEV_DVB_DVR, adapter, frontend, O_RDONLY | O_NONBLOCK, true);
+- if (fd_dvr >= 0)
+- tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(5), CardIndex() + 1);
++ if (fd_dvr >= 0) {
++ if (ciAdapter)
++ tsBuffer = ciAdapter->GetTSBuffer(fd_dvr);
++ if (tsBuffer == NULL)
++ tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(5), CardIndex() + 1);
++ }
+ return fd_dvr >= 0;
+ }
+
+diff --git a/dvbdevice.h b/dvbdevice.h
+index b4d07f5..f66720d 100644
+--- a/dvbdevice.h
++++ b/dvbdevice.h
+@@ -107,7 +107,7 @@ class cDvbTuner;
+ /// The cDvbDevice implements a DVB device which can be accessed through the Linux DVB driver API.
+
+ class cDvbDevice : public cDevice {
+-protected:
++public:
+ static cString DvbName(const char *Name, int Adapter, int Frontend);
+ static int DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError = false);
+ private:
+@@ -127,19 +127,20 @@ private:
+ int deliverySystems[MAXDELIVERYSYSTEMS];
+ int numDeliverySystems;
+ int numModulations;
+- int fd_dvr, fd_ca;
++ int fd_dvr;
+ static cMutex bondMutex;
+ cDvbDevice *bondedDevice;
+ mutable bool needsDetachBondedReceivers;
+ bool QueryDeliverySystems(int fd_frontend);
+ public:
+- cDvbDevice(int Adapter, int Frontend);
++ cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice = NULL);
+ virtual ~cDvbDevice();
+ int Adapter(void) const { return adapter; }
+ int Frontend(void) const { return frontend; }
+ virtual bool Ready(void);
+ virtual cString DeviceType(void) const;
+ virtual cString DeviceName(void) const;
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly);
+ static bool BondDevices(const char *Bondings);
+ ///< Bonds the devices as defined in the given Bondings string.
+ ///< A bonding is a sequence of device numbers (starting at 1),
+@@ -225,7 +226,7 @@ public:
+ // Receiver facilities
+
+ private:
+- cTSBuffer *tsBuffer;
++ cTSBufferBase *tsBuffer;
+ protected:
+ virtual bool OpenDvr(void);
+ virtual void CloseDvr(void);
diff --git a/patches/vdr-2.0.0-dynamite.patch b/patches/vdr-2.0.0-dynamite.patch
new file mode 100644
index 0000000..3782e05
--- /dev/null
+++ b/patches/vdr-2.0.0-dynamite.patch
@@ -0,0 +1,818 @@
+diff --git a/ci.c b/ci.c
+index 9a4a829..88b50e7 100644
+--- a/ci.c
++++ b/ci.c
+@@ -1571,6 +1571,8 @@ cCamSlot::cCamSlot(cCiAdapter *CiAdapter)
+
+ cCamSlot::~cCamSlot()
+ {
++ if (ciAdapter && ciAdapter->assignedDevice)
++ ciAdapter->assignedDevice->SetCamSlot(NULL);
+ CamSlots.Del(this, false);
+ DeleteAllConnections();
+ }
+diff --git a/ci.h b/ci.h
+index 74e0270..d38e2dd 100644
+--- a/ci.h
++++ b/ci.h
+@@ -115,6 +115,8 @@ public:
+ ///< 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.
++ virtual bool SetIdle(bool Idle, bool TestOnly) { return false; }
++ virtual bool IsIdle(void) const { return false; }
+ };
+
+ class cTPDU;
+diff --git a/device.c b/device.c
+index ff51a11..dfdc081 100644
+--- a/device.c
++++ b/device.c
+@@ -69,12 +69,22 @@ int cDevice::currentChannel = 1;
+ cDevice *cDevice::device[MAXDEVICES] = { NULL };
+ cDevice *cDevice::primaryDevice = NULL;
+ cList<cDeviceHook> cDevice::deviceHooks;
+-
+-cDevice::cDevice(void)
++cDevice *cDevice::nextParentDevice = NULL;
++
++cDevice::cDevice(cDevice *ParentDevice)
+ :patPmtParser(true)
+-{
+- cardIndex = nextCardIndex++;
+- dsyslog("new device number %d", CardIndex() + 1);
++,isIdle(false)
++,parentDevice(ParentDevice)
++,subDevice(NULL)
++{
++ if (!ParentDevice)
++ parentDevice = nextParentDevice;
++ cDevice::nextParentDevice = NULL;
++ if (parentDevice)
++ cardIndex = parentDevice->cardIndex;
++ else
++ cardIndex = nextCardIndex++;
++ dsyslog("new %sdevice number %d", parentDevice ? "sub-" : "", CardIndex() + 1);
+
+ SetDescription("receiver on device %d", CardIndex() + 1);
+
+@@ -106,10 +116,14 @@ cDevice::cDevice(void)
+ for (int i = 0; i < MAXRECEIVERS; i++)
+ receiver[i] = NULL;
+
+- if (numDevices < MAXDEVICES)
+- device[numDevices++] = this;
++ if (!parentDevice) {
++ if (numDevices < MAXDEVICES)
++ device[numDevices++] = this;
++ else
++ esyslog("ERROR: too many devices or \"dynamite\"-unpatched device creator!");
++ }
+ else
+- esyslog("ERROR: too many devices!");
++ parentDevice->subDevice = this;
+ }
+
+ cDevice::~cDevice()
+@@ -118,6 +132,29 @@ cDevice::~cDevice()
+ DetachAllReceivers();
+ delete liveSubtitle;
+ delete dvbSubtitleConverter;
++ if (parentDevice && (parentDevice->subDevice == this))
++ parentDevice->subDevice = NULL;
++}
++
++bool cDevice::SetIdle(bool Idle)
++{
++ if (parentDevice)
++ return parentDevice->SetIdle(Idle);
++ if (isIdle == Idle)
++ return true;
++ if (Receiving(false))
++ return false;
++ if (Idle) {
++ Detach(player);
++ DetachAllReceivers();
++ }
++ if (!SetIdleDevice(Idle, true))
++ return false;
++ isIdle = Idle;
++ if (SetIdleDevice(Idle, false))
++ return true;
++ isIdle = !Idle;
++ return false;
+ }
+
+ bool cDevice::WaitForAllDevicesReady(int Timeout)
+@@ -156,6 +193,8 @@ int cDevice::NextCardIndex(int n)
+
+ int cDevice::DeviceNumber(void) const
+ {
++ if (parentDevice)
++ return parentDevice->DeviceNumber();
+ for (int i = 0; i < numDevices; i++) {
+ if (device[i] == this)
+ return i;
+@@ -356,6 +395,8 @@ bool cDevice::HasCi(void)
+
+ void cDevice::SetCamSlot(cCamSlot *CamSlot)
+ {
++ if (parentDevice)
++ return parentDevice->SetCamSlot(CamSlot);
+ camSlot = CamSlot;
+ }
+
+@@ -568,6 +609,10 @@ void cDevice::DelLivePids(void)
+
+ void cDevice::StartSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StartSectionHandler();
++ return;
++ }
+ if (!sectionHandler) {
+ sectionHandler = new cSectionHandler(this);
+ AttachFilter(eitFilter = new cEitFilter);
+@@ -579,6 +624,10 @@ void cDevice::StartSectionHandler(void)
+
+ void cDevice::StopSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StopSectionHandler();
++ return;
++ }
+ if (sectionHandler) {
+ delete nitFilter;
+ delete sdtFilter;
+@@ -610,12 +659,17 @@ void cDevice::CloseFilter(int Handle)
+
+ void cDevice::AttachFilter(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->AttachFilter(Filter);
++ SetIdle(false);
+ if (sectionHandler)
+ sectionHandler->Attach(Filter);
+ }
+
+ void cDevice::Detach(cFilter *Filter)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Filter);
+ if (sectionHandler)
+ sectionHandler->Detach(Filter);
+ }
+@@ -777,6 +831,7 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
+ sectionHandler->SetStatus(false);
+ sectionHandler->SetChannel(NULL);
+ }
++ SetIdle(false);
+ // Tell the camSlot about the channel switch and add all PIDs of this
+ // channel to it, for possible later decryption:
+ if (camSlot)
+@@ -823,19 +878,27 @@ void cDevice::ForceTransferMode(void)
+ {
+ if (!cTransferControl::ReceiverDevice()) {
+ cChannel *Channel = Channels.GetByNumber(CurrentChannel());
+- if (Channel)
++ if (Channel) {
++ SetIdle(false);
+ SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
++ }
+ }
+ }
+
+ int cDevice::Occupied(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Occupied();
+ int Seconds = occupiedTimeout - time(NULL);
+ return Seconds > 0 ? Seconds : 0;
+ }
+
+ void cDevice::SetOccupied(int Seconds)
+ {
++ if (parentDevice) {
++ parentDevice->SetOccupied(Seconds);
++ return;
++ }
+ if (Seconds >= 0)
+ occupiedTimeout = time(NULL) + min(Seconds, MAXOCCUPIEDTIMEOUT);
+ }
+@@ -1216,7 +1279,10 @@ bool cDevice::Transferring(void) const
+
+ bool cDevice::AttachPlayer(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->AttachPlayer(Player);
+ if (CanReplay()) {
++ SetIdle(false);
+ if (player)
+ Detach(player);
+ DELETENULL(liveSubtitle);
+@@ -1235,6 +1301,8 @@ bool cDevice::AttachPlayer(cPlayer *Player)
+
+ void cDevice::Detach(cPlayer *Player)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Player);
+ if (Player && player == Player) {
+ cPlayer *p = player;
+ player = NULL; // avoids recursive calls to Detach()
+@@ -1254,6 +1322,8 @@ void cDevice::Detach(cPlayer *Player)
+
+ void cDevice::StopReplay(void)
+ {
++ if (parentDevice)
++ return parentDevice->StopReplay();
+ if (player) {
+ Detach(player);
+ if (IsPrimaryDevice())
+@@ -1534,6 +1604,8 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
+
+ int cDevice::Priority(void) const
+ {
++ if (parentDevice)
++ return parentDevice->Priority();
+ int priority = IDLEPRIORITY;
+ if (IsPrimaryDevice() && !Replaying() && HasProgramme())
+ priority = TRANSFERPRIORITY; // we use the same value here, no matter whether it's actual Transfer Mode or real live viewing
+@@ -1552,6 +1624,8 @@ bool cDevice::Ready(void)
+
+ bool cDevice::Receiving(bool Dummy) const
+ {
++ if (parentDevice)
++ return parentDevice->Receiving(Dummy);
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+ if (receiver[i])
+@@ -1632,10 +1706,13 @@ bool cDevice::GetTSPacket(uchar *&Data)
+
+ bool cDevice::AttachReceiver(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->AttachReceiver(Receiver);
+ if (!Receiver)
+ return false;
+ if (Receiver->device == this)
+ return true;
++ SetIdle(false);
+ // activate the following line if you need it - actually the driver should be fixed!
+ //#define WAIT_FOR_TUNER_LOCK
+ #ifdef WAIT_FOR_TUNER_LOCK
+@@ -1674,6 +1751,8 @@ bool cDevice::AttachReceiver(cReceiver *Receiver)
+
+ void cDevice::Detach(cReceiver *Receiver)
+ {
++ if (parentDevice)
++ return parentDevice->Detach(Receiver);
+ if (!Receiver || Receiver->device != this)
+ return;
+ bool receiversLeft = false;
+@@ -1699,6 +1778,8 @@ void cDevice::Detach(cReceiver *Receiver)
+
+ void cDevice::DetachAll(int Pid)
+ {
++ if (parentDevice)
++ return parentDevice->DetachAll(Pid);
+ if (Pid) {
+ cMutexLock MutexLock(&mutexReceiver);
+ for (int i = 0; i < MAXRECEIVERS; i++) {
+@@ -1783,3 +1864,25 @@ uchar *cTSBuffer::Get(void)
+ }
+ return NULL;
+ }
++
++// --- cDynamicDeviceProbe -------------------------------------------------------
++
++cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
++cList<cDynamicDeviceProbe::cDynamicDeviceProbeItem> cDynamicDeviceProbe::commandQueue;
++
++void cDynamicDeviceProbe::QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath)
++{
++ if (DevPath)
++ commandQueue.Add(new cDynamicDeviceProbeItem(Cmd, new cString(DevPath)));
++}
++
++cDynamicDeviceProbe::cDynamicDeviceProbe(void)
++{
++ DynamicDeviceProbes.Add(this);
++}
++
++cDynamicDeviceProbe::~cDynamicDeviceProbe()
++{
++ DynamicDeviceProbes.Del(this, false);
++}
+diff --git a/device.h b/device.h
+index fd010d4..3217e9d 100644
+--- a/device.h
++++ b/device.h
+@@ -169,7 +169,6 @@ private:
+ static int nextCardIndex;
+ int cardIndex;
+ protected:
+- cDevice(void);
+ virtual ~cDevice();
+ virtual bool Ready(void);
+ ///< Returns true if this device is ready. Devices with conditional
+@@ -196,9 +195,6 @@ protected:
+ ///< A derived class must call the MakePrimaryDevice() function of its
+ ///< base class.
+ public:
+- bool IsPrimaryDevice(void) const { return this == primaryDevice; }
+- int CardIndex(void) const { return cardIndex; }
+- ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
+ int DeviceNumber(void) const;
+ ///< Returns the number of this device (0 ... numDevices).
+ virtual cString DeviceType(void) const;
+@@ -423,9 +419,6 @@ public:
+ ///< shall check whether the channel can be decrypted.
+ 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
+
+@@ -586,9 +579,6 @@ private:
+ cTsToPes tsToPesSubtitle;
+ bool isPlayingVideo;
+ protected:
+- const cPatPmtParser *PatPmtParser(void) const { return &patPmtParser; }
+- ///< Returns a pointer to the patPmtParser, so that a derived device
+- ///< can use the stream information from it.
+ virtual bool CanReplay(void) const;
+ ///< Returns true if this device can currently start a replay session.
+ virtual bool SetPlayMode(ePlayMode PlayMode);
+@@ -802,6 +792,38 @@ public:
+ ///< Detaches all receivers from this device for this pid.
+ virtual void DetachAllReceivers(void);
+ ///< Detaches all receivers from this device.
++
++// --- dynamite subdevice patch start ---
++ friend class cDynamicDevice;
++private:
++ static cDevice *nextParentDevice;
++ ///< Holds the parent device for the next subdevice
++ ///< so the dynamite-plugin can work with unpatched plugins
++ bool isIdle;
++protected:
++ cDevice *parentDevice;
++ cDevice *subDevice;
++ cDevice(cDevice *ParentDevice = NULL);
++ const cPatPmtParser *PatPmtParser(void) const { if (parentDevice) return parentDevice->PatPmtParser(); return &patPmtParser; }
++ ///< Returns a pointer to the patPmtParser, so that a derived device
++ ///< can use the stream information from it.
++public:
++ bool IsPrimaryDevice(void) const { if (parentDevice) return parentDevice->IsPrimaryDevice(); return this == primaryDevice; }
++ int CardIndex(void) const { if (parentDevice) return parentDevice->cardIndex; return cardIndex; }
++ ///< Returns the card index of this device (0 ... MAXDEVICES - 1).
++ cCamSlot *CamSlot(void) const { if (parentDevice) return parentDevice->CamSlot(); return camSlot; }
++ ///< Returns the CAM slot that is currently used with this device,
++ ///< or NULL if no CAM slot is in use.
++ bool IsSubDevice(void) const { return (parentDevice != NULL); }
++ bool HasSubDevice(void) const { return (subDevice != NULL); }
++ cDevice *SubDevice(void) const { return subDevice; }
++ bool IsIdle(void) const { if (parentDevice) return parentDevice->IsIdle(); return isIdle; }
++ bool SetIdle(bool Idle);
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly) { return false; }
++ ///< Called by SetIdle
++ ///< if TestOnly, don't do anything, just return, if the device
++ ///< can be set to the new idle state
++ // --- dynamite subdevice patch end ---
+ };
+
+ /// Derived cDevice classes that can receive channels will have to provide
+@@ -825,4 +847,47 @@ public:
+ uchar *Get(void);
+ };
+
++/// A plugin that want to create devices handled by the dynamite-plugin needs to create
++/// a cDynamicDeviceProbe derived object on the heap in order to have its Probe()
++/// function called, where it can actually create the appropriate device.
++/// The cDynamicDeviceProbe object must be created in the plugin's constructor,
++/// and deleted in its destructor.
++/// The "DevPath" hasn't to be a physical device or a path in the filesystem.
++/// It can be any string a plugin may react on.
++
++#define __DYNAMIC_DEVICE_PROBE
++
++enum eDynamicDeviceProbeCommand { ddpcAttach, ddpcDetach, ddpcService };
++
++class cDynamicDeviceProbe : public cListObject {
++ friend class cDynamicDevice;
++private:
++ class cDynamicDeviceProbeItem : public cListObject {
++ public:
++ eDynamicDeviceProbeCommand cmd;
++ cString *devpath;
++ cDynamicDeviceProbeItem(eDynamicDeviceProbeCommand Cmd, cString *DevPath):cmd(Cmd),devpath(DevPath) {}
++ virtual ~cDynamicDeviceProbeItem() { if (devpath) delete devpath; }
++ };
++ static cList<cDynamicDeviceProbeItem> commandQueue;
++ ///< A list where all attach/detach commands are queued
++ ///< so they can be processed in the MainThreadHook of
++ ///< the dynamite plugin.
++public:
++ static void QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath);
++ ///< Plugins which support cDynamicDeviceProbe must use this function
++ ///< to queue the devices they normally create in their Initialize method.
++ ///< These devices are created as subdevices in the Start-method of the dynamite-plugin.
++ cDynamicDeviceProbe(void);
++ virtual ~cDynamicDeviceProbe();
++ virtual cDevice *Attach(cDevice *ParentDevice, const char *DevPath) = 0;
++ ///< Probes for a device at the given device-path like /dev/dvb/adapter0/frontend0
++ ///< or /dev/video0 etc. and creates the appropriate
++ ///< object derived from cDevice if applicable.
++ ///< Returns the device that has been created or NULL if not.
++ ///< The dynamite-plugin will delete the device if it is detached.
++ };
++
++extern cList<cDynamicDeviceProbe> DynamicDeviceProbes;
++
+ #endif //__DEVICE_H
+diff --git a/dvbci.c b/dvbci.c
+index 5289bbd..fea3a83 100644
+--- a/dvbci.c
++++ b/dvbci.c
+@@ -10,15 +10,18 @@
+ #include "dvbci.h"
+ #include <linux/dvb/ca.h>
+ #include <sys/ioctl.h>
+-#include "device.h"
++#include "dvbdevice.h"
+
+ // --- cDvbCiAdapter ---------------------------------------------------------
+
+-cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
++cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
+ {
+ device = Device;
+ SetDescription("CI adapter on device %d", device->DeviceNumber());
+ fd = Fd;
++ adapter = Adapter;
++ frontend = Frontend;
++ idle = false;
+ ca_caps_t Caps;
+ if (ioctl(fd, CA_GET_CAP, &Caps) == 0) {
+ if ((Caps.slot_type & CA_CI_LINK) != 0) {
+@@ -41,10 +44,44 @@ cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
+ cDvbCiAdapter::~cDvbCiAdapter()
+ {
+ Cancel(3);
++ if (device->IsSubDevice() || device->HasSubDevice())
++ CloseCa();
++}
++
++bool cDvbCiAdapter::OpenCa(void)
++{
++ if (fd >= 0)
++ return true;
++ fd = cDvbDevice::DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
++ return (fd >= 0);
++}
++
++void cDvbCiAdapter::CloseCa(void)
++{
++ if (fd < 0)
++ return;
++ close(fd);
++ fd = -1;
++}
++
++bool cDvbCiAdapter::SetIdle(bool Idle, bool TestOnly)
++{
++ if ((adapter < 0) || (frontend < 0))
++ return false;
++ if (TestOnly || (idle == Idle))
++ return true;
++ if (Idle)
++ CloseCa();
++ else
++ OpenCa();
++ idle = Idle;
++ return true;
+ }
+
+ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+ {
++ if (idle || (fd < 0))
++ return 0;
+ if (Buffer && MaxLength > 0) {
+ struct pollfd pfd[1];
+ pfd[0].fd = fd;
+@@ -61,6 +98,8 @@ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+
+ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+ {
++ if (idle || (fd < 0))
++ return;
+ 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());
+@@ -69,6 +108,8 @@ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
+
+ bool cDvbCiAdapter::Reset(int Slot)
+ {
++ if (idle || (fd < 0))
++ return false;
+ if (ioctl(fd, CA_RESET, 1 << Slot) != -1)
+ return true;
+ else
+@@ -78,6 +119,8 @@ bool cDvbCiAdapter::Reset(int Slot)
+
+ eModuleStatus cDvbCiAdapter::ModuleStatus(int Slot)
+ {
++ if (idle || (fd < 0))
++ return msNone;
+ ca_slot_info_t sinfo;
+ sinfo.num = Slot;
+ if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) {
+@@ -99,10 +142,10 @@ bool cDvbCiAdapter::Assign(cDevice *Device, bool Query)
+ return true;
+ }
+
+-cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd)
++cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend)
+ {
+ // TODO check whether a CI is actually present?
+ if (Device)
+- return new cDvbCiAdapter(Device, Fd);
++ return new cDvbCiAdapter(Device, Fd, Adapter, Frontend);
+ return NULL;
+ }
+diff --git a/dvbci.h b/dvbci.h
+index adbe40d..6d117b2 100644
+--- a/dvbci.h
++++ b/dvbci.h
+@@ -16,16 +16,24 @@ class cDvbCiAdapter : public cCiAdapter {
+ private:
+ cDevice *device;
+ int fd;
++ int adapter;
++ int frontend;
++ bool idle;
++
++ bool OpenCa(void);
++ void CloseCa(void);
+ 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);
++ cDvbCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
+ public:
+ virtual ~cDvbCiAdapter();
+- static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd);
++ virtual bool SetIdle(bool Idle, bool TestOnly);
++ virtual bool IsIdle(void) const { return idle; }
++ static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1);
+ };
+
+ #endif //__DVBCI_H
+diff --git a/dvbdevice.c b/dvbdevice.c
+index ab6149f..47cf585 100644
+--- a/dvbdevice.c
++++ b/dvbdevice.c
+@@ -290,7 +290,7 @@ private:
+ enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked };
+ int frontendType;
+ const cDvbDevice *device;
+- int fd_frontend;
++ mutable int fd_frontend;
+ int adapter, frontend;
+ uint32_t subsystemId;
+ int tuneTimeout;
+@@ -301,7 +301,7 @@ private:
+ const cScr *scr;
+ bool lnbPowerTurnedOn;
+ eTunerStatus tunerStatus;
+- cMutex mutex;
++ mutable cMutex mutex;
+ cCondVar locked;
+ cCondVar newSet;
+ cDvbTuner *bondedTuner;
+@@ -316,6 +316,10 @@ private:
+ void ResetToneAndVoltage(void);
+ bool SetFrontend(void);
+ virtual void Action(void);
++
++ mutable bool isIdle;
++ bool OpenFrontend(void) const;
++ bool CloseFrontend(void);
+ public:
+ cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend);
+ virtual ~cDvbTuner();
+@@ -330,6 +334,9 @@ public:
+ bool Locked(int TimeoutMs = 0);
+ int GetSignalStrength(void) const;
+ int GetSignalQuality(void) const;
++
++ bool SetIdle(bool Idle);
++ bool IsIdle(void) const { return isIdle; }
+ };
+
+ cMutex cDvbTuner::bondMutex;
+@@ -351,6 +358,7 @@ cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int
+ tunerStatus = tsIdle;
+ bondedTuner = NULL;
+ bondedMaster = false;
++ isIdle = false;
+ SetDescription("tuner on frontend %d/%d", adapter, frontend);
+ Start();
+ }
+@@ -368,6 +376,8 @@ cDvbTuner::~cDvbTuner()
+ ExecuteDiseqc(lastDiseqc, &Frequency);
+ }
+ */
++ if (device && device->IsSubDevice())
++ CloseFrontend();
+ }
+
+ bool cDvbTuner::Bond(cDvbTuner *Tuner)
+@@ -512,6 +522,8 @@ bool cDvbTuner::Locked(int TimeoutMs)
+
+ void cDvbTuner::ClearEventQueue(void) const
+ {
++ if (!OpenFrontend())
++ return;
+ cPoller Poller(fd_frontend);
+ if (Poller.Poll(TUNER_POLL_TIMEOUT)) {
+ dvb_frontend_event Event;
+@@ -722,6 +734,8 @@ static int GetRequiredDeliverySystem(const cChannel *Channel, const cDvbTranspon
+
+ bool cDvbTuner::SetFrontend(void)
+ {
++ if (!OpenFrontend())
++ return false;
+ #define MAXFRONTENDCMDS 16
+ #define SETCMD(c, d) { Frontend[CmdSeq.num].cmd = (c);\
+ Frontend[CmdSeq.num].u.data = (d);\
+@@ -868,9 +882,11 @@ void cDvbTuner::Action(void)
+ bool LostLock = false;
+ fe_status_t Status = (fe_status_t)0;
+ while (Running()) {
+- fe_status_t NewStatus;
+- if (GetFrontendStatus(NewStatus))
+- Status = NewStatus;
++ if (!isIdle) {
++ fe_status_t NewStatus;
++ if (GetFrontendStatus(NewStatus))
++ Status = NewStatus;
++ }
+ cMutexLock MutexLock(&mutex);
+ int WaitTime = 1000;
+ switch (tunerStatus) {
+@@ -923,6 +939,40 @@ void cDvbTuner::Action(void)
+ }
+ }
+
++bool cDvbTuner::SetIdle(bool Idle)
++{
++ if (isIdle == Idle)
++ return true;
++ isIdle = Idle;
++ if (Idle)
++ return CloseFrontend();
++ return OpenFrontend();
++}
++
++bool cDvbTuner::OpenFrontend(void) const
++{
++ if (fd_frontend >= 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ fd_frontend = cDvbDevice::DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK);
++ if (fd_frontend < 0)
++ return false;
++ isIdle = false;
++ return true;
++}
++
++bool cDvbTuner::CloseFrontend(void)
++{
++ if (fd_frontend < 0)
++ return true;
++ cMutexLock MutexLock(&mutex);
++ tunerStatus = tsIdle;
++ newSet.Broadcast();
++ close(fd_frontend);
++ fd_frontend = -1;
++ return true;
++}
++
+ // --- cDvbSourceParam -------------------------------------------------------
+
+ class cDvbSourceParam : public cSourceParam {
+@@ -1008,7 +1058,8 @@ const char *DeliverySystemNames[] = {
+ NULL
+ };
+
+-cDvbDevice::cDvbDevice(int Adapter, int Frontend)
++cDvbDevice::cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice)
++:cDevice(ParentDevice)
+ {
+ adapter = Adapter;
+ frontend = Frontend;
+@@ -1028,7 +1079,7 @@ cDvbDevice::cDvbDevice(int Adapter, int Frontend)
+
+ fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
+ if (fd_ca >= 0)
+- ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca);
++ ciAdapter = cDvbCiAdapter::CreateCiAdapter(parentDevice ? parentDevice : this, fd_ca, adapter, frontend);
+
+ // The DVR device (will be opened and closed as needed):
+
+@@ -1256,7 +1307,11 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ if (d >= 0) {
+ int ErrorDevice = 0;
+ if (cDevice *Device1 = cDevice::GetDevice(i)) {
++ if (Device1->HasSubDevice())
++ Device1 = Device1->SubDevice();
+ if (cDevice *Device2 = cDevice::GetDevice(d)) {
++ if (Device2->HasSubDevice())
++ Device2 = Device2->SubDevice();
+ if (cDvbDevice *DvbDevice1 = dynamic_cast<cDvbDevice *>(Device1)) {
+ if (cDvbDevice *DvbDevice2 = dynamic_cast<cDvbDevice *>(Device2)) {
+ if (!DvbDevice1->Bond(DvbDevice2))
+@@ -1290,7 +1345,10 @@ bool cDvbDevice::BondDevices(const char *Bondings)
+ void cDvbDevice::UnBondDevices(void)
+ {
+ for (int i = 0; i < cDevice::NumDevices(); i++) {
+- if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(cDevice::GetDevice(i)))
++ cDevice *dev = cDevice::GetDevice(i);
++ if (dev && dev->HasSubDevice())
++ dev = dev->SubDevice();
++ if (cDvbDevice *d = dynamic_cast<cDvbDevice *>(dev))
+ d->UnBond();
+ }
+ }
+@@ -1344,6 +1402,26 @@ bool cDvbDevice::BondingOk(const cChannel *Channel, bool ConsiderOccupied) const
+ return true;
+ }
+
++bool cDvbDevice::SetIdleDevice(bool Idle, bool TestOnly)
++{
++ if (TestOnly) {
++ if (ciAdapter)
++ return ciAdapter->SetIdle(Idle, true);
++ return true;
++ }
++ if (!dvbTuner->SetIdle(Idle))
++ return false;
++ if (ciAdapter && !ciAdapter->SetIdle(Idle, false)) {
++ dvbTuner->SetIdle(!Idle);
++ return false;
++ }
++ if (Idle)
++ StopSectionHandler();
++ else
++ StartSectionHandler();
++ return true;
++}
++
+ bool cDvbDevice::HasCi(void)
+ {
+ return ciAdapter;
+@@ -1513,7 +1591,7 @@ bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne
+
+ bool cDvbDevice::ProvidesEIT(void) const
+ {
+- return dvbTuner != NULL;
++ return !IsIdle() && (dvbTuner != NULL) && !dvbTuner->IsIdle() && ((ciAdapter == NULL) || !ciAdapter->IsIdle());
+ }
+
+ int cDvbDevice::NumProvidedSystems(void) const
+diff --git a/dvbdevice.h b/dvbdevice.h
+index b4d07f5..6f785ec 100644
+--- a/dvbdevice.h
++++ b/dvbdevice.h
+@@ -107,7 +107,7 @@ class cDvbTuner;
+ /// The cDvbDevice implements a DVB device which can be accessed through the Linux DVB driver API.
+
+ class cDvbDevice : public cDevice {
+-protected:
++public:
+ static cString DvbName(const char *Name, int Adapter, int Frontend);
+ static int DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError = false);
+ private:
+@@ -133,13 +133,14 @@ private:
+ mutable bool needsDetachBondedReceivers;
+ bool QueryDeliverySystems(int fd_frontend);
+ public:
+- cDvbDevice(int Adapter, int Frontend);
++ cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice = NULL);
+ virtual ~cDvbDevice();
+ int Adapter(void) const { return adapter; }
+ int Frontend(void) const { return frontend; }
+ virtual bool Ready(void);
+ virtual cString DeviceType(void) const;
+ virtual cString DeviceName(void) const;
++ virtual bool SetIdleDevice(bool Idle, bool TestOnly);
+ static bool BondDevices(const char *Bondings);
+ ///< Bonds the devices as defined in the given Bondings string.
+ ///< A bonding is a sequence of device numbers (starting at 1),