| // 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); |
| } |
| } |
| } |
| } |
| } |