/* PLIB - A Suite of Portable Game Libraries Copyright (C) 1998,2002 Steve Baker This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA For further information visit http://plib.sourceforge.net $Id: jsBSD.cxx 2133 2008-07-18 14:32:22Z fayjf $ */ /* * Inspired by the X-Mame USB HID joystick driver for NetBSD and * FreeBSD by Krister Walfridsson . * Incorporates the original analog joystick driver for BSD by * Stephen Montgomery-Smith , with * NetBSD mods courtesy of Rene Hexel. * * Bert Driehuis * * Notes: * Hats are mapped to two axes for now. A cleaner implementation requires * an API extension, and to be useful for my devious purposes, FlightGear * would need to understand that. */ #include "js.h" #if defined(__NetBSD__) || defined(__FreeBSD__) #define HAVE_USB_JS 1 #endif #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif #include /* for jsSetError and SG_WARN */ #include #include #include #include #include #if defined(__FreeBSD__) # include #else # include // For analog joysticks #endif #ifdef HAVE_USB_JS #if defined(__NetBSD__) #ifdef HAVE_USBHID_H #include #else #include #endif #elif defined(__FreeBSD__) extern "C" { # if __FreeBSD_version < 500000 # include # else # define HAVE_USBHID_H 1 # include # endif } #endif #include #include /* Compatibility with older usb.h revisions */ #if !defined(USB_MAX_DEVNAMES) && defined(MAXDEVNAMES) #define USB_MAX_DEVNAMES MAXDEVNAMES #endif #endif static int hatmap_x[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 }; static int hatmap_y[9] = { 0, 1, 1, 0, -1, -1, -1, 0, 1 }; struct os_specific_s { char fname [128 ]; int fd; int is_analog; // The following structure members are specific to analog joysticks struct joystick ajs; #ifdef HAVE_USB_JS // The following structure members are specific to USB joysticks struct hid_item *hids; int hid_dlen; int hid_offset; char *hid_data_buf; int axes_usage [ _JS_MAX_AXES ] ; #endif // We keep button and axes state ourselves, as they might not be updated // on every read of a USB device int cache_buttons ; float cache_axes [ _JS_MAX_AXES ] ; float axes_minimum [ _JS_MAX_AXES ] ; float axes_maximum [ _JS_MAX_AXES ] ; }; // Idents lower than USB_IDENT_OFFSET are for analog joysticks. #define USB_IDENT_OFFSET 2 #define USBDEV "/dev/usb" #define UHIDDEV "/dev/uhid" #define AJSDEV "/dev/joy" #ifdef HAVE_USB_JS /* * findusbdev (and its helper, walkusbdev) try to locate the full name * of a USB device. If /dev/usbN isn't readable, we punt and return the * uhidN device name. We warn the user of this situation once. */ static char * walkusbdev(int f, char *dev, char *out, int outlen) { struct usb_device_info di; int i, a; char *cp; for (a = 1; a < USB_MAX_DEVICES; a++) { di.udi_addr = a; if (ioctl(f, USB_DEVICEINFO, &di) != 0) return NULL; for (i = 0; i < USB_MAX_DEVNAMES; i++) if (di.udi_devnames[i][0] && strcmp(di.udi_devnames[i], dev) == 0) { cp = new char[strlen(di.udi_vendor) + strlen(di.udi_product) + 2]; strcpy(cp, di.udi_vendor); strcat(cp, " "); strcat(cp, di.udi_product); strncpy(out, cp, outlen - 1); out[outlen - 1] = 0; delete cp; return out; } } return NULL; } static int findusbdev(char *name, char *out, int outlen) { int i, f; char buf[50]; char *cp; static int protection_warned = 0; for (i = 0; i < 16; i++) { sprintf(buf, "%s%d", USBDEV, i); f = open(buf, O_RDONLY); if (f >= 0) { cp = walkusbdev(f, name, out, outlen); close(f); if (cp) return 1; } else if (errno == EACCES) { if (!protection_warned) { fprintf(stderr, "Can't open %s for read!\n", buf); protection_warned = 1; } } } return 0; } static int joy_initialize_hid(struct os_specific_s *os, int *num_axes, int *num_buttons) { int size, is_joystick; #ifdef HAVE_USBHID_H int report_id = 0; #endif struct hid_data *d; struct hid_item h; report_desc_t rd; if ((rd = hid_get_report_desc(os->fd)) == 0) { fprintf(stderr, "error: %s: %s", os->fname, strerror(errno)); return FALSE; } os->hids = NULL; #ifdef HAVE_USBHID_H if (ioctl(os->fd, USB_GET_REPORT_ID, &report_id) < 0) { fprintf(stderr, "error: %s: %s", os->fname, strerror(errno)); return FALSE; } size = hid_report_size(rd, hid_input, report_id); #else size = hid_report_size(rd, 0, hid_input); #endif os->hid_data_buf = new char[size]; os->hid_dlen = size; is_joystick = 0; #ifdef HAVE_USBHID_H d = hid_start_parse(rd, 1 << hid_input, report_id); #else d = hid_start_parse(rd, 1 << hid_input); #endif while (hid_get_item(d, &h)) { int usage, page, interesting_hid; page = HID_PAGE(h.usage); usage = HID_USAGE(h.usage); /* This test is somewhat too simplistic, but this is how MicroSoft * does, so I guess it works for all joysticks/game pads. */ is_joystick = is_joystick || (h.kind == hid_collection && page == HUP_GENERIC_DESKTOP && (usage == HUG_JOYSTICK || usage == HUG_GAME_PAD)); if (h.kind != hid_input) continue; if (!is_joystick) continue; interesting_hid = TRUE; if (page == HUP_GENERIC_DESKTOP) { switch(usage) { case HUG_X: case HUG_RX: case HUG_Y: case HUG_RY: case HUG_Z: case HUG_RZ: case HUG_SLIDER: if (*num_axes < _JS_MAX_AXES) { os->axes_usage[*num_axes] = usage; os->axes_minimum[*num_axes] = h.logical_minimum; os->axes_maximum[*num_axes] = h.logical_maximum; (*num_axes)++; } break; case HUG_HAT_SWITCH: if (*num_axes + 1 < _JS_MAX_AXES) // Allocate two axes for a hat { os->axes_usage[*num_axes] = usage; (*num_axes)++; os->axes_usage[*num_axes] = usage; (*num_axes)++; } break; default: interesting_hid = FALSE; } } else if (page == HUP_BUTTON) { interesting_hid = (usage > 0) && (usage <= _JS_MAX_BUTTONS); if (interesting_hid && usage - 1 > *num_buttons) { *num_buttons = usage - 1; } } if (interesting_hid) { h.next = os->hids; os->hids = new struct hid_item; *os->hids = h; } } hid_end_parse(d); return (os->hids != NULL); } #endif void jsJoystick::open () { char *cp; name [0] = '\0' ; for ( int i = 0 ; i < _JS_MAX_AXES ; i++ ) os->cache_axes [ i ] = 0.0f ; os->cache_buttons = 0 ; os->fd = ::open ( os->fname, O_RDONLY | O_NONBLOCK) ; if (os->fd < 0 && errno == EACCES) fprintf(stderr, "%s exists but is not readable by you\n", os->fname); error = ( os->fd < 0 ) ; if ( error ) return ; num_axes = 0; num_buttons = 0; if ( os->is_analog ) { num_axes = 2 ; num_buttons = 32 ; FILE *joyfile ; char joyfname [ 1024 ] ; int noargs, in_no_axes ; float axes [ _JS_MAX_AXES ] ; int buttons [ _JS_MAX_AXES ] ; rawRead ( buttons, axes ) ; error = axes[0] < -1000000000.0f && axes[1] < -1000000000.0f ; if ( error ) return ; sprintf( joyfname, "%s/.joy%drc", ::getenv ( "HOME" ), id ) ; joyfile = fopen ( joyfname, "r" ) ; error = ( joyfile == NULL ) ; if ( error ) { jsSetError ( SG_WARN, "unable to open calibration file %s (%s), joystick %i disabled (you can generate the calibration file with the plib-jscal utility)", joyfname, strerror ( errno ), id + 1 ); return ; } noargs = fscanf ( joyfile, "%d%f%f%f%f%f%f", &in_no_axes, &min [ 0 ], ¢er [ 0 ], &max [ 0 ], &min [ 1 ], ¢er [ 1 ], &max [ 1 ] ) ; error = noargs != 7 || in_no_axes != _JS_MAX_AXES ; fclose ( joyfile ) ; if ( error ) return ; for ( int i = 0 ; i < _JS_MAX_AXES ; i++ ) { dead_band [ i ] = 0.0f ; saturate [ i ] = 1.0f ; } return; // End of analog code } #ifdef HAVE_USB_JS if ( !joy_initialize_hid(os, &num_axes, &num_buttons ) ) { ::close(os->fd); error = 1; return; } cp = strrchr(os->fname, '/'); if (cp) { if (findusbdev(&cp[1], name, sizeof(name)) == 0) strcpy(name, &cp[1]); } if ( num_axes > _JS_MAX_AXES ) num_axes = _JS_MAX_AXES ; for ( int i = 0 ; i < _JS_MAX_AXES ; i++ ) { if ( os->axes_usage [ i ] == HUG_HAT_SWITCH ) { max [ i ] = 1.0f ; center [ i ] = 0.0f ; min [ i ] = -1.0f ; } else { max [ i ] = os->axes_maximum [ i ]; min [ i ] = os->axes_minimum [ i ]; center [ i ] = (max [ i ] + min [ i ]) / 2; } dead_band [ i ] = 0.0f ; saturate [ i ] = 1.0f ; } #endif } void jsJoystick::close () { if (os) { if ( ! error ) ::close ( os->fd ) ; #ifdef HAVE_USB_JS if (os->hids) delete os->hids; if (os->hid_data_buf) delete os->hid_data_buf; #endif delete os; } } jsJoystick::jsJoystick ( int ident ) { id = ident ; error = 0; os = new struct os_specific_s; memset(os, 0, sizeof(struct os_specific_s)); if (ident < USB_IDENT_OFFSET) os->is_analog = 1; if (os->is_analog) sprintf(os->fname, "%s%d", AJSDEV, ident); else sprintf(os->fname, "%s%d", UHIDDEV, ident - USB_IDENT_OFFSET); open () ; } void jsJoystick::rawRead ( int *buttons, float *axes ) { int len, usage, page, d; struct hid_item *h; if ( error ) { if ( buttons ) *buttons = 0 ; if ( axes ) for ( int i = 0 ; i < num_axes ; i++ ) axes[i] = 1500.0f ; return ; } if ( os->is_analog ) { int status = ::read ( os->fd, &os->ajs, sizeof(os->ajs) ); if ( status != sizeof(os->ajs) ) { perror ( os->fname ) ; setError () ; return ; } if ( buttons != NULL ) *buttons = ( os->ajs.b1 ? 1 : 0 ) | ( os->ajs.b2 ? 2 : 0 ) ; if ( axes != NULL ) { if ( os->ajs.x >= -1000000000 ) os->cache_axes[0] = os->ajs.x; if ( os->ajs.y >= -1000000000 ) os->cache_axes[1] = os->ajs.y; axes[0] = os->cache_axes[0]; axes[1] = os->cache_axes[1]; } return; } #ifdef HAVE_USB_JS while ((len = ::read(os->fd, os->hid_data_buf, os->hid_dlen)) == os->hid_dlen) { for (h = os->hids; h; h = h->next) { d = hid_get_data(os->hid_data_buf, h); page = HID_PAGE(h->usage); usage = HID_USAGE(h->usage); if (page == HUP_GENERIC_DESKTOP) { for (int i = 0; i < num_axes; i++) if (os->axes_usage[i] == usage) { if (usage == HUG_HAT_SWITCH) { if (d < 0 || d > 8) d = 0; // safety os->cache_axes[i] = (float)hatmap_x[d]; os->cache_axes[i + 1] = (float)hatmap_y[d]; } else { os->cache_axes[i] = (float)d; } break; } } else if (page == HUP_BUTTON) { if (usage > 0 && usage < _JS_MAX_BUTTONS + 1) { if (d) os->cache_buttons |= (1 << usage - 1) ; else os->cache_buttons &= ~(1 << usage - 1) ; } } } } if (len < 0 && errno != EAGAIN) { perror( os->fname ) ; setError () ; error = 1; } if ( buttons != NULL ) *buttons = os->cache_buttons ; if ( axes != NULL ) memcpy ( axes, os->cache_axes, sizeof(float) * num_axes ) ; #endif } void jsInit () {}