ebeam xinput driver : ask for review

Peter Hutterer peter.hutterer at who-t.net
Wed Jan 5 16:58:23 PST 2011


On Tue, Jan 04, 2011 at 08:44:12PM +0100, Yann Cantin wrote:
> Hi,
> 
> This is a attempt to replace the closed source ebeam xinput driver provided by Luidia for ebeam device.
> (see http://www.e-beam.com/business/ebeam-classic-projection/overview.html).
> 
> I have to do that cause their driver don't support recent xorg version. Support says they are working
> on a re-implementation from scratch, but my device is unusable since i update my distro 7 mouths back, so...
> 
> One particularity of ebeam devices handling is that it use a 2 parts system : a daemon service that talk directly
> to the hardware (usb for mine, there is also bluetouth or serial ones), and a xinput driver. The 2 communicate
> thru a network socket via binary packets. The good news is that the daemon run perfectly and seems to provide a
> common interface for multiple devices, the bad one is that it is also closed, so this driver can't work alone,
> and need a proprietary part to be useful. I can live with that, but i don't know how you xorg guys can deal with.
> 
> Anyway, the driver works. I'm sure i've reproduce all the original driver's mechanics (it's vrey basic), and i
> try hard to stick to coding rules i've found in other xinput drivers, but as you may know, there's no clear
> documentation out there to help beginner like me.

note that one of the reasons for the lack of documentation is that we
discourage people to write new X input drivers. we generally recommend to
implement kernel drivers instead, though I realise that this is not easily
possible in this case.

> 
> There is clearly space for optimization in the code, but i mainly worry about correctness for now.
> 
> Many thanks for the time you'll spend.
> 
> -- 
> Yann Cantin
> A4FEB47F
> --

> /*
>  * Copyright 2010 Yann Cantin <yann.cantin at laposte.net>
>  *
>  * Parts inspired from Peter Hutterer and Przemys??aw Firszt "random"
>  * driver, logic reverse-engeniered from the old (closed) Luida driver.
>  *
>  * Permission to use, copy, modify, distribute, and sell this software
>  * and its documentation for any purpose is hereby granted without
>  * fee, provided that the above copyright notice appear in all copies
>  * and that both that copyright notice and this permission notice
>  * appear in supporting documentation, and that the name of Red Hat

Red Hat?

>  * not be used in advertising or publicity pertaining to distribution
>  * of the software without specific, written prior permission.  Red
>  * Hat makes no representations about the suitability of this software
>  * for any purpose.  It is provided "as is" without express or implied
>  * warranty.
>  *
>  * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
>  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
>  * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
>  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
>  * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
>  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
>  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
>  */
> 
> #ifdef HAVE_CONFIG_H
> #include "config.h"
> #endif
> 
> #include <linux/input.h>
> #include <linux/types.h>
> 
> #include <xf86_OSproc.h>
> 
> #include <unistd.h>
> 
> #include <xf86.h>
> #include <xf86Xinput.h>
> #include <exevents.h>
> #include <xorgVersion.h>
> #include <xkbsrv.h>
> 
> #if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 3
> #define HAVE_PROPERTIES 1
> #endif


given that ebeam's driver claims to support ubunut 9.04 you shouldn't need
to worry about XINPUT ABI < 7 (server 1.7) and remove all the ifdefs.
also, with ABI 12 just around the corner (and massive rewrites in mainly the
device init system) this driver needs some rewriting again to work with
1.10. look at any of the other drivers for hints.

> 
> #ifdef HAVE_PROPERTIES
> #include <xserver-properties.h>
> /* 1.6 has properties, but no labels */
> #ifdef AXIS_LABEL_PROP
> #define HAVE_LABELS
> #else
> #undef HAVE_LABELS
> #endif
> #endif
> 
> #include <stdio.h>
> #include <sys/stat.h>
> #include <sys/syscall.h>
> 
> /* network socket */
> #include <sys/types.h>
> #include <sys/socket.h>
> #include <netinet/in.h>
> #include <arpa/inet.h>
> #include <netdb.h>
> 
> #include <unistd.h>
> #include <errno.h>
> #include <sys/types.h>
> #include <fcntl.h>
> #include <xorg-server.h>
> #include <xorgVersion.h>
> #include <xf86Module.h>
> #include <X11/Xatom.h>
> 
> #include "ebeam.h"
> 
> static InputInfoPtr EbeamInit(InputDriverPtr drv, IDevPtr dev, int flags);
> static void         EbeamUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
> static pointer      EbeamPlug(pointer module, pointer options, int *errmaj, int *errmin);
> static void         EbeamUnplug(pointer p);
> static void         EbeamReadInput(InputInfoPtr pInfo);
> static int          EbeamControl(DeviceIntPtr device,int what);
> 
> static int          _ebeam_open_socket(InputInfoPtr pInfo);
> static int          _ebeam_init_buttons(DeviceIntPtr device);
> static int          _ebeam_init_axes(DeviceIntPtr device);
> 
> /* packet buffer */
> char packetbuf[256 * MAX_PACKET];
> 
> 
> _X_EXPORT InputDriverRec EBEAM =
> {
>     1,
>     "ebeam",
>     NULL,
>     EbeamInit,
>     EbeamUnInit,
>     NULL,
>     0
> };
> 
> static XF86ModuleVersionInfo EbeamVersionRec =
> {
>     "ebeam",
>     MODULEVENDORSTRING,
>     MODINFOSTRING1,
>     MODINFOSTRING2,
>     XORG_VERSION_CURRENT,
>     PACKAGE_VERSION_MAJOR, PACKAGE_VERSION_MINOR,
>     PACKAGE_VERSION_PATCHLEVEL,
>     ABI_CLASS_XINPUT,
>     ABI_XINPUT_VERSION,
>     MOD_CLASS_XINPUT,
>     {0, 0, 0, 0}
> };
> 
> _X_EXPORT XF86ModuleData ebeamModuleData =
> {
>     &EbeamVersionRec,
>     &EbeamPlug,
>     &EbeamUnplug
> };
> 
> static void EbeamUnplug(pointer p)
> {
> };
> 
> static pointer EbeamPlug(pointer module,
>                          pointer options,
>                          int     *errmaj,
>                          int     *errmin)
> {
>     xf86AddInputDriver(&EBEAM, module, 0);
>     return module;
> };
> 
> static int _ebeam_open_socket(InputInfoPtr pInfo)
> {
>     EbeamDevicePtr pEbeam = pInfo->private;
> 
>     int initblob[2] = {4 , 3};
> 
>     int fd = -1;
>     struct sockaddr_in addr;
>     struct hostent *host = gethostbyname(pEbeam->host);
>     int port = atoi(pEbeam->port);
> 
>     if ( host == NULL || port == 0 ) {
>         xf86Msg(X_ERROR, "%s: Bad hostname:port : %s:%s.\n", pInfo->name, pEbeam->host, pEbeam->port);
>         return fd;
>     }
> 
>     fd = socket(AF_INET, SOCK_STREAM, PF_UNSPEC);
>     if ( fd < 0 )
>     {
>         xf86Msg(X_ERROR, "%s: failed creating socket.\n", pInfo->name);
>     }
>     else
>     {
>         addr.sin_family = AF_INET;
>         memcpy(&addr.sin_addr, host->h_addr, host->h_length);
>         addr.sin_port = htons(port);
>         if ( connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1 )
>         {
>             xf86Msg(X_ERROR, "%s: failed connecting to %s:%s.\n", pInfo->name, pEbeam->host, pEbeam->port);
>             close(fd);
>             fd = -1;
>         }
>         else
>         {
>             write(fd, &initblob, 2 * sizeof(int));
>         }
>     }
>     return fd;
> }
> 
> static InputInfoPtr EbeamInit(InputDriverPtr drv,
>                                  IDevPtr     dev,
>                                  int         flags)
> {
>     InputInfoPtr   pInfo;
>     EbeamDevicePtr pEbeam;
>     char           *pointer_name;
> 
>     if (!(pInfo = xf86AllocateInput(drv, 0)))
>         return NULL;
 
>     pEbeam = xcalloc(1, sizeof(EbeamDeviceRec));

xcalloc, xfree, etc. have been deprecated. use calloc, free, etc. instead.

>     if (!pEbeam) {
>         pInfo->private = NULL;
>         xf86DeleteInput(pInfo, 0);
>         return NULL;
>     }
> 
>     pInfo->private = pEbeam;
>     pInfo->name = xstrdup(dev->identifier);
>     pInfo->flags = 0;
>     pInfo->type_name = XI_MOUSE;
>     pInfo->conf_idev = dev;
>     pInfo->read_input = EbeamReadInput;
>     pInfo->switch_mode = NULL;
>     pInfo->device_control = EbeamControl;
> 
>     /* process driver specific options */
>     pEbeam->host = xf86SetStrOption(dev->commonOptions, "Host", "localhost");
>     pEbeam->port = xf86SetStrOption(dev->commonOptions, "Port", "7802");
>     xf86Msg(X_INFO, "%s: Using ebeam-server at %s:%s.\n", pInfo->name, pEbeam->host, pEbeam->port);
> 
>     pointer_name = xf86SetStrOption(dev->commonOptions, "Color", "wand");
>     if ( !xf86NameCmp(pointer_name, "red") )
>         pEbeam->pointer_id = 1;
>     else if ( !xf86NameCmp(pointer_name, "blue") )
>         pEbeam->pointer_id = 2;
>     else if ( !xf86NameCmp(pointer_name, "green") )
>         pEbeam->pointer_id = 3;
>     else if ( !xf86NameCmp(pointer_name, "black") )
>         pEbeam->pointer_id = 4;
>     else if ( !xf86NameCmp(pointer_name, "erase") )
>         pEbeam->pointer_id = 5;
>     else if ( !xf86NameCmp(pointer_name, "wand") )
>         pEbeam->pointer_id = 6;
>     else if ( !xf86NameCmp(pointer_name, "any") )
>         pEbeam->pointer_id = 7;
>     else {
>         xf86Msg(X_ERROR, "%s: Unknown pointer name : %s.\n", pInfo->name, pointer_name);
>         pEbeam->pointer_id = 7;
>     }
>     xfree(pointer_name);
> 
>     xf86Msg(X_INFO, "%s: Using pointer ID %u.\n", pInfo->name, pEbeam->pointer_id);
> 
>     /* process generic options */
>     xf86CollectInputOptions(pInfo, NULL, NULL);
>     xf86ProcessCommonOptions(pInfo, pInfo->options);
> 
>     /* test socket */
>     pInfo->fd = _ebeam_open_socket(pInfo);
>     if (pInfo->fd < 0)
>     {
>         xf86Msg(X_ERROR, "%s: No device present, exiting.\n", pInfo->name);
>         pInfo->private = NULL;
>         xfree(pEbeam);
>         xf86DeleteInput(pInfo, 0);
>         return NULL;
>     }
> 
>     close(pInfo->fd);
>     pInfo->fd = -1;
>     pInfo->flags |= XI86_OPEN_ON_INIT;
>     pInfo->flags |= XI86_CONFIGURED;
>     return pInfo;
> }
> 
> static void EbeamUnInit(InputDriverPtr drv,
>                         InputInfoPtr   pInfo,
>                         int            flags)
> {
>     EbeamDevicePtr pEbeam = pInfo->private;
>     if (pEbeam->host)
>     {
>         xfree(pEbeam->host);
>         pEbeam->host = NULL;
>     }
>     if (pEbeam->port)
>     {
>         xfree(pEbeam->port);
>         pEbeam->port = NULL;
>     }
>     /* Common error - pInfo->private must be NULL or valid memoy before
>      * passing into xf86DeleteInput */
>     pInfo->private = NULL;
> 
>     xf86DeleteInput(pInfo, 0);
> }
> 
> static void _ebeam_fill_labels(DeviceIntPtr device)
> {
>     InputInfoPtr   pInfo = device->public.devicePrivate;
>     EbeamDevicePtr pEbeam = pInfo->private;
>     int            i;
>     int            ret = Success;
> 
>     for ( i = 0; i <  EBEAM_BTNS; i++) {
>         pEbeam->btn_map[i + 1] = i + 1; /* default mapping */
>         pEbeam->btn_labels[i] = 0;
>     }
> 
>     for ( i = 0; i < EBEAM_AXES; i++) {
>         pEbeam->axis_labels[i] = 0;
>     }
> 
> #ifdef HAVE_LABELS
>     if (EBEAM_BTNS > 0)
>         pEbeam->btn_labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
>     if (EBEAM_BTNS > 1)
>         pEbeam->btn_labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
>     if (EBEAM_BTNS > 2) {
>         pEbeam->btn_labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
>         pEbeam->btn_labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
>     }
> 
>     if (EBEAM_AXES > 0)
>         pEbeam->axis_labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_X);
>     if (EBEAM_AXES > 1)
>         pEbeam->axis_labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_Y);
> #endif
> }
> 
> 
> static int _ebeam_init_buttons(DeviceIntPtr device)
> {
>     InputInfoPtr   pInfo = device->public.devicePrivate;
>     EbeamDevicePtr pEbeam = pInfo->private;
>     int            ret = Success;
> 
>     if (!InitButtonClassDeviceStruct(device, EBEAM_BTNS, pEbeam->btn_labels, pEbeam->btn_map)) {
>             xf86Msg(X_ERROR, "%s: Failed to register buttons.\n", pInfo->name);
>             ret = BadAlloc;
>     }
>     return ret;
> }
> 
> static int _ebeam_init_axes(DeviceIntPtr device)
> {
>     InputInfoPtr   pInfo = device->public.devicePrivate;
>     EbeamDevicePtr pEbeam = pInfo->private;
>     int            i;
> 
>     if (!InitValuatorClassDeviceStruct(device,
>                                        EBEAM_AXES,
>                                        pEbeam->axis_labels,
>                                        GetMotionHistorySize(),
>                                        0))
>         return BadAlloc;
> 
>     pInfo->dev->valuator->mode = Absolute;
>     if (!InitAbsoluteClassDeviceStruct(device))
>             return BadAlloc;
> 
>     for (i = 0; i < EBEAM_AXES; i++) {
>             xf86InitValuatorAxisStruct(device, i, pEbeam->axis_labels[i], 0, -1, 1, 0, 1);
>             xf86InitValuatorDefaults(device, i);
>     }
>     return Success;
> }
> 
> static int EbeamControl(DeviceIntPtr device,
>                         int          what)
> {
>     InputInfoPtr   pInfo  = device->public.devicePrivate;
>     EbeamDevicePtr pEbeam = pInfo->private;
> 
>     switch(what)
>     {
>         case DEVICE_INIT:
>             _ebeam_fill_labels(device);
>             _ebeam_init_buttons(device);
>             _ebeam_init_axes(device);
>             break;
> 
>         /* Switch device on.  Establish socket, start event delivery.  */
>         case DEVICE_ON:
>             xf86Msg(X_INFO, "%s: On.\n", pInfo->name);
>             if (device->public.on)
>                     break;
> 
>             pInfo->fd = _ebeam_open_socket(pInfo);
>             if (pInfo->fd < 0)
>             {
>                 xf86Msg(X_ERROR, "%s: cannot open device.\n", pInfo->name);
>                 return BadRequest;
>             }
> 
>             /* xf86FlushInput(pInfo->fd); */
>             xf86AddEnabledDevice(pInfo);
>             device->public.on = TRUE;
>             break;
> 
>         /* Switch device off.  Close socket, stop event delivery.  */
>        case DEVICE_OFF:
>             xf86Msg(X_INFO, "%s: Off.\n", pInfo->name);
>             if (!device->public.on)
>                 break;
>             xf86RemoveEnabledDevice(pInfo);
>             close(pInfo->fd);
>             pInfo->fd = -1;
>             device->public.on = FALSE;
>             break;
> 
>       case DEVICE_CLOSE:
>             /* free what we have to free */
>             break;
>     }
>     return Success;
> }
> 
> /* ebeam-server packets :
>  * taille : 256 bytes / 64 int
>  * int_0  : 0 : event ; 1 : control ; other : ignore
>  *
>  * CONTROL PACKET :
>  * int_3  : pInfo->flags : 0 : unset XI_ALWAYS_CORE ; 1 : set XI_ALWAYS_CORE
>  * int_6  : little button map to ...
>  * int_7  : big button map to ...
>  * int_8  : tip button map to ...
>  * WARNING : ebeam-server always send : little 1 ; big 2 ; tip 4 ; it's up to the driver to map.
>  *
>  * EVENT PACKET
>  * int_3 : pos X
>  * int_4 : pos Y
>  * int_5 : pointer ID : wand(6) ; red(1) ; blue(2) ; green(3) ; black(4) ; erase(5)
>  * int_7 : buttons flags : big 1 | little 2 | tip 4
>  */
> static void EbeamReadInput(InputInfoPtr pInfo)
> {
>     EbeamDevicePtr pEbeam = pInfo->private;
>     ssize_t len;
>     int i, j, b;
>     int n;
>     int *data;
>     int real_btns_state, mapped_btns_state, x, y;
> 
>     while(xf86WaitForInput(pInfo->fd, 0) > 0)
>     {
>         len = read(pInfo->fd, packetbuf, 256 * MAX_PACKET);
>         n = len / 256;
> 
>         if ( len == 0 || len != n * 256 ) {
>             xf86Msg(X_ERROR, "%s : %u bytes read, ignoring bad packet.\n", pInfo->name, len);
>             return;
>         }
> 
>         for ( i = 0; i < n; i++ ) { /* packet */
>             data = (int *) &packetbuf[256*i];
> 
>             if ( *data == 1 ) { /* control packet */
> 
>                 /* ALWAYS_CORE flag */
>                 if ( *(data + 3) )
>                     pInfo->flags |= XI86_ALWAYS_CORE;  /* set */
>                 else
>                     pInfo->flags &= ~XI86_ALWAYS_CORE; /* unset*/

this flag is ignored in servers 1.7 and later, no point setting it.

> 
>                 /* hardware buttons mapping */
>                 if ( *(data + 6) > 0 )          /* Little */
>                     pEbeam->btn_map[1] = *(data + 6);
>                 else
>                     pEbeam->btn_map[1] = 0;
> 
>                 if ( *(data + 7) > 0 )          /* Big */
>                     pEbeam->btn_map[2] = *(data + 7);
>                 else
>                     pEbeam->btn_map[2] = 0;
> 
>                 if ( *(data + 8) > 0 )          /* Tip */
>                     pEbeam->btn_map[3] = *(data + 8);
>                 else
>                     pEbeam->btn_map[3] = 0;
> 
>                 xf86Msg(X_INFO, "%s : Buttons map : Little : %u ; Big : %u ; Tip : %u.\n",
>                         pInfo->name, pEbeam->btn_map[1], pEbeam->btn_map[2], pEbeam->btn_map[3]);
>             }
>             else if ( *data == 0 ) { /* event packet */
> 
>                 /* pointer ID */
>                 if ( pEbeam->pointer_id == 7 || *(data + 5) == pEbeam->pointer_id ) {   /* any pointer or good pointer */
>                     real_btns_state = *(data + 7);
>                     x = *(data + 3);
>                     y = *(data + 4);
> 
>                     /* Tip pressed : send motion */
>                     if ( real_btns_state & TIP_BIT )
>                         xf86PostMotionEvent(pInfo->dev, 1, 0, 2, x, y);
> 
>                     /* Buttons */
>                     mapped_btns_state = 0;
> 
>                     /* little */
>                     if ( pEbeam->btn_map[1] && (real_btns_state & LIT_BIT) )
>                         mapped_btns_state = 1 << (pEbeam->btn_map[1] - 1);
>                     /* big */
>                     if ( pEbeam->btn_map[2] && (real_btns_state & BIG_BIT) )
>                         mapped_btns_state |= 1 << (pEbeam->btn_map[2] - 1);
>                     /* tip */
>                     if ( pEbeam->btn_map[3] && (real_btns_state & TIP_BIT) )
>                         mapped_btns_state |= 1 << (pEbeam->btn_map[3] - 1);
> 
>                     if ( mapped_btns_state != pEbeam->previous_mapped_btns_state ) { /* changes occured */
>                         for ( b = 0; b < 3; b++ ) {
>                             if ( (1 & (mapped_btns_state >> b)) != (1 & (pEbeam->previous_mapped_btns_state >> b)) )
>                                 xf86PostButtonEvent(pInfo->dev,
>                                                     1,
>                                                     b+1,
>                                                     1 & (mapped_btns_state >> b),
>                                                     0,
>                                                     2,
>                                                     x,
>                                                     y);
>                         }
>                         pEbeam->previous_mapped_btns_state = mapped_btns_state;
>                     }
>                 }
>             }
>             else {
>                 xf86Msg(X_ERROR, "%s : Unknown packet type (%u), ignoring.\n", pInfo->name, *data);
>             }
>         }
>     }
> }

> /*
>  * Copyright 2010 Yann Cantin <yann.cantin at laposte.net>
>  *
>  * Parts inspired from Peter Hutterer and Przemys??aw Firszt "random"
>  * driver, logic reverse-engeniered from the old (closed) Luida driver.
>  *
>  * Permission to use, copy, modify, distribute, and sell this software
>  * and its documentation for any purpose is hereby granted without
>  * fee, provided that the above copyright notice appear in all copies
>  * and that both that copyright notice and this permission notice
>  * appear in supporting documentation, and that the name of Red Hat
>  * not be used in advertising or publicity pertaining to distribution
>  * of the software without specific, written prior permission.  Red
>  * Hat makes no representations about the suitability of this software
>  * for any purpose.  It is provided "as is" without express or implied
>  * warranty.
>  *
>  * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
>  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
>  * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
>  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
>  * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
>  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
>  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
>  */
> 
> #define SYSCALL(call) while (((call) == -1) && (errno == EINTR))
> 
> #define EBEAM_AXES 2
> #define EBEAM_BTNS 3
> #define MAX_PACKET 20
> 
> #define BIG_BIT 1
> #define LIT_BIT 2
> #define TIP_BIT 4
> 
> typedef struct _EbeamDeviceRec
> {
>     char  *host;
>     char  *port;
>     int   pointer_id;
>     Atom  axis_labels[EBEAM_AXES];
>     CARD8 btn_map[EBEAM_BTNS + 1];      /* 1 :Little ; 2 : Big ; 3 : Tip */
>     int   previous_mapped_btns_state;   /* keep state from previous run */
>     Atom  btn_labels[EBEAM_BTNS];
> } EbeamDeviceRec, *EbeamDevicePtr ;
> 

nothing that strikes me as obviously wrong, but I didn't spend too much time
on it. forgive me for not being overly enthused about a driver that requires
a blackbox component to run. so feel free to continue to work on the driver,
but there is little chance of xorg hosting for it.

besides if the need for the closed source daemon is gone (e.g. by someone
reverse-engineering the protocol or Luidia finding some sense), it'd be
easier to just integrate support into the kernel and then use evdev to
handle the device.

Cheers,
  Peter


More information about the xorg-devel mailing list