mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
- Date and time in the title of an event info page are now always right adjusted. - The 'current channel' is now handled device specific (in case there is more than one DVB card). - The 'SetSystemTime' option in the "Setup" menu is now shown as "yes/no". - Implemented "internationalization" (see 'i18n.c' for information on how to add new languages). Thanks to Miha Setina for translating the OSD texts to the Slovenian language. - Fixed learning keys on the PC keyboard (display oscillated). - Fixed a timing problem with OSD refresh and SVDRP. - Avoiding multiple definitions of the same timer in the "Schedule" menu (this could happen when pressing the "Red" button while editing the timer). - There can now be a configuration file named 'commands.conf' that defines commands that can be executed through the "Main" menu's "Commands" option (see FORMATS for details on how to define these commands). - Added a 'fixed' font for use with the output of system commands. - The 'Priority' parameter of the timers is now also used to interrupt a low priority timer recording if a higher priority timer wants to record. - A timer recording on a DVB card with a CAM module will now be interrupted by a timer that needs to use this specific DVB card to record an encrypted channel, if the timer currently occupying this DVB card doesn't need the CAM module (and thus can continue recording on a different DVB card). - The "Yellow" button in the "What's on now/next?" menus now displays the schedule of the current channel from that menu. - All DVB cards in a multi-card system now write their EIT information into the same data structure. - If there is more than one DVB card in the system, the non-primary cards are now used to periodically scan through the channels in order to keep the EPG info up-to-date. Scanning kicks in after 60 seconds of user inactivity (timeout in order to keep user interactions instantaneously) and each channel that has the 'pnr' parameter defined in 'channels.conf' is switched to for 20 seconds. If there is only one DVB card in the system, that card will start scanning after 5 hours (configurable through the "Setup" menu) of user inactivity and will switch back to the channel it originally displayed at the first sign of user activity. Any scanning will only occur if that particular card is not currently recording or replaying. - Now shifting the 'Subtitle' info into the 'ExtendedDescription' on stations that don't send the EIT information correctly (like, e.g., 'VOX'). - Implemented a 10 seconds latency when removing files. - Fixed unwanted reaction on the "Green" and "Yellow" button in the "Event" display. - Implemented 'Transfer Mode' to display video data from the DVB card that actually can receive a certain channel on the primary interface. This is currently in an early state and may still cause some problems, but it appears to work nice already.
451 lines
11 KiB
C
451 lines
11 KiB
C
/*
|
|
* interface.c: Abstract user interface layer
|
|
*
|
|
* See the main source file 'vdr.c' for copyright information and
|
|
* how to reach the author.
|
|
*
|
|
* $Id: interface.c 1.32 2000/11/18 15:28:50 kls Exp $
|
|
*/
|
|
|
|
#include "interface.h"
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include "i18n.h"
|
|
|
|
cInterface *Interface = NULL;
|
|
|
|
cInterface::cInterface(int SVDRPport)
|
|
{
|
|
open = 0;
|
|
cols[0] = 0;
|
|
width = height = 0;
|
|
keyFromWait = kNone;
|
|
rcIo = NULL;
|
|
SVDRP = NULL;
|
|
#if defined(REMOTE_RCU)
|
|
rcIo = new cRcIoRCU("/dev/ttyS1");
|
|
#elif defined(REMOTE_LIRC)
|
|
rcIo = new cRcIoLIRC("/dev/lircd");
|
|
#else
|
|
rcIo = new cRcIoKBD;
|
|
#endif
|
|
rcIo->SetCode(Keys.code, Keys.address);
|
|
if (SVDRPport)
|
|
SVDRP = new cSVDRP(SVDRPport);
|
|
}
|
|
|
|
cInterface::~cInterface()
|
|
{
|
|
delete rcIo;
|
|
delete SVDRP;
|
|
}
|
|
|
|
void cInterface::Open(int NumCols, int NumLines)
|
|
{
|
|
if (!open++)
|
|
cDvbApi::PrimaryDvbApi->Open(width = NumCols, height = NumLines);
|
|
}
|
|
|
|
void cInterface::Close(void)
|
|
{
|
|
if (open == 1)
|
|
Clear();
|
|
if (!--open) {
|
|
cDvbApi::PrimaryDvbApi->Close();
|
|
width = height = 0;
|
|
}
|
|
}
|
|
|
|
unsigned int cInterface::GetCh(bool Wait, bool *Repeat, bool *Release)
|
|
{
|
|
Flush();
|
|
if (!rcIo->InputAvailable())
|
|
cFile::AnyFileReady(-1, Wait ? 1000 : 0);
|
|
unsigned int Command;
|
|
return rcIo->GetCommand(&Command, Repeat, Release) ? Command : 0;
|
|
}
|
|
|
|
eKeys cInterface::GetKey(bool Wait)
|
|
{
|
|
Flush();
|
|
if (SVDRP)
|
|
SVDRP->Process();
|
|
eKeys Key = keyFromWait;
|
|
if (Key == kNone) {
|
|
bool Repeat = false, Release = false;
|
|
Key = Keys.Get(GetCh(Wait, &Repeat, &Release));
|
|
if (Repeat)
|
|
Key = eKeys(Key | k_Repeat);
|
|
if (Release)
|
|
Key = eKeys(Key | k_Release);
|
|
}
|
|
keyFromWait = kNone;
|
|
return Key;
|
|
}
|
|
|
|
void cInterface::PutKey(eKeys Key)
|
|
{
|
|
keyFromWait = Key;
|
|
}
|
|
|
|
eKeys cInterface::Wait(int Seconds, bool KeepChar)
|
|
{
|
|
Flush();
|
|
eKeys Key = kNone;
|
|
time_t timeout = time(NULL) + Seconds;
|
|
for (;;) {
|
|
Key = GetKey();
|
|
if ((Key != kNone && (RAWKEY(Key) != kOk || RAWKEY(Key) == Key)) || time(NULL) > timeout)
|
|
break;
|
|
}
|
|
if (KeepChar && ISRAWKEY(Key))
|
|
keyFromWait = Key;
|
|
return Key;
|
|
}
|
|
|
|
void cInterface::Clear(void)
|
|
{
|
|
if (open)
|
|
cDvbApi::PrimaryDvbApi->Clear();
|
|
}
|
|
|
|
void cInterface::ClearEol(int x, int y, eDvbColor Color)
|
|
{
|
|
if (open)
|
|
cDvbApi::PrimaryDvbApi->ClrEol(x, y, Color);
|
|
}
|
|
|
|
void cInterface::Fill(int x, int y, int w, int h, eDvbColor Color)
|
|
{
|
|
if (open)
|
|
cDvbApi::PrimaryDvbApi->Fill(x, y, w, h, Color);
|
|
}
|
|
|
|
void cInterface::Flush(void)
|
|
{
|
|
if (open)
|
|
cDvbApi::PrimaryDvbApi->Flush();
|
|
}
|
|
|
|
void cInterface::SetCols(int *c)
|
|
{
|
|
for (int i = 0; i < MaxCols; i++) {
|
|
cols[i] = *c++;
|
|
if (cols[i] == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
eDvbFont cInterface::SetFont(eDvbFont Font)
|
|
{
|
|
return cDvbApi::PrimaryDvbApi->SetFont(Font);
|
|
}
|
|
|
|
char *cInterface::WrapText(const char *Text, int Width, int *Height)
|
|
{
|
|
// Wraps the Text to make it fit into the area defined by the given Width
|
|
// (which is given in character cells).
|
|
// The actual number of lines resulting from this operation is returned in
|
|
// Height.
|
|
// The returned string is newly created on the heap and the caller
|
|
// is responsible for deleting it once it is no longer used.
|
|
// Wrapping is done by inserting the necessary number of newline
|
|
// characters into the string.
|
|
|
|
int Lines = 1;
|
|
char *t = strdup(Text);
|
|
char *Blank = NULL;
|
|
char *Delim = NULL;
|
|
int w = 0;
|
|
|
|
Width *= cDvbApi::PrimaryDvbApi->CellWidth();
|
|
|
|
while (*t && t[strlen(t) - 1] == '\n')
|
|
t[strlen(t) - 1] = 0; // skips trailing newlines
|
|
|
|
for (char *p = t; *p; ) {
|
|
if (*p == '\n') {
|
|
Lines++;
|
|
w = 0;
|
|
Blank = Delim = NULL;
|
|
p++;
|
|
continue;
|
|
}
|
|
else if (isspace(*p))
|
|
Blank = p;
|
|
int cw = cDvbApi::PrimaryDvbApi->Width(*p);
|
|
if (w + cw > Width) {
|
|
if (Blank) {
|
|
*Blank = '\n';
|
|
p = Blank;
|
|
continue;
|
|
}
|
|
else {
|
|
// Here's the ugly part, where we don't have any whitespace to
|
|
// punch in a newline, so we need to make room for it:
|
|
if (Delim)
|
|
p = Delim + 1; // let's fall back to the most recent delimiter
|
|
char *s = new char[strlen(t) + 2]; // The additional '\n' plus the terminating '\0'
|
|
int l = p - t;
|
|
strncpy(s, t, l);
|
|
s[l] = '\n';
|
|
strcpy(s + l + 1, p);
|
|
delete t;
|
|
t = s;
|
|
p = t + l;
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
w += cw;
|
|
if (strchr("-.,:;!?_", *p)) {
|
|
Delim = p;
|
|
Blank = NULL;
|
|
}
|
|
p++;
|
|
}
|
|
|
|
*Height = Lines;
|
|
return t;
|
|
}
|
|
|
|
void cInterface::Write(int x, int y, const char *s, eDvbColor FgColor, eDvbColor BgColor)
|
|
{
|
|
if (open)
|
|
cDvbApi::PrimaryDvbApi->Text(x, y, s, FgColor, BgColor);
|
|
}
|
|
|
|
void cInterface::WriteText(int x, int y, const char *s, eDvbColor FgColor, eDvbColor BgColor)
|
|
{
|
|
if (open) {
|
|
ClearEol(x, y, BgColor);
|
|
int col = 0;
|
|
for (;;) {
|
|
const char *t = strchr(s, '\t');
|
|
const char *p = s;
|
|
char buf[1000];
|
|
if (t && col < MaxCols && cols[col] > 0) {
|
|
unsigned int n = t - s;
|
|
if (n >= sizeof(buf))
|
|
n = sizeof(buf) - 1;
|
|
strncpy(buf, s, n);
|
|
buf[n] = 0;
|
|
p = buf;
|
|
s = t + 1;
|
|
}
|
|
Write(x, y, p, FgColor, BgColor);
|
|
if (p == s)
|
|
break;
|
|
x += cols[col++];
|
|
}
|
|
}
|
|
}
|
|
|
|
void cInterface::Title(const char *s)
|
|
{
|
|
ClearEol(0, 0, clrCyan);
|
|
const char *t = strchr(s, '\t');
|
|
if (t) {
|
|
char buffer[Width() + 1];
|
|
unsigned int n = t - s;
|
|
if (n >= sizeof(buffer))
|
|
n = sizeof(buffer) - 1;
|
|
strn0cpy(buffer, s, n);
|
|
Write(1, 0, buffer, clrBlack, clrCyan);
|
|
t++;
|
|
Write(-(cDvbApi::PrimaryDvbApi->WidthInCells(t) + 1), 0, t, clrBlack, clrCyan);
|
|
}
|
|
else {
|
|
int x = (Width() - strlen(s)) / 2;
|
|
if (x < 0)
|
|
x = 0;
|
|
Write(x, 0, s, clrBlack, clrCyan);
|
|
}
|
|
}
|
|
|
|
void cInterface::Status(const char *s, eDvbColor FgColor, eDvbColor BgColor)
|
|
{
|
|
ClearEol(0, -3, s ? BgColor : clrBackground);
|
|
if (s)
|
|
Write(0, -3, s, FgColor, BgColor);
|
|
}
|
|
|
|
void cInterface::Info(const char *s)
|
|
{
|
|
Open();
|
|
isyslog(LOG_INFO, s);
|
|
Status(s, clrWhite, clrGreen);
|
|
Wait();
|
|
Status(NULL);
|
|
Close();
|
|
}
|
|
|
|
void cInterface::Error(const char *s)
|
|
{
|
|
Open();
|
|
esyslog(LOG_ERR, s);
|
|
Status(s, clrWhite, clrRed);
|
|
Wait();
|
|
Status(NULL);
|
|
Close();
|
|
}
|
|
|
|
bool cInterface::Confirm(const char *s)
|
|
{
|
|
Open();
|
|
isyslog(LOG_INFO, "confirm: %s", s);
|
|
Status(s, clrBlack, clrGreen);
|
|
bool result = Wait(10) == kOk;
|
|
Status(NULL);
|
|
Close();
|
|
isyslog(LOG_INFO, "%sconfirmed", result ? "" : "not ");
|
|
return result;
|
|
}
|
|
|
|
void cInterface::HelpButton(int Index, const char *Text, eDvbColor FgColor, eDvbColor BgColor)
|
|
{
|
|
if (open && Text) {
|
|
const int w = Width() / 4;
|
|
int l = (w - strlen(Text)) / 2;
|
|
if (l < 0)
|
|
l = 0;
|
|
cDvbApi::PrimaryDvbApi->Fill(Index * w, -1, w, 1, BgColor);
|
|
cDvbApi::PrimaryDvbApi->Text(Index * w + l, -1, Text, FgColor, BgColor);
|
|
}
|
|
}
|
|
|
|
void cInterface::Help(const char *Red, const char *Green, const char *Yellow, const char *Blue)
|
|
{
|
|
HelpButton(0, Red, clrBlack, clrRed);
|
|
HelpButton(1, Green, clrBlack, clrGreen);
|
|
HelpButton(2, Yellow, clrBlack, clrYellow);
|
|
HelpButton(3, Blue, clrWhite, clrBlue);
|
|
}
|
|
|
|
void cInterface::QueryKeys(void)
|
|
{
|
|
Keys.Clear();
|
|
Clear();
|
|
WriteText(1, 1, tr("Learning Remote Control Keys"));
|
|
WriteText(1, 3, tr("Phase 1: Detecting RC code type"));
|
|
WriteText(1, 5, tr("Press any key on the RC unit"));
|
|
Flush();
|
|
#ifndef REMOTE_KBD
|
|
unsigned char Code = 0;
|
|
unsigned short Address;
|
|
#endif
|
|
for (;;) {
|
|
#ifdef REMOTE_KBD
|
|
if (GetCh())
|
|
break;
|
|
#else
|
|
//TODO on screen display...
|
|
if (rcIo->DetectCode(&Code, &Address)) {
|
|
Keys.code = Code;
|
|
Keys.address = Address;
|
|
WriteText(1, 5, tr("RC code detected!"));
|
|
WriteText(1, 6, tr("Do not press any key..."));
|
|
Flush();
|
|
rcIo->Flush(3000);
|
|
ClearEol(0, 5);
|
|
ClearEol(0, 6);
|
|
Flush();
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
WriteText(1, 3, tr("Phase 2: Learning specific key codes"));
|
|
tKey *k = Keys.keys;
|
|
while (k->type != kNone) {
|
|
char *Prompt;
|
|
asprintf(&Prompt, tr("Press key for '%s'"), tr(k->name));
|
|
WriteText(1, 5, Prompt);
|
|
delete Prompt;
|
|
for (;;) {
|
|
unsigned int ch = GetCh();
|
|
if (ch != 0) {
|
|
switch (Keys.Get(ch)) {
|
|
case kUp: if (k > Keys.keys) {
|
|
k--;
|
|
break;
|
|
}
|
|
case kDown: if (k > Keys.keys + 1) {
|
|
WriteText(1, 5, tr("Press 'Up' to confirm"));
|
|
WriteText(1, 6, tr("Press 'Down' to continue"));
|
|
ClearEol(0, 7);
|
|
ClearEol(0, 8);
|
|
for (;;) {
|
|
eKeys key = GetKey();
|
|
if (key == kUp) {
|
|
Clear();
|
|
return;
|
|
}
|
|
else if (key == kDown) {
|
|
ClearEol(0, 6);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case kNone: k->code = ch;
|
|
k++;
|
|
break;
|
|
default: break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (k > Keys.keys)
|
|
WriteText(1, 7, tr("(press 'Up' to go back)"));
|
|
else
|
|
ClearEol(0, 7);
|
|
if (k > Keys.keys + 1)
|
|
WriteText(1, 8, tr("(press 'Down' to end key definition)"));
|
|
else
|
|
ClearEol(0, 8);
|
|
}
|
|
}
|
|
|
|
void cInterface::LearnKeys(void)
|
|
{
|
|
isyslog(LOG_INFO, "learning keys");
|
|
Open();
|
|
for (;;) {
|
|
Clear();
|
|
QueryKeys();
|
|
Clear();
|
|
WriteText(1, 1, tr("Learning Remote Control Keys"));
|
|
WriteText(1, 3, tr("Phase 3: Saving key codes"));
|
|
WriteText(1, 5, tr("Press 'Up' to save, 'Down' to cancel"));
|
|
for (;;) {
|
|
eKeys key = GetKey();
|
|
if (key == kUp) {
|
|
Keys.Save();
|
|
Close();
|
|
return;
|
|
}
|
|
else if (key == kDown) {
|
|
Keys.Load();
|
|
Close();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void cInterface::DisplayChannelNumber(int Number)
|
|
{
|
|
rcIo->Number(Number);
|
|
}
|
|
|
|
void cInterface::DisplayRecording(int Index, bool On)
|
|
{
|
|
rcIo->SetPoints(1 << Index, On);
|
|
}
|
|
|
|
bool cInterface::Recording(void)
|
|
{
|
|
// This is located here because the Interface has to do with the "PrimaryDvbApi" anyway
|
|
return cDvbApi::PrimaryDvbApi->Recording();
|
|
}
|