diff -Nru vdr-2.2.0/args.c vdr-2.3.3/args.c --- vdr-2.2.0/args.c 2014-04-14 12:02:38.000000000 +0000 +++ vdr-2.3.3/args.c 2014-04-14 12:02:38.000000000 +0000 @@ -6,7 +6,7 @@ * * Original version written by Lars Hanisch . * - * $Id: args.c 1.1 2014/04/14 12:02:38 kls Exp $ + * $Id: args.c 4.0 2014/04/14 12:02:38 kls Exp $ */ #include "args.h" diff -Nru vdr-2.2.0/args.h vdr-2.3.3/args.h --- vdr-2.2.0/args.h 2014-04-14 11:54:21.000000000 +0000 +++ vdr-2.3.3/args.h 2014-04-14 11:54:21.000000000 +0000 @@ -6,7 +6,7 @@ * * Original version written by Lars Hanisch . * - * $Id: args.h 1.1 2014/04/14 11:54:21 kls Exp $ + * $Id: args.h 4.0 2014/04/14 11:54:21 kls Exp $ */ #ifndef __ARGS_H diff -Nru vdr-2.2.0/audio.c vdr-2.3.3/audio.c --- vdr-2.2.0/audio.c 2010-05-16 13:30:11.000000000 +0000 +++ vdr-2.3.3/audio.c 2010-05-16 13:30:11.000000000 +0000 @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: audio.c 3.0 2010/05/16 13:30:11 kls Exp $ + * $Id: audio.c 4.0 2010/05/16 13:30:11 kls Exp $ */ #include "audio.h" diff -Nru vdr-2.2.0/audio.h vdr-2.3.3/audio.h --- vdr-2.2.0/audio.h 2008-07-06 11:39:21.000000000 +0000 +++ vdr-2.3.3/audio.h 2008-07-06 11:39:21.000000000 +0000 @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: audio.h 3.0 2008/07/06 11:39:21 kls Exp $ + * $Id: audio.h 4.0 2008/07/06 11:39:21 kls Exp $ */ #ifndef __AUDIO_H diff -Nru vdr-2.2.0/channels.c vdr-2.3.3/channels.c --- vdr-2.2.0/channels.c 2015-02-01 13:47:05.000000000 +0000 +++ vdr-2.3.3/channels.c 2015-09-09 10:21:22.000000000 +0000 @@ -4,15 +4,13 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: channels.c 3.8 2015/02/01 13:47:05 kls Exp $ + * $Id: channels.c 4.3 2015/09/09 10:21:22 kls Exp $ */ #include "channels.h" #include #include "device.h" -#include "epg.h" #include "libsi/si.h" -#include "timers.h" // IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d' // format characters in order to allow any number of blanks after a numeric @@ -79,27 +77,13 @@ schedule = NULL; linkChannels = NULL; refChannel = NULL; + seen = 0; *this = Channel; } cChannel::~cChannel() { - delete linkChannels; - linkChannels = NULL; // more than one channel can link to this one, so we need the following loop - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { - if (Channel->linkChannels) { - for (cLinkChannel *lc = Channel->linkChannels->First(); lc; lc = Channel->linkChannels->Next(lc)) { - if (lc->Channel() == this) { - Channel->linkChannels->Del(lc); - break; - } - } - if (Channel->linkChannels->Count() == 0) { - delete Channel->linkChannels; - Channel->linkChannels = NULL; - } - } - } + delete linkChannels; // any links from other channels pointing to this one have been deleted in cChannels::Del() free(name); free(shortName); free(provider); @@ -167,16 +151,7 @@ return tf; } -bool cChannel::HasTimer(void) const -{ - for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) { - if (Timer->Channel() == this) - return true; - } - return false; -} - -int cChannel::Modification(int Mask) +int cChannel::Modification(int Mask) const { int Result = modification & Mask; modification = CHANNELMOD_NONE; @@ -223,41 +198,57 @@ if (Number() && !Quiet) { dsyslog("changing transponder data of channel %d (%s) from %s to %s", Number(), name, *OldTransponderData, *TransponderDataToString()); modification |= CHANNELMOD_TRANSP; - Channels.SetModified(); } + return true; } - return true; + return false; } -void cChannel::SetId(int Nid, int Tid, int Sid, int Rid) +bool cChannel::SetSource(int Source) { - if (nid != Nid || tid != Tid || sid != Sid || rid != Rid) { + if (source != Source) { if (Number()) { + dsyslog("changing source of channel %d (%s) from %s to %s", Number(), name, *cSource::ToString(source), *cSource::ToString(Source)); + modification |= CHANNELMOD_TRANSP; + } + source = Source; + return true; + } + return false; +} + +bool cChannel::SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid) +{ + if (nid != Nid || tid != Tid || sid != Sid || rid != Rid) { + if (Channels && Number()) { dsyslog("changing id of channel %d (%s) from %d-%d-%d-%d to %d-%d-%d-%d", Number(), name, nid, tid, sid, rid, Nid, Tid, Sid, Rid); modification |= CHANNELMOD_ID; - Channels.SetModified(); - Channels.UnhashChannel(this); + Channels->UnhashChannel(this); } nid = Nid; tid = Tid; sid = Sid; rid = Rid; - if (Number()) - Channels.HashChannel(this); + if (Channels) + Channels->HashChannel(this); schedule = NULL; + return true; } + return false; } -void cChannel::SetLcn(int Lcn) +bool cChannel::SetLcn(int Lcn) { if (lcn != Lcn) { if (Number()) dsyslog("changing lcn of channel %d (%s) from %d to %d\n", Number(), name, lcn, Lcn); lcn = Lcn; + return true; } + return false; } -void cChannel::SetName(const char *Name, const char *ShortName, const char *Provider) +bool cChannel::SetName(const char *Name, const char *ShortName, const char *Provider) { if (!isempty(Name)) { bool nn = strcmp(name, Name) != 0; @@ -267,7 +258,6 @@ if (Number()) { dsyslog("changing name of channel %d from '%s,%s;%s' to '%s,%s;%s'", Number(), name, shortName, provider, Name, ShortName, Provider); modification |= CHANNELMOD_NAME; - Channels.SetModified(); } if (nn) { name = strcpyrealloc(name, Name); @@ -279,20 +269,23 @@ } if (np) provider = strcpyrealloc(provider, Provider); + return true; } } + return false; } -void cChannel::SetPortalName(const char *PortalName) +bool cChannel::SetPortalName(const char *PortalName) { if (!isempty(PortalName) && strcmp(portalName, PortalName) != 0) { if (Number()) { dsyslog("changing portal name of channel %d (%s) from '%s' to '%s'", Number(), name, portalName, PortalName); modification |= CHANNELMOD_NAME; - Channels.SetModified(); } portalName = strcpyrealloc(portalName, PortalName); + return true; } + return false; } #define STRDIFF 0x01 @@ -337,7 +330,7 @@ return q - s; } -void cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid) +bool cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid) { int mod = CHANNELMOD_NONE; if (vpid != Vpid || ppid != Ppid || vtype != Vtype) @@ -400,25 +393,33 @@ spids[MAXSPIDS] = 0; tpid = Tpid; modification |= mod; - if (Number()) - Channels.SetModified(); + return true; } + return false; } -void cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds) +bool cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds) { + bool Modified = false; if (SubtitlingTypes) { - for (int i = 0; i < MAXSPIDS; i++) + for (int i = 0; i < MAXSPIDS; i++) { + Modified = subtitlingTypes[i] != SubtitlingTypes[i]; subtitlingTypes[i] = SubtitlingTypes[i]; + } } if (CompositionPageIds) { - for (int i = 0; i < MAXSPIDS; i++) + for (int i = 0; i < MAXSPIDS; i++) { + Modified = compositionPageIds[i] != CompositionPageIds[i]; compositionPageIds[i] = CompositionPageIds[i]; + } } if (AncillaryPageIds) { - for (int i = 0; i < MAXSPIDS; i++) + for (int i = 0; i < MAXSPIDS; i++) { + Modified = ancillaryPageIds[i] != AncillaryPageIds[i]; ancillaryPageIds[i] = AncillaryPageIds[i]; + } } + return Modified; } void cChannel::SetSeen(void) @@ -426,10 +427,26 @@ seen = time(NULL); } -void cChannel::SetCaIds(const int *CaIds) +void cChannel::DelLinkChannel(cChannel *LinkChannel) +{ + if (linkChannels) { + for (cLinkChannel *lc = linkChannels->First(); lc; lc = linkChannels->Next(lc)) { + if (lc->Channel() == LinkChannel) { + linkChannels->Del(lc); + break; + } + } + if (linkChannels->Count() == 0) { + delete linkChannels; + linkChannels = NULL; + } + } +} + +bool cChannel::SetCaIds(const int *CaIds) { if (caids[0] && caids[0] <= CA_USER_MAX) - return; // special values will not be overwritten + return false; // special values will not be overwritten if (IntArraysDiffer(caids, CaIds)) { char OldCaIdsBuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia char NewCaIdsBuf[MAXCAIDS * 5 + 10]; @@ -443,24 +460,26 @@ break; } modification |= CHANNELMOD_CA; - Channels.SetModified(); + return true; } + return false; } -void cChannel::SetCaDescriptors(int Level) +bool cChannel::SetCaDescriptors(int Level) { if (Level > 0) { modification |= CHANNELMOD_CA; - Channels.SetModified(); if (Number() && Level > 1) dsyslog("changing ca descriptors of channel %d (%s)", Number(), name); + return true; } + return false; } -void cChannel::SetLinkChannels(cLinkChannels *LinkChannels) +bool cChannel::SetLinkChannels(cLinkChannels *LinkChannels) { if (!linkChannels && !LinkChannels) - return; + return false; if (linkChannels && LinkChannels) { cLinkChannel *lca = linkChannels->First(); cLinkChannel *lcb = LinkChannels->First(); @@ -474,7 +493,7 @@ } if (!lca && !lcb) { delete LinkChannels; - return; // linkage has not changed + return false; // linkage has not changed } } char buffer[((linkChannels ? linkChannels->Count() : 0) + (LinkChannels ? LinkChannels->Count() : 0)) * 6 + 256]; // 6: 5 digit channel number plus blank, 256: other texts (see below) plus reserve @@ -502,6 +521,7 @@ q += sprintf(q, " none"); if (Number()) dsyslog("%s", buffer); + return true; } void cChannel::SetRefChannel(cChannel *RefChannel) @@ -534,9 +554,9 @@ cString buffer; if (Channel->groupSep) { if (Channel->number) - buffer = cString::sprintf(":@%d %s\n", Channel->number, FullName); + buffer = cString::sprintf(":@%d %s", Channel->number, FullName); else - buffer = cString::sprintf(":%s\n", FullName); + buffer = cString::sprintf(":%s", FullName); } else { char vpidbuf[32]; @@ -568,7 +588,7 @@ q = caidbuf; q += IntArrayToString(q, Channel->caids, 16); *q = 0; - buffer = cString::sprintf("%s:%d:%s:%s:%d:%s:%s:%s:%s:%d:%d:%d:%d\n", FullName, Channel->frequency, *Channel->parameters, *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, tpidbuf, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid); + buffer = cString::sprintf("%s:%d:%s:%s:%d:%s:%s:%s:%s:%d:%d:%d:%d", FullName, Channel->frequency, *Channel->parameters, *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, tpidbuf, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid); } return buffer; } @@ -786,7 +806,7 @@ bool cChannel::Save(FILE *f) { - return fprintf(f, "%s", *ToText()) > 0; + return fprintf(f, "%s\n", *ToText()) > 0; } // --- cChannelSorter -------------------------------------------------------- @@ -807,40 +827,52 @@ // --- cChannels ------------------------------------------------------------- -cChannels Channels; +cChannels cChannels::channels; +int cChannels::maxNumber = 0; +int cChannels::maxChannelNameLength = 0; +int cChannels::maxShortChannelNameLength = 0; cChannels::cChannels(void) +:cConfig("Channels") { - maxNumber = 0; - maxChannelNameLength = 0; - maxShortChannelNameLength = 0; - modified = CHANNELSMOD_NONE; + modifiedByUser = 0; +} + +const cChannels *cChannels::GetChannelsRead(cStateKey &StateKey, int TimeoutMs) +{ + return channels.Lock(StateKey, false, TimeoutMs) ? &channels : NULL; +} + +cChannels *cChannels::GetChannelsWrite(cStateKey &StateKey, int TimeoutMs) +{ + return channels.Lock(StateKey, true, TimeoutMs) ? &channels : NULL; } void cChannels::DeleteDuplicateChannels(void) { cList ChannelSorter; - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (!channel->GroupSep()) - ChannelSorter.Add(new cChannelSorter(channel)); + for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (!Channel->GroupSep()) + ChannelSorter.Add(new cChannelSorter(Channel)); } ChannelSorter.Sort(); cChannelSorter *cs = ChannelSorter.First(); while (cs) { - cChannelSorter *next = ChannelSorter.Next(cs); - if (next && cs->channelID == next->channelID) { - dsyslog("deleting duplicate channel %s", *next->channel->ToText()); - Del(next->channel); + cChannelSorter *Next = ChannelSorter.Next(cs); + if (Next && cs->channelID == Next->channelID) { + dsyslog("deleting duplicate channel %s", *Next->channel->ToText()); + Del(Next->channel); } - cs = next; + cs = Next; } } bool cChannels::Load(const char *FileName, bool AllowComments, bool MustExist) { - if (cConfig::Load(FileName, AllowComments, MustExist)) { - DeleteDuplicateChannels(); - ReNumber(); + LOCK_CHANNELS_WRITE; + if (channels.cConfig::Load(FileName, AllowComments, MustExist)) { + channels.DeleteDuplicateChannels(); + channels.ReNumber(); return true; } return false; @@ -856,36 +888,36 @@ channelsHashSid.Del(Channel, Channel->Sid()); } -int cChannels::GetNextGroup(int Idx) +int cChannels::GetNextGroup(int Idx) const { - cChannel *channel = Get(++Idx); - while (channel && !(channel->GroupSep() && *channel->Name())) - channel = Get(++Idx); - return channel ? Idx : -1; + const cChannel *Channel = Get(++Idx); + while (Channel && !(Channel->GroupSep() && *Channel->Name())) + Channel = Get(++Idx); + return Channel ? Idx : -1; } -int cChannels::GetPrevGroup(int Idx) +int cChannels::GetPrevGroup(int Idx) const { - cChannel *channel = Get(--Idx); - while (channel && !(channel->GroupSep() && *channel->Name())) - channel = Get(--Idx); - return channel ? Idx : -1; + const cChannel *Channel = Get(--Idx); + while (Channel && !(Channel->GroupSep() && *Channel->Name())) + Channel = Get(--Idx); + return Channel ? Idx : -1; } -int cChannels::GetNextNormal(int Idx) +int cChannels::GetNextNormal(int Idx) const { - cChannel *channel = Get(++Idx); - while (channel && channel->GroupSep()) - channel = Get(++Idx); - return channel ? Idx : -1; + const cChannel *Channel = Get(++Idx); + while (Channel && Channel->GroupSep()) + Channel = Get(++Idx); + return Channel ? Idx : -1; } -int cChannels::GetPrevNormal(int Idx) +int cChannels::GetPrevNormal(int Idx) const { - cChannel *channel = Get(--Idx); - while (channel && channel->GroupSep()) - channel = Get(--Idx); - return channel ? Idx : -1; + const cChannel *Channel = Get(--Idx); + while (Channel && Channel->GroupSep()) + Channel = Get(--Idx); + return Channel ? Idx : -1; } void cChannels::ReNumber(void) @@ -893,110 +925,120 @@ channelsHashSid.Clear(); maxNumber = 0; int Number = 1; - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (channel->GroupSep()) { - if (channel->Number() > Number) - Number = channel->Number(); + for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (Channel->GroupSep()) { + if (Channel->Number() > Number) + Number = Channel->Number(); } else { - HashChannel(channel); + HashChannel(Channel); maxNumber = Number; - channel->SetNumber(Number++); + Channel->SetNumber(Number++); } } } -cChannel *cChannels::GetByNumber(int Number, int SkipGap) +void cChannels::Del(cChannel *Channel) { - cChannel *previous = NULL; - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (!channel->GroupSep()) { - if (channel->Number() == Number) - return channel; - else if (SkipGap && channel->Number() > Number) - return SkipGap > 0 ? channel : previous; - previous = channel; + UnhashChannel(Channel); + for (cChannel *ch = First(); ch; ch = Next(ch)) + ch->DelLinkChannel(Channel); + cList::Del(Channel); +} + +const cChannel *cChannels::GetByNumber(int Number, int SkipGap) const +{ + const cChannel *Previous = NULL; + for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (!Channel->GroupSep()) { + if (Channel->Number() == Number) + return Channel; + else if (SkipGap && Channel->Number() > Number) + return SkipGap > 0 ? Channel : Previous; + Previous = Channel; } } return NULL; } -cChannel *cChannels::GetByServiceID(int Source, int Transponder, unsigned short ServiceID) +const cChannel *cChannels::GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const { cList *list = channelsHashSid.GetList(ServiceID); if (list) { for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { - cChannel *channel = (cChannel *)hobj->Object(); - if (channel->Sid() == ServiceID && channel->Source() == Source && ISTRANSPONDER(channel->Transponder(), Transponder)) - return channel; + cChannel *Channel = (cChannel *)hobj->Object(); + if (Channel->Sid() == ServiceID && Channel->Source() == Source && ISTRANSPONDER(Channel->Transponder(), Transponder)) + return Channel; } } return NULL; } -cChannel *cChannels::GetByChannelID(tChannelID ChannelID, bool TryWithoutRid, bool TryWithoutPolarization) +const cChannel *cChannels::GetByChannelID(tChannelID ChannelID, bool TryWithoutRid, bool TryWithoutPolarization) const { int sid = ChannelID.Sid(); cList *list = channelsHashSid.GetList(sid); if (list) { for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { - cChannel *channel = (cChannel *)hobj->Object(); - if (channel->Sid() == sid && channel->GetChannelID() == ChannelID) - return channel; + cChannel *Channel = (cChannel *)hobj->Object(); + if (Channel->Sid() == sid && Channel->GetChannelID() == ChannelID) + return Channel; } if (TryWithoutRid) { ChannelID.ClrRid(); for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { - cChannel *channel = (cChannel *)hobj->Object(); - if (channel->Sid() == sid && channel->GetChannelID().ClrRid() == ChannelID) - return channel; + cChannel *Channel = (cChannel *)hobj->Object(); + if (Channel->Sid() == sid && Channel->GetChannelID().ClrRid() == ChannelID) + return Channel; } } if (TryWithoutPolarization) { ChannelID.ClrPolarization(); for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { - cChannel *channel = (cChannel *)hobj->Object(); - if (channel->Sid() == sid && channel->GetChannelID().ClrPolarization() == ChannelID) - return channel; + cChannel *Channel = (cChannel *)hobj->Object(); + if (Channel->Sid() == sid && Channel->GetChannelID().ClrPolarization() == ChannelID) + return Channel; } } } return NULL; } -cChannel *cChannels::GetByTransponderID(tChannelID ChannelID) + +const cChannel *cChannels::GetByTransponderID(tChannelID ChannelID) const { int source = ChannelID.Source(); int nid = ChannelID.Nid(); int tid = ChannelID.Tid(); - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (channel->Tid() == tid && channel->Nid() == nid && channel->Source() == source) - return channel; + for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (Channel->Tid() == tid && Channel->Nid() == nid && Channel->Source() == source) + return Channel; } return NULL; } -bool cChannels::HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel) +bool cChannels::HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel) const { tChannelID NewChannelID = NewChannel->GetChannelID(); - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (!channel->GroupSep() && channel != OldChannel && channel->GetChannelID() == NewChannelID) + for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (!Channel->GroupSep() && Channel != OldChannel && Channel->GetChannelID() == NewChannelID) return false; } return true; } -bool cChannels::SwitchTo(int Number) +bool cChannels::SwitchTo(int Number) const { - cChannel *channel = GetByNumber(Number); - return channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true); + const cChannel *Channel = GetByNumber(Number); + return Channel && cDevice::PrimaryDevice()->SwitchChannel(Channel, true); } int cChannels::MaxChannelNameLength(void) { if (!maxChannelNameLength) { - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (!channel->GroupSep()) - maxChannelNameLength = max(Utf8StrLen(channel->Name()), maxChannelNameLength); + LOCK_CHANNELS_READ; + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { + if (!Channel->GroupSep()) + maxChannelNameLength = max(Utf8StrLen(Channel->Name()), maxChannelNameLength); } } return maxChannelNameLength; @@ -1005,24 +1047,25 @@ int cChannels::MaxShortChannelNameLength(void) { if (!maxShortChannelNameLength) { - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (!channel->GroupSep()) - maxShortChannelNameLength = max(Utf8StrLen(channel->ShortName(true)), maxShortChannelNameLength); + LOCK_CHANNELS_READ; + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { + if (!Channel->GroupSep()) + maxShortChannelNameLength = max(Utf8StrLen(Channel->ShortName(true)), maxShortChannelNameLength); } } return maxShortChannelNameLength; } -void cChannels::SetModified(bool ByUser) +void cChannels::SetModifiedByUser(void) { - modified = ByUser ? CHANNELSMOD_USER : !modified ? CHANNELSMOD_AUTO : modified; + modifiedByUser++; maxChannelNameLength = maxShortChannelNameLength = 0; } -int cChannels::Modified(void) +bool cChannels::ModifiedByUser(int &State) const { - int Result = modified; - modified = CHANNELSMOD_NONE; + int Result = State != modifiedByUser; + State = modifiedByUser; return Result; } @@ -1032,7 +1075,7 @@ dsyslog("creating new channel '%s,%s;%s' on %s transponder %d with id %d-%d-%d-%d", Name, ShortName, Provider, *cSource::ToString(Transponder->Source()), Transponder->Transponder(), Nid, Tid, Sid, Rid); cChannel *NewChannel = new cChannel; NewChannel->CopyTransponderData(Transponder); - NewChannel->SetId(Nid, Tid, Sid, Rid); + NewChannel->SetId(this, Nid, Tid, Sid, Rid); NewChannel->SetName(Name, ShortName, Provider); NewChannel->SetSeen(); Add(NewChannel); @@ -1045,17 +1088,19 @@ #define CHANNELMARKOBSOLETE "OBSOLETE" #define CHANNELTIMEOBSOLETE 3600 // seconds to wait before declaring a channel obsolete (in case it has actually been seen before) -void cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid) +bool cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid) { - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (time(NULL) - channel->Seen() > CHANNELTIMEOBSOLETE && channel->Source() == Source && channel->Nid() == Nid && channel->Tid() == Tid && channel->Rid() == 0) { + bool ChannelsModified = false; + for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (time(NULL) - Channel->Seen() > CHANNELTIMEOBSOLETE && Channel->Source() == Source && Channel->Nid() == Nid && Channel->Tid() == Tid && Channel->Rid() == 0) { bool OldShowChannelNamesWithSource = Setup.ShowChannelNamesWithSource; Setup.ShowChannelNamesWithSource = false; - if (!endswith(channel->Name(), CHANNELMARKOBSOLETE)) - channel->SetName(cString::sprintf("%s %s", channel->Name(), CHANNELMARKOBSOLETE), channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, channel->Provider())); + if (!endswith(Channel->Name(), CHANNELMARKOBSOLETE)) + ChannelsModified |= Channel->SetName(cString::sprintf("%s %s", Channel->Name(), CHANNELMARKOBSOLETE), Channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, Channel->Provider())); Setup.ShowChannelNamesWithSource = OldShowChannelNamesWithSource; } } + return ChannelsModified; } cString ChannelString(const cChannel *Channel, int Number) diff -Nru vdr-2.2.0/channels.h vdr-2.3.3/channels.h --- vdr-2.2.0/channels.h 2015-02-01 13:30:26.000000000 +0000 +++ vdr-2.3.3/channels.h 2015-08-17 09:39:48.000000000 +0000 @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: channels.h 3.3 2015/02/01 13:30:26 kls Exp $ + * $Id: channels.h 4.2 2015/08/17 09:39:48 kls Exp $ */ #ifndef __CHANNELS_H @@ -28,10 +28,6 @@ #define CHANNELMOD_LANGS 0x40 #define CHANNELMOD_RETUNE (CHANNELMOD_PIDS | CHANNELMOD_CA | CHANNELMOD_TRANSP) -#define CHANNELSMOD_NONE 0 -#define CHANNELSMOD_AUTO 1 -#define CHANNELSMOD_USER 2 - #define MAXAPIDS 32 // audio #define MAXDPIDS 16 // dolby (AC3 + DTS) #define MAXSPIDS 32 // subtitles @@ -86,6 +82,7 @@ }; class cSchedule; +class cChannels; class cChannel : public cListObject { friend class cSchedules; @@ -128,7 +125,7 @@ mutable cString nameSource; mutable cString shortNameSource; cString parameters; - int modification; + mutable int modification; time_t seen; // When this channel was last seen in the SDT of its transponder mutable const cSchedule *schedule; cLinkChannels *linkChannels; @@ -188,65 +185,84 @@ bool IsTerr(void) const { return cSource::IsTerr(source); } bool IsSourceType(char Source) const { return cSource::IsType(source, Source); } tChannelID GetChannelID(void) const { return tChannelID(source, nid, (nid || tid) ? tid : Transponder(), sid, rid); } - bool HasTimer(void) const; - int Modification(int Mask = CHANNELMOD_ALL); - time_t Seen(void) { return seen; } + int Modification(int Mask = CHANNELMOD_ALL) const; + time_t Seen(void) const { return seen; } void CopyTransponderData(const cChannel *Channel); bool SetTransponderData(int Source, int Frequency, int Srate, const char *Parameters, bool Quiet = false); - void SetId(int Nid, int Tid, int Sid, int Rid = 0); - void SetLcn(int Lcn); - void SetName(const char *Name, const char *ShortName, const char *Provider); - void SetPortalName(const char *PortalName); - void SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid); - void SetCaIds(const int *CaIds); // list must be zero-terminated - void SetCaDescriptors(int Level); - void SetLinkChannels(cLinkChannels *LinkChannels); + bool SetSource(int Source); + bool SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid = 0); + bool SetLcn(int Lcn); + bool SetName(const char *Name, const char *ShortName, const char *Provider); + bool SetPortalName(const char *PortalName); + bool SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid); + bool SetCaIds(const int *CaIds); // list must be zero-terminated + bool SetCaDescriptors(int Level); + bool SetLinkChannels(cLinkChannels *LinkChannels); void SetRefChannel(cChannel *RefChannel); - void SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds); + bool SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds); void SetSeen(void); + void DelLinkChannel(cChannel *LinkChannel); }; -class cChannels : public cRwLock, public cConfig { +class cChannels : public cConfig { private: - int maxNumber; - int maxChannelNameLength; - int maxShortChannelNameLength; - int modified; - int beingEdited; + static cChannels channels; + static int maxNumber; + static int maxChannelNameLength; + static int maxShortChannelNameLength; + int modifiedByUser; cHash channelsHashSid; void DeleteDuplicateChannels(void); public: cChannels(void); - bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false); + static const cChannels *GetChannelsRead(cStateKey &StateKey, int TimeoutMs = 0); + ///< Gets the list of channels for read access. + ///< See cTimers::GetTimersRead() for details. + static cChannels *GetChannelsWrite(cStateKey &StateKey, int TimeoutMs = 0); + ///< Gets the list of channels for write access. + ///< See cTimers::GetTimersWrite() for details. + static bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false); void HashChannel(cChannel *Channel); void UnhashChannel(cChannel *Channel); - int GetNextGroup(int Idx); // Get next channel group - int GetPrevGroup(int Idx); // Get previous channel group - int GetNextNormal(int Idx); // Get next normal channel (not group) - int GetPrevNormal(int Idx); // Get previous normal channel (not group) - void ReNumber(void); // Recalculate 'number' based on channel type - cChannel *GetByNumber(int Number, int SkipGap = 0); - cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID); - cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false); - cChannel *GetByTransponderID(tChannelID ChannelID); - int BeingEdited(void) { return beingEdited; } - void IncBeingEdited(void) { beingEdited++; } - void DecBeingEdited(void) { beingEdited--; } - bool HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel = NULL); - bool SwitchTo(int Number); - int MaxNumber(void) { return maxNumber; } - int MaxChannelNameLength(void); - int MaxShortChannelNameLength(void); - void SetModified(bool ByUser = false); - int Modified(void); - ///< Returns 0 if no channels have been modified, 1 if an automatic - ///< modification has been made, and 2 if the user has made a modification. - ///< Calling this function resets the 'modified' flag to 0. + int GetNextGroup(int Idx) const; ///< Get next channel group + int GetPrevGroup(int Idx) const; ///< Get previous channel group + int GetNextNormal(int Idx) const; ///< Get next normal channel (not group) + int GetPrevNormal(int Idx) const; ///< Get previous normal channel (not group) + void ReNumber(void); ///< Recalculate 'number' based on channel type + void Del(cChannel *Channel); ///< Delete the given Channel from the list + const cChannel *GetByNumber(int Number, int SkipGap = 0) const; + cChannel *GetByNumber(int Number, int SkipGap = 0) { return const_cast(static_cast(this)->GetByNumber(Number, SkipGap)); } + const cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const; + cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) { return const_cast(static_cast(this)->GetByServiceID(Source, Transponder, ServiceID)); } + const cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) const; + cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) { return const_cast(static_cast(this)->GetByChannelID(ChannelID, TryWithoutRid, TryWithoutPolarization)); } + const cChannel *GetByTransponderID(tChannelID ChannelID) const; + cChannel *GetByTransponderID(tChannelID ChannelID) { return const_cast(static_cast(this)->GetByTransponderID(ChannelID)); } + bool HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel = NULL) const; + bool SwitchTo(int Number) const; + static int MaxNumber(void) { return maxNumber; } + static int MaxChannelNameLength(void); + static int MaxShortChannelNameLength(void); + void SetModifiedByUser(void); + bool ModifiedByUser(int &State) const; + ///< Returns true if the channels have been modified by the user since the last call + ///< to this function with the same State variable. State must be initialized with 0 + ///< and will be set to the current value of the list's internal state variable upon + ///< return from this function. cChannel *NewChannel(const cChannel *Transponder, const char *Name, const char *ShortName, const char *Provider, int Nid, int Tid, int Sid, int Rid = 0); - void MarkObsoleteChannels(int Source, int Nid, int Tid); + bool MarkObsoleteChannels(int Source, int Nid, int Tid); }; -extern cChannels Channels; +// Provide lock controlled access to the list: + +DEF_LIST_LOCK(Channels); + +// These macros provide a convenient way of locking the global channels list +// and making sure the lock is released as soon as the current scope is left +// (note that these macros wait forever to obtain the lock!): + +#define LOCK_CHANNELS_READ USE_LIST_LOCK_READ(Channels) +#define LOCK_CHANNELS_WRITE USE_LIST_LOCK_WRITE(Channels) cString ChannelString(const cChannel *Channel, int Number); diff -Nru vdr-2.2.0/ci.c vdr-2.3.3/ci.c --- vdr-2.2.0/ci.c 2015-02-02 13:57:39.000000000 +0000 +++ vdr-2.3.3/ci.c 2017-03-25 14:09:23.000000000 +0000 @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: ci.c 3.19 2015/02/02 13:57:39 kls Exp $ + * $Id: ci.c 4.9 2017/03/25 14:09:23 kls Exp $ */ #include "ci.h" @@ -13,11 +13,13 @@ #include #include #include +#include #include #include #include #include #include "device.h" +#include "mtd.h" #include "pat.h" #include "receiver.h" #include "remux.h" @@ -117,16 +119,15 @@ cVector emmPids; uchar buffer[2048]; // 11 bit length, max. 2048 byte uchar *bufp; + uchar mtdCatBuffer[TS_SIZE]; // TODO: handle multi packet CATs! int length; void AddEmmPid(int Pid); void DelEmmPids(void); -protected: - virtual void Activate(bool On); public: cCaPidReceiver(void); virtual ~cCaPidReceiver() { Detach(); } - virtual void Receive(uchar *Data, int Length); - bool HasCaPids(void) { return NumPids() - emmPids.Size() - 1 > 0; } + virtual void Receive(const uchar *Data, int Length); + bool HasCaPids(void) const { return NumPids() - emmPids.Size() - 1 > 0; } void Reset(void) { DelEmmPids(); catVersion = -1; } }; @@ -155,15 +156,11 @@ emmPids.Clear(); } -void cCaPidReceiver::Activate(bool On) -{ - catVersion = -1; // can be done independent of 'On' -} - -void cCaPidReceiver::Receive(uchar *Data, int Length) +void cCaPidReceiver::Receive(const uchar *Data, int Length) { if (TsPid(Data) == CATPID) { - uchar *p = NULL; + cMtdCamSlot *MtdCamSlot = dynamic_cast(Device()->CamSlot()); + const uchar *p = NULL; if (TsPayloadStart(Data)) { if (Data[5] == SI::TableIdCAT) { length = (int(Data[6] & 0x03) << 8) | Data[7]; // section length @@ -172,6 +169,8 @@ if (v != catVersion) { if (Data[11] == 0 && Data[12] == 0) { // section number, last section number if (length > TS_SIZE - 8) { + if (MtdCamSlot) + esyslog("ERROR: need to implement multi packet CAT handling for MTD!"); int n = TS_SIZE - 13; memcpy(buffer, Data + 13, n); bufp = buffer + n; @@ -186,6 +185,8 @@ dsyslog("multi table CAT section - unhandled!"); catVersion = v; } + else if (MtdCamSlot) + MtdCamSlot->PutCat(mtdCatBuffer, TS_SIZE); } } } @@ -207,32 +208,32 @@ } } if (p) { - int OldCatVersion = catVersion; // must preserve the current version number - cDevice *AttachedDevice = Device(); - if (AttachedDevice) - AttachedDevice->Detach(this); DelEmmPids(); for (int i = 0; i < length - 4; i++) { // -4 = checksum if (p[i] == 0x09) { int CaId = int(p[i + 2] << 8) | p[i + 3]; - int EmmPid = int(((p[i + 4] & 0x1F) << 8)) | p[i + 5]; + int EmmPid = Peek13(p + i + 4); AddEmmPid(EmmPid); + if (MtdCamSlot) + MtdMapPid(const_cast(p + i + 4), MtdCamSlot->MtdMapper()); switch (CaId >> 8) { case 0x01: for (int j = i + 7; j < p[i + 1] + 2; j += 4) { - EmmPid = (int(p[j] & 0x0F) << 8) | p[j + 1]; + EmmPid = Peek13(p + j); AddEmmPid(EmmPid); + if (MtdCamSlot) + MtdMapPid(const_cast(p + j), MtdCamSlot->MtdMapper()); } break; } i += p[i + 1] + 2 - 1; // -1 to compensate for the loop increment } } - if (AttachedDevice) - AttachedDevice->AttachReceiver(this); - catVersion = OldCatVersion; p = NULL; bufp = 0; length = 0; + memcpy(mtdCatBuffer, Data, TS_SIZE); + if (MtdCamSlot) + MtdCamSlot->PutCat(mtdCatBuffer, TS_SIZE); } } } @@ -251,7 +252,7 @@ time_t lastScrambledTime; int numTsPackets; protected: - virtual void Receive(uchar *Data, int Length); + virtual void Receive(const uchar *Data, int Length); public: cCaActivationReceiver(const cChannel *Channel, cCamSlot *CamSlot); virtual ~cCaActivationReceiver(); @@ -270,7 +271,7 @@ Detach(); } -void cCaActivationReceiver::Receive(uchar *Data, int Length) +void cCaActivationReceiver::Receive(const uchar *Data, int Length) { if (numTsPackets++ % TS_PACKET_FACTOR == 0) { time_t Now = time(NULL); @@ -279,7 +280,12 @@ else if (Now - lastScrambledTime > UNSCRAMBLE_TIME) { dsyslog("CAM %d: activated!", camSlot->SlotNumber()); Skins.QueueMessage(mtInfo, tr("CAM activated!")); + cDevice *d = Device(); Detach(); + if (d) { + if (cCamSlot *s = d->CamSlot()) + s->CancelActivation(); // this will delete *this* object, so no more code referencing *this* after this call! + } } } } @@ -756,9 +762,9 @@ friend class cCiConditionalAccessSupport; private: uint8_t cmdId; - int length; int esInfoLengthPos; - uint8_t capmt[2048]; ///< XXX is there a specified maximum? + cDynamicBuffer caDescriptors; + cDynamicBuffer capmt; int source; int transponder; int programNumber; @@ -768,8 +774,9 @@ cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds); uint8_t CmdId(void) { return cmdId; } void SetListManagement(uint8_t ListManagement); - uint8_t ListManagement(void) { return capmt[0]; } + uint8_t ListManagement(void) { return capmt.Get(0); } void AddPid(int Pid, uint8_t StreamType); + void MtdMapPids(cMtdMapper *MtdMapper); }; cCiCaPmt::cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds) @@ -784,61 +791,130 @@ caSystemIds[i] = CaSystemIds[i]; } caSystemIds[i] = 0; - uint8_t caDescriptors[512]; - int caDescriptorsLength = GetCaDescriptors(source, transponder, programNumber, caSystemIds, sizeof(caDescriptors), caDescriptors, 0); - length = 0; - capmt[length++] = CPLM_ONLY; - capmt[length++] = (ProgramNumber >> 8) & 0xFF; - capmt[length++] = ProgramNumber & 0xFF; - capmt[length++] = 0x01; // version_number, current_next_indicator - apparently vn doesn't matter, but cni must be 1 - esInfoLengthPos = length; - capmt[length++] = 0x00; // program_info_length H (at program level) - capmt[length++] = 0x00; // program_info_length L - AddCaDescriptors(caDescriptorsLength, caDescriptors); + GetCaDescriptors(source, transponder, programNumber, caSystemIds, caDescriptors, 0); + capmt.Append(CPLM_ONLY); + capmt.Append((ProgramNumber >> 8) & 0xFF); + capmt.Append( ProgramNumber & 0xFF); + capmt.Append(0x01); // version_number, current_next_indicator - apparently vn doesn't matter, but cni must be 1 + esInfoLengthPos = capmt.Length(); + capmt.Append(0x00); // program_info_length H (at program level) + capmt.Append(0x00); // program_info_length L + AddCaDescriptors(caDescriptors.Length(), caDescriptors.Data()); } void cCiCaPmt::SetListManagement(uint8_t ListManagement) { - capmt[0] = ListManagement; + capmt.Set(0, ListManagement); } void cCiCaPmt::AddPid(int Pid, uint8_t StreamType) { if (Pid) { - uint8_t caDescriptors[512]; - int caDescriptorsLength = GetCaDescriptors(source, transponder, programNumber, caSystemIds, sizeof(caDescriptors), caDescriptors, Pid); - //XXX buffer overflow check??? - capmt[length++] = StreamType; - capmt[length++] = (Pid >> 8) & 0xFF; - capmt[length++] = Pid & 0xFF; - esInfoLengthPos = length; - capmt[length++] = 0x00; // ES_info_length H (at ES level) - capmt[length++] = 0x00; // ES_info_length L - AddCaDescriptors(caDescriptorsLength, caDescriptors); + GetCaDescriptors(source, transponder, programNumber, caSystemIds, caDescriptors, Pid); + capmt.Append(StreamType); + capmt.Append((Pid >> 8) & 0xFF); + capmt.Append( Pid & 0xFF); + esInfoLengthPos = capmt.Length(); + capmt.Append(0x00); // ES_info_length H (at ES level) + capmt.Append(0x00); // ES_info_length L + AddCaDescriptors(caDescriptors.Length(), caDescriptors.Data()); } } void cCiCaPmt::AddCaDescriptors(int Length, const uint8_t *Data) { if (esInfoLengthPos) { - if (length + Length < int(sizeof(capmt))) { - if (Length || cmdId == CPCI_QUERY) { - capmt[length++] = cmdId; - memcpy(capmt + length, Data, Length); - length += Length; - int l = length - esInfoLengthPos - 2; - capmt[esInfoLengthPos] = (l >> 8) & 0xFF; - capmt[esInfoLengthPos + 1] = l & 0xFF; - } + if (Length || cmdId == CPCI_QUERY) { + capmt.Append(cmdId); + capmt.Append(Data, Length); + int l = capmt.Length() - esInfoLengthPos - 2; + capmt.Set(esInfoLengthPos, (l >> 8) & 0xFF); + capmt.Set(esInfoLengthPos + 1, l & 0xFF); } - else - esyslog("ERROR: buffer overflow in CA descriptor"); esInfoLengthPos = 0; } else esyslog("ERROR: adding CA descriptor without Pid!"); } +static int MtdMapCaDescriptor(uchar *p, cMtdMapper *MtdMapper) +{ + // See pat.c: cCaDescriptor::cCaDescriptor() for the layout of the data! + if (*p == SI::CaDescriptorTag) { + int l = *++p; + if (l >= 4) { + MtdMapPid(p + 3, MtdMapper); + return l + 2; + } + else + esyslog("ERROR: wrong length (%d) in MtdMapCaDescriptor()", l); + } + else + esyslog("ERROR: wrong tag (%d) in MtdMapCaDescriptor()", *p); + return -1; +} + +static int MtdMapCaDescriptors(uchar *p, cMtdMapper *MtdMapper) +{ + int Length = p[0] * 256 + p[1]; + if (Length >= 3) { + p += 3; + int m = Length - 1; + while (m > 0) { + int l = MtdMapCaDescriptor(p, MtdMapper); + if (l > 0) { + p += l; + m -= l; + } + } + } + return Length + 2; +} + +static int MtdMapStream(uchar *p, cMtdMapper *MtdMapper) +{ + // See ci.c: cCiCaPmt::AddPid() for the layout of the data! + MtdMapPid(p + 1, MtdMapper); + int l = MtdMapCaDescriptors(p + 3, MtdMapper); + if (l > 0) + return l + 3; + return -1; +} + +static int MtdMapStreams(uchar *p, cMtdMapper *MtdMapper, int Length) +{ + int m = Length; + while (m >= 5) { + int l = MtdMapStream(p, MtdMapper); + if (l > 0) { + p += l; + m -= l; + } + else + break; + } + return Length; +} + +void cCiCaPmt::MtdMapPids(cMtdMapper *MtdMapper) +{ + uchar *p = capmt.Data(); + int m = capmt.Length(); + if (m >= 3) { + MtdMapSid(p + 1, MtdMapper); + p += 4; + m -= 4; + if (m >= 2) { + int l = MtdMapCaDescriptors(p, MtdMapper); + if (l >= 0) { + p += l; + m -= l; + MtdMapStreams(p, MtdMapper, m); + } + } + } +} + // --- cCiConditionalAccessSupport ------------------------------------------- // CA Enable Ids: @@ -919,8 +995,12 @@ case AOT_CA_PMT_REPLY: { dbgprotocol("Slot %d: <== Ca Pmt Reply (%d)", Tc()->CamSlot()->SlotNumber(), SessionId()); if (!repliesToQuery) { - dsyslog("CAM %d: replies to QUERY - multi channel decryption possible", Tc()->CamSlot()->SlotNumber()); + dsyslog("CAM %d: replies to QUERY - multi channel decryption (MCD) possible", Tc()->CamSlot()->SlotNumber()); repliesToQuery = true; + if (Tc()->CamSlot()->MtdAvailable()) { + dsyslog("CAM %d: supports multi transponder decryption (MTD)", Tc()->CamSlot()->SlotNumber()); + Tc()->CamSlot()->MtdActivate(true); + } } state = 5; // got ca pmt reply int l = 0; @@ -995,7 +1075,7 @@ { if (CaPmt && state >= 2) { dbgprotocol("Slot %d: ==> Ca Pmt (%d) %d %d\n", Tc()->CamSlot()->SlotNumber(), SessionId(), CaPmt->ListManagement(), CaPmt->CmdId()); - SendData(AOT_CA_PMT, CaPmt->length, CaPmt->capmt); + SendData(AOT_CA_PMT, CaPmt->capmt.Length(), CaPmt->capmt.Data()); state = 4; // sent ca pmt } } @@ -1672,6 +1752,14 @@ programNumber = ProgramNumber; modified = false; } + bool Active(void) + { + for (cCiCaPidData *p = pidList.First(); p; p = pidList.Next(p)) { + if (p->active) + return true; + } + return false; + } }; // --- cCiAdapter ------------------------------------------------------------ @@ -1739,55 +1827,79 @@ #define MODULE_CHECK_INTERVAL 500 // ms #define MODULE_RESET_TIMEOUT 2 // s -cCamSlot::cCamSlot(cCiAdapter *CiAdapter, bool ReceiveCaPids) +cCamSlot::cCamSlot(cCiAdapter *CiAdapter, bool WantsTsData, cCamSlot *MasterSlot) { ciAdapter = CiAdapter; + masterSlot = MasterSlot; assignedDevice = NULL; - caPidReceiver = ReceiveCaPids ? new cCaPidReceiver : NULL; + caPidReceiver = WantsTsData ? new cCaPidReceiver : NULL; caActivationReceiver = NULL; slotIndex = -1; + mtdAvailable = false; + mtdHandler = NULL; lastModuleStatus = msReset; // avoids initial reset log message resetTime = 0; resendPmt = false; - source = transponder = 0; for (int i = 0; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) // tc[0] is not used, but initialized anyway tc[i] = NULL; - CamSlots.Add(this); - slotNumber = Index() + 1; - if (ciAdapter) + if (MasterSlot) + slotNumber = MasterSlot->SlotNumber(); + if (ciAdapter) { + CamSlots.Add(this); + slotNumber = Index() + 1; ciAdapter->AddCamSlot(this); - Reset(); + Reset(); + } } cCamSlot::~cCamSlot() { - if (assignedDevice) - assignedDevice->SetCamSlot(NULL); + Assign(NULL); delete caPidReceiver; delete caActivationReceiver; CamSlots.Del(this, false); DeleteAllConnections(); + delete mtdHandler; +} + +cCamSlot *cCamSlot::MtdSpawn(void) +{ + cMutexLock MutexLock(&mutex); + if (mtdHandler) + return mtdHandler->GetMtdCamSlot(this); + return this; } bool cCamSlot::Assign(cDevice *Device, bool Query) { cMutexLock MutexLock(&mutex); + if (Device == assignedDevice) + return true; if (ciAdapter) { + int OldDeviceNumber = 0; + if (assignedDevice && !Query) { + OldDeviceNumber = assignedDevice->DeviceNumber() + 1; + if (caPidReceiver) + assignedDevice->Detach(caPidReceiver); + assignedDevice->SetCamSlot(NULL); + assignedDevice = NULL; + } if (ciAdapter->Assign(Device, true)) { - if (!Device && assignedDevice) - assignedDevice->SetCamSlot(NULL); - if (!Query || !Device) { + if (!Query) { StopDecrypting(); - source = transponder = 0; if (ciAdapter->Assign(Device)) { - assignedDevice = Device; if (Device) { Device->SetCamSlot(this); - dsyslog("CAM %d: assigned to device %d", slotNumber, Device->DeviceNumber() + 1); + assignedDevice = Device; + if (caPidReceiver) { + caPidReceiver->Reset(); + Device->AttachReceiver(caPidReceiver); + } + dsyslog("CAM %d: assigned to device %d", MasterSlotNumber(), Device->DeviceNumber() + 1); } else { CancelActivation(); - dsyslog("CAM %d: unassigned", slotNumber); + dsyslog("CAM %d: unassigned from device %d", MasterSlotNumber(), OldDeviceNumber); } } else @@ -1799,6 +1911,16 @@ return false; } +bool cCamSlot::Devices(cVector &CardIndexes) +{ + cMutexLock MutexLock(&mutex); + if (mtdHandler) + return mtdHandler->Devices(CardIndexes); + if (assignedDevice) + CardIndexes.Append(assignedDevice->CardIndex()); + return CardIndexes.Size() > 0; +} + void cCamSlot::NewConnection(void) { cMutexLock MutexLock(&mutex); @@ -1824,8 +1946,6 @@ void cCamSlot::Process(cTPDU *TPDU) { cMutexLock MutexLock(&mutex); - if (caActivationReceiver && !caActivationReceiver->IsAttached()) - CancelActivation(); if (TPDU) { int n = TPDU->Tcid(); if (1 <= n && n <= MAX_CONNECTIONS_PER_CAM_SLOT) { @@ -1836,9 +1956,9 @@ for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) { if (tc[i]) { if (!tc[i]->Process()) { - Reset(); - return; - } + Reset(); + return; + } } } if (moduleCheckTimer.TimedOut()) { @@ -1848,6 +1968,7 @@ case msNone: dbgprotocol("Slot %d: no module present\n", slotNumber); isyslog("CAM %d: no module present", slotNumber); + MtdActivate(false); DeleteAllConnections(); CancelActivation(); break; @@ -1864,7 +1985,7 @@ dbgprotocol("Slot %d: module ready\n", slotNumber); isyslog("CAM %d: module ready", slotNumber); NewConnection(); - resendPmt = caProgramList.Count() > 0; + resendPmt = true; break; default: esyslog("ERROR: unknown module status %d (%s)", ms, __FUNCTION__); @@ -1873,8 +1994,14 @@ } moduleCheckTimer.Set(MODULE_CHECK_INTERVAL); } - if (resendPmt) - SendCaPmt(CPCI_OK_DESCRAMBLING); + if (resendPmt && Ready()) { + if (mtdHandler) { + mtdHandler->StartDecrypting(); + resendPmt = false; + } + else if (caProgramList.Count()) + StartDecrypting(); + } processed.Broadcast(); } @@ -1921,7 +2048,8 @@ cMutexLock MutexLock(&mutex); if (!caActivationReceiver) { if (cDevice *d = Device()) { - if (cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel())) { + LOCK_CHANNELS_READ; + if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) { caActivationReceiver = new cCaActivationReceiver(Channel, this); d->AttachReceiver(caActivationReceiver); dsyslog("CAM %d: activating on device %d with channel %d (%s)", SlotNumber(), d->DeviceNumber() + 1, Channel->Number(), Channel->Name()); @@ -1933,12 +2061,18 @@ void cCamSlot::CancelActivation(void) { cMutexLock MutexLock(&mutex); - delete caActivationReceiver; - caActivationReceiver = NULL; + if (mtdHandler) + mtdHandler->CancelActivation(); + else { + delete caActivationReceiver; + caActivationReceiver = NULL; + } } bool cCamSlot::IsActivating(void) { + if (mtdHandler) + return mtdHandler->IsActivating(); return caActivationReceiver; } @@ -2012,67 +2146,115 @@ return NULL; } -void cCamSlot::SendCaPmt(uint8_t CmdId) +cCiCaPmtList::~cCiCaPmtList() +{ + for (int i = 0; i < caPmts.Size(); i++) + delete caPmts[i]; +} + +cCiCaPmt *cCiCaPmtList::Add(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds) +{ + cCiCaPmt *p = new cCiCaPmt(CmdId, Source, Transponder, ProgramNumber, CaSystemIds); + caPmts.Append(p); + return p; +} + +void cCiCaPmtList::Del(cCiCaPmt *CaPmt) +{ + if (caPmts.RemoveElement(CaPmt)) + delete CaPmt; +} + +bool cCamSlot::RepliesToQuery(void) { cMutexLock MutexLock(&mutex); cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); - if (cas) { - const int *CaSystemIds = cas->GetCaSystemIds(); - if (CaSystemIds && *CaSystemIds) { - if (caProgramList.Count()) { - if (caPidReceiver && caPidReceiver->HasCaPids()) { - if (cDevice *d = Device()) - d->Detach(caPidReceiver); - } - for (int Loop = 1; Loop <= 2; Loop++) { - for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { - if (p->modified || resendPmt) { - bool Active = false; - cCiCaPmt CaPmt(CmdId, source, transponder, p->programNumber, CaSystemIds); - for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { - if (q->active) { - CaPmt.AddPid(q->pid, q->streamType); - Active = true; - } - } - if ((Loop == 1) != Active) { // first remove, then add - if (caPidReceiver) { - int CaPids[MAXRECEIVEPIDS + 1]; - if (GetCaPids(source, transponder, p->programNumber, CaSystemIds, MAXRECEIVEPIDS + 1, CaPids) > 0) { - if (Loop == 1) - caPidReceiver->DelPids(CaPids); - else - caPidReceiver->AddPids(CaPids); - } - } - if (cas->RepliesToQuery()) - CaPmt.SetListManagement(Active ? CPLM_ADD : CPLM_UPDATE); - if (Active || cas->RepliesToQuery()) - cas->SendPMT(&CaPmt); - p->modified = false; - } - } + return cas && cas->RepliesToQuery(); +} + +void cCamSlot::BuildCaPmts(uint8_t CmdId, cCiCaPmtList &CaPmtList, cMtdMapper *MtdMapper) +{ + cMutexLock MutexLock(&mutex); + CaPmtList.caPmts.Clear(); + const int *CaSystemIds = GetCaSystemIds(); + if (CaSystemIds && *CaSystemIds) { + if (caProgramList.Count()) { + for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { + if (p->modified || resendPmt) { + bool Active = p->Active(); + cCiCaPmt *CaPmt = CaPmtList.Add(Active ? CmdId : CPCI_NOT_SELECTED, source, transponder, p->programNumber, CaSystemIds); + for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { + if (q->active) + CaPmt->AddPid(q->pid, q->streamType); } + if (caPidReceiver) { + int CaPids[MAXRECEIVEPIDS + 1]; + if (GetCaPids(source, transponder, p->programNumber, CaSystemIds, MAXRECEIVEPIDS + 1, CaPids) > 0) { + if (Active) + caPidReceiver->AddPids(CaPids); + else + caPidReceiver->DelPids(CaPids); + } + } + if (RepliesToQuery()) + CaPmt->SetListManagement(Active ? CPLM_ADD : CPLM_UPDATE); + if (MtdMapper) + CaPmt->MtdMapPids(MtdMapper); + p->modified = false; } - if (caPidReceiver && caPidReceiver->HasCaPids()) { - if (cDevice *d = Device()) - d->AttachReceiver(caPidReceiver); - } - resendPmt = false; - } - else { - cCiCaPmt CaPmt(CmdId, 0, 0, 0, NULL); - cas->SendPMT(&CaPmt); - if (caPidReceiver) { - if (cDevice *d = Device()) - d->Detach(caPidReceiver); - caPidReceiver->Reset(); - } + } + } + else if (CmdId == CPCI_NOT_SELECTED) + CaPmtList.Add(CmdId, 0, 0, 0, NULL); + } +} + +void cCamSlot::SendCaPmts(cCiCaPmtList &CaPmtList) +{ + cMutexLock MutexLock(&mutex); + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); + if (cas) { + for (int i = 0; i < CaPmtList.caPmts.Size(); i++) + cas->SendPMT(CaPmtList.caPmts[i]); + } + resendPmt = false; +} + +void cCamSlot::SendCaPmt(uint8_t CmdId) +{ + cMutexLock MutexLock(&mutex); + cCiCaPmtList CaPmtList; + BuildCaPmts(CmdId, CaPmtList); + SendCaPmts(CaPmtList); +} + +void cCamSlot::MtdEnable(void) +{ + mtdAvailable = true; +} + +void cCamSlot::MtdActivate(bool On) +{ + if (McdAvailable() && MtdAvailable()) { + if (On) { + if (!mtdHandler) { + dsyslog("CAM %d: activating MTD support", SlotNumber()); + mtdHandler = new cMtdHandler; } } + else if (mtdHandler) { + dsyslog("CAM %d: deactivating MTD support", SlotNumber()); + delete mtdHandler; + mtdHandler = NULL; + } } } +int cCamSlot::MtdPutData(uchar *Data, int Count) +{ + return mtdHandler->Put(Data, Count); +} + const int *cCamSlot::GetCaSystemIds(void) { cMutexLock MutexLock(&mutex); @@ -2082,6 +2264,8 @@ int cCamSlot::Priority(void) { + if (mtdHandler) + return mtdHandler->Priority(); cDevice *d = Device(); return d ? d->Priority() : IDLEPRIORITY; } @@ -2131,7 +2315,7 @@ } return; } - } + } } } @@ -2160,7 +2344,7 @@ #define QUERY_REPLY_WAIT 100 // ms to wait between checks for a reply -bool cCamSlot::CanDecrypt(const cChannel *Channel) +bool cCamSlot::CanDecrypt(const cChannel *Channel, cMtdMapper *MtdMapper) { if (Channel->Ca() < CA_ENCRYPTED_MIN) return true; // channel not encrypted @@ -2178,6 +2362,8 @@ CaPmt.AddPid(*Dpid, STREAM_TYPE_PRIVATE); for (const int *Spid = Channel->Spids(); *Spid; Spid++) CaPmt.AddPid(*Spid, STREAM_TYPE_PRIVATE); + if (MtdMapper) + CaPmt.MtdMapPids(MtdMapper); cas->SendPMT(&CaPmt); cTimeMs Timeout(QUERY_REPLY_TIMEOUT); do { @@ -2204,13 +2390,16 @@ cMutexLock MutexLock(&mutex); if (caProgramList.Count()) { caProgramList.Clear(); - SendCaPmt(CPCI_NOT_SELECTED); + if (!dynamic_cast(this)) + SendCaPmt(CPCI_NOT_SELECTED); } } bool cCamSlot::IsDecrypting(void) { cMutexLock MutexLock(&mutex); + if (mtdHandler) + return mtdHandler->IsDecrypting(); if (caProgramList.Count()) { for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { if (p->modified) @@ -2234,10 +2423,21 @@ cCamSlots CamSlots; +int cCamSlots::NumReadyMasterSlots(void) +{ + int n = 0; + for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) { + if (CamSlot->IsMasterSlot() && CamSlot->ModuleStatus() == msReady) + n++; + } + return n; +} + bool cCamSlots::WaitForAllCamSlotsReady(int Timeout) { + bool ready = true; for (time_t t0 = time(NULL); time(NULL) - t0 < Timeout; ) { - bool ready = true; + ready = true; for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) { if (!CamSlot->Ready()) { ready = false; @@ -2245,9 +2445,11 @@ } } if (ready) - return true; + break; } - return false; + for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) + dsyslog("CAM %d: %sready, %s", CamSlot->SlotNumber(), CamSlot->Ready() ? "" : "not ", CamSlot->IsMasterSlot() ? *cString::sprintf("master (%s)", CamSlot->GetCamName() ? CamSlot->GetCamName() : "empty") : *cString::sprintf("slave of CAM %d", CamSlot->MasterSlotNumber())); + return ready; } // --- cChannelCamRelation --------------------------------------------------- @@ -2325,6 +2527,7 @@ // --- cChannelCamRelations -------------------------------------------------- +#define MAX_CAM_NUMBER 32 #define CHANNEL_CAM_RELATIONS_CLEANUP_INTERVAL 3600 // seconds between cleanups cChannelCamRelations ChannelCamRelations; @@ -2422,3 +2625,60 @@ if (ccr) ccr->ClrDecrypt(CamSlotNumber); } + +void cChannelCamRelations::Load(const char *FileName) +{ + cMutexLock MutexLock(&mutex); + fileName = FileName; + if (access(fileName, R_OK) == 0) { + dsyslog("loading %s", *fileName); + if (FILE *f = fopen(fileName, "r")) { + cReadLine ReadLine; + char *s; + while ((s = ReadLine.Read(f)) != NULL) { + if (char *p = strchr(s, ' ')) { + *p = 0; + if (*++p) { + tChannelID ChannelID = tChannelID::FromString(s); + if (ChannelID.Valid()) { + char *q; + char *strtok_next; + while ((q = strtok_r(p, " ", &strtok_next)) != NULL) { + int CamSlotNumber = atoi(q); + if (CamSlotNumber >= 1 && CamSlotNumber <= MAX_CAM_NUMBER) + SetDecrypt(ChannelID, CamSlotNumber); + p = NULL; + } + } + } + } + } + fclose(f); + } + else + LOG_ERROR_STR(*fileName); + } +} + +void cChannelCamRelations::Save(void) +{ + cMutexLock MutexLock(&mutex); + dsyslog("saving %s", *fileName); + cSafeFile f(fileName); + if (f.Open()) { + for (cChannelCamRelation *ccr = First(); ccr; ccr = Next(ccr)) { + if (ccr->ChannelID().Valid()) { + cString s; + for (int i = 1; i <= MAX_CAM_NUMBER; i++) { + if (ccr->CamDecrypt(i)) + s = cString::sprintf("%s%s%d", *s ? *s : "", *s ? " " : "", i); + } + if (*s) + fprintf(f, "%s %s\n", *ccr->ChannelID().ToString(), *s); + } + } + f.Close(); + } + else + LOG_ERROR_STR(*fileName); +} diff -Nru vdr-2.2.0/ci.h vdr-2.3.3/ci.h --- vdr-2.2.0/ci.h 2015-01-31 14:36:41.000000000 +0000 +++ vdr-2.3.3/ci.h 2017-03-23 14:23:33.000000000 +0000 @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: ci.h 3.11 2015/01/31 14:36:41 kls Exp $ + * $Id: ci.h 4.5 2017/03/23 14:23:33 kls Exp $ */ #ifndef __CI_H @@ -13,10 +13,11 @@ #include #include #include "channels.h" +#include "ringbuffer.h" #include "thread.h" #include "tools.h" -#define MAX_CAM_SLOTS_PER_ADAPTER 8 // maximum possible value is 255 +#define MAX_CAM_SLOTS_PER_ADAPTER 16 // maximum possible value is 255 (same value as MAXDEVICES!) #define MAX_CONNECTIONS_PER_CAM_SLOT 8 // maximum possible value is 254 #define CAM_READ_TIMEOUT 50 // ms @@ -124,14 +125,28 @@ class cCiCaProgramData; class cCaPidReceiver; class cCaActivationReceiver; +class cMtdHandler; +class cMtdMapper; +class cMtdCamSlot; +class cCiCaPmt; + +struct cCiCaPmtList { + cVector caPmts; + ~cCiCaPmtList(); + cCiCaPmt *Add(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds); + void Del(cCiCaPmt *CaPmt); + }; class cCamSlot : public cListObject { friend class cCiAdapter; friend class cCiTransportConnection; + friend class cCiConditionalAccessSupport; + friend class cMtdCamSlot; private: cMutex mutex; cCondVar processed; cCiAdapter *ciAdapter; + cCamSlot *masterSlot; cDevice *assignedDevice; cCaPidReceiver *caPidReceiver; cCaActivationReceiver *caActivationReceiver; @@ -145,23 +160,70 @@ int source; int transponder; cList caProgramList; - const int *GetCaSystemIds(void); - void SendCaPmt(uint8_t CmdId); + bool mtdAvailable; + cMtdHandler *mtdHandler; void NewConnection(void); void DeleteAllConnections(void); void Process(cTPDU *TPDU = NULL); void Write(cTPDU *TPDU); cCiSession *GetSessionByResourceId(uint32_t ResourceId); + void MtdActivate(bool On); + ///< Activates (On == true) or deactivates (On == false) MTD. +protected: + virtual const int *GetCaSystemIds(void); + virtual void SendCaPmt(uint8_t CmdId); + virtual bool RepliesToQuery(void); + ///< Returns true if the CAM in this slot replies to queries and thus + ///< supports MCD ("Multi Channel Decryption"). + void BuildCaPmts(uint8_t CmdId, cCiCaPmtList &CaPmtList, cMtdMapper *MtdMapper = NULL); + ///< Generates all CA_PMTs with the given CmdId and stores them in the given CaPmtList. + ///< If MtdMapper is given, all SIDs and PIDs will be mapped accordingly. + void SendCaPmts(cCiCaPmtList &CaPmtList); + ///< Sends the given list of CA_PMTs to the CAM. + void MtdEnable(void); + ///< Enables MTD support for this CAM. Note that actual MTD operation also + ///< requires a CAM that supports MCD ("Multi Channel Decryption"). + int MtdPutData(uchar *Data, int Count); + ///< Sends at most Count bytes of the given Data to the individual MTD CAM slots + ///< that are using this CAM. Data must point to the beginning of a TS packet. + ///< Returns the number of bytes actually processed. public: - cCamSlot(cCiAdapter *CiAdapter, bool WantsTsData = false); + bool McdAvailable(void) { return RepliesToQuery(); } + ///< Returns true if this CAM supports MCD ("Multi Channel Decyption"). + bool MtdAvailable(void) { return mtdAvailable; } + ///< Returns true if this CAM supports MTD ("Multi Transponder Decryption"). + bool MtdActive(void) { return mtdHandler != NULL; } + ///< Returns true if MTD is currently active. +public: + cCamSlot(cCiAdapter *CiAdapter, bool WantsTsData = false, cCamSlot *MasterSlot = NULL); ///< Creates a new CAM slot for the given CiAdapter. ///< The CiAdapter will take care of deleting the CAM slot, ///< so the caller must not delete it! ///< If WantsTsData is true, the device this CAM slot is assigned to will ///< call the Decrypt() function of this CAM slot, presenting it the complete ///< TS data stream of the encrypted programme, including the CA pids. + ///< If this CAM slot is basically the same as an other one, MasterSlot can + ///< be given to indicate this. This can be used for instance for CAM slots + ///< that can do MTD ("Multi Transponder Decryption"), where the first cCamSlot + ///< is created without giving a MasterSlot, and all others are given the first + ///< one as their MasterSlot. This can speed up the search for a suitable CAM + ///< when tuning to an encrypted channel, and it also makes the Setup/CAM menu + ///< clearer because only the master CAM slots will be shown there. virtual ~cCamSlot(); - bool Assign(cDevice *Device, bool Query = false); + bool IsMasterSlot(void) { return !masterSlot; } + ///< Returns true if this CAM slot itself is a master slot (which means that + ///< it doesn't have pointer to another CAM slot that's its master). + cCamSlot *MasterSlot(void) { return masterSlot ? masterSlot : this; } + ///< Returns this CAM slot's master slot, or a pointer to itself if it is a + ///< master slot. + cCamSlot *MtdSpawn(void); + ///< If this CAM slot can do MTD ("Multi Transponder Decryption"), + ///< a call to this function returns a cMtdCamSlot with this CAM slot + ///< as its master. Otherwise a pointer to this object is returned, which + ///< means that MTD is not supported. + void TriggerResendPmt(void) { resendPmt = true; } + ///< Tells this CAM slot to resend the list of CA_PMTs to the CAM. + virtual bool Assign(cDevice *Device, bool Query = false); ///< Assigns this CAM slot to the given Device, if this is possible. ///< If Query is 'true', the CI adapter of this slot only checks whether ///< it can be assigned to the Device, but doesn't actually assign itself to it. @@ -170,8 +232,16 @@ ///< device it was previously assigned to. The value of Query ///< is ignored in that case, and this function always returns ///< 'true'. + ///< If a derived class reimplements this function, it can return 'false' + ///< if this CAM can't be assigned to the given Device. If the CAM can be + ///< assigned to the Device, or if Device is NULL, it must call the base + ///< class function. cDevice *Device(void) { return assignedDevice; } ///< Returns the device this CAM slot is currently assigned to. + bool Devices(cVector &CardIndexes); + ///< Adds the card indexes of any devices that currently use this CAM to + ///< the given CardIndexes. This can be more than one in case of MTD. + ///< Returns true if the array is not empty. bool WantsTsData(void) const { return caPidReceiver != NULL; } ///< Returns true if this CAM slot wants to receive the TS data through ///< its Decrypt() function. @@ -181,6 +251,9 @@ int SlotNumber(void) { return slotNumber; } ///< Returns the number of this CAM slot within the whole system. ///< The first slot has the number 1. + int MasterSlotNumber(void) { return masterSlot ? masterSlot->SlotNumber() : slotNumber; } + ///< Returns the number of this CAM's master slot within the whole system. + ///< The first slot has the number 1. virtual bool Reset(void); ///< Resets the CAM in this slot. ///< Returns true if the operation was successful. @@ -241,7 +314,7 @@ ///< If the source or transponder of the channel are different than ///< what was given in a previous call to AddChannel(), any previously ///< added PIDs will be cleared. - virtual bool CanDecrypt(const cChannel *Channel); + virtual bool CanDecrypt(const cChannel *Channel, cMtdMapper *MtdMapper = NULL); ///< Returns true if there is a CAM in this slot that is able to decrypt ///< the given Channel (or at least claims to be able to do so). ///< Since the QUERY/REPLY mechanism for CAMs is pretty unreliable (some @@ -252,11 +325,18 @@ ///< to the initial QUERY will perform this check at all. CAMs that never ///< replied to the initial QUERY are assumed not to be able to handle ///< more than one channel at a time. + ///< If MtdMapper is given, all SIDs and PIDs will be mapped accordingly. virtual void StartDecrypting(void); - ///< Triggers sending all currently active CA_PMT entries to the CAM, - ///< so that it will start decrypting. + ///< Sends all CA_PMT entries to the CAM that have been modified since the + ///< last call to this function. This includes CA_PMTs that have been + ///< added or activated, as well as ones that have been deactivated. + ///< StartDecrypting() will be called whenever a PID is activated or + ///< deactivated. virtual void StopDecrypting(void); ///< Clears the list of CA_PMT entries and tells the CAM to stop decrypting. + ///< Note that this function is only called when there are no more PIDs for + ///< the CAM to decrypt. There is no symmetry between StartDecrypting() and + ///< StopDecrypting(). virtual bool IsDecrypting(void); ///< Returns true if the CAM in this slot is currently used for decrypting. virtual uchar *Decrypt(uchar *Data, int &Count); @@ -287,7 +367,8 @@ ///< the CAM's control). If no decrypted TS packet is currently available, NULL ///< shall be returned. If no data from Data can currently be processed, Count ///< shall be set to 0 and the same Data pointer will be offered in the next - ///< call to Decrypt(). + ///< call to Decrypt(). See mtd.h for further requirements if this CAM can + ///< do MTD ("Multi Transponder Decryption"). ///< A derived class that implements this function will also need ///< to set the WantsTsData parameter in the call to the base class ///< constructor to true in order to receive the TS data. @@ -295,6 +376,9 @@ class cCamSlots : public cList { public: + int NumReadyMasterSlots(void); + ///< Returns the number of master CAM slots in the system that are ready + ///< to decrypt. bool WaitForAllCamSlotsReady(int Timeout = 0); ///< Waits until all CAM slots have become ready, or the given Timeout ///< (seconds) has expired. While waiting, the Ready() function of each @@ -310,6 +394,7 @@ class cChannelCamRelations : public cList { private: cMutex mutex; + cString fileName; cChannelCamRelation *GetEntry(tChannelID ChannelID); cChannelCamRelation *AddEntry(tChannelID ChannelID); time_t lastCleanup; @@ -323,6 +408,8 @@ void SetDecrypt(tChannelID ChannelID, int CamSlotNumber); void ClrChecked(tChannelID ChannelID, int CamSlotNumber); void ClrDecrypt(tChannelID ChannelID, int CamSlotNumber); + void Load(const char *FileName); + void Save(void); }; extern cChannelCamRelations ChannelCamRelations; diff -Nru vdr-2.2.0/config.c vdr-2.3.3/config.c --- vdr-2.2.0/config.c 2015-02-10 12:24:13.000000000 +0000 +++ vdr-2.3.3/config.c 2017-02-14 11:02:48.000000000 +0000 @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.c 3.10 2015/02/10 12:24:13 kls Exp $ + * $Id: config.c 4.5 2017/02/14 11:02:48 kls Exp $ */ #include "config.h" @@ -412,12 +412,16 @@ EPGBugfixLevel = 3; EPGLinger = 0; SVDRPTimeout = 300; + SVDRPPeering = 0; + strn0cpy(SVDRPHostName, GetHostName(), sizeof(SVDRPHostName)); + strcpy(SVDRPDefaultHost, ""); ZapTimeout = 3; ChannelEntryTimeout = 1000; RcRepeatDelay = 300; RcRepeatDelta = 100; DefaultPriority = 50; DefaultLifetime = MAXLIFETIME; + RecordKeyHandling = 2; PauseKeyHandling = 2; PausePriority = 10; PauseLifetime = 1; @@ -427,6 +431,7 @@ RecordingDirs = 1; FoldersInTimerMenu = 1; AlwaysSortFoldersFirst = 1; + DefaultSortModeRec = rsmTime; NumberKeysForChars = 1; ColorKey0 = 0; ColorKey1 = 1; @@ -634,12 +639,16 @@ else if (!strcasecmp(Name, "EPGBugfixLevel")) EPGBugfixLevel = atoi(Value); else if (!strcasecmp(Name, "EPGLinger")) EPGLinger = atoi(Value); else if (!strcasecmp(Name, "SVDRPTimeout")) SVDRPTimeout = atoi(Value); + else if (!strcasecmp(Name, "SVDRPPeering")) SVDRPPeering = atoi(Value); + else if (!strcasecmp(Name, "SVDRPHostName")) { if (!*SVDRPHostName) strn0cpy(SVDRPHostName, Value, sizeof(SVDRPHostName)); } + else if (!strcasecmp(Name, "SVDRPdefaultHost")) strn0cpy(SVDRPDefaultHost, Value, sizeof(SVDRPDefaultHost)); else if (!strcasecmp(Name, "ZapTimeout")) ZapTimeout = atoi(Value); else if (!strcasecmp(Name, "ChannelEntryTimeout")) ChannelEntryTimeout= atoi(Value); else if (!strcasecmp(Name, "RcRepeatDelay")) RcRepeatDelay = atoi(Value); else if (!strcasecmp(Name, "RcRepeatDelta")) RcRepeatDelta = atoi(Value); else if (!strcasecmp(Name, "DefaultPriority")) DefaultPriority = atoi(Value); else if (!strcasecmp(Name, "DefaultLifetime")) DefaultLifetime = atoi(Value); + else if (!strcasecmp(Name, "RecordKeyHandling")) RecordKeyHandling = atoi(Value); else if (!strcasecmp(Name, "PauseKeyHandling")) PauseKeyHandling = atoi(Value); else if (!strcasecmp(Name, "PausePriority")) PausePriority = atoi(Value); else if (!strcasecmp(Name, "PauseLifetime")) PauseLifetime = atoi(Value); @@ -649,6 +658,7 @@ else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value); else if (!strcasecmp(Name, "FoldersInTimerMenu")) FoldersInTimerMenu = atoi(Value); else if (!strcasecmp(Name, "AlwaysSortFoldersFirst")) AlwaysSortFoldersFirst = atoi(Value); + else if (!strcasecmp(Name, "DefaultSortModeRec")) DefaultSortModeRec = atoi(Value); else if (!strcasecmp(Name, "NumberKeysForChars")) NumberKeysForChars = atoi(Value); else if (!strcasecmp(Name, "ColorKey0")) ColorKey0 = atoi(Value); else if (!strcasecmp(Name, "ColorKey1")) ColorKey1 = atoi(Value); @@ -760,12 +770,16 @@ Store("EPGBugfixLevel", EPGBugfixLevel); Store("EPGLinger", EPGLinger); Store("SVDRPTimeout", SVDRPTimeout); + Store("SVDRPPeering", SVDRPPeering); + Store("SVDRPHostName", SVDRPHostName); + Store("SVDRPDefaultHost", SVDRPDefaultHost); Store("ZapTimeout", ZapTimeout); Store("ChannelEntryTimeout",ChannelEntryTimeout); Store("RcRepeatDelay", RcRepeatDelay); Store("RcRepeatDelta", RcRepeatDelta); Store("DefaultPriority", DefaultPriority); Store("DefaultLifetime", DefaultLifetime); + Store("RecordKeyHandling", RecordKeyHandling); Store("PauseKeyHandling", PauseKeyHandling); Store("PausePriority", PausePriority); Store("PauseLifetime", PauseLifetime); @@ -775,6 +789,7 @@ Store("RecordingDirs", RecordingDirs); Store("FoldersInTimerMenu", FoldersInTimerMenu); Store("AlwaysSortFoldersFirst", AlwaysSortFoldersFirst); + Store("DefaultSortModeRec", DefaultSortModeRec); Store("NumberKeysForChars", NumberKeysForChars); Store("ColorKey0", ColorKey0); Store("ColorKey1", ColorKey1); diff -Nru vdr-2.2.0/config.h vdr-2.3.3/config.h --- vdr-2.2.0/config.h 2015-02-13 15:39:08.000000000 +0000 +++ vdr-2.3.3/config.h 2016-12-27 11:45:25.000000000 +0000 @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 3.21 2015/02/13 15:39:08 kls Exp $ + * $Id: config.h 4.7 2016/12/27 11:45:25 kls Exp $ */ #ifndef __CONFIG_H @@ -22,13 +22,13 @@ // VDR's own version number: -#define VDRVERSION "2.2.0" -#define VDRVERSNUM 20200 // Version * 10000 + Major * 100 + Minor +#define VDRVERSION "2.3.3" +#define VDRVERSNUM 20303 // Version * 10000 + Major * 100 + Minor // The plugin API's version number: -#define APIVERSION "2.2.0" -#define APIVERSNUM 20200 // Version * 10000 + Major * 100 + Minor +#define APIVERSION "2.3.3" +#define APIVERSNUM 20303 // Version * 10000 + Major * 100 + Minor // When loading plugins, VDR searches them by their APIVERSION, which // may be smaller than VDRVERSION in case there have been no changes to @@ -110,7 +110,7 @@ cList::Clear(); } public: - cConfig(void) { fileName = NULL; } + cConfig(const char *NeedsLocking = NULL): cList(NeedsLocking) { fileName = NULL; } virtual ~cConfig() { free(fileName); } const char *FileName(void) { return fileName; } bool Load(const char *FileName = NULL, bool AllowComments = false, bool MustExist = false) @@ -160,7 +160,7 @@ fprintf(stderr, "vdr: error while reading '%s'\n", fileName); return result; } - bool Save(void) + bool Save(void) const { bool result = true; T *l = (T *)this->First(); @@ -288,19 +288,24 @@ int EPGBugfixLevel; int EPGLinger; int SVDRPTimeout; + int SVDRPPeering; + char SVDRPHostName[HOST_NAME_MAX]; + char SVDRPDefaultHost[HOST_NAME_MAX]; int ZapTimeout; int ChannelEntryTimeout; int RcRepeatDelay; int RcRepeatDelta; int DefaultPriority, DefaultLifetime; - int PausePriority, PauseLifetime; + int RecordKeyHandling; int PauseKeyHandling; + int PausePriority, PauseLifetime; int UseSubtitle; int UseVps; int VpsMargin; int RecordingDirs; int FoldersInTimerMenu; int AlwaysSortFoldersFirst; + int DefaultSortModeRec; int NumberKeysForChars; int ColorKey0, ColorKey1, ColorKey2, ColorKey3; int VideoDisplayFormat; diff -Nru vdr-2.2.0/CONTRIBUTORS vdr-2.3.3/CONTRIBUTORS --- vdr-2.2.0/CONTRIBUTORS 2015-02-19 08:20:08.000000000 +0000 +++ vdr-2.3.3/CONTRIBUTORS 2017-03-28 08:31:39.000000000 +0000 @@ -714,6 +714,8 @@ to detect the frame type for suggesting to ignore channels with an RID that is not 0 when checking for obsolete channels + for fixing a possible stack overflow in cListBase::Sort() + for reporting a crash when deleting a recording Reinhard Walter Buchner for adding some satellites to 'sources.conf' @@ -1843,6 +1845,7 @@ for exporting some libsi functions for suggesting to add functions to cDevice that allow derived output devices to implement scaling the video to a given size and location + fpr sorting sources.conf by continous azimuth Mattias Grönlund for pointing out a missing cleanup at program exit in case there is a problem @@ -2061,6 +2064,8 @@ for reporting a possible NULL pointer dereference in cCiSession::SendData() for reporting a superfluous assignment in cPipe::Open() for avoiding unnecessary pkg-config warnings in plugin Makefiles + for fixing building VDR with systemd >= 230 + for avoiding some duplicate code and unnecessary work in nit.c Steffen Beyer for fixing setting the colored button help after deleting a recording in case the next @@ -2473,6 +2478,8 @@ numbers use a decimal point for adding dependency on 'i18n' to 'install-i18n' in the VDR Makefile for adding a manual page for 'svdrpsend' + for adding a missing dependency to the Makefile to avoid error messages in the + clean-plugins target Helge Lenz for reporting a bug in setting the 'Delta' parameter when calling the shutdown @@ -2842,6 +2849,8 @@ for reporting a missing template for DVBDIR in Make.config.template for suggesting to add ARGSDIR to the ONEDIR section of Make.config.template for suggesting to change the naming of "binary skip mode" to "adaptive skip mode" + for suggesting to make the -u option also accept a numerical user id + for reporting a problem with abs() in gcc6+ when called with an unsigned variable Marcel Unbehaun for adding cRecordingInfo::GetEvent() @@ -2922,6 +2931,10 @@ for making VDR read command line options from *.conf files in /etc/vdr/conf.d for adding a missing backslash to the help text of the SVDRP command MOVR for fixing a memory leak in case of broken Extended Event Descriptors + for adding a 'const' version of cTimers::GetTimer() + for fixing a typo in the description of cTimers::GetTimersRead() + for suggesting to use dynamic buffering in handling CA descriptors to avoid a + possible buffer overflow Alex Lasnier for adding tuning support for ATSC devices @@ -2964,6 +2977,7 @@ for reporting a possible crash in the OSD demo for adding support for systemd for suggesting to replace VDR_CHARSET_OVERRIDE with a command line option + for making the recorder skip empty adaptation field TS packets Stefan Huskamp for suggesting to make entering characters via the number keys @@ -3003,6 +3017,8 @@ for reporting a problem with tuning timeouts when using SCR with multiple tuners for fixing the German translation of "VDR will shut down in %s minutes" for adding support for "Satellite Channel Routing" (SCR) according to EN50607 ("JESS") + for suggesting to make the Setup/CAM menu display which device an individual CAM + is currently assigned to Gerald Dachs for reporting a problem with checking for minimum line length of 21 characters in @@ -3157,6 +3173,7 @@ Andrey Pridvorov for reporting a problem with detecting frames in H.264 video, and pointing towards a better way of doing it + for updating the Russian OSD texts Jens Vogel for suggesting to make cPatPmtParser::ParsePmt() also recognize stream type 0x81 @@ -3220,6 +3237,7 @@ Marc Perrudin for translating OSD texts to the French language + for adding support for the systemd watchdog Bernard Jaulin for translating OSD texts to the French language @@ -3262,6 +3280,7 @@ for suggesting to add the menu category mcRecordingEdit for marking menus that edit recording properties for suggesting to make cRecording::GetResume() public + for implementing the possibility for skins to display horizontal menus Jochen Dolze for changing cThread::SetIOPriority() from "best effort class" to "idle class" in order @@ -3311,6 +3330,23 @@ skipping" for suggesting to change the return value of cOsd::RenderPixmaps() from cPixmapMemory to cPixmap + for adding detection of 24fps + for suggesting to add some comment to cPixmap about the relation between OSD, + ViewPort and DrawPort + for suggesting to reduce the priority of the "video directory scanner" thread + for making the 'newplugin' script create the 'po' subdirectory for translations + for suggesting to add a note to the description of cFont::Size(), regarding possible + differences between it and cFont::Height() + for making the cPlayer member functions FramesPerSecond, GetIndex and GetReplayMode + 'const' + for fixing resuming replay at a given position, which was off by one frame + for improving handling frame numbers to have a smoother progress display during + replay of recordings with B-frames + for fixing replaying recordings to their very end, if they don't end with an I-frame + for implementing a frame parser for H.265 (HEVC) recordings + for adding cFont::Width(void) to get the default character width and allow stretched + font drawing in high level OSDs + for fixing regenerating the index of audio recordings Eike Sauer for reporting a problem with channels that need more than 5 TS packets for detecting @@ -3345,6 +3381,10 @@ for reporting a problem that led to a fix with EMM pids not being properly reset for CAMs that need to receive the TS for suggesting to add the channel name to log messages that reference a channel + for suggesting to provide a way of using no DVB devices at all + for suggesting that the -V and -h options should list the plugins in alphabetical order + for suggesting to implement the setup option "Recording/Record key handling" + for suggesting to cache the channel/CAM relations in the file 'cam.data' Stefan Schallenberg for adding the functions IndexOf(), InsertUnique(), AppendUnique() and RemoveElement() @@ -3359,10 +3399,14 @@ case replay was paused for reporting a problem with the system getting unresponsive when removing a huge number of files in the thread that removes deleted recordings + for suggesting to call the script that gets called for recordings also right before a + recording is edited Jasmin Jessich for modifying the CAM API so that it is possible to implement CAMs that can be freely assigned to any devices + for writing the ddci2 plugin and for valuable input and help with testing and + debugging MTD support Martin Schirrmacher for suggesting to provide a way for skin plugins to get informed about the currently @@ -3400,3 +3444,28 @@ Gabriel Bonich for translating OSD texts to the Spanish language + +Daniel Ribeiro + for reporting a problem with setting the source value of newly created channels, in + case the NIT is received from a different, but very close satellite position, and + for helping to debug this + +Janne Pänkälä + for reporting that some broadcasters use the character 0x0D in EPG texts + +Stefan Pöschel + for coding the AFFcleaner, parts of which were used to make the recorder skip empty + adaptation field TS packets + +Robert Hannebauer + for fixing an overflow of PIDs in a receiver + +Aitugan Sarbassov + for adding 'S58.5E Kazsat 3' to sources.conf + +Sergey Chernyavskiy + for reporting truncated date/time strings in the skins on multi-byte UTF-8 + for adding a short sleep to cTSBuffer::Action() to avoid high CPU usage + +Frank Richter + for adding 'S3W ABS-3A' to sources.conf diff -Nru vdr-2.2.0/cutter.c vdr-2.3.3/cutter.c --- vdr-2.2.0/cutter.c 2013-10-02 13:18:02.000000000 +0000 +++ vdr-2.3.3/cutter.c 2015-08-09 12:24:28.000000000 +0000 @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: cutter.c 3.4 2013/10/02 13:18:02 kls Exp $ + * $Id: cutter.c 4.2 2015/08/09 12:24:28 kls Exp $ */ #include "cutter.h" @@ -634,7 +634,6 @@ } } } - Recordings.TouchUpdate(); } else esyslog("no editing marks found!"); @@ -675,9 +674,11 @@ editedVersionName = EditedFileName(originalVersionName); if (*editedVersionName) { if (strcmp(originalVersionName, editedVersionName) != 0) { // names must be different! + cRecordingUserCommand::InvokeCommand(RUC_EDITINGRECORDING, editedVersionName, originalVersionName); if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) { Recording.WriteInfo(editedVersionName); - Recordings.AddByName(editedVersionName, false); + LOCK_RECORDINGS_WRITE; + Recordings->AddByName(editedVersionName, false); cuttingThread = new cCuttingThread(originalVersionName, editedVersionName); return true; } @@ -702,7 +703,8 @@ if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0) cControl::Shutdown(); cVideoDirectory::RemoveVideoFile(editedVersionName); - Recordings.DelByName(editedVersionName); + LOCK_RECORDINGS_WRITE; + Recordings->DelByName(editedVersionName); } } diff -Nru vdr-2.2.0/cutter.h vdr-2.3.3/cutter.h --- vdr-2.2.0/cutter.h 2013-10-05 11:34:55.000000000 +0000 +++ vdr-2.3.3/cutter.h 2013-10-05 11:34:55.000000000 +0000 @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: cutter.h 3.1 2013/10/05 11:34:55 kls Exp $ + * $Id: cutter.h 4.0 2013/10/05 11:34:55 kls Exp $ */ #ifndef __CUTTER_H diff -Nru vdr-2.2.0/debian/abi-version vdr-2.3.3/debian/abi-version --- vdr-2.2.0/debian/abi-version 2016-03-01 17:53:03.000000000 +0000 +++ vdr-2.3.3/debian/abi-version 2017-04-14 08:49:21.000000000 +0000 @@ -1 +1 @@ -vdr-abi-2.2.0-yavdr3 +vdr-abi-2.3.3-4yavdr0 diff -Nru vdr-2.2.0/debian/changelog vdr-2.3.3/debian/changelog --- vdr-2.2.0/debian/changelog 2016-10-22 19:26:32.000000000 +0000 +++ vdr-2.3.3/debian/changelog 2017-04-14 08:50:00.000000000 +0000 @@ -1,30 +1,57 @@ -vdr (2.2.0-17yavdr5~xenial) xenial; urgency=medium +vdr (2.3.3-5yavdr0~xenial) xenial; urgency=medium - * added hevc patch + * add vdr-2.3.3-camrelations.patch - -- Alex Zeis Sat, 22 Oct 2016 21:25:53 +0200 + -- Alexander Grothe Fri, 14 Apr 2017 10:49:40 +0200 -vdr (2.2.0-17yavdr3~xenial) xenial; urgency=medium +vdr (2.3.3-4yavdr0~xenial) xenial; urgency=medium - * rebuild + * add fixsvdrpplaylock.diff (can be removed for 2.3.4) - -- Alex Zeis Tue, 31 May 2016 20:44:53 +0200 + -- Alexander Grothe Tue, 04 Apr 2017 13:17:44 +0200 -vdr (2.2.0-17yavdr2~xenial) xenial; urgency=medium +vdr (2.3.3-3yavdr0~xenial) xenial; urgency=medium - * updated zapcockpit patch + * set correct abi version - -- Alex Zeis Sun, 29 May 2016 12:11:21 +0200 + -- Alexander Grothe Fri, 31 Mar 2017 10:53:14 +0200 -vdr (2.2.0-17yavdr1~xenial) xenial; urgency=medium +vdr (2.3.3-2yavdr0~xenial) xenial; urgency=medium - * added zapcockpit patch + * add vdr-2.3.2_osd2web.diff - -- Alex Zeis Fri, 27 May 2016 13:02:06 +0200 + -- Alexander Grothe Thu, 30 Mar 2017 15:43:43 +0200 + +vdr (2.3.3-1yavdr0~xenial) xenial; urgency=medium + + * added permashift patch + + -- Alexander Grothe Wed, 29 Mar 2017 23:47:20 +0200 + +vdr (2.3.3-0yavdr0~xenial) xenial; urgency=medium + + * new vdr version 2.3.3 + - vdr-plugin-dvbsddevice has been removed from vdr package + - vdr-plugin-dvbhddevice has been removed from vdr package + + -- Alexander Grothe Wed, 29 Mar 2017 22:36:14 +0200 + +vdr (2.2.0-18yavdr0~xenial) xenial; urgency=medium + + * add patch for basic x265 support + * add patch to fix deadlock when switching channels + + -- Alexander Grothe Sun, 29 May 2016 22:12:54 +0200 + +vdr (2.2.0-17yavdr0~xenial) xenial; urgency=medium + + * add systemd runtime order "After=network.target" + + -- Alexander Grothe Wed, 20 Apr 2016 16:37:03 +0200 vdr (2.2.0-17yavdr0~xenial) xenial; urgency=medium - * use libsystemd-dev instead of libsystemd-daemon-dev + * use libsystemd-dev instead of libsystemd-daemon-dev -- Alexander Grothe Sat, 06 Feb 2016 22:22:48 +0100 diff -Nru vdr-2.2.0/debian/control vdr-2.3.3/debian/control --- vdr-2.2.0/debian/control 2016-03-01 17:53:12.000000000 +0000 +++ vdr-2.3.3/debian/control 2017-03-29 20:35:52.000000000 +0000 @@ -58,55 +58,6 @@ . This package contains the debugging symbols for vdr. -Package: vdr-plugin-dvbsddevice -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, vdr (= ${binary:Version}) -Description: Plugin that adds support for full featured SD-DVB cards to VDR - Video Disk Recorder (VDR) is a digital sat-receiver program using - Linux and DVB technologies. It allows one to record MPEG2 streams, - as well as output the stream to TV. - . - This package contains the dvbsddevice-plugin, which implements the - output device for the "Full Featured" DVB cards based on the - TechnoTrend/Fujitsu-Siemens design. - -Package: vdr-plugin-dvbsddevice-dbg -Section: debug -Priority: extra -Architecture: any -Depends: ${misc:Depends}, vdr-plugin-dvbsddevice (= ${binary:Version}) -Description: Plugin that adds support for full featured SD-DVB cards to VDR - Video Disk Recorder (VDR) is a digital sat-receiver program using - Linux and DVB technologies. It allows one to record MPEG2 streams, - as well as output the stream to TV. - . - This package contains the debugging symbols for the dvbsddevice-plugin. - -Package: vdr-plugin-dvbhddevice -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, vdr (= ${binary:Version}) -Replaces: vdr (<< 2.0.1-1) -Breaks: vdr (<< 2.0.1-1) -Description: Plugin that adds support for full featured HD-DVB cards to VDR - Video Disk Recorder (VDR) is a digital sat-receiver program using - Linux and DVB technologies. It allows one to record MPEG2 streams, - as well as output the stream to TV. - . - This package contains the dvbhddevice-plugin, which implements the output - device for the "Full Featured TechnoTrend S2-6400" DVB cards. - -Package: vdr-plugin-dvbhddevice-dbg -Section: debug -Priority: extra -Architecture: any -Depends: ${misc:Depends}, vdr-plugin-dvbhddevice (= ${binary:Version}) -Description: Plugin that adds support for full featured HD-DVB cards to VDR - Video Disk Recorder (VDR) is a digital sat-receiver program using - Linux and DVB technologies. It allows one to record MPEG2 streams, - as well as output the stream to TV. - . - This package contains the debugging symbols for the dvbhddevice-plugin. - Package: vdr-plugin-examples Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, vdr (= ${binary:Version}) diff -Nru vdr-2.2.0/debian/patches/01-hevc.patch vdr-2.3.3/debian/patches/01-hevc.patch --- vdr-2.2.0/debian/patches/01-hevc.patch 2016-10-22 19:28:48.000000000 +0000 +++ vdr-2.3.3/debian/patches/01-hevc.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,135 +0,0 @@ -Index: vdr-2.2.0/pat.c -=================================================================== ---- vdr-2.2.0.orig/pat.c -+++ vdr-2.2.0/pat.c -@@ -440,6 +440,7 @@ void cPatFilter::Process(u_short Pid, u_ - case 1: // STREAMTYPE_11172_VIDEO - case 2: // STREAMTYPE_13818_VIDEO - case 0x1B: // H.264 -+ case 0x24: // H.265 - Vpid = esPid; - Ppid = pmt.getPCRPid(); - Vtype = stream.getStreamType(); -Index: vdr-2.2.0/remux.c -=================================================================== ---- vdr-2.2.0.orig/remux.c -+++ vdr-2.2.0/remux.c -@@ -708,6 +708,7 @@ void cPatPmtParser::ParsePmt(const uchar - case 0x01: // STREAMTYPE_11172_VIDEO - case 0x02: // STREAMTYPE_13818_VIDEO - case 0x1B: // H.264 -+ case 0x24: // H.265 - vpid = stream.getPid(); - vtype = stream.getStreamType(); - ppid = Pmt.getPCRPid(); -@@ -1204,16 +1205,16 @@ private: - nutSequenceParameterSet = 7, - nutAccessUnitDelimiter = 9, - }; -- cTsPayload tsPayload; - uchar byte; // holds the current byte value in case of bitwise access - int bit; // the bit index into the current byte (-1 if we're not in bit reading mode) - int zeroBytes; // the number of consecutive zero bytes (to detect 0x000003) -- uint32_t scanner; - // Identifiers written in '_' notation as in "ITU-T H.264": - bool separate_colour_plane_flag; - int log2_max_frame_num; - bool frame_mbs_only_flag; -- // -+protected: -+ cTsPayload tsPayload; -+ uint32_t scanner; - bool gotAccessUnitDelimiter; - bool gotSequenceParameterSet; - uchar GetByte(bool Raw = false); -@@ -1430,6 +1431,81 @@ void cH264Parser::ParseSliceHeader(void) - } - } - -+// --- cH265Parser ----------------------------------------------------------- -+ -+class cH265Parser : public cH264Parser { -+private: -+ enum eNalUnitType { -+ nutSliceSegmentTrailingN = 0, -+ nutSliceSegmentTrailingR = 1, -+ nutSliceSegmentTSAN = 2, -+ nutSliceSegmentTSAR = 3, -+ nutSliceSegmentSTSAN = 4, -+ nutSliceSegmentSTSAR = 5, -+ nutSliceSegmentRADLN = 6, -+ nutSliceSegmentRADLR = 7, -+ nutSliceSegmentRASLN = 8, -+ nutSliceSegmentRASLR = 9, -+ nutSliceSegmentBLAWLP = 16, -+ nutSliceSegmentBLAWRADL = 17, -+ nutSliceSegmentBLANLP = 18, -+ nutSliceSegmentIDRWRADL = 19, -+ nutSliceSegmentIDRNLP = 20, -+ nutSliceSegmentCRANUT = 21, -+ nutVideoParameterSet = 32, -+ nutSequenceParameterSet = 33, -+ nutPictureParameterSet = 34, -+ nutAccessUnitDelimiter = 35, -+ nutEndOfSequence = 36, -+ nutEndOfBitstream = 37, -+ nutFillerData = 38, -+ nutPrefixSEI = 39, -+ nutSuffixSEI = 40, -+ nutNonVCLRes0 = 41, -+ nutNonVCLRes3 = 44, -+ nutUnspecified0 = 48, -+ nutUnspecified7 = 55, -+ }; -+public: -+ cH265Parser(void); -+ virtual int Parse(const uchar *Data, int Length, int Pid); -+ }; -+ -+cH265Parser::cH265Parser(void) : -+ cH264Parser() -+{ -+} -+ -+int cH265Parser::Parse(const uchar *Data, int Length, int Pid) -+{ -+ newFrame = independentFrame = false; -+ tsPayload.Setup(const_cast(Data), Length, Pid); -+ if (TsPayloadStart(Data)) { -+ tsPayload.SkipPesHeader(); -+ scanner = EMPTY_SCANNER; -+ } -+ for (;;) { -+ scanner = (scanner << 8) | GetByte(true); -+ if ((scanner & 0xFFFFFF00) == 0x00000100) { // NAL unit start -+ uchar NalUnitType = (scanner >> 1) & 0x3F; -+ GetByte(); // nuh_layer_id + nuh_temporal_id_plus1 -+ if (NalUnitType <= nutSliceSegmentRASLR || (NalUnitType >= nutSliceSegmentBLAWLP && NalUnitType <= nutSliceSegmentCRANUT)) { -+ if (NalUnitType == nutSliceSegmentIDRWRADL || NalUnitType == nutSliceSegmentIDRNLP || NalUnitType == nutSliceSegmentCRANUT) -+ independentFrame = true; -+ if (GetBit()) { // first_slice_segment_in_pic_flag -+ newFrame = true; -+ tsPayload.Statistics(); -+ } -+ break; -+ } -+ } -+ if (tsPayload.AtPayloadStart() // stop at any new payload start to have the buffer refilled if necessary -+ || tsPayload.Eof()) // or if we're out of data -+ break; -+ } -+ return tsPayload.Used(); -+} -+ - // --- cFrameDetector -------------------------------------------------------- - - cFrameDetector::cFrameDetector(int Pid, int Type) -@@ -1463,6 +1539,8 @@ void cFrameDetector::SetPid(int Pid, int - parser = new cMpeg2Parser; - else if (type == 0x1B) - parser = new cH264Parser; -+ else if (type == 0x24) -+ parser = new cH265Parser; - else if (type == 0x04 || type == 0x06) // MPEG audio or AC3 audio - parser = new cAudioParser; - else if (type != 0) diff -Nru vdr-2.2.0/debian/patches/04_newplugin.patch vdr-2.3.3/debian/patches/04_newplugin.patch --- vdr-2.2.0/debian/patches/04_newplugin.patch 2016-03-01 17:53:03.000000000 +0000 +++ vdr-2.3.3/debian/patches/04_newplugin.patch 2017-03-29 20:33:05.000000000 +0000 @@ -2,10 +2,8 @@ vdr-source-dir Author: Thomas Schmidt -Index: vdr/newplugin -=================================================================== ---- vdr.orig/newplugin 2013-03-06 22:17:53.000000000 +0100 -+++ vdr/newplugin 2013-03-10 18:36:53.000000000 +0100 +--- a/newplugin ++++ b/newplugin @@ -24,7 +24,7 @@ $PLUGIN_DESCRIPTION = "Enter description for '$PLUGIN_NAME' plugin"; $PLUGIN_MAINENTRY = $PLUGIN_CLASS; @@ -33,7 +31,7 @@ die "The directory $PLUGINS_SRC doesn't exist!\n" unless (-d "$PLUGINS_SRC"); die "A plugin named '$PLUGIN_NAME' already exists in $PLUGINS_SRC!\n" if (-e "$PLUGINDIR"); -@@ -346,7 +346,8 @@ +@@ -347,7 +347,8 @@ * fill in the code skeleton in "$PLUGIN_NAME.c" to implement your plugin function * add further source files if necessary * adapt the "Makefile" if necessary diff -Nru vdr-2.2.0/debian/patches/06_default_svdrp_port_0.patch vdr-2.3.3/debian/patches/06_default_svdrp_port_0.patch --- vdr-2.2.0/debian/patches/06_default_svdrp_port_0.patch 2016-03-01 17:53:03.000000000 +0000 +++ vdr-2.3.3/debian/patches/06_default_svdrp_port_0.patch 2017-03-29 20:34:07.000000000 +0000 @@ -3,11 +3,9 @@ another port with the --port option when starting vdr Author: Thomas Schmidt -Index: vdr/vdr.c -=================================================================== ---- vdr.orig/vdr.c 2015-02-12 18:35:32.103236816 +0100 -+++ vdr/vdr.c 2015-02-12 18:35:32.103236816 +0100 -@@ -187,7 +187,7 @@ +--- a/vdr.c ++++ b/vdr.c +@@ -191,7 +191,7 @@ // Command line options: #define dd(a, b) (*a ? a : b) diff -Nru vdr-2.2.0/debian/patches/99_ncursesw-include.patch vdr-2.3.3/debian/patches/99_ncursesw-include.patch --- vdr-2.2.0/debian/patches/99_ncursesw-include.patch 2016-03-01 17:53:03.000000000 +0000 +++ vdr-2.3.3/debian/patches/99_ncursesw-include.patch 2017-03-29 20:34:14.000000000 +0000 @@ -1,12 +1,10 @@ Description: Use ncursesw header file Author: Tobias Grimm -Index: vdr/PLUGINS/src/skincurses/skincurses.c -=================================================================== ---- vdr.orig/PLUGINS/src/skincurses/skincurses.c 2015-02-21 11:33:20.023435359 +0100 -+++ vdr/PLUGINS/src/skincurses/skincurses.c 2015-02-21 11:33:20.023435359 +0100 +--- a/PLUGINS/src/skincurses/skincurses.c ++++ b/PLUGINS/src/skincurses/skincurses.c @@ -6,7 +6,7 @@ - * $Id: skincurses.c 3.3 2015/02/17 13:13:17 kls Exp $ + * $Id: skincurses.c 4.2 2016/12/22 14:09:09 kls Exp $ */ -#include diff -Nru vdr-2.2.0/debian/patches/device.c.4.diff vdr-2.3.3/debian/patches/device.c.4.diff --- vdr-2.2.0/debian/patches/device.c.4.diff 1970-01-01 00:00:00.000000000 +0000 +++ vdr-2.3.3/debian/patches/device.c.4.diff 2017-03-29 20:32:01.000000000 +0000 @@ -0,0 +1,90 @@ +Description: This patch is supposed to fix a deadlock when using a MutexLock + for channel switching +Author: jrie (http://www.vdr-portal.de/board16-video-disk-recorder/board55-vdr-plugins/p1273527-ring-buffer-overflows-cdevice-detach-blockiert/#post1273527) +--- a/device.c ++++ b/device.c +@@ -1683,7 +1683,8 @@ + } + } + // Distribute the packet to all attached receivers: +- Lock(); ++ { ++ cMutexLock MutexLock(&mutexReceiver); + for (int i = 0; i < MAXRECEIVERS; i++) { + if (receiver[i] && receiver[i]->WantsPid(Pid)) { + if (DetachReceivers && cs && (!cs->IsActivating() || receiver[i]->Priority() >= LIVEPRIORITY)) { +@@ -1697,7 +1698,7 @@ + ChannelCamRelations.SetDecrypt(receiver[i]->ChannelID(), CamSlotNumber); + } + } +- Unlock(); ++ } + } + } + else +@@ -1739,6 +1740,8 @@ + return false; + } + #endif ++ bool breakout = false; ++ { + cMutexLock MutexLock(&mutexReceiver); + for (int i = 0; i < MAXRECEIVERS; i++) { + if (!receiver[i]) { +@@ -1754,16 +1757,23 @@ + Receiver->device = this; + receiver[i] = Receiver; + Unlock(); +- if (camSlot && Receiver->priority > MINPRIORITY) { // priority check to avoid an infinite loop with the CAM slot's caPidReceiver +- camSlot->StartDecrypting(); +- startScrambleDetection = time(NULL); +- } +- Start(); +- return true; ++ breakout = true; ++ break; // leave mutexReceiver-block asap + } + } +- esyslog("ERROR: no free receiver slot!"); +- return false; ++ } ++ if (breakout) { ++ if (camSlot && Receiver->priority > MINPRIORITY) { // priority check to avoid an infinite loop with the CAM slot's caPidReceiver ++ camSlot->StartDecrypting(); ++ startScrambleDetection = time(NULL); ++ } ++ Start(); // has to be outside of mutexReceiver-block ++ return true; ++ } ++ else { ++ esyslog("ERROR: no free receiver slot!"); ++ return false; ++ } + } + + void cDevice::Detach(cReceiver *Receiver) +@@ -1773,6 +1783,7 @@ + if (!Receiver || Receiver->device != this) + return; + bool receiversLeft = false; ++ { + cMutexLock MutexLock(&mutexReceiver); + for (int i = 0; i < MAXRECEIVERS; i++) { + if (receiver[i] == Receiver) { +@@ -1787,6 +1798,7 @@ + else if (receiver[i]) + receiversLeft = true; + } ++ } // leave mutexReceiver-block asap + if (camSlot) { + if (Receiver->priority > MINPRIORITY) { // priority check to avoid an infinite loop with the CAM slot's caPidReceiver + camSlot->StartDecrypting(); +@@ -1795,7 +1807,7 @@ + } + } + if (!receiversLeft) +- Cancel(-1); ++ Cancel(-1); // has to be outside of mutexReceiver-block + } + + void cDevice::DetachAll(int Pid) diff -Nru vdr-2.2.0/debian/patches/epg2vdr-2.3.2.patch vdr-2.3.3/debian/patches/epg2vdr-2.3.2.patch --- vdr-2.2.0/debian/patches/epg2vdr-2.3.2.patch 1970-01-01 00:00:00.000000000 +0000 +++ vdr-2.3.3/debian/patches/epg2vdr-2.3.2.patch 2017-03-29 21:05:58.000000000 +0000 @@ -0,0 +1,60 @@ +Description: Patch needed for vdr-plugin-epg2vdr +Author: Jörg Wendel +Origin: https://projects.vdr-developer.org/git/vdr-plugin-epg2vdr.git/tree/patches/vdr-2.3.2-aux.patch + +--- a/epg.c ++++ b/epg.c +@@ -1552,12 +1552,13 @@ + Schedule->DropOutdated(SegmentStart, SegmentEnd, TableID, Version); + } + +-void cEpgHandlers::BeginSegmentTransfer(const cChannel *Channel) ++bool cEpgHandlers::BeginSegmentTransfer(const cChannel *Channel) + { + for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { +- if (eh->BeginSegmentTransfer(Channel, false)) +- return; ++ if (!eh->BeginSegmentTransfer(Channel, false)) ++ return false; + } ++ return true; + } + + void cEpgHandlers::EndSegmentTransfer(bool Modified) +--- a/eit.c ++++ b/eit.c +@@ -67,8 +67,13 @@ + return; + } + ++ if (!EpgHandlers.BeginSegmentTransfer(Channel)) { ++ SchedulesStateKey.Remove(false); ++ ChannelsStateKey.Remove(false); ++ return; ++ } ++ + bool ChannelsModified = false; +- EpgHandlers.BeginSegmentTransfer(Channel); + bool handledExternally = EpgHandlers.HandledExternally(Channel); + cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(Channel, true); + +--- a/epg.h ++++ b/epg.h +@@ -74,7 +74,7 @@ + + class cSchedule; + +-typedef u_int16_t tEventID; ++typedef u_int32_t tEventID; + + class cEvent : public cListObject { + friend class cSchedule; +@@ -324,7 +324,7 @@ + void HandleEvent(cEvent *Event); + void SortSchedule(cSchedule *Schedule); + void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version); +- void BeginSegmentTransfer(const cChannel *Channel); ++ bool BeginSegmentTransfer(const cChannel *Channel); + void EndSegmentTransfer(bool Modified); + }; + diff -Nru vdr-2.2.0/debian/patches/fixsvdrpplaylock.diff vdr-2.3.3/debian/patches/fixsvdrpplaylock.diff --- vdr-2.2.0/debian/patches/fixsvdrpplaylock.diff 1970-01-01 00:00:00.000000000 +0000 +++ vdr-2.3.3/debian/patches/fixsvdrpplaylock.diff 2017-04-04 11:20:00.000000000 +0000 @@ -0,0 +1,66 @@ +Description: Fix a lockup when repeatedly sending a play recording command using svdrp +Author: Klaus Schmiedinger +Origin: http://www.vdr-portal.de/board1-news/board2-vdr-news/p1289947-announce-vdr-developer-version-2-3-3/#post1289947 + +--- a/svdrp.c ++++ b/svdrp.c +@@ -2059,29 +2059,38 @@ + char c = *option; + *option = 0; + if (isnumber(num)) { +- LOCK_RECORDINGS_READ; +- if (const cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) { +- if (c) +- option = skipspace(++option); +- cReplayControl::SetRecording(NULL); +- cControl::Shutdown(); +- if (*option) { +- int pos = 0; +- if (strcasecmp(option, "BEGIN") != 0) +- pos = HMSFToIndex(option, Recording->FramesPerSecond()); +- cResumeFile Resume(Recording->FileName(), Recording->IsPesRecording()); +- if (pos <= 0) +- Resume.Delete(); +- else +- Resume.Save(pos); ++ cStateKey StateKey; ++ if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(StateKey)) { ++ if (const cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) { ++ cString FileName = Recording->FileName(); ++ cString Title = Recording->Title(); ++ int FramesPerSecond = Recording->FramesPerSecond(); ++ bool IsPesRecording = Recording->IsPesRecording(); ++ StateKey.Remove(); // must give up the lock for the call to cControl::Shutdown() ++ if (c) ++ option = skipspace(++option); ++ cReplayControl::SetRecording(NULL); ++ cControl::Shutdown(); ++ if (*option) { ++ int pos = 0; ++ if (strcasecmp(option, "BEGIN") != 0) ++ pos = HMSFToIndex(option, FramesPerSecond); ++ cResumeFile Resume(FileName, IsPesRecording); ++ if (pos <= 0) ++ Resume.Delete(); ++ else ++ Resume.Save(pos); ++ } ++ cReplayControl::SetRecording(FileName); ++ cControl::Launch(new cReplayControl); ++ cControl::Attach(); ++ Reply(250, "Playing recording \"%s\" [%s]", num, *Title); ++ } ++ else { ++ StateKey.Remove(); ++ Reply(550, "Recording \"%s\" not found", num); + } +- cReplayControl::SetRecording(Recording->FileName()); +- cControl::Launch(new cReplayControl); +- cControl::Attach(); +- Reply(250, "Playing recording \"%s\" [%s]", num, Recording->Title()); + } +- else +- Reply(550, "Recording \"%s\" not found", num); + } + else + Reply(501, "Error in recording number \"%s\"", num); diff -Nru vdr-2.2.0/debian/patches/opt-37-x_menuorg.patch vdr-2.3.3/debian/patches/opt-37-x_menuorg.patch --- vdr-2.2.0/debian/patches/opt-37-x_menuorg.patch 2016-03-01 17:53:03.000000000 +0000 +++ vdr-2.3.3/debian/patches/opt-37-x_menuorg.patch 2017-03-29 20:34:21.000000000 +0000 @@ -70,7 +70,7 @@ +#endif //__MAINMENUITEMSPROVIDER_H --- a/menu.c +++ b/menu.c -@@ -31,6 +31,7 @@ +@@ -32,6 +32,7 @@ #include "timers.h" #include "transfer.h" #include "videodir.h" @@ -78,7 +78,7 @@ #define MAXWAIT4EPGINFO 3 // seconds #define MODETIMEOUT 3 // seconds -@@ -3943,6 +3944,9 @@ +@@ -4261,6 +4262,9 @@ cancelEditingItem = NULL; stopRecordingItem = NULL; recordControlsState = 0; @@ -88,7 +88,7 @@ Set(); // Initial submenus: -@@ -3971,6 +3975,29 @@ +@@ -4289,6 +4293,29 @@ SetTitle("VDR"); SetHasHotkeys(); @@ -118,7 +118,7 @@ // Basic menu items: Add(new cOsdItem(hk(tr("Schedule")), osSchedule)); -@@ -3997,6 +4024,8 @@ +@@ -4315,6 +4342,8 @@ if (Commands.Count()) Add(new cOsdItem(hk(tr("Commands")), osCommands)); @@ -127,7 +127,7 @@ Update(true); Display(); -@@ -4102,6 +4131,41 @@ +@@ -4419,6 +4448,41 @@ state = osEnd; } break; diff -Nru vdr-2.2.0/debian/patches/opt-42-x_MainMenuHooks-v1.0.1.patch vdr-2.3.3/debian/patches/opt-42-x_MainMenuHooks-v1.0.1.patch --- vdr-2.2.0/debian/patches/opt-42-x_MainMenuHooks-v1.0.1.patch 2016-03-01 17:53:03.000000000 +0000 +++ vdr-2.3.3/debian/patches/opt-42-x_MainMenuHooks-v1.0.1.patch 2017-03-29 20:34:25.000000000 +0000 @@ -86,7 +86,7 @@ #define LIVEPRIORITY 0 // priority used when selecting a device for live viewing --- a/menu.c +++ b/menu.c -@@ -3951,15 +3951,31 @@ +@@ -4269,15 +4269,31 @@ // Initial submenus: @@ -124,7 +124,7 @@ } cOsdObject *cMenuMain::PluginOsdObject(void) -@@ -4092,13 +4108,34 @@ +@@ -4410,13 +4426,34 @@ eOSState state = cOsdMenu::ProcessKey(Key); HadSubMenu |= HasSubMenu(); @@ -163,9 +163,9 @@ + case osSetup: menu = new cMenuSetup; break; + case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break; case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) { - cOsdItem *item = Get(Current()); - if (item) { -@@ -4185,6 +4222,12 @@ + if (cOsdItem *item = Get(Current())) { + cRecordControls::Stop(item->Text() + strlen(tr(STOP_RECORDING))); +@@ -4502,6 +4539,12 @@ default: break; } } diff -Nru vdr-2.2.0/debian/patches/opt-50_graphtft.patch vdr-2.3.3/debian/patches/opt-50_graphtft.patch --- vdr-2.2.0/debian/patches/opt-50_graphtft.patch 1970-01-01 00:00:00.000000000 +0000 +++ vdr-2.3.3/debian/patches/opt-50_graphtft.patch 2017-03-29 20:34:30.000000000 +0000 @@ -0,0 +1,152 @@ +Description: Patch required for the GraphTFT plugin. +Author: Jörg Wendel (Horchi) + +--- a/menu.c ++++ b/menu.c +@@ -1467,6 +1467,7 @@ + { + cOsdMenu::Display(); + DisplayMenu()->SetEvent(event); ++ cStatus::MsgOsdSetEvent(event); + if (event->Description()) + cStatus::MsgOsdTextItem(event->Description()); + } +@@ -1596,6 +1597,7 @@ + static void SetCurrentChannel(int ChannelNr) { currentChannel = ChannelNr; } + static const cEvent *ScheduleEvent(void); + virtual eOSState ProcessKey(eKeys Key); ++ virtual void Display(void); + }; + + int cMenuWhatsOn::currentChannel = 0; +@@ -1621,6 +1623,18 @@ + SetHelpKeys(); + } + ++void cMenuWhatsOn::Display(void) ++{ ++ cOsdMenu::Display(); ++ ++ if (Count() > 0) { ++ int ni = 0; ++ for (cOsdItem *item = First(); item; item = Next(item)) { ++ cStatus::MsgOsdEventItem(((cMenuScheduleItem*)item)->event, item->Text(), ni++, Count()); ++ } ++ } ++} ++ + bool cMenuWhatsOn::Update(void) + { + bool result = false; +@@ -1790,6 +1804,7 @@ + cMenuSchedule(void); + virtual ~cMenuSchedule(); + virtual eOSState ProcessKey(eKeys Key); ++ virtual void Display(void); + }; + + cMenuSchedule::cMenuSchedule(void) +@@ -1848,6 +1863,18 @@ + } + } + ++void cMenuSchedule::Display(void) ++{ ++ cOsdMenu::Display(); ++ ++ if (Count() > 0) { ++ int ni = 0; ++ for (cOsdItem *item = First(); item; item = Next(item)) { ++ cStatus::MsgOsdEventItem(((cMenuScheduleItem*)item)->event, item->Text(), ni++, Count()); ++ } ++ } ++} ++ + bool cMenuSchedule::PrepareScheduleAllThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel) + { + if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { +--- a/osdbase.c ++++ b/osdbase.c +@@ -107,6 +107,7 @@ + free(status); + displayMenu->Clear(); + cStatus::MsgOsdClear(); ++ cStatus::MsgOsdMenuDestroy(); + if (!--displayMenuCount) + DELETENULL(displayMenu); + } +@@ -236,6 +237,7 @@ + displayMenuItems = displayMenu->MaxItems(); + displayMenu->SetTabs(cols[0], cols[1], cols[2], cols[3], cols[4]);//XXX + displayMenu->SetTitle(title); ++ cStatus::MsgOsdMenuDisplay(menuCategory); + cStatus::MsgOsdTitle(title); + DisplayHelp(true); + int count = Count(); +--- a/status.c ++++ b/status.c +@@ -130,3 +130,32 @@ + for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) + sm->OsdProgramme(PresentTime, PresentTitle, PresentSubtitle, FollowingTime, FollowingTitle, FollowingSubtitle); + } ++ ++void cStatus::MsgOsdSetEvent(const cEvent* event) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->OsdSetEvent(event); ++} ++ ++void cStatus::MsgOsdSetRecording(const cRecording* recording) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->OsdSetRecording(recording); ++} ++ ++void cStatus::MsgOsdMenuDisplay(eMenuCategory menuCategory) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->OsdMenuDisplay(menuCategory); ++} ++ ++void cStatus::MsgOsdMenuDestroy() ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->OsdMenuDestroy(); ++} ++void cStatus::MsgOsdEventItem(const cEvent* Event, const char *Text, int Index, int Count) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->OsdEventItem(Event, Text, Index, Count); ++} +--- a/status.h ++++ b/status.h +@@ -81,6 +81,17 @@ + // The OSD displays the single line Text with the current channel information. + virtual void OsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle) {} + // The OSD displays the given programme information. ++ virtual void OsdSetRecording(const cRecording* recording) {} ++ // The OSD displays the recording information. ++ virtual void OsdSetEvent(const cEvent* event) {} ++ // The OSD displays the event information. ++ virtual void OsdMenuDisplay(eMenuCategory menuCategory) {} ++ // report menu creation ++ virtual void OsdMenuDestroy() {} ++ // report menu destruvtion ++ virtual void OsdEventItem(const cEvent* Event, const char *Text, int Index, int Count) {} ++ // The OSD displays the given single line Event as menu item at Index. ++ + public: + cStatus(void); + virtual ~cStatus(); +@@ -103,6 +114,11 @@ + static void MsgOsdTextItem(const char *Text, bool Scroll = false); + static void MsgOsdChannel(const char *Text); + static void MsgOsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle); ++ static void MsgOsdSetEvent(const cEvent* event); ++ static void MsgOsdSetRecording(const cRecording* recording); ++ static void MsgOsdMenuDisplay(eMenuCategory menuCategory); ++ static void MsgOsdMenuDestroy(); ++ static void MsgOsdEventItem(const cEvent* Event, const char *Text, int Index, int Count); + }; + + #endif //__STATUS_H diff -Nru vdr-2.2.0/debian/patches/opt-56_eventdetails_v3.patch vdr-2.3.3/debian/patches/opt-56_eventdetails_v3.patch --- vdr-2.2.0/debian/patches/opt-56_eventdetails_v3.patch 2016-03-01 17:53:03.000000000 +0000 +++ vdr-2.3.3/debian/patches/opt-56_eventdetails_v3.patch 2017-03-29 20:33:05.000000000 +0000 @@ -4,7 +4,7 @@ --- a/epg.c +++ b/epg.c -@@ -429,6 +429,21 @@ +@@ -440,6 +440,21 @@ return buf; } @@ -26,7 +26,7 @@ void cEvent::Dump(FILE *f, const char *Prefix, bool InfoOnly) const { if (InfoOnly || startTime + duration + Setup.EPGLinger * 60 >= time(NULL)) { -@@ -442,6 +457,12 @@ +@@ -453,6 +468,12 @@ fprintf(f, "%sD %s\n", Prefix, description); strreplace(description, '|', '\n'); } @@ -39,7 +39,7 @@ if (contents[0]) { fprintf(f, "%sG", Prefix); for (int i = 0; Contents(i); i++) -@@ -474,6 +495,9 @@ +@@ -485,6 +506,9 @@ case 'D': strreplace(t, '|', '\n'); SetDescription(t); break; @@ -49,7 +49,7 @@ case 'G': { memset(contents, 0, sizeof(contents)); for (int i = 0; i < MaxEventContents; i++) { -@@ -538,6 +562,7 @@ +@@ -549,6 +573,7 @@ } } } @@ -80,8 +80,8 @@ + class cSchedule; - typedef u_int32_t tEventID; -@@ -87,6 +95,7 @@ + typedef u_int16_t tEventID; +@@ -89,6 +97,7 @@ int duration; // Duration of this event in seconds time_t vps; // Video Programming Service timestamp (VPS, aka "Programme Identification Label", PIL) time_t seen; // When this event was last seen in the data stream @@ -89,7 +89,7 @@ public: cEvent(tEventID EventID); ~cEvent(); -@@ -117,6 +126,7 @@ +@@ -121,6 +130,7 @@ cString GetTimeString(void) const; cString GetEndTimeString(void) const; cString GetVpsString(void) const; @@ -97,7 +97,7 @@ void SetEventID(tEventID EventID); void SetTableID(uchar TableID); void SetVersion(uchar Version); -@@ -131,6 +141,9 @@ +@@ -135,6 +145,9 @@ void SetDuration(int Duration); void SetVps(time_t Vps); void SetSeen(void); diff -Nru vdr-2.2.0/debian/patches/opt-57_vdr-remote_instant_recordings.patch vdr-2.3.3/debian/patches/opt-57_vdr-remote_instant_recordings.patch --- vdr-2.2.0/debian/patches/opt-57_vdr-remote_instant_recordings.patch 2016-03-01 17:53:03.000000000 +0000 +++ vdr-2.3.3/debian/patches/opt-57_vdr-remote_instant_recordings.patch 2017-03-29 20:33:05.000000000 +0000 @@ -24,7 +24,7 @@ #define MAXWAIT4EPGINFO 3 // seconds #define MODETIMEOUT 3 // seconds #define NEWTIMERLIMIT 120 // seconds until the start time of a new timer created from the Schedule menu, -@@ -5037,6 +5039,50 @@ +@@ -5051,6 +5053,50 @@ bool cRecordControls::Start(cTimer *Timer, bool Pause) { diff -Nru vdr-2.2.0/debian/patches/opt-61_dynamite.patch vdr-2.3.3/debian/patches/opt-61_dynamite.patch --- vdr-2.2.0/debian/patches/opt-61_dynamite.patch 2016-03-01 17:53:03.000000000 +0000 +++ vdr-2.3.3/debian/patches/opt-61_dynamite.patch 2017-03-29 20:34:37.000000000 +0000 @@ -6,7 +6,7 @@ --- a/ci.h +++ b/ci.h -@@ -116,6 +116,8 @@ +@@ -117,6 +117,8 @@ cCiAdapter(void); virtual ~cCiAdapter(); ///< The derived class must call Cancel(3) in its destructor. @@ -44,7 +44,7 @@ SetDescription("device %d receiver", CardIndex() + 1); -@@ -107,10 +117,14 @@ +@@ -108,10 +118,14 @@ for (int i = 0; i < MAXRECEIVERS; i++) receiver[i] = NULL; @@ -62,7 +62,7 @@ } cDevice::~cDevice() -@@ -121,6 +135,29 @@ +@@ -122,6 +136,29 @@ delete dvbSubtitleConverter; if (this == primaryDevice) primaryDevice = NULL; @@ -92,7 +92,7 @@ } bool cDevice::WaitForAllDevicesReady(int Timeout) -@@ -159,6 +196,8 @@ +@@ -160,6 +197,8 @@ int cDevice::DeviceNumber(void) const { @@ -101,7 +101,7 @@ for (int i = 0; i < numDevices; i++) { if (device[i] == this) return i; -@@ -359,6 +398,8 @@ +@@ -438,6 +477,8 @@ void cDevice::SetCamSlot(cCamSlot *CamSlot) { @@ -110,7 +110,7 @@ LOCK_THREAD; camSlot = CamSlot; } -@@ -566,6 +607,10 @@ +@@ -649,6 +690,10 @@ void cDevice::StartSectionHandler(void) { @@ -121,7 +121,7 @@ if (!sectionHandler) { sectionHandler = new cSectionHandler(this); AttachFilter(eitFilter = new cEitFilter); -@@ -577,6 +622,10 @@ +@@ -660,6 +705,10 @@ void cDevice::StopSectionHandler(void) { @@ -132,7 +132,7 @@ if (sectionHandler) { delete nitFilter; delete sdtFilter; -@@ -608,12 +657,17 @@ +@@ -691,12 +740,17 @@ void cDevice::AttachFilter(cFilter *Filter) { @@ -150,7 +150,7 @@ if (sectionHandler) sectionHandler->Detach(Filter); } -@@ -783,6 +837,7 @@ +@@ -866,6 +920,7 @@ sectionHandler->SetStatus(false); sectionHandler->SetChannel(NULL); } @@ -158,12 +158,12 @@ // Tell the camSlot about the channel switch and add all PIDs of this // channel to it, for possible later decryption: if (camSlot) -@@ -830,19 +885,27 @@ +@@ -913,19 +968,27 @@ { if (!cTransferControl::ReceiverDevice()) { - cChannel *Channel = Channels.GetByNumber(CurrentChannel()); -- if (Channel) -+ if (Channel) { + LOCK_CHANNELS_READ; +- if (const cChannel *Channel = Channels->GetByNumber(CurrentChannel())) ++ if (const cChannel *Channel = Channels->GetByNumber(CurrentChannel())) { + SetIdle(false); SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode + } @@ -187,7 +187,7 @@ if (Seconds >= 0) occupiedTimeout = time(NULL) + min(Seconds, MAXOCCUPIEDTIMEOUT); } -@@ -1225,7 +1288,10 @@ +@@ -1308,7 +1371,10 @@ bool cDevice::AttachPlayer(cPlayer *Player) { @@ -198,7 +198,7 @@ if (player) Detach(player); DELETENULL(liveSubtitle); -@@ -1244,6 +1310,8 @@ +@@ -1327,6 +1393,8 @@ void cDevice::Detach(cPlayer *Player) { @@ -207,7 +207,7 @@ if (Player && player == Player) { cPlayer *p = player; player = NULL; // avoids recursive calls to Detach() -@@ -1263,6 +1331,8 @@ +@@ -1346,6 +1414,8 @@ void cDevice::StopReplay(void) { @@ -216,7 +216,7 @@ if (player) { Detach(player); if (IsPrimaryDevice()) -@@ -1543,6 +1613,8 @@ +@@ -1621,6 +1691,8 @@ int cDevice::Priority(void) const { @@ -225,7 +225,7 @@ int priority = IDLEPRIORITY; if (IsPrimaryDevice() && !Replaying() && HasProgramme()) priority = TRANSFERPRIORITY; // we use the same value here, no matter whether it's actual Transfer Mode or real live viewing -@@ -1561,6 +1633,8 @@ +@@ -1639,6 +1711,8 @@ bool cDevice::Receiving(bool Dummy) const { @@ -234,7 +234,7 @@ cMutexLock MutexLock(&mutexReceiver); for (int i = 0; i < MAXRECEIVERS; i++) { if (receiver[i]) -@@ -1642,10 +1716,13 @@ +@@ -1725,10 +1799,13 @@ bool cDevice::AttachReceiver(cReceiver *Receiver) { @@ -248,7 +248,7 @@ // activate the following line if you need it - actually the driver should be fixed! //#define WAIT_FOR_TUNER_LOCK #ifdef WAIT_FOR_TUNER_LOCK -@@ -1684,6 +1761,8 @@ +@@ -1774,6 +1851,8 @@ void cDevice::Detach(cReceiver *Receiver) { @@ -257,7 +257,7 @@ if (!Receiver || Receiver->device != this) return; bool receiversLeft = false; -@@ -1714,6 +1793,8 @@ +@@ -1804,6 +1883,8 @@ void cDevice::DetachAll(int Pid) { @@ -266,7 +266,7 @@ if (Pid) { cMutexLock MutexLock(&mutexReceiver); for (int i = 0; i < MAXRECEIVERS; i++) { -@@ -1806,3 +1887,25 @@ +@@ -1897,3 +1978,25 @@ ringBuffer->Del(Count); delivered = false; } @@ -294,7 +294,7 @@ +} --- a/device.h +++ b/device.h -@@ -174,7 +174,6 @@ +@@ -175,7 +175,6 @@ static int nextCardIndex; int cardIndex; protected: @@ -302,7 +302,7 @@ virtual ~cDevice(); virtual bool Ready(void); ///< Returns true if this device is ready. Devices with conditional -@@ -201,9 +200,6 @@ +@@ -202,9 +201,6 @@ ///< A derived class must call the MakePrimaryDevice() function of its ///< base class. public: @@ -312,7 +312,7 @@ int DeviceNumber(void) const; ///< Returns the number of this device (0 ... numDevices - 1). virtual cString DeviceType(void) const; -@@ -433,9 +429,6 @@ +@@ -440,9 +436,6 @@ ///< shall check whether the channel can be decrypted. void SetCamSlot(cCamSlot *CamSlot); ///< Sets the given CamSlot to be used with this device. @@ -322,7 +322,7 @@ // Image Grab facilities -@@ -604,9 +597,6 @@ +@@ -611,9 +604,6 @@ cTsToPes tsToPesSubtitle; bool isPlayingVideo; protected: @@ -332,7 +332,7 @@ virtual bool CanReplay(void) const; ///< Returns true if this device can currently start a replay session. virtual bool SetPlayMode(ePlayMode PlayMode); -@@ -821,6 +811,38 @@ +@@ -828,6 +818,38 @@ ///< Detaches all receivers from this device for this pid. virtual void DetachAllReceivers(void); ///< Detaches all receivers from this device. @@ -371,7 +371,7 @@ }; /// Derived cDevice classes that can receive channels will have to provide -@@ -857,4 +879,47 @@ +@@ -864,4 +886,47 @@ ///< to Get(). }; @@ -687,7 +687,7 @@ // --- cDvbSourceParam ------------------------------------------------------- class cDvbSourceParam : public cSourceParam { -@@ -1089,7 +1138,8 @@ +@@ -1090,7 +1139,8 @@ NULL }; @@ -697,7 +697,7 @@ { adapter = Adapter; frontend = Frontend; -@@ -1109,7 +1159,7 @@ +@@ -1110,7 +1160,7 @@ fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR); if (fd_ca >= 0) @@ -706,7 +706,7 @@ // The DVR device (will be opened and closed as needed): -@@ -1349,7 +1399,11 @@ +@@ -1350,7 +1400,11 @@ if (d >= 0) { int ErrorDevice = 0; if (cDevice *Device1 = cDevice::GetDevice(i)) { @@ -718,7 +718,7 @@ if (cDvbDevice *DvbDevice1 = dynamic_cast(Device1)) { if (cDvbDevice *DvbDevice2 = dynamic_cast(Device2)) { if (!DvbDevice1->Bond(DvbDevice2)) -@@ -1383,7 +1437,10 @@ +@@ -1384,7 +1438,10 @@ void cDvbDevice::UnBondDevices(void) { for (int i = 0; i < cDevice::NumDevices(); i++) { @@ -730,7 +730,7 @@ d->UnBond(); } } -@@ -1437,6 +1494,26 @@ +@@ -1438,6 +1495,26 @@ return true; } @@ -757,7 +757,7 @@ bool cDvbDevice::HasCi(void) { return ciAdapter; -@@ -1606,7 +1683,7 @@ +@@ -1607,7 +1684,7 @@ bool cDvbDevice::ProvidesEIT(void) const { @@ -777,7 +777,7 @@ static cString DvbName(const char *Name, int Adapter, int Frontend); static int DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError = false); private: -@@ -191,12 +191,14 @@ +@@ -192,12 +192,14 @@ mutable bool needsDetachBondedReceivers; bool QueryDeliverySystems(int fd_frontend); public: diff -Nru vdr-2.2.0/debian/patches/opt-65_pin.patch vdr-2.3.3/debian/patches/opt-65_pin.patch --- vdr-2.2.0/debian/patches/opt-65_pin.patch 2016-03-01 17:53:03.000000000 +0000 +++ vdr-2.3.3/debian/patches/opt-65_pin.patch 2017-03-29 20:34:43.000000000 +0000 @@ -3,15 +3,15 @@ --- a/device.c +++ b/device.c -@@ -779,6 +779,7 @@ - cChannel *channel; - while ((channel = Channels.GetByNumber(n, Direction)) != NULL) { +@@ -863,6 +863,7 @@ + const cChannel *Channel; + while ((Channel = Channels->GetByNumber(n, Direction)) != NULL) { // try only channels which are currently available -+ if (!cStatus::MsgChannelProtected(0, channel)) // PIN PATCH - if (GetDevice(channel, LIVEPRIORITY, true, true)) ++ if (!cStatus::MsgChannelProtected(0, Channel)) // PIN PATCH + if (GetDevice(Channel, LIVEPRIORITY, true, true)) break; - n = channel->Number() + Direction; -@@ -800,6 +801,12 @@ + n = Channel->Number() + Direction; +@@ -884,6 +885,12 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) { @@ -24,9 +24,20 @@ cStatus::MsgChannelSwitch(this, 0, LiveView); if (LiveView) { +--- a/Makefile ++++ b/Makefile +@@ -324,7 +324,7 @@ + clean: + @$(MAKE) --no-print-directory -C $(LSIDIR) clean + @-rm -f $(OBJS) $(DEPFILE) vdr vdr.pc core* *~ +- @-rm -rf $(LOCALEDIR) $(PODIR)/*.mo $(PODIR)/*.pot ++ @-rm -rf $(LOCALEDIR) $(PODIR)/*~ $(PODIR)/*.mo $(PODIR)/*.pot + @-rm -rf include + @-rm -rf srcdoc + CLEAN: clean --- a/menu.c +++ b/menu.c -@@ -961,6 +961,18 @@ +@@ -999,6 +999,18 @@ Add(new cMenuEditBitItem( tr("VPS"), &data.flags, tfVps)); Add(new cMenuEditIntItem( tr("Priority"), &data.priority, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Lifetime"), &data.lifetime, 0, MAXLIFETIME)); @@ -44,18 +55,18 @@ + Add(file = new cMenuEditStrItem( tr("File"), data.file, sizeof(data.file))); SetFirstDayItem(); - } -@@ -2771,7 +2783,8 @@ + if (data.remote) +@@ -3002,7 +3014,8 @@ + } } - } - } -- if (*Item->Text() && !LastDir) { -+ if (*Item->Text() && !LastDir -+ && (!cStatus::MsgReplayProtected(Item->Recording(), Item->Name(), base, Item->IsDirectory(), true))) { // PIN PATCH - Add(Item); - LastItem = Item; - if (Item->IsDirectory()) -@@ -2837,6 +2850,9 @@ + } +- if (*Item->Text() && !LastDir) { ++ if (*Item->Text() && !LastDir ++ && (!cStatus::MsgReplayProtected(Item->Recording(), Item->Name(), base, Item->IsDirectory(), true))) { // PIN PATCH + Add(Item); + LastItem = Item; + if (Item->IsDirectory()) +@@ -3072,6 +3085,9 @@ { cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); if (ri) { @@ -65,7 +76,7 @@ if (ri->IsDirectory()) Open(); else { -@@ -4046,28 +4062,32 @@ +@@ -4361,28 +4377,32 @@ // Basic menu items: @@ -103,7 +114,7 @@ Add(new cOsdItem(hk(tr("Commands")), osCommands)); } -@@ -4138,6 +4158,14 @@ +@@ -4453,6 +4473,14 @@ eOSState state = cOsdMenu::ProcessKey(Key); HadSubMenu |= HasSubMenu(); @@ -118,7 +129,7 @@ cOsdObject *menu = NULL; switch (state) { case osSchedule: -@@ -4184,6 +4212,7 @@ +@@ -4498,6 +4526,7 @@ if (item) { cPlugin *p = cPluginManager::GetPlugin(item->PluginIndex()); if (p) { @@ -126,7 +137,7 @@ cOsdObject *menu = p->MainMenuAction(); if (menu) { if (menu->IsMenu()) -@@ -4195,6 +4224,7 @@ +@@ -4509,6 +4538,7 @@ } } } @@ -134,26 +145,26 @@ state = osEnd; } break; -@@ -4409,6 +4439,7 @@ - Channel = Direction > 0 ? Channels.Next(Channel) : Channels.Prev(Channel); +@@ -4719,6 +4749,7 @@ + Channel = Direction > 0 ? Channels->Next(Channel) : Channels->Prev(Channel); if (!Channel && Setup.ChannelsWrap) - Channel = Direction > 0 ? Channels.First() : Channels.Last(); + Channel = Direction > 0 ? Channels->First() : Channels->Last(); + if (!cStatus::MsgChannelProtected(0, Channel)) // PIN PATCH if (Channel && !Channel->GroupSep() && cDevice::GetDevice(Channel, LIVEPRIORITY, true, true)) return Channel; } -@@ -5117,6 +5148,7 @@ +@@ -5369,6 +5400,7 @@ for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (!RecordControls[i]) { - RecordControls[i] = new cRecordControl(device, Timer, Pause); + RecordControls[i] = new cRecordControl(device, Timers, Timer, Pause); + cStatus::MsgRecordingFile(RecordControls[i]->FileName()); // PIN PATCH return RecordControls[i]->Process(time(NULL)); } } --- a/osd.c +++ b/osd.c -@@ -1643,6 +1643,7 @@ - int cOsd::osdHeight = 0; +@@ -1644,6 +1644,7 @@ + cSize cOsd::maxPixmapSize(2048, 2048); cVector cOsd::Osds; cMutex cOsd::mutex; +bool cOsd::pinValid = false; // PIN PATCH @@ -162,7 +173,7 @@ { --- a/osd.h +++ b/osd.h -@@ -941,6 +941,7 @@ +@@ -952,6 +952,7 @@ ///< ///< If a plugin uses a derived cPixmap implementation, it needs to use that ///< type instead of cPixmapMemory. @@ -215,10 +226,10 @@ + return false; +} + -+void cStatus::MsgUserAction(const eKeys key, const cOsdObject* Interact) // PIN PATCH ++void cStatus::MsgUserAction(const eKeys key) // PIN PATCH +{ + for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) -+ sm->UserAction(key, Interact); ++ sm->UserAction(key); +} + +bool cStatus::MsgMenuItemProtected(const char* Name, int menuView) // PIN PATCH @@ -236,12 +247,12 @@ #include "tools.h" +#include "plugin.h" - enum eTimerChange { tcMod, tcAdd, tcDel }; + enum eTimerChange { tcMod, tcAdd, tcDel }; // tcMod is obsolete and no longer used! -@@ -94,6 +95,22 @@ - // report menu destruvtion - virtual void OsdEventItem(const cEvent* Event, const char *Text, int Index, int Count) {} - // The OSD displays the given single line Event as menu item at Index. +@@ -81,6 +82,23 @@ + // The OSD displays the single line Text with the current channel information. + virtual void OsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle) {} + // The OSD displays the given programme information. + virtual bool ChannelProtected(const cDevice *Device, const cChannel* Channel) { return false; } // PIN PATCH + // Checks if a channel is protected. + virtual bool ReplayProtected(const cRecording* Recording, const char* Name, @@ -254,31 +265,32 @@ + // The given timer is created + virtual bool PluginProtected(cPlugin* Plugin, int menuView = false) { return false; } // PIN PATCH + // Checks if a plugin is protected. -+ virtual void UserAction(const eKeys key, const cOsdObject* Interact) {} // PIN PATCH ++ virtual void UserAction(const eKeys key) {} // PIN PATCH + // report user action + virtual bool MenuItemProtected(const char* Name, int menuView = false) { return false; } // PIN PATCH + // Checks if a menu entry is protected. - - public: - cStatus(void); -@@ -122,6 +139,14 @@ - static void MsgOsdMenuDisplay(eMenuCategory menuCategory); - static void MsgOsdMenuDestroy(); - static void MsgOsdEventItem(const cEvent* Event, const char *Text, int Index, int Count); ++ + virtual void OsdSetRecording(const cRecording* recording) {} + // The OSD displays the recording information. + virtual void OsdSetEvent(const cEvent* event) {} +@@ -114,6 +132,14 @@ + static void MsgOsdTextItem(const char *Text, bool Scroll = false); + static void MsgOsdChannel(const char *Text); + static void MsgOsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle); + static bool MsgChannelProtected(const cDevice* Device, const cChannel* Channel); // PIN PATCH + static bool MsgReplayProtected(const cRecording* Recording, const char* Name, + const char* Base, bool isDirectory, int menuView = false); // PIN PATCH + static void MsgRecordingFile(const char* FileName); // PIN PATCH + static void MsgTimerCreation(cTimer* Timer, const cEvent *Event); // PIN PATCH + static bool MsgPluginProtected(cPlugin* Plugin, int menuView = false); // PIN PATCH -+ static void MsgUserAction(const eKeys key, const cOsdObject* Interact); ++ static void MsgUserAction(const eKeys key); // PIN PATCH + static bool MsgMenuItemProtected(const char* Name, int menuView = false); // PIN PATCH - }; - - #endif //__STATUS_H + static void MsgOsdSetEvent(const cEvent* event); + static void MsgOsdSetRecording(const cRecording* recording); + static void MsgOsdMenuDisplay(eMenuCategory menuCategory); --- a/timers.c +++ b/timers.c -@@ -76,6 +76,7 @@ +@@ -77,6 +77,7 @@ stop -= 2400; priority = Pause ? Setup.PausePriority : Setup.DefaultPriority; lifetime = Pause ? Setup.PauseLifetime : Setup.DefaultLifetime; @@ -286,7 +298,7 @@ if (Instant && channel) snprintf(file, sizeof(file), "%s%s", Setup.MarkInstantRecord ? "@" : "", *Setup.NameInstantRecord ? Setup.NameInstantRecord : channel->Name()); } -@@ -110,10 +111,12 @@ +@@ -114,10 +115,12 @@ stop -= 2400; priority = Setup.DefaultPriority; lifetime = Setup.DefaultLifetime; @@ -299,7 +311,7 @@ } cTimer::cTimer(const cTimer &Timer) -@@ -149,6 +152,7 @@ +@@ -156,6 +159,7 @@ stop = Timer.stop; priority = Timer.priority; lifetime = Timer.lifetime; @@ -307,7 +319,7 @@ strncpy(file, Timer.file, sizeof(file)); free(aux); aux = Timer.aux ? strdup(Timer.aux) : NULL; -@@ -331,6 +335,7 @@ +@@ -344,6 +348,7 @@ result = false; } } @@ -315,7 +327,7 @@ free(channelbuffer); free(daybuffer); free(filebuffer); -@@ -689,6 +694,36 @@ +@@ -708,6 +713,36 @@ Matches(); // refresh start and end time } @@ -351,10 +363,10 @@ + // --- cTimers --------------------------------------------------------------- - cTimers Timers; + cTimers cTimers::timers; --- a/timers.h +++ b/timers.h -@@ -38,6 +38,7 @@ +@@ -39,6 +39,7 @@ int start; int stop; int priority; @@ -362,7 +374,7 @@ int lifetime; mutable char file[NAME_MAX * 2 + 1]; // *2 to be able to hold 'title' and 'episode', which can each be up to 255 characters long char *aux; -@@ -59,6 +60,7 @@ +@@ -62,6 +63,7 @@ int Start(void) const { return start; } int Stop(void) const { return stop; } int Priority(void) const { return priority; } @@ -370,8 +382,8 @@ int Lifetime(void) const { return lifetime; } const char *File(void) const { return file; } time_t FirstDay(void) const { return weekdays ? day : 0; } -@@ -95,6 +97,7 @@ - void SetAux(const char *Aux); +@@ -102,6 +104,7 @@ + void SetRemote(const char *Remote); void SetDeferred(int Seconds); void SetFlags(uint Flags); + void SetFskProtection(int aFlag); // PIN PATCH @@ -380,7 +392,7 @@ bool HasFlags(uint Flags) const; --- a/vdr.c +++ b/vdr.c -@@ -70,6 +70,7 @@ +@@ -71,6 +71,7 @@ #include "tools.h" #include "transfer.h" #include "videodir.h" @@ -388,15 +400,15 @@ #define MINCHANNELWAIT 10 // seconds to wait between failed channel switchings #define ACTIVITYTIMEOUT 60 // seconds before starting housekeeping -@@ -1087,6 +1088,7 @@ +@@ -1182,6 +1183,7 @@ cOsdObject *Interact = Menu ? Menu : cControl::Control(); eKeys key = Interface->GetKey(!Interact || !Interact->NeedsFastResponse()); if (ISREALKEY(key)) { -+ cStatus::MsgUserAction(key, Interact); // PIN PATCH ++ cStatus::MsgUserAction(key); // PIN PATCH EITScanner.Activity(); // Cancel shutdown countdown: if (ShutdownHandler.countdown) -@@ -1159,10 +1161,12 @@ +@@ -1254,10 +1256,12 @@ cControl::Control()->Hide(); cPlugin *plugin = cPluginManager::GetPlugin(PluginName); if (plugin) { @@ -409,7 +421,7 @@ else esyslog("ERROR: unknown plugin '%s'", PluginName); } -@@ -1375,9 +1379,11 @@ +@@ -1475,9 +1479,11 @@ // Instant resume of the last viewed recording: case kPlay: if (cReplayControl::LastReplayed()) { diff -Nru vdr-2.2.0/debian/patches/series vdr-2.3.3/debian/patches/series --- vdr-2.2.0/debian/patches/series 2016-10-22 19:27:42.000000000 +0000 +++ vdr-2.3.3/debian/patches/series 2017-04-14 08:46:17.000000000 +0000 @@ -6,20 +6,21 @@ opt-37-x_menuorg.patch opt-42-x_MainMenuHooks-v1.0.1.patch -opt-50_graphtftng.patch +opt-50_graphtft.patch opt-56_eventdetails_v3.patch -opt-57_vdr-remote_instant_recordings.patch +#opt-57_vdr-remote_instant_recordings.patch opt-61_dynamite.patch opt-65_pin.patch -vdr-2.2.0-hide-first-recording-level-v5.patch +vdr-2.3.1-hide-first-recording-level-v1.patch -vdr-2.2.0-permashift.patch +#vdr-2.2.0-permashift.patch +epg2vdr-2.3.2.patch vdr-2.2.0-missing-plugin.patch -vdr-2.2.0-toomanypids.patch -vdr-2.2.0-caid_buffer-v3.patch -systemd.diff -vdr-2.2.0_zapcockpit.patch -01-hevc.patch +vdr-2.3.2-aux.patch +vdr-2.3-patch-for-permashift.diff +vdr-2.3.2_osd2web.diff +fixsvdrpplaylock.diff +vdr-2.3.3-camrelations.diff diff -Nru vdr-2.2.0/debian/patches/systemd.diff vdr-2.3.3/debian/patches/systemd.diff --- vdr-2.2.0/debian/patches/systemd.diff 2016-05-31 18:41:20.000000000 +0000 +++ vdr-2.3.3/debian/patches/systemd.diff 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -Index: vdr-2.2.0/Makefile -=================================================================== ---- vdr-2.2.0.orig/Makefile -+++ vdr-2.2.0/Makefile -@@ -95,9 +95,9 @@ DEFINES += -DBIDI - LIBS += $(shell pkg-config --libs fribidi) - endif - ifdef SDNOTIFY --INCLUDES += $(shell pkg-config --cflags libsystemd-daemon) -+INCLUDES += $(shell pkg-config --cflags libsystemd) - DEFINES += -DSDNOTIFY --LIBS += $(shell pkg-config --libs libsystemd-daemon) -+LIBS += $(shell pkg-config --libs libsystemd) - endif - - LIRC_DEVICE ?= /var/run/lirc/lircd diff -Nru vdr-2.2.0/debian/patches/vdr-2.2.0-permashift.patch vdr-2.3.3/debian/patches/vdr-2.2.0-permashift.patch --- vdr-2.2.0/debian/patches/vdr-2.2.0-permashift.patch 2016-03-01 17:53:03.000000000 +0000 +++ vdr-2.3.3/debian/patches/vdr-2.2.0-permashift.patch 2017-03-29 20:33:05.000000000 +0000 @@ -185,7 +185,7 @@ bool Active(void); --- a/menu.c +++ b/menu.c -@@ -4930,6 +4930,16 @@ +@@ -4944,6 +4944,16 @@ cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause) { @@ -202,7 +202,7 @@ // Whatever happens here, the timers will be modified in some way... Timers.SetModified(); // We're going to manipulate an event here, so we need to prevent -@@ -4951,6 +4961,7 @@ +@@ -4965,6 +4975,7 @@ timer->SetPending(true); timer->SetRecording(true); event = timer->Event(); @@ -210,7 +210,7 @@ if (event || GetEvent()) dsyslog("Title: '%s' Subtitle: '%s'", event->Title(), event->ShortText()); -@@ -4978,8 +4989,21 @@ +@@ -4992,8 +5003,21 @@ isyslog("record %s", fileName); if (MakeDirs(fileName, true)) { const cChannel *ch = timer->Channel(); @@ -234,7 +234,7 @@ Recording.WriteInfo(); cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true); if (!Timer && !cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo() -@@ -5001,8 +5025,6 @@ +@@ -5015,8 +5039,6 @@ } return; } @@ -243,7 +243,7 @@ } else timer->SetDeferred(DEFERTIMER); -@@ -5077,7 +5099,7 @@ +@@ -5091,7 +5113,7 @@ cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL }; int cRecordControls::state = 0; @@ -252,7 +252,7 @@ { if (!Timer) { cTimer *t = new cTimer(true, Pause); -@@ -5156,7 +5178,7 @@ +@@ -5170,7 +5192,7 @@ if (!Timer || Timer->Matches()) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (!RecordControls[i]) { @@ -261,7 +261,7 @@ cStatus::MsgRecordingFile(RecordControls[i]->FileName()); // PIN PATCH return RecordControls[i]->Process(time(NULL)); } -@@ -5173,6 +5195,11 @@ +@@ -5187,6 +5209,11 @@ return false; } @@ -273,7 +273,7 @@ void cRecordControls::Stop(const char *InstantId) { ChangeState(); -@@ -5195,10 +5222,16 @@ +@@ -5209,10 +5236,16 @@ bool cRecordControls::PauseLiveVideo(void) { @@ -292,7 +292,7 @@ cControl::Launch(rc); cControl::Attach(); Skins.Message(mtStatus, NULL); -@@ -5338,7 +5371,18 @@ +@@ -5352,7 +5385,18 @@ cReplayControl::cReplayControl(bool PauseLive) :cDvbPlayerControl(fileName, PauseLive) { @@ -314,7 +314,7 @@ marksModified = false; --- a/menu.h +++ b/menu.h -@@ -240,6 +240,8 @@ +@@ -245,6 +245,8 @@ bool GetEvent(void); public: cRecordControl(cDevice *Device, cTimer *Timer = NULL, bool Pause = false); @@ -323,7 +323,7 @@ virtual ~cRecordControl(); bool Process(time_t t); cDevice *Device(void) { return device; } -@@ -255,8 +257,10 @@ +@@ -260,8 +262,10 @@ static int state; public: static bool Start(cTimer *Timer = NULL, bool Pause = false); @@ -334,7 +334,7 @@ static const char *GetInstantId(const char *LastInstantId); static cRecordControl *GetRecordControl(const char *FileName); static cRecordControl *GetRecordControl(const cTimer *Timer); -@@ -311,6 +315,8 @@ +@@ -316,6 +320,8 @@ void EditTest(void); public: cReplayControl(bool PauseLive = false); diff -Nru vdr-2.2.0/debian/patches/vdr-2.2.0_zapcockpit.patch vdr-2.3.3/debian/patches/vdr-2.2.0_zapcockpit.patch --- vdr-2.2.0/debian/patches/vdr-2.2.0_zapcockpit.patch 2016-05-31 18:44:01.000000000 +0000 +++ vdr-2.3.3/debian/patches/vdr-2.2.0_zapcockpit.patch 2017-03-29 20:32:01.000000000 +0000 @@ -1,8 +1,10 @@ -Index: vdr-2.2.0/config.c -=================================================================== ---- vdr-2.2.0.orig/config.c -+++ vdr-2.2.0/config.c -@@ -414,6 +414,11 @@ cSetup::cSetup(void) +Description: This patch provides an additional information in the channel info + menu. At the moment it only works with skindesigner skins. +Author: Louis Braun + +--- a/config.c ++++ b/config.c +@@ -414,6 +414,11 @@ SVDRPTimeout = 300; ZapTimeout = 3; ChannelEntryTimeout = 1000; @@ -14,7 +16,7 @@ RcRepeatDelay = 300; RcRepeatDelta = 100; DefaultPriority = 50; -@@ -636,6 +641,11 @@ bool cSetup::Parse(const char *Name, con +@@ -636,6 +641,11 @@ else if (!strcasecmp(Name, "SVDRPTimeout")) SVDRPTimeout = atoi(Value); else if (!strcasecmp(Name, "ZapTimeout")) ZapTimeout = atoi(Value); else if (!strcasecmp(Name, "ChannelEntryTimeout")) ChannelEntryTimeout= atoi(Value); @@ -26,7 +28,7 @@ else if (!strcasecmp(Name, "RcRepeatDelay")) RcRepeatDelay = atoi(Value); else if (!strcasecmp(Name, "RcRepeatDelta")) RcRepeatDelta = atoi(Value); else if (!strcasecmp(Name, "DefaultPriority")) DefaultPriority = atoi(Value); -@@ -762,6 +772,11 @@ bool cSetup::Save(void) +@@ -762,6 +772,11 @@ Store("SVDRPTimeout", SVDRPTimeout); Store("ZapTimeout", ZapTimeout); Store("ChannelEntryTimeout",ChannelEntryTimeout); @@ -38,11 +40,9 @@ Store("RcRepeatDelay", RcRepeatDelay); Store("RcRepeatDelta", RcRepeatDelta); Store("DefaultPriority", DefaultPriority); -Index: vdr-2.2.0/config.h -=================================================================== ---- vdr-2.2.0.orig/config.h -+++ vdr-2.2.0/config.h -@@ -296,6 +296,11 @@ public: +--- a/config.h ++++ b/config.h +@@ -296,6 +296,11 @@ int SVDRPTimeout; int ZapTimeout; int ChannelEntryTimeout; @@ -54,11 +54,9 @@ int RcRepeatDelay; int RcRepeatDelta; int DefaultPriority, DefaultLifetime; -Index: vdr-2.2.0/menu.c -=================================================================== ---- vdr-2.2.0.orig/menu.c -+++ vdr-2.2.0/menu.c -@@ -3815,6 +3815,11 @@ cMenuSetupMisc::cMenuSetupMisc(void) +--- a/menu.c ++++ b/menu.c +@@ -3815,6 +3815,11 @@ Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$SVDRP timeout (s)"), &data.SVDRPTimeout)); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Zap timeout (s)"), &data.ZapTimeout)); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Channel entry timeout (ms)"), &data.ChannelEntryTimeout, 0)); @@ -70,7 +68,7 @@ Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Remote control repeat delay (ms)"), &data.RcRepeatDelay, 0)); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Remote control repeat delta (ms)"), &data.RcRepeatDelta, 0)); Add(new cMenuEditChanItem(tr("Setup.Miscellaneous$Initial channel"), &data.InitialChannel, tr("Setup.Miscellaneous$as before"))); -@@ -4383,7 +4388,7 @@ cDisplayChannel::cDisplayChannel(int Num +@@ -4383,7 +4388,7 @@ lastTime.Set(); } @@ -79,7 +77,7 @@ :cOsdObject(true) { currentDisplayChannel = this; -@@ -4396,7 +4401,8 @@ cDisplayChannel::cDisplayChannel(eKeys F +@@ -4396,7 +4401,8 @@ displayChannel = Skins.Current()->DisplayChannel(withInfo); positioner = NULL; channel = Channels.GetByNumber(cDevice::CurrentChannel()); @@ -89,7 +87,7 @@ } cDisplayChannel::~cDisplayChannel() -@@ -4638,6 +4644,784 @@ eOSState cDisplayChannel::ProcessKey(eKe +@@ -4638,6 +4644,784 @@ return osEnd; } @@ -874,11 +872,9 @@ // --- cDisplayVolume -------------------------------------------------------- #define VOLUMETIMEOUT 1000 //ms -Index: vdr-2.2.0/menu.h -=================================================================== ---- vdr-2.2.0.orig/menu.h -+++ vdr-2.2.0/menu.h -@@ -115,30 +115,102 @@ public: +--- a/menu.h ++++ b/menu.h +@@ -115,30 +115,102 @@ class cDisplayChannel : public cOsdObject { private: @@ -989,11 +985,9 @@ class cDisplayVolume : public cOsdObject { private: cSkinDisplayVolume *displayVolume; -Index: vdr-2.2.0/po/de_DE.po -=================================================================== ---- vdr-2.2.0.orig/po/de_DE.po -+++ vdr-2.2.0/po/de_DE.po -@@ -8,7 +8,7 @@ msgid "" +--- a/po/de_DE.po ++++ b/po/de_DE.po +@@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: VDR 2.2.0\n" "Report-Msgid-Bugs-To: \n" @@ -1002,7 +996,7 @@ "PO-Revision-Date: 2015-02-10 13:45+0100\n" "Last-Translator: Klaus Schmidinger \n" "Language-Team: German \n" -@@ -1284,6 +1284,21 @@ msgstr "Mindestzeit für vorherigen Kanal +@@ -1284,6 +1284,21 @@ msgid "Setup.Miscellaneous$Channel entry timeout (ms)" msgstr "Zeitlimit für Kanaleingabe (ms)" @@ -1024,7 +1018,7 @@ msgid "Setup.Miscellaneous$Remote control repeat delay (ms)" msgstr "Fernbedienung Wiederholverzögerung (ms)" -@@ -1359,6 +1374,9 @@ msgstr "Aufzeichnung beenden?" +@@ -1359,6 +1374,9 @@ msgid "Cancel editing?" msgstr "Bearbeitung abbrechen?" @@ -1034,11 +1028,9 @@ msgid "No audio available!" msgstr "Kein Audio verfügbar!" -Index: vdr-2.2.0/skins.c -=================================================================== ---- vdr-2.2.0.orig/skins.c -+++ vdr-2.2.0/skins.c -@@ -79,6 +79,13 @@ void cSkinDisplayChannel::SetPositioner( +--- a/skins.c ++++ b/skins.c +@@ -79,6 +79,13 @@ SetMessage(mtInfo, cString::sprintf(tr("Moving dish to %.1f..."), double(positioner->TargetLongitude()) / 10)); } @@ -1052,11 +1044,9 @@ // --- cSkinDisplayMenu ------------------------------------------------------ cSkinDisplayMenu::cSkinDisplayMenu(void) -Index: vdr-2.2.0/skins.h -=================================================================== ---- vdr-2.2.0.orig/skins.h -+++ vdr-2.2.0/skins.h -@@ -88,6 +88,34 @@ public: +--- a/skins.h ++++ b/skins.h +@@ -88,6 +88,34 @@ */ }; @@ -1091,11 +1081,9 @@ enum eMenuCategory { mcUndefined = -1, mcUnknown = 0, -Index: vdr-2.2.0/vdr.c -=================================================================== ---- vdr-2.2.0.orig/vdr.c -+++ vdr-2.2.0/vdr.c -@@ -1007,7 +1007,7 @@ int main(int argc, char *argv[]) +--- a/vdr.c ++++ b/vdr.c +@@ -1007,7 +1007,7 @@ // Channel display: if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) { if (!Menu) @@ -1104,7 +1092,7 @@ LastChannel = cDevice::CurrentChannel(); LastChannelChanged = Now; } -@@ -1188,8 +1188,10 @@ int main(int argc, char *argv[]) +@@ -1188,8 +1188,10 @@ case kChanUp: case kChanDn|k_Repeat: case kChanDn: @@ -1117,7 +1105,7 @@ else if (cDisplayChannel::IsOpen() || cControl::Control()) { Interact->ProcessKey(key); continue; -@@ -1386,7 +1388,8 @@ int main(int argc, char *argv[]) +@@ -1386,7 +1388,8 @@ case kUp: case kDown|k_Repeat: case kDown: diff -Nru vdr-2.2.0/debian/patches/vdr-2.3.1-hide-first-recording-level-v1.patch vdr-2.3.3/debian/patches/vdr-2.3.1-hide-first-recording-level-v1.patch --- vdr-2.2.0/debian/patches/vdr-2.3.1-hide-first-recording-level-v1.patch 1970-01-01 00:00:00.000000000 +0000 +++ vdr-2.3.3/debian/patches/vdr-2.3.1-hide-first-recording-level-v1.patch 2017-03-29 20:34:48.000000000 +0000 @@ -0,0 +1,711 @@ +Description: Hide the first level of directories + in the recordings menu, all recordings are stored at + the "local" directory +Origin: http://www.vdr-portal.de/board17-developer/board97-vdr-core/p1255679-/#post1255679 +Forwarded: no +Author: Lars Hanisch + +--- a/menu.c ++++ b/menu.c +@@ -824,7 +824,7 @@ + else { + cStringList Dirs; + for (const cRecording *Recording = Recordings->First(); Recording; Recording = Recordings->Next(Recording)) { +- cString Folder = Recording->Folder(); ++ cString Folder = Recording->FileFolder(); + strreplace((char *)*Folder, FOLDERDELIMCHAR, FOLDERDELIMCHARSUBST); // makes sure parent folders come before subfolders + if (Dirs.Find(Folder) < 0) + Dirs.Append(strdup(Folder)); +@@ -2588,7 +2588,7 @@ + SetMenuCategory(mcRecordingEdit); + recording = Recording; + originalFileName = recording->FileName(); +- strn0cpy(folder, recording->Folder(), sizeof(folder)); ++ strn0cpy(folder, recording->FileFolder(), sizeof(folder)); + strn0cpy(name, recording->BaseName(), sizeof(name)); + priority = recording->Priority(); + lifetime = recording->Lifetime(); +@@ -2673,7 +2673,7 @@ + + eOSState cMenuRecordingEdit::Folder(void) + { +- return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, recording->Name())); ++ return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, *recording->FullName())); + } + + eOSState cMenuRecordingEdit::Action(void) +@@ -2746,7 +2746,7 @@ + } + cString NewName = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name; + NewName.CompactChars(FOLDERDELIMCHAR); +- if (strcmp(NewName, Recording->Name())) { ++ if (strcmp(NewName, *Recording->FullName())) { + if (!Recording->ChangeName(NewName)) { + StateKey.Remove(Modified); + Skins.Message(mtError, tr("Error while changing folder/name!")); +@@ -2999,7 +2999,7 @@ + CurrentRecording = ri->Recording()->FileName(); + int current = Current(); + Clear(); +- GetRecordingsSortMode(DirectoryName()); ++ GetRecordingsSortMode(DirectoryName(Recordings)); + Recordings->Sort(); + for (const cRecording *Recording = Recordings->First(); Recording; Recording = Recordings->Next(Recording)) { + if ((!filter || filter->Filter(Recording)) && (!base || (strstr(Recording->Name(), base) == Recording->Name() && Recording->Name()[strlen(base)] == FOLDERDELIMCHAR))) { +@@ -3054,10 +3054,19 @@ + fileName = FileName; + } + +-cString cMenuRecordings::DirectoryName(void) ++cString cMenuRecordings::DirectoryName(const cRecordings *Recordings) + { + cString d(cVideoDirectory::Name()); + if (base) { ++ if (cVideoDirectory::HideFirstRecordingLevel()) { ++ cRecordings::cFolderInfos::cFolderInfo* info = Recordings->GetFolderInfo(base); ++ if (info) { ++ if (info->FirstFolderNames.Size() > 0) ++ d = AddDirectory(d, info->FirstFolderNames.At(0)); ++ delete info; ++ } ++ } ++ + char *s = ExchangeChars(strdup(base), true); + d = AddDirectory(d, s); + free(s); +@@ -3204,7 +3213,10 @@ + { + if (HasSubMenu()) + return osContinue; +- IncRecordingsSortMode(DirectoryName()); ++ cStateKey recState; ++ const cRecordings *recs = cRecordings::GetRecordingsRead(recState); ++ IncRecordingsSortMode(DirectoryName(recs)); ++ recState.Remove(); + recordingsStateKey.Reset(); + Set(true); + return osContinue; +--- a/menu.h ++++ b/menu.h +@@ -224,7 +224,7 @@ + eOSState Sort(void); + eOSState Commands(eKeys Key = kNone); + protected: +- cString DirectoryName(void); ++ cString DirectoryName(const cRecordings *Recordings); + public: + cMenuRecordings(const char *Base = NULL, int Level = 0, bool OpenSubMenus = false, const cRecordingFilter *Filter = NULL); + ~cMenuRecordings(); +--- a/recording.c ++++ b/recording.c +@@ -44,9 +44,9 @@ + #define NAMEFORMAT "%s/%s/" DATAFORMAT + */ + #define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT +-#define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT ++#define NAMEFORMATPES "%s/%s%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT + #define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT +-#define NAMEFORMATTS "%s/%s/" DATAFORMATTS ++#define NAMEFORMATTS "%s/%s%s/" DATAFORMATTS + + #define RESUMEFILESUFFIX "/resume%s%s" + #ifdef SUMMARYFALLBACK +@@ -758,6 +758,9 @@ + sortBufferName = sortBufferTime = NULL; + fileName = NULL; + name = NULL; ++ firstFolder = ""; ++ if (cVideoDirectory::HideFirstRecordingLevel()) ++ firstFolder = LOCALRECFOLDER; + fileSizeMB = -1; // unknown + channel = Timer->Channel()->Number(); + instanceId = InstanceId; +@@ -827,6 +830,7 @@ + if (strstr(FileName, cVideoDirectory::Name()) == FileName) + FileName += strlen(cVideoDirectory::Name()) + 1; + const char *p = strrchr(FileName, '/'); ++ firstFolder = ""; + + name = NULL; + info = new cRecordingInfo(fileName); +@@ -841,9 +845,18 @@ + t.tm_mon--; + t.tm_sec = 0; + start = mktime(&t); +- name = MALLOC(char, p - FileName + 1); +- strncpy(name, FileName, p - FileName); +- name[p - FileName] = 0; ++ const char *copyFileName = FileName; ++ if (cVideoDirectory::HideFirstRecordingLevel()) { ++ const char *f = strchr(FileName, '/'); ++ if ((f != NULL) && (f < p)) { ++ copyFileName = f + 1; ++ firstFolder = FileName; ++ firstFolder.Truncate(f - FileName + 1); ++ } ++ } ++ name = MALLOC(char, p - copyFileName + 1); ++ strncpy(name, copyFileName, p - copyFileName); ++ name[p - copyFileName] = 0; + name = ExchangeChars(name, false); + isPesRecording = instanceId < 0; + } +@@ -980,7 +993,7 @@ + *sb = strdup(buf); + } + else { +- char *s = strdup(FileName() + strlen(cVideoDirectory::Name())); ++ char *s = strdup(FileName() + strlen(cVideoDirectory::Name()) + strlen(*firstFolder)); + if (RecordingsSortMode != rsmName || Setup.AlwaysSortFoldersFirst) + s = StripEpisodeName(s, RecordingsSortMode != rsmName); + strreplace(s, '/', '0'); // some locales ignore '/' when sorting +@@ -1023,6 +1036,30 @@ + return strncmp(Path, name, l) == 0 && (name[l] == FOLDERDELIMCHAR); + } + ++cString cRecording::FileFolder(void) const ++{ ++ if (cVideoDirectory::HideFirstRecordingLevel() && **firstFolder) { ++ char *s = strdup(*firstFolder); ++ s = ExchangeChars(s, false); ++ cString f = cString::sprintf("%s%s", s, *Folder()); ++ free(s); ++ return f; ++ } ++ return Folder(); ++} ++ ++cString cRecording::FullName(void) const ++{ ++ if (cVideoDirectory::HideFirstRecordingLevel() && **firstFolder) { ++ char *s = strdup(*firstFolder); ++ s = ExchangeChars(s, false); ++ cString n = cString::sprintf("%s%s", s, Name()); ++ free(s); ++ return n; ++ } ++ return Name(); ++} ++ + cString cRecording::Folder(void) const + { + if (char *s = strrchr(name, FOLDERDELIMCHAR)) +@@ -1049,7 +1086,7 @@ + if (strcmp(Name, name) != 0) + dsyslog("recording file name '%s' truncated to '%s'", name, Name); + Name = ExchangeChars(Name, true); +- fileName = strdup(cString::sprintf(fmt, cVideoDirectory::Name(), Name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri)); ++ fileName = strdup(cString::sprintf(fmt, cVideoDirectory::Name(), *firstFolder, Name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri)); + free(Name); + } + return fileName; +@@ -1217,20 +1254,36 @@ + + bool cRecording::ChangeName(const char *NewName) + { +- if (strcmp(NewName, Name())) { +- dsyslog("changing name of '%s' to '%s'", Name(), NewName); ++ cString fullName = FullName(); ++ if (strcmp(NewName, *fullName)) { ++ dsyslog("changing name of '%s' to '%s'", *fullName, NewName); + cString OldName = Name(); + cString OldFileName = FileName(); ++ cString OldFirstFolder = FirstFolder(); + free(fileName); + fileName = NULL; + free(name); +- name = strdup(NewName); ++ const char *p = strrchr(NewName, FOLDERDELIMCHAR); ++ const char *copyFileName = NewName; ++ if (cVideoDirectory::HideFirstRecordingLevel()) { ++ const char *f = strchr(NewName, FOLDERDELIMCHAR); ++ if ((f != NULL) && (f <= p)) { ++ copyFileName = f + 1; ++ char *s = strdup(NewName); ++ s[f - NewName + 1] = 0; ++ s = ExchangeChars(s, true); ++ firstFolder = s; ++ free(s); ++ } ++ } ++ name = strdup(copyFileName); + cString NewFileName = FileName(); + if (!(MakeDirs(NewFileName, true) && cVideoDirectory::MoveVideoFile(OldFileName, NewFileName))) { + free(name); + name = strdup(OldName); + free(fileName); + fileName = strdup(OldFileName); ++ firstFolder = OldFirstFolder; + return false; + } + isOnVideoDirectoryFileSystem = -1; // it might have been moved to a different file system +@@ -1452,6 +1505,202 @@ + } + } + ++// --- cRecordings::cFolderInfos --------------------------------------------- ++ ++class cRecordings::cFolderInfos::cFolderTree : public cListObject { ++private: ++ cFolderTree *parent; ++ cList *subFolders; ++ ++ cString name; ++ int count; ++ time_t latest; ++ cString latestFileName; ++ cStringList firstFolderNames; ++ ++ void UpdateData(const cRecording *Recording); ++ cFolderTree *FindSubFolder(const char *Name) const; ++ ++public: ++ cFolderTree(cFolderTree *Parent, const char *Name); ++ virtual ~cFolderTree(void); ++ ++ // split Name and find folder-info in tree ++ // if "Add", missing folders are created ++ cFolderTree *Find(const char *Name, bool Add); ++ void Add(const cRecording *Recording); ++ cFolderInfo *GetInfo(void) const; ++ cString FullName(void) const; ++}; ++ ++cRecordings::cFolderInfos::cFolderTree::cFolderTree(cFolderTree *Parent, const char *Name) ++:parent(Parent) ++,name(Name) ++,count(0) ++,latest(0) ++,latestFileName("") ++{ ++ subFolders = new cList(); ++} ++ ++cRecordings::cFolderInfos::cFolderTree::~cFolderTree(void) ++{ ++ delete subFolders; ++ subFolders = NULL; ++} ++ ++cRecordings::cFolderInfos::cFolderTree *cRecordings::cFolderInfos::cFolderTree::Find(const char *Name, bool Add) ++{ ++ cFolderTree *info = NULL; ++ if (Add) ++ info = this; ++ ++ if (Name && *Name) { ++ static char delim[2] = { FOLDERDELIMCHAR, 0 }; ++ char *strtok_next; ++ cFolderTree *next; ++ char *folder = strdup(Name); ++ info = this; ++ for (char *t = strtok_r(folder, delim, &strtok_next); t; t = strtok_r(NULL, delim, &strtok_next)) { ++ next = info->FindSubFolder(t); ++ if (next == NULL) { ++ if (!Add) { ++ info = NULL; ++ break; ++ } ++ ++ next = new cFolderTree(info, t); ++ info->subFolders->Add(next); ++ } ++ info = next; ++ } ++ free(folder); ++ } ++ ++ return info; ++} ++ ++void cRecordings::cFolderInfos::cFolderTree::UpdateData(const cRecording *Recording) ++{ ++ // count every recording ++ count++; ++ ++ // update date if newer ++ time_t recdate = Recording->Start(); ++ if (latest < recdate) { ++ latest = recdate; ++ latestFileName = Recording->FileName(); ++ } ++ ++ // add all possible first level folders ++ if (cVideoDirectory::HideFirstRecordingLevel()) { ++ const char *firstFolder = Recording->FirstFolder(); ++ if (firstFolderNames.Find(firstFolder) < 0) ++ firstFolderNames.Append(strdup(firstFolder)); ++ } ++} ++ ++cRecordings::cFolderInfos::cFolderTree *cRecordings::cFolderInfos::cFolderTree::FindSubFolder(const char *Name) const ++{ ++ for (cFolderTree *info = subFolders->First(); info; info = subFolders->Next(info)) { ++ if (strcmp(info->name, Name) == 0) ++ return info; ++ } ++ return NULL; ++} ++ ++void cRecordings::cFolderInfos::cFolderTree::Add(const cRecording *Recording) ++{ ++ if (Recording == NULL) ++ return; ++ ++ // update this and all parent folders ++ for (cFolderTree *p = this; p; p = p->parent) ++ p->UpdateData(Recording); ++} ++ ++cRecordings::cFolderInfos::cFolderInfo *cRecordings::cFolderInfos::cFolderTree::GetInfo(void) const ++{ ++ cFolderInfo *info = new cFolderInfo(*name, *FullName(), count, latest, *latestFileName); ++ // take care that LOCALRECFOLDER is the first item ++ bool addLocal = false; ++ for (int i = 0; i < firstFolderNames.Size(); i++) { ++ if (strcmp(firstFolderNames.At(i), LOCALRECFOLDER)) ++ addLocal = true; ++ else ++ info->FirstFolderNames.Append(strdup(firstFolderNames.At(i))); ++ } ++ info->FirstFolderNames.Sort(); ++ if (addLocal) ++ info->FirstFolderNames.Insert(strdup(LOCALRECFOLDER)); ++ return info; ++} ++ ++cString cRecordings::cFolderInfos::cFolderTree::FullName(void) const ++{ ++ static char delim[2] = { FOLDERDELIMCHAR, 0 }; ++ ++ cString n = name; ++ for (cFolderTree *p = parent; p; p = p->parent) { ++ // don't add FOLDERDELIMCHAR at start of FullName ++ if (p->parent == NULL) ++ break; ++ n = cString::sprintf("%s%s%s", *p->name, delim, *n); ++ } ++ return n; ++} ++ ++cRecordings::cFolderInfos::cFolderInfo::cFolderInfo(const char *Name, const char *FullName, int Count, time_t Latest, const char *LatestFileName) ++{ ++ this->Name = Name; ++ this->FullName = FullName; ++ this->Count = Count; ++ this->Latest = Latest; ++ this->LatestFileName= LatestFileName; ++} ++ ++cRecordings::cFolderInfos::cFolderInfos(const cRecordings *Recordings) ++:root(NULL) ++{ ++ Rebuild(Recordings); ++} ++ ++cRecordings::cFolderInfos::~cFolderInfos(void) ++{ ++ delete root; ++ root = NULL; ++} ++ ++void cRecordings::cFolderInfos::Rebuild(const cRecordings *Recordings) ++{ ++ delete root; ++ root = new cFolderTree(NULL, ""); ++ ++ cFolderTree *info; ++ cString folder; ++ for (const cRecording *rec = Recordings->First(); rec; rec = Recordings->Next(rec)) { ++ folder = rec->Folder(); ++ info = root->Find(*folder, true); ++ info->Add(rec); ++ } ++} ++ ++cRecordings::cFolderInfos::cFolderInfo *cRecordings::cFolderInfos::Get(const cRecordings *Recordings, const char *Folder) ++{ ++ cMutexLock lock(&rootLock); ++ ++ if (Recordings->Lock(recState)) { ++ Rebuild(Recordings); ++ recState.Remove(); ++ } ++ ++ cFolderTree *info = root->Find(Folder, false); ++ if (info == NULL) ++ return NULL; ++ ++ return info->GetInfo(); ++} ++ + // --- cRecordings ----------------------------------------------------------- + + cRecordings cRecordings::recordings; +@@ -1463,6 +1712,7 @@ + cRecordings::cRecordings(bool Deleted) + :cList(Deleted ? "DelRecs" : "Recordings") + { ++ folderInfos = NULL; + } + + cRecordings::~cRecordings() +@@ -1470,6 +1720,7 @@ + // The first one to be destructed deletes it: + delete videoDirectoryScannerThread; + videoDirectoryScannerThread = NULL; ++ delete folderInfos; + } + + const char *cRecordings::UpdateFileName(void) +@@ -1641,6 +1892,19 @@ + Recording->ClearSortName(); + } + ++cRecordings::cFolderInfos &cRecordings::GetFolderInfos(void) const ++{ ++ cMutexLock lock((cMutex*)&folderInfosMutex); ++ if (folderInfos == NULL) ++ folderInfos = new cFolderInfos(this); ++ return *folderInfos; ++} ++ ++cRecordings::cFolderInfos::cFolderInfo *cRecordings::GetFolderInfo(const char *Folder) const ++{ ++ return GetFolderInfos().Get(this, Folder); ++} ++ + // --- cDirCopier ------------------------------------------------------------ + + class cDirCopier : public cThread { +--- a/recording.h ++++ b/recording.h +@@ -19,6 +19,8 @@ + #include "tools.h" + + #define FOLDERDELIMCHAR '~' ++#define LOCALRECFOLDER "local/" ++#define HIDE_FIRST_RECORDING_LEVEL_PATCH + + extern int DirectoryPathMax; + extern int DirectoryNameMax; +@@ -103,6 +105,7 @@ + mutable char *sortBufferTime; + mutable char *fileName; + mutable char *name; ++ cString firstFolder; + mutable int fileSizeMB; + mutable int numFrames; + int channel; +@@ -133,6 +136,18 @@ + bool IsInPath(const char *Path) const; + ///< Returns true if this recording is stored anywhere under the given Path. + ///< If Path is NULL or an empty string, the entire video directory is checked. ++ const char *FirstFolder(void) const { return *firstFolder; } ++ ///< Returns the name of the first folder (without the video directory) of ++ ///< this recording including a trailing slash. Only filled with content if the ++ ///< option "hide-first-recording-level" is activated, otherwise and empty string "". ++ cString FileFolder(void) const; ++ ///< Returns the name of the folder this recording is stored in (without the ++ ///< video directory) but including the "first folder", even if the option ++ ///< "hide-first-recording-level" is activated. ++ cString FullName(void) const; ++ ///< Returns the full name of the recording (without the video directory) ++ ///< but including the "first folder", even if the option ++ ///< "hide-first-recording-level" is activated. + cString Folder(void) const; + ///< Returns the name of the folder this recording is stored in (without the + ///< video directory). For use in menus etc. +@@ -219,7 +234,50 @@ + class cVideoDirectoryScannerThread; + + class cRecordings : public cList { ++public: ++ class cFolderInfos { ++ private: ++ class cFolderTree; ++ ++ cStateKey recState; ++ cFolderTree *root; ++ cMutex rootLock; ++ ++ void Rebuild(const cRecordings *Recordings); ++ public: ++ class cFolderInfo { ++ public: ++ cString Name; ++ ///< Name of the folder ++ cString FullName; ++ ///< Name of the folder with all parent folders ++ cStringList FirstFolderNames; ++ ///< Names of the first level folders this folder belongs to ++ ///< if the first level is hidden ++ int Count; ++ ///< Total count of recordings in this folder and subfolders ++ time_t Latest; ++ ///< Timestamp of the latest recording in this folder or subfolders ++ cString LatestFileName; ++ ///< Filename of the latest recording ++ ++ cFolderInfo(const char *Name, const char *FullName, int Count, time_t Latest, const char *LatestFileName); ++ }; ++ ++ cFolderInfos(const cRecordings *Recordings); ++ ~cFolderInfos(void); ++ ++ cFolderInfo *Get(const cRecordings *Recordings, const char *Folder); ++ ///< The caller must delete the cFolderInfo object. ++ ///< If the given folder doesn't exists, NULL is returned. ++ ///< The internal tree will be rebuild if the recordings' ++ ///< state has changed. ++ ///< This function is thread-safe. ++ }; ++ + private: ++ cMutex folderInfosMutex; ++ mutable cFolderInfos *folderInfos; + static cRecordings recordings; + static cRecordings deletedRecordings; + static char *updateFileName; +@@ -284,6 +342,13 @@ + ///< If OldPath and NewPath are on different file systems, the recordings + ///< will be moved in a background process and this function returns true + ///< if all recordings have been successfully added to the RecordingsHandler. ++ cFolderInfos &GetFolderInfos(void) const; ++ cFolderInfos::cFolderInfo *GetFolderInfo(const char *Folder) const; ++ ///< The caller must delete the cFolderInfo object. ++ ///< If the given folder doesn't exists, NULL is returned. ++ ///< The internal tree will be rebuild if the recordings' ++ ///< state has changed. ++ ///< This function is thread-safe. + }; + + // Provide lock controlled access to the list: +--- a/svdrp.c ++++ b/svdrp.c +@@ -764,11 +764,12 @@ + " only data for that channel is listed. 'now', 'next', or 'at