/** * @file keyboard_control.cpp * @author Martin Pulec * * With code taken from Olivier Mehani (set_tio()). */ /* * Copyright (c) 2015-2021 CESNET, z. s. p. o. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of CESNET nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @file * @todo * Consider using some abstraction over terminal control mainly because * differencies between Linux and Windows (ANSI mode or terminfo?) * * @todo * - key should not be overriden (eg. preset and custom) * - preset keys and customs ones should be unified (in key mapping) */ #ifdef HAVE_CONFIG_H #include "config.h" #include "config_unix.h" #include "config_win32.h" #endif // HAVE_CONFIG_H #include #include #include #include #include #include #include "debug.h" #include "host.h" #include "keyboard_control.h" #include "messaging.h" #include "utils/color_out.h" #include "utils/thread.h" #include "video.h" #ifdef HAVE_TERMIOS_H #include #include #else #include #endif static bool set_tio(); #define CONFIG_FILE "ug-key-map.txt" #define MOD_NAME "[key control] " using namespace std; using namespace std::chrono; #ifdef HAVE_TERMIOS_H static bool signal_catched = false; static void catch_signal(int) { signal_catched = true; } #endif #ifdef HAVE_TERMIOS_H static bool old_tio_set = false; static struct termios old_tio; #endif void restore_old_tio(void) { #ifdef HAVE_TERMIOS_H if (!old_tio_set) { return; } struct sigaction sa, sa_old; memset(&sa, 0, sizeof sa); sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sigaction(SIGTTOU, &sa, &sa_old); /* set the new settings immediately */ /* restore the former settings */ tcsetattr(STDIN_FILENO,TCSANOW,&old_tio); sigaction(SIGTTOU, &sa_old, NULL); old_tio_set = false; #endif } keyboard_control::keyboard_control(struct module *parent) : m_mod{}, m_root(nullptr), m_parent(parent), #ifdef HAVE_TERMIOS_H m_event_pipe{0, 0}, #endif m_should_exit(false), m_started(false), m_locked_against_changes(true), guarded_keys { '*', '/', '9', '0', 'c', 'C', 'm', 'M', '>', '<', 'e', 's', 'S', 'v', 'V', 'r' } { m_start_time = time(NULL); m_root = get_root_module(parent); #ifdef HAVE_TERMIOS_H /* get the terminal settings for stdin */ /* we want to keep the old setting to restore them a the end */ tcgetattr(STDIN_FILENO,&old_tio); #endif module_init_default(&m_mod); m_mod.cls = MODULE_CLASS_KEYCONTROL; m_mod.priv_data = this; m_mod.new_message = keyboard_control::msg_received_func; module_register(&m_mod, m_parent); } keyboard_control::~keyboard_control() { stop(); // in case that it was not called explicitly } ADD_TO_PARAM("disable-keyboard-control", "* disable-keyboard-control\n" " disables keyboard control (usable mainly for non-interactive runs)\n"); void keyboard_control::start() { if (get_commandline_param("disable-keyboard-control")) { return; } if(running_in_debugger()){ LOG(LOG_LEVEL_WARNING) << MOD_NAME "Running inside gdb - disabling interactive keyboard control\n"; return; } #ifdef HAVE_TERMIOS_H if (pipe(m_event_pipe) != 0) { log_msg(LOG_LEVEL_ERROR, "[key control] Cannot create control pipe!\n"); return; } if (!isatty(STDIN_FILENO)) { log_msg(LOG_LEVEL_WARNING, "[key control] Stdin is not a TTY - disabling keyboard control.\n"); return; } if (!set_tio()) { log_msg(LOG_LEVEL_WARNING, "[key control] Background task - disabling keyboard control.\n"); return; } old_tio_set = true; atexit(restore_old_tio); #endif load_config_map(); m_keyboard_thread = thread(&keyboard_control::run, this); m_started = true; } void keyboard_control::signal() { #ifdef HAVE_TERMIOS_H char c = 0; if (write(m_event_pipe[1], &c, 1) != 1) { perror(MOD_NAME "m_event_pipe write"); } #else m_cv.notify_one(); #endif } void keyboard_control::stop() { module_done(&m_mod); if (!m_started) { return; } unique_lock lk(m_lock); m_should_exit = true; lk.unlock(); signal(); m_keyboard_thread.join(); #ifdef HAVE_TERMIOS_H close(m_event_pipe[0]); close(m_event_pipe[1]); #endif m_started = false; } #ifdef HAVE_TERMIOS_H #define GETCH getchar #else #define GETCH getch #endif #define CHECK_EOF(x) do { if (x == EOF) { LOG(LOG_LEVEL_WARNING) << MOD_NAME "Unexpected EOF detected!\n"; return EOF; } } while(0) /** * Tries to parse at least some small subset of ANSI control sequences not to * be ArrowUp interpreted as '\033', '[' and 'A' individually. * * @todo * * improve coverage and/or find some suitable implemnation (ideally one file, * not ncurses) * * the function can be moved to separate util file */ static int64_t get_ansi_code() { int64_t c = GETCH(); debug_msg(MOD_NAME "Pressed %" PRId64 "\n", c); if (c == '[') { // CSI c = '\033' << 8 | '['; while (true) { if ((c >> 56u) != 0) { LOG(LOG_LEVEL_WARNING) << MOD_NAME "Long control sequence detected!\n"; return INT64_MIN; } c <<= 8; int tmp = GETCH(); assert(tmp < 0x80); // ANSI esc seq should use only 7-bit encoding debug_msg(MOD_NAME "Pressed %d\n", tmp); CHECK_EOF(tmp); c |= tmp; if (tmp >= 0x40 && tmp <= 0xee) { // final byte break; } } } else if (c == 'N' // is this even used? || c == 'O') { // eg. \EOP - F1-F4 (the rest of Fn is CSI) int tmp = GETCH(); debug_msg(MOD_NAME "Pressed %d\n", tmp); CHECK_EOF(tmp); c = '\033' << 16 | c << 8 | tmp; } else if (c >= 'a' && c <= 'z') { return K_ALT(c); } else { LOG(LOG_LEVEL_WARNING) << MOD_NAME "Unknown control sequence!\n"; return INT64_MIN; } return c; } #ifndef WIN32 /* * Input must be 0x80-0xff */ static int count_utf8_bytes(unsigned int i) { int count = 0; while ((i & 0x80) != 0u) { count++; i <<= 1; } return count; } static int64_t get_utf8_code(int c) { if (c < 0xc0) { LOG(LOG_LEVEL_WARNING) << MOD_NAME "Wrong UTF sequence!\n"; return INT64_MIN; } int ones = count_utf8_bytes(c); for (int i = 1; i < ones; ++i) { c = (c & 0x7fffff) << 8; int tmp = GETCH(); debug_msg(MOD_NAME "Pressed %d\n", tmp); CHECK_EOF(tmp); c |= tmp; } if (ones > 7) { LOG(LOG_LEVEL_WARNING) << MOD_NAME "Unsupported UTF sequence length!\n"; return INT64_MIN; } return c; } #endif #ifdef WIN32 static int64_t convert_win_to_ansi_keycode(int c) { switch (c) { case 0xe048: return K_UP; case 0xe04b: return K_LEFT; case 0xe04d: return K_RIGHT; case 0xe050: return K_DOWN; default: LOG(LOG_LEVEL_WARNING) << MOD_NAME "Cannot map Win keycode 0x" << hex << setfill('0') << setw(2) << c << setw(0) << dec << " to ANSI.\n"; return -1; } } #endif static bool is_utf8(int64_t ch) { #ifdef WIN32 return false; #endif if (ch <= 0) { return false; } unsigned char first_byte = 0U; while (ch != 0) { first_byte = ch & 0xff; ch >>= 8; } return first_byte & 0x80; /// @todo UTF-8 validity check would be nice } static string get_utf8_representation(int64_t ch) { char str[9] = ""; while (ch != 0) { memmove(str + 1, str, 8); str[0] = ch & 0xff; ch >>= 8; } return str; } static string get_keycode_representation(int64_t ch) { switch (ch) { case K_UP: return "Up"; case K_DOWN: return "Down"; case K_LEFT: return "Left"; case K_RIGHT: return "Right"; case K_PGUP: return "Page-Up"; case K_PGDOWN: return "Page-Dn"; case K_CTRL_UP: return "Ctrl-Up"; case K_CTRL_DOWN: return "Ctrl-Dn"; } if (ch >= 1 && ch <= 'z' - 'a' + 1) { return string("Ctrl-") + string(1, 'a' + ch - 1); } if (ch >> 16 == 0 && ch >> 8 == '\033') { return string("Alt-") + string(1, ch & 0xff); } if (ch <= UCHAR_MAX && isprint(ch)) { return string("'") + string(1, ch) + "'"; } if (is_utf8(ch)) { return string("'") + get_utf8_representation(ch) + "'"; } stringstream oss; oss << "0x" << hex << ch; return oss.str(); } /** * @returns next key either from keyboard or received via control socket. * If m_should_exit is set, returns 0. */ int64_t keyboard_control::get_next_key() { int64_t c; while (1) { { unique_lock lk(m_lock); if (m_should_exit) { return 0; } if (!m_pressed_keys.empty()) { c = m_pressed_keys.front(); m_pressed_keys.pop(); return c; } } #ifdef HAVE_TERMIOS_H fd_set set; FD_ZERO(&set); FD_SET(0, &set); FD_SET(m_event_pipe[0], &set); select(m_event_pipe[0] + 1, &set, NULL, NULL, NULL); if (FD_ISSET(m_event_pipe[0], &set)) { char c; int bytes = read(m_event_pipe[0], &c, 1); assert(bytes == 1); continue; // skip to event processing } if (FD_ISSET(0, &set)) { #else if (kbhit()) { #endif int64_t c = GETCH(); debug_msg(MOD_NAME "Pressed %" PRId64 "\n", c); if (c == '\033') { if ((c = get_ansi_code()) < 0) { continue; } #ifdef WIN32 } else if (c == 0x0 || c == 0xe0) { // Win keycodes int tmp = GETCH(); debug_msg(MOD_NAME "Pressed %d\n", tmp); CHECK_EOF(tmp); if ((c = convert_win_to_ansi_keycode(c << 8 | tmp)) == -1) { continue; } #else } else if (c > 0x80) { if ((c = get_utf8_code(c)) < 0) { continue; } #endif } return c; } #if !defined HAVE_TERMIOS_H // kbhit is non-blockinkg - we don't want to busy-wait unique_lock lk(m_lock); m_cv.wait_for(lk, milliseconds(200)); lk.unlock(); #endif } } void keyboard_control::run() { set_thread_name("keyboard-control"); int saved_log_level = -1; int64_t c; while ((c = get_next_key())) { if (c == EOF) { if (feof(stdin)) { LOG(LOG_LEVEL_WARNING) << MOD_NAME "EOF detected! Exiting keyboard control.\n"; break; } if (ferror(stdin)) { LOG(LOG_LEVEL_WARNING) << MOD_NAME "Error detected!\n"; clearerr(stdin); continue; } } if (c == K_CTRL('X')) { m_locked_against_changes = !m_locked_against_changes; // ctrl-x pressed col() << TGREEN("Keyboard control: " << (m_locked_against_changes ? "" : "un") << "locked against changes\n"); continue; } if (m_locked_against_changes && guarded_keys.find(c) != guarded_keys.end()) { col() << TGREEN("Keyboard control: locked against changes, press 'Ctrl-x' to unlock or 'h' for help.\n"); continue; } m_lock.lock(); if (key_mapping.find(c) != key_mapping.end()) { // user defined mapping exists string cmd = key_mapping.at(c).first; exec_external_commands(cmd.c_str()); m_lock.unlock(); continue; } m_lock.unlock(); switch (c) { case '*': case '/': case '9': case '0': case 'm': { char path[] = "audio.receiver"; auto m = (struct msg_receiver *) new_message(sizeof(struct msg_receiver)); switch (c) { case '0': case '*': m->type = RECEIVER_MSG_INCREASE_VOLUME; break; case '9': case '/': m->type = RECEIVER_MSG_DECREASE_VOLUME; break; case 'm': m->type = RECEIVER_MSG_MUTE; break; } auto resp = send_message(m_root, path, (struct message *) m); free_response(resp); break; } case 'M': { char path[] = "audio.sender"; auto m = (struct msg_sender *) new_message(sizeof(struct msg_sender)); m->type = SENDER_MSG_MUTE; auto resp = send_message(m_root, path, (struct message *) m); free_response(resp); break; } case '>': case '<': { int audio_delay = get_audio_delay(); audio_delay += c == '>' ? 10 : -10; log_msg(LOG_LEVEL_INFO, "New audio delay: %d ms.\n", audio_delay); set_audio_delay(audio_delay); break; } case 'e': { char path[] = "exporter"; auto m = (struct message *) new_message(sizeof(struct msg_universal)); strcpy(((struct msg_universal *) m)->text, "toggle"); auto resp = send_message(m_root, path, (struct message *) m); free_response(resp); break; } case 'c': case 'C': read_command(c == 'C'); break; case 'v': case 'V': { if (islower(c)) { log_level = (log_level + 1) % (LOG_LEVEL_MAX + 1); } else { log_level = (log_level - 1 + (LOG_LEVEL_MAX + 1)) % (LOG_LEVEL_MAX + 1); } cout << "Log level: " << log_level << "\n"; break; } case 'r': { bool skip = !get_log_output().get_skip_repeats(); get_log_output().set_skip_repeats(skip); cout << "Skip repeated messages: " << std::boolalpha << skip << std::noboolalpha << "\n"; break; } case 's': if (saved_log_level == -1) { saved_log_level = log_level; col() << TGREEN("Output suspended, press 'q' to continue.\n"); log_level = LOG_LEVEL_QUIET; } break; case 'S': if (saved_log_level != -1) { log_level = saved_log_level; saved_log_level = -1; col() << TGREEN( "Output resumed.\n"); } break; case 'h': usage(); break; case 'i': cout << "\n"; info(); break; case '\n': case '\r': cout << endl; break; default: LOG(LOG_LEVEL_WARNING) << MOD_NAME "Unsupported key " << get_keycode_representation(c) << " pressed. Press 'h' to help.\n"; } } } void keyboard_control::info() { col() << TBOLD("UltraGrid version: ") << get_version_details() << "\n"; col() << TBOLD("Start time: ") << asctime(localtime(&m_start_time)); col() << TBOLD("Verbosity level: ") << log_level << (log_level == LOG_LEVEL_INFO ? " (default)" : "") << "\n"; col() << TBOLD("Locked against changes: ") << (m_locked_against_changes ? "true" : "false") << "\n"; col() << TBOLD("Audio playback delay: ") << get_audio_delay() << " ms\n"; { int muted_sender = -1; int muted_receiver = -1; const char *path = "audio.receiver"; auto *m_recv = reinterpret_cast(new_message(sizeof(struct msg_receiver))); m_recv->type = RECEIVER_MSG_GET_AUDIO_STATUS; auto *resp = send_message_sync(m_root, path, reinterpret_cast(m_recv), 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE); if (response_get_status(resp) == 200) { double vol = 0; sscanf(response_get_text(resp), "%lf,%d", &vol, &muted_receiver); double db = 20.0 * log10(vol); color_printf(TBOLD("Playback volume: ") "%.2f%% (%s%.2f dB)\n", vol * 100.0, (db >= 0.0 ? "+" : ""), db); } free_response(resp); path = "audio.sender"; auto *m_send = reinterpret_cast(new_message(sizeof(struct msg_sender))); m_send->type = SENDER_MSG_GET_STATUS; resp = send_message_sync(m_root, path, reinterpret_cast(m_send), 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE); if (response_get_status(resp) == 200) { sscanf(response_get_text(resp), "%d", &muted_sender); } free_response(resp); auto muted_to_string = [](int val) { switch (val) { case 0: return "false"; case 1: return "true"; default: return "(unknown)"; } }; col() << TBOLD("Audio muted - sender: ") << muted_to_string(muted_sender) << ", " << TBOLD("receiver: ") << muted_to_string(muted_receiver) << "\n"; } { struct video_desc desc{}; struct msg_sender *m = (struct msg_sender *) new_message(sizeof(struct msg_sender)); m->type = SENDER_MSG_QUERY_VIDEO_MODE; struct response *r = send_message_sync(m_root, "sender", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE); if (response_get_status(r) == RESPONSE_OK) { const char *text = response_get_text(r); istringstream iss(text); iss >> desc; col() << TBOLD("Captured video format: ") << desc << "\n"; } free_response(r); } { struct video_desc desc{}; struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal)); strcpy(m->text, "get_format"); struct response *r = send_message_sync(m_root, "receiver.decoder", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE); if (response_get_status(r) == RESPONSE_OK) { const char *text = response_get_text(r); istringstream iss(text); iss >> desc; col() << TBOLD("Received video format: ") << desc << "\n"; } free_response(r); } { struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal)); strcpy(m->text, "get_port"); struct response *r = send_message_sync(m_root, "control", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE); if (response_get_status(r) == RESPONSE_OK) { col() << TBOLD("Control port: ") << response_get_text(r) << "\n"; } free_response(r); } { struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal)); strcpy(m->text, "status"); struct response *r = send_message_sync(m_root, "exporter", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE); if (response_get_status(r) == RESPONSE_OK) { col() << TBOLD("Exporting: ") << response_get_text(r) << "\n"; } free_response(r); } cout << "\n"; } #define G(key) (guarded_keys.find(key) != guarded_keys.end() ? " [g]" : "") void keyboard_control::usage() { col() << "\nAvailable keybindings:\n" << TBOLD("\t * 0 ") << "- increase volume" << G('*') << "\n" << TBOLD("\t / 9 ") << "- decrease volume" << G('/') << "\n" << TBOLD("\t > ") << "- increase audio delay by 10 ms" << G('>') << "\n" << TBOLD("\t < ") << "- decrease audio delay by 10 ms" << G('<') << "\n" << TBOLD("\t m ") << "- mute/unmute receiver" << G('m') << "\n" << TBOLD("\t M ") << "- mute/unmute sender" << G('M') << "\n" << TBOLD("\t v ") << "- increase verbosity level" << G('v') << "\n" << TBOLD("\t V ") << "- decrease verbosity level" << G('V') << "\n" << TBOLD("\t r ") << "- skip repeated messages (toggle) " << G('r') << "\n" << TBOLD("\t e ") << "- record captured content (toggle)" << G('e') << "\n" << TBOLD("\t h ") << "- show help" << G('h') << "\n" << TBOLD("\t i ") << "- show various information" << G('i') << "\n" << TBOLD("\t s S ") << "- suspend/resume output" << G('s') << G('S') << "\n" << TBOLD("\t c C ") << "- execute command through control socket (capital for multiple)" << G('c') << G('C') << "\n" << TBOLD("\tCtrl-x ") << "- unlock/lock against changes" << G(K_CTRL('X')) << "\n" << TBOLD("\tCtrl-c ") << "- exit " << G(K_CTRL('c')) << "\n" << "\n"; if (key_mapping.size() > 0) { cout << "Custom keybindings:\n"; for (auto const &it : key_mapping) { 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"; } cout << "\n"; } col() << TBOLD("[g]") << " indicates that that option is guarded (Ctrl-x needs to be pressed first)\n"; } void keyboard_control::load_config_map() { FILE *in = fopen(CONFIG_FILE, "r"); if (!in) { return; } char buf[1024]; while (fgets(buf, sizeof buf - 1, in)) { if (strlen(buf) > 0 && buf[strlen(buf) - 1] == '\n') { // trim NL buf[strlen(buf) - 1] = '\0'; } if (strlen(buf) == 0) { continue; } if (!exec_local_command(buf)) { LOG(LOG_LEVEL_ERROR) << "Wrong config: " << buf << "\n"; continue; } } fclose(in); } bool keyboard_control::exec_local_command(const char *command) { if (strcmp(command, "localhelp") == 0) { printf("Local help:\n" "\tmap cmd1[;cmd2..][#name]\n" "\t\tmaps key to command(s) for control socket (with an optional\n\t\tidentifier)\n" "\tmap # cmd1[;cmd2..][#name]\n" "\t\tmaps key number (ASCII or multi-byte for non-ASCII) to\n\t\tcommand(s) for control socket (with an optional identifier)\n" "\tunmap \n" "\tpress \n" "\t\tEmulate pressing key \n\n" "Mappings can be stored in " CONFIG_FILE "\n\n"); return true; } else if (strstr(command, "map ") == command) { char *ccpy = strdup(command + strlen("map ")); if (strlen(ccpy) > 3) { int64_t key = ccpy[0]; char *command = ccpy + 2; if (key == '#' && isdigit(ccpy[1])) { key = strtoll(ccpy + 1, &command, 10); command += 1; // skip ' ' } const char *name = ""; if (strchr(command, '#')) { name = strchr(command, '#') + 1; *strchr(command, '#') = '\0'; } if (key_mapping.find(key) == key_mapping.end()) { key_mapping.insert({key, make_pair(command, name)}); } else { LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Trying to register key shortcut " << get_keycode_representation(key) << ", which is already regestered, ignoring.\n"; } } free(ccpy); return true; } else if (strstr(command, "unmap ") == command) { command += strlen("unmap "); if (strlen(command) >= 1) { key_mapping.erase(command[0]); } return true; } else if (strstr(command, "press ") == command) { const char *key = command + strlen("press "); int64_t k; sscanf(key, "%" SCNi64, &k); m_pressed_keys.push(k); signal(); return true; } return false; } void keyboard_control::exec_external_commands(const char *commands) { char *cpy = strdup(commands); char *item, *save_ptr, *tmp = cpy; while ((item = strtok_r(tmp, ";", &save_ptr))) { struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal)); strcpy(m->text, "execute "); strncat(m->text, item, sizeof m->text - strlen(m->text) - 1); struct response *r = send_message_sync(m_root, "control", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE); if (response_get_status(r) != RESPONSE_OK) { LOG(LOG_LEVEL_ERROR) << "Could not send a message!\n"; } free_response(r); tmp = nullptr; } free(cpy); } void keyboard_control::execute_command(const char *command) { if (!exec_local_command(command)) { exec_external_commands(command); } } void keyboard_control::read_command(bool multiple) { int saved_log_level = log_level; log_level = LOG_LEVEL_QUIET; restore_old_tio(); if (multiple) { printf("Enter commands for control (try \"help\" or \"localhelp\").\n"); printf("Exit the mode with blank line.\n"); } else { printf("Enter a command for control (try \"help\" or \"localhelp\"):\n"); } while (1) { log_level = LOG_LEVEL_QUIET; printf("control> "); char buf[sizeof msg_universal::text]; if (!fgets(buf, sizeof buf - 1, stdin)) { log_level = saved_log_level; break; } log_level = saved_log_level; if (strlen(buf) == 0 || (strlen(buf) == 1 && buf[0] == '\n')) { // empty input break; } if (buf[strlen(buf) - 1] == '\n') { // strip newline buf[strlen(buf) - 1] = '\0'; } m_lock.lock(); execute_command(buf); m_lock.unlock(); if (!multiple) { break; } } set_tio(); } /** * From http://shtrom.ssji.net/skb/getc.html */ static bool set_tio() { #ifdef HAVE_TERMIOS_H struct termios new_tio = old_tio; /* disable canonical mode (buffered i/o) and local echo */ new_tio.c_lflag &=(~ICANON & ~ECHO); // Wrap calling of tcsetattr() by handling SIGTTOU. SIGTTOU can be raised if task is // run in background and trying to call tcsetattr(). If so, we disable keyboard // control. struct sigaction sa, sa_old; memset(&sa, 0, sizeof sa); sa.sa_handler = catch_signal; sigemptyset(&sa.sa_mask); sigaction(SIGTTOU, &sa, &sa_old); /* set the new settings immediately */ tcsetattr(STDIN_FILENO,TCSANOW,&new_tio); sigaction(SIGTTOU, &sa_old, NULL); if (!signal_catched) { return true; } #endif return false; } void keyboard_control::msg_received_func(struct module *m) { auto s = static_cast(m->priv_data); s->msg_received(); } void keyboard_control::msg_received() { struct message *msg; while ((msg = check_message(&m_mod))) { auto m = reinterpret_cast(msg); lock_guard lk(m_lock); execute_command(m->text); struct response *r = new_response(RESPONSE_OK, nullptr); free_message((struct message *) m, r); } } void keycontrol_send_key(struct module *root, int64_t key) { struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal)); sprintf(m->text, "press %" PRId64, key); struct response *r = send_message_sync(root, "keycontrol", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE); if (response_get_status(r) != RESPONSE_OK) { log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot set key to keycontrol (error %d)!\n", response_get_status(r)); } free_response(r); } /** @brief Registers callback message for given key * * @ref message_universal message type is always sent * @param[in] sender_mod module that should receive the message for key * @param[in] message text that will be passed back to receiver_mod if key was pressed * @param[in] description optional decription that will be displayed in keyboard control help (may be NULL) */ bool keycontrol_register_key(struct module *receiver_mod, int64_t key, const char *message, const char *description) { assert(strchr(message, '#') == nullptr && (description == nullptr || strchr(description, '#') == nullptr)); char receiver_path[1024]; if (!module_get_path_str(receiver_mod, receiver_path, sizeof receiver_path)) { log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot format format path for sender!\n"); return false; } struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal)); if (description == nullptr) { description = message; } sprintf(m->text, "map #%" PRId64 " %s %s#%s", key, receiver_path, message, description); 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); if (response_get_status(r) != RESPONSE_OK) { log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot register keyboard control (error %d)!\n", response_get_status(r)); free_response(r); return false; } free_response(r); return true; } void get_keycode_name(int64_t ch, char *buf, size_t buflen) { if (buflen == 0) { return; } string name = get_keycode_representation(ch); strncpy(buf, name.c_str(), buflen - 1); buf[buflen - 1] = '\0'; }