summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Hanisch <dvb@flensrocker.de>2011-02-02 14:18:45 +0100
committerLars Hanisch <dvb@flensrocker.de>2011-02-02 14:18:45 +0100
commitc03cb92fb43baab9136bd9122d757359e0590fda (patch)
treedbd9851e29274ba4b0189f3f859c1a29d20b8a88
downloadvdr-plugin-dynamite-c03cb92fb43baab9136bd9122d757359e0590fda.tar.gz
vdr-plugin-dynamite-c03cb92fb43baab9136bd9122d757359e0590fda.tar.bz2
initial commit of version 0.0.5c
-rw-r--r--COPYING340
-rw-r--r--HISTORY72
-rw-r--r--Makefile118
-rw-r--r--README156
-rw-r--r--dynamicdevice.c743
-rw-r--r--dynamicdevice.h127
-rw-r--r--dynamite.c341
-rw-r--r--monitor.c220
-rw-r--r--monitor.h58
-rw-r--r--patches/sc-1.0.0pre-subdevice.patch215
-rw-r--r--patches/vdr-1.7.16-dynamite-subdevice.patch361
-rw-r--r--udev.c110
-rw-r--r--udev.h41
13 files changed, 2902 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..f90922e
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..7ded11b
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,72 @@
+VDR Plugin 'dynamite' Revision History
+--------------------------------------
+
+2011-01-06: Version 0.0.1
+
+- Initial revision.
+
+(developing and releasing were fast the last days
+ so skipping some meaningless changes...)
+
+2011-01-08: Version 0.0.4a
+
+- rework patch for vdr to it won't collide with other patches
+- LSTD outputs an asterisk behind the number of the primary device
+- the number corresponds to the index in the array so it won't
+ change for one device if others are attached/detached.
+- DETD called with a number tries to detach the device at the
+ given position in the array.
+
+2011-01-09: Version 0.0.4b
+
+- protect device array with a mutex
+- add Service Interface "dynamite-AttachDevice-v0.1" and
+ "dynamite-DetachDevice-v0.1"
+ the pointer passed in "Data" is interpreted as a devpath like
+ the SVDRP command ATTD and DETD.
+ Returns always "true" on these commands even if Data is NULL
+ as suggested in the docs.
+- don't detach device which are receiving something with a
+ priority > 0.
+
+2011-01-09: Version 0.0.4c
+
+- add new commands for locking/unlocking devices so the can
+ be protected from accidently detaching (LCKD / UNLD)
+- extend the Service interface with these commands
+ "dynamite-LockDevice-v0.1" / "dynamite-UnlockDevice-v0.1"
+
+2011-01-11: Version 0.0.4e
+
+- add new command SCND
+
+2011-01-11: Version 0.0.4i
+
+- now it seems to work... :-)
+
+2011-01-27: Version 0.0.4j
+
+- add "GetTS" watchdog
+- detach player and transfer mode on detach so all handles are freed
+
+2011-01-27: Version 0.0.5
+
+- add udev monitor for auto attaching new dvb-frontends
+- you can use "dynamite --log-udev" on the vdr commandline for logging
+ lots of udev events
+
+2011-01-27: Version 0.0.5a
+
+- refactor udev wrapper
+
+2011-01-29: Version 0.0.5b
+
+- add new command "SDGT" for setting a default "GetTS"-timeout for all attached
+ and "to be attached" devices
+- add "dynamite.DefaultGetTSTimeout" to setup.conf
+- speed up dvb-device creation
+- add alternate (more readable) commands for SVDRP like "AttachDevice" for "ATTD"
+
+2011-01-30: Version 0.0.5c
+
+- correct LDFLAGS in Makefile
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f3f7775
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,118 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id$
+
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+# IMPORTANT: the presence of this macro is important for the Make.config
+# file. So it must be defined, even if it is not used here!
+#
+PLUGIN = dynamite
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g')
+
+### The C++ compiler and options:
+
+CXX ?= g++
+CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses
+LDFLAGS += -ludev
+
+### The directory environment:
+
+VDRDIR = ../../..
+LIBDIR = ../../lib
+TMPDIR = /tmp
+
+### Make sure that necessary options are included:
+
+include $(VDRDIR)/Make.global
+
+### Allow user defined options to overwrite defaults:
+
+-include $(VDRDIR)/Make.config
+
+### The version number of VDR's plugin API (taken from VDR's "config.h"):
+
+APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h)
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### Includes and Defines (add further entries here):
+
+INCLUDES += -I$(VDRDIR)/include
+
+DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+ifdef YAVDR_PATCHES
+DEFINE += -DYAVDRPATCHES
+endif
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o dynamicdevice.o monitor.o udev.o
+
+### The main target:
+
+all: libvdr-$(PLUGIN).so i18n
+
+### Implicit rules:
+
+%.o: %.c
+ $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $<
+
+### Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+ @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR = po
+LOCALEDIR = $(VDRDIR)/locale
+I18Npo = $(wildcard $(PODIR)/*.po)
+I18Nmsgs = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Npot = $(PODIR)/$(PLUGIN).pot
+
+%.mo: %.po
+ msgfmt -c -o $@ $<
+
+$(I18Npot): $(wildcard *.c)
+ xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='<see README>' -o $@ $^
+
+%.po: $(I18Npot)
+ msgmerge -U --no-wrap --no-location --backup=none -q $@ $<
+ @touch $@
+
+$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+ @mkdir -p $(dir $@)
+ cp $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmsgs) $(I18Npot)
+
+### Targets:
+
+libvdr-$(PLUGIN).so: $(OBJS)
+ $(CXX) $(CXXFLAGS) -shared $(OBJS) $(LDFLAGS) -o $@
+ @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
+
+dist: clean
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @mkdir $(TMPDIR)/$(ARCHIVE)
+ @cp -a * $(TMPDIR)/$(ARCHIVE)
+ @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+ @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot
diff --git a/README b/README
new file mode 100644
index 0000000..0b401ae
--- /dev/null
+++ b/README
@@ -0,0 +1,156 @@
+This is a "plugin" for the Video Disk Recorder (VDR).
+
+Written by: Lars Hanisch <dvb@flensrocker.de>
+
+Project's homepage: <not yet assigned>
+
+Latest version available at: http://www.vdr-portal.de/board/thread.php?threadid=102903
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+See the file COPYING for more information.
+
+Description
+-----------
+This plugin turns the dvbdevices into hotpluggable devices. They
+can be dynamically attached and detached while vdr is running.
+They are replaced with a "dumb device" which can receive nothing.
+Don't forget to patch the vdr-source, you find the necessary one
+in the "patches" directory of the plugin sources.
+
+How it works
+------------
+It creates as many devices as it can till the global vdr-device-array
+is full. After this plugin is loaded every device created by a
+plugin can't be used by vdr. So make sure dynamite is loaded as
+the last plugin.
+
+If a device is dynamically attached to vdr, this plugin creates an
+instance of the corresponding cDevice-subclass and plugs it into
+a free "dumb device" mentioned above. Now this device can do
+everything its subdevice can do.
+
+If a device is detached the corresponding subdevice is deleted and
+the "dumb device" is dumb again.
+
+A device with receivers at priorities > 0 can't be detached.
+
+How to make other plugins "explosive"
+-------------------------------------
+Like cDvbDeviceProbe there is a list of probe-objects where a plugin
+can hook into (see cDynamicDeviceProbe at the end of patched device.h).
+The Probe-function gets an devicepath and if the plugin can handle
+this it returns a pointer to a new created device on the heap.
+Don't forget to pass through "ParentDevice" parameter to the cDevice
+constructor!
+
+You don't need to remember this pointer, it will be deleted by dynamite.
+The devicepath doesn't need to be a valid path like
+/dev/dvb/adapter0/frontend0 or /dev/video0 etc. since the plugin decides
+what it should do with this string. In dynamite.c you can see an example
+of an dynamic dummy device creator. It reacts on the devicepath
+"dummydevice" followed by a number e.g. "dummydevice0".
+
+If a plugin wants its device to be dynamically attached/detached it must
+not create its devices in its Initialize function. Instead it should
+queue the found devicepathes with the helper function
+"cDynamicDeviceProbe::QueueDynamicDeviceCommand". After initialization
+the dynamite-plugin is calling every cDynamicDeviceProbe with the queued
+devicepathes in its Initialize-function. See the patches directory for examples.
+
+The devices have to close all their handles in their destructor and clean
+up after them. Otherwise this is a potential source of memory leaks.
+Plugins which creates a cDvbDeviceProbe should replace it with a
+cDynamicDeviceProbe (you can use "#ifdef __DYNAMIC_DEVICE_PROBE" for
+conditional compiling).
+
+How to attach/detach a device
+-----------------------------
+There a some new SVDRP commands for the dynamic devices. The string in quotes
+above the command is for internal Service-interface of the vdr-plugins.
+
+"dynamite-AttachDevice-v0.1"
+ATTD devicepath
+ Asks all cDynamicDeviceProbe-objects to attach the given devicepath
+ till one returns a valid pointer. You can control the order by the
+ order of the plugins you load
+ alternate command: AttachDevice
+
+"dynamite-DetachDevice-v0.1"
+DETD devicepath
+ Looks through its remembered devicepaths and deletes the attached
+ device if found. Case is important!
+ Any timeouts or locks set to this slot will be reset to its defaults.
+ alternate command: DetachDevice
+
+"dynamite-ScanDevices-v0.1"
+SCND '/dev/path/glob*/pattern*'
+ Scan filesystem with pattern and try to attach each found device
+ don't forget to enclose the pattern with single quotes
+ e.g. SCND '/dev/dvb/adapter*/frontend*'
+ alternate command: ScanDevices
+
+"dynamite-LockDevice-v0.1"
+LCKD /dev/path/to/device
+ Lock the device so it can't be detached
+ alternate command: LockDevice
+
+"dynamite-UnlockDevice-v0.1"
+UNLD /dev/path/to/device
+ Remove the lock of the device so it can be detached
+ alternate command: UnlockDevice
+
+(no Service-interface)
+LSTD
+ Lists all devices managed by this plugin. The first column is an id,
+ the second column is the devicepath passed with ATTD
+ The id can be used with the DETD and UNLD commands instead of the path.
+ An asterisk behind the id means that this device is locked and
+ can't be detached.
+ alternate command: ListDevices
+
+"dynamite-SetGetTSTimeout-v0.1"
+SGTT /dev/path/to/device seconds
+ Sets the \"GetTSPacket\"-watchdog timeout to specified amount of seconds
+ If the device returns no data (Data == NULL) for this period of time,
+ the device will be detached. Usefull if you want to reload the driver etc.
+ A value of 0 seconds disables the watchdog.
+ alternate command: SetGetTSTimeout
+
+"dynamite-SetDefaultGetTSTimeout-v0.1"
+SDGT seconds
+ Sets the \"GetTSPacket\"-watchdog timeout for all attached devices
+ and all devices that will be attached.
+ alternate command: SetDefaultGetTSTimeout
+
+Don't forget to prefix them with "plug dynamite"...
+
+Parameters in setup.conf
+------------------------
+dynamite.DefaultGetTSTimeout = 0
+
+"GetTS" watchdog
+----------------
+Some DVB cards are known to be unstable - sometimes their driver just hangs
+and vdr won't get any data anymore. Mostly you have to stop vdr, reload the
+drivers and restart vdr again. But that would affect other recordings etc.
+If you have such a card the "Auto-Detach" feature may be useful. Just set
+the "GetTS" timeout to 10 or 15 seconds (or whatever you like) and dynamite
+will automatically detach this device if its GetTSPacket method returns
+no data for this period of time.
+WARNING: You have to attach this device manually again! For now there's no
+automatism to reload driver (or whatever is needed) to reanimate the device.
+
+Known issues
+------------
+If a device managed by this plugin is the primary device it cannot be
+detached. That would imply that vdr searches for a new primary device
+or should be forced to transfer mode or something else. These
+circumstances are under research...
+
+TODO
+----
+* implement interface for other plugins to use the udev monitor
+* implement some OSD functionality for detaching, locking etc.
diff --git a/dynamicdevice.c b/dynamicdevice.c
new file mode 100644
index 0000000..f918b22
--- /dev/null
+++ b/dynamicdevice.c
@@ -0,0 +1,743 @@
+#include "dynamicdevice.h"
+#include <glob.h>
+#include <vdr/transfer.h>
+
+int cDynamicDevice::defaultGetTSTimeout = 0;
+cDvbDeviceProbe *cDynamicDevice::dvbprobe = NULL;
+int cDynamicDevice::numDynamicDevices = 0;
+cMutex cDynamicDevice::arrayMutex;
+cDynamicDevice *cDynamicDevice::dynamicdevice[MAXDEVICES] = { NULL };
+
+int cDynamicDevice::IndexOf(const char *DevPath, int &NextFreeIndex)
+{
+ cMutexLock lock(&arrayMutex);
+ NextFreeIndex = -1;
+ int index = -1;
+ for (int i = 0; ((index < 0) || (NextFreeIndex < 0)) && (i < numDynamicDevices); i++) {
+ if (dynamicdevice[i]->devpath == NULL) {
+ if (NextFreeIndex < 0)
+ NextFreeIndex = i;
+ }
+ else if (index < 0) {
+ if (strcmp(DevPath, **dynamicdevice[i]->devpath) == 0)
+ index = i;
+ }
+ }
+ return index;
+}
+
+bool cDynamicDevice::ProcessQueuedCommands(void)
+{
+ for (cDynamicDeviceProbe::cDynamicDeviceProbeItem *dev = cDynamicDeviceProbe::commandQueue.First(); dev; dev = cDynamicDeviceProbe::commandQueue.Next(dev)) {
+ switch (dev->cmd) {
+ case ddpcAttach:
+ {
+ AttachDevice(*dev->devpath);
+ break;
+ }
+ case ddpcDetach:
+ {
+ DetachDevice(*dev->devpath);
+ break;
+ }
+ }
+ }
+ cDynamicDeviceProbe::commandQueue.Clear();
+ return true;
+}
+
+void cDynamicDevice::DetachAllDevices(void)
+{
+ cMutexLock lock(&arrayMutex);
+ for (int i = 0; i < numDynamicDevices; i++)
+ dynamicdevice[i]->DeleteSubDevice();
+}
+
+cString cDynamicDevice::ListAllDevices(int &ReplyCode)
+{
+ cMutexLock lock(&arrayMutex);
+ cString devices;
+ int count = 0;
+ for (int i = 0; i < numDynamicDevices; i++) {
+ if ((dynamicdevice[i]->devpath != NULL) && (dynamicdevice[i]->subDevice != NULL)) {
+ count++;
+ devices = cString::sprintf("%s%d%s %s\n", (count == 1) ? "" : *devices
+ , i + 1
+ , ((PrimaryDevice() == dynamicdevice[i]) || !dynamicdevice[i]->isDetachable) ? "*" : ""
+ , **dynamicdevice[i]->devpath);
+ }
+ }
+ if (count == 0) {
+ ReplyCode = 901;
+ return cString::sprintf("there are no attached devices");
+ }
+ return devices;
+}
+
+cString cDynamicDevice::AttachDevicePattern(const char *Pattern)
+{
+ if (!Pattern)
+ return "invalid pattern";
+ cString reply;
+ glob_t result;
+ if (glob(Pattern, GLOB_MARK, 0, &result) == 0) {
+ for (uint i = 0; i < result.gl_pathc; i++) {
+ cDynamicDeviceProbe::QueueDynamicDeviceCommand(ddpcAttach, result.gl_pathv[i]);
+ reply = cString::sprintf("%squeued %s for attaching\n", (i == 0) ? "" : *reply, result.gl_pathv[i]);
+ }
+ }
+ globfree(&result);
+ return reply;
+}
+
+eDynamicDeviceReturnCode cDynamicDevice::AttachDevice(const char *DevPath)
+{
+ if (!DevPath)
+ return ddrcNotSupported;
+
+ cMutexLock lock(&arrayMutex);
+ int freeIndex = -1;
+ int index = IndexOf(DevPath, freeIndex);
+ int adapter = -1;
+ int frontend = -1;
+
+ if (index >= 0) {
+ esyslog("dynamite: %s is already attached", DevPath);
+ return ddrcAlreadyAttached;
+ }
+
+ if (freeIndex < 0) {
+ esyslog("dynamite: no more free slots for %s", DevPath);
+ return ddrcNoFreeDynDev;
+ }
+
+ cDevice::nextParentDevice = dynamicdevice[freeIndex];
+
+ for (cDynamicDeviceProbe *ddp = DynamicDeviceProbes.First(); ddp; ddp = DynamicDeviceProbes.Next(ddp)) {
+ if (ddp->Attach(dynamicdevice[freeIndex], DevPath))
+ goto attach; // a plugin has created the actual device
+ }
+
+ // if it's a dvbdevice try the DvbDeviceProbes as a fallback for unpatched plugins
+ if (sscanf(DevPath, "/dev/dvb/adapter%d/frontend%d", &adapter, &frontend) == 2) {
+ for (cDvbDeviceProbe *dp = DvbDeviceProbes.First(); dp; dp = DvbDeviceProbes.Next(dp)) {
+ if (dp != dvbprobe) {
+ if (dp->Probe(adapter, frontend))
+ goto attach;
+ }
+ }
+ new cDvbDevice(adapter, frontend, dynamicdevice[freeIndex]);
+ goto attach;
+ }
+
+ esyslog("dynamite: can't attach %s", DevPath);
+ return ddrcNotSupported;
+
+attach:
+ while (!dynamicdevice[freeIndex]->Ready())
+ cCondWait::SleepMs(2);
+ dynamicdevice[freeIndex]->devpath = new cString(DevPath);
+ isyslog("dynamite: attached device %s to dynamic device slot %d", DevPath, freeIndex + 1);
+ return ddrcSuccess;
+}
+
+eDynamicDeviceReturnCode cDynamicDevice::DetachDevice(const char *DevPath)
+{
+ if (!DevPath)
+ return ddrcNotSupported;
+
+ cMutexLock lock(&arrayMutex);
+ int freeIndex = -1;
+ int index = -1;
+ if (isnumber(DevPath))
+ index = strtol(DevPath, NULL, 10) - 1;
+ else
+ index = IndexOf(DevPath, freeIndex);
+
+ if ((index < 0) || (index >= numDynamicDevices)) {
+ esyslog("dynamite: device %s not found", DevPath);
+ return ddrcNotFound;
+ }
+
+ if (!dynamicdevice[index]->isDetachable) {
+ esyslog("dynamite: detaching of device %s is not allowed", DevPath);
+ return ddrcNotAllowed;
+ }
+
+ if (dynamicdevice[index] == PrimaryDevice()) {
+ esyslog("dynamite: detaching of primary device %s is not supported", DevPath);
+ return ddrcIsPrimaryDevice;
+ }
+
+ if (dynamicdevice[index]->Receiving(false)) {
+ esyslog("dynamite: can't detach device %s, it's receiving something important", DevPath);
+ return ddrcIsReceiving;
+ }
+
+ dynamicdevice[index]->DeleteSubDevice();
+ isyslog("dynamite: detached device %s", DevPath);
+ return ddrcSuccess;
+}
+
+eDynamicDeviceReturnCode cDynamicDevice::SetLockDevice(const char *DevPath, bool Lock)
+{
+ if (!DevPath)
+ return ddrcNotSupported;
+
+ cMutexLock lock(&arrayMutex);
+ int freeIndex = -1;
+ int index = -1;
+ if (isnumber(DevPath))
+ index = strtol(DevPath, NULL, 10) - 1;
+ else
+ index = IndexOf(DevPath, freeIndex);
+
+ if ((index < 0) || (index >= numDynamicDevices))
+ return ddrcNotFound;
+
+ dynamicdevice[index]->isDetachable = !Lock;
+ isyslog("dynamite: %slocked device %s", Lock ? "" : "un", DevPath);
+ return ddrcSuccess;
+}
+
+eDynamicDeviceReturnCode cDynamicDevice::SetGetTSTimeout(const char *DevPath, int Seconds)
+{
+ if (!DevPath || (Seconds < 0))
+ return ddrcNotSupported;
+
+ cMutexLock lock(&arrayMutex);
+ int freeIndex = -1;
+ int index = -1;
+ if (isnumber(DevPath))
+ index = strtol(DevPath, NULL, 10) - 1;
+ else
+ index = IndexOf(DevPath, freeIndex);
+
+ if ((index < 0) || (index >= numDynamicDevices))
+ return ddrcNotFound;
+
+ dynamicdevice[index]->getTSTimeout = Seconds;
+ if (Seconds == 0)
+ isyslog("dynamite: disable GetTSTimeout on device %s", DevPath);
+ else
+ isyslog("dynamite: set GetTSTimeout on device %s to %d seconds", DevPath, Seconds);
+ return ddrcSuccess;
+}
+
+void cDynamicDevice::SetDefaultGetTSTimeout(int Seconds)
+{
+ if (Seconds >= 0) {
+ defaultGetTSTimeout = Seconds;
+ cMutexLock lock(&arrayMutex);
+ for (int i = 0; i < numDynamicDevices; i++)
+ dynamicdevice[i]->getTSTimeout = Seconds;
+ isyslog("dynamite: set default GetTSTimeout on all devices to %d seconds", Seconds);
+ }
+}
+
+bool cDynamicDevice::IsAttached(const char *DevPath)
+{
+ cMutexLock lock(&arrayMutex);
+ int freeIndex = -1;
+ int index = IndexOf(DevPath, freeIndex);
+ return ((index >= 0) && (index >= numDynamicDevices));
+}
+
+cDynamicDevice::cDynamicDevice()
+:index(-1)
+,devpath(NULL)
+,isDetachable(true)
+,getTSTimeout(defaultGetTSTimeout)
+{
+ index = numDynamicDevices;
+ if (numDynamicDevices < MAXDEVICES) {
+ dynamicdevice[index] = this;
+ numDynamicDevices++;
+ }
+ else
+ esyslog("dynamite: ERROR: too many dynamic devices!");
+}
+
+cDynamicDevice::~cDynamicDevice()
+{
+ DeleteSubDevice();
+}
+
+void cDynamicDevice::DeleteSubDevice()
+{
+ if (subDevice) {
+ Cancel(3);
+ if (cTransferControl::ReceiverDevice() == this)
+ cControl::Shutdown();
+ subDevice->Detach(player);
+ subDevice->DetachAllReceivers();
+ subDevice->StopSectionHandler();
+ delete subDevice;
+ subDevice = NULL;
+ }
+ if (devpath) {
+ delete devpath;
+ devpath = NULL;
+ }
+ isDetachable = true;
+ getTSTimeout = defaultGetTSTimeout;
+}
+
+void cDynamicDevice::MakePrimaryDevice(bool On)
+{
+ if (subDevice)
+ subDevice->MakePrimaryDevice(On);
+ cDevice::MakePrimaryDevice(On);
+}
+
+bool cDynamicDevice::HasDecoder(void) const
+{
+ if (subDevice)
+ return subDevice->HasDecoder();
+ return cDevice::HasDecoder();
+}
+
+cSpuDecoder *cDynamicDevice::GetSpuDecoder(void)
+{
+ if (subDevice)
+ return subDevice->GetSpuDecoder();
+ return cDevice::GetSpuDecoder();
+}
+
+bool cDynamicDevice::HasCi(void)
+{
+ if (subDevice)
+ return subDevice->HasCi();
+ return cDevice::HasCi();
+}
+
+uchar *cDynamicDevice::GrabImage(int &Size, bool Jpeg, int Quality, int SizeX, int SizeY)
+{
+ if (subDevice)
+ return subDevice->GrabImage(Size, Jpeg, Quality, SizeX, SizeY);
+ return cDevice::GrabImage(Size, Jpeg, Quality, SizeX, SizeY);
+}
+
+void cDynamicDevice::SetVideoDisplayFormat(eVideoDisplayFormat VideoDisplayFormat)
+{
+ if (subDevice)
+ return subDevice->SetVideoDisplayFormat(VideoDisplayFormat);
+ cDevice::SetVideoDisplayFormat(VideoDisplayFormat);
+}
+
+void cDynamicDevice::SetVideoFormat(bool VideoFormat16_9)
+{
+ if (subDevice)
+ return subDevice->SetVideoFormat(VideoFormat16_9);
+ cDevice::SetVideoFormat(VideoFormat16_9);
+}
+
+eVideoSystem cDynamicDevice::GetVideoSystem(void)
+{
+ if (subDevice)
+ return subDevice->GetVideoSystem();
+ return cDevice::GetVideoSystem();
+}
+
+void cDynamicDevice::GetVideoSize(int &Width, int &Height, double &VideoAspect)
+{
+ if (subDevice)
+ return subDevice->GetVideoSize(Width, Height, VideoAspect);
+ cDevice::GetVideoSize(Width, Height, VideoAspect);
+}
+
+void cDynamicDevice::GetOsdSize(int &Width, int &Height, double &PixelAspect)
+{
+ if (subDevice)
+ return subDevice->GetOsdSize(Width, Height, PixelAspect);
+ cDevice::GetOsdSize(Width, Height, PixelAspect);
+}
+
+bool cDynamicDevice::SetPid(cPidHandle *Handle, int Type, bool On)
+{
+ if (subDevice)
+ return subDevice->SetPid(Handle, Type, On);
+ return cDevice::SetPid(Handle, Type, On);
+}
+
+int cDynamicDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask)
+{
+ if (subDevice)
+ return subDevice->OpenFilter(Pid, Tid, Mask);
+ return cDevice::OpenFilter(Pid, Tid, Mask);
+}
+
+void cDynamicDevice::CloseFilter(int Handle)
+{
+ if (subDevice)
+ return subDevice->CloseFilter(Handle);
+ cDevice::CloseFilter(Handle);
+}
+
+bool cDynamicDevice::ProvidesSource(int Source) const
+{
+ if (subDevice)
+ return subDevice->ProvidesSource(Source);
+ return cDevice::ProvidesSource(Source);
+}
+
+bool cDynamicDevice::ProvidesTransponder(const cChannel *Channel) const
+{
+ if (subDevice)
+ return subDevice->ProvidesTransponder(Channel);
+ return cDevice::ProvidesTransponder(Channel);
+}
+
+bool cDynamicDevice::ProvidesTransponderExclusively(const cChannel *Channel) const
+{
+ if (subDevice)
+ return subDevice->ProvidesTransponderExclusively(Channel);
+ return cDevice::ProvidesTransponderExclusively(Channel);
+}
+
+bool cDynamicDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const
+{
+ if (subDevice)
+ return subDevice->ProvidesChannel(Channel, Priority, NeedsDetachReceivers);
+ return cDevice::ProvidesChannel(Channel, Priority, NeedsDetachReceivers);
+}
+
+int cDynamicDevice::NumProvidedSystems(void) const
+{
+ if (subDevice)
+ return subDevice->NumProvidedSystems();
+ return cDevice::NumProvidedSystems();
+}
+
+const cChannel *cDynamicDevice::GetCurrentlyTunedTransponder(void) const
+{
+ if (subDevice)
+ return subDevice->GetCurrentlyTunedTransponder();
+ return cDevice::GetCurrentlyTunedTransponder();
+}
+
+bool cDynamicDevice::IsTunedToTransponder(const cChannel *Channel)
+{
+ if (subDevice)
+ return subDevice->IsTunedToTransponder(Channel);
+ return cDevice::IsTunedToTransponder(Channel);
+}
+
+bool cDynamicDevice::MaySwitchTransponder(void)
+{
+ if (subDevice)
+ return subDevice->MaySwitchTransponder();
+ return cDevice::MaySwitchTransponder();
+}
+
+bool cDynamicDevice::SetChannelDevice(const cChannel *Channel, bool LiveView)
+{
+ if (subDevice)
+ return subDevice->SetChannelDevice(Channel, LiveView);
+ return cDevice::SetChannelDevice(Channel, LiveView);
+}
+
+bool cDynamicDevice::HasLock(int TimeoutMs)
+{
+ if (subDevice)
+ return subDevice->HasLock(TimeoutMs);
+ return cDevice::HasLock(TimeoutMs);
+}
+
+bool cDynamicDevice::HasProgramme(void)
+{
+ if (subDevice)
+ return subDevice->HasProgramme();
+ return cDevice::HasProgramme();
+}
+
+int cDynamicDevice::GetAudioChannelDevice(void)
+{
+ if (subDevice)
+ return subDevice->GetAudioChannelDevice();
+ return GetAudioChannelDevice();
+}
+
+void cDynamicDevice::SetAudioChannelDevice(int AudioChannel)
+{
+ if (subDevice)
+ return subDevice->SetAudioChannelDevice(AudioChannel);
+ cDevice::SetAudioChannelDevice(AudioChannel);
+}
+
+void cDynamicDevice::SetVolumeDevice(int Volume)
+{
+ if (subDevice)
+ return subDevice->SetVolumeDevice(Volume);
+ cDevice::SetVolumeDevice(Volume);
+}
+
+void cDynamicDevice::SetDigitalAudioDevice(bool On)
+{
+ if (subDevice)
+ return subDevice->SetDigitalAudioDevice(On);
+ cDevice::SetDigitalAudioDevice(On);
+}
+
+void cDynamicDevice::SetAudioTrackDevice(eTrackType Type)
+{
+ if (subDevice)
+ return subDevice->SetAudioTrackDevice(Type);
+ cDevice::SetAudioTrackDevice(Type);
+}
+
+void cDynamicDevice::SetSubtitleTrackDevice(eTrackType Type)
+{
+ if (subDevice)
+ return subDevice->SetSubtitleTrackDevice(Type);
+ cDevice::SetSubtitleTrackDevice(Type);
+}
+
+bool cDynamicDevice::CanReplay(void) const
+{
+ if (subDevice)
+ return subDevice->CanReplay();
+ return cDevice::CanReplay();
+}
+
+bool cDynamicDevice::SetPlayMode(ePlayMode PlayMode)
+{
+ if (subDevice)
+ return subDevice->SetPlayMode(PlayMode);
+ return cDevice::SetPlayMode(PlayMode);
+}
+
+int64_t cDynamicDevice::GetSTC(void)
+{
+ if (subDevice)
+ return subDevice->GetSTC();
+ return cDevice::GetSTC();
+}
+
+bool cDynamicDevice::IsPlayingVideo(void) const
+{
+ if (subDevice)
+ return subDevice->IsPlayingVideo();
+ return cDevice::IsPlayingVideo();
+}
+
+bool cDynamicDevice::HasIBPTrickSpeed(void)
+{
+ if (subDevice)
+ return subDevice->HasIBPTrickSpeed();
+ return cDevice::HasIBPTrickSpeed();
+}
+
+void cDynamicDevice::TrickSpeed(int Speed)
+{
+ if (subDevice)
+ return subDevice->TrickSpeed(Speed);
+ cDevice::TrickSpeed(Speed);
+}
+
+void cDynamicDevice::Clear(void)
+{
+ if (subDevice)
+ return subDevice->Clear();
+ cDevice::Clear();
+}
+
+void cDynamicDevice::Play(void)
+{
+ if (subDevice)
+ return subDevice->Play();
+ cDevice::Play();
+}
+
+void cDynamicDevice::Freeze(void)
+{
+ if (subDevice)
+ return subDevice->Freeze();
+ cDevice::Freeze();
+}
+
+void cDynamicDevice::Mute(void)
+{
+ if (subDevice)
+ return subDevice->Mute();
+ cDevice::Mute();
+}
+
+void cDynamicDevice::StillPicture(const uchar *Data, int Length)
+{
+ if (subDevice)
+ return subDevice->StillPicture(Data, Length);
+ cDevice::StillPicture(Data, Length);
+}
+
+bool cDynamicDevice::Poll(cPoller &Poller, int TimeoutMs)
+{
+ if (subDevice)
+ return subDevice->Poll(Poller, TimeoutMs);
+ return cDevice::Poll(Poller, TimeoutMs);
+}
+
+bool cDynamicDevice::Flush(int TimeoutMs)
+{
+ if (subDevice)
+ return subDevice->Flush(TimeoutMs);
+ return cDevice::Flush(TimeoutMs);
+}
+
+int cDynamicDevice::PlayVideo(const uchar *Data, int Length)
+{
+ if (subDevice)
+ return subDevice->PlayVideo(Data, Length);
+ return cDevice::PlayVideo(Data, Length);
+}
+
+int cDynamicDevice::PlayAudio(const uchar *Data, int Length, uchar Id)
+{
+ if (subDevice)
+ return subDevice->PlayAudio(Data, Length, Id);
+ return cDevice::PlayAudio(Data, Length, Id);
+}
+
+int cDynamicDevice::PlaySubtitle(const uchar *Data, int Length)
+{
+ if (subDevice)
+ return subDevice->PlaySubtitle(Data, Length);
+ return cDevice::PlaySubtitle(Data, Length);
+}
+
+int cDynamicDevice::PlayPesPacket(const uchar *Data, int Length, bool VideoOnly)
+{
+ if (subDevice)
+ return subDevice->PlayPesPacket(Data, Length, VideoOnly);
+ return cDevice::PlayPesPacket(Data, Length, VideoOnly);
+}
+
+int cDynamicDevice::PlayPes(const uchar *Data, int Length, bool VideoOnly)
+{
+ if (subDevice)
+ return subDevice->PlayPes(Data, Length, VideoOnly);
+ return cDevice::PlayPes(Data, Length, VideoOnly);
+}
+
+int cDynamicDevice::PlayTsVideo(const uchar *Data, int Length)
+{
+ if (subDevice)
+ return subDevice->PlayTsVideo(Data, Length);
+ return cDevice::PlayTsVideo(Data, Length);
+}
+
+int cDynamicDevice::PlayTsAudio(const uchar *Data, int Length)
+{
+ if (subDevice)
+ return subDevice->PlayTsAudio(Data, Length);
+ return cDevice::PlayTsAudio(Data, Length);
+}
+
+int cDynamicDevice::PlayTsSubtitle(const uchar *Data, int Length)
+{
+ if (subDevice)
+ return subDevice->PlayTsSubtitle(Data, Length);
+ return cDevice::PlayTsSubtitle(Data, Length);
+}
+
+int cDynamicDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
+{
+ if (subDevice)
+ return subDevice->PlayTs(Data, Length, VideoOnly);
+ return cDevice::PlayTs(Data, Length, VideoOnly);
+}
+
+bool cDynamicDevice::Ready(void)
+{
+ if (subDevice)
+ return subDevice->Ready();
+ return cDevice::Ready();
+}
+
+bool cDynamicDevice::OpenDvr(void)
+{
+ if (subDevice) {
+ getTSWatchdog = 0;
+ return subDevice->OpenDvr();
+ }
+ return cDevice::OpenDvr();
+}
+
+void cDynamicDevice::CloseDvr(void)
+{
+ if (subDevice)
+ return subDevice->CloseDvr();
+ cDevice::CloseDvr();
+}
+
+bool cDynamicDevice::GetTSPacket(uchar *&Data)
+{
+ if (subDevice) {
+ bool r = subDevice->GetTSPacket(Data);
+ if (getTSTimeout > 0) {
+ if (Data == NULL) {
+ if (getTSWatchdog == 0)
+ getTSWatchdog = time(NULL);
+ else if ((time(NULL) - getTSWatchdog) > getTSTimeout) {
+ const char *d = NULL;
+ if (devpath)
+ d = **devpath;
+ esyslog("dynamite: device %s hasn't delivered any data for %d seconds, it will be detached", d, getTSTimeout);
+ cDynamicDeviceProbe::QueueDynamicDeviceCommand(ddpcDetach, *devpath);
+ return false;
+ }
+ }
+ else
+ getTSWatchdog = 0;
+ }
+ return r;
+ }
+ return cDevice::GetTSPacket(Data);
+}
+
+#ifdef YAVDR_PATCHES
+//opt-21_internal-cam-devices.dpatch
+bool cDynamicDevice::HasInternalCam(void)
+{
+ if (subDevice)
+ return subDevice->HasInternalCam();
+ return cDevice::HasInternalCam();
+}
+
+//opt-44_rotor.dpatch
+bool cDynamicDevice::SendDiseqcCmd(dvb_diseqc_master_cmd cmd)
+{
+ if (subDevice)
+ return subDevice->SendDiseqcCmd(cmd);
+ return cDevice::SendDiseqcCmd(cmd);
+}
+
+//opt-64_lnb-sharing.dpatch
+void cDynamicDevice::SetLnbNrFromSetup(void)
+{
+ if (subDevice)
+ return subDevice->SetLnbNrFromSetup();
+ cDevice::SetLnbNrFromSetup();
+}
+
+int cDynamicDevice::LnbNr(void) const
+{
+ if (subDevice)
+ return subDevice->LnbNr();
+ return cDevice::LnbNr();
+}
+
+bool cDynamicDevice::IsShareLnb(const cDevice *Device)
+{
+ if (subDevice)
+ return subDevice->IsShareLnb(Device);
+ return cDevice::IsShareLnb(Device);
+}
+
+bool cDynamicDevice::IsLnbConflict(const cChannel *Channel)
+{
+ if (subDevice)
+ return subDevice->IsLnbConflict(Channel);
+ return cDevice::IsLnbConflict(Channel);
+}
+#endif
diff --git a/dynamicdevice.h b/dynamicdevice.h
new file mode 100644
index 0000000..1fe9831
--- /dev/null
+++ b/dynamicdevice.h
@@ -0,0 +1,127 @@
+#ifndef __DYNAMITEDEVICE_H
+#define __DYNAMITEDEVICE_H
+
+#include <vdr/dvbdevice.h>
+
+enum eDynamicDeviceReturnCode { ddrcSuccess,
+ ddrcNoFreeDynDev,
+ ddrcAlreadyAttached,
+ ddrcNotFound,
+ ddrcIsPrimaryDevice,
+ ddrcIsReceiving,
+ ddrcNotAllowed,
+ ddrcNotSupported
+ };
+
+class cDynamicDevice : public cDevice {
+private:
+ static int defaultGetTSTimeout;
+
+ static int numDynamicDevices;
+ static cMutex arrayMutex;
+ static cDynamicDevice *dynamicdevice[MAXDEVICES];
+ static int IndexOf(const char *DevPath, int &NextFreeIndex);
+public:
+ static cDvbDeviceProbe *dvbprobe;
+ static int NumDynamicDevices(void) { return numDynamicDevices; }
+ ///< Returns the total number of dynamic devices.
+ static bool ProcessQueuedCommands(void);
+ static void DetachAllDevices(void);
+ static cString ListAllDevices(int &ReplyCode); // for SVDRP command LSTD
+ static cString AttachDevicePattern(const char *Pattern);
+ static eDynamicDeviceReturnCode AttachDevice(const char *DevPath);
+ static eDynamicDeviceReturnCode DetachDevice(const char *DevPath);
+ static eDynamicDeviceReturnCode SetLockDevice(const char *DevPath, bool Lock);
+ static eDynamicDeviceReturnCode SetGetTSTimeout(const char *DevPath, int Seconds);
+ static void SetDefaultGetTSTimeout(int Seconds);
+ static bool IsAttached(const char *DevPath);
+private:
+ int index;
+ cString *devpath;
+ bool isDetachable;
+ time_t getTSWatchdog;
+ int getTSTimeout;
+public:
+ cDynamicDevice();
+ void DeleteSubDevice(void);
+protected:
+ virtual ~cDynamicDevice();
+ virtual bool Ready(void);
+ virtual void MakePrimaryDevice(bool On);
+public:
+ virtual bool HasDecoder(void) const;
+ virtual cSpuDecoder *GetSpuDecoder(void);
+ virtual bool ProvidesSource(int Source) const;
+ virtual bool ProvidesTransponder(const cChannel *Channel) const;
+ virtual bool ProvidesTransponderExclusively(const cChannel *Channel) const;
+ virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL) const;
+ virtual int NumProvidedSystems(void) const;
+ virtual const cChannel *GetCurrentlyTunedTransponder(void) const;
+ virtual bool IsTunedToTransponder(const cChannel *Channel);
+ virtual bool MaySwitchTransponder(void);
+protected:
+ virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
+public:
+ virtual bool HasLock(int TimeoutMs = 0);
+ virtual bool HasProgramme(void);
+protected:
+ virtual bool SetPid(cPidHandle *Handle, int Type, bool On);
+public:
+ virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask);
+ virtual void CloseFilter(int Handle);
+ virtual bool HasCi(void);
+ virtual uchar *GrabImage(int &Size, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1);
+ virtual void SetVideoDisplayFormat(eVideoDisplayFormat VideoDisplayFormat);
+ virtual void SetVideoFormat(bool VideoFormat16_9);
+ virtual eVideoSystem GetVideoSystem(void);
+ virtual void GetVideoSize(int &Width, int &Height, double &VideoAspect);
+ virtual void GetOsdSize(int &Width, int &Height, double &PixelAspect);
+protected:
+ virtual void SetAudioTrackDevice(eTrackType Type);
+ virtual void SetSubtitleTrackDevice(eTrackType Type);
+ virtual int GetAudioChannelDevice(void);
+ virtual void SetAudioChannelDevice(int AudioChannel);
+ virtual void SetVolumeDevice(int Volume);
+ virtual void SetDigitalAudioDevice(bool On);
+ virtual bool CanReplay(void) const;
+ virtual bool SetPlayMode(ePlayMode PlayMode);
+ virtual int PlayVideo(const uchar *Data, int Length);
+ virtual int PlayAudio(const uchar *Data, int Length, uchar Id);
+ virtual int PlaySubtitle(const uchar *Data, int Length);
+ virtual int PlayPesPacket(const uchar *Data, int Length, bool VideoOnly = false);
+ virtual int PlayTsVideo(const uchar *Data, int Length);
+ virtual int PlayTsAudio(const uchar *Data, int Length);
+ virtual int PlayTsSubtitle(const uchar *Data, int Length);
+public:
+ virtual int64_t GetSTC(void);
+ virtual bool IsPlayingVideo(void) const;
+ virtual bool HasIBPTrickSpeed(void);
+ virtual void TrickSpeed(int Speed);
+ virtual void Clear(void);
+ virtual void Play(void);
+ virtual void Freeze(void);
+ virtual void Mute(void);
+ virtual void StillPicture(const uchar *Data, int Length);
+ virtual bool Poll(cPoller &Poller, int TimeoutMs = 0);
+ virtual bool Flush(int TimeoutMs = 0);
+ virtual int PlayPes(const uchar *Data, int Length, bool VideoOnly = false);
+ virtual int PlayTs(const uchar *Data, int Length, bool VideoOnly = false);
+protected:
+ virtual bool OpenDvr(void);
+ virtual void CloseDvr(void);
+ virtual bool GetTSPacket(uchar *&Data);
+
+#ifdef YAVDR_PATCHES
+//opt-21_internal-cam-devices.dpatch
+ virtual bool HasInternalCam(void);
+//opt-44_rotor.dpatch
+ virtual bool SendDiseqcCmd(dvb_diseqc_master_cmd cmd);
+//opt-64_lnb-sharing.dpatch
+ virtual void SetLnbNrFromSetup(void);
+ virtual int LnbNr(void) const;
+ virtual bool IsShareLnb(const cDevice *Device);
+ virtual bool IsLnbConflict(const cChannel *Channel);
+#endif
+ };
+
+#endif //__DYNAMITEDEVICE_H
diff --git a/dynamite.c b/dynamite.c
new file mode 100644
index 0000000..79db409
--- /dev/null
+++ b/dynamite.c
@@ -0,0 +1,341 @@
+/*
+ * dynamite.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ */
+
+#include <vdr/plugin.h>
+#include "dynamicdevice.h"
+#include "monitor.h"
+
+static const char *VERSION = "0.0.5c";
+static const char *DESCRIPTION = "attach/detach devices on the fly";
+static const char *MAINMENUENTRY = NULL;
+
+class cDynamiteDvbDeviceProbe : public cDvbDeviceProbe {
+private:
+ static bool firstProbe;
+public:
+ virtual bool Probe(int Adapter, int Frontend)
+ {
+ if (firstProbe) {
+ firstProbe = false;
+ while (cDevice::NumDevices() < MAXDVBDEVICES)
+ new cDynamicDevice;
+ }
+ isyslog("dynamite: grab dvb device %d/%d", Adapter, Frontend);
+ cDynamicDevice::AttachDevice(*cString::sprintf("/dev/dvb/adapter%d/frontend%d", Adapter, Frontend));
+ return true; // grab all dvbdevices
+ }
+ };
+
+bool cDynamiteDvbDeviceProbe::firstProbe = true;
+
+class cDynamiteDeviceProbe : public cDynamicDeviceProbe {
+private:
+ class cDummyDevice: public cDevice {
+ public:
+ cDummyDevice(cDevice *ParentDevice):cDevice(ParentDevice) {}
+ virtual ~cDummyDevice() {};
+ };
+public:
+ virtual cDevice *Attach(cDevice *ParentDevice, const char *DevPath)
+ {
+ int nr;
+ if (sscanf(DevPath, "dummydevice%d", &nr) == 1)
+ return new cDummyDevice(ParentDevice);
+ return NULL;
+ }
+ };
+
+class cPluginDynamite : public cPlugin {
+private:
+ cDynamiteDeviceProbe *probe;
+public:
+ cPluginDynamite(void);
+ virtual ~cPluginDynamite();
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return DESCRIPTION; }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual bool Initialize(void);
+ virtual bool Start(void);
+ virtual void Stop(void);
+ virtual void Housekeeping(void);
+ virtual void MainThreadHook(void);
+ virtual cString Active(void);
+ virtual time_t WakeupTime(void);
+ virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
+ virtual cOsdObject *MainMenuAction(void);
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *Name, const char *Value);
+ virtual bool Service(const char *Id, void *Data = NULL);
+ virtual const char **SVDRPHelpPages(void);
+ virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
+ };
+
+cPluginDynamite::cPluginDynamite(void)
+{
+ cDynamicDevice::dvbprobe = new cDynamiteDvbDeviceProbe;
+ // make sure we're the first one you cares for dvbdevices
+ cDvbDeviceProbe *firstDvbProbe = DvbDeviceProbes.First();
+ if (firstDvbProbe != cDynamicDevice::dvbprobe)
+ DvbDeviceProbes.Move(cDynamicDevice::dvbprobe, firstDvbProbe);
+ probe = new cDynamiteDeviceProbe;
+ cUdevMonitor::AddFilter("dvb", new cUdevDvbFilter());
+}
+
+cPluginDynamite::~cPluginDynamite()
+{
+ cUdevMonitor::ShutdownAllMonitors();
+ cUdev::Free();
+ if (cDynamicDevice::dvbprobe)
+ delete cDynamicDevice::dvbprobe;
+ if (probe)
+ delete probe;
+}
+
+const char *cPluginDynamite::CommandLineHelp(void)
+{
+ return " --log-udev log all udev events to syslog (useful for diagnostics)\n";
+}
+
+bool cPluginDynamite::ProcessArgs(int argc, char *argv[])
+{
+ for (int i = 0; i < argc; i++) {
+ if (strcmp(argv[i], "--log-udev") == 0)
+ cUdevMonitor::AddFilter(NULL, new cUdevLogFilter());
+ }
+ return true;
+}
+
+bool cPluginDynamite::Initialize(void)
+{
+ // create dynamic devices
+ while (cDevice::NumDevices() < MAXDEVICES)
+ new cDynamicDevice;
+ if (!cDynamicDevice::ProcessQueuedCommands())
+ esyslog("dynamite: can't process all queued commands");
+ return true;
+}
+
+bool cPluginDynamite::Start(void)
+{
+ if (!cDynamicDevice::ProcessQueuedCommands())
+ esyslog("dynamite: can't process all queued commands");
+ return true;
+}
+
+void cPluginDynamite::Stop(void)
+{
+ cDynamicDevice::DetachAllDevices();
+}
+
+void cPluginDynamite::Housekeeping(void)
+{
+ // Perform any cleanup or other regular tasks.
+}
+
+void cPluginDynamite::MainThreadHook(void)
+{
+ // Perform actions in the context of the main program thread.
+ // WARNING: Use with great care - see PLUGINS.html!
+ if (!cDynamicDevice::ProcessQueuedCommands())
+ esyslog("dynamite: can't process all queued commands");
+}
+
+cString cPluginDynamite::Active(void)
+{
+ // Return a message string if shutdown should be postponed
+ return NULL;
+}
+
+time_t cPluginDynamite::WakeupTime(void)
+{
+ // Return custom wakeup time for shutdown script
+ return 0;
+}
+
+cOsdObject *cPluginDynamite::MainMenuAction(void)
+{
+ // Perform the action when selected from the main VDR menu.
+ return NULL;
+}
+
+cMenuSetupPage *cPluginDynamite::SetupMenu(void)
+{
+ // Return a setup menu in case the plugin supports one.
+ return NULL;
+}
+
+bool cPluginDynamite::SetupParse(const char *Name, const char *Value)
+{
+ int replyCode;
+ if (strcasecmp(Name, "DefaultGetTSTimeout") == 0)
+ SVDRPCommand("SetDefaultGetTSTimeout", Value, replyCode);
+ else
+ return false;
+ return true;
+}
+
+bool cPluginDynamite::Service(const char *Id, void *Data)
+{
+ if (strcmp(Id, "dynamite-AttachDevice-v0.1") == 0) {
+ if (Data != NULL)
+ cDynamicDeviceProbe::QueueDynamicDeviceCommand(ddpcAttach, (const char*)Data);
+ return true;
+ }
+ if (strcmp(Id, "dynamite-ScanDevices-v0.1") == 0) {
+ if (Data != NULL)
+ cDynamicDevice::AttachDevicePattern((const char*)Data);
+ return true;
+ }
+ if (strcmp(Id, "dynamite-DetachDevice-v0.1") == 0) {
+ if (Data != NULL)
+ cDynamicDeviceProbe::QueueDynamicDeviceCommand(ddpcDetach, (const char*)Data);
+ return true;
+ }
+ if (strcmp(Id, "dynamite-LockDevice-v0.1") == 0) {
+ if (Data != NULL)
+ cDynamicDevice::SetLockDevice((const char*)Data, true);
+ return true;
+ }
+ if (strcmp(Id, "dynamite-UnlockDevice-v0.1") == 0) {
+ if (Data != NULL)
+ cDynamicDevice::SetLockDevice((const char*)Data, false);
+ return true;
+ }
+ if (strcmp(Id, "dynamite-SetGetTSTimeout-v0.1") == 0) {
+ if (Data != NULL) {
+ int replyCode;
+ SVDRPCommand("SetGetTSTimeout", (const char*)Data, replyCode);
+ }
+ return true;
+ }
+ if (strcmp(Id, "dynamite-SetDefaultGetTSTimeout-v0.1") == 0) {
+ if (Data != NULL) {
+ int replyCode;
+ SVDRPCommand("SetDefaultGetTSTimeout", (const char*)Data, replyCode);
+ }
+ return true;
+ }
+ return false;
+}
+
+const char **cPluginDynamite::SVDRPHelpPages(void)
+{
+ static const char *HelpPages[] = {
+ "ATTD devpath\n"
+ " Asks all cDynamicDeviceProbe-objects to attach the given devicepath\n"
+ " till one returns a valid pointer. You can control the order by the\n"
+ " order of the plugins you load\n"
+ " e.g. /dev/dvb/adapter0/frontend0\n"
+ " alternate command: AttachDevice",
+ "DETD devpath\n"
+ " Looks through its remembered devicepaths and deletes the attached\n"
+ " device if found. Case is important!\n"
+ " Any timeouts or locks set to this slot will be reset to its defaults\n"
+ " alternate command: DetachDevice",
+ "SCND '/dev/path/glob*/pattern*'\n"
+ " Scan filesystem with pattern and try to attach each found device\n"
+ " don't forget to enclose the pattern with single quotes\n"
+ " e.g. SCND '/dev/dvb/adapter*/frontend*'\n"
+ " alternate command: ScanDevices",
+ "LCKD /dev/path/to/device\n"
+ " alternate command: LockDevice",
+ " Lock the device so it can't be detached\n"
+ " alternate command: LockDevice",
+ "UNLD /dev/path/to/device\n"
+ " Remove the lock of the device so it can be detached\n"
+ " alternate command: UnlockDevice",
+ "LSTD\n"
+ " Lists all devices managed by this plugin. The first column is an id,\n"
+ " the second column is the devicepath passed with ATTD\n"
+ " The id can be used with the DETD and UNLD commands instead of the path.\n"
+ " An asterisk behind the id means that this device is locked and\n"
+ " can't be detached.\n"
+ " alternate command: ListDevices",
+ "SGTT /dev/path/to/device seconds\n"
+ " Sets the \"GetTSPacket\"-watchdog timeout to specified amount of seconds\n"
+ " If the device returns no data (Data == NULL) for this period of time,\n"
+ " the device will be detached. Usefull if you want to reload the driver etc.\n"
+ " A value of 0 seconds disables the watchdog.\n"
+ " alternate command: SetGetTSTimeout",
+ "SDGT seconds\n"
+ " Sets the \"GetTSPacket\"-watchdog timeout for all attached devices\n"
+ " and all devices that will be attached.\n"
+ " alternate command: SetDefaultGetTSTimeout",
+ NULL
+ };
+ return HelpPages;
+}
+
+cString cPluginDynamite::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
+{
+ if ((strcasecmp(Command, "ATTD") == 0) || (strcasecmp(Command, "AttachDevice") == 0)) {
+ cDynamicDeviceProbe::QueueDynamicDeviceCommand(ddpcAttach, Option);
+ return cString::sprintf("queued command for attaching %s", Option);
+ }
+
+ if ((strcasecmp(Command, "DETD") == 0) || (strcasecmp(Command, "DetachDevice") == 0)) {
+ cDynamicDeviceProbe::QueueDynamicDeviceCommand(ddpcDetach, Option);
+ return cString::sprintf("queued command for detaching %s", Option);
+ }
+
+ if ((strcasecmp(Command, "SCND") == 0) || (strcasecmp(Command, "ScanDevices") == 0))
+ return cDynamicDevice::AttachDevicePattern(Option);
+
+ if ((strcasecmp(Command, "LSTD") == 0) || (strcasecmp(Command, "ListDevices") == 0))
+ return cDynamicDevice::ListAllDevices(ReplyCode);
+
+ int lock = 0;
+ if ((strcasecmp(Command, "LCKD") == 0) || (strcasecmp(Command, "LockDevice") == 0))
+ lock = 1;
+ else if ((strcasecmp(Command, "UNLD") == 0) || (strcasecmp(Command, "UnlockDevice") == 0))
+ lock = 2;
+ if (lock > 0) {
+ switch (cDynamicDevice::SetLockDevice(Option, lock == 1)) {
+ case ddrcSuccess:
+ return cString::sprintf("%slocked device %s", (lock == 2) ? "un" : "", Option);
+ case ddrcNotFound:
+ {
+ ReplyCode = 550;
+ return cString::sprintf("device %s not found", Option);
+ }
+ default:
+ {
+ ReplyCode = 550;
+ return cString::sprintf("can't %slock device %s and I don't know why...", (lock == 2) ? "un" : "", Option);
+ }
+ }
+ }
+
+ if ((strcasecmp(Command, "SGTT") == 0) || (strcasecmp(Command, "SetGetTSTimeout") == 0)) {
+ cString ret;
+ int len = strlen(Option);
+ if (len > 0) {
+ char *devPath = new char[len];
+ int seconds = -1;
+ if ((sscanf(Option, "%s %d", devPath, &seconds) == 2) && (seconds >= 0)) {
+ cDynamicDevice::SetGetTSTimeout(devPath, seconds);
+ if (seconds > 0)
+ ret = cString::sprintf("set GetTS-Timeout on device %s to %d seconds", devPath, seconds);
+ else
+ ret = cString::sprintf("disable GetTS-Timeout on device %s", devPath);
+ }
+ delete [] devPath;
+ }
+ return ret;
+ }
+
+ if ((strcasecmp(Command, "SDGT") == 0) || (strcasecmp(Command, "SetDefaultGetTSTimeout") == 0)) {
+ if (isnumber(Option)) {
+ int seconds = strtol(Option, NULL, 10);
+ cDynamicDevice::SetDefaultGetTSTimeout(seconds);
+ return cString::sprintf("set default GetTS-Timeout on all devices to %d seconds", seconds);
+ }
+ }
+ return NULL;
+}
+
+VDRPLUGINCREATOR(cPluginDynamite); // Don't touch this!
diff --git a/monitor.c b/monitor.c
new file mode 100644
index 0000000..ff9392c
--- /dev/null
+++ b/monitor.c
@@ -0,0 +1,220 @@
+#include "monitor.h"
+#include "dynamicdevice.h"
+#include <unistd.h>
+
+// --- cUdevMonitor ----------------------------------------------------------
+
+cMutex cUdevMonitor::mutexMonitors;
+int cUdevMonitor::numMonitors = 0;
+cUdevMonitor *cUdevMonitor::monitors[MAXUDEVMONITORS] = { NULL };
+
+cUdevMonitor *cUdevMonitor::Get(const char *Subsystem)
+{
+ cMutexLock lock(&mutexMonitors);
+ int i = 0;
+ while (i < numMonitors) {
+ if (monitors[i]) {
+ if ((Subsystem == NULL) && (*monitors[i]->subsystem == NULL))
+ return monitors[i];
+ if ((Subsystem != NULL)
+ && (*monitors[i]->subsystem != NULL)
+ && (strcmp(*monitors[i]->subsystem, Subsystem) == 0))
+ return monitors[i];
+ }
+ i++;
+ }
+ if (i < MAXUDEVMONITORS) {
+ numMonitors++;
+ cUdevMonitor *m = new cUdevMonitor(Subsystem);
+ m->index = i;
+ monitors[i] = m;
+ return m;
+ }
+ return NULL;
+}
+
+bool cUdevMonitor::AddFilter(const char *Subsystem, cUdevFilter *Filter)
+{
+ if (Filter == NULL)
+ return false;
+ cUdevMonitor *m = Get(Subsystem);
+ if (m == NULL) {
+ delete Filter;
+ return false;
+ }
+ if (!m->AddFilter(Filter)) {
+ delete Filter;
+ return false;
+ }
+ return true;
+}
+
+void cUdevMonitor::ShutdownAllMonitors(void)
+{
+ cMutexLock lock(&mutexMonitors);
+ for (int i = 0; i < numMonitors; i++) {
+ if (monitors[i]) {
+ delete monitors[i];
+ monitors[i] = NULL;
+ }
+ }
+}
+
+cUdevMonitor::cUdevMonitor(const char *Subsystem)
+:monitor(NULL)
+,index(-1)
+,subsystem(Subsystem)
+{
+ struct udev *udev = cUdev::Init();
+ if (udev) {
+ monitor = udev_monitor_new_from_netlink(udev, "udev");
+ if (monitor && Subsystem)
+ udev_monitor_filter_add_match_subsystem_devtype(monitor, Subsystem, NULL);
+ }
+ SetDescription("dynamite udev monitor for subsystem %s", Subsystem);
+}
+
+cUdevMonitor::~cUdevMonitor(void)
+{
+ if ((index >= 0) && (index < MAXUDEVMONITORS)) {
+ cMutexLock lock(&mutexMonitors);
+ if (monitors[index] == this)
+ monitors[index] = NULL;
+ }
+ Cancel(3);
+ filters.Clear();
+ if (monitor)
+ udev_monitor_unref(monitor);
+ monitor = NULL;
+}
+
+#define MONITOR_POLL_INTERVAL_MS 500
+
+void cUdevMonitor::Action(void)
+{
+ if (monitor == NULL)
+ return;
+ udev_monitor_enable_receiving(monitor);
+ int fd = udev_monitor_get_fd(monitor);
+ if (fd == 0)
+ return;
+ fd_set fds;
+ struct timeval tv;
+ while (Running()) {
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+ tv.tv_sec = 0;
+ tv.tv_usec = MONITOR_POLL_INTERVAL_MS * 1000;
+ int ret = select(fd+1, &fds, NULL, NULL, &tv);
+ if (!Running())
+ break;
+ if ((ret > 0) && FD_ISSET(fd, &fds)) {
+ udev_device *dev = udev_monitor_receive_device(monitor);
+ if (dev) {
+ cMutexLock lock(&filtersMutex);
+ cUdevDevice d(dev);
+ for (cUdevFilter *f = filters.First(); f; f = filters.Next(f))
+ f->Process(d);
+ }
+ }
+ }
+ close(fd);
+}
+
+bool cUdevMonitor::AddFilter(cUdevFilter *Filter)
+{
+ if (monitor == NULL)
+ return false;
+ if (Filter == NULL)
+ return false;
+ if (Filter->monitor == this)
+ return true;
+ if (Filter->monitor != NULL)
+ return false;
+ cMutexLock lock(&filtersMutex);
+ Filter->monitor = this;
+ filters.Add(Filter);
+ if (!Running())
+ Start();
+ return true;
+}
+
+bool cUdevMonitor::DelFilter(cUdevFilter *Filter)
+{
+ if (monitor == NULL)
+ return false;
+ if (Filter == NULL)
+ return false;
+ if (Filter->monitor != this)
+ return false;
+ cMutexLock lock(&filtersMutex);
+ filters.Del(Filter);
+ Filter->monitor = NULL;
+ if (filters.Count() == 0)
+ Cancel(3);
+ return true;
+}
+
+// --- cUdevFilter -----------------------------------------------------------
+
+cUdevFilter::cUdevFilter(void)
+:monitor(NULL)
+{
+}
+
+cUdevFilter::~cUdevFilter(void)
+{
+}
+
+// --- cUdevLogFilter --------------------------------------------------------
+
+static void InternLogProcess(int Level, cUdevDevice &Device)
+{
+ char *indent = new char[Level + 2];
+ indent[0] = '+';
+ int i = 0;
+ while (i < Level) {
+ indent[i + 1] = '-';
+ i++;
+ }
+ indent[i + 1] = '\0';
+ const char *action = Device.GetAction();
+ const char *subsystem = Device.GetPropertyValue("SUBSYSTEM");
+ const char *syspath = Device.GetSyspath();
+ const char *devpath = Device.GetPropertyValue("DEVPATH");
+ const char *devname = Device.GetPropertyValue("DEVNAME");
+ isyslog("dynamite: udev: %s action=%s, subsystem=%s, syspath=%s, devpath=%s, devname=%s", indent, action, subsystem, syspath, devpath, devname);
+ cUdevListEntry *devlink = Device.GetDevlinksList();
+ while (devlink) {
+ isyslog("dynamite: udev: %s devlink: %s", indent, devlink->GetName());
+ cUdevListEntry *tmp = devlink->GetNext();
+ delete devlink;
+ devlink = tmp;
+ }
+ cUdevDevice *parent = Device.GetParent();
+ if (parent != NULL) {
+ InternLogProcess(Level + 1, *parent);
+ delete parent;
+ }
+}
+
+void cUdevLogFilter::Process(cUdevDevice &Device)
+{
+ InternLogProcess(0, Device);
+}
+
+// --- cUdevDvbFilter --------------------------------------------------------
+
+void cUdevDvbFilter::Process(cUdevDevice &Device)
+{
+ const char *action = Device.GetAction();
+ if (action && (strcmp(action, "add") == 0)) {
+ const char *dvb_device_type = Device.GetPropertyValue("DVB_DEVICE_TYPE");
+ if (!dvb_device_type || (strcmp(dvb_device_type, "frontend") != 0))
+ return;
+ const char *devname = Device.GetPropertyValue("DEVNAME");
+ if (!devname || cDynamicDevice::IsAttached(devname))
+ return;
+ cDynamicDeviceProbe::QueueDynamicDeviceCommand(ddpcAttach, devname);
+ }
+}
diff --git a/monitor.h b/monitor.h
new file mode 100644
index 0000000..659e504
--- /dev/null
+++ b/monitor.h
@@ -0,0 +1,58 @@
+#ifndef __DYNAMITEMONITOR_H
+#define __DYNAMITEMONITOR_H
+
+#include <vdr/thread.h>
+#include <vdr/tools.h>
+#include "udev.h"
+
+#define MAXUDEVMONITORS 10
+
+class cUdevFilter;
+
+class cUdevMonitor : public cThread {
+private:
+ static cMutex mutexMonitors;
+ static int numMonitors;
+ static cUdevMonitor *monitors[MAXUDEVMONITORS];
+
+ struct udev_monitor *monitor;
+ int index;
+ cString subsystem;
+ cMutex filtersMutex;
+ cList<cUdevFilter> filters;
+
+ cUdevMonitor(const char *Subsystem);
+protected:
+ virtual void Action(void);
+public:
+ static cUdevMonitor *Get(const char *Subsystem);
+ static bool AddFilter(const char *Subsystem, cUdevFilter *Filter);
+ static void ShutdownAllMonitors(void);
+
+ virtual ~cUdevMonitor(void);
+ cString GetSubsystem() const { return subsystem; };
+ bool AddFilter(cUdevFilter *Filter);
+ bool DelFilter(cUdevFilter *Filter);
+ };
+
+class cUdevFilter : public cListObject {
+friend class cUdevMonitor;
+protected:
+ const cUdevMonitor *monitor;
+ virtual void Process(cUdevDevice &Device) = 0;
+public:
+ cUdevFilter(void);
+ virtual ~cUdevFilter(void);
+ };
+
+class cUdevLogFilter : public cUdevFilter {
+protected:
+ virtual void Process(cUdevDevice &Device);
+ };
+
+class cUdevDvbFilter : public cUdevFilter {
+protected:
+ virtual void Process(cUdevDevice &Device);
+ };
+
+#endif // __DYNAMITEMONITOR_H
diff --git a/patches/sc-1.0.0pre-subdevice.patch b/patches/sc-1.0.0pre-subdevice.patch
new file mode 100644
index 0000000..39ca3e5
--- /dev/null
+++ b/patches/sc-1.0.0pre-subdevice.patch
@@ -0,0 +1,215 @@
+diff --git a/device.c b/device.c
+index fe10d5e..b29a993 100644
+--- a/device.c
++++ b/device.c
+@@ -1334,6 +1334,17 @@ bool cScDeviceProbe::Probe(int Adapter, int Frontend)
+ // -- cScDevices ---------------------------------------------------------------
+
+ int cScDevices::budget=0;
++int cScDevices::numScDevices = 0;
++cScDevice *cScDevices::scdevice[MAXDEVICES] = { NULL };
++
++cScDevice *cScDevices::GetScDevice(int CardIndex)
++{
++ for (int n = 0; n < numScDevices; n++) {
++ if (scdevice[n] && (scdevice[n]->CardIndex() == CardIndex))
++ return scdevice[n];
++ }
++ return NULL;
++}
+
+ void cScDevices::DvbName(const char *Name, int a, int f, char *buffer, int len)
+ {
+@@ -1439,16 +1450,16 @@ void cScDevices::Startup(void)
+ {
+ if(ScSetup.ForceTransfer)
+ SetTransferModeForDolbyDigital(2);
+- for(int n=cDevice::NumDevices(); --n>=0;) {
+- cScDevice *dev=dynamic_cast<cScDevice *>(cDevice::GetDevice(n));
++ for(int n=cScDevices::numScDevices; --n>=0;) {
++ cScDevice *dev=cScDevices::scdevice[n];
+ if(dev) dev->LateInit();
+ }
+ }
+
+ void cScDevices::Shutdown(void)
+ {
+- for(int n=cDevice::NumDevices(); --n>=0;) {
+- cScDevice *dev=dynamic_cast<cScDevice *>(cDevice::GetDevice(n));
++ for(int n=cScDevices::numScDevices; --n>=0;) {
++ cScDevice *dev=cScDevices::scdevice[n];
+ if(dev) dev->EarlyShutdown();
+ }
+ }
+@@ -1490,19 +1501,36 @@ cScDevice::cScDevice(int Adapter, int Frontend, int cafd)
+ :cDvbDevice(Adapter)
+ #endif
+ {
++ lateInit = false;
+ #ifndef SASC
+ decsa=0; tsBuffer=0; cam=0; fullts=false;
+ ciadapter=0; hwciadapter=0;
+ fd_ca=cafd; fd_ca2=dup(fd_ca); fd_dvr=-1;
+ softcsa=(fd_ca<0);
++#ifdef __DYNAMIC_DEVICE_PROBE
++ if (parentDevice)
++ LateInit();
++#endif
+ #else
+ softcsa=fullts=false;
+ cam=new cCam(this,Adapter);
+ #endif // !SASC
++ index = 0;
++ while ((index < cScDevices::numScDevices) && (index < MAXDEVICES) && cScDevices::scdevice[index])
++ index++;
++ if (index < MAXDEVICES) {
++ cScDevices::scdevice[index] = this;
++ if (index == cScDevices::numScDevices)
++ cScDevices::numScDevices++;
++ }
++ else
++ esyslog("too many sc-devices!");
+ }
+
+ cScDevice::~cScDevice()
+ {
++ if ((index >= 0) && (index < MAXDEVICES) && (cScDevices::scdevice[index] == this))
++ cScDevices::scdevice[index] = NULL;
+ #ifndef SASC
+ DetachAllReceivers();
+ Cancel(3);
+@@ -1528,6 +1556,8 @@ void cScDevice::EarlyShutdown(void)
+
+ void cScDevice::LateInit(void)
+ {
++ if (lateInit) return;
++ lateInit = true;
+ int n=CardIndex();
+ if(DeviceNumber()!=n)
+ PRINTF(L_GEN_ERROR,"CardIndex - DeviceNumber mismatch! Put SC plugin first on VDR commandline!");
+@@ -1538,10 +1568,16 @@ void cScDevice::LateInit(void)
+ PRINTF(L_GEN_INFO,"Budget mode forced on card %d",n);
+ softcsa=true;
+ }
+-
++#ifdef __DYNAMIC_DEVICE_PROBE
++ cDevice *cidev = parentDevice ? parentDevice : this;
++ if(fd_ca2>=0) hwciadapter=cDvbCiAdapter::CreateCiAdapter(cidev,fd_ca2);
++ cam=new cCam(this,n);
++ ciadapter=new cScCiAdapter(cidev,n,cam);
++#else
+ if(fd_ca2>=0) hwciadapter=cDvbCiAdapter::CreateCiAdapter(this,fd_ca2);
+ cam=new cCam(this,n);
+ ciadapter=new cScCiAdapter(this,n,cam);
++#endif
+ if(softcsa) {
+ decsa=new cDeCSA(n);
+ if(IsPrimaryDevice() && HasDecoder()) {
+diff --git a/device.h b/device.h
+index 5ad83f9..454d6ea 100644
+--- a/device.h
++++ b/device.h
+@@ -88,6 +88,8 @@ public:
+
+ // ----------------------------------------------------------------
+
++class cScDevice;
++
+ class cScDevices : public cDvbDevice {
+ private:
+ static int budget;
+@@ -106,6 +108,10 @@ public:
+ static bool ForceBudget(int n);
+ static void DvbName(const char *Name, int a, int f, char *buffer, int len);
+ static int DvbOpen(const char *Name, int a, int f, int Mode, bool ReportError=false);
++
++ static int numScDevices;
++ static cScDevice *scdevice[MAXDEVICES];
++ static cScDevice *GetScDevice(int CardIndex);
+ };
+
+ // ----------------------------------------------------------------
+@@ -123,6 +129,8 @@ private:
+ bool softcsa, fullts;
+ cMutex cafdMutex;
+ cTimeMs lastDump;
++ int index;
++ bool lateInit;
+ //
+ #ifndef SASC
+ void LateInit(void);
+diff --git a/sc.c b/sc.c
+index 82960bf..9f01217 100644
+--- a/sc.c
++++ b/sc.c
+@@ -1009,7 +1009,7 @@ void cSoftCAM::Shutdown(void)
+
+ char *cSoftCAM::CurrKeyStr(int CardNum, int num)
+ {
+- cScDevice *dev=dynamic_cast<cScDevice *>(cDevice::GetDevice(CardNum));
++ cScDevice *dev=cScDevices::GetScDevice(CardNum);
+ char *str=0;
+ if(dev) {
+ if(dev->Cam()) str=dev->Cam()->CurrentKeyStr(num);
+@@ -1020,8 +1020,8 @@ char *cSoftCAM::CurrKeyStr(int CardNum, int num)
+
+ bool cSoftCAM::Active(bool log)
+ {
+- for(int n=cDevice::NumDevices(); --n>=0;) {
+- cScDevice *dev=dynamic_cast<cScDevice *>(cDevice::GetDevice(n));
++ for(int n=cScDevices::numScDevices; --n>=0;) {
++ cScDevice *dev=cScDevices::scdevice[n];
+ if(dev && dev->Cam() && dev->Cam()->Active(log)) return true;
+ }
+ return false;
+@@ -1029,33 +1029,33 @@ bool cSoftCAM::Active(bool log)
+
+ void cSoftCAM::SetLogStatus(int CardNum, const cEcmInfo *ecm, bool on)
+ {
+- cScDevice *dev=dynamic_cast<cScDevice *>(cDevice::GetDevice(CardNum));
++ cScDevice *dev=cScDevices::GetScDevice(CardNum);
+ if(dev && dev->Cam()) dev->Cam()->LogEcmStatus(ecm,on);
+ }
+
+ void cSoftCAM::AddHook(int CardNum, cLogHook *hook)
+ {
+- cScDevice *dev=dynamic_cast<cScDevice *>(cDevice::GetDevice(CardNum));
++ cScDevice *dev=cScDevices::GetScDevice(CardNum);
+ if(dev && dev->Cam()) dev->Cam()->AddHook(hook);
+ }
+
+ bool cSoftCAM::TriggerHook(int CardNum, int id)
+ {
+- cScDevice *dev=dynamic_cast<cScDevice *>(cDevice::GetDevice(CardNum));
++ cScDevice *dev=cScDevices::GetScDevice(CardNum);
+ return dev && dev->Cam() && dev->Cam()->TriggerHook(id);
+ }
+
+ void cSoftCAM::CaidsChanged(void)
+ {
+- for(int n=cDevice::NumDevices(); --n>=0;) {
+- cScDevice *dev=dynamic_cast<cScDevice *>(cDevice::GetDevice(n));
++ for(int n=cScDevices::numScDevices; --n>=0;) {
++ cScDevice *dev=cScDevices::scdevice[n];
+ if(dev) dev->CaidsChanged();
+ }
+ }
+
+ int cSoftCAM::FilterHandle(int CardNum)
+ {
+- cScDevice *dev=dynamic_cast<cScDevice *>(cDevice::GetDevice(CardNum));
++ cScDevice *dev=cScDevices::GetScDevice(CardNum);
+ return dev ? dev->FilterHandle() : -1;
+ }
+
+@@ -1086,8 +1086,8 @@ void cScHousekeeper::Action(void)
+ while(Running()) {
+ if(++c==20) {
+ c=0;
+- for(int n=cDevice::NumDevices(); --n>=0;) {
+- cScDevice *dev=dynamic_cast<cScDevice *>(cDevice::GetDevice(n));
++ for(int n=cScDevices::numScDevices; --n>=0;) {
++ cScDevice *dev=cScDevices::scdevice[n];
+ if(dev && dev->Cam()) dev->Cam()->HouseKeeping();
+ }
+ }
diff --git a/patches/vdr-1.7.16-dynamite-subdevice.patch b/patches/vdr-1.7.16-dynamite-subdevice.patch
new file mode 100644
index 0000000..f0ee935
--- /dev/null
+++ b/patches/vdr-1.7.16-dynamite-subdevice.patch
@@ -0,0 +1,361 @@
+diff --git a/device.c b/device.c
+index 681049b..aaee92f 100644
+--- a/device.c
++++ b/device.c
+@@ -72,12 +72,18 @@ cDevice *cDevice::device[MAXDEVICES] = { NULL };
+ cDevice *cDevice::primaryDevice = NULL;
+ cDevice *cDevice::avoidDevice = NULL;
+ cList<cDeviceHook> cDevice::deviceHooks;
++cDevice *cDevice::nextParentDevice = NULL;
+
+-cDevice::cDevice(void)
++cDevice::cDevice(cDevice *ParentDevice)
+ :patPmtParser(true)
++,parentDevice(ParentDevice)
++,subDevice(NULL)
+ {
++ if (!ParentDevice)
++ parentDevice = nextParentDevice;
++ cDevice::nextParentDevice = NULL;
+ cardIndex = nextCardIndex++;
+- dsyslog("new device number %d", CardIndex() + 1);
++ dsyslog("new %sdevice number %d", parentDevice ? "sub-" : "", CardIndex() + 1);
+
+ SetDescription("receiver on device %d", CardIndex() + 1);
+
+@@ -108,10 +114,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()
+@@ -120,6 +130,8 @@ cDevice::~cDevice()
+ DetachAllReceivers();
+ delete liveSubtitle;
+ delete dvbSubtitleConverter;
++ if (parentDevice && (parentDevice->subDevice == this))
++ parentDevice->subDevice = NULL;
+ }
+
+ bool cDevice::WaitForAllDevicesReady(int Timeout)
+@@ -158,6 +170,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;
+@@ -328,6 +342,10 @@ bool cDevice::HasCi(void)
+
+ void cDevice::SetCamSlot(cCamSlot *CamSlot)
+ {
++ if (parentDevice) {
++ parentDevice->SetCamSlot(CamSlot);
++ return;
++ }
+ camSlot = CamSlot;
+ }
+
+@@ -531,6 +549,10 @@ bool cDevice::SetPid(cPidHandle *Handle, int Type, bool On)
+
+ void cDevice::StartSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StartSectionHandler();
++ return;
++ }
+ if (!sectionHandler) {
+ sectionHandler = new cSectionHandler(this);
+ AttachFilter(eitFilter = new cEitFilter);
+@@ -542,6 +564,10 @@ void cDevice::StartSectionHandler(void)
+
+ void cDevice::StopSectionHandler(void)
+ {
++ if (parentDevice) {
++ parentDevice->StopSectionHandler();
++ return;
++ }
+ if (sectionHandler) {
+ delete nitFilter;
+ delete sdtFilter;
+@@ -568,12 +594,20 @@ void cDevice::CloseFilter(int Handle)
+
+ void cDevice::AttachFilter(cFilter *Filter)
+ {
++ if (parentDevice) {
++ parentDevice->AttachFilter(Filter);
++ return;
++ }
+ if (sectionHandler)
+ sectionHandler->Attach(Filter);
+ }
+
+ void cDevice::Detach(cFilter *Filter)
+ {
++ if (parentDevice) {
++ parentDevice->Detach(Filter);
++ return;
++ }
+ if (sectionHandler)
+ sectionHandler->Detach(Filter);
+ }
+@@ -1690,3 +1724,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 cb3bc2c..0b6634b 100644
+--- a/device.h
++++ b/device.h
+@@ -163,7 +163,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
+@@ -191,8 +190,6 @@ protected:
+ ///< 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 bool HasDecoder(void) const;
+@@ -365,9 +362,6 @@ public:
+ ///< Returns true if this device has a Common Interface.
+ 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
+
+@@ -524,9 +518,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);
+@@ -712,6 +703,30 @@ public:
+ ///< Detaches all receivers from this device for this pid.
+ 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
++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:
++ 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; }
++ // --- dynamite subdevice patch end ---
+ };
+
+ /// Derived cDevice classes that can receive channels will have to provide
+@@ -735,4 +750,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 };
++
++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..ea54bdb 100644
+--- a/dvbci.c
++++ b/dvbci.c
+@@ -41,6 +41,8 @@ cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
+ cDvbCiAdapter::~cDvbCiAdapter()
+ {
+ Cancel(3);
++ if (device->IsSubDevice() || device->HasSubDevice())
++ close(fd);
+ }
+
+ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
+diff --git a/dvbdevice.c b/dvbdevice.c
+index f32b350..df2e679 100644
+--- a/dvbdevice.c
++++ b/dvbdevice.c
+@@ -259,6 +259,7 @@ private:
+ int device;
+ int fd_frontend;
+ int adapter, frontend;
++ cDvbDevice *dvbdevice;
+ int tuneTimeout;
+ int lockTimeout;
+ time_t lastTimeoutReport;
+@@ -273,7 +274,7 @@ private:
+ bool SetFrontend(void);
+ virtual void Action(void);
+ public:
+- cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType);
++ cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType, cDvbDevice *Dvbdevice);
+ virtual ~cDvbTuner();
+ const cChannel *GetTransponder(void) const { return &channel; }
+ bool IsTunedTo(const cChannel *Channel) const;
+@@ -281,13 +282,14 @@ public:
+ bool Locked(int TimeoutMs = 0);
+ };
+
+-cDvbTuner::cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType)
++cDvbTuner::cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType, cDvbDevice *Dvbdevice)
+ {
+ device = Device;
+ fd_frontend = Fd_Frontend;
+ adapter = Adapter;
+ frontend = Frontend;
+ frontendType = FrontendType;
++ dvbdevice = Dvbdevice;
+ tuneTimeout = 0;
+ lockTimeout = 0;
+ lastTimeoutReport = 0;
+@@ -305,6 +307,8 @@ cDvbTuner::~cDvbTuner()
+ newSet.Broadcast();
+ locked.Broadcast();
+ Cancel(3);
++ if (dvbdevice && dvbdevice->IsSubDevice())
++ close(fd_frontend);
+ }
+
+ bool cDvbTuner::IsTunedTo(const cChannel *Channel) const
+@@ -661,7 +665,8 @@ const char *DeliverySystems[] = {
+ NULL
+ };
+
+-cDvbDevice::cDvbDevice(int Adapter, int Frontend)
++cDvbDevice::cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice)
++:cDevice(ParentDevice)
+ {
+ adapter = Adapter;
+ frontend = Frontend;
+@@ -678,7 +683,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);
+
+ // The DVR device (will be opened and closed as needed):
+
+@@ -718,7 +723,7 @@ cDvbDevice::cDvbDevice(int Adapter, int Frontend)
+ else
+ p = (char *)"unknown modulations";
+ isyslog("frontend %d/%d provides %s with %s (\"%s\")", adapter, frontend, DeliverySystems[frontendType], p, frontendInfo.name);
+- dvbTuner = new cDvbTuner(CardIndex() + 1, fd_frontend, adapter, frontend, frontendType);
++ dvbTuner = new cDvbTuner(CardIndex() + 1, fd_frontend, adapter, frontend, frontendType, this);
+ }
+ }
+ else
+diff --git a/dvbdevice.h b/dvbdevice.h
+index ff606fd..0ac3a24 100644
+--- a/dvbdevice.h
++++ b/dvbdevice.h
+@@ -123,7 +123,7 @@ private:
+ fe_delivery_system frontendType;
+ int fd_dvr, fd_ca;
+ public:
+- cDvbDevice(int Adapter, int Frontend);
++ cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice = NULL);
+ virtual ~cDvbDevice();
+ virtual bool Ready(void);
+
diff --git a/udev.c b/udev.c
new file mode 100644
index 0000000..77cc744
--- /dev/null
+++ b/udev.c
@@ -0,0 +1,110 @@
+#include "udev.h"
+#include <linux/stddef.h>
+
+// --- cUdevListEntry --------------------------------------------------------
+
+cUdevListEntry::cUdevListEntry(struct udev_list_entry *ListEntry)
+:listEntry(ListEntry)
+{
+}
+
+cUdevListEntry::~cUdevListEntry(void)
+{
+}
+
+cUdevListEntry *cUdevListEntry::GetNext(void) const
+{
+ if (listEntry == NULL)
+ return NULL;
+ struct udev_list_entry *next = udev_list_entry_get_next(listEntry);
+ if (next == NULL)
+ return NULL;
+ return new cUdevListEntry(next);
+}
+
+const char *cUdevListEntry::GetName(void) const
+{
+ if (listEntry == NULL)
+ return NULL;
+ return udev_list_entry_get_name(listEntry);
+}
+
+const char *cUdevListEntry::GetValue(void) const
+{
+ if (listEntry == NULL)
+ return NULL;
+ return udev_list_entry_get_value(listEntry);
+}
+
+// --- cUdevDevice -----------------------------------------------------------
+
+cUdevDevice::cUdevDevice(udev_device *Device, bool DoUnref)
+:device(Device)
+,doUnref(DoUnref)
+{
+}
+
+cUdevDevice::~cUdevDevice(void)
+{
+ if (doUnref && device)
+ udev_device_unref(device);
+}
+
+const char *cUdevDevice::GetAction(void) const
+{
+ if (device == NULL)
+ return NULL;
+ return udev_device_get_action(device);
+}
+
+cUdevListEntry *cUdevDevice::GetDevlinksList(void) const
+{
+ if (device == NULL)
+ return NULL;
+ struct udev_list_entry *listEntry = udev_device_get_devlinks_list_entry(device);
+ if (listEntry == NULL)
+ return NULL;
+ return new cUdevListEntry(listEntry);
+}
+
+cUdevDevice *cUdevDevice::GetParent(void) const
+{
+ if (device == NULL)
+ return NULL;
+ struct udev_device *parent = udev_device_get_parent(device);
+ if (parent == NULL)
+ return NULL;
+ return new cUdevDevice(parent, false);
+}
+
+const char *cUdevDevice::GetPropertyValue(const char *Key) const
+{
+ if (device == NULL)
+ return false;
+ return udev_device_get_property_value(device, Key);
+}
+
+const char *cUdevDevice::GetSyspath(void) const
+{
+ if (device == NULL)
+ return false;
+ return udev_device_get_syspath(device);
+}
+
+// --- cUdev -----------------------------------------------------------------
+
+struct udev *cUdev::udev = NULL;
+
+struct udev *cUdev::Init(void)
+{
+ if (udev == NULL)
+ udev = udev_new();
+ return udev;
+}
+
+void cUdev::Free(void)
+{
+ if (udev)
+ udev_unref(udev);
+ udev = NULL;
+}
diff --git a/udev.h b/udev.h
new file mode 100644
index 0000000..e72fe19
--- /dev/null
+++ b/udev.h
@@ -0,0 +1,41 @@
+#ifndef __DYNAMITEUDEV_H
+#define __DYNAMITEUDEV_H
+
+#include <libudev.h>
+
+class cUdevListEntry {
+private:
+ struct udev_list_entry *listEntry;
+public:
+ cUdevListEntry(struct udev_list_entry *ListEntry);
+ virtual ~cUdevListEntry(void);
+
+ cUdevListEntry *GetNext(void) const;
+ const char *GetName(void) const;
+ const char *GetValue(void) const;
+ };
+
+class cUdevDevice {
+private:
+ struct udev_device *device;
+ bool doUnref;
+public:
+ cUdevDevice(udev_device *Device, bool DoUnref = true);
+ virtual ~cUdevDevice(void);
+
+ const char *GetAction(void) const;
+ cUdevListEntry *GetDevlinksList(void) const;
+ cUdevDevice *GetParent(void) const;
+ const char *GetPropertyValue(const char *Key) const;
+ const char *GetSyspath(void) const;
+ };
+
+class cUdev {
+private:
+ static struct udev *udev;
+public:
+ static struct udev *Init(void);
+ static void Free(void);
+ };
+
+#endif // __DYNAMITEUDEV_H