blob: cd53501e7abac72c1dfd45508f908aaa89719238 [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by the GNU General Public License v2.
//
// Partially adapted from evtest:
// http://cgit.freedesktop.org/evtest
//
// Copyright (c) 1999-2000 Vojtech Pavlik
// Copyright (c) 2009-2011 Red Hat, Inc
#define _GNU_SOURCE // for asprintf
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <linux/input.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <X11/keysym.h>
#include <X11/Xlib.h>
#define BITS_PER_LONG (sizeof(long) * 8)
#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
#define OFF(x) ((x)%BITS_PER_LONG)
#define BIT(x) (1UL<<OFF(x))
#define LONG(x) ((x)/BITS_PER_LONG)
#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define LONG_BITS (sizeof(long) * 8)
#define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS)
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define DEV_INPUT_EVENT "/dev/input"
#define EVENT_DEV_NAME "event"
#ifndef EV_SYN
#define EV_SYN 0
#endif
#ifndef SYN_MT_REPORT
#define SYN_MT_REPORT 2
#endif
#ifndef ABS_MT_SLOT
#define ABS_MT_SLOT 47
#endif
#define MAX_SLOT_COUNT 64
/* Set clockid to be used for timestamps */
#ifndef EVIOCSCLOCKID
#define EVIOCSCLOCKID _IOW('E', 0xa0, int)
#endif
#define NAME_ELEMENT(element) [element] = #element
// Define the access permission of the screenshot output directory.
#define OUTPUT_DIR_MODE (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)
#define DEFAULT_OUTPUT_DIR "/tmp"
static const char * const events[EV_MAX + 1] = {
[0 ... EV_MAX] = NULL,
NAME_ELEMENT(EV_SYN), NAME_ELEMENT(EV_KEY), NAME_ELEMENT(EV_REL),
NAME_ELEMENT(EV_ABS), NAME_ELEMENT(EV_MSC), NAME_ELEMENT(EV_LED),
NAME_ELEMENT(EV_SND), NAME_ELEMENT(EV_REP), NAME_ELEMENT(EV_FF),
NAME_ELEMENT(EV_PWR), NAME_ELEMENT(EV_FF_STATUS), NAME_ELEMENT(EV_SW),
};
static const char * const keys[KEY_MAX + 1] = {
[0 ... KEY_MAX] = NULL,
NAME_ELEMENT(KEY_RESERVED), NAME_ELEMENT(KEY_ESC),
NAME_ELEMENT(KEY_1), NAME_ELEMENT(KEY_2), NAME_ELEMENT(KEY_3),
NAME_ELEMENT(KEY_4), NAME_ELEMENT(KEY_5), NAME_ELEMENT(KEY_6),
NAME_ELEMENT(KEY_7), NAME_ELEMENT(KEY_8), NAME_ELEMENT(KEY_9),
NAME_ELEMENT(KEY_0), NAME_ELEMENT(KEY_MINUS), NAME_ELEMENT(KEY_EQUAL),
NAME_ELEMENT(KEY_BACKSPACE), NAME_ELEMENT(KEY_TAB), NAME_ELEMENT(KEY_Q),
NAME_ELEMENT(KEY_W), NAME_ELEMENT(KEY_E), NAME_ELEMENT(KEY_R),
NAME_ELEMENT(KEY_T), NAME_ELEMENT(KEY_Y), NAME_ELEMENT(KEY_U),
NAME_ELEMENT(KEY_I), NAME_ELEMENT(KEY_O), NAME_ELEMENT(KEY_P),
NAME_ELEMENT(KEY_LEFTBRACE), NAME_ELEMENT(KEY_RIGHTBRACE),
NAME_ELEMENT(KEY_ENTER), NAME_ELEMENT(KEY_LEFTCTRL), NAME_ELEMENT(KEY_A),
NAME_ELEMENT(KEY_S), NAME_ELEMENT(KEY_D), NAME_ELEMENT(KEY_F),
NAME_ELEMENT(KEY_G), NAME_ELEMENT(KEY_H), NAME_ELEMENT(KEY_J),
NAME_ELEMENT(KEY_K), NAME_ELEMENT(KEY_L), NAME_ELEMENT(KEY_SEMICOLON),
NAME_ELEMENT(KEY_APOSTROPHE), NAME_ELEMENT(KEY_GRAVE),
NAME_ELEMENT(KEY_LEFTSHIFT), NAME_ELEMENT(KEY_BACKSLASH),
NAME_ELEMENT(KEY_Z), NAME_ELEMENT(KEY_X), NAME_ELEMENT(KEY_C),
NAME_ELEMENT(KEY_V), NAME_ELEMENT(KEY_B), NAME_ELEMENT(KEY_N),
NAME_ELEMENT(KEY_M), NAME_ELEMENT(KEY_COMMA), NAME_ELEMENT(KEY_DOT),
NAME_ELEMENT(KEY_SLASH), NAME_ELEMENT(KEY_RIGHTSHIFT),
NAME_ELEMENT(KEY_KPASTERISK), NAME_ELEMENT(KEY_LEFTALT),
NAME_ELEMENT(KEY_SPACE), NAME_ELEMENT(KEY_CAPSLOCK), NAME_ELEMENT(KEY_F1),
NAME_ELEMENT(KEY_F2), NAME_ELEMENT(KEY_F3), NAME_ELEMENT(KEY_F4),
NAME_ELEMENT(KEY_F5), NAME_ELEMENT(KEY_F6), NAME_ELEMENT(KEY_F7),
NAME_ELEMENT(KEY_F8), NAME_ELEMENT(KEY_F9), NAME_ELEMENT(KEY_F10),
NAME_ELEMENT(KEY_NUMLOCK), NAME_ELEMENT(KEY_SCROLLLOCK),
NAME_ELEMENT(KEY_KP7), NAME_ELEMENT(KEY_KP8), NAME_ELEMENT(KEY_KP9),
NAME_ELEMENT(KEY_KPMINUS), NAME_ELEMENT(KEY_KP4), NAME_ELEMENT(KEY_KP5),
NAME_ELEMENT(KEY_KP6), NAME_ELEMENT(KEY_KPPLUS), NAME_ELEMENT(KEY_KP1),
NAME_ELEMENT(KEY_KP2), NAME_ELEMENT(KEY_KP3), NAME_ELEMENT(KEY_KP0),
NAME_ELEMENT(KEY_KPDOT), NAME_ELEMENT(KEY_ZENKAKUHANKAKU),
NAME_ELEMENT(KEY_102ND), NAME_ELEMENT(KEY_F11), NAME_ELEMENT(KEY_F12),
NAME_ELEMENT(KEY_RO), NAME_ELEMENT(KEY_KATAKANA),
NAME_ELEMENT(KEY_HIRAGANA), NAME_ELEMENT(KEY_HENKAN),
NAME_ELEMENT(KEY_KATAKANAHIRAGANA), NAME_ELEMENT(KEY_MUHENKAN),
NAME_ELEMENT(KEY_KPJPCOMMA), NAME_ELEMENT(KEY_KPENTER),
NAME_ELEMENT(KEY_RIGHTCTRL), NAME_ELEMENT(KEY_KPSLASH),
NAME_ELEMENT(KEY_SYSRQ), NAME_ELEMENT(KEY_RIGHTALT),
NAME_ELEMENT(KEY_LINEFEED), NAME_ELEMENT(KEY_HOME), NAME_ELEMENT(KEY_UP),
NAME_ELEMENT(KEY_PAGEUP), NAME_ELEMENT(KEY_LEFT), NAME_ELEMENT(KEY_RIGHT),
NAME_ELEMENT(KEY_END), NAME_ELEMENT(KEY_DOWN), NAME_ELEMENT(KEY_PAGEDOWN),
NAME_ELEMENT(KEY_INSERT), NAME_ELEMENT(KEY_DELETE),
NAME_ELEMENT(KEY_MACRO), NAME_ELEMENT(KEY_MUTE),
NAME_ELEMENT(KEY_VOLUMEDOWN), NAME_ELEMENT(KEY_VOLUMEUP),
NAME_ELEMENT(KEY_POWER), NAME_ELEMENT(KEY_KPEQUAL),
NAME_ELEMENT(KEY_KPPLUSMINUS), NAME_ELEMENT(KEY_PAUSE),
NAME_ELEMENT(KEY_KPCOMMA), NAME_ELEMENT(KEY_HANGUEL),
NAME_ELEMENT(KEY_HANJA), NAME_ELEMENT(KEY_YEN),
NAME_ELEMENT(KEY_LEFTMETA), NAME_ELEMENT(KEY_RIGHTMETA),
NAME_ELEMENT(KEY_COMPOSE), NAME_ELEMENT(KEY_STOP),
NAME_ELEMENT(KEY_AGAIN), NAME_ELEMENT(KEY_PROPS), NAME_ELEMENT(KEY_UNDO),
NAME_ELEMENT(KEY_FRONT), NAME_ELEMENT(KEY_COPY), NAME_ELEMENT(KEY_OPEN),
NAME_ELEMENT(KEY_PASTE), NAME_ELEMENT(KEY_FIND), NAME_ELEMENT(KEY_CUT),
NAME_ELEMENT(KEY_HELP), NAME_ELEMENT(KEY_MENU), NAME_ELEMENT(KEY_CALC),
NAME_ELEMENT(KEY_SETUP), NAME_ELEMENT(KEY_SLEEP),
NAME_ELEMENT(KEY_WAKEUP), NAME_ELEMENT(KEY_FILE),
NAME_ELEMENT(KEY_SENDFILE), NAME_ELEMENT(KEY_DELETEFILE),
NAME_ELEMENT(KEY_XFER), NAME_ELEMENT(KEY_PROG1), NAME_ELEMENT(KEY_PROG2),
NAME_ELEMENT(KEY_WWW), NAME_ELEMENT(KEY_MSDOS), NAME_ELEMENT(KEY_COFFEE),
NAME_ELEMENT(KEY_DIRECTION), NAME_ELEMENT(KEY_CYCLEWINDOWS),
NAME_ELEMENT(KEY_MAIL), NAME_ELEMENT(KEY_BOOKMARKS),
NAME_ELEMENT(KEY_COMPUTER), NAME_ELEMENT(KEY_BACK),
NAME_ELEMENT(KEY_FORWARD), NAME_ELEMENT(KEY_CLOSECD),
NAME_ELEMENT(KEY_EJECTCD), NAME_ELEMENT(KEY_EJECTCLOSECD),
NAME_ELEMENT(KEY_NEXTSONG), NAME_ELEMENT(KEY_PLAYPAUSE),
NAME_ELEMENT(KEY_PREVIOUSSONG), NAME_ELEMENT(KEY_STOPCD),
NAME_ELEMENT(KEY_RECORD), NAME_ELEMENT(KEY_REWIND),
NAME_ELEMENT(KEY_PHONE), NAME_ELEMENT(KEY_ISO), NAME_ELEMENT(KEY_CONFIG),
NAME_ELEMENT(KEY_HOMEPAGE), NAME_ELEMENT(KEY_REFRESH),
NAME_ELEMENT(KEY_EXIT), NAME_ELEMENT(KEY_MOVE), NAME_ELEMENT(KEY_EDIT),
NAME_ELEMENT(KEY_SCROLLUP), NAME_ELEMENT(KEY_SCROLLDOWN),
NAME_ELEMENT(KEY_KPLEFTPAREN), NAME_ELEMENT(KEY_KPRIGHTPAREN),
NAME_ELEMENT(KEY_F13), NAME_ELEMENT(KEY_F14), NAME_ELEMENT(KEY_F15),
NAME_ELEMENT(KEY_F16), NAME_ELEMENT(KEY_F17), NAME_ELEMENT(KEY_F18),
NAME_ELEMENT(KEY_F19), NAME_ELEMENT(KEY_F20), NAME_ELEMENT(KEY_F21),
NAME_ELEMENT(KEY_F22), NAME_ELEMENT(KEY_F23), NAME_ELEMENT(KEY_F24),
NAME_ELEMENT(KEY_PLAYCD), NAME_ELEMENT(KEY_PAUSECD),
NAME_ELEMENT(KEY_PROG3), NAME_ELEMENT(KEY_PROG4),
NAME_ELEMENT(KEY_SUSPEND), NAME_ELEMENT(KEY_CLOSE),
NAME_ELEMENT(KEY_PLAY), NAME_ELEMENT(KEY_FASTFORWARD),
NAME_ELEMENT(KEY_BASSBOOST), NAME_ELEMENT(KEY_PRINT),
NAME_ELEMENT(KEY_HP), NAME_ELEMENT(KEY_CAMERA), NAME_ELEMENT(KEY_SOUND),
NAME_ELEMENT(KEY_QUESTION), NAME_ELEMENT(KEY_EMAIL),
NAME_ELEMENT(KEY_CHAT), NAME_ELEMENT(KEY_SEARCH),
NAME_ELEMENT(KEY_CONNECT), NAME_ELEMENT(KEY_FINANCE),
NAME_ELEMENT(KEY_SPORT), NAME_ELEMENT(KEY_SHOP),
NAME_ELEMENT(KEY_ALTERASE), NAME_ELEMENT(KEY_CANCEL),
NAME_ELEMENT(KEY_BRIGHTNESSDOWN), NAME_ELEMENT(KEY_BRIGHTNESSUP),
NAME_ELEMENT(KEY_MEDIA), NAME_ELEMENT(KEY_UNKNOWN), NAME_ELEMENT(KEY_OK),
NAME_ELEMENT(KEY_SELECT), NAME_ELEMENT(KEY_GOTO), NAME_ELEMENT(KEY_CLEAR),
NAME_ELEMENT(KEY_POWER2), NAME_ELEMENT(KEY_OPTION),
NAME_ELEMENT(KEY_INFO), NAME_ELEMENT(KEY_TIME), NAME_ELEMENT(KEY_VENDOR),
NAME_ELEMENT(KEY_ARCHIVE), NAME_ELEMENT(KEY_PROGRAM),
NAME_ELEMENT(KEY_CHANNEL), NAME_ELEMENT(KEY_FAVORITES),
NAME_ELEMENT(KEY_EPG), NAME_ELEMENT(KEY_PVR), NAME_ELEMENT(KEY_MHP),
NAME_ELEMENT(KEY_LANGUAGE), NAME_ELEMENT(KEY_TITLE),
NAME_ELEMENT(KEY_SUBTITLE), NAME_ELEMENT(KEY_ANGLE),
NAME_ELEMENT(KEY_ZOOM), NAME_ELEMENT(KEY_MODE),
NAME_ELEMENT(KEY_KEYBOARD), NAME_ELEMENT(KEY_SCREEN),
NAME_ELEMENT(KEY_PC), NAME_ELEMENT(KEY_TV), NAME_ELEMENT(KEY_TV2),
NAME_ELEMENT(KEY_VCR), NAME_ELEMENT(KEY_VCR2), NAME_ELEMENT(KEY_SAT),
NAME_ELEMENT(KEY_SAT2), NAME_ELEMENT(KEY_CD), NAME_ELEMENT(KEY_TAPE),
NAME_ELEMENT(KEY_RADIO), NAME_ELEMENT(KEY_TUNER),
NAME_ELEMENT(KEY_PLAYER), NAME_ELEMENT(KEY_TEXT), NAME_ELEMENT(KEY_DVD),
NAME_ELEMENT(KEY_AUX), NAME_ELEMENT(KEY_MP3), NAME_ELEMENT(KEY_AUDIO),
NAME_ELEMENT(KEY_VIDEO), NAME_ELEMENT(KEY_DIRECTORY),
NAME_ELEMENT(KEY_LIST), NAME_ELEMENT(KEY_MEMO),
NAME_ELEMENT(KEY_CALENDAR), NAME_ELEMENT(KEY_RED),
NAME_ELEMENT(KEY_GREEN), NAME_ELEMENT(KEY_YELLOW), NAME_ELEMENT(KEY_BLUE),
NAME_ELEMENT(KEY_CHANNELUP), NAME_ELEMENT(KEY_CHANNELDOWN),
NAME_ELEMENT(KEY_FIRST), NAME_ELEMENT(KEY_LAST), NAME_ELEMENT(KEY_AB),
NAME_ELEMENT(KEY_NEXT), NAME_ELEMENT(KEY_RESTART), NAME_ELEMENT(KEY_SLOW),
NAME_ELEMENT(KEY_SHUFFLE), NAME_ELEMENT(KEY_BREAK),
NAME_ELEMENT(KEY_PREVIOUS), NAME_ELEMENT(KEY_DIGITS),
NAME_ELEMENT(KEY_TEEN), NAME_ELEMENT(KEY_TWEN), NAME_ELEMENT(KEY_DEL_EOL),
NAME_ELEMENT(KEY_DEL_EOS), NAME_ELEMENT(KEY_INS_LINE),
NAME_ELEMENT(KEY_DEL_LINE), NAME_ELEMENT(KEY_VIDEOPHONE),
NAME_ELEMENT(KEY_GAMES), NAME_ELEMENT(KEY_ZOOMIN),
NAME_ELEMENT(KEY_ZOOMOUT), NAME_ELEMENT(KEY_ZOOMRESET),
NAME_ELEMENT(KEY_WORDPROCESSOR), NAME_ELEMENT(KEY_EDITOR),
NAME_ELEMENT(KEY_SPREADSHEET), NAME_ELEMENT(KEY_GRAPHICSEDITOR),
NAME_ELEMENT(KEY_PRESENTATION), NAME_ELEMENT(KEY_DATABASE),
NAME_ELEMENT(KEY_NEWS), NAME_ELEMENT(KEY_VOICEMAIL),
NAME_ELEMENT(KEY_ADDRESSBOOK), NAME_ELEMENT(KEY_MESSENGER),
NAME_ELEMENT(KEY_DISPLAYTOGGLE), NAME_ELEMENT(KEY_SPELLCHECK),
NAME_ELEMENT(KEY_LOGOFF), NAME_ELEMENT(KEY_DOLLAR),
NAME_ELEMENT(KEY_EURO), NAME_ELEMENT(KEY_FRAMEBACK),
NAME_ELEMENT(KEY_FRAMEFORWARD), NAME_ELEMENT(KEY_CONTEXT_MENU),
NAME_ELEMENT(KEY_MEDIA_REPEAT), NAME_ELEMENT(KEY_DEL_EOL),
NAME_ELEMENT(KEY_DEL_EOS), NAME_ELEMENT(KEY_INS_LINE),
NAME_ELEMENT(KEY_DEL_LINE), NAME_ELEMENT(KEY_FN),
NAME_ELEMENT(KEY_FN_ESC), NAME_ELEMENT(KEY_FN_F1),
NAME_ELEMENT(KEY_FN_F2), NAME_ELEMENT(KEY_FN_F3), NAME_ELEMENT(KEY_FN_F4),
NAME_ELEMENT(KEY_FN_F5), NAME_ELEMENT(KEY_FN_F6), NAME_ELEMENT(KEY_FN_F7),
NAME_ELEMENT(KEY_FN_F8), NAME_ELEMENT(KEY_FN_F9),
NAME_ELEMENT(KEY_FN_F10), NAME_ELEMENT(KEY_FN_F11),
NAME_ELEMENT(KEY_FN_F12), NAME_ELEMENT(KEY_FN_1), NAME_ELEMENT(KEY_FN_2),
NAME_ELEMENT(KEY_FN_D), NAME_ELEMENT(KEY_FN_E), NAME_ELEMENT(KEY_FN_F),
NAME_ELEMENT(KEY_FN_S), NAME_ELEMENT(KEY_FN_B),
NAME_ELEMENT(KEY_BRL_DOT1), NAME_ELEMENT(KEY_BRL_DOT2),
NAME_ELEMENT(KEY_BRL_DOT3), NAME_ELEMENT(KEY_BRL_DOT4),
NAME_ELEMENT(KEY_BRL_DOT5), NAME_ELEMENT(KEY_BRL_DOT6),
NAME_ELEMENT(KEY_BRL_DOT7), NAME_ELEMENT(KEY_BRL_DOT8),
NAME_ELEMENT(KEY_BRL_DOT9), NAME_ELEMENT(KEY_BRL_DOT10),
NAME_ELEMENT(KEY_NUMERIC_0), NAME_ELEMENT(KEY_NUMERIC_1),
NAME_ELEMENT(KEY_NUMERIC_2), NAME_ELEMENT(KEY_NUMERIC_3),
NAME_ELEMENT(KEY_NUMERIC_4), NAME_ELEMENT(KEY_NUMERIC_5),
NAME_ELEMENT(KEY_NUMERIC_6), NAME_ELEMENT(KEY_NUMERIC_7),
NAME_ELEMENT(KEY_NUMERIC_8), NAME_ELEMENT(KEY_NUMERIC_9),
NAME_ELEMENT(KEY_NUMERIC_STAR), NAME_ELEMENT(KEY_NUMERIC_POUND),
NAME_ELEMENT(KEY_BATTERY), NAME_ELEMENT(KEY_BLUETOOTH),
NAME_ELEMENT(KEY_BRIGHTNESS_CYCLE), NAME_ELEMENT(KEY_BRIGHTNESS_ZERO),
NAME_ELEMENT(KEY_DASHBOARD), NAME_ELEMENT(KEY_DISPLAY_OFF),
NAME_ELEMENT(KEY_DOCUMENTS), NAME_ELEMENT(KEY_FORWARDMAIL),
NAME_ELEMENT(KEY_NEW), NAME_ELEMENT(KEY_KBDILLUMDOWN),
NAME_ELEMENT(KEY_KBDILLUMUP), NAME_ELEMENT(KEY_KBDILLUMTOGGLE),
NAME_ELEMENT(KEY_REDO), NAME_ELEMENT(KEY_REPLY), NAME_ELEMENT(KEY_SAVE),
NAME_ELEMENT(KEY_SCALE), NAME_ELEMENT(KEY_SEND),
NAME_ELEMENT(KEY_SCREENLOCK), NAME_ELEMENT(KEY_SWITCHVIDEOMODE),
NAME_ELEMENT(KEY_UWB), NAME_ELEMENT(KEY_VIDEO_NEXT),
NAME_ELEMENT(KEY_VIDEO_PREV), NAME_ELEMENT(KEY_WIMAX),
NAME_ELEMENT(KEY_WLAN),
#ifdef KEY_RFKILL
NAME_ELEMENT(KEY_RFKILL),
#endif
#ifdef KEY_WPS_BUTTON
NAME_ELEMENT(KEY_WPS_BUTTON),
#endif
#ifdef KEY_TOUCHPAD_TOGGLE
NAME_ELEMENT(KEY_TOUCHPAD_TOGGLE),
NAME_ELEMENT(KEY_TOUCHPAD_ON),
NAME_ELEMENT(KEY_TOUCHPAD_OFF),
#endif
NAME_ELEMENT(BTN_0), NAME_ELEMENT(BTN_1), NAME_ELEMENT(BTN_2),
NAME_ELEMENT(BTN_3), NAME_ELEMENT(BTN_4), NAME_ELEMENT(BTN_5),
NAME_ELEMENT(BTN_6), NAME_ELEMENT(BTN_7), NAME_ELEMENT(BTN_8),
NAME_ELEMENT(BTN_9), NAME_ELEMENT(BTN_LEFT), NAME_ELEMENT(BTN_RIGHT),
NAME_ELEMENT(BTN_MIDDLE), NAME_ELEMENT(BTN_SIDE),
NAME_ELEMENT(BTN_EXTRA), NAME_ELEMENT(BTN_FORWARD),
NAME_ELEMENT(BTN_BACK), NAME_ELEMENT(BTN_TASK), NAME_ELEMENT(BTN_TRIGGER),
NAME_ELEMENT(BTN_THUMB), NAME_ELEMENT(BTN_THUMB2), NAME_ELEMENT(BTN_TOP),
NAME_ELEMENT(BTN_TOP2), NAME_ELEMENT(BTN_PINKIE), NAME_ELEMENT(BTN_BASE),
NAME_ELEMENT(BTN_BASE2), NAME_ELEMENT(BTN_BASE3), NAME_ELEMENT(BTN_BASE4),
NAME_ELEMENT(BTN_BASE5), NAME_ELEMENT(BTN_BASE6), NAME_ELEMENT(BTN_DEAD),
NAME_ELEMENT(BTN_A), NAME_ELEMENT(BTN_B), NAME_ELEMENT(BTN_C),
NAME_ELEMENT(BTN_X), NAME_ELEMENT(BTN_Y), NAME_ELEMENT(BTN_Z),
NAME_ELEMENT(BTN_TL), NAME_ELEMENT(BTN_TR), NAME_ELEMENT(BTN_TL2),
NAME_ELEMENT(BTN_TR2), NAME_ELEMENT(BTN_SELECT), NAME_ELEMENT(BTN_START),
NAME_ELEMENT(BTN_MODE), NAME_ELEMENT(BTN_THUMBL),
NAME_ELEMENT(BTN_THUMBR), NAME_ELEMENT(BTN_TOOL_PEN),
NAME_ELEMENT(BTN_TOOL_RUBBER), NAME_ELEMENT(BTN_TOOL_BRUSH),
NAME_ELEMENT(BTN_TOOL_PENCIL), NAME_ELEMENT(BTN_TOOL_AIRBRUSH),
NAME_ELEMENT(BTN_TOOL_FINGER), NAME_ELEMENT(BTN_TOOL_MOUSE),
NAME_ELEMENT(BTN_TOOL_LENS), NAME_ELEMENT(BTN_TOUCH),
NAME_ELEMENT(BTN_STYLUS), NAME_ELEMENT(BTN_STYLUS2),
NAME_ELEMENT(BTN_TOOL_DOUBLETAP), NAME_ELEMENT(BTN_TOOL_TRIPLETAP),
NAME_ELEMENT(BTN_TOOL_QUADTAP), NAME_ELEMENT(BTN_GEAR_DOWN),
NAME_ELEMENT(BTN_GEAR_UP),
#ifdef BTN_TRIGGER_HAPPY
NAME_ELEMENT(BTN_TRIGGER_HAPPY1), NAME_ELEMENT(BTN_TRIGGER_HAPPY11),
NAME_ELEMENT(BTN_TRIGGER_HAPPY2), NAME_ELEMENT(BTN_TRIGGER_HAPPY12),
NAME_ELEMENT(BTN_TRIGGER_HAPPY3), NAME_ELEMENT(BTN_TRIGGER_HAPPY13),
NAME_ELEMENT(BTN_TRIGGER_HAPPY4), NAME_ELEMENT(BTN_TRIGGER_HAPPY14),
NAME_ELEMENT(BTN_TRIGGER_HAPPY5), NAME_ELEMENT(BTN_TRIGGER_HAPPY15),
NAME_ELEMENT(BTN_TRIGGER_HAPPY6), NAME_ELEMENT(BTN_TRIGGER_HAPPY16),
NAME_ELEMENT(BTN_TRIGGER_HAPPY7), NAME_ELEMENT(BTN_TRIGGER_HAPPY17),
NAME_ELEMENT(BTN_TRIGGER_HAPPY8), NAME_ELEMENT(BTN_TRIGGER_HAPPY18),
NAME_ELEMENT(BTN_TRIGGER_HAPPY9), NAME_ELEMENT(BTN_TRIGGER_HAPPY19),
NAME_ELEMENT(BTN_TRIGGER_HAPPY10), NAME_ELEMENT(BTN_TRIGGER_HAPPY20),
NAME_ELEMENT(BTN_TRIGGER_HAPPY21), NAME_ELEMENT(BTN_TRIGGER_HAPPY31),
NAME_ELEMENT(BTN_TRIGGER_HAPPY22), NAME_ELEMENT(BTN_TRIGGER_HAPPY32),
NAME_ELEMENT(BTN_TRIGGER_HAPPY23), NAME_ELEMENT(BTN_TRIGGER_HAPPY33),
NAME_ELEMENT(BTN_TRIGGER_HAPPY24), NAME_ELEMENT(BTN_TRIGGER_HAPPY34),
NAME_ELEMENT(BTN_TRIGGER_HAPPY25), NAME_ELEMENT(BTN_TRIGGER_HAPPY35),
NAME_ELEMENT(BTN_TRIGGER_HAPPY26), NAME_ELEMENT(BTN_TRIGGER_HAPPY36),
NAME_ELEMENT(BTN_TRIGGER_HAPPY27), NAME_ELEMENT(BTN_TRIGGER_HAPPY37),
NAME_ELEMENT(BTN_TRIGGER_HAPPY28), NAME_ELEMENT(BTN_TRIGGER_HAPPY38),
NAME_ELEMENT(BTN_TRIGGER_HAPPY29), NAME_ELEMENT(BTN_TRIGGER_HAPPY39),
NAME_ELEMENT(BTN_TRIGGER_HAPPY30), NAME_ELEMENT(BTN_TRIGGER_HAPPY40),
#endif
};
static const char * const absval[6] = {
"Value", "Min ", "Max ", "Fuzz ", "Flat ", "Resolution " };
static const char * const relatives[REL_MAX + 1] = {
[0 ... REL_MAX] = NULL,
NAME_ELEMENT(REL_X), NAME_ELEMENT(REL_Y), NAME_ELEMENT(REL_Z),
NAME_ELEMENT(REL_RX), NAME_ELEMENT(REL_RY), NAME_ELEMENT(REL_RZ),
NAME_ELEMENT(REL_HWHEEL), NAME_ELEMENT(REL_DIAL), NAME_ELEMENT(REL_WHEEL),
NAME_ELEMENT(REL_MISC),
};
static const char * const absolutes[ABS_MAX + 1] = {
[0 ... ABS_MAX] = NULL,
NAME_ELEMENT(ABS_X), NAME_ELEMENT(ABS_Y), NAME_ELEMENT(ABS_Z),
NAME_ELEMENT(ABS_RX), NAME_ELEMENT(ABS_RY), NAME_ELEMENT(ABS_RZ),
NAME_ELEMENT(ABS_THROTTLE), NAME_ELEMENT(ABS_RUDDER),
NAME_ELEMENT(ABS_WHEEL), NAME_ELEMENT(ABS_GAS), NAME_ELEMENT(ABS_BRAKE),
NAME_ELEMENT(ABS_HAT0X), NAME_ELEMENT(ABS_HAT0Y), NAME_ELEMENT(ABS_HAT1X),
NAME_ELEMENT(ABS_HAT1Y), NAME_ELEMENT(ABS_HAT2X), NAME_ELEMENT(ABS_HAT2Y),
NAME_ELEMENT(ABS_HAT3X), NAME_ELEMENT(ABS_HAT3Y),
NAME_ELEMENT(ABS_PRESSURE), NAME_ELEMENT(ABS_DISTANCE),
NAME_ELEMENT(ABS_TILT_X), NAME_ELEMENT(ABS_TILT_Y),
NAME_ELEMENT(ABS_TOOL_WIDTH), NAME_ELEMENT(ABS_VOLUME),
NAME_ELEMENT(ABS_MISC),
#ifdef ABS_MT_BLOB_ID
NAME_ELEMENT(ABS_MT_TOUCH_MAJOR),
NAME_ELEMENT(ABS_MT_TOUCH_MINOR),
NAME_ELEMENT(ABS_MT_WIDTH_MAJOR),
NAME_ELEMENT(ABS_MT_WIDTH_MINOR),
NAME_ELEMENT(ABS_MT_ORIENTATION),
NAME_ELEMENT(ABS_MT_POSITION_X),
NAME_ELEMENT(ABS_MT_POSITION_Y),
NAME_ELEMENT(ABS_MT_TOOL_TYPE),
NAME_ELEMENT(ABS_MT_BLOB_ID),
#endif
#ifdef ABS_MT_TRACKING_ID
NAME_ELEMENT(ABS_MT_TRACKING_ID),
#endif
#ifdef ABS_MT_PRESSURE
NAME_ELEMENT(ABS_MT_PRESSURE),
#endif
#ifdef ABS_MT_SLOT
NAME_ELEMENT(ABS_MT_SLOT),
#endif
};
static const char * const misc[MSC_MAX + 1] = {
[0 ... MSC_MAX] = NULL,
NAME_ELEMENT(MSC_SERIAL), NAME_ELEMENT(MSC_PULSELED),
NAME_ELEMENT(MSC_GESTURE), NAME_ELEMENT(MSC_RAW),
NAME_ELEMENT(MSC_SCAN),
};
static const char * const leds[LED_MAX + 1] = {
[0 ... LED_MAX] = NULL,
NAME_ELEMENT(LED_NUML), NAME_ELEMENT(LED_CAPSL),
NAME_ELEMENT(LED_SCROLLL), NAME_ELEMENT(LED_COMPOSE),
NAME_ELEMENT(LED_KANA), NAME_ELEMENT(LED_SLEEP),
NAME_ELEMENT(LED_SUSPEND), NAME_ELEMENT(LED_MUTE), NAME_ELEMENT(LED_MISC),
};
static const char * const repeats[REP_MAX + 1] = {
[0 ... REP_MAX] = NULL,
NAME_ELEMENT(REP_DELAY), NAME_ELEMENT(REP_PERIOD)
};
static const char * const sounds[SND_MAX + 1] = {
[0 ... SND_MAX] = NULL,
NAME_ELEMENT(SND_CLICK), NAME_ELEMENT(SND_BELL), NAME_ELEMENT(SND_TONE)
};
static const char * const syns[3] = {
NAME_ELEMENT(SYN_REPORT), NAME_ELEMENT(SYN_CONFIG),
#ifdef SYN_MT_REPORT
NAME_ELEMENT(SYN_MT_REPORT)
#endif
};
static const char * const switches[SW_MAX + 1] = {
[0 ... SW_MAX] = NULL,
NAME_ELEMENT(SW_LID), NAME_ELEMENT(SW_TABLET_MODE),
NAME_ELEMENT(SW_HEADPHONE_INSERT), NAME_ELEMENT(SW_RFKILL_ALL),
NAME_ELEMENT(SW_MICROPHONE_INSERT), NAME_ELEMENT(SW_DOCK),
NAME_ELEMENT(SW_LINEOUT_INSERT), NAME_ELEMENT(SW_JACK_PHYSICAL_INSERT),
#ifdef SW_VIDEOOUT_INSERT
NAME_ELEMENT(SW_VIDEOOUT_INSERT),
#endif
#ifdef SW_CAMERA_LENS_COVER
NAME_ELEMENT(SW_CAMERA_LENS_COVER),
NAME_ELEMENT(SW_KEYPAD_SLIDE),
NAME_ELEMENT(SW_FRONT_PROXIMITY),
#endif
#ifdef SW_ROTATE_LOCK
NAME_ELEMENT(SW_ROTATE_LOCK),
#endif
};
static const char * const force[FF_MAX + 1] = {
[0 ... FF_MAX] = NULL,
NAME_ELEMENT(FF_RUMBLE), NAME_ELEMENT(FF_PERIODIC),
NAME_ELEMENT(FF_CONSTANT), NAME_ELEMENT(FF_SPRING),
NAME_ELEMENT(FF_FRICTION), NAME_ELEMENT(FF_DAMPER),
NAME_ELEMENT(FF_INERTIA), NAME_ELEMENT(FF_RAMP), NAME_ELEMENT(FF_SQUARE),
NAME_ELEMENT(FF_TRIANGLE), NAME_ELEMENT(FF_SINE), NAME_ELEMENT(FF_SAW_UP),
NAME_ELEMENT(FF_SAW_DOWN), NAME_ELEMENT(FF_CUSTOM), NAME_ELEMENT(FF_GAIN),
NAME_ELEMENT(FF_AUTOCENTER),
};
static const char * const forcestatus[FF_STATUS_MAX + 1] = {
[0 ... FF_STATUS_MAX] = NULL,
NAME_ELEMENT(FF_STATUS_STOPPED), NAME_ELEMENT(FF_STATUS_PLAYING),
};
static const char * const * const names[EV_MAX + 1] = {
[0 ... EV_MAX] = NULL,
[EV_SYN] = events, [EV_KEY] = keys, [EV_REL] = relatives,
[EV_ABS] = absolutes, [EV_MSC] = misc, [EV_LED] = leds, [EV_SND] = sounds,
[EV_REP] = repeats, [EV_SW] = switches, [EV_FF] = force,
[EV_FF_STATUS] = forcestatus,
};
struct mt_slot {
int track_id;
int touch_major;
int touch_minor;
int width_major;
int width_minor;
int orientation;
int x;
int y;
int pressure;
int tool_type;
};
struct mt_state {
int current;
struct mt_slot *slot;
};
typedef struct {
uint32_t code;
int32_t values[MAX_SLOT_COUNT];
} MTSlotInfo, *MTSlotInfoPtr;
// Program Options
bool clickclear = true;
bool monotonic = true;
bool persist = true;
bool showclicks = false;
bool single_pressure = false;
static Display *dpy;
static unsigned long blackColor;
static Window w;
static GC gc;
static Colormap colormap;
const char * const color_str[] = {
"#0000FF", // Blue
"#00FF00", // Green
"#FF0000", // Red
"#FFFF00", // Yellow
"#00FFFF", // Cyan
"#FF00FF", // Magenta
"#FFFFFF", // White
"#FF8040", // Orange
"#804000", // Brown
"#808080", // Grey
};
#define COLOR_COUNT ARRAY_SIZE(color_str)
static XColor color[COLOR_COUNT];
#define WHITE_INDEX 6
static int slot_min = 0;
static int slot_max = 10;
// TODO: Update x/y_min/max on window resize
static int x_min = 0;
static int x_max = 0;
static int y_min = 0;
static int y_max = 0;
static int pressure_min = 0;
static int pressure_max = 255;
static int touch_major_min = 0;
static int touch_major_max = 255;
// For aspect ratio preservation
static float pad_to_pixel = 1.0;
static int pad_x_offset = 0;
static int pad_y_offset = 0;
static bool single_pressure_device;
static bool has_mt_slot = false;
static unsigned int w_width;
static unsigned int w_height;
static void MtClear() {
XClearWindow(dpy, w);
XSetForeground(dpy, gc, color[WHITE_INDEX].pixel);
XSetLineAttributes(dpy, gc, 1, LineSolid, CapRound, JoinRound);
XDrawRectangle(dpy, w, gc, pad_x_offset, pad_y_offset,
w_width - pad_x_offset * 2 - 1,
w_height - pad_y_offset * 2 - 1);
XFlush(dpy);
}
static Display *InitDisplay(const char *displayname, const char *geometry) {
Window root;
int x, y;
unsigned int border_width;
unsigned int depth;
int i;
dpy = XOpenDisplay(displayname);
if (!dpy)
return NULL;
blackColor = BlackPixel(dpy, DefaultScreen(dpy));
// Determine the window geometry
if (!geometry || XParseGeometry(geometry, &x, &y, &w_width, &w_height) == 0) {
x = y = 0;
w_width = DisplayWidth(dpy, 0);
w_height = DisplayHeight(dpy, 0);
}
// Create a window
w = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), x, y, w_width, w_height,
0, blackColor, blackColor);
// Set the application name so that it is easier to look up in window manager.
XStoreName(dpy, w, "mtplot");
// We want to get MapNotify events
XSelectInput(dpy, w, StructureNotifyMask | KeyPressMask);
// "Map" the window (that is, make it appear on the screen)
XMapWindow(dpy, w);
// Create a "Graphics Context"
gc = XCreateGC(dpy, w, 0, NULL);
colormap = DefaultColormap(dpy, DefaultScreen(dpy));
for (i = 0; i < COLOR_COUNT; i++) {
XParseColor(dpy, colormap, color_str[i], &color[i]);
XAllocColor(dpy, colormap, &color[i]);
}
// Wait for the MapNotify event
while (1) {
XEvent e;
XNextEvent(dpy, &e);
if (e.type == MapNotify)
break;
}
XGetGeometry(dpy, w, &root, &x, &y, &w_width, &w_height, &border_width,
&depth);
// TODO: Resize touch surface to 90% of screen, and draw a virtual bezel
// since Touchpads sometimes emit events outside advertised range.
return dpy;
}
static void MtSlotInit(struct mt_slot *s) {
memset(s, 0, sizeof(struct mt_slot));
s->track_id = -1;
}
static void InitPreserveRatio() {
float pad_width= x_max - x_min + 1;
float pad_height = y_max - y_min + 1;
// Compute pad to window scaling factors
float pad_x_scale = w_width / pad_width;
float pad_y_scale = w_height / pad_height;
// Choose smaller scaling factor for both x & y
pad_to_pixel = MIN(pad_x_scale, pad_y_scale);
// Resulting pixel sizes:
float pad_pixel_width = pad_width * pad_to_pixel;
float pad_pixel_height = pad_height * pad_to_pixel;
// Compute offsets to center pad in window
pad_x_offset = (w_width - pad_pixel_width) / 2;
pad_y_offset = (w_height - pad_pixel_height) / 2;
}
static void MtSlotPaint(struct mt_slot *s) {
int x_pixel, y_pixel;
float width, height;
unsigned long forecolor;
if (s->track_id == -1)
return;
if (s->pressure) {
// TODO: Get really fancy and use touch_minor & orientation
// TODO: Get really really fancy, and use 'width_*'
width = 50.0 *
(float)(s->pressure - pressure_min) / (pressure_max - pressure_min + 1);
height = 50.0 *
(float)(s->pressure - pressure_min) / (pressure_max - pressure_min + 1);
} else if (s->touch_major) {
// In case we don't have pressure reading, use touch_major instead.
width = 50.0 * (float)(s->touch_major - touch_major_min) /
(touch_major_max - touch_major_min + 1);
height = 50.0 * (float)(s->touch_major - touch_major_min) /
(touch_major_max - touch_major_min + 1);
} else {
width = 10;
height = 10;
}
x_pixel = s->x * pad_to_pixel + pad_x_offset;
y_pixel = s->y * pad_to_pixel + pad_y_offset;
// TODO: Get fancy and adjust color intensity using pressure
forecolor = color[s->track_id % COLOR_COUNT].pixel;
XSetForeground(dpy, gc, forecolor);
XFillArc(dpy, w, gc, x_pixel, y_pixel, width, height, 0, 360 * 64);
}
int ProbeAbsInfo(int fd, struct input_absinfo *absinfo, size_t key) {
if (ioctl(fd, EVIOCGABS(key), absinfo) < 0) {
perror("Error in getting info in ProbeAbsInfo");
return false;
}
return true;
}
int ProbeMTSlot(int fd, MTSlotInfoPtr req, uint32_t code) {
req->code = code;
if (ioctl(fd, EVIOCGMTSLOTS((sizeof(*req))), req) < 0) {
perror("Error in getting info in ProbeMTSlot");
return false;
}
return true;
}
static void MtStateInit(struct mt_state *s, char *filename) {
int i;
int fd = open(filename, O_RDWR | O_NONBLOCK, 0);
struct input_absinfo absinfo;
struct mt_slot *current_slot;
MTSlotInfo req;
s->current = 0;
s->slot = calloc(slot_max - slot_min + 1, sizeof(struct mt_slot));
for (i = slot_min; i <= slot_max; i++)
MtSlotInit(&s->slot[i - slot_min]);
// Synchronize the last slot number with the kernel evdev driver.
if (ProbeAbsInfo(fd, &absinfo, ABS_MT_SLOT))
s->current = absinfo.value;
current_slot = &s->slot[s->current];
// Synchronize last X with the kernel evdev driver.
if (ProbeMTSlot(fd, &req, ABS_MT_POSITION_X))
current_slot->x = req.values[s->current];
// Synchronize last Y with the kernel evdev driver.
if (ProbeMTSlot(fd, &req, ABS_MT_POSITION_Y))
current_slot->y = req.values[s->current];
// Synchronize last PRESSURE with the kernel evdev driver.
if (ProbeMTSlot(fd, &req, ABS_MT_PRESSURE))
current_slot->pressure = req.values[s->current];
}
static void MtStatePaint(struct mt_state *s) {
int i;
if (!persist)
MtClear(dpy, w);
for (i = slot_min; i <= slot_max; i++)
MtSlotPaint(&s->slot[i - slot_min]);
XFlush(dpy);
}
static void MtClickPaint(struct mt_state *s) {
int i;
for (i = slot_min; i <= slot_max; i++) {
int x_pixel, y_pixel;
unsigned long forecolor;
struct mt_slot finger = s->slot[i - slot_min];
if (finger.track_id == -1)
continue;
y_pixel = finger.y * pad_to_pixel + pad_y_offset;
x_pixel = finger.x * pad_to_pixel + pad_x_offset;
forecolor = color[finger.track_id % COLOR_COUNT].pixel;
XSetForeground(dpy, gc, forecolor);
XSetLineAttributes(dpy, gc, 5, LineSolid, CapRound, JoinRound);
XDrawRectangle(dpy, w, gc, x_pixel, y_pixel, 15, 15);
}
XFlush(dpy);
}
static void ProcessSynReport(struct mt_state *s, struct input_event *e) {
MtStatePaint(s);
}
static void ProcessKey(struct mt_state *s, struct input_event *e) {
if (e->code == BTN_LEFT) {
if (clickclear)
MtClear();
if (showclicks)
MtClickPaint(s);
}
}
// Update all active slots with the same ABS_PRESSURE value as it is a
// semi-mt device.
static void UpdateWithAbsPressure(struct mt_state *s, struct input_event *e) {
int i;
for (i = slot_min; i <= slot_max; i++) {
struct mt_slot *slt = &s->slot[i];
if (slt->track_id != -1)
slt->pressure = e->value;
}
}
// Associate the track ID with slot number.
static int track_id_to_slot(struct mt_state *s, int track_id) {
int i;
// For existing track ID, return its associated slot.
for (i = slot_min; i <= slot_max; i++)
if (s->slot[i].track_id == track_id)
return i;
// For new track ID, return a free slot.
for (i = slot_min; i <= slot_max; i++)
if (s->slot[i].track_id == -1)
return i;
perror("No free slot for for new track_id");
return slot_min;
}
static void ProcessAbs(struct mt_state *s, struct input_event *e) {
struct mt_slot *slot = &s->slot[s->current];
switch (e->code) {
case ABS_MT_SLOT:
s->current = e->value;
break;
case ABS_MT_TOUCH_MAJOR:
// If there is no ABS_MT_SLOT support, use a 0 touch major as
// the indication of removed touch contact.
if (!has_mt_slot && e->value == 0)
slot->track_id = -1;
slot->touch_major = e->value;
break;
case ABS_MT_TOUCH_MINOR:
slot->touch_minor = e->value;
break;
case ABS_MT_WIDTH_MAJOR:
slot->width_major = e->value;
break;
case ABS_MT_WIDTH_MINOR:
slot->width_minor = e->value;
break;
case ABS_MT_ORIENTATION:
slot->orientation = e->value;
break;
case ABS_MT_POSITION_X:
slot->x = e->value;
break;
case ABS_MT_POSITION_Y:
slot->y = e->value;
break;
case ABS_MT_TOOL_TYPE:
slot->tool_type = e->value;
break;
case ABS_MT_BLOB_ID:
break;
case ABS_MT_TRACKING_ID:
// If there is no ABS_MT_SLOT support, use ABS_MT_TRACKING_ID as
// replacement. If new ABS_MT_TRACKING_ID is received, update the
// current slot to be the one associated with the new track ID.
if (!has_mt_slot && e->value >= 0) {
s->current = track_id_to_slot(s, e->value);
slot = &s->slot[s->current];
}
slot->track_id = e->value;
break;
case ABS_MT_PRESSURE:
slot->pressure = single_pressure ? 3 : e->value;
break;
case ABS_PRESSURE:
if (single_pressure_device)
UpdateWithAbsPressure(s, e);
break;
default:
break;
}
}
static void MtStateUpdate(struct mt_state *s, struct input_event *e) {
if (e->type == EV_SYN && e->code == SYN_REPORT)
ProcessSynReport(s, e);
else if (e->type == EV_KEY)
ProcessKey(s, e);
else if (e->type == EV_ABS)
ProcessAbs(s, e);
}
// Filter for the AutoDevProbe scandir on /dev/input.
//
// @param dir The current directory entry provided by scandir.
//
// @return Non-zero if the given directory entry starts with "event", or zero
// otherwise.
static int IsEventDevice(const struct dirent *dir) {
return strncmp(EVENT_DEV_NAME, dir->d_name, 5) == 0;
}
// Scans all /dev/input/event*, display them and ask the user which one to
// open.
//
// @return The event device file name of the device file selected. This
// string is allocated and must be freed by the caller.
static char *ScanDevices(void) {
struct dirent **namelist;
int i, ndev, devnum, rc;
char *filename;
ndev = scandir(DEV_INPUT_EVENT, &namelist, IsEventDevice, alphasort);
if (ndev <= 0)
return NULL;
fprintf(stderr, "Available devices:\n");
for (i = 0; i < ndev; i++)
{
char fname[64];
int fd = -1;
char name[256] = "???";
snprintf(fname, sizeof(fname), "%s/%s", DEV_INPUT_EVENT,
namelist[i]->d_name);
fd = open(fname, O_RDONLY);
if (fd < 0)
continue;
ioctl(fd, EVIOCGNAME(sizeof(name)), name);
fprintf(stderr, "%s:\t%s\n", fname, name);
close(fd);
free(namelist[i]);
}
fprintf(stderr, "Select the device event number [0-%d]: ", ndev - 1);
rc = scanf("%d", &devnum);
if (rc != 1 || devnum >= ndev || devnum < 0)
return NULL;
rc = asprintf(&filename, "%s/%s%d",
DEV_INPUT_EVENT, EVENT_DEV_NAME, devnum);
if (rc == -1)
return NULL;
return filename;
}
// Print additional information for absolute axes (min/max, current value,
// etc.).
//
// @param fd The file descriptor to the device.
// @param axis The axis identifier (e.g. ABS_X).
static void PrintAbsData(int fd, int axis) {
int abs[6] = {0};
int k;
ioctl(fd, EVIOCGABS(axis), abs);
for (k = 0; k < 6; k++)
if ((k < 3) || abs[k]) {
printf(" %s %6d\n", absval[k], abs[k]);
if (k == 1) {
if (axis == ABS_MT_SLOT)
slot_min = abs[k];
else if (axis == ABS_MT_POSITION_X)
x_min = abs[k];
else if (axis == ABS_MT_POSITION_Y)
y_min = abs[k];
else if (axis == ABS_MT_PRESSURE)
pressure_min = abs[k];
else if (axis == ABS_PRESSURE)
pressure_min = abs[k];
else if (axis == ABS_MT_TOUCH_MAJOR)
touch_major_min = abs[k];
} else if (k == 2) {
if (axis == ABS_MT_SLOT)
slot_max = abs[k];
else if (axis == ABS_MT_POSITION_X)
x_max = abs[k];
else if (axis == ABS_MT_POSITION_Y)
y_max = abs[k];
else if (axis == ABS_MT_PRESSURE)
pressure_max = abs[k];
else if (axis == ABS_PRESSURE)
pressure_max = abs[k];
else if (axis == ABS_MT_TOUCH_MAJOR)
touch_major_max = abs[k];
}
}
}
// Test if the device supports single-pressure value only. If so, we will
// retrieve the pressure value from the element ABS_PRESSURE instead.
//
// @param fd The file descriptor to the device.
// @return false if the bit ABS_MT_PRESSURE is set, or true otherwise.
static bool IsSinglePressureDevice(int fd) {
unsigned long bits[NBITS(KEY_MAX)];
memset(bits, 0, sizeof(bits));
ioctl(fd, EVIOCGBIT(EV_ABS, EV_MAX), bits);
return (!test_bit(ABS_MT_PRESSURE, bits) && test_bit(ABS_PRESSURE, bits));
}
// Print static device information (no events). This information includes
// version numbers, device name and all bits supported by this device.
//
// @param fd The file descriptor to the device.
// @return 0 on success or 1 otherwise.
static int PrintDeviceInfo(int fd) {
int i, j;
int version;
unsigned short id[4];
char name[256] = "Unknown";
unsigned long bit[EV_MAX][NBITS(KEY_MAX)];
if (ioctl(fd, EVIOCGVERSION, &version)) {
perror("mtplot: can't get version");
return 1;
}
printf("Input driver version is %d.%d.%d\n",
version >> 16, (version >> 8) & 0xff, version & 0xff);
ioctl(fd, EVIOCGID, id);
printf("Input device ID: bus 0x%x vendor 0x%x product 0x%x version 0x%x\n",
id[ID_BUS], id[ID_VENDOR], id[ID_PRODUCT], id[ID_VERSION]);
ioctl(fd, EVIOCGNAME(sizeof(name)), name);
printf("Input device name: \"%s\"\n", name);
memset(bit, 0, sizeof(bit));
ioctl(fd, EVIOCGBIT(0, EV_MAX), bit[0]);
printf("Supported events:\n");
for (i = 0; i < EV_MAX; i++)
if (test_bit(i, bit[0])) {
printf(" Event type %d (%s)\n", i, events[i] ? events[i] : "?");
if (!i)
continue;
ioctl(fd, EVIOCGBIT(i, KEY_MAX), bit[i]);
for (j = 0; j < KEY_MAX; j++)
if (test_bit(j, bit[i])) {
printf(" Event code %d (%s)\n", j,
names[i] ? (names[i][j] ? names[i][j] : "?") : "?");
if (i == EV_ABS) {
PrintAbsData(fd, j);
if (j == ABS_MT_SLOT)
has_mt_slot = true;
}
}
}
return 0;
}
// Grab and immediately ungrab the device.
//
// @param fd The file descriptor to the device.
// @return 0 if the grab was successful, or 1 otherwise.
static int TestGrab(int fd) {
int rc;
rc = ioctl(fd, EVIOCGRAB, (void *)1);
if (!rc)
ioctl(fd, EVIOCGRAB, (void *)0);
return rc;
}
// Dump the screen shot
//
// @param displayname The display name.
// @param w The mtplot window.
// @param output The screenshot output directory.
static void DumpScreenShot(char *displayname, Window w, char *output) {
char display_str[16];
char filename[PATH_MAX];
char dump_str[PATH_MAX + 64];
char command[PATH_MAX + 128];
XID wid = (XID) w;
char *xauthority_str = "XAUTHORITY=/home/chronos/.Xauthority ";
struct timeval curr_time;
if (system("which import > /dev/null 2>&1") != 0) {
printf("Warning: The screenshot executable (import) does not exist.\n");
return;
}
// If the display is not specified as a command line option, get it
// from the environment.
if (displayname == NULL)
displayname = getenv("DISPLAY");
sprintf(display_str, "DISPLAY=%s ", displayname);
// The screenshot file name could be distinguished with the last event time.
gettimeofday(&curr_time, NULL);
sprintf(filename, "%s/mtplot_%ld.%06ld.png", output, curr_time.tv_sec,
curr_time.tv_usec);
// Use the "import" utility to dump the mtplot window.
sprintf(dump_str, "import -window %ld %s", wid, filename);
strcpy(command, display_str);
strcat(command, xauthority_str);
strcat(command, dump_str);
if (system(command) == 0)
printf("The screenshot is saved as '%s'\n", filename);
else
perror("import command error");
}
static void EnableMonotonicTimestamps(int fd)
{
unsigned int clk = CLOCK_MONOTONIC;
int ret = ioctl(fd, EVIOCSCLOCKID, &clk);
if (ret < 0)
fprintf(stderr, "Monotonic timestamps not supported in this kernel.\n");
}
static const struct option long_options[] = {
{ "clickclear", optional_argument, NULL, 'c' },
{ "display", required_argument, NULL, 'd' },
{ "geometry", optional_argument, NULL, 'g' },
{ "monotonic", optional_argument, NULL, 'm' },
{ "output", optional_argument, NULL, 'o' },
{ "persist", optional_argument, NULL, 'p' },
{ "showclicks", optional_argument, NULL, 's' },
{ "singlepressure", optional_argument, NULL, 'a' },
{ 0, },
};
// Create the specified directory.
int MkDir(char *dir, int mode) {
struct stat st;
if (stat(dir, &st) != 0 && mkdir(dir, mode) != 0) {
perror("Error in making screenshot output directory");
return false;
}
return true;
}
// Create intermediate directories.
int MkDirs(char *path, int mode) {
char int_path[PATH_MAX];
char *p = strcpy(int_path, path);
// Skip the initial '/' if any.
while (*p == '/')
p++;
// Make the intermediate directories if not existing yet.
while (p = strchr(p, '/')) {
*p = '\0';
if (!MkDir(int_path, mode))
return false;
*p++ = '/';
}
// Need to mkdir the full path too.
return MkDir(path, mode);
}
// Check if it has the write access to the specified directory.
// Create the intermediate directories if needed.
//
// @param output The screenshot output directory.
bool CheckDirectory(char *output) {
struct stat st;
// Check if output already exists.
if (stat(output, &st) == 0) {
// Check if output is a regular directory.
if (S_ISDIR(st.st_mode)) {
// check its write permission.
if (st.st_mode & S_IWUSR) {
return true;
} else {
printf("Error: no write permission to '%s'.\n", output);
return false;
}
} else {
printf("Error: the output '%s' must be a directory.\n", output);
return false;
}
} else {
// Try to create the output directory if it does not exist yet.
return MkDirs(output, OUTPUT_DIR_MODE);
}
}
// Print usage information.
static int Usage(void) {
printf("USAGE:\n");
printf(" %s /dev/input/eventX [--display DISPLAY] [--monotonic] [--persist]\n",
program_invocation_short_name);
printf("\n");
printf(" -c, --clickclear[=0|1] =1: clicks clear the screen (default)\n"
" =0: clicks do not clear the screen\n"
" To toggle at runtime press 'c'\n");
printf(" -g, --geometry[=wxh+x+y] : sets the window geometry\n");
printf(" e.g., 1280x800+0+0\n");
printf(" -m, --monotonic[=0|1] =1: requests input events with monotonic timestamps\n"
" if supported by the kernel (default)\n"
" =0: request input events with wallclock timestamps\n");
printf(" -o, --output[=<dir>] : specifies the screenshot output directory\n"
" (default: /tmp)\n");
printf(" -p, --persist[=0|1] =1: display persistent trails for each contact (default)\n"
" =0: only display the current contact positions\n"
" To toggle at runtime press 'p'\n");
printf(" -s, --showclicks[=0|1] =1: clicks are shown visibly marked on screen\n"
" =0: clicks do not leave a visible mark\n");
printf(" -a, --singlepressure[=0|1] =1: "
"All points are drawn with same pressure\n"
" =0: "
"Point size indicates pressure (default)\n");
return EXIT_FAILURE;
}
int main(int argc, char **argv) {
const char *device = NULL;
char *displayname = NULL;
char *filename;
char *geometry;
struct input_event ev[64];
int i, rd;
struct mt_state mt_state;
int fd, x11_fd, max_fd;
fd_set rdfs;
char output[PATH_MAX];
strcpy(output, DEFAULT_OUTPUT_DIR);
/* Set DISPLAY to :0 if it's not already set */
setenv("DISPLAY", ":0", 0);
while (1) {
int c = getopt_long_only(argc, argv, "c:d:g:m:o:p:s:", long_options, NULL);
if (c == -1)
break;
switch (c) {
case 'a':
single_pressure = (optarg) ? atoi(optarg) : true;
break;
case 'c':
clickclear = (optarg) ? atoi(optarg) : true;
break;
case 'd':
displayname = optarg;
break;
case 'g':
geometry = optarg;
break;
case 'm':
monotonic = (optarg) ? atoi(optarg) : true;
break;
case 'o':
strcpy(output, optarg);
if (!CheckDirectory(output))
return EXIT_FAILURE;
break;
case 'p':
persist = (optarg) ? atoi(optarg) : true;
break;
case 's':
showclicks = (optarg) ? atoi(optarg) : true;
break;
default: // Fall through case
printf("Unknown option '%c'\n", c);
case '?':
return Usage();
}
}
if (optind < argc)
device = argv[optind++];
if (!device) {
fprintf(stderr, "No device specified, trying to scan all of %s/%s*\n",
DEV_INPUT_EVENT, EVENT_DEV_NAME);
if (getuid() != 0)
fprintf(stderr, "Not running as root, no devices may be available.\n");
filename = ScanDevices();
if (!filename)
return Usage();
} else {
filename = strdup(device);
}
if (!filename)
return EXIT_FAILURE;
if ((fd = open(filename, O_RDONLY)) < 0) {
perror("mtplot");
if (errno == EACCES && getuid() != 0)
fprintf(stderr, "You do not have access to %s. Try "
"running as root instead.\n",
filename);
return EXIT_FAILURE;
}
if (!isatty(fileno(stdout)))
setbuf(stdout, NULL);
if (PrintDeviceInfo(fd))
return EXIT_FAILURE;
single_pressure_device = IsSinglePressureDevice(fd);
if (monotonic)
EnableMonotonicTimestamps(fd);
printf("Testing ... (interrupt to exit)\n");
if (TestGrab(fd)) {
printf("***********************************************\n");
printf(" This device is grabbed by another process.\n");
printf(" No events are available to mtplot while the\n");
printf(" other grab is active.\n");
printf(" In most cases, this is caused by an X driver,\n");
printf(" try VT-switching and re-run mtplot again.\n");
printf("***********************************************\n");
}
dpy = InitDisplay(displayname, geometry);
if (!dpy) {
fprintf (stderr, "mtplot: unable to open display '%s'\n",
XDisplayName (displayname));
return EXIT_FAILURE;
}
MtStateInit(&mt_state, filename);
InitPreserveRatio();
free(filename);
printf("If the display isn't showing, this command may help:\n"
"echo 0 > /sys/module/i915/parameters/i915_enable_fbc\n");
x11_fd = ConnectionNumber(dpy);
max_fd = (x11_fd > fd) ? x11_fd : fd;
MtClear();
while (1) {
int rc;
// Add device and X file descriptors to fd_set
FD_ZERO(&rdfs);
FD_SET(x11_fd, &rdfs);
FD_SET(fd, &rdfs);
rc = select(max_fd + 1, &rdfs, NULL, NULL, NULL);
if (rc == -1)
perror("select()");
if (FD_ISSET(fd, &rdfs)) {
rd = read(fd, ev, sizeof(struct input_event) * 64);
if (rd < (int) sizeof(struct input_event)) {
printf("expected %d bytes, got %d\n",
(int) sizeof(struct input_event), rd);
perror("\nmtplot: error reading");
return EXIT_FAILURE;
}
for (i = 0; i < rd / sizeof(struct input_event); i++) {
printf("Event: time %ld.%06ld, ", ev[i].time.tv_sec,
ev[i].time.tv_usec);
if (ev[i].type == EV_SYN) {
if (ev[i].code == SYN_MT_REPORT)
printf("++++++++++++++ %s ++++++++++++\n", syns[ev[i].code]);
else
printf("-------------- %s ------------\n", syns[ev[i].code]);
} else {
int type = ev[i].type;
int code = ev[i].code;
int value = ev[i].value;
printf("type %d (%s), code %d (%s), ", type, events[type] ?: "?",
code, names[type] ? (names[type][code] ?: "?") : "?");
if (type == EV_MSC && (code == MSC_RAW || code == MSC_SCAN))
printf("value %02x\n", value);
else
printf("value %d\n", value);
}
MtStateUpdate(&mt_state, &ev[i]);
}
}
if (FD_ISSET(x11_fd, &rdfs)) {
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
if (ev.type == ConfigureNotify) {
XConfigureEvent xce = ev.xconfigure;
// Save new window dimensions
w_width = xce.width;
w_height = xce.height;
} else if (ev.type == KeyPress) {
XKeyEvent xke = ev.xkey;
KeySym ks = XLookupKeysym(&xke, 0);
if (ks == XK_Escape)
MtClear();
else if (ks == XK_a)
single_pressure = !single_pressure;
else if (ks == XK_c)
clickclear = !clickclear;
else if (ks == XK_p)
persist = !persist;
else if (ks == XK_q)
return 0;
else if (ks == XK_s)
DumpScreenShot(displayname, w, output);
}
}
}
}
}