mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
VDR developer version 2.1.5 is now available at ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.5.tar.bz2 A 'diff' against the previous version is available at ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.4-2.1.5.diff MD5 checksums: ce561eef64c13e24e4817f70a6d9d5b0 vdr-2.1.5.tar.bz2 f433e78d90bc414bd9d858ca6e58e539 vdr-2.1.4-2.1.5.diff WARNING: ======== This is a *developer* version. Even though *I* use it in my productive environment, I strongly recommend that you only use it under controlled conditions and for testing and debugging. From the HISTORY file: - Now checking whether the primary device actually has a decoder before retuning the current channel after a change in its parameters. This fixes broken recordings on the primary device on "headless" systems. - Increased MIN_TS_PACKETS_FOR_FRAME_DETECTOR to 100 and introduced counting the number of actual video TS packets in cTsPayload in order to be able to record channels that sometimes need even more than 10 TS packets for detecting frame borders (reported by Oliver Endriss). - Fixed sorting recordings by time in the Recordings menu if "Setup/OSD/Recording directories" is set to "no". - Fixed clearing non-editable members in the channel editor (thanks to Rolf Ahrenberg). - Updated the Estonian OSD texts (thanks to Arthur Konovalov). - Further clarified the semantics of cCamSlot::Decrypt(). - Fixed flickering if subtitles are active while the OSD demo is running. - Fixed numbering frames. Previously they were numbered starting from 1, while it is apparently standard to number them from 0. Any existing recordings with editing marks (which will now be off by one) can still be cut with all VDR versions from 1.7.32, because these will automatically adjust editing marks to I-frames. Users of stable releases shouldn't notice any problems. - Fixed a possible crash in the OSD demo (reported by Christopher Reimer). - Fixed some compiler warnings with Clang 3.4.1 (reported by Paul Menzel). - Added LinkageTypePremiere to libsi/si.h and eit.c to avoid a compiler warning with Clang 3.4.1 (suggested by Tony Houghten). - Replaced the NULL pointer assignment in ~cReceiver() to force a segfault with a call to abort() (suggested by Tony Houghten). - Fixed learning keyboard remote control codes (thanks to Lars Hanisch). - Improved PAT/PMT scanning to speed up initial tuning to encrypted channels on transponders with many PAT entries (reported by Mariusz Bialonczyk). - Fixed the replay progress display for very long recordings. - Fixed detecting broken video data streams when recording. - Fixed handling frame detection buffer length (reported by Eike Sauer).
437 lines
11 KiB
C
437 lines
11 KiB
C
/*
|
|
* remote.c: General Remote Control handling
|
|
*
|
|
* See the main source file 'vdr.c' for copyright information and
|
|
* how to reach the author.
|
|
*
|
|
* $Id: remote.c 3.2 2014/02/15 12:40:39 kls Exp $
|
|
*/
|
|
|
|
#include "remote.h"
|
|
#include <fcntl.h>
|
|
#define __STDC_FORMAT_MACROS // Required for format specifiers
|
|
#include <inttypes.h>
|
|
#include <netinet/in.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
#include "tools.h"
|
|
|
|
// --- cRemote ---------------------------------------------------------------
|
|
|
|
#define INITTIMEOUT 10000 // ms
|
|
#define REPEATTIMEOUT 1000 // ms
|
|
|
|
eKeys cRemote::keys[MaxKeys];
|
|
int cRemote::in = 0;
|
|
int cRemote::out = 0;
|
|
cTimeMs cRemote::repeatTimeout(-1);
|
|
cRemote *cRemote::learning = NULL;
|
|
char *cRemote::unknownCode = NULL;
|
|
cMutex cRemote::mutex;
|
|
cCondVar cRemote::keyPressed;
|
|
const char *cRemote::keyMacroPlugin = NULL;
|
|
const char *cRemote::callPlugin = NULL;
|
|
bool cRemote::enabled = true;
|
|
time_t cRemote::lastActivity = 0;
|
|
|
|
cRemote::cRemote(const char *Name)
|
|
{
|
|
name = Name ? strdup(Name) : NULL;
|
|
Remotes.Add(this);
|
|
}
|
|
|
|
cRemote::~cRemote()
|
|
{
|
|
Remotes.Del(this, false);
|
|
free(name);
|
|
}
|
|
|
|
const char *cRemote::GetSetup(void)
|
|
{
|
|
return Keys.GetSetup(Name());
|
|
}
|
|
|
|
void cRemote::PutSetup(const char *Setup)
|
|
{
|
|
Keys.PutSetup(Name(), Setup);
|
|
}
|
|
|
|
bool cRemote::Initialize(void)
|
|
{
|
|
if (Ready()) {
|
|
char *NewCode = NULL;
|
|
eKeys Key = Get(INITTIMEOUT, &NewCode);
|
|
if (Key != kNone || NewCode)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void cRemote::Clear(void)
|
|
{
|
|
cMutexLock MutexLock(&mutex);
|
|
in = out = 0;
|
|
if (learning) {
|
|
free(unknownCode);
|
|
unknownCode = NULL;
|
|
}
|
|
}
|
|
|
|
bool cRemote::Put(eKeys Key, bool AtFront)
|
|
{
|
|
if (Key != kNone) {
|
|
cMutexLock MutexLock(&mutex);
|
|
if (in != out && (keys[out] & k_Repeat) && (Key & k_Release))
|
|
Clear();
|
|
int d = out - in;
|
|
if (d <= 0)
|
|
d = MaxKeys + d;
|
|
if (d - 1 > 0) {
|
|
if (AtFront) {
|
|
if (--out < 0)
|
|
out = MaxKeys - 1;
|
|
keys[out] = Key;
|
|
}
|
|
else {
|
|
keys[in] = Key;
|
|
if (++in >= MaxKeys)
|
|
in = 0;
|
|
}
|
|
keyPressed.Broadcast();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return true; // only a real key shall report an overflow!
|
|
}
|
|
|
|
bool cRemote::PutMacro(eKeys Key)
|
|
{
|
|
const cKeyMacro *km = KeyMacros.Get(Key);
|
|
if (km) {
|
|
keyMacroPlugin = km->Plugin();
|
|
cMutexLock MutexLock(&mutex);
|
|
for (int i = km->NumKeys(); --i > 0; ) {
|
|
if (!Put(km->Macro()[i], true))
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool cRemote::Put(uint64_t Code, bool Repeat, bool Release)
|
|
{
|
|
char buffer[32];
|
|
snprintf(buffer, sizeof(buffer), "%016"PRIX64, Code);
|
|
return Put(buffer, Repeat, Release);
|
|
}
|
|
|
|
bool cRemote::Put(const char *Code, bool Repeat, bool Release)
|
|
{
|
|
if (learning && this != learning)
|
|
return false;
|
|
eKeys Key = Keys.Get(Name(), Code);
|
|
if (Key != kNone) {
|
|
if (Repeat)
|
|
Key = eKeys(Key | k_Repeat);
|
|
if (Release)
|
|
Key = eKeys(Key | k_Release);
|
|
return Put(Key);
|
|
}
|
|
if (learning) {
|
|
free(unknownCode);
|
|
unknownCode = strdup(Code);
|
|
keyPressed.Broadcast();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool cRemote::CallPlugin(const char *Plugin)
|
|
{
|
|
cMutexLock MutexLock(&mutex);
|
|
if (!callPlugin) {
|
|
callPlugin = Plugin;
|
|
Put(k_Plugin);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const char *cRemote::GetPlugin(void)
|
|
{
|
|
cMutexLock MutexLock(&mutex);
|
|
const char *p = keyMacroPlugin;
|
|
if (p)
|
|
keyMacroPlugin = NULL;
|
|
else {
|
|
p = callPlugin;
|
|
callPlugin = NULL;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
bool cRemote::HasKeys(void)
|
|
{
|
|
cMutexLock MutexLock(&mutex);
|
|
return in != out && !(keys[out] & k_Repeat);
|
|
}
|
|
|
|
eKeys cRemote::Get(int WaitMs, char **UnknownCode)
|
|
{
|
|
for (;;) {
|
|
cMutexLock MutexLock(&mutex);
|
|
if (in != out) {
|
|
eKeys k = keys[out];
|
|
if (++out >= MaxKeys)
|
|
out = 0;
|
|
if ((k & k_Repeat) != 0)
|
|
repeatTimeout.Set(REPEATTIMEOUT);
|
|
TriggerLastActivity();
|
|
return enabled ? k : kNone;
|
|
}
|
|
else if (!WaitMs || !keyPressed.TimedWait(mutex, WaitMs) && repeatTimeout.TimedOut())
|
|
return kNone;
|
|
else if (learning && UnknownCode && unknownCode) {
|
|
*UnknownCode = unknownCode;
|
|
unknownCode = NULL;
|
|
return kNone;
|
|
}
|
|
}
|
|
}
|
|
|
|
void cRemote::TriggerLastActivity(void)
|
|
{
|
|
lastActivity = time(NULL);
|
|
}
|
|
|
|
// --- cRemotes --------------------------------------------------------------
|
|
|
|
cRemotes Remotes;
|
|
|
|
// --- cKbdRemote ------------------------------------------------------------
|
|
|
|
struct tKbdMap {
|
|
eKbdFunc func;
|
|
uint64_t code;
|
|
};
|
|
|
|
static tKbdMap KbdMap[] = {
|
|
{ kfF1, 0x0000001B5B31317EULL },
|
|
{ kfF2, 0x0000001B5B31327EULL },
|
|
{ kfF3, 0x0000001B5B31337EULL },
|
|
{ kfF4, 0x0000001B5B31347EULL },
|
|
{ kfF5, 0x0000001B5B31357EULL },
|
|
{ kfF6, 0x0000001B5B31377EULL },
|
|
{ kfF7, 0x0000001B5B31387EULL },
|
|
{ kfF8, 0x0000001B5B31397EULL },
|
|
{ kfF9, 0x0000001B5B32307EULL },
|
|
{ kfF10, 0x0000001B5B32317EULL },
|
|
{ kfF11, 0x0000001B5B32327EULL },
|
|
{ kfF12, 0x0000001B5B32337EULL },
|
|
{ kfUp, 0x00000000001B5B41ULL },
|
|
{ kfDown, 0x00000000001B5B42ULL },
|
|
{ kfLeft, 0x00000000001B5B44ULL },
|
|
{ kfRight, 0x00000000001B5B43ULL },
|
|
{ kfHome, 0x00000000001B5B48ULL },
|
|
{ kfEnd, 0x00000000001B5B46ULL },
|
|
{ kfPgUp, 0x000000001B5B357EULL },
|
|
{ kfPgDown, 0x000000001B5B367EULL },
|
|
{ kfIns, 0x000000001B5B327EULL },
|
|
{ kfDel, 0x000000001B5B337EULL },
|
|
{ kfNone, 0x0000000000000000ULL }
|
|
};
|
|
|
|
bool cKbdRemote::kbdAvailable = false;
|
|
bool cKbdRemote::rawMode = false;
|
|
|
|
cKbdRemote::cKbdRemote(void)
|
|
:cRemote("KBD")
|
|
,cThread("KBD remote control")
|
|
{
|
|
tcgetattr(STDIN_FILENO, &savedTm);
|
|
struct termios tm;
|
|
if (tcgetattr(STDIN_FILENO, &tm) == 0) {
|
|
tm.c_iflag = 0;
|
|
tm.c_lflag &= ~(ICANON | ECHO);
|
|
tm.c_cc[VMIN] = 0;
|
|
tm.c_cc[VTIME] = 0;
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &tm);
|
|
}
|
|
kbdAvailable = true;
|
|
systemIsUtf8 = !cCharSetConv::SystemCharacterTable() || strcmp(cCharSetConv::SystemCharacterTable(), "UTF-8") == 0;
|
|
Start();
|
|
}
|
|
|
|
cKbdRemote::~cKbdRemote()
|
|
{
|
|
kbdAvailable = false;
|
|
Cancel(3);
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &savedTm);
|
|
}
|
|
|
|
void cKbdRemote::SetRawMode(bool RawMode)
|
|
{
|
|
rawMode = RawMode;
|
|
}
|
|
|
|
uint64_t cKbdRemote::MapFuncToCode(int Func)
|
|
{
|
|
for (tKbdMap *p = KbdMap; p->func != kfNone; p++) {
|
|
if (p->func == Func)
|
|
return p->code;
|
|
}
|
|
return (Func <= 0xFF) ? Func : 0;
|
|
}
|
|
|
|
int cKbdRemote::MapCodeToFunc(uint64_t Code)
|
|
{
|
|
for (tKbdMap *p = KbdMap; p->func != kfNone; p++) {
|
|
if (p->code == Code)
|
|
return p->func;
|
|
}
|
|
if (Code <= 0xFF)
|
|
return Code;
|
|
return kfNone;
|
|
}
|
|
|
|
void cKbdRemote::PutKey(uint64_t Code, bool Repeat, bool Release)
|
|
{
|
|
if (rawMode || (!Put(Code, Repeat, Release) && !IsLearning())) {
|
|
if (int func = MapCodeToFunc(Code))
|
|
Put(KBDKEY(func), Repeat, Release);
|
|
}
|
|
}
|
|
|
|
int cKbdRemote::ReadKey(void)
|
|
{
|
|
cPoller Poller(STDIN_FILENO);
|
|
if (Poller.Poll(50)) {
|
|
uchar ch = 0;
|
|
int r = safe_read(STDIN_FILENO, &ch, 1);
|
|
if (r == 1)
|
|
return ch;
|
|
if (r < 0)
|
|
LOG_ERROR_STR("cKbdRemote");
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
uint64_t cKbdRemote::ReadKeySequence(void)
|
|
{
|
|
uint64_t k = 0;
|
|
int key1;
|
|
|
|
if ((key1 = ReadKey()) >= 0) {
|
|
k = key1;
|
|
if (systemIsUtf8 && (key1 & 0xC0) == 0xC0) {
|
|
char bytes[4] = { 0 };
|
|
bytes[0] = key1;
|
|
int bytescount = 1;
|
|
if ((key1 & 0xF0) == 0xF0)
|
|
bytescount = 3;
|
|
else if ((key1 & 0xE0) == 0xE0)
|
|
bytescount = 2;
|
|
for (int i = 0; i < bytescount; i++) {
|
|
if ((key1 = ReadKey()) >= 0)
|
|
bytes[i + 1] = key1;
|
|
}
|
|
k = Utf8CharGet(bytes);
|
|
if (k > 0xFF)
|
|
k = 0;
|
|
}
|
|
else if (key1 == 0x1B) {
|
|
// Start of escape sequence
|
|
if ((key1 = ReadKey()) >= 0) {
|
|
k <<= 8;
|
|
k |= key1 & 0xFF;
|
|
switch (key1) {
|
|
case 0x4F: // 3-byte sequence
|
|
if ((key1 = ReadKey()) >= 0) {
|
|
k <<= 8;
|
|
k |= key1 & 0xFF;
|
|
}
|
|
break;
|
|
case 0x5B: // 3- or more-byte sequence
|
|
if ((key1 = ReadKey()) >= 0) {
|
|
k <<= 8;
|
|
k |= key1 & 0xFF;
|
|
switch (key1) {
|
|
case 0x31 ... 0x3F: // more-byte sequence
|
|
case 0x5B: // strange, may apparently occur
|
|
do {
|
|
if ((key1 = ReadKey()) < 0)
|
|
break; // Sequence ends here
|
|
k <<= 8;
|
|
k |= key1 & 0xFF;
|
|
} while (key1 != 0x7E);
|
|
break;
|
|
default: ;
|
|
}
|
|
}
|
|
break;
|
|
default: ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return k;
|
|
}
|
|
|
|
void cKbdRemote::Action(void)
|
|
{
|
|
cTimeMs FirstTime;
|
|
cTimeMs LastTime;
|
|
uint64_t FirstCommand = 0;
|
|
uint64_t LastCommand = 0;
|
|
bool Delayed = false;
|
|
bool Repeat = false;
|
|
|
|
while (Running()) {
|
|
uint64_t Command = ReadKeySequence();
|
|
if (Command) {
|
|
if (Command == LastCommand) {
|
|
// If two keyboard events with the same command come in without an intermediate
|
|
// timeout, this is a long key press that caused the repeat function to kick in:
|
|
Delayed = false;
|
|
FirstCommand = 0;
|
|
if (FirstTime.Elapsed() < (uint)Setup.RcRepeatDelay)
|
|
continue; // repeat function kicks in after a short delay
|
|
if (LastTime.Elapsed() < (uint)Setup.RcRepeatDelta)
|
|
continue; // skip same keys coming in too fast
|
|
PutKey(Command, true);
|
|
Repeat = true;
|
|
LastTime.Set();
|
|
}
|
|
else if (Command == FirstCommand) {
|
|
// If the same command comes in twice with an intermediate timeout, we
|
|
// need to delay the second command to see whether it is going to be
|
|
// a repeat function or a separate key press:
|
|
Delayed = true;
|
|
}
|
|
else {
|
|
// This is a totally new key press, so we accept it immediately:
|
|
PutKey(Command);
|
|
Delayed = false;
|
|
FirstCommand = Command;
|
|
FirstTime.Set();
|
|
}
|
|
}
|
|
else if (Repeat) {
|
|
// Timeout after a repeat function, so we generate a 'release':
|
|
PutKey(LastCommand, false, true);
|
|
Repeat = false;
|
|
}
|
|
else if (Delayed && FirstCommand) {
|
|
// Timeout after two normal key presses of the same key, so accept the
|
|
// delayed key:
|
|
PutKey(FirstCommand);
|
|
Delayed = false;
|
|
FirstCommand = 0;
|
|
FirstTime.Set();
|
|
}
|
|
LastCommand = Command;
|
|
}
|
|
}
|