UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
keyboard_control.cpp
Go to the documentation of this file.
1
7/*
8 * Copyright (c) 2015-2024 CESNET
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, is permitted provided that the following conditions
13 * are met:
14 *
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 *
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 *
22 * 3. Neither the name of CESNET nor the names of its contributors may be
23 * used to endorse or promote products derived from this software without
24 * specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
27 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
28 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
29 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
30 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
31 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
32 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
36 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
37 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 */
50#ifdef HAVE_CONFIG_H
51#include "config.h" // for HAVE_TERMIOS_H
52#endif // HAVE_CONFIG_H
53
54#include <cassert> // for assert
55#include <cctype> // for isdigit, islower, isprint
56#include <chrono> // for milliseconds
57#include <climits>
58#include <cmath>
59#include <condition_variable>
60#include <csignal> // for sigaction, sigemptyset, sa_handle
61#include <cstdio> // for printf, sscanf, EOF, fgets, perror
62#include <cstdlib> // for free, atexit, strtoll
63#include <cstring> // for strlen, strchr, strcpy, strstr, memset
64#include <ctime> // for strftime, time, time_t, tm
65#include <iomanip>
66#include <iostream>
67#include <map>
68#include <mutex>
69#include <queue>
70#include <set>
71#include <sstream>
72#include <string>
73#include <thread>
74#include <utility>
75
76#ifdef _WIN32
77#include <winsock2.h> // for FD_ISSET, FD_SET, select, FD_ZERO, fd_set
78#else
79#include <sys/select.h> // for FD_ISSET, FD_SET, select, FD_ZERO, fd_set
80#endif
81
82#include "compat/time.h" // for gmtime_s, localtime_s
83#include "debug.h"
84#include "host.h"
85#include "keyboard_control.h"
86#include "messaging.h"
87#include "module.h"
88#include "types.h" // for video_desc
89#include "utils/color_out.h"
90#include "utils/thread.h"
91#include "video.h"
92
93#ifdef HAVE_TERMIOS_H
94#include <termios.h>
95#include <unistd.h>
96#else
97#include <conio.h>
98#endif
99
100static bool set_tio();
101
102#define CONFIG_FILE "ug-key-map.txt"
103#define MOD_NAME "[key control] "
104
105using std::chrono::milliseconds; // _WIN32 only
106using std::cout;
107using std::dec; // _WIN32 only
108using std::endl;
109using std::hex;
110using std::istringstream;
111using std::lock_guard;
112using std::make_pair;
113using std::mutex;
114using std::setw;
115using std::setfill; // _WIN32 only
116using std::string;
117using std::stringstream;
118using std::thread;
119using std::unique_lock;
120
121#ifdef HAVE_TERMIOS_H
122static bool signal_catched = false;
123
124static void catch_signal(int)
125{
126 signal_catched = true;
127}
128#endif
129
130#ifdef HAVE_TERMIOS_H
131static struct termios old_tio;
132#endif
133
135public:
136 explicit impl(struct module *parent);
137 ~impl();
138 void start();
139 void stop();
140
141 void msg_received();
142 static void msg_received_func(struct module *);
143
144private:
145 void read_command(bool multiple);
146 void execute_command(const char *command);
147 bool exec_local_command(const char *command);
148 void exec_external_commands(const char *commands);
149
150 void load_config_map();
151
152 int64_t get_next_key();
153 void info();
154 void run();
155 void signal();
156 void usage();
157
158 struct module m_mod{};
159
160 std::thread m_keyboard_thread;
161 struct module *m_root {nullptr};
163#ifdef HAVE_TERMIOS_H
164 int m_event_pipe[2] = {0, 0};
165#else
166 std::condition_variable m_cv;
167#endif
168 bool m_should_exit {false};
169 std::queue<int64_t> m_pressed_keys;
170 bool m_started {false};
172 std::time_t m_start_time;
173
174 std::set<int64_t> guarded_keys { '*', '/', '9', '0', 'c', 'C', 'm', 'M', '>', '<', 'e', 's', 'S', 'v', 'V', 'r' };
175 std::map<int64_t, std::pair<std::string, std::string> > key_mapping; // user defined - key, command, name
176 std::mutex m_lock;
177};
178
179keyboard_control::keyboard_control(struct module *parent) : m_impl{std::make_unique<impl>(parent)} {}
185 m_impl->stop();
186}
187
189{
190#ifdef HAVE_TERMIOS_H
191 struct sigaction sa, sa_old;
192 memset(&sa, 0, sizeof sa);
193 sa.sa_handler = SIG_IGN;
194 sigemptyset(&sa.sa_mask);
195 sigaction(SIGTTOU, &sa, &sa_old);
196 /* set the new settings immediately */
197 /* restore the former settings */
198 tcsetattr(STDIN_FILENO,TCSANOW,&old_tio);
199 sigaction(SIGTTOU, &sa_old, NULL);
200#endif
201}
202
204 m_parent(parent)
205{
206 m_start_time = time(NULL);
207
209
210#ifdef HAVE_TERMIOS_H
211 /* get the terminal settings for stdin */
212 /* we want to keep the old setting to restore them a the end */
213 tcgetattr(STDIN_FILENO,&old_tio);
214#endif
215
218 m_mod.priv_data = this;
221}
222
224 stop(); // in case that it was not called explicitly
225}
226
227ADD_TO_PARAM("disable-keyboard-control", "* disable-keyboard-control[=no]\n"
228 " disables keyboard control (usable mainly for non-interactive runs)\n");
230{
231 bool force_key_control = false;
232 if (get_commandline_param("disable-keyboard-control")) {
233 if (strcmp(get_commandline_param("disable-keyboard-control"),
234 "no") != 0) {
235 return;
236 }
237 force_key_control = true;
238 }
239
240 if (!force_key_control) {
241 if (running_in_debugger()) {
243 << MOD_NAME "Running inside gdb - disabling "
244 "interactive keyboard control\n";
245 return;
246 }
247#ifdef HAVE_TERMIOS_H
248 if (!isatty(STDIN_FILENO)) {
250 "[key control] Stdin is not a TTY - disabling "
251 "keyboard control.\n");
252 return;
253 }
254#endif // defined HAVE_TERMIOS_H
255 }
256
257#ifdef HAVE_TERMIOS_H
258 if (pipe(m_event_pipe) != 0) {
259 log_msg(LOG_LEVEL_ERROR, "[key control] Cannot create control pipe!\n");
260 return;
261 }
262
263 if (!set_tio()) {
264 log_msg(LOG_LEVEL_WARNING, "[key control] Background task - disabling keyboard control.\n");
265 return;
266 }
267 atexit(restore_old_tio);
268#endif
269
270 load_config_map();
271
272 m_keyboard_thread = thread(&keyboard_control::impl::run, this);
273 m_started = true;
274}
275
277{
278#ifdef HAVE_TERMIOS_H
279 char c = 0;
280 if (write(m_event_pipe[1], &c, 1) != 1) {
281 perror(MOD_NAME "m_event_pipe write");
282 }
283#else
284 m_cv.notify_one();
285#endif
286}
287
289{
291 unique_lock<mutex> lk(m_lock);
292 if (!m_started) {
293 return;
294 }
295 m_should_exit = true;
296 m_started = false;
297 lk.unlock();
298 signal();
299 m_keyboard_thread.join();
300
301#ifdef HAVE_TERMIOS_H
302 close(m_event_pipe[0]);
303 close(m_event_pipe[1]);
304#endif
305}
306
307#ifdef HAVE_TERMIOS_H
308static int
309GETCH()
310{
311 unsigned char ch = 0;
312 const ssize_t ret = read(0, &ch, sizeof ch);
313 return ret <= 0 ? (int) ret - 1 : ch;
314}
315#else
316#define GETCH getch
317#endif
318
319#define CHECK_EOF(x) \
320 do { \
321 if ((x) < 0) { \
322 LOG(LOG_LEVEL_WARNING) << MOD_NAME \
323 "Unexpected " << ((x) == -1 ? "EOF" : "error") \
324 << " detected!\n"; \
325 return EOF; \
326 } \
327 } while (0)
328
340static int64_t get_ansi_code() {
341 int64_t c = GETCH();
342 debug_msg(MOD_NAME "Pressed %" PRId64 "\n", c);
343 if (c == '[') { // CSI
344 c = '\033' << 8 | '[';
345 while (true) {
346 if ((c >> 56u) != 0) {
347 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Long control sequence detected!\n";
348 return INT64_MIN;
349 }
350 c <<= 8;
351 int tmp = GETCH();
352 assert(tmp < 0x80); // ANSI esc seq should use only 7-bit encoding
353 debug_msg(MOD_NAME "Pressed %d\n", tmp);
354 CHECK_EOF(tmp);
355 c |= tmp;
356 if (tmp >= 0x40 && tmp <= 0xee) { // final byte
357 break;
358 }
359 }
360 } else if (c == 'N' // is this even used?
361 || c == 'O') { // eg. \EOP - F1-F4 (the rest of Fn is CSI)
362 int tmp = GETCH();
363 debug_msg(MOD_NAME "Pressed %d\n", tmp);
364 CHECK_EOF(tmp);
365 c = '\033' << 16 | c << 8 | tmp;
366 } else if (c >= 'a' && c <= 'z') {
367 return K_ALT(c);
368 } else {
369 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Unknown control sequence!\n";
370 return INT64_MIN;
371 }
372
373 return c;
374}
375
376#ifndef _WIN32
377/*
378 * Input must be 0x80-0xff
379 */
380static int count_utf8_bytes(unsigned int i) {
381 int count = 0;
382 while ((i & 0x80) != 0u) {
383 count++;
384 i <<= 1;
385 }
386 return count;
387}
388
390static int64_t get_utf8_code(int c) {
391 if (c < 0xc0) {
392 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Wrong UTF sequence!\n";
393 return INT64_MIN;
394 }
395 int ones = count_utf8_bytes(c);
396 for (int i = 1; i < ones; ++i) {
397 c = (c & 0x7fffff) << 8;
398 int tmp = GETCH();
399 debug_msg(MOD_NAME "Pressed %d\n", tmp);
400 CHECK_EOF(tmp);
401 c |= tmp;
402 }
403 if (ones > 7) {
404 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Unsupported UTF sequence length!\n";
405 return INT64_MIN;
406 }
407 return c;
408}
409#endif
410
411#ifdef _WIN32
412static int64_t convert_win_to_ansi_keycode(int c) {
413 switch (c) {
414 case 0xe048: return K_UP;
415 case 0xe04b: return K_LEFT;
416 case 0xe04d: return K_RIGHT;
417 case 0xe050: return K_DOWN;
418 default:
419 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Cannot map Win keycode 0x"
420 << hex << setfill('0') << setw(2) << c << setw(0) << dec << " to ANSI.\n";
421 return -1;
422 }
423}
424#endif
425
426static bool is_utf8(int64_t ch) {
427#ifdef _WIN32
428 return false;
429#endif
430
431 if (ch <= 0) {
432 return false;
433 }
434
435 unsigned char first_byte = 0U;
436 while (ch != 0) {
437 first_byte = ch & 0xff;
438 ch >>= 8;
439 }
440 return first_byte & 0x80;
441}
442
443static string get_utf8_representation(int64_t ch) {
444 char str[9] = "";
445 while (ch != 0) {
446 memmove(str + 1, str, 8);
447 str[0] = ch & 0xff;
448 ch >>= 8;
449 }
450 return str;
451}
452
453static string get_keycode_representation(int64_t ch) {
454 switch (ch) {
455 case K_UP: return "Up";
456 case K_DOWN: return "Down";
457 case K_LEFT: return "Left";
458 case K_RIGHT: return "Right";
459 case K_PGUP: return "Page-Up";
460 case K_PGDOWN: return "Page-Dn";
461 case K_CTRL_UP: return "Ctrl-Up";
462 case K_CTRL_DOWN: return "Ctrl-Dn";
463 }
464
465 if (ch >= 1 && ch <= 'z' - 'a' + 1) {
466 return string("Ctrl-") + string(1, 'a' + ch - 1);
467 }
468
469 if (ch >> 16 == 0 && ch >> 8 == '\033') {
470 return string("Alt-") + string(1, ch & 0xff);
471 }
472
473 if (ch <= UCHAR_MAX && isprint(ch)) {
474 return string("'") + string(1, ch) + "'";
475 }
476
477 if (is_utf8(ch)) {
478 return string("'") + get_utf8_representation(ch) + "'";
479 }
480
481 stringstream oss;
482 oss << "0x" << hex << ch;
483 return oss.str();
484}
485
493{
494 int64_t c;
495 while (1) {
496 {
497 unique_lock<mutex> lk(m_lock);
498 if (m_should_exit) {
499 return 0;
500 }
501
502 if (!m_pressed_keys.empty()) {
503 c = m_pressed_keys.front();
504 m_pressed_keys.pop();
505 return c;
506 }
507 }
508#ifdef HAVE_TERMIOS_H
509 fd_set set;
510 FD_ZERO(&set);
511 FD_SET(0, &set);
512 FD_SET(m_event_pipe[0], &set);
513 select(m_event_pipe[0] + 1, &set, NULL, NULL, NULL);
514
515 if (FD_ISSET(m_event_pipe[0], &set)) {
516 char c;
517 int bytes = read(m_event_pipe[0], &c, 1);
518 assert(bytes == 1);
519 continue; // skip to event processing
520 }
521
522 if (FD_ISSET(0, &set)) {
523#else
524 if (kbhit()) {
525#endif
526 int64_t c = GETCH();
527 debug_msg(MOD_NAME "Pressed %" PRId64 "\n", c);
528 if (c == '\033') {
529 if ((c = get_ansi_code()) < 0) {
530 continue;
531 }
532#ifdef _WIN32
533 } else if (c == 0x0 || c == 0xe0) { // Win keycodes
534 int tmp = GETCH();
535 debug_msg(MOD_NAME "Pressed %d\n", tmp);
536 CHECK_EOF(tmp);
537 if ((c = convert_win_to_ansi_keycode(c << 8 | tmp)) == -1) {
538 continue;
539 }
540#else
541 } else if (c > 0x80) {
542 if ((c = get_utf8_code(c)) < 0) {
543 continue;
544 }
545#endif
546 }
547 return c;
548 }
549#if !defined HAVE_TERMIOS_H
550 // kbhit is non-blockinkg - we don't want to busy-wait
551 unique_lock<mutex> lk(m_lock);
552 m_cv.wait_for(lk, milliseconds(200));
553 lk.unlock();
554#endif
555 }
556}
557
559{
560 set_thread_name("keyboard-control");
561 int saved_log_level = -1;
562 int64_t c;
563
564 while ((c = get_next_key())) {
565 if (c == -1) {
567 "EOF detected! Exiting keyboard control.\n";
568 break;
569 }
570 if (c == -2) {
571 perror(MOD_NAME "Error detected");
572 continue;
573 }
574 if (c == K_CTRL('X')) {
575 m_locked_against_changes = !m_locked_against_changes; // ctrl-x pressed
576 col() << TGREEN("Keyboard control: " << (m_locked_against_changes ? "" : "un") << "locked against changes\n");
577 continue;
578 }
579 if (m_locked_against_changes && guarded_keys.find(c) != guarded_keys.end()) {
580 col() << TGREEN("Keyboard control: locked against changes, press 'Ctrl-x' to unlock or 'h' for help.\n");
581 continue;
582 }
583
584 m_lock.lock();
585 if (key_mapping.find(c) != key_mapping.end()) { // user defined mapping exists
586 string cmd = key_mapping.at(c).first;
587 exec_external_commands(cmd.c_str());
588 m_lock.unlock();
589 continue;
590 }
591 m_lock.unlock();
592
593 switch (c) {
594 case '*':
595 case '/':
596 case '9':
597 case '0':
598 case 'm':
599 {
600 char path[] = "audio.receiver";
601 auto m = (struct msg_receiver *) new_message(sizeof(struct msg_receiver));
602 switch (c) {
603 case '0':
604 case '*': m->type = RECEIVER_MSG_INCREASE_VOLUME; break;
605 case '9':
606 case '/': m->type = RECEIVER_MSG_DECREASE_VOLUME; break;
607 case 'm': m->type = RECEIVER_MSG_MUTE_TOGGLE; break;
608 }
609
610 auto resp = send_message(m_root, path, (struct message *) m);
611 free_response(resp);
612 break;
613 }
614 case 'M':
615 {
616 char path[] = "audio.sender";
617 auto m = (struct msg_sender *) new_message(sizeof(struct msg_sender));
618 m->type = SENDER_MSG_MUTE_TOGGLE;
619
620 auto resp = send_message(m_root, path, (struct message *) m);
621 free_response(resp);
622 break;
623 }
624 case '>':
625 case '<':
626 {
627 int audio_delay = get_audio_delay();
628 audio_delay += c == '>' ? 10 : -10;
629 log_msg(LOG_LEVEL_INFO, "New audio delay: %d ms.\n", audio_delay);
630 set_audio_delay(audio_delay);
631 break;
632 }
633 case 'e':
634 {
635 char path[] = "exporter";
636 auto m = (struct message *) new_message(sizeof(struct msg_universal));
637 strcpy(((struct msg_universal *) m)->text, "toggle");
638 auto resp = send_message(m_root, path, (struct message *) m);
639 free_response(resp);
640 break;
641 }
642 case 'c':
643 case 'C':
644 read_command(c == 'C');
645 break;
646 case 'v':
647 case 'V':
648 {
649 if (islower(c)) {
650 log_level = (log_level + 1) % (LOG_LEVEL_MAX + 1);
651 } else {
652 log_level = (log_level - 1 + (LOG_LEVEL_MAX + 1)) % (LOG_LEVEL_MAX + 1);
653 }
654 cout << "Log level: " << log_level << "\n";
655 break;
656 }
657 case 'r':
658 {
659 bool skip = !get_log_output().get_skip_repeats();
661 cout << "Skip repeated messages: " << std::boolalpha << skip << std::noboolalpha << "\n";
662 break;
663 }
664 case 's':
665 if (saved_log_level == -1) {
666 saved_log_level = log_level;
667 col() << TGREEN("Output suspended, press 'q' to continue.\n");
669 }
670 break;
671 case 'S':
672 if (saved_log_level != -1) {
673 log_level = saved_log_level;
674 saved_log_level = -1;
675 col() << TGREEN( "Output resumed.\n");
676 }
677 break;
678 case 'h':
679 usage();
680 break;
681 case 'i':
682 cout << "\n";
683 info();
684 break;
685 case '\n':
686 case '\r':
687 cout << endl;
688 break;
689 default:
690 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Unsupported key " << get_keycode_representation(c) << " pressed. Press 'h' to help.\n";
691 }
692 }
693}
694
695static void
697{
698 enum {
699 ELAPS_LEN = 9,
700 START_LEN = 25,
701 };
702 struct tm tm_buf = {};
703
704 localtime_s(&start_time, &tm_buf);
705 char start[START_LEN] = "";
706 strftime(start, sizeof start, "%c", &tm_buf);
707
708 const time_t diff = time(nullptr) - start_time;
709 gmtime_s(&diff, &tm_buf);
710 char elapsed[ELAPS_LEN] = "";
711 strftime(elapsed, sizeof elapsed, "%T", &tm_buf);
712
713 col() << TBOLD("Start time: ") << start << " (elapsed " << elapsed
714 << ")\n";
715}
716
718{
719 col() << TBOLD("UltraGrid version: ") << get_version_details() << "\n";
720 print_start_elapsed_time(m_start_time);
721 col() << TBOLD("Verbosity level: ") << log_level << (log_level == LOG_LEVEL_INFO ? " (default)" : "") << "\n";
722 col() << TBOLD("Locked against changes: ") << (m_locked_against_changes ? "true" : "false") << "\n";
723 col() << TBOLD("Audio playback delay: ") << get_audio_delay() << " ms\n";
724
725 {
726 int muted_sender = -1;
727 int muted_receiver = -1;
728 const char *path = "audio.receiver";
729 auto *m_recv = reinterpret_cast<struct msg_receiver *>(new_message(sizeof(struct msg_receiver)));
731 auto *resp = send_message_sync(m_root, path, reinterpret_cast<struct message *>(m_recv), 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE);
732 if (response_get_status(resp) == 200) {
733 double vol = 0;
734 sscanf(response_get_text(resp), "%lf,%d", &vol, &muted_receiver);
735 double db = 20.0 * log10(vol);
736 color_printf(TBOLD("Playback volume: ") "%.2f%% (%s%.2f dB)\n", vol * 100.0, (db >= 0.0 ? "+" : ""), db);
737 }
738 free_response(resp);
739 path = "audio.sender";
740 auto *m_send = reinterpret_cast<struct msg_sender *>(new_message(sizeof(struct msg_sender)));
741 m_send->type = SENDER_MSG_GET_STATUS;
742 resp = send_message_sync(m_root, path, reinterpret_cast<struct message *>(m_send), 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE);
743 if (response_get_status(resp) == 200) {
744 sscanf(response_get_text(resp), "%d", &muted_sender);
745 }
746 free_response(resp);
747 auto muted_to_string = [](int val) {
748 switch (val) {
749 case 0: return "false";
750 case 1: return "true";
751 default: return "(unknown)";
752 }
753 };
754 col() << TBOLD("Audio muted - sender: ") << muted_to_string(muted_sender) << ", " << TBOLD("receiver: ") << muted_to_string(muted_receiver) << "\n";
755 }
756
757 {
758 struct video_desc desc{};
759 struct msg_sender *m = (struct msg_sender *) new_message(sizeof(struct msg_sender));
761 struct response *r = send_message_sync(m_root, "sender", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE);
763 const char *text = response_get_text(r);
764 istringstream iss(text);
765 iss >> desc;
766 col() << TBOLD("Captured video format: ") << desc << "\n";
767 }
768 free_response(r);
769 }
770
771 {
772 struct video_desc desc{};
773 struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal));
774 strcpy(m->text, "get_format");
775 struct response *r = send_message_sync(m_root, "receiver.decoder", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE);
777 const char *text = response_get_text(r);
778 istringstream iss(text);
779 iss >> desc;
780 col() << TBOLD("Received video format: ") << desc << "\n";
781 }
782 free_response(r);
783 }
784
785 {
786 struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal));
787 strcpy(m->text, "get_port");
788 struct response *r = send_message_sync(m_root, "control", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE);
790 col() << TBOLD("Control port: ") << response_get_text(r) << "\n";
791 }
792 free_response(r);
793 }
794
795 {
796 struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal));
797 strcpy(m->text, "status");
798 struct response *r = send_message_sync(m_root, "exporter", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE);
800 col() << TBOLD("Exporting: ") << response_get_text(r) << "\n";
801 }
802 free_response(r);
803 }
804
805 cout << "\n";
806}
807
808#define G(key) (guarded_keys.find(key) != guarded_keys.end() ? " [g]" : "")
809
811{
812 col() << "\nAvailable keybindings:\n" <<
813 TBOLD("\t * 0 ") << "- increase receiver volume" << G('*') << "\n" <<
814 TBOLD("\t / 9 ") << "- decrease receiver volume" << G('/') << "\n" <<
815 TBOLD("\t > ") << "- increase receiver audio delay by 10 ms" << G('>') << "\n" <<
816 TBOLD("\t < ") << "- decrease receiver audio delay by 10 ms" << G('<') << "\n" <<
817 TBOLD("\t m ") << "- mute/unmute receiver" << G('m') << "\n" <<
818 TBOLD("\t M ") << "- mute/unmute sender" << G('M') << "\n" <<
819 TBOLD("\t v ") << "- increase verbosity level" << G('v') << "\n" <<
820 TBOLD("\t V ") << "- decrease verbosity level" << G('V') << "\n" <<
821 TBOLD("\t r ") << "- skip repeated messages (toggle) " << G('r') << "\n" <<
822 TBOLD("\t e ") << "- record captured content (toggle)" << G('e') << "\n" <<
823 TBOLD("\t h ") << "- show help" << G('h') << "\n" <<
824 TBOLD("\t i ") << "- show various information" << G('i') << "\n" <<
825 TBOLD("\t s S ") << "- suspend/resume output" << G('s') << G('S') << "\n" <<
826 TBOLD("\t c C ") << "- execute command through control socket (capital for multiple)" << G('c') << G('C') << "\n" <<
827 TBOLD("\tCtrl-x ") << "- unlock/lock against changes" << G(K_CTRL('X')) << "\n" <<
828 TBOLD("\tCtrl-c ") << "- exit " << G(K_CTRL('c')) << "\n" <<
829 "\n";
830
831 if (key_mapping.size() > 0) {
832 cout << "Custom keybindings:\n";
833 for (auto const &it : key_mapping) {
834 col() << TBOLD("\t" << setw(10) << get_keycode_representation(it.first) << setw(0) <<) << " - " << (it.second.second.empty() ? it.second.first : it.second.second) << G(it.first) << "\n";
835 }
836 cout << "\n";
837 }
838 col() << TBOLD("[g]") << " indicates that that option is guarded (Ctrl-x needs to be pressed first)\n";
839}
840
842 FILE *in = fopen(CONFIG_FILE, "r");
843 if (!in) {
844 return;
845 }
846 char buf[1024];
847 while (fgets(buf, sizeof buf - 1, in)) {
848 if (strlen(buf) > 0 && buf[strlen(buf) - 1] == '\n') { // trim NL
849 buf[strlen(buf) - 1] = '\0';
850 }
851 if (strlen(buf) == 0) {
852 continue;
853 }
854 if (!exec_local_command(buf)) {
855 LOG(LOG_LEVEL_ERROR) << "Wrong config: " << buf << "\n";
856 continue;
857 }
858 }
859 fclose(in);
860}
861
863{
864 if (strcmp(command, "localhelp") == 0) {
865 printf("Local help:\n"
866 "\tmap <X> cmd1[;cmd2..][#name]\n"
867 "\t\tmaps key to command(s) for control socket (with an optional\n\t\tidentifier)\n"
868 "\tmap #<X> cmd1[;cmd2..][#name]\n"
869 "\t\tmaps key number (ASCII or multi-byte for non-ASCII) to\n\t\tcommand(s) for control socket (with an optional identifier)\n"
870 "\tunmap <X>\n"
871 "\tpress <num>\n"
872 "\t\tEmulate pressing key <num>\n\n"
873 "Mappings can be stored in " CONFIG_FILE "\n\n");
874 return true;
875 } else if (strstr(command, "map ") == command) {
876 char *ccpy = strdup(command + strlen("map "));
877 if (strlen(ccpy) > 3) {
878 int64_t key = ccpy[0];
879 char *command = ccpy + 2;
880 if (key == '#' && isdigit(ccpy[1])) {
881 key = strtoll(ccpy + 1, &command, 10);
882 command += 1; // skip ' '
883 }
884 const char *name = "";
885 if (strchr(command, '#')) {
886 name = strchr(command, '#') + 1;
887 *strchr(command, '#') = '\0';
888 }
889 if (key_mapping.find(key) == key_mapping.end()) {
890 key_mapping.insert({key, make_pair(command, name)});
891 } else {
892 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Trying to register key shortcut " << get_keycode_representation(key) << ", which is already regestered, ignoring.\n";
893 }
894 }
895 free(ccpy);
896 return true;
897 } else if (strstr(command, "unmap ") == command) {
898 command += strlen("unmap ");
899 if (strlen(command) >= 1) {
900 key_mapping.erase(command[0]);
901 }
902 return true;
903 } else if (strstr(command, "press ") == command) {
904 const char *key = command + strlen("press ");
905 int64_t k;
906 sscanf(key, "%" SCNi64, &k);
907 m_pressed_keys.push(k);
908 signal();
909 return true;
910 }
911 return false;
912}
913
915{
916 char *cpy = strdup(commands);
917 char *item, *save_ptr, *tmp = cpy;
918 while ((item = strtok_r(tmp, ";", &save_ptr))) {
919 struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal));
920 strcpy(m->text, "execute ");
921 strncat(m->text, item, sizeof m->text - strlen(m->text) - 1);
922 struct response *r = send_message_sync(m_root, "control", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE);
924 LOG(LOG_LEVEL_ERROR) << "Could not send a message!\n";
925 }
926 free_response(r);
927
928 tmp = nullptr;
929 }
930 free(cpy);
931}
932
934 if (!exec_local_command(command)) {
935 exec_external_commands(command);
936 }
937}
938
940{
941 int saved_log_level = log_level;
944 if (multiple) {
945 printf("Enter commands for control (try \"help\" or \"localhelp\").\n");
946 printf("Exit the mode with blank line.\n");
947 } else {
948 printf("Enter a command for control (try \"help\" or \"localhelp\"):\n");
949 }
950 while (1) {
952 printf("control> ");
953 char buf[sizeof msg_universal::text];
954 if (!fgets(buf, sizeof buf - 1, stdin)) {
955 log_level = saved_log_level;
956 break;
957 }
958 log_level = saved_log_level;
959
960 if (strlen(buf) == 0 || (strlen(buf) == 1 && buf[0] == '\n')) { // empty input
961 break;
962 }
963 if (buf[strlen(buf) - 1] == '\n') { // strip newline
964 buf[strlen(buf) - 1] = '\0';
965 }
966
967 m_lock.lock();
968 execute_command(buf);
969 m_lock.unlock();
970
971 if (!multiple) {
972 break;
973 }
974 }
975
976 set_tio();
977}
978
982static bool set_tio()
983{
984#ifdef HAVE_TERMIOS_H
985 struct termios new_tio = old_tio;
986 /* disable canonical mode (buffered i/o) and local echo */
987 new_tio.c_lflag &=(~ICANON & ~ECHO);
988 // Wrap calling of tcsetattr() by handling SIGTTOU. SIGTTOU can be raised if task is
989 // run in background and trying to call tcsetattr(). If so, we disable keyboard
990 // control.
991 struct sigaction sa, sa_old;
992 memset(&sa, 0, sizeof sa);
993 sa.sa_handler = catch_signal;
994 sigemptyset(&sa.sa_mask);
995 sigaction(SIGTTOU, &sa, &sa_old);
996 /* set the new settings immediately */
997 tcsetattr(STDIN_FILENO,TCSANOW,&new_tio);
998 sigaction(SIGTTOU, &sa_old, NULL);
999
1000 if (!signal_catched) {
1001 return true;
1002 }
1003#endif
1004 return false;
1005}
1006
1008 auto s = static_cast<keyboard_control::impl *>(m->priv_data);
1009
1010 s->msg_received();
1011}
1012
1014 struct message *msg;
1015 while ((msg = check_message(&m_mod))) {
1016 auto m = reinterpret_cast<struct msg_universal *>(msg);
1017 lock_guard<mutex> lk(m_lock);
1018 execute_command(m->text);
1019 struct response *r = new_response(RESPONSE_OK, nullptr);
1020 free_message((struct message *) m, r);
1021 }
1022}
1023
1024void keycontrol_send_key(struct module *root, int64_t key) {
1025 struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal));
1026 snprintf(m->text, sizeof m->text, "press %" PRId64, key);
1027 struct response *r = send_message_sync(root, "keycontrol", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE);
1028 if (response_get_status(r) != RESPONSE_OK) {
1029 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot set key to keycontrol (error %d)!\n", response_get_status(r));
1030 }
1031 free_response(r);
1032}
1033
1041bool keycontrol_register_key(struct module *receiver_mod, int64_t key, const char *message, const char *description) {
1042 assert(strchr(message, '#') == nullptr && (description == nullptr || strchr(description, '#') == nullptr));
1043 char receiver_path[1024];
1044 if (!module_get_path_str(receiver_mod, receiver_path, sizeof receiver_path)) {
1045 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot format format path for sender!\n");
1046 return false;
1047 }
1048 struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal));
1049 if (description == nullptr) {
1051 }
1052 snprintf(m->text, sizeof m->text, "map #%" PRId64 " %s %s#%s", key, receiver_path, message, description);
1053 struct response *r = send_message_sync(get_root_module(receiver_mod), "keycontrol", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE);
1054 if (response_get_status(r) != RESPONSE_OK) {
1055 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot register keyboard control (error %d)!\n", response_get_status(r));
1056 free_response(r);
1057 return false;
1058 }
1059 free_response(r);
1060 return true;
1061}
1062
1063void get_keycode_name(int64_t ch, char *buf, size_t buflen) {
1064 if (buflen == 0) {
1065 return;
1066 }
1067 string name = get_keycode_representation(ch);
1068 strncpy(buf, name.c_str(), buflen - 1);
1069 buf[buflen - 1] = '\0';
1070}
1071
int void * val
struct video_desc desc
#define MOD_NAME
Definition audio.cpp:110
void set_skip_repeats(bool val)
Definition debug.h:155
bool get_skip_repeats() const
Definition debug.h:156
impl(struct module *parent)
void execute_command(const char *command)
void read_command(bool multiple)
void exec_external_commands(const char *commands)
std::queue< int64_t > m_pressed_keys
std::condition_variable m_cv
std::map< int64_t, std::pair< std::string, std::string > > key_mapping
static void msg_received_func(struct module *)
bool exec_local_command(const char *command)
std::set< int64_t > guarded_keys
std::unique_ptr< impl > m_impl
keyboard_control(struct module *parent)
int color_printf(const char *format,...)
#define TGREEN(x)
Definition color_out.h:72
#define TBOLD(x)
Definition color_out.h:70
#define gmtime_s
Definition time.h:50
#define localtime_s
Definition time.h:51
static void new_message(struct module *m)
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_QUIET
suppress all logging
Definition debug.h:57
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG_LEVEL_MAX
Definition debug.h:69
#define LOG(level)
Definition debug.h:313
#define debug_msg(...)
#define debug_msg(...) log_msg(LOG_LEVEL_DEBUG, "[pid/%d +%d %s] ", getpid(), LINE,...
Definition debug.h:92
Log_output & get_log_output()
Definition debug.h:266
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
void module_init_default(struct module *module_data)
Definition module.c:48
void module_register(struct module *module_data, struct module *parent)
Definition module.c:82
bool module_get_path_str(struct module *mod, char *buf, size_t buflen)
Definition module.c:408
void module_done(struct module *module_data)
Definition module.c:93
struct module * get_root_module(struct module *node)
Definition module.c:203
@ MODULE_CLASS_KEYCONTROL
Definition module.h:97
int count
Definition hd-rum.c:44
bool running_in_debugger()
Definition host.cpp:1190
void set_audio_delay(int audio_delay)
Definition host.cpp:914
const char * get_commandline_param(const char *key)
Definition host.cpp:894
int get_audio_delay(void)
Definition host.cpp:909
const char * get_version_details()
Definition host.cpp:856
#define ADD_TO_PARAM(param, doc)
Definition host.h:203
volatile int log_level
Definition debug.cpp:65
static bool set_tio()
#define CONFIG_FILE
static int64_t get_utf8_code(int c)
static void print_start_elapsed_time(time_t start_time)
#define CHECK_EOF(x)
static int64_t get_ansi_code()
static string get_keycode_representation(int64_t ch)
static int count_utf8_bytes(unsigned int i)
static string get_utf8_representation(int64_t ch)
static bool is_utf8(int64_t ch)
#define GETCH
void restore_old_tio(void)
#define K_CTRL_DOWN
#define K_LEFT
#define K_CTRL_UP
#define K_RIGHT
#define K_ALT(x)
EXTERN_C void get_keycode_name(int64_t ch, char *buf, size_t buflen) __attribute__((used))
EXTERN_C void keycontrol_send_key(struct module *root, int64_t key) __attribute__((used))
EXTERN_C bool keycontrol_register_key(struct module *sender_mod, int64_t key, const char *message, const char *description) __attribute__((used))
Registers callback message for given key.
#define K_PGDOWN
#define K_DOWN
#define K_CTRL(x)
#define K_PGUP
#define K_UP
EXTERN_C void restore_old_tio(void)
void usage()
Definition main.cpp:28
#define G(x, y, z)
Definition md5.c:64
void free_message(struct message *msg, struct response *r)
struct response * new_response(int status, const char *text)
struct response * send_message_sync(struct module *root, const char *const_path, struct message *msg, int timeout_ms, int flags)
Sends message without waiting for response.
int response_get_status(struct response *r)
struct response * send_message(struct module *root, const char *const_path, struct message *msg)
Sends message without waiting for response.
struct message * check_message(struct module *mod)
void free_response(struct response *r)
const char * response_get_text(struct response *r)
#define SEND_MESSAGE_FLAG_QUIET
do not print error messages on console (recv not found/queue full)
Definition messaging.h:171
@ SENDER_MSG_MUTE_TOGGLE
Definition messaging.h:89
@ SENDER_MSG_QUERY_VIDEO_MODE
Definition messaging.h:91
@ SENDER_MSG_GET_STATUS
Definition messaging.h:86
#define RESPONSE_OK
Definition messaging.h:57
#define SEND_MESSAGE_FLAG_NO_STORE
if receiver doesn't exist, doesn't store it and retur 404 instead
Definition messaging.h:172
@ RECEIVER_MSG_MUTE_TOGGLE
Definition messaging.h:116
@ RECEIVER_MSG_DECREASE_VOLUME
Definition messaging.h:113
@ RECEIVER_MSG_INCREASE_VOLUME
Definition messaging.h:112
@ RECEIVER_MSG_GET_AUDIO_STATUS
Definition messaging.h:111
struct msghdr msg
const char * parent
Definition settings.cpp:198
const char * name
Definition settings.cpp:193
Definition hd-rum.c:27
void * priv_data
Definition module.h:126
enum msg_receiver_type type
Definition messaging.h:120
enum msg_sender_type type
Definition messaging.h:97
char text[8192]
Definition messaging.h:157
void set_thread_name(const char *name)
Definition thread.c:72
static struct timeval start_time
Definition tv.c:57
This file contains some common data types.
This is an umbrella header for video functions.
const char * description
Definition cineform.cpp:126
const char * key
Definition cineform.cpp:125