Driver for Acer T230H

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Pre-note:
I wrote this mail for the linux kernel development list, and realized, once finished writing the mail, that linux-input would be a better place to post this. I searched the archives and found a few mails that I missed before starting to write this driver and mail. So I understand that some work is already going on, and some might require changes to the HID system?

Anyway... I hope my mail may still be of some help to the process. My (proof of concept) driver is functional for the Acer T230H. I am currently using it to my great amusement :-D


Hi all of you,

This mail is in regard to an Acer T230H touchscreen, and aimed to
 - Daniel Ritz, who is the maintainer of the usbtouchscreen kernel module
 - Alex Custov, whom's friend also have this screen
 - Linux kernel input list

You are all on the CC, because I hope that some of you can and will help me finish the driver for this screen.

So... I got this screen, and in Linux it is recognized only as /dev/hidraw0 and /dev/usb/hiddev0 devices. I really wanted to get it to work, so I sat down and analyzed the output, and wrote a small program as a proof-of-concept driver.

Once I got this working, I went to the kernel source of the usbtouchscreen driver, and tried to expand it to include support for this screen at kernel space - but failed. Ii could not get the driver to properly register my screen - no matter what I did, it always ended up as the hiddev and hidraw devices. (And kernel messages were not very helpful).

Anyway... I have never hacked the kernel source before, and - due to lack of time - I now give up, and hope some of you will take your time to include my code in the driver.

I have attached my sample driver. It opens the hidraw device, reads data, parses it, and passes it on to Xorg (via XTest). The parsing is done in the decodeData()-function. The rest is done in mail(). I hope I have written enough comments for my code to be readable by you.

What I hope you can do, is
- Expand the usbtouchscreen driver to register the screen (vendor:product id 0408:3000)
 - Copy the decodeData function and pass the raw data to it.
- Send the parsed data to the kernel event interface - hopefully with some sensible mappings of the axes.

To Alex: Until support is in the kernel, you should be able to use my program get the screen up'n'running.

The screen supports two fingers touch. I have implemented the following functionality:
 - One finger press: Left click'n'drag.
 - Two fingers press:
   - Center point of fingers are calculated.
   - Distance between fingers is calculated.
- If one finger is released between center or distance has changed over a certain threshold, a right click'n'drag is initiated. - If center point is moved beyond a threshold, scrolling mode is initiated. Vertical and horizontal scroll is affected by the movement of the center. Relative coordinates. - If distance is changed beyond a threshold, well mode is initiated. Wheel is turned to the change of distance. Relative movement. - If both fingers are released before any mode is initiated, a right click and release is performed.

The threshold is currently hardcoded into the program. I know this is ugly - but again (due to my lack of knowledge on kernel programming), I don't know how to make this a parameter that can be changed. (I expect one need to make an interface, that HAL can connect to, and in the end, someone need to make a nice gui program to change the settings... but I know this is probably further than you want to go right now. I would be glad, if you in kernel space, would make the necessary preparations for it to be possible in the future.)

Note, that my sample program opens the file /dev/input/t230h, which I on my system have set up to be a symlink to /dev/hidraw0. If you do not want to do this just for testing the driver, just change the input file.

To Alex: I got a udev-rule, which you might find handy too:
SUBSYSTEM=="hidraw", ATTRS{manufacturer}=="Acer", ATTRS{product}=="T230H", SYMLINK+="input/t230h", GROUP="plugdev"

I have attached the output from lsusb about the device - in case you think it is useful.

I hope that one of you will take your time to include this into the kernel driver. A big thanks to him or her :-) I will of course be happy with what help I can offer - but I have not got much time to do it. The screen is actually for my parents, and I will set it up for them when I visit them in the Christmas holidays.

Best regards
Jørn

/**
 * Proof-of-concept driver for the Acer T230H touchscreen.
 * By JVC, December 2009.
 */
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <strings.h>

Display *display;
Window winRoot;

#define ACER_T230H_GARB_0 0 /* Was 0 for a long time, but suddenly changed to 247 */
#define ACER_T230H_GARB_1 1 /* Always 1. Sync ? Don't quite trust it - also thought that the above was sync - but proved wrong after a whole day's use... */

#define ACER_T230H_GOT_DATA 2 /* 0 on no data - 7 when finger pressed - 4 when releasing finger (and last data input from that source) */
#define ACER_T230H_IS_POINTER 3 /* 0 if this finger is the (mouse) pointer - see note */
#define ACER_T230H_POS_X_LSB 4
#define ACER_T230H_POS_X_MSB 5
#define ACER_T230H_POS_Y_LSB 6
#define ACER_T230H_POS_Y_MSB 7

#define ACER_T230H_GOT_DATA_2ND 8 /* 0 on no data - 7 when finger pressed - 4 when releasing finger (and last data input from that source) */
#define ACER_T230H_IS_POINTER_2ND 9 /* 0 if this finger is the (mouse) pointer - see note */
#define ACER_T230H_POS_2ND_X_LSB 10
#define ACER_T230H_POS_2ND_X_MSB 11
#define ACER_T230H_POS_2ND_Y_LSB 12
#define ACER_T230H_POS_2ND_Y_MSB 13

#define ACER_T230H_TOUCH_NUM_FINGERS 14 /* 0 (just when putting finger down) - 2 */

/**
 * Note: The pointer finger will normaly be the first finger pressed.
 *       But if the two fingers are pressed, and the first finger is lifted,
 *       the second finger will receive the pointer flag - AND the data from
 *       the second finger is automatically copied to the first.
 */

#define BUTTON_LEFT 1
#define BUTTON_MIDDLE 2
#define BUTTON_RIGHT 4
struct ev_data {
	int x, y, button, scrollH, scrollV, wheel;
};

#define MOVEMENT_THRESHOLD 40 /* Movement threshold in pixels for mode 2. If finger is released before this threshold has been moved => right finger click. Else wheel or scrool. */

bool decodeData(unsigned char pkt[], struct ev_data *ev) {
    static int mode = 0; /* mode/buttons pressed. 0 => no buttons. 1 => left finger. 2 => two fingers => multi functionality */
    static unsigned int last_x = 0;
    static unsigned int last_y = 0;
    static unsigned int last_d = 0;
    unsigned int x, y, x2, y2, tmpd, tmpx, tmpy;
    int raisingFingerCount = 0;
    bool releaseOne; /* True if a finger is released, and the other is still pressed */
    bool releaseFull; /* True if a finger is released, and no more fingers are pressed */
    static bool pause = false; /* When scrolling or using wheel - one finger can be released to pause action */

/*    
    static int lastMode = 0;
    if(mode != lastMode) {
        fprintf(stderr, "Entering mode %d\n", mode);
        lastMode = mode;
    }
*/    
    
	/* packets should start with sync */
/* Apparently the first byte was not sync anyhow... it changed from 0 to 247 for no apparent reason
	if (pkt[ACER_T230H_SYNC_0] != 0 || pkt[ACER_T230H_SYNC_1] != 1) {
	    mode = 0;
		return false;
    }
*/
    
    /* Zero out buttons and relative axis */
    ev->button = 0;
    ev->scrollH = 0;
    ev->scrollV = 0;
    ev->wheel = 0;
    
	/* Calculate position and fingers released - always available */
	x = (pkt[ACER_T230H_POS_X_MSB] << 8) | pkt[ACER_T230H_POS_X_LSB]; if(x > 1919) x = 1919;
	y = (pkt[ACER_T230H_POS_Y_MSB] << 8) | pkt[ACER_T230H_POS_Y_LSB]; if(y > 1079) y = 1079;
    x2 = (pkt[ACER_T230H_POS_2ND_X_MSB] << 8) | pkt[ACER_T230H_POS_2ND_X_LSB]; if(x2 > 1919) x2 = 1919;
    y2 = (pkt[ACER_T230H_POS_2ND_Y_MSB] << 8) | pkt[ACER_T230H_POS_2ND_Y_LSB]; if(y2 > 1079) y2 = 1079;
    releaseOne = (pkt[ACER_T230H_GOT_DATA] == 4 && pkt[ACER_T230H_GOT_DATA_2ND] == 7) || (pkt[ACER_T230H_GOT_DATA_2ND] == 4 && pkt[ACER_T230H_GOT_DATA] == 7);
    releaseFull = (pkt[ACER_T230H_GOT_DATA] == 4 && pkt[ACER_T230H_GOT_DATA_2ND] != 7) || (pkt[ACER_T230H_GOT_DATA_2ND] == 4 && pkt[ACER_T230H_GOT_DATA] != 7);
    
    /* State machine. States:
     *  0: No finger pressed. 'Idle'. Next states: 1, 2
     *  1: One finger pressed: Left finger. Next states: 0.
     *  2: Multi-touch mode. Function not decided. Next states: 3, 4, 5.
     *  3: Multi.touch mode. Right click. Next states: 0.
     *  4: Multi.touch mode. Scroll. Next states: 0.
     *  5: Multi.touch mode. Wheel. Next states: 0.
     *
     * In addition to next states above, it is also valid to have no state change.
     */
    if(pkt[ACER_T230H_TOUCH_NUM_FINGERS] == 0) { /* Reset to mode zero */
        mode = 0;
        last_x = x;
        last_y = y;
        ev->x = x;
        ev->y = y;
        return true;
    }
    else if(mode == 0) { /* 'Idle' */
        if(pkt[ACER_T230H_TOUCH_NUM_FINGERS] == 1) { /* Single finger => left finger */
            mode = 1;
            ev->x = x;
            ev->y = y;
            ev->button = BUTTON_LEFT;
            return true;
        }

        if(pkt[ACER_T230H_TOUCH_NUM_FINGERS] == 2) { /* Two fingers => Multi mode */
            mode = 2;

            last_x = (x + x2) >> 1; /* Becomes center coordinates */
            last_y = (y + y2) >> 1; /* Becomes center coordinates */
            last_d = (unsigned int) sqrtf((x - x2) * (x - x2) + (y - y2) * (y - y2));
            
            ev->x = x;
            ev->y = y;
            return true;
        }

        mode = 0;
        return false; /* Unknown value for ACER_T230H_TOUCH_NUM_FINGERS */
    }
    else if(mode == 1) { /* Left finger */
        if(releaseFull) { /* Exit mode */
            mode = 0;
            return true;
        }

        if(pkt[ACER_T230H_TOUCH_NUM_FINGERS] == 1) { /* Keep left finger pressed */
            ev->x = x;
            ev->y = y;
            ev->button = BUTTON_LEFT;
            return true;
        }
            
        if(pkt[ACER_T230H_TOUCH_NUM_FINGERS] == 2) {
            ev->x = x;
            ev->y = y;
            if(releaseOne) /* Had two fingers, but going back to one */
                ev->button = BUTTON_LEFT;
            else /* Two fingers */
                ev->button = BUTTON_LEFT | BUTTON_RIGHT;

            return true;
        }

        /* Error - no fingers pressed ? */
        mode = 0;
        return false;
    }
    else if(mode == 2) { /* Multi touch - undecided function */
        tmpx = (x + x2) >> 1; /* Center coordinates */
        tmpy = (y + y2) >> 1; /* Center coordinates */
        tmpd = (unsigned int) sqrtf((x - x2) * (x - x2) + (y - y2) * (y - y2));

        /* Releasing two fingers => Make a right click and go back to mode 0 */
        if(releaseFull) { 
            mode = 0;
            ev->x = tmpx;
            ev->y = tmpy;
            ev->button = BUTTON_RIGHT;
            return true;
        }
        
        /* Wheel moved more than threshold => wheel mode */
        if(abs(tmpd - last_d) > MOVEMENT_THRESHOLD) { 
            mode = 5;
            ev->x = tmpx;
            ev->y = tmpy;
            ev->wheel = tmpd - last_d;
            last_d = tmpd;
            return true;
        }
        
        /* Center moved more than threshold => scroll mode */
        if((unsigned int) sqrtf((last_x - tmpx) * (last_x - tmpx) + (last_y - tmpy) * (last_y - tmpy)) > MOVEMENT_THRESHOLD) { 
            ev->scrollH = tmpx - last_x;
            ev->scrollV = tmpy - last_y;
            last_x = tmpx;
            last_y = tmpy;
            mode = 4;
            return true;
        }
        
        /* No movement exceeded threshold - and release finger => right click */
        if(releaseOne) {
            mode = 3;
            ev->x = tmpx;
            ev->y = tmpy;
            ev->button = BUTTON_RIGHT;
        }
            

        /* No decicion yet */            
        return true;
    }
    else if(mode == 3) { /* Multi touch - right button */
        if(releaseFull) {
            mode = 0;
            return true;
        }
        
        /* Only accept one finger press */
        if(pkt[ACER_T230H_TOUCH_NUM_FINGERS] == 1) { 
            ev->x = x;
            ev->y = y;
            ev->button = BUTTON_RIGHT;
            return true;
        }

        /* No movement - but keep button pressed */
        ev->button = BUTTON_RIGHT;
        return true;
    }
    else if(mode == 4) { /* Multi touch - scroll */
        if(releaseFull) {
            mode = 0;
            return true;
        }

        /* Only accept two fingers press */
        if(pkt[ACER_T230H_TOUCH_NUM_FINGERS] == 2) { 
            tmpx = (x + x2) >> 1; /* Center coordinates */
            tmpy = (y + y2) >> 1; /* Center coordinates */
            if(!pause) {
                ev->scrollH = tmpx - last_x;
                ev->scrollV = tmpy - last_y;
            }
            pause = false;
            last_x = tmpx;
            last_y = tmpy;
            return true;
        }
        
        /* Silently do nothing */
        pause = true;
        return true;
    }
    else if(mode == 5) { /* Multi touch - wheel */
        if(releaseFull) {
            mode = 0;
            return true;
        }
    
        /* Only accept two fingers press */
        if(pkt[ACER_T230H_TOUCH_NUM_FINGERS] == 2) { 
            tmpd = (unsigned int) sqrtf((x - x2) * (x - x2) + (y - y2) * (y - y2));
            tmpx = (x + x2) >> 1; /* Center coordinates */
            tmpy = (y + y2) >> 1; /* Center coordinates */
            ev->x = tmpx;
            ev->y = tmpy;
            if(!pause)
                ev->wheel = tmpd - last_d;
            last_d = tmpd;
            pause = false;
            return true;
        }

        /* Silently do nothing */
        pause = true;
        return true;
    }
    
    /* Should not happen */
    mode = 0;
    return false;
}



int main(int argc, char *argv[]) {
  FILE *hidraw = fopen("/dev/input/t230h", "rb");
  if(hidraw == NULL) {
    fprintf(stderr, "Could not open /dev/input/t230h. Exiting.\n"); /* /dev/input/t230h is symlink to /dev/hidraw? */
    return 1;
  }
  
  unsigned char buf[15];
  bool left = false, right = false;

  struct ev_data ev;
  bzero(&ev, sizeof(ev));
  
  int wheelCount = 0;
  int scrollHCount = 0;
  int scrollVCount = 0;


  while(1) {
    int s = fread(buf, sizeof(char), 15, hidraw);
    if(s == 0) { /* End of stream - perhaps unplugged device */
      fprintf(stderr, "End of stream. Perhaps the device is unplugged?\n"); /* /dev/input/t230h is symlink to /dev/hidraw? */
      fclose(hidraw);
      return 0;
    }

    if(s != 15) {
      fprintf(stderr, "Expected 15 bytes. Got %d instead. Skipping.\n", s);
      continue;
    }

/*    
    for(int i=0; i < 15; i++)
      printf("%4u ", (unsigned int) buf[i]);
    printf("\n");
*/
    
    if(decodeData(buf, &ev)) {
        //printf("Mouse pos: %4d x %4d - Scroll: %4d x %4d - Wheel: %4d - Button: %d\n", ev.x, ev.y, ev.scrollH, ev.scrollV, ev.wheel, ev.button);
        
        display = XOpenDisplay(0);
        if(display == NULL) {
          fprintf(stderr, "Could not open display. Exiting.\n");
          return 1;
        }

        XTestFakeMotionEvent(display, -1, ev.x, ev.y, 0);

        if(((ev.button & BUTTON_LEFT)) && !left)
            XTestFakeButtonEvent(display, 1, true, 0);
        else if(((ev.button & BUTTON_LEFT) == 0) && left)
            XTestFakeButtonEvent(display, 1, false, 0);
        left = (bool) (ev.button & BUTTON_LEFT);

        if(((ev.button & BUTTON_RIGHT)) && !right)
            XTestFakeButtonEvent(display, 3, true, 0);
        else if(((ev.button & BUTTON_RIGHT) == 0) && right)
            XTestFakeButtonEvent(display, 3, false, 0);
        right = (bool) (ev.button & BUTTON_RIGHT);

        #define MOVE_SPEED 66
        wheelCount += ev.wheel;
        if(wheelCount > MOVE_SPEED) {
//            printf("Wheel up\n");
            XTestFakeButtonEvent(display, 4, true, 0);
            XTestFakeButtonEvent(display, 4, false, 5);
            wheelCount -= MOVE_SPEED;
        }
        if(wheelCount < -MOVE_SPEED) {
//            printf("Wheel down\n");
            XTestFakeButtonEvent(display, 5, true, 0);
            XTestFakeButtonEvent(display, 5, false, 5);
            wheelCount += MOVE_SPEED;
        }
            
        scrollHCount += ev.scrollH;
        if(scrollHCount > MOVE_SPEED) {
//            printf("Scroll H left\n");
            XTestFakeButtonEvent(display, 6, true, 0);
            XTestFakeButtonEvent(display, 6, false, 5);
            scrollHCount -= MOVE_SPEED;
        }
        if(scrollHCount < -MOVE_SPEED) {
//            printf("Scroll H right\n");
            XTestFakeButtonEvent(display, 7, true, 0);
            XTestFakeButtonEvent(display, 7, false, 5);
            scrollHCount += MOVE_SPEED;
        }

        scrollVCount += ev.scrollV;
        if(scrollVCount > MOVE_SPEED) {
//            printf("Scroll V up\n");
            XTestFakeButtonEvent(display, 4, true, 0);
            XTestFakeButtonEvent(display, 4, false, 5);
            scrollVCount -= MOVE_SPEED;
        }
        if(scrollVCount < -MOVE_SPEED) {
//            printf("Scroll V down\n");
            XTestFakeButtonEvent(display, 5, true, 0);
            XTestFakeButtonEvent(display, 5, false, 5);
            scrollVCount += MOVE_SPEED;
        }
    
        XCloseDisplay(display);
    }
    else { /* Error - release buttons */
        if(left) {
            XTestFakeButtonEvent(display, 1, false, 0);
            left = false;
        }
        if(right) {
            XTestFakeButtonEvent(display, 3, false, 0);
            right = false;
        }

    }
  }
}

Bus 006 Device 007: ID 0408:3000 Quanta Computer, Inc. 
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0        64
  idVendor           0x0408 Quanta Computer, Inc.
  idProduct          0x3000 
  bcdDevice            0.00
  iManufacturer           1 Acer
  iProduct                2 T230H
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           34
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0 
      ** UNRECOGNIZED:  09 21 10 01 00 01 22 d2 00
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x83  EP 3 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               8
Device Status:     0x0000
  (Bus Powered)

[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux