Major code refactoring and various fixes - all as of yet untested\!

git-svn-id: https://logkeys.googlecode.com/svn/trunk@37 c501e62c-e7d1-11de-a198-37193048d1ed
This commit is contained in:
kernc 2010-04-22 02:06:49 +00:00
parent 82dc8c1a23
commit 3c1f5c2c70
10 changed files with 472 additions and 538 deletions

29
INSTALL
View File

@ -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.

View File

@ -1,3 +1,3 @@
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

View File

@ -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

29
README
View File

@ -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.

8
TODO
View File

@ -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).

View File

@ -3,6 +3,9 @@
/* Define to 1 if you have the `atoi' function. */
#undef HAVE_ATOI
/* Define to 1 if you have the <cassert> header file. */
#undef HAVE_CASSERT
/* Define to 1 if you have the <cerrno> header file. */
#undef HAVE_CERRNO
@ -21,6 +24,18 @@
/* Define to 1 if you have the <cstring> 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 <error.h> 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 <getopt.h> 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 <inttypes.h> 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

61
configure vendored
View File

@ -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

View File

@ -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])

90
src/keytables.cc Normal file
View File

@ -0,0 +1,90 @@
#ifndef _DEFAULT_KEYS_H_
#define _DEFAULT_KEYS_H_
#include <cassert>
#include <linux/input.h>
// 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] = {
"<Esc>", "<BckSp>", "<Tab>", "<Enter>", "<LCtrl>", "<LShft>", "<RShft>", "<KP*>", "<LAlt>", " ", "<CpsLk>", "<F1>", "<F2>", "<F3>", "<F4>", "<F5>",
"<F6>", "<F7>", "<F8>", "<F9>", "<F10>", "<NumLk>", "<ScrLk>", "<KP7>", "<KP8>", "<KP9>", "<KP->", "<KP4>", "<KP5>", "<KP6>", "<KP+>", "<KP1>",
"<KP2>", "<KP3>", "<KP0>", "<KP.>", /*"<",*/ "<F11>", "<F12>", "<KPEnt>", "<RCtrl>", "<KP/>", "<PrtSc>", "<AltGr>", "<Break>" /*linefeed?*/, "<Home>", "<Up>", "<PgUp>",
"<Left>", "<Right>", "<End>", "<Down>", "<PgDn>", "<Ins>", "<Del>", "<Pause>", "<LMeta>", "<RMeta>", "<Menu>"
};
const char char_or_func[] = // c = character key, f = function key, _ = blank/error ('_' is used, don't change); all according to KEY_* defines from <linux/input.h>
"_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

View File

@ -12,48 +12,42 @@
#include <sstream>
#include <cstdlib>
#include <csignal>
#include <error.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <linux/input.h>
#include "keytables.cc" // character and function key tables and helper functions
#ifdef HAVE_CONFIG_H
# include <config.h> // 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 <config.h>
#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] = {
"<Esc>", "<BckSp>", "<Tab>", "<Enter>", "<LCtrl>", "<LShft>", "<RShft>", "<KP*>", "<LAlt>", " ", "<CpsLk>", "<F1>", "<F2>", "<F3>", "<F4>", "<F5>",
"<F6>", "<F7>", "<F8>", "<F9>", "<F10>", "<NumLk>", "<ScrLk>", "<KP7>", "<KP8>", "<KP9>", "<KP->", "<KP4>", "<KP5>", "<KP6>", "<KP+>", "<KP1>",
"<KP2>", "<KP3>", "<KP0>", "<KP.>", /*"<",*/ "<F11>", "<F12>", "<KPEnt>", "<RCtrl>", "<KP/>", "<PrtSc>", "<AltGr>", "<Break>" /*linefeed?*/, "<Home>", "<Up>", "<PgUp>",
"<Left>", "<Right>", "<End>", "<Down>", "<PgDn>", "<Ins>", "<Del>", "<Pause>", "<LMeta>", "<RMeta>", "<Menu>"
};
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 <linux/input.h>
"_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. <LShift>, 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. <LShift>, 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<wchar_t>(utf8code);
char_keys[index] = static_cast<wchar_t>(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<wchar_t>(utf8code);
shift_keys[index] = static_cast<wchar_t>(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<wchar_t>(utf8code);
altgr_keys[index] = static_cast<wchar_t>(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<wchar_t>(utf8code);
if (line[1] == 's') // if line starts with "shift"
shift_keys[index] = static_cast<wchar_t>(utf8code);
if (line[1] == 'a') // if line starts with "altgr"
altgr_keys[index] = static_cast<wchar_t>(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<wchar_t>(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<char*>(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<char*>((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 <linux/input.h>)
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 "<x%d>"
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 <linux/input.h> ?
#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, "<E-%x>", 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 "<Enter>"
}
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, "<E-%x>", 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, "<E-%x>", 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 "<Enter>"
}
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, "<E-%x>", 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);
}