mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
VDR developer version 2.1.8 is now available at ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.8.tar.bz2 A 'diff' against the previous version is available at ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.7-2.1.8.diff MD5 checksums: 1d2751e87def9b18b448513f24e635e9 vdr-2.1.8.tar.bz2 0487e037278f6f6684a7933674910f05 vdr-2.1.7-2.1.8.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: - Updated the Italian OSD texts (thanks to Diego Pierotto). - Fixed "warning: invalid suffix on literal" with GCC 4.8 and C++11 (thanks to Joerg Bornkessel). - Fixed the link to "svdrpsend (1)" in the vdr.1 man page (thanks to Chris Mayo). - Updated the Finnish OSD texts (thanks to Rolf Ahrenberg). - Updated the Romanian OSD texts (thanks to Lucian Muresan). - Added functionality based on the "jumpplay" patch from Torsten Kunkel and Thomas Günther: + The new option "Setup/Replay/Pause replay when jumping to a mark" can be used to turn off pausing replay when jumping to an editing mark with the '9' key. + The new option "Setup/Replay/Skip edited parts" can be used to automatically skip the edited parts of a recording during replay, without the need to actually cut the recording. + The new option "Setup/Replay/Pause replay at last mark" can be used to make replay go into Pause mode when it has reached the last "end" mark. + The '8' key for testing an edited sequence now also jumps to the next *end* mark if "Setup/Replay/Skip edited parts" is active. This allows for testing edits in recordings that have actually been cut, as well as recordings that have not been cut, in case "Skip edited parts" is enabled. - Added support for "Satellite Channel Routing" (SCR) according to EN50607, also known as "JESS" (thanks to Manfred Völkel and Frank Neumann). - The keys '1' and '3' can now be used in replay mode to position an editing mark in "binary" mode (based on a patch from Rolf Ahrenberg, with modifications by Helmut Auer). See MANUAL, section "Editing a Recording". - The Yellow button in the "Setup/CAM" menu can now be used to put the selected CAM into a mode where it remains assigned to a device that is tuned to the current channel until the smart card it contains is activated and the CAM thus starts to descramble (see MANUAL, section "Setup/CAM" for details). - Updated the Estonian OSD texts (thanks to Arthur Konovalov). - Added ARGSDIR to the ONEDIR section of Make.config.template (suggested by Derek Kelly). - Made cRecording::GetResume() public (suggested by Stefan Braun). - Fixed setting the read index in cDvbPlayer::Goto() in case Still is false. - The function cDvbPlayer::Goto() now automatically calls Play() if Still is false. - Added support for LCN (Logical Channel Numbers), which plugins may use to sort channels (thanks to Rolf Ahrenberg).
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.3 2015/01/20 14:53:57 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;
|
|
}
|
|
}
|