John Forkosh
John Forkosh

Reputation: 522

Interpretation of input_event members for mouse actions

I'm writing an essentially character-based program, but running in an xterm, and want to use mouse scroll-wheel-up/down and left-clicks as synonyms for keyboard arrow-up/down and return, just for a little extra user convenience.

I've got a select() with all the input fdset's working fine, and am asynchronously capturing the raw input fine (seems so, anyway). But I'm having a little trouble unambiguously interpreting the type,code,value members from the input_event struct. /usr/include/linux/input.h seems to have some EV's, and I'm seeing

Question 1:Is that universally true??? I'm not successfully googling anything much about that specifically.

But beyond EV's, I'm seeing nothing in /usr/include/ for codes,values. My experimenting shows the following, and I'm further asking if everything below is (universally) true. Or even better, where's (definitive) documentation about this stuff? I'd have thought it would be easy to google, but couldn't find answers.

Any one action seems to generate either two or three separate input_event's, with the last (the second or third) a "trailer" with type=code=value=0. I'm writing input_event's below as triples (type,code,value)...

For left-click-press you get three events: (4,4,589825),(1,272,1),(0,0,0). And for left-click-release you get: (4,4,589825),(1,272,0),(0,0,0). Is that all correct? And what the heck's 589825???

For scroll-wheel-up you get two events: (2,8,1),(0,0,0). And for scroll-wheel-down you get: (2,8,-1),(0,0,0). (Universally) correct, again?

I don't particularly care about right-clicks or mouse movements, which I'll just be ignoring. So can I hard-code (with some #define'ed symbols) the preceding stuff, or is it more like termcap, where it's device-capability-dependent in some way? And, again, where's this stuff documented for real? Thanks.

Edit regarding NominalAnimal's /dev/input/mice remarks below

As NominalAnimal suggested in his terrific answer (thanks again, Nominal), I'm reading /dev/input/event16, which I figured from looking at the /proc/bus/input/devices file. I'd wanted (and would still like) to code that more generally, but trying to read /dev/input/mice returns only three bytes per read, rather than the 16 comprising an input_event struct. At least that's what happened to me when I did it. And I couldn't figure any way to "decode" those bytes to tell me "event16".

So I would have asked this originally, but figured I'd talked enough: Is there any way to get all the data from /dev/input/mice that I'm now getting from /dev/input/event16? Or is there any way to programmatically determine which /dev/input/event?? is for the mouse during initialization (without parsing that /proc/ file)?

Upvotes: 2

Views: 5321

Answers (4)

John Forkosh
John Forkosh

Reputation: 522

Below is another version of Nominal Animal's preceding code, re-factored into a user-callable function mouseread() that provides all necesary functionality open/close/read/interpret mouse events, as illustrated by Nominal Animal's demo program above. Rather than cut-and-paste (some comments below are misaligned, maybe due to stackexchange tab settings), I've also placed a copy at http://www.forkosh.com/mouseread.c

/****************************************************************************
 *
 * Copyright(c) 2016-2016, John Forkosh Associates, Inc. All rights reserved.
 *           http://www.forkosh.com   mailto: john@forkosh.com
 * --------------------------------------------------------------------------
 * This file is mouseread.c, which is free software.
 * You may redistribute and/or modify mouseread.c under the
 * terms of the GNU General Public License, version 3 or later,
 * as published by the Free Software Foundation.
 *      mouseread.c is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY, not even the implied warranty of MERCHANTABILITY.
 * See the GNU General Public License for specific details.
 *      By using mouseread.c, you warrant that you have read, understood
 * and agreed to these terms and conditions, and that you possess the legal
 * right and ability to enter into this agreement and to use mouseread.c
 * in accordance with it.
 *      To read the GNU General Public License, version 3, point your
 * browser to  http://www.gnu.org/licenses/  or write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330,  Boston, MA 02111-1307 USA.
 * --------------------------------------------------------------------------
 *
 * Purpose:   o mouseread.c, licensed under the gpl, reads and interprets
 *      mouse events for the calling application-level program
 *      (see Notes below for further usage instructions).
 *         Thanks: mouseread.c is based on code supplied by
 *      Nominal Animal <question@nominal-animal.net>
 *      ( AKA Jouko Orava <jouko.orava@iki.fi> )
 *      in  http://stackoverflow.com/questions/38197517/
 *
 * Functions:   The following "table of contents" lists each function
 *      comprising mouseread.c in the order it appears in this file.
 *      See individual function entry points for specific comments
 *      about its purpose, calling sequence, side effects, etc.
 *      =============================================================
 *              --- primary user-callable function ---
 *      mouseread(action)                   open/close/read/etc mouse
 *              ---  helper (static in this module) functions ---
 *      mouseopen(void)                             open mouse device
 *      mouseget(fd)                  read and interpret mouse device
 *              --- main() test driver ---
 *      #if defined(TESTMOUSEREAD)
 *        main(argc,argv)                                 test driver
 *      #endif
 *      =============================================================
 *
 * Source:  mouseread.c
 *
 * --------------------------------------------------------------------------
 * Notes      o See individual function entry points for specific comments
 *      about the purpose, calling sequence, side effects, etc
 *      of each mouseread.c function listed above.
 *        o compile as
 *         cc yourprogram.c mouseread.c -o yourprogram
 *      or for test driver
 *         cc -DTESTMOUSEREAD mouseread.c -o mouseread
 *        o mouseread() opens /dev/input/mice by default
 *      make sure /dev/input/mice is chmod o+rw,
 *      or else mouseread() will fail. To use another device,
 *         cc -DMOUSEDEV=\"/dev/input/mouse0\" etc.
 *      (which must also be chmod o+rw)
 *        o in yourprogram.c write the two lines
 *         #define _MOUSEHEADERS
 *         #include "mouseread.c"
 *      to get the defined symbols for recognized mouseread() actions.
 *      there's no separate mouseread.h file.
 *        o --- usage instructions ---
 *      Initialization:
 *         First make sure to follow the instructions immediately
 *       above to include the header information defining recognized
 *       mouseread() actions.
 *         Then the basic declaration your program should contain
 *       is of the form
 *           int fd=(-1), result=(-1), mouseread(int);
 *       And initialization consists of the single line
 *           fd = mouseread(_MOUSE_OPEN);
 *       which should be issued just once, and which returns
 *       the file descriptor of the open()'ed MOUSEDEV, or -1=error.
 *       You can combine declaration/initialization into one line
 *           int result=(-1), mouseread(int), fd=mouseread(_MOUSE_OPEN);
 *       And you won't need fd unless issuing a select(), or using
 *       it with other non-blocking i/o mechanisms, etc.
 *      Action _MOUSE_GET:
 *           result = mouseread(_MOUSE_GET);
 *       reads the next queued mouse event, returning
 *       +1=successful read, -1=some_i/o_error.
 *       The nature of that event is determined by interrogations
 *       performed by the following calls. You can issue as many
 *       of these calls as you like against the current event.
 *       The next event isn't read until you issue
 *       the next _MOUSE_GET action.
 *      Action _MOUSE_LEFT:
 *           result = mouseread(_MOUSE_LEFT);
 *       interrogates the left mouse button, returning
 *        0 = button was up, and button remains up,
 *       +1 = button was up, and was just pressed down,
 *       -1 = button was down, and was just released,
 *       99 = button was down, and remains down.
 *      Actions _MOUSE_RIGHT, _MOUSE_MIDDLE:
 *           result = mouseread(_MOUSE_RIGHT);
 *           result = mouseread(_MOUSE_MIDDLE);
 *       interrogates the right or middle mouse button, returning
 *       the same results as above for _MOUSE_LEFT.
 *      Action _MOUSE_X:
 *           result = mouseread(_MOUSE_X);
 *        0 = no x-axis movement,
 *       +1,+2,+3,+etc = right x-axis movement by 1,2,3,etc pixels
 *       -1,-2,-3,-etc =  left x-axis movement by 1,2,3,etc pixels
 *      Action _MOUSE_Y:
 *           result = mouseread(_MOUSE_Y);
 *        0 = no y-axis movement,
 *       +1,+2,+3,+etc =    up y-axis movement by 1,2,3,etc pixels
 *       -1,-2,-3,-etc =  down y-axis movement by 1,2,3,etc pixels
 *      Action _MOUSE_WHEEL:
 *           result = mouseread(_MOUSE_WHEEL);
 *        0 = no wheel movement,
 *       +1,+2,+3,+etc =  down  wheel movement by 1,2,3,etc pixels
 *       -1,-2,-3,-etc =    up  wheel movement by 1,2,3,etc pixels
 *       note: +/- for wheel has opposite meaning as for y-axis
 *       (that appears to be the standard).
 *      Exit:
 *       Just issue the single call
 *           mousread(_MOUSE_CLOSE);
 * --------------------------------------------------------------------------
 * Revision History:
 * 07/05/16 J.Forkosh   Installation.
 * 07/09/16 J.Forkosh   Most recent revision
 *
 ****************************************************************************/

/* ---
 * header information: no mouseread.h file, instead...
 *  #define _MOUSEHEADERS
 *  #include "mouseread.c"
 * ----------------------------------------------------------------------- */
/* --- recognized mousread() actions --- */
#define _MOUSE_GET (128)        /* read (wait for) next mouse event */
#define _MOUSE_OPEN (1)         /* initialize mouse */
#define _MOUSE_CLOSE ((_MOUSE_OPEN)+1)  /* close mouse device file */
#define _MOUSE_LEFT ((_MOUSE_CLOSE)+1)  /* +1,-1,0 if left pressed,released */
#define _MOUSE_RIGHT ((_MOUSE_LEFT)+1)  /* +1,-1,0 if right pressed,released*/
#define _MOUSE_MIDDLE ((_MOUSE_RIGHT)+1)/* +1,-1,0 if middle pressed,released*/
#define _MOUSE_X ((_MOUSE_MIDDLE)+1)    /* +,-,0  right,left x-axis movement */
#define _MOUSE_Y ((_MOUSE_X)+1)     /* +,-,0  up,down y-axis movement */
#define _MOUSE_WHEEL ((_MOUSE_Y)+1) /* +,-,0  down,up wheel movement */
/* note: the LEFT,RIGHT,MIDDLE have an additional return value 99
   if the corresponding button was pressed down and remains pressed down */
/* --- end-of-header-information --- */

#if !defined(_MOUSEHEADERS)
/* -------------------------------------------------------------------------
Possibly device/installation-dependent constants
-------------------------------------------------------------------------- */
#if !defined(MOUSESTRING)
  /* string to switch mouse to ImPS/2 protocol,
   * unsigned char mousestring[] = {0xf3,200, 0xf3,100, 0xf3,80};... */
  #define MOUSESTRING "\xf3\xc8\xf3\x64\xf3\x50"
#endif
#if !defined(MOUSEDEV)
  /* must be chmod 666, i.e., o+rw... */
  #define MOUSEDEV "/dev/input/mice"
#endif

/* -------------------------------------------------------------------------
standard headers
-------------------------------------------------------------------------- */
#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

/* -------------------------------------------------------------------------
helper functions
-------------------------------------------------------------------------- */
#define bytetoint(c) ( ( ((int)(c)) < 128? ((int)(c)) : ((int)(c))-256 ) )
static  int mouseopen(void);        /* open mouse device file */
static  int mouseget(int);      /* get mouse device */
/* -------------------------------------------------------------------------
global data -- read/interpreted by mouseget(), returned to user by mouseread()
-------------------------------------------------------------------------- */
static  int x=0,y=0,wheel=0, left=0,right=0,middle=0; /* coord,button states */
static  int wasleft=0, wasright=0, wasmiddle=0; /* previous button states */

/* ==========================================================================
 * Function:    mouseread ( int action )
 * Purpose: open/read/close mouse device
 * --------------------------------------------------------------------------
 * Arguments:   action (I)  int specifying action,
 *              see Notes comments above for complete
 *              usage instructions.
 * --------------------------------------------------------------------------
 * Returns: ( int )     result of action (see Notes above)
 * --------------------------------------------------------------------------
 * Notes:     o caller can |OR (or +add) _MOUSE_GET to any interrogation
 *      request to read the next packet >>before<< checking, e.g.,
 *        instead of: mouseread(_MOUSE_GET); mouseread(_MOUSE_LEFT);
 *        just write: mouseread(_MOUSE_GET|_MOUSE_LEFT);
 *      You can >>only<< OR (or +add) _MOUSE_GET with one other action.
 *        o _MOUSE_GET will first perform a _MOUSE_OPEN if the mouse
 *      device has not already been open()'ed. But you won't get
 *      back the mouse fd. So if you need that for a select() or
 *      other purpose, make sure to issue a separate _MOUSE_OPEN
 *      and save the returned fd.
 * ======================================================================= */
/* --- entry point --- */
int mouseread ( int action  ) {
/* -------------------------------------------------------------------------
Allocations and Declarations
-------------------------------------------------------------------------- */
static  int fd = (-1);          /* fd of mouse device file after open */
int isget  = 0;         /* set true if _MOUSE_GET requested */
int result = (-1);          /* result of action, -1=error */
/* -------------------------------------------------------------------------
check if _MOUSE_GET has been |or'ed or +added to action
-------------------------------------------------------------------------- */
if ( action >= _MOUSE_GET ) {       /* _MOUSE_GET requested */
  isget = 1;                /* set flag to call mouseget() */
  action -= _MOUSE_GET; }       /* any remaining action */
/* -------------------------------------------------------------------------
open/close mouse device
-------------------------------------------------------------------------- */
if ( action == _MOUSE_OPEN ) {      /* open request */
  if ( fd == (-1) )         /* not yet opened */
    fd = mouseopen();           /* try to open it */
  result = fd;              /* return fd to caller */
  /* --- re-init global variables --- */
  x=y=wheel=0; left=right=middle=0; /* reset coord,button states */
  wasleft=wasright=wasmiddle = 0;   /* reset previous button states */
  } /* --- end-of-if(action==_MOUSE_OPEN) --- */
if ( action == _MOUSE_CLOSE ) {     /* close request */
  if ( fd != (-1) )         /* opened */
    close(fd);              /* close it */
  fd = (-1);                /* reset fd to signal closed */
  result = 1;               /* signal success to caller */
  } /* --- end-of-if(action==_MOUSE_CLOSE) --- */
/* -------------------------------------------------------------------------
read mouse device
-------------------------------------------------------------------------- */
if ( isget ) {              /* read mouse event */
  if ( fd == (-1) )         /* caller maybe forgot _MOUSE_OPEN */
    fd = mouseopen();           /* try to open it */
  if ( fd != (-1) )         /* opened */
    result = mouseget(fd);      /* read */
  result = 1;               /* signal success to caller */
  } /* --- end-of-if(action==_MOUSE_GET) --- */
/* -------------------------------------------------------------------------
determine current state of mouse buttons
-------------------------------------------------------------------------- */
/* --- left button --- */
if ( action == _MOUSE_LEFT ) {      /* check left mouse button */
  result = 0;               /* left button up and remains up */
  if (left && !wasleft) result=(+1);    /* left button pressed down */
  else if (left && wasleft) result=99;  /* left down and remains down */
  else if (!left && wasleft) result=(-1); /* left button released */
  } /* --- end-of-if(action==_MOUSE_LEFT) --- */
/* --- right button --- */
if ( action == _MOUSE_RIGHT ) {     /* check right mouse button */
  result = 0;               /* right button up and remains up */
  if (right && !wasright) result=(+1);  /* right button pressed down */
  else if (right && wasright) result=99; /* right down and remains down */
  else if (!right && wasright) result=(-1); /* right button released */
  } /* --- end-of-if(action==_MOUSE_RIGHT) --- */
/* --- middle button --- */
if ( action == _MOUSE_MIDDLE ) {    /* check middle mouse button */
  result = 0;               /* middle button up and remains up */
  if (middle && !wasmiddle) result=(+1); /* middle button pressed down */
  else if (middle && wasmiddle) result=99; /* middle down and remains down */
  else if (!middle && wasmiddle) result=(-1); /* middle button released */
  } /* --- end-of-if(action==_MOUSE_MIDDLE) --- */
/* -------------------------------------------------------------------------
determine current x,y,z(wheel)-axis movements
-------------------------------------------------------------------------- */
if ( action == _MOUSE_X ) {     /* check for x-axis movement */
  result = x;               /* 0=none, or +/- x-axis movement */
  } /* --- end-of-if(action==_MOUSE_X) --- */
if ( action == _MOUSE_Y ) {     /* check for y-axis movement */
  result = y;               /* 0=none, or +/- y-axis movement */
  } /* --- end-of-if(action==_MOUSE_Y) --- */
if ( action == _MOUSE_WHEEL ) {     /* check for z-axis/wheel movement */
  result = wheel;           /* 0=none, or +/- wheel movement */
  } /* --- end-of-if(action==_MOUSE_WHEEL) --- */
end_of_job:
  return ( result );            /* result of action back to caller */
} /* --- end-of-function mouseread() --- */


/* ==========================================================================
 * Function:    mouseopen ( void )
 * Purpose: open mouse device
 * --------------------------------------------------------------------------
 * Arguments:   void (I)    no args
 * --------------------------------------------------------------------------
 * Returns: ( int )     fd of open()'ed mouse, or -1=error
 * --------------------------------------------------------------------------
 * Notes:     o MOUSEDEV, e.g., "/dev/input/mice", must be chmod o+rw
 * ======================================================================= */
/* --- entry point --- */
static  int mouseopen ( void  ) {
/* -------------------------------------------------------------------------
Allocations and Declarations
-------------------------------------------------------------------------- */
char    *mousedev = MOUSEDEV;       /* path to mouse device file */
unsigned char *mousestring = MOUSESTRING; /* ImPS/2 initialization string */
unsigned char mbuffer[4]={0,0,0,0}; /* read to check initialization */
int mwrtlen=strlen(mousestring),    /* #initialization bytes to write */
    mrdlen = (-1);          /* #bytes read from mouse device */
int fd = (-1);          /* fd of open()'ed device, -1=error */
/* --- open the mouse device file --- */
do { fd = open(mousedev, O_RDWR | O_NOCTTY); /* open for read/write */
  } while ( fd==(-1) && errno==EINTR ); /* retry if interrupted */
if ( fd==(-1) ) goto end_of_job;    /* failed to open mouse device file */
/* --- switch mouse to ImPS/2 protocol --- */
if ( write(fd,mousestring,mwrtlen) == mwrtlen ) /* write the request, */
   mrdlen = read(fd,mbuffer,4);             /*   read the reply */
if ( mrdlen != 1 || mbuffer[0] != 0xFA) {       /* check for success, */
   close(fd); fd=(-1); goto end_of_job; }       /*   if failed then die :) */
/* --- back to caller --- */
end_of_job:
  return ( fd );            /* fd or -1=error back to caller */
} /* --- end-of-function mouseopen() --- */


/* ==========================================================================
 * Function:    mouseget ( int fd )
 * Purpose: read and interpret mouse device
 * --------------------------------------------------------------------------
 * Arguments:   fd (I)      fd of open()'ed mouse device file
 * --------------------------------------------------------------------------
 * Returns: ( int )     get status
 * --------------------------------------------------------------------------
 * Notes:     o IntelliMouse protocol uses four byte reports:
 *             Bit   7     6     5     4     3     2     1     0
 *        --------+-----+-----+-----+-----+-----+-----+-----+-----
 *         Byte 0 |  0     0   Neg-Y Neg-X   1    Mid  Right Left
 *         Byte 1 |  X     X     X     X     X     X     X     X
 *         Byte 2 |  Y     Y     Y     Y     Y     Y     Y     Y
 *         Byte 3 |  W     W     W     W     W     W     W     W
 *      XXXXXXXX, YYYYYYYY, and WWWWWWWW are 8-bit two's complement
 *      values indicating changes in x-coordinate, y-coordinate,
 *      and scroll wheel.
 *         That is, 0 = no change, 1..127 = positive change +1 to +127,
 *      and 129..255 = negative change -127 to -1.
 *         Left, Right, and Mid are the three button states,
 *      1 if being depressed.  Neg-X and Neg-Y are set if XXXXXXXX
 *      and YYYYYYYY are negative, respectively.
 * ======================================================================= */
/* --- entry point --- */
static  int mouseget ( int fd  ) {
/* -------------------------------------------------------------------------
Allocations and Declarations
-------------------------------------------------------------------------- */
unsigned char mbuffer[4]={0,0,0,0}; /* read buffer */
int mrdlen = (-1);          /* #bytes read from mouse device */
int status = (-1);          /* i/o status */
/* --- reset previous button states --- */
wasleft=left;  wasmiddle=middle;  wasright=right; /* previous button states */
/* -------------------------------------------------------------------------
read four bytes into mbuffer
-------------------------------------------------------------------------- */
/* --- read --- */
if ( fd == (-1) ) goto end_of_job;  /* invalid/not_open fd */
do { mrdlen = read(fd,mbuffer,4);   /* read four-byte protocol */
  } while ( mrdlen==(-1) && errno==EINTR ); /* retry if interrupted */
if ( mrdlen == (-1) ) goto end_of_job;  /* return -1 error if read failed */
/* --- only interested in four-byte reports with bit 3 set in 1st byte --- */
if ( mrdlen != 4 || !(mbuffer[0] & 0x08)) { /* unwanted packet */
  status=0; goto end_of_job; }      /* signal 0="nothing" to caller */
/* -------------------------------------------------------------------------
interpret the fields of the four-byte report packet
-------------------------------------------------------------------------- */
x=y=wheel=0; left=right=middle=0;   /* reset global coord,button states */
left   = mbuffer[0] & 1;        /* bit#0 (low-order bit) of 1st byte */
right  = mbuffer[0] & 2;        /* bit#1 of 1st byte */
middle = mbuffer[0] & 4;        /* bit#2 of 1st byte */
x      = bytetoint(mbuffer[1]);     /* 2nd byte */
y      = bytetoint(mbuffer[2]);     /* 3rd byte */
wheel  = bytetoint(mbuffer[3]);     /* 4th byte */
/* --- back to caller --- */
status = 1;             /* success */
end_of_job:
  return ( status );            /* read status */
} /* --- end-of-function mouseget() --- */
#endif  /* --- #if !defined(_MOUSEHEADERS) --- */


#if defined(TESTMOUSEREAD)
/* ==========================================================================
 * Function:    main ( int argc, char *argv[] )
 * Purpose: test driver for mouseread()
 * --------------------------------------------------------------------------
 * Arguments:   argc (I)    (int) containing the usual...  unused
 *      argv (I)    (char **) containing...        unused
 * --------------------------------------------------------------------------
 * Returns: ( int )     exit(1) status
 * --------------------------------------------------------------------------
 * Notes:     o exercises mouseread(), compile as
 *      cc -DTESTMOUSEREAD mouseread.c -o mouseread
 * ======================================================================= */
/* --- entry point --- */
int main ( int argc, char *argv[] ) {
/* -------------------------------------------------------------------------
Allocations and Declarations
-------------------------------------------------------------------------- */
int msglevel = (argc>1?atoi(argv[1]):3);  /* currently unused */
int mouseread(int), fd = mouseread(_MOUSE_OPEN);
int result=0, nactions=0;
/* -------------------------------------------------------------------------
interrogate mouse
-------------------------------------------------------------------------- */
if ( fd < 0 ) {             /* probably forgot to chmod o+rw */
  if(msglevel>0) printf("Unable to open %s\n",MOUSEDEV);  goto end_of_job; }
if(msglevel>0) printf("Move,click,etc mouse. Ctrl-C to exit...\n");
while ( 1 ) {
  nactions++;
  printf("(%d) ",nactions);
  result = mouseread(_MOUSE_GET|_MOUSE_X);  /*GET the next queued mouse event*/
    if (result!=0) printf(" x%+d", result); /* | and check for x-axis movement*/
  result = mouseread(_MOUSE_Y);             /* check for y-axis movement */
    if (result!=0) printf(" y%+d", result); /* 0=none, + = up, - = down */
  result = mouseread(_MOUSE_WHEEL);         /* check for wheel movement */
    if (result!=0) printf(" w%+d", result); /* 0=none, + = up, - = down */
  result = mouseread(_MOUSE_LEFT);          /* check left button */
    if (result!=0) printf( " %s",           /* see Notes for return values */
      (result==1?"LEFT-PRESSED":(result<0?"LEFT-RELEASED":"LEFT-DOWN")) );
  result = mouseread(_MOUSE_RIGHT);         /* check right button */
    if (result!=0) printf( " %s",
      (result==1?"RIGHT-PRESSED":(result<0?"RIGHT-RELEASED":"RIGHT-DOWN")) );
  result = mouseread(_MOUSE_MIDDLE);        /* check middle button */
    if (result!=0) printf( " %s",
      (result==1?"MIDDLE-PRESSED":(result<0?"MIDDLE-RELEASED":"MIDDLE-DOWN")) );
  printf("\n"); fflush(stdout);             /* ready to GET next event */
  } /* --- end-of-while(1) --- */
end_of_job:
  mouseread(_MOUSE_CLOSE);
  exit(1);
} /* --- end-of-function main() --- */
#endif  /* --- #if defined(TESTMOUSEREAD) --- */
/* =======================================================================
END-OF-FILE MOUSEREAD.C
========================================================================== */

Upvotes: 1

John Forkosh
John Forkosh

Reputation: 522

Note: this is just a followup to NominalAnimal's preceding answer containing his demo program illustrating low-level mouse handling in C. Thanks, Nominal.

His original code worked perfectly on my box, except for the Middle-down button. And I started messing with the code trying to figure that out, before realizing it's kind of an artifact. X uses Middle-down for "paste" and was somehow (I still don't understand exactly) grabbing portions of preceding printf's and re-pasting them into the output stream every time Middle-down was pressed. The fflush at the beginning of each pass through the while() loop seems to have fixed that. Now pressing Middle-down also works fine (as long as your paste buffer's empty)...

#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#define RECOGNIZED(s) { printf(s); fflush(stdout); recognized++; }

static const size_t        mousedev_seq_len    = 6;
static const unsigned char mousedev_imps_seq[] =
                { 0xf3, 200, 0xf3, 100, 0xf3, 80 };

static int bytetoint(const unsigned char c) {
    return ( (c < 128? c : c-256) ); }

int main(int argc, char *argv[]) {
    char          *devpath = (argc>1?argv[1]:"/dev/input/mice");
    int           msglevel = (argc>2?atoi(argv[2]):3);
    int           devfd = (-1);
    unsigned char buffer[4];
    ssize_t       len = (-1);
    int           nactions=0, wasleft=0, wasmiddle=0, wasright=0;

    /* Open the mouse. */
    fflush(NULL);
    do { devfd = open(devpath, O_RDWR | O_NOCTTY);
       } while (devfd == -1 && errno == EINTR);
    if (devfd == -1) {
        printf("Cannot open %s: %s.\n", devpath, strerror(errno));
        goto end_of_job; }

    /* Switch the mouse to ImPS/2 protocol. */
    if (write(devfd, mousedev_imps_seq, mousedev_seq_len)
    != (ssize_t)mousedev_seq_len) {
        printf("Cannot switch to ImPS/2 protocol.\n");
        goto end_of_job; }

    if (read(devfd, buffer, sizeof buffer) != 1 || buffer[0] != 0xFA) {
        printf("Failed to switch to ImPS/2 protocol.\n");
        goto end_of_job; }

    printf("Mouse device %s opened successfully.\n", devpath);
    printf("Press CTRL+C to exit.\n" /*, (int)getpid()*/ );

    /* IntelliMouse protocol uses four byte reports:
     *      Bit   7     6     5     4     3     2     1     0
     * --------+-----+-----+-----+-----+-----+-----+-----+-----
     *  Byte 0 |  0     0   Neg-Y Neg-X   1    Mid  Right Left
     *  Byte 1 |  X     X     X     X     X     X     X     X
     *  Byte 2 |  Y     Y     Y     Y     Y     Y     Y     Y
     *  Byte 3 |  W     W     W     W     W     W     W     W
     *
     * XXXXXXXX, YYYYYYYY, and WWWWWWWW are 8-bit two's complement values
     * indicating changes in x-coordinate, y-coordinate, and scroll wheel.
     * That is, 0 = no change, 1..127 = positive change +1 to +127,
     * and 129..255 = negative change -127 to -1.
     *
     * Left, Right, and Mid are the three button states, 1 if being depressed.
     * Neg-X and Neg-Y are set if XXXXXXXX and YYYYYYYY are negative,
     * respectively.  */

    while (1) {
        int x, y, wheel, left, middle, right;
        int recognized = 0;
        nactions++;
        fflush(stdout);

        len = read(devfd, buffer, 4);
        if (len == -1) {
          if (errno == EINTR) continue;
          printf("%s.\n", strerror(errno));
          break; }
        /* We are only interested in four-byte reports,
         * that have bit 3 set in the first byte. */
        if (len != 4 || !(buffer[0] & 0x08)) {
          printf("Warning: Ignored a %d-byte report.\n", (int)len);
          continue; }

        /* --- Unpack. --- */
        left = buffer[0] & 1;
        middle = buffer[0] & 4;
        right = buffer[0] & 2;
        x = bytetoint(buffer[1]);
        y = bytetoint(buffer[2]);
        wheel = bytetoint(buffer[3]);
        if ( msglevel >= 1 ) {
          printf("(%d) buffer=%02x,%02x,%02x,%02x"
                 ", xy=%d,%d, lmrw=%d,%d,%d,%d:  ",
                 nactions, buffer[0],buffer[1],buffer[2],buffer[3],
                 x,y, left,middle,right,wheel); fflush(stdout); }

        /* --- Describe: --- */
        if (x) { RECOGNIZED(" x"); printf("%+d", x); }
        if (y) { RECOGNIZED(" y"); printf("%+d", y); }
        if (wheel) { RECOGNIZED(" w"); printf("%+d", wheel); }

        if (left && !wasleft) { RECOGNIZED(" LeftDown"); }
        else if (left && wasleft) { RECOGNIZED(" Left"); }
             else if (!left && wasleft) { RECOGNIZED(" LeftUp"); }

        if (middle && !wasmiddle) { RECOGNIZED(" MiddleDown"); }
        else if (middle && wasmiddle) { RECOGNIZED(" Middle"); }
             else if (!middle && wasmiddle) { RECOGNIZED(" MiddleUp"); }

        if (right && !wasright) { RECOGNIZED(" RightDown"); }
        else if (right && wasright) { RECOGNIZED(" Right"); }
             else if (!right && wasright) { RECOGNIZED(" RightUp"); }

        printf(" (recognized %d)\n",recognized);
        wasleft=left;  wasmiddle=middle;  wasright=right;
        } /* --- end-of-while(1) --- */

    /* Done. */
    end_of_job: if ( devfd!=(-1) ) close(devfd);
    fflush(NULL);
    return EXIT_SUCCESS;
    } /* --- end-of-main() --- */

And a few lines of demo output...

Mouse device /dev/input/mice opened successfully.
Press CTRL+C to exit.
(1) buffer=0c,00,00,00, xy=0,0, lmrw=0,4,0,0:   MiddleDown (recognized 1)
(2) buffer=08,00,00,00, xy=0,0, lmrw=0,0,0,0:   MiddleUp (recognized 1)
(3) buffer=0c,00,00,00, xy=0,0, lmrw=0,4,0,0:   MiddleDown (recognized 1)
(4) buffer=08,00,00,00, xy=0,0, lmrw=0,0,0,0:   MiddleUp (recognized 1)
(5) buffer=09,00,00,00, xy=0,0, lmrw=1,0,0,0:   LeftDown (recognized 1)
(6) buffer=08,00,00,00, xy=0,0, lmrw=0,0,0,0:   LeftUp (recognized 1)
(7) buffer=09,00,00,00, xy=0,0, lmrw=1,0,0,0:   LeftDown (recognized 1)
(8) buffer=08,00,00,00, xy=0,0, lmrw=0,0,0,0:   LeftUp (recognized 1)
(9) buffer=0a,01,00,00, xy=1,0, lmrw=0,0,2,0: x+1 RightDown(recognized 2)
(10) buffer=08,00,00,00, xy=0,0, lmrw=0,0,0,0:   RightUp (recognized 1)
(11) buffer=0a,00,00,00, xy=0,0, lmrw=0,0,2,0:   RightDown (recognized 1)
(12) buffer=08,00,00,00, xy=0,0, lmrw=0,0,0,0:   RightUp (recognized 1)
(13) buffer=18,ff,00,00, xy=-1,0, lmrw=0,0,0,0:   x-1 (recognized 1)
(14) buffer=18,ff,00,00, xy=-1,0, lmrw=0,0,0,0:   x-1 (recognized 1)
(15) buffer=18,fe,00,00, xy=-2,0, lmrw=0,0,0,0:   x-2 (recognized 1)
(16) buffer=18,fd,00,00, xy=-3,0, lmrw=0,0,0,0:   x-3 (recognized 1)
(17) buffer=18,fd,00,00, xy=-3,0, lmrw=0,0,0,0:   x-3 (recognized 1)

Upvotes: 1

Nominal Animal
Nominal Animal

Reputation: 39426

You can also use the old-style mouse interface (/dev/mouse, /dev/input/mouseN, or from all mice connected to the machine, /dev/input/mice). You do need to switch the device to four-byte ImPS protocol to support all three buttons and the wheel, but that is easy: just write the six bytes 0xf3, 200, 0xf3, 100, 0xf3, 80, and read the ACK byte (0xfa).

Consider the following example program. You can specify it the mousedev device it should read from; if none specified, it defaults to /dev/input/mice:

#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

static const size_t        mousedev_seq_len    = 6;
static const unsigned char mousedev_imps_seq[] = { 0xf3, 200, 0xf3, 100, 0xf3, 80 };

static volatile sig_atomic_t  done = 0;

static void handle_done(int signum)
{
    done = 1;
}

static int install_done(const int signum)
{
    struct sigaction  act;

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);
    act.sa_handler = handle_done;
    act.sa_flags = 0;
    if (sigaction(signum, &act, NULL) == -1)
        return errno;

    return 0;
}

static int bytetoint(const unsigned char c)
{
    if (c < 128)
        return c;
    else
        return c - 256;
}

int main(int argc, char *argv[])
{
    unsigned char buffer[4];
    ssize_t       len;
    const char   *devpath = "/dev/input/mice";
    int           devfd;
    int           wasleft, wasmiddle, wasright;

    if (argc < 1 || argc > 2 || (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")))) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s -h | --help\n", argv[0]);
        fprintf(stderr, "       %s /dev/input/mouseX\n", argv[0]);
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    if (argc == 2)
        devpath = argv[1];

    if (install_done(SIGINT) ||
        install_done(SIGTERM) ||
        install_done(SIGHUP)) {
        fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    /* Open the mouse. */
    do {
        devfd = open(devpath, O_RDWR | O_NOCTTY);
    } while (devfd == -1 && errno == EINTR);
    if (devfd == -1) {
        fprintf(stderr, "Cannot open %s: %s.\n", devpath, strerror(errno));
        return EXIT_FAILURE;
    }

    /* Switch the mouse to ImPS/2 protocol. */
    if (write(devfd, mousedev_imps_seq, mousedev_seq_len) != (ssize_t)mousedev_seq_len) {
        fprintf(stderr, "Cannot switch to ImPS/2 protocol.\n");
        close(devfd);
        return EXIT_FAILURE;
    }
    if (read(devfd, buffer, sizeof buffer) != 1 || buffer[0] != 0xFA) {
        fprintf(stderr, "Failed to switch to ImPS/2 protocol.\n");
        close(devfd);
        return EXIT_FAILURE;
    }

    /* IntelliMouse protocol uses four byte reports:
     *      Bit   7     6     5     4     3     2     1     0
     * --------+-----+-----+-----+-----+-----+-----+-----+-----
     *  Byte 0 |  0     0   Neg-Y Neg-X   1    Mid  Right Left
     *  Byte 1 |  X     X     X     X     X     X     X     X
     *  Byte 2 |  Y     Y     Y     Y     Y     Y     Y     Y
     *  Byte 3 |  W     W     W     W     W     W     W     W
     *
     * XXXXXXXX, YYYYYYYY, and WWWWWWWW are 8-bit two's complement values
     * indicating changes in x-coordinate, y-coordinate, and scroll wheel.
     * That is, 0 = no change, 1..127 = positive change +1 to +127,
     * and 129..255 = negative change -127 to -1.
     *
     * Left, Right, and Mid are the three button states, 1 if being depressed.
     * Neg-X and Neg-Y are set if XXXXXXXX and YYYYYYYY are negative, respectively.
    */

    fprintf(stderr, "Mouse device %s opened successfully.\n", devpath);
    fprintf(stderr, "Press CTRL+C (or send INT, TERM, or HUP signal to process %d) to exit.\n",
                    (int)getpid());
    fflush(stderr);

    wasleft = 0;
    wasmiddle = 0;
    wasright = 0;

    while (!done) {
        int x, y, wheel, left, middle, right;

        len = read(devfd, buffer, 4);
        if (len == -1) {
            if (errno == EINTR)
                continue;
            fprintf(stderr, "%s.\n", strerror(errno));
            break;
        } else
        if (len != 4 || !(buffer[0] & 0x08)) {
            /* We are only interested in four-byte reports,
             * that have bit 3 set in the first byte. */
            fprintf(stderr, "Warning: Ignored a %d-byte report.\n", (int)len);
            continue;
        }

        /* Unpack. */
        left = buffer[0] & 1;
        middle = buffer[0] & 4;
        right = buffer[0] & 2;
        x = bytetoint(buffer[1]);
        y = bytetoint(buffer[2]);
        wheel = bytetoint(buffer[3]);

        /* Describe: */

        if (x)
            printf(" x%+d", x);

        if (y)
            printf(" y%+d", y);

        if (wheel)
            printf(" w%+d", wheel);

        if (left && !wasleft)
            printf(" LeftDown");
        else
        if (left && wasleft)
            printf(" Left");
        else
        if (!left && wasleft)
            printf(" LeftUp");

        if (middle && !wasmiddle)
            printf(" MiddleDown");
        else
        if (middle && wasmiddle)
            printf(" Middle");
        else
        if (!middle && wasmiddle)
            printf(" MiddleUp");

        if (right && !wasright)
            printf(" RightDown");
        else
        if (right && wasright)
            printf(" Right");
        else
        if (!right && wasright)
            printf(" RightUp");

        printf("\n");
        fflush(stdout);

        wasleft = left;
        wasmiddle = middle;
        wasright = right;
    }

    /* Done. */
    close(devfd);
    return EXIT_SUCCESS;
}

Here's a snippet of the output on my machine (and a cheap Logitech mouse). x refers to changes in the x coordinate, y to changes in the y coordinate, w to changes in the wheel state, and so on.

Mouse device /dev/input/mice opened successfully.
Press CTRL+C (or send INT, TERM, or HUP signal to process 10356) to exit.
 x-1
 x-1 y-1
 x-1
 x-2
 x-1 y-1
 x-1
 x-1
 x-1 y-1
 y+1
 y+1
 y+1
 RightDown
 x-1 Right
 x-2 y+1 Right
 x-2 Right
 x-1 Right
 y+1 Right
 x-1 Right
 x-2 Right
 x-1 Right
 x-2 Right
 x-1 Right
 y+1 Right
 x-1 Right
 x-2 Right
 x-1 Right
 RightUp
 y-1
 y-1
 LeftDown
 y-1 Left
 x+1 Left
 x+1 Left
 x+1 Left
 x+1 Left
 LeftUp
 w+1
 w+1
 w-1
 w-2
 w-1
 w+1
 w+1
 w+2
 w+1
 w+1
 w+1
 w+1
 w-1
 w-1
 w-1
 w-1
 w-1
 w-1
 w-1
 w-1

Upvotes: 4

Nominal Animal
Nominal Animal

Reputation: 39426

I shall assume you are using the event input subsystem (instead of /dev/input/mice) because you wish to read directly form a specific mouse, not from any mice connected to the machine.

The canonical documentation is at doc/Documentation/input/event-codes.txt in (the documentation for) the Linux kernel. That links takes you to the up-to-date web page.

  • type=EV_REL, code=REL_WHEEL, value=1 (2,8,1) indicates (vertical) scroll wheel by one tick forward. The value may be larger than 1 if the user rotates the wheel fast, or if it is a programmable mouse with "fast" scroll wheel mode.

  • type=EV_REL, code=REL_WHEEL, value=-1 (2,8,-1) indicates (vertical) scroll wheel by one tick backward. The value may be smaller than -1 if the user rotates the wheel fast, or if it is a progammable mouse with "fast" scroll wheel mode.

  • Many mice have horizontal scroll wheels. These work the same way as the vertical scroll wheel, except the code is REL_HWHEEL.

  • Other interesting type=EV_REL codes are REL_X, REL_Y, REL_Z (relative movement, REL_Z being "height" or distance from table for 3D mice); REL_RX, REL_RY, REL_RZ for rotation around each axis for things like 3D mice with six-axis accelerometers; and REL_DIAL for jog wheels.

  • type=EV_KEY, code=BTN_MOUSE, value=1 (1,272,1) indicates a mouse click (left click on multi-button mice), and value=0 (1,272,0) a release.

    The code can also be any other KEY_ or BTN_ constant. value is zero for release, nonzero for press.

    In particular, BTN_MOUSE=BTN_LEFT, right mouse button is BTN_RIGHT, middle mouse button is BTN_MIDDLE, side button is BTN_SIDE, extra button is BTN_EXTRA, task button is BTN_TASK, and forward and backward buttons (like on some Logitech mice) are BTN_FORWARD and BTN_BACK.

  • type=EV_MSC, code=MSC_SCAN (4,4,value) provide keyboard scan codes for key/button events not standardized by USB/HID. I do believe you can just ignore these (they often are duplicates of actual events for some odd reason, probably backwards Windows compatibility).

  • type=EV_SYN, code=SYN_REPORT (0,0), is a synchronization event; it means that at this point, the input event state has been completely updated.

    You receive zero or more input records, followed by a type=EV_SYN, code=SYN_REPORT (0,0), for events that happened "at the same time".

    Typically, the HID device will report the changes on all axes and all buttons in one chunk, followed by one of these. This makes a lot of sense, because we want our pointers to move according to the real movement, and not just horizontally/vertically... it'd look weird.

Overall, this is the Linux Input Subsystem, and is extremely stable; it won't change. (New keys, buttons etc. may be added, but existing ones should never change.) The Linux Input Subsystem articles I and II at LinuxJournal (from 2003) are still relevant as background information.

Upvotes: 3

Related Questions