| // 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 <linux/input.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdbool.h> |
| #include <time.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 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 |
| |
| /* Set clockid to be used for timestamps */ |
| #ifndef EVIOCSCLOCKID |
| #define EVIOCSCLOCKID _IOW('E', 0xa0, int) |
| #endif |
| |
| #define NAME_ELEMENT(element) [element] = #element |
| |
| 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; |
| }; |
| |
| 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]; |
| |
| static int slot_min = 0; |
| static int slot_max = 10; |
| 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 bool semi_mt_device = false; |
| |
| static unsigned int w_width; |
| static unsigned int w_height; |
| |
| static Display *InitDisplay(const char *displayname) { |
| 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)); |
| |
| // Create a window |
| w = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, |
| DisplayWidth(dpy, 0), DisplayHeight(dpy, 0), |
| 0, blackColor, blackColor); |
| |
| // We want to get MapNotify events |
| XSelectInput(dpy, w, StructureNotifyMask); |
| |
| // "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 MtSlotPaint(struct mt_slot *s) { |
| int x, y; |
| unsigned int width, height; |
| unsigned long forecolor; |
| |
| if ((s->track_id == -1) || (s->pressure == 0)) |
| return; |
| |
| // TODO: Get really fancy and use touch_minor & orientation |
| // TODO: Get really really fancy, and use 'width_*' |
| width = 50 * (s->pressure - pressure_min) / (pressure_max - pressure_min); |
| height = 50 * (s->pressure - pressure_min) / (pressure_max - pressure_min); |
| |
| // TODO: Update x/y_min/max on window resize |
| x = (s->x - x_min) * w_width / (x_max - x_min) - (width - 1) / 2; |
| y = (s->y - y_min) * w_height / (y_max - y_min) - (height - 1) / 2; |
| |
| // 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, y, width, height, 0, 360 * 64); |
| } |
| |
| static void MtStateInit(struct mt_state *s) { |
| int i; |
| 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]); |
| } |
| |
| static void MtStatePaint(struct mt_state *s) { |
| int i; |
| for (i = slot_min; i <= slot_max; i++) |
| MtSlotPaint(&s->slot[i - slot_min]); |
| 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) |
| XClearWindow(dpy, w); |
| } |
| |
| // 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; |
| } |
| } |
| |
| 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: |
| 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: |
| slot->track_id = e->value; |
| break; |
| case ABS_MT_PRESSURE: |
| slot->pressure = e->value; |
| break; |
| case ABS_PRESSURE: |
| if (semi_mt_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 usage information. |
| static int Usage(void) { |
| printf("USAGE:\n"); |
| printf(" %s /dev/input/eventX [--display DISPLAY] [--monotonic]\n", |
| program_invocation_short_name); |
| |
| return EXIT_FAILURE; |
| } |
| |
| // 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 (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]; |
| } |
| } |
| } |
| |
| // Test if the INPUT_PROP_SEMI_MT bit is set. If so, we will retrieve the |
| // pressure value from the element ABS_PRESSURE instead. |
| // |
| // @param fd The file descriptor to the device. |
| // @return true if the bit INPUT_PROP_SEMI_MT is set, or zero otherwise. |
| static bool IsSemiMtDevice(int fd) { |
| unsigned long prop_bitmask[NLONGS(INPUT_PROP_CNT)]; |
| int len = ioctl(fd, EVIOCGPROP(sizeof(prop_bitmask)), prop_bitmask); |
| if (len < 0) { |
| printf("get properties error: %s\n", strerror(errno)); |
| return false; |
| } |
| return test_bit(INPUT_PROP_SEMI_MT, prop_bitmask); |
| } |
| |
| // 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); |
| } |
| } |
| |
| 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; |
| } |
| |
| 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[] = { |
| { "display", required_argument, NULL, 'd' }, |
| { "monotonic", optional_argument, NULL, 'm' }, |
| { 0, }, |
| }; |
| |
| int main(int argc, char **argv) { |
| const char *device = NULL; |
| char *displayname = NULL; |
| char *filename; |
| struct input_event ev[64]; |
| int i, rd; |
| struct mt_state mt_state; |
| int fd, x11_fd, max_fd; |
| fd_set rdfs; |
| bool monotonic_timestamps = false; |
| |
| while (1) { |
| int option_index = 0; |
| int c = getopt_long_only(argc, argv, "d:", long_options, &option_index); |
| if (c == -1) |
| break; |
| switch (c) { |
| case 'd': |
| displayname = optarg; |
| break; |
| case 'm': |
| monotonic_timestamps = true; |
| break; |
| default: |
| 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; |
| } |
| |
| free(filename); |
| |
| if (!isatty(fileno(stdout))) |
| setbuf(stdout, NULL); |
| |
| if (PrintDeviceInfo(fd)) |
| return EXIT_FAILURE; |
| |
| if (IsSemiMtDevice(fd)) |
| semi_mt_device = true; |
| |
| if (monotonic_timestamps) |
| 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); |
| if (!dpy) { |
| fprintf (stderr, "mtplot: unable to open display '%s'\n", |
| XDisplayName (displayname)); |
| return EXIT_FAILURE; |
| } |
| |
| MtStateInit(&mt_state); |
| |
| x11_fd = ConnectionNumber(dpy); |
| max_fd = (x11_fd > fd) ? x11_fd : fd; |
| |
| 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; |
| } |
| } |
| } |
| } |
| } |