59#include <condition_variable>
79#include <sys/select.h>
102#define CONFIG_FILE "ug-key-map.txt"
103#define MOD_NAME "[key control] "
105using std::chrono::milliseconds;
110using std::istringstream;
111using std::lock_guard;
117using std::stringstream;
119using std::unique_lock;
122static bool signal_catched =
false;
124static void catch_signal(
int)
126 signal_catched =
true;
131static struct termios old_tio;
164 int m_event_pipe[2] = {0, 0};
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;
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);
198 tcsetattr(STDIN_FILENO,TCSANOW,&old_tio);
199 sigaction(SIGTTOU, &sa_old, NULL);
213 tcgetattr(STDIN_FILENO,&old_tio);
218 m_mod.priv_data =
this;
227ADD_TO_PARAM(
"disable-keyboard-control",
"* disable-keyboard-control[=no]\n"
228 " disables keyboard control (usable mainly for non-interactive runs)\n");
231 bool force_key_control =
false;
237 force_key_control =
true;
240 if (!force_key_control) {
243 <<
MOD_NAME "Running inside gdb - disabling "
244 "interactive keyboard control\n";
248 if (!isatty(STDIN_FILENO)) {
250 "[key control] Stdin is not a TTY - disabling "
251 "keyboard control.\n");
258 if (pipe(m_event_pipe) != 0) {
280 if (write(m_event_pipe[1], &c, 1) != 1) {
281 perror(
MOD_NAME "m_event_pipe write");
291 unique_lock<mutex> lk(m_lock);
295 m_should_exit =
true;
299 m_keyboard_thread.join();
302 close(m_event_pipe[0]);
303 close(m_event_pipe[1]);
311 unsigned char ch = 0;
312 const ssize_t ret = read(0, &ch,
sizeof ch);
313 return ret <= 0 ? (int) ret - 1 : ch;
319#define CHECK_EOF(x) \
322 LOG(LOG_LEVEL_WARNING) << MOD_NAME \
323 "Unexpected " << ((x) == -1 ? "EOF" : "error") \
344 c =
'\033' << 8 |
'[';
346 if ((c >> 56u) != 0) {
356 if (tmp >= 0x40 && tmp <= 0xee) {
365 c =
'\033' << 16 | c << 8 | tmp;
366 }
else if (c >=
'a' && c <=
'z') {
382 while ((i & 0x80) != 0u) {
396 for (
int i = 1; i < ones; ++i) {
397 c = (c & 0x7fffff) << 8;
412static int64_t convert_win_to_ansi_keycode(
int c) {
414 case 0xe048:
return K_UP;
415 case 0xe04b:
return K_LEFT;
417 case 0xe050:
return K_DOWN;
420 << hex << setfill(
'0') << setw(2) << c << setw(0) << dec <<
" to ANSI.\n";
435 unsigned char first_byte = 0U;
437 first_byte = ch & 0xff;
440 return first_byte & 0x80;
446 memmove(str + 1, str, 8);
455 case K_UP:
return "Up";
456 case K_DOWN:
return "Down";
457 case K_LEFT:
return "Left";
459 case K_PGUP:
return "Page-Up";
465 if (ch >= 1 && ch <=
'z' -
'a' + 1) {
466 return string(
"Ctrl-") + string(1,
'a' + ch - 1);
469 if (ch >> 16 == 0 && ch >> 8 ==
'\033') {
470 return string(
"Alt-") + string(1, ch & 0xff);
473 if (ch <= UCHAR_MAX && isprint(ch)) {
474 return string(
"'") + string(1, ch) +
"'";
482 oss <<
"0x" << hex << ch;
497 unique_lock<mutex> lk(m_lock);
502 if (!m_pressed_keys.empty()) {
503 c = m_pressed_keys.front();
504 m_pressed_keys.pop();
512 FD_SET(m_event_pipe[0], &set);
513 select(m_event_pipe[0] + 1, &set, NULL, NULL, NULL);
515 if (FD_ISSET(m_event_pipe[0], &set)) {
517 int bytes = read(m_event_pipe[0], &c, 1);
522 if (FD_ISSET(0, &set)) {
533 }
else if (c == 0x0 || c == 0xe0) {
537 if ((c = convert_win_to_ansi_keycode(c << 8 | tmp)) == -1) {
541 }
else if (c > 0x80) {
549#if !defined HAVE_TERMIOS_H
551 unique_lock<mutex> lk(m_lock);
552 m_cv.wait_for(lk, milliseconds(200));
561 int saved_log_level = -1;
564 while ((c = get_next_key())) {
567 "EOF detected! Exiting keyboard control.\n";
575 m_locked_against_changes = !m_locked_against_changes;
576 col() <<
TGREEN(
"Keyboard control: " << (m_locked_against_changes ?
"" :
"un") <<
"locked against changes\n");
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");
585 if (key_mapping.find(c) != key_mapping.end()) {
586 string cmd = key_mapping.at(c).first;
587 exec_external_commands(cmd.c_str());
600 char path[] =
"audio.receiver";
616 char path[] =
"audio.sender";
628 audio_delay += c ==
'>' ? 10 : -10;
635 char path[] =
"exporter";
644 read_command(c ==
'C');
654 cout <<
"Log level: " <<
log_level <<
"\n";
661 cout <<
"Skip repeated messages: " << std::boolalpha << skip << std::noboolalpha <<
"\n";
665 if (saved_log_level == -1) {
667 col() <<
TGREEN(
"Output suspended, press 'q' to continue.\n");
672 if (saved_log_level != -1) {
674 saved_log_level = -1;
702 struct tm tm_buf = {};
705 char start[START_LEN] =
"";
708 const time_t diff = time(
nullptr) -
start_time;
710 char elapsed[ELAPS_LEN] =
"";
711 strftime(elapsed,
sizeof elapsed,
"%T", &tm_buf);
713 col() <<
TBOLD(
"Start time: ") <<
start <<
" (elapsed " << elapsed
722 col() <<
TBOLD(
"Locked against changes: ") << (m_locked_against_changes ?
"true" :
"false") <<
"\n";
726 int muted_sender = -1;
727 int muted_receiver = -1;
728 const char *path =
"audio.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);
739 path =
"audio.sender";
747 auto muted_to_string = [](
int val) {
749 case 0:
return "false";
750 case 1:
return "true";
751 default:
return "(unknown)";
754 col() <<
TBOLD(
"Audio muted - sender: ") << muted_to_string(muted_sender) <<
", " <<
TBOLD(
"receiver: ") << muted_to_string(muted_receiver) <<
"\n";
764 istringstream iss(text);
766 col() <<
TBOLD(
"Captured video format: ") <<
desc <<
"\n";
774 strcpy(m->
text,
"get_format");
778 istringstream iss(text);
780 col() <<
TBOLD(
"Received video format: ") <<
desc <<
"\n";
787 strcpy(m->
text,
"get_port");
797 strcpy(m->
text,
"status");
808#define G(key) (guarded_keys.find(key) != guarded_keys.end() ? " [g]" : "")
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" <<
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";
838 col() <<
TBOLD(
"[g]") <<
" indicates that that option is guarded (Ctrl-x needs to be pressed first)\n";
847 while (fgets(buf,
sizeof buf - 1, in)) {
848 if (strlen(buf) > 0 && buf[strlen(buf) - 1] ==
'\n') {
849 buf[strlen(buf) - 1] =
'\0';
851 if (strlen(buf) == 0) {
854 if (!exec_local_command(buf)) {
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"
872 "\t\tEmulate pressing key <num>\n\n"
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);
884 const char *
name =
"";
885 if (strchr(command,
'#')) {
886 name = strchr(command,
'#') + 1;
887 *strchr(command,
'#') =
'\0';
889 if (key_mapping.find(
key) == key_mapping.end()) {
890 key_mapping.insert({
key, make_pair(command,
name)});
897 }
else if (strstr(command,
"unmap ") == command) {
898 command += strlen(
"unmap ");
899 if (strlen(command) >= 1) {
900 key_mapping.erase(command[0]);
903 }
else if (strstr(command,
"press ") == command) {
904 const char *
key = command + strlen(
"press ");
906 sscanf(
key,
"%" SCNi64, &k);
907 m_pressed_keys.push(k);
916 char *cpy = strdup(commands);
917 char *
item, *save_ptr, *tmp = cpy;
918 while ((
item = strtok_r(tmp,
";", &save_ptr))) {
920 strcpy(m->
text,
"execute ");
934 if (!exec_local_command(command)) {
935 exec_external_commands(command);
945 printf(
"Enter commands for control (try \"help\" or \"localhelp\").\n");
946 printf(
"Exit the mode with blank line.\n");
948 printf(
"Enter a command for control (try \"help\" or \"localhelp\"):\n");
954 if (!fgets(buf,
sizeof buf - 1, stdin)) {
960 if (strlen(buf) == 0 || (strlen(buf) == 1 && buf[0] ==
'\n')) {
963 if (buf[strlen(buf) - 1] ==
'\n') {
964 buf[strlen(buf) - 1] =
'\0';
968 execute_command(buf);
985 struct termios new_tio = old_tio;
987 new_tio.c_lflag &=(~ICANON & ~ECHO);
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);
997 tcsetattr(STDIN_FILENO,TCSANOW,&new_tio);
998 sigaction(SIGTTOU, &sa_old, NULL);
1000 if (!signal_catched) {
1017 lock_guard<mutex> lk(m_lock);
1018 execute_command(m->text);
1026 snprintf(m->
text,
sizeof m->
text,
"press %" PRId64,
key);
1043 char receiver_path[1024];
1068 strncpy(buf,
name.c_str(), buflen - 1);
1069 buf[buflen - 1] =
'\0';
void set_skip_repeats(bool val)
bool get_skip_repeats() const
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
std::thread m_keyboard_thread
static void msg_received_func(struct module *)
bool exec_local_command(const char *command)
std::set< int64_t > guarded_keys
bool m_locked_against_changes
std::unique_ptr< impl > m_impl
keyboard_control(struct module *parent)
int color_printf(const char *format,...)
static void new_message(struct module *m)
void log_msg(int level, const char *format,...)
#define LOG_LEVEL_INFO
normal reporting
#define LOG_LEVEL_QUIET
suppress all logging
#define LOG_LEVEL_ERROR
general errors
#define debug_msg(...)
#define debug_msg(...) log_msg(LOG_LEVEL_DEBUG, "[pid/%d +%d %s] ", getpid(), LINE,...
Log_output & get_log_output()
#define LOG_LEVEL_WARNING
less severe errors
void module_init_default(struct module *module_data)
void module_register(struct module *module_data, struct module *parent)
bool module_get_path_str(struct module *mod, char *buf, size_t buflen)
void module_done(struct module *module_data)
struct module * get_root_module(struct module *node)
@ MODULE_CLASS_KEYCONTROL
bool running_in_debugger()
void set_audio_delay(int audio_delay)
const char * get_commandline_param(const char *key)
int get_audio_delay(void)
const char * get_version_details()
#define ADD_TO_PARAM(param, doc)
static int64_t get_utf8_code(int c)
static void print_start_elapsed_time(time_t start_time)
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)
void restore_old_tio(void)
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.
EXTERN_C void restore_old_tio(void)
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)
@ SENDER_MSG_QUERY_VIDEO_MODE
#define SEND_MESSAGE_FLAG_NO_STORE
if receiver doesn't exist, doesn't store it and retur 404 instead
@ RECEIVER_MSG_MUTE_TOGGLE
@ RECEIVER_MSG_DECREASE_VOLUME
@ RECEIVER_MSG_INCREASE_VOLUME
@ RECEIVER_MSG_GET_AUDIO_STATUS
enum msg_receiver_type type
enum msg_sender_type type
void set_thread_name(const char *name)
static struct timeval start_time
This file contains some common data types.
This is an umbrella header for video functions.