vdr/osdbase.c
Klaus Schmidinger 66ab78a40f Version 1.5.0
- The CAM handling has been refactored. Instead of a cCiHandler per device there
  is now an abstract cCiAdapter and a cCamSlot. This allows each slot to be
  accessed individually.
- The general 15 seconds workaround time before opening the CAM menu has been
  removed. If the CAM menu doesn't open within a timeout, the enter menu command
  is now sent again.
- If a CAM is reset or pulled and reinserted, it now automatically starts
  decrypting the current channel again.
- The Setup/CAM menu now dynamically refreshes its items and displays whether
  a CAM is present or ready. The 'Reset' function no longer leaves the menu.
- The CAM menu will now be openend when pressing the Ok key on a slot entry.
- The CAM menu now stays within the current menu context and doesn't close and
  reopen the menu every time an option is selected.
- When an encrypted channel is switched to for the first time, VDR now checks
  explicitly whether a CAM can actually decrypt that channel. If there is more
  than one CAM in the system that claims to be able to decrypt the channel,
  they are all tried in turn.
  To make this possible, an encrypted channel needs to be received in Transfer
  Mode when it is switched to for the first time, so that VDR can determine
  whether the TS packets are actually decrypted. Once a channel is known to
  be decrypted by a particular CAM, the next time it is switched to it will
  be shown in normal live viewing mode.
- A cDevice now automatically detaches all cReceiver objects that receive PIDs
  that can't be decrypted with the current CAM. A plugin that attaches a cReceiver
  to a device should therefore watch the receiver's IsAttached() function to
  see if it is still attached to the device.
- The cReceiver constructor no longer takes an 'int Ca' as its first parameter,
  but rather a 'tChannelID ChannelID'. This is necessary for the device to be
  able to determine which CAM a particular channel can be decrypted with. If the
  channel is known to be unencrypted, or a plugin doesn't want to provide the
  channel id for other reasons, an invalid tChannelID() can be given.
- The cThread::Start() function now waits until a previous incarnation of this
  thread has actually stopped. Before this it could happen that a thread's
  Cancel(-1) function was called and immediately after that it was started again,
  but the Start() function still found it to be 'active'.
- The parameter NeedsDetachReceivers in cDevice::GetDevice(const cChannel *Channel, ...)
  has been removed. A call to this function will automatically detach all receivers
  from the device if it returns a non-NULL pointer.
- The cTimeMs class now accepts an initial timeout value in its constructor.
- A CAM is now explicitly instructed to stop decrypting when switching away from
  an encrypted channel.
- If the CAM in use can decrypt several channels at the same time, VDR can
  now make use if this capability. Whether or not a CAM can decrypt more
  than one channel is determined by sending it an initial empty QUERY command
  and testing whether it replies to it.
- Ca values in the range 0...F in channels.conf can still be used to assign a channel
  to a particular device, but this will no longer work with encrypted channels because
  without valid CA ids VDR can't decide which CAM slot to use. However, since VDR now
  automatically determines which CAM can decrypt which channel, setting fixed
  channel/device relations should no longer be necessary.
  IF AN ENCRYPTED CHANNEL CAN'T BE DECRYPTED AND YOU HAVE A CA VALUE IN THE RANGE
  0...F FOR THAT CHANNEL, SET IT TO 0 (FTA) AND TUNE TO THE CHANNEL AGAIN.
2007-01-07 18:00:00 +01:00

518 lines
12 KiB
C

/*
* osdbase.c: Basic interface to the On Screen Display
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: osdbase.c 1.30 2007/01/05 10:41:16 kls Exp $
*/
#include "osdbase.h"
#include <string.h>
#include "device.h"
#include "i18n.h"
#include "remote.h"
#include "status.h"
// --- cOsdItem --------------------------------------------------------------
cOsdItem::cOsdItem(eOSState State)
{
text = NULL;
state = State;
selectable = true;
fresh = true;
}
cOsdItem::cOsdItem(const char *Text, eOSState State, bool Selectable)
{
text = NULL;
state = State;
selectable = Selectable;
fresh = true;
SetText(Text);
}
cOsdItem::~cOsdItem()
{
free(text);
}
void cOsdItem::SetText(const char *Text, bool Copy)
{
free(text);
text = Copy ? strdup(Text) : (char *)Text; // text assumes ownership!
}
void cOsdItem::SetSelectable(bool Selectable)
{
selectable = Selectable;
}
void cOsdItem::SetFresh(bool Fresh)
{
fresh = Fresh;
}
eOSState cOsdItem::ProcessKey(eKeys Key)
{
return Key == kOk ? state : osUnknown;
}
// --- cOsdObject ------------------------------------------------------------
void cOsdObject::Show(void)
{
if (isMenu)
((cOsdMenu *)this)->Display();
}
// --- cOsdMenu --------------------------------------------------------------
cSkinDisplayMenu *cOsdMenu::displayMenu = NULL;
int cOsdMenu::displayMenuCount = 0;
int cOsdMenu::displayMenuItems = 0;//XXX dynamic???
cOsdMenu::cOsdMenu(const char *Title, int c0, int c1, int c2, int c3, int c4)
{
isMenu = true;
digit = 0;
hasHotkeys = false;
title = NULL;
SetTitle(Title);
SetCols(c0, c1, c2, c3, c4);
first = 0;
current = marked = -1;
subMenu = NULL;
helpRed = helpGreen = helpYellow = helpBlue = NULL;
status = NULL;
if (!displayMenuCount++) {
displayMenu = Skins.Current()->DisplayMenu();
displayMenuItems = displayMenu->MaxItems();
}
}
cOsdMenu::~cOsdMenu()
{
free(title);
delete subMenu;
free(status);
displayMenu->Clear();
cStatus::MsgOsdClear();
if (!--displayMenuCount)
DELETENULL(displayMenu);
}
const char *cOsdMenu::hk(const char *s)
{
static char buffer[64];
if (s && hasHotkeys) {
if (digit == 0 && '1' <= *s && *s <= '9' && *(s + 1) == ' ')
digit = -1; // prevents automatic hotkeys - input already has them
if (digit >= 0) {
digit++;
snprintf(buffer, sizeof(buffer), " %c %s", (digit < 10) ? '0' + digit : ' ' , s);
s = buffer;
}
}
return s;
}
void cOsdMenu::SetCols(int c0, int c1, int c2, int c3, int c4)
{
cols[0] = c0;
cols[1] = c1;
cols[2] = c2;
cols[3] = c3;
cols[4] = c4;
}
void cOsdMenu::SetHasHotkeys(bool HasHotkeys)
{
hasHotkeys = HasHotkeys;
digit = 0;
}
void cOsdMenu::SetStatus(const char *s)
{
free(status);
status = s ? strdup(s) : NULL;
displayMenu->SetMessage(mtStatus, s);
}
void cOsdMenu::SetTitle(const char *Title)
{
free(title);
title = strdup(Title);
}
void cOsdMenu::SetHelp(const char *Red, const char *Green, const char *Yellow, const char *Blue)
{
// strings are NOT copied - must be constants!!!
helpRed = Red;
helpGreen = Green;
helpYellow = Yellow;
helpBlue = Blue;
displayMenu->SetButtons(helpRed, helpGreen, helpYellow, helpBlue);
cStatus::MsgOsdHelpKeys(helpRed, helpGreen, helpYellow, helpBlue);
}
void cOsdMenu::Del(int Index)
{
cList<cOsdItem>::Del(Get(Index));
int count = Count();
while (current < count && !SelectableItem(current))
current++;
if (current == count) {
while (current > 0 && !SelectableItem(current))
current--;
}
if (Index == first && first > 0)
first--;
}
void cOsdMenu::Add(cOsdItem *Item, bool Current, cOsdItem *After)
{
cList<cOsdItem>::Add(Item, After);
if (Current)
current = Item->Index();
}
void cOsdMenu::Ins(cOsdItem *Item, bool Current, cOsdItem *Before)
{
cList<cOsdItem>::Ins(Item, Before);
if (Current)
current = Item->Index();
}
void cOsdMenu::Display(void)
{
if (subMenu) {
subMenu->Display();
return;
}
displayMenu->SetMessage(mtStatus, NULL);
displayMenu->Clear();
cStatus::MsgOsdClear();
displayMenu->SetTabs(cols[0], cols[1], cols[2], cols[3], cols[4]);//XXX
displayMenu->SetTitle(title);
cStatus::MsgOsdTitle(title);
displayMenu->SetButtons(helpRed, helpGreen, helpYellow, helpBlue);
cStatus::MsgOsdHelpKeys(helpRed, helpGreen, helpYellow, helpBlue);
int count = Count();
if (count > 0) {
int ni = 0;
for (cOsdItem *item = First(); item; item = Next(item)) {
cStatus::MsgOsdItem(item->Text(), ni++);
if (current < 0 && item->Selectable())
current = item->Index();
}
if (current < 0)
current = 0; // just for safety - there HAS to be a current item!
if (current - first >= displayMenuItems || current < first) {
first = current - displayMenuItems / 2;
if (first + displayMenuItems > count)
first = count - displayMenuItems;
if (first < 0)
first = 0;
}
int i = first;
int n = 0;
for (cOsdItem *item = Get(first); item; item = Next(item)) {
bool CurrentSelectable = (i == current) && item->Selectable();
displayMenu->SetItem(item->Text(), i - first, CurrentSelectable, item->Selectable());
if (CurrentSelectable)
cStatus::MsgOsdCurrentItem(item->Text());
if (++n == displayMenuItems)
break;
i++;
}
}
if (!isempty(status))
displayMenu->SetMessage(mtStatus, status);
}
void cOsdMenu::SetCurrent(cOsdItem *Item)
{
current = Item ? Item->Index() : -1;
}
void cOsdMenu::RefreshCurrent(void)
{
cOsdItem *item = Get(current);
if (item)
item->Set();
}
void cOsdMenu::DisplayCurrent(bool Current)
{
cOsdItem *item = Get(current);
if (item) {
displayMenu->SetItem(item->Text(), current - first, Current && item->Selectable(), item->Selectable());
if (Current && item->Selectable())
cStatus::MsgOsdCurrentItem(item->Text());
if (!Current)
item->SetFresh(true); // leaving the current item resets 'fresh'
}
}
void cOsdMenu::DisplayItem(cOsdItem *Item)
{
if (Item) {
int Index = Item->Index();
int Offset = Index - first;
if (Offset >= 0 && Offset < first + displayMenuItems) {
bool Current = Index == current;
displayMenu->SetItem(Item->Text(), Offset, Current && Item->Selectable(), Item->Selectable());
if (Current && Item->Selectable())
cStatus::MsgOsdCurrentItem(Item->Text());
}
}
}
void cOsdMenu::Clear(void)
{
if (marked >= 0)
SetStatus(NULL);
first = 0;
current = marked = -1;
cList<cOsdItem>::Clear();
}
bool cOsdMenu::SelectableItem(int idx)
{
cOsdItem *item = Get(idx);
return item && item->Selectable();
}
void cOsdMenu::CursorUp(void)
{
int tmpCurrent = current;
int lastOnScreen = first + displayMenuItems - 1;
int last = Count() - 1;
if (last < 0)
return;
while (--tmpCurrent != current) {
if (tmpCurrent < 0) {
if (first > 0) {
// make non-selectable items at the beginning visible:
first = 0;
Display();
return;
}
if (Setup.MenuScrollWrap)
tmpCurrent = last + 1;
else
return;
}
else if (SelectableItem(tmpCurrent))
break;
}
if (first <= tmpCurrent && tmpCurrent <= lastOnScreen)
DisplayCurrent(false);
current = tmpCurrent;
if (current < first) {
first = Setup.MenuScrollPage ? max(0, current - displayMenuItems + 1) : current;
Display();
}
else if (current > lastOnScreen) {
first = max(0, current - displayMenuItems + 1);
Display();
}
else
DisplayCurrent(true);
}
void cOsdMenu::CursorDown(void)
{
int tmpCurrent = current;
int lastOnScreen = first + displayMenuItems - 1;
int last = Count() - 1;
if (last < 0)
return;
while (++tmpCurrent != current) {
if (tmpCurrent > last) {
if (first < last - displayMenuItems) {
// make non-selectable items at the end visible:
first = last - displayMenuItems + 1;
Display();
return;
}
if (Setup.MenuScrollWrap)
tmpCurrent = -1;
else
return;
}
else if (SelectableItem(tmpCurrent))
break;
}
if (first <= tmpCurrent && tmpCurrent <= lastOnScreen)
DisplayCurrent(false);
current = tmpCurrent;
if (current > lastOnScreen) {
first = Setup.MenuScrollPage ? current : max(0, current - displayMenuItems + 1);
if (first + displayMenuItems > last)
first = max(0, last - displayMenuItems + 1);
Display();
}
else if (current < first) {
first = current;
Display();
}
else
DisplayCurrent(true);
}
void cOsdMenu::PageUp(void)
{
int oldCurrent = current;
int oldFirst = first;
current -= displayMenuItems;
first -= displayMenuItems;
int last = Count() - 1;
if (current < 0)
current = 0;
if (first < 0)
first = 0;
int tmpCurrent = current;
while (!SelectableItem(tmpCurrent) && --tmpCurrent >= 0)
;
if (tmpCurrent < 0) {
tmpCurrent = current;
while (++tmpCurrent <= last && !SelectableItem(tmpCurrent))
;
}
current = tmpCurrent <= last ? tmpCurrent : -1;
if (current >= 0) {
if (current < first)
first = current;
else if (current - first >= displayMenuItems)
first = current - displayMenuItems + 1;
}
if (current != oldCurrent || first != oldFirst) {
Display();
DisplayCurrent(true);
}
else if (Setup.MenuScrollWrap)
CursorUp();
}
void cOsdMenu::PageDown(void)
{
int oldCurrent = current;
int oldFirst = first;
current += displayMenuItems;
first += displayMenuItems;
int last = Count() - 1;
if (current > last)
current = last;
if (first + displayMenuItems > last)
first = max(0, last - displayMenuItems + 1);
int tmpCurrent = current;
while (!SelectableItem(tmpCurrent) && ++tmpCurrent <= last)
;
if (tmpCurrent > last) {
tmpCurrent = current;
while (--tmpCurrent >= 0 && !SelectableItem(tmpCurrent))
;
}
current = tmpCurrent > 0 ? tmpCurrent : -1;
if (current >= 0) {
if (current < first)
first = current;
else if (current - first >= displayMenuItems)
first = current - displayMenuItems + 1;
}
if (current != oldCurrent || first != oldFirst) {
Display();
DisplayCurrent(true);
}
else if (Setup.MenuScrollWrap)
CursorDown();
}
void cOsdMenu::Mark(void)
{
if (Count() && marked < 0) {
marked = current;
SetStatus(tr("Up/Dn for new location - OK to move"));
}
}
eOSState cOsdMenu::HotKey(eKeys Key)
{
for (cOsdItem *item = First(); item; item = Next(item)) {
const char *s = item->Text();
if (s && (s = skipspace(s)) != NULL) {
if (*s == Key - k1 + '1') {
current = item->Index();
RefreshCurrent();
Display();
cRemote::Put(kOk, true);
break;
}
}
}
return osContinue;
}
eOSState cOsdMenu::AddSubMenu(cOsdMenu *SubMenu)
{
delete subMenu;
subMenu = SubMenu;
subMenu->Display();
return osContinue; // convenience return value
}
eOSState cOsdMenu::CloseSubMenu()
{
delete subMenu;
subMenu = NULL;
RefreshCurrent();
Display();
return osContinue; // convenience return value
}
eOSState cOsdMenu::ProcessKey(eKeys Key)
{
if (subMenu) {
eOSState state = subMenu->ProcessKey(Key);
if (state == osBack)
return CloseSubMenu();
return state;
}
cOsdItem *item = Get(current);
if (marked < 0 && item) {
eOSState state = item->ProcessKey(Key);
if (state != osUnknown) {
DisplayCurrent(true);
return state;
}
}
switch (Key) {
case k0: return osUnknown;
case k1...k9: return hasHotkeys ? HotKey(Key) : osUnknown;
case kUp|k_Repeat:
case kUp: CursorUp(); break;
case kDown|k_Repeat:
case kDown: CursorDown(); break;
case kLeft|k_Repeat:
case kLeft: PageUp(); break;
case kRight|k_Repeat:
case kRight: PageDown(); break;
case kBack: return osBack;
case kOk: if (marked >= 0) {
SetStatus(NULL);
if (marked != current)
Move(marked, current);
marked = -1;
break;
}
// else run into default
default: if (marked < 0)
return osUnknown;
}
return osContinue;
}