vdr 2.8.2
remote.c
Go to the documentation of this file.
1/*
2 * remote.c: General Remote Control handling
3 *
4 * See the main source file 'vdr.c' for copyright information and
5 * how to reach the author.
6 *
7 * $Id: remote.c 5.2 2026/05/24 20:04:42 kls Exp $
8 */
9
10#include "remote.h"
11#include <fcntl.h>
12#define __STDC_FORMAT_MACROS // Required for format specifiers
13#include <inttypes.h>
14#include <netinet/in.h>
15#include <string.h>
16#include <sys/types.h>
17#include <sys/time.h>
18#include <unistd.h>
19#include "tools.h"
20
21// --- cRemote ---------------------------------------------------------------
22
23#define INITTIMEOUT 10000 // ms
24#define REPEATTIMEOUT 1000 // ms
25
26eKeys cRemote::keys[MaxKeys];
27int cRemote::in = 0;
28int cRemote::out = 0;
31char *cRemote::unknownCode = NULL;
34const char *cRemote::keyMacroPlugin = NULL;
35const char *cRemote::callPlugin = NULL;
36bool cRemote::enabled = true;
37bool cRemote::inEditMode = false;
38time_t cRemote::lastActivity = 0;
39
41{
42 name = Name ? strdup(Name) : NULL;
43 Remotes.Add(this);
44}
45
47{
48 Remotes.Del(this, false);
49 free(name);
50}
51
52const char *cRemote::GetSetup(void)
53{
54 return Keys.GetSetup(Name());
55}
56
57void cRemote::PutSetup(const char *Setup)
58{
59 Keys.PutSetup(Name(), Setup);
60}
61
63{
64 if (Ready()) {
65 char *NewCode = NULL;
66 eKeys Key = Get(INITTIMEOUT, &NewCode);
67 if (Key != kNone || NewCode)
68 return true;
69 }
70 return false;
71}
72
74{
75 cMutexLock MutexLock(&mutex);
76 in = out = 0;
77 if (learning) {
78 free(unknownCode);
79 unknownCode = NULL;
80 }
81}
82
83bool cRemote::Put(eKeys Key, bool AtFront)
84{
85 if (Key != kNone) {
86 cMutexLock MutexLock(&mutex);
87 // In case of a release remove repeat from queue:
88 if (in != out && (Key & k_Release) && (keys[((in > 0) ? in : MaxKeys) - 1] | k_Release) == (Key | k_Repeat)) {
89 if (--in < 0)
90 in = MaxKeys - 1;
91 }
92 int d = out - in;
93 if (d <= 0)
94 d = MaxKeys + d;
95 if (d - 1 > 0) {
96 if (AtFront) {
97 if (--out < 0)
98 out = MaxKeys - 1;
99 keys[out] = Key;
100 }
101 else {
102 if (in != out && (Key & k_Repeat) && (keys[((in > 0) ? in : MaxKeys) - 1] | k_Repeat) == Key)
103 return true; // queue only one repeat!
104 keys[in] = Key;
105 if (++in >= MaxKeys)
106 in = 0;
107 }
108 keyPressed.Broadcast();
109 return true;
110 }
111 return false;
112 }
113 return true; // only a real key shall report an overflow!
114}
115
117{
118 const cKeyMacro *km = KeyMacros.Get(Key);
119 if (km) {
120 keyMacroPlugin = km->Plugin();
121 cMutexLock MutexLock(&mutex);
122 for (int i = km->NumKeys(); --i > 0; ) {
123 if (!Put(km->Macro()[i], true))
124 return false;
125 }
126 }
127 return true;
128}
129
130bool cRemote::Put(uint64_t Code, bool Repeat, bool Release)
131{
132 char buffer[32];
133 snprintf(buffer, sizeof(buffer), "%016" PRIX64, Code);
134 return Put(buffer, Repeat, Release);
135}
136
137bool cRemote::Put(const char *Code, bool Repeat, bool Release)
138{
139 if (learning && this != learning)
140 return false;
141 eKeys Key = Keys.Get(Name(), Code);
142 if (Key != kNone) {
143 if (Repeat)
144 Key = eKeys(Key | k_Repeat);
145 if (Release)
146 Key = eKeys(Key | k_Release);
147 return Put(Key);
148 }
149 if (learning) {
150 free(unknownCode);
151 unknownCode = strdup(Code);
152 keyPressed.Broadcast();
153 }
154 return false;
155}
156
157bool cRemote::CallPlugin(const char *Plugin)
158{
159 cMutexLock MutexLock(&mutex);
160 if (!callPlugin) {
161 callPlugin = Plugin;
162 Put(k_Plugin);
163 return true;
164 }
165 return false;
166}
167
168const char *cRemote::GetPlugin(void)
169{
170 cMutexLock MutexLock(&mutex);
171 const char *p = keyMacroPlugin;
172 if (p)
173 keyMacroPlugin = NULL;
174 else {
175 p = callPlugin;
176 callPlugin = NULL;
177 }
178 return p;
179}
180
182{
183 cMutexLock MutexLock(&mutex);
184 return in != out && !(keys[out] & k_Repeat);
185}
186
187eKeys cRemote::Get(int WaitMs, char **UnknownCode)
188{
189 for (;;) {
190 cMutexLock MutexLock(&mutex);
191 if (in != out) {
192 eKeys k = keys[out];
193 if (++out >= MaxKeys)
194 out = 0;
195 if ((k & k_Repeat) != 0)
198 return enabled ? k : kNone;
199 }
200 else if (!WaitMs || !keyPressed.TimedWait(mutex, WaitMs) && repeatTimeout.TimedOut())
201 return kNone;
202 else if (learning && UnknownCode && unknownCode) {
203 *UnknownCode = unknownCode;
204 unknownCode = NULL;
205 return kNone;
206 }
207 }
208}
209
211{
212 lastActivity = time(NULL);
213}
214
215// --- cRemotes --------------------------------------------------------------
216
218
219// --- cKbdRemote ------------------------------------------------------------
220
221struct tKbdMap {
223 uint64_t code;
224 };
225
226static tKbdMap KbdMap[] = {
227 { kfF1, 0x0000001B5B31317EULL },
228 { kfF2, 0x0000001B5B31327EULL },
229 { kfF3, 0x0000001B5B31337EULL },
230 { kfF4, 0x0000001B5B31347EULL },
231 { kfF5, 0x0000001B5B31357EULL },
232 { kfF6, 0x0000001B5B31377EULL },
233 { kfF7, 0x0000001B5B31387EULL },
234 { kfF8, 0x0000001B5B31397EULL },
235 { kfF9, 0x0000001B5B32307EULL },
236 { kfF10, 0x0000001B5B32317EULL },
237 { kfF11, 0x0000001B5B32327EULL },
238 { kfF12, 0x0000001B5B32337EULL },
239 { kfUp, 0x00000000001B5B41ULL },
240 { kfDown, 0x00000000001B5B42ULL },
241 { kfLeft, 0x00000000001B5B44ULL },
242 { kfRight, 0x00000000001B5B43ULL },
243 { kfHome, 0x00000000001B5B48ULL },
244 { kfEnd, 0x00000000001B5B46ULL },
245 { kfPgUp, 0x000000001B5B357EULL },
246 { kfPgDown, 0x000000001B5B367EULL },
247 { kfIns, 0x000000001B5B327EULL },
248 { kfDel, 0x000000001B5B337EULL },
249 { kfNone, 0x0000000000000000ULL }
250 };
251
252bool cKbdRemote::kbdAvailable = false;
253bool cKbdRemote::rawMode = false;
254
256:cRemote("KBD")
257,cThread("KBD remote control")
258{
259 tcgetattr(STDIN_FILENO, &savedTm);
260 struct termios tm;
261 if (tcgetattr(STDIN_FILENO, &tm) == 0) {
262 tm.c_iflag = 0;
263 tm.c_lflag &= ~(ICANON | ECHO);
264 tm.c_cc[VMIN] = 0;
265 tm.c_cc[VTIME] = 0;
266 tcsetattr(STDIN_FILENO, TCSANOW, &tm);
267 }
268 kbdAvailable = true;
270 Start();
271}
272
274{
275 kbdAvailable = false;
276 Cancel(3);
277 tcsetattr(STDIN_FILENO, TCSANOW, &savedTm);
278}
279
280void cKbdRemote::SetRawMode(bool RawMode)
281{
282 rawMode = RawMode;
283}
284
286{
287 for (tKbdMap *p = KbdMap; p->func != kfNone; p++) {
288 if (p->func == Func)
289 return p->code;
290 }
291 return (Func <= 0xFF) ? Func : 0;
292}
293
295{
296 for (tKbdMap *p = KbdMap; p->func != kfNone; p++) {
297 if (p->code == Code)
298 return p->func;
299 }
300 if (Code <= 0xFF)
301 return Code;
302 return kfNone;
303}
304
305void cKbdRemote::PutKey(uint64_t Code, bool Repeat, bool Release)
306{
307 if (rawMode || (!Put(Code, Repeat, Release) && !IsLearning())) {
308 if (int func = MapCodeToFunc(Code))
309 Put(KBDKEY(func), Repeat, Release);
310 }
311}
312
314{
315 cPoller Poller(STDIN_FILENO);
316 if (Poller.Poll(Setup.RcRepeatDelta * 3 / 2)) {
317 uchar ch = 0;
318 int r = safe_read(STDIN_FILENO, &ch, 1);
319 if (r == 1)
320 return ch;
321 if (r < 0)
322 LOG_ERROR_STR("cKbdRemote");
323 }
324 return -1;
325}
326
328{
329 uint64_t k = 0;
330 int key1;
331
332 if ((key1 = ReadKey()) >= 0) {
333 k = key1;
334 if (systemIsUtf8 && (key1 & 0xC0) == 0xC0) {
335 char bytes[4] = { 0 };
336 bytes[0] = key1;
337 int bytescount = 1;
338 if ((key1 & 0xF0) == 0xF0)
339 bytescount = 3;
340 else if ((key1 & 0xE0) == 0xE0)
341 bytescount = 2;
342 for (int i = 0; i < bytescount; i++) {
343 if ((key1 = ReadKey()) >= 0)
344 bytes[i + 1] = key1;
345 }
346 k = Utf8CharGet(bytes);
347 if (k > 0xFF)
348 k = 0;
349 }
350 else if (key1 == 0x1B) {
351 // Start of escape sequence
352 if ((key1 = ReadKey()) >= 0) {
353 k <<= 8;
354 k |= key1 & 0xFF;
355 switch (key1) {
356 case 0x4F: // 3-byte sequence
357 if ((key1 = ReadKey()) >= 0) {
358 k <<= 8;
359 k |= key1 & 0xFF;
360 }
361 break;
362 case 0x5B: // 3- or more-byte sequence
363 if ((key1 = ReadKey()) >= 0) {
364 k <<= 8;
365 k |= key1 & 0xFF;
366 switch (key1) {
367 case 0x31 ... 0x3F: // more-byte sequence
368 case 0x5B: // strange, may apparently occur
369 do {
370 if ((key1 = ReadKey()) < 0)
371 break; // Sequence ends here
372 k <<= 8;
373 k |= key1 & 0xFF;
374 } while (key1 != 0x7E);
375 break;
376 default: ;
377 }
378 }
379 break;
380 default: ;
381 }
382 }
383 }
384 }
385 return k;
386}
387
389{
390 cTimeMs FirstTime;
391 cTimeMs LastTime;
392 uint64_t FirstCommand = 0;
393 uint64_t LastCommand = 0;
394 bool Delayed = false;
395 bool Repeat = false;
396
397 while (Running()) {
398 uint64_t Command = ReadKeySequence();
399 if (Command) {
400 if (Command == LastCommand) {
401 // If two keyboard events with the same command come in without an intermediate
402 // timeout, this is a long key press that caused the repeat function to kick in:
403 Delayed = false;
404 FirstCommand = 0;
405 if (FirstTime.Elapsed() < (uint)Setup.RcRepeatDelay)
406 continue; // repeat function kicks in after a short delay
407 if (LastTime.Elapsed() < (uint)Setup.RcRepeatDelta)
408 continue; // skip same keys coming in too fast
409 PutKey(Command, true);
410 Repeat = true;
411 LastTime.Set();
412 }
413 else if (Command == FirstCommand) {
414 // If the same command comes in twice with an intermediate timeout, we
415 // need to delay the second command to see whether it is going to be
416 // a repeat function or a separate key press:
417 Delayed = true;
418 }
419 else {
420 // This is a totally new key press, so we accept it immediately:
421 PutKey(Command);
422 Delayed = false;
423 FirstCommand = Command;
424 FirstTime.Set();
425 }
426 }
427 else if (Repeat) {
428 // Timeout after a repeat function, so we generate a 'release':
429 PutKey(LastCommand, false, true);
430 Repeat = false;
431 }
432 else if (Delayed && FirstCommand) {
433 // Timeout after two normal key presses of the same key, so accept the
434 // delayed key:
435 PutKey(FirstCommand);
436 Delayed = false;
437 FirstCommand = 0;
438 FirstTime.Set();
439 }
440 else if (FirstCommand && FirstTime.Elapsed() > (uint)Setup.RcRepeatDelay) {
441 // Don't wait too long for that second key press:
442 Delayed = false;
443 FirstCommand = 0;
444 }
445 LastCommand = Command;
446 }
447}
static const char * SystemCharacterTable(void)
Definition tools.h:174
static bool rawMode
Definition remote.h:111
uint64_t ReadKeySequence(void)
Definition remote.c:327
int ReadKey(void)
Definition remote.c:313
int MapCodeToFunc(uint64_t Code)
Definition remote.c:294
cKbdRemote(void)
Definition remote.c:255
void PutKey(uint64_t Code, bool Repeat=false, bool Release=false)
Definition remote.c:305
bool systemIsUtf8
Definition remote.h:112
virtual void Action(void) override
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition remote.c:388
static uint64_t MapFuncToCode(int Func)
Definition remote.c:285
struct termios savedTm
Definition remote.h:113
virtual ~cKbdRemote() override
Definition remote.c:273
static void SetRawMode(bool RawMode)
Definition remote.c:280
static bool kbdAvailable
Definition remote.h:110
const eKeys * Macro(void) const
Definition keys.h:135
int NumKeys(void) const
Returns the number of keys in this macro.
Definition keys.h:131
const char * Plugin(void) const
Definition keys.h:136
bool Poll(int TimeoutMs=0)
Definition tools.c:1606
static const char * GetPlugin(void)
Returns the name of the plugin that was set with a previous call to PutMacro() or CallPlugin().
Definition remote.c:168
static cRemote * learning
Definition remote.h:27
static const char * keyMacroPlugin
Definition remote.h:32
static time_t lastActivity
Definition remote.h:31
const char * Name(void)
Definition remote.h:47
char * name
Definition remote.h:36
static eKeys Get(int WaitMs=1000, char **UnknownCode=NULL)
Definition remote.c:187
bool Put(uint64_t Code, bool Repeat=false, bool Release=false)
Definition remote.c:130
static void Clear(void)
Definition remote.c:73
static cCondVar keyPressed
Definition remote.h:30
static char * unknownCode
Definition remote.h:28
static bool PutMacro(eKeys Key)
Definition remote.c:116
static cTimeMs repeatTimeout
Definition remote.h:26
static bool HasKeys(void)
Definition remote.c:181
static void TriggerLastActivity(void)
Simulates user activity, for instance to keep the current menu open even if no remote control key has...
Definition remote.c:210
const char * GetSetup(void)
Definition remote.c:52
static bool CallPlugin(const char *Plugin)
Initiates calling the given plugin's main menu function.
Definition remote.c:157
cRemote(const char *Name)
Definition remote.c:40
static int in
Definition remote.h:24
static int out
Definition remote.h:25
static const char * callPlugin
Definition remote.h:33
virtual bool Ready(void)
Definition remote.h:45
static eKeys keys[MaxKeys]
Definition remote.h:23
void PutSetup(const char *Setup)
Definition remote.c:57
virtual bool Initialize(void)
Definition remote.c:62
virtual ~cRemote() override
Definition remote.c:46
static bool enabled
Definition remote.h:34
static bool IsLearning(void)
Definition remote.h:49
static bool inEditMode
Definition remote.h:35
@ MaxKeys
Definition remote.h:22
static cMutex mutex
Definition remote.h:29
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
Definition thread.c:320
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
Definition thread.h:102
cThread(const char *Description=NULL, bool LowPriority=false)
Creates a new thread.
Definition thread.c:254
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
Definition thread.c:370
uint64_t Elapsed(void) const
Returns the number of milliseconds that have elapsed since the last call to Set().
Definition tools.c:829
void Set(int Ms=0)
Sets the timer.
Definition tools.c:816
cSetup Setup
Definition config.c:372
cKeyMacros KeyMacros
Definition keys.c:267
cKeys Keys
Definition keys.c:156
#define KBDKEY(k)
Definition keys.h:84
eKeys
Definition keys.h:16
@ kNone
Definition keys.h:55
@ k_Release
Definition keys.h:62
@ k_Plugin
Definition keys.h:58
@ k_Repeat
Definition keys.h:61
cRemotes Remotes
Definition remote.c:217
static tKbdMap KbdMap[]
Definition remote.c:226
#define REPEATTIMEOUT
Definition remote.c:24
#define INITTIMEOUT
Definition remote.c:23
cRemotes Remotes
Definition remote.c:217
eKbdFunc
Definition remote.h:82
@ kfNone
Definition remote.h:83
@ kfF10
Definition remote.h:93
@ kfF11
Definition remote.h:94
@ kfF7
Definition remote.h:90
@ kfUp
Definition remote.h:96
@ kfF9
Definition remote.h:92
@ kfDown
Definition remote.h:97
@ kfF3
Definition remote.h:86
@ kfRight
Definition remote.h:99
@ kfF2
Definition remote.h:85
@ kfF4
Definition remote.h:87
@ kfPgUp
Definition remote.h:102
@ kfF8
Definition remote.h:91
@ kfLeft
Definition remote.h:98
@ kfF6
Definition remote.h:89
@ kfIns
Definition remote.h:104
@ kfDel
Definition remote.h:105
@ kfF12
Definition remote.h:95
@ kfEnd
Definition remote.h:101
@ kfF1
Definition remote.h:84
@ kfF5
Definition remote.h:88
@ kfHome
Definition remote.h:100
@ kfPgDown
Definition remote.h:103
uint64_t code
Definition remote.c:223
eKbdFunc func
Definition remote.c:222
ssize_t safe_read(int filedes, void *buffer, size_t size)
Definition tools.c:57
uint Utf8CharGet(const char *s, int Length)
Returns the UTF-8 symbol at the beginning of the given string.
Definition tools.c:862
#define LOG_ERROR_STR(s)
Definition tools.h:40
unsigned char uchar
Definition tools.h:31