linux-joystick.cpp

Go to the documentation of this file.
00001 /*
00002  * linux-joystick.cpp
00003  *
00004  * Copyright (C) 2009,2011  Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  *
00008  * Redistribution and use in source and binary forms, with or without
00009  * modification, are permitted provided that the following conditions are met:
00010  *     * Redistributions of source code must retain the above copyright
00011  *       notice, this list of conditions and the following disclaimer.
00012  *     * Redistributions in binary form must reproduce the above copyright
00013  *       notice, this list of conditions and the following disclaimer in the
00014  *       documentation and/or other materials provided with the distribution.
00015  *     * Neither the name of the <organization> nor the
00016  *       names of its contributors may be used to endorse or promote products
00017  *       derived from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THOMAS A. VAUGHAN ''AS IS'' AND ANY
00020  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022  * DISCLAIMED. IN NO EVENT SHALL THOMAS A. VAUGHAN BE LIABLE FOR ANY
00023  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00024  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00026  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00027  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00028  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029  *
00030  *
00031  * Device support built on the linux/joystick.h system.  See linux-joystick.h
00032  */
00033 
00034 // includes --------------------------------------------------------------------
00035 #include "linux-joystick.h"             // always include our own header first
00036 
00037 #ifdef WIN32
00038 #error "Can't compile linux-joystick module under Windows"
00039 #endif  // WIN32
00040 
00041 #include <linux/joystick.h>
00042 #include <sstream>
00043 #include <iostream>
00044 
00045 #include "perf/perf.h"
00046 #include "util/file.h"
00047 #include "wave-nix/wave-nix.h"
00048 
00049 
00050 namespace gamepad {
00051 
00052 
00053 
00054 #define s_devicePath            "/dev/input/"
00055 #define s_devicePrefix          "js"
00056 
00057 const int s_maxName = 80;       // everyone on the Internets uses this value...
00058 
00059 static const char * s_prefix            = "LinuxJoystick";
00060 
00061 static SetString s_devicePaths; // set of recognized device paths
00062 
00063 
00064 ////////////////////////////////////////////////////////////////////////////////
00065 //
00066 //      static helper methods
00067 //
00068 ////////////////////////////////////////////////////////////////////////////////
00069 
00070 static bool
00071 test_bit
00072 (
00073 IN int bit,
00074 IN const byte_t * array
00075 )
00076 {
00077         ASSERT(bit >= 0, "Bad bit: %d", bit);
00078         ASSERT(array, "null");
00079 
00080         int offset = bit / 8;
00081         byte_t b = array[offset];
00082         byte_t mask = 1 << (bit % 8);
00083         return b & mask;
00084 }
00085 
00086 
00087 
00088 #define DUMP_EVBIT(name, desc) {                                        \
00089         if (test_bit(EV_ ##name , pb)) { DPRINTF("  %s", desc); }       \
00090         }
00091 
00092 static void
00093 dumpCapabilities
00094 (
00095 IN const byte_t * pb
00096 )
00097 throw()
00098 {
00099         ASSERT(pb, "null");
00100 
00101         DPRINTF("Device capabilities:");        // examples
00102         DUMP_EVBIT(SYN, "Synchronizes");
00103         DUMP_EVBIT(KEY, "Buttons/keys");
00104         DUMP_EVBIT(REL, "Relative input");      // trackballs
00105         DUMP_EVBIT(ABS, "Absolute input");      // joysticks
00106         DUMP_EVBIT(MSC, "Miscellaneous");       // ???
00107         DUMP_EVBIT(SW,  "Switch");
00108         DUMP_EVBIT(LED, "LEDs");
00109         DUMP_EVBIT(SND, "Sound");
00110         DUMP_EVBIT(REP, "Repeat events");
00111         DUMP_EVBIT(FF,  "Force Feedback");
00112         DUMP_EVBIT(PWR, "Power management");
00113         DUMP_EVBIT(FF_STATUS, "Force feedback status");
00114 }
00115 
00116 
00117 
00118 static int
00119 getIndex
00120 (
00121 IN int value,
00122 IN const int * map,
00123 IN int size
00124 )
00125 {
00126         THROW(value >= 0, "Bad input index value: " << value);
00127         ASSERT(map, "null");
00128         ASSERT(size > 0, "Bad map size: %d", size);
00129 
00130         for (int i = 0; i < size; ++i) {
00131                 if (value == map[i])
00132                         return i;
00133         }
00134 
00135         // couldn't find input index in map!
00136 //      THROW(false, "Input index not found in map: " << value);
00137         DPRINTF("WARNING: input index not found in map?  %d", value);
00138 
00139         return -1;      // never get here
00140 }
00141 
00142 
00143 
00144 static inline void
00145 diagnoseError
00146 (
00147 void
00148 )
00149 {
00150         dumpErrorInfo(std::cerr, errno);
00151 }
00152 
00153 
00154 
00155 static bool
00156 isJoystickEntry
00157 (
00158 IN smart_fd& fd
00159 )
00160 {
00161         ASSERT(fd, "invalid");
00162 
00163         // get the version
00164         dword_t version;
00165         if (ioctl(fd, JSIOCGVERSION, &version)) {
00166                 diagnoseError();
00167                 DPRINTF("  ...unable to retrieve joystick version");
00168                 return false;
00169         }
00170         DPRINTF("Version = 0x%08x", version);
00171         int16_t majorV = version >> 16;
00172         byte_t minorV = (version >> 8) & 0xff;
00173         byte_t patchV = version & 0xff;
00174         DPRINTF("  major:%d  minor:%d  patch:%d", majorV, minorV, patchV);
00175 
00176         // get the name
00177         char name[s_maxName];
00178         int nchars = ioctl(fd, JSIOCGNAME(s_maxName), &name);
00179         if (nchars < 1 || nchars > s_maxName) {
00180                 diagnoseError();
00181                 DPRINTF("   ...failed to get joystick name");
00182                 return false;
00183         }
00184         DPRINTF("Joystick name: '%s'", name);
00185 
00186         // should have at least 2 axes...
00187         static const int s_minAxes = 2;
00188         int numAxes = 0;
00189         if (ioctl(fd, JSIOCGAXES, &numAxes)) {
00190                 diagnoseError();
00191                 DPRINTF("  ...failed to retrieve axis count");
00192                 return false;
00193         }
00194         DPRINTF("Number of axes: %d", numAxes);
00195         if (numAxes < s_minAxes) {
00196                 DPRINTF("  ...not enough axes (want %d, only found %d)",
00197                     s_minAxes, numAxes);
00198                 return false;
00199         }
00200 
00201         // should have at least 1 button...
00202         int numButtons = 0;
00203         if (ioctl(fd, JSIOCGBUTTONS, &numButtons)) {
00204                 diagnoseError();
00205                 DPRINTF("  ...failed to retrieve button count");
00206                 return false;
00207         }
00208         DPRINTF("Number of buttons: %d", numButtons);
00209         if (numButtons < 1) {
00210                 DPRINTF("  ...does not contain any buttons?");
00211                 return false;
00212         }
00213 
00214         // got here?  all good.
00215         return true;
00216 }
00217 
00218 
00219 
00220 const char *
00221 getJoystickIndex
00222 (
00223 IN const char * path
00224 )
00225 {
00226         ASSERT(path, "null");
00227 
00228         const char * key = s_devicePath s_devicePrefix;
00229 
00230         const char * p = strstr(path, key);
00231         THROW(p, "Bad joystick device path: " << path);
00232 
00233         p += strlen(key);
00234 
00235         DPRINTF("path: '%s' --> index '%s'", path, p);
00236         return p;
00237 }
00238 
00239 
00240 
00241 ////////////////////////////////////////////////////////////////////////////////
00242 //
00243 //      LJDevice -- class that implements the gamepad::SourceDevice interface
00244 //              for Linux Joystick objects (using linux/joystick.h)
00245 //
00246 ////////////////////////////////////////////////////////////////////////////////
00247 
00248 class LJDevice : public SourceDevice {
00249 public:
00250         // constructor, destructor ---------------------------------------------
00251         LJDevice(void) throw();
00252         ~LJDevice(void) throw();
00253 
00254         // public class methods ------------------------------------------------
00255         void initialize(IN const char * path);
00256 
00257         // gamepad::SourceDevice class interface methods -----------------------
00258         const char * getPublicName(void) throw() { return m_name.c_str(); }
00259         const char * getUniqueId(void) throw() { return m_uid.c_str(); }
00260         eDeviceState getState(void) throw();
00261         int getNumPots(void) throw() { return m_nPots; }
00262         int getNumButtons(void) throw() { return m_nButtons; }
00263         void poll(void) throw();
00264         const pot_value_t& getPotValue(IN int potIndex);
00265         eButton getButtonValue(IN int buttonIndex);
00266 
00267 private:
00268         // private helper methods ----------------------------------------------
00269         // private member data -------------------------------------------------
00270         std::string             m_path;         // device path
00271         std::string             m_name;         // public name
00272         std::string             m_uid;          // unique ID
00273         smart_fd                m_fd;           // device handle
00274         byte_t                  m_eventTypes[(EV_MAX + 7) / 8];// capabilities
00275         int                     m_nPots;        // count of potentiometers
00276         int                     m_nButtons;     // count of buttons
00277         std::vector<byte_t>     m_button;
00278         std::vector<pot_value_t> m_pot;
00279 };
00280 
00281 
00282 LJDevice::LJDevice(void)
00283 throw()
00284 {
00285         m_nPots = 0;
00286         m_nButtons = 0;
00287 }
00288 
00289 
00290 LJDevice::~LJDevice(void)
00291 throw()
00292 {
00293 }
00294 
00295 
00296 
00297 ////////////////////////////////////////////////////////////////////////////////
00298 //
00299 //      LJDevice -- public class methods
00300 //
00301 ////////////////////////////////////////////////////////////////////////////////
00302 
00303 void
00304 LJDevice::initialize
00305 (
00306 IN const char * path
00307 )
00308 {
00309         perf::Timer timer("LinuxInputDevice::initialize()");
00310         ASSERT(path, "null");
00311 
00312         DPRINTF("Opening linux joystick source device: '%s'", path);
00313 
00314         // remember path
00315         m_path = path;
00316 
00317         // open device
00318         THROW(m_fd.open(path, O_RDONLY),
00319             "Failed to open linux joystick device: " << path);
00320 
00321         // get the name of the device
00322         char buffer[s_maxName];
00323         int nchars = ioctl(m_fd, JSIOCGNAME(s_maxName), &buffer);
00324         THROW(nchars > 0 || nchars <= s_maxName,
00325             "Failed to acquire linux joystick device name");
00326         m_name = buffer;
00327         DPRINTF("  public name: '%s'", m_name.c_str());
00328 
00329         // construct the unique ID
00330         std::ostringstream out;
00331         out << s_prefix << ",";
00332         out << m_name << ",";
00333         out << "idx:" << getJoystickIndex(path);
00334 
00335         m_uid = out.str();
00336         DPRINTF("  unique ID: '%s'", m_uid.c_str());
00337 
00338         // construct button map
00339         if (ioctl(m_fd, JSIOCGBUTTONS, &m_nButtons)) {
00340                 diagnoseError();
00341                 THROW(false, "Failed to acquire button count");
00342         }
00343         DPRINTF("Found %d buttons on device", m_nButtons);
00344         m_button.resize(m_nButtons, eButtonUp);
00345 
00346         // get count of potentiometers
00347         if (ioctl(m_fd, JSIOCGAXES, &m_nPots)) {
00348                 diagnoseError();
00349                 THROW(false, "Failed to acquire axis count");
00350         }
00351         DPRINTF("Found %d potentiometers on device", m_nPots);
00352         m_pot.resize(m_nPots);
00353 
00354         // last thing: set to non-blocking
00355         THROW(!fcntl(m_fd, F_SETFL, O_NONBLOCK),
00356             "Failed to set linux joystick device to nonblocking i/o");
00357 }
00358 
00359 
00360 
00361 ////////////////////////////////////////////////////////////////////////////////
00362 //
00363 //      LJDevice -- gamepad::SourceDevice class interface methods
00364 //
00365 ////////////////////////////////////////////////////////////////////////////////
00366 
00367 eDeviceState
00368 LJDevice::getState
00369 (
00370 void
00371 )
00372 throw()
00373 {
00374         if (s_devicePaths.end() == s_devicePaths.find(m_path.c_str())) {
00375                 // cannot find device!
00376                 return eDevice_Detached;
00377         }
00378 
00379         // device appears to be there...
00380         return eDevice_Attached;
00381 }
00382 
00383 
00384 
00385 void
00386 LJDevice::poll
00387 (
00388 void
00389 )
00390 throw()
00391 {
00392         ASSERT(m_fd, "need to open device first");
00393 
00394         // clear "was pushed" and "was released" bits for all buttons
00395         for (int i = 0; i < m_nButtons; ++i) {
00396                 m_button[i] &= ~eButtonWasPushed;
00397                 m_button[i] &= ~eButtonWasReleased;
00398         }
00399 
00400         // grab all available events
00401         struct js_event event;
00402         while (true) {
00403                 int len = read(m_fd, &event, sizeof(event));
00404                 if (!len) {
00405                         break;          // no more events!
00406                 }
00407                 if (len < 0) {
00408                         // read call failed--why?
00409                         if (EAGAIN == errno)
00410                                 break;  // no more data--fine
00411 
00412                         // failed for some other (unexpected) reason
00413                         diagnoseError();
00414                         DPRINTF("Failed to read from joystick!");
00415                         break;
00416                 }
00417 
00418                 int idx = event.number;
00419                 switch (event.type) {
00420                 case JS_EVENT_INIT | JS_EVENT_AXIS:
00421                 case JS_EVENT_AXIS:
00422                         if (idx < 0 || idx >= m_nPots)
00423                                 break;
00424                         m_pot[idx].update(event.value);
00425                         break;
00426 
00427                 case JS_EVENT_INIT | JS_EVENT_BUTTON:
00428                 case JS_EVENT_BUTTON:
00429                         if (idx < 0 || idx >= m_nButtons)
00430                                 break;
00431                         m_button[idx] =
00432                             updateButton(m_button[idx], event.value);
00433                         break;
00434 
00435                 default:
00436                         DPRINTF("Unrecognized joystick event: %d", event.type);
00437                 }
00438         }
00439 }
00440 
00441 
00442 
00443 const pot_value_t&
00444 LJDevice::getPotValue
00445 (
00446 IN int potIndex
00447 )
00448 {
00449         THROW(potIndex >= 0 && potIndex < m_nPots,
00450             "Bad pot index: " << potIndex);
00451         return m_pot[potIndex];
00452 }
00453 
00454 
00455 
00456 eButton
00457 LJDevice::getButtonValue
00458 (
00459 IN int buttonIndex
00460 )
00461 {
00462         THROW(buttonIndex >= 0 && buttonIndex < m_nButtons,
00463             "Bad button index: " << buttonIndex);
00464         return (eButton) m_button[buttonIndex];
00465 }
00466 
00467 
00468 
00469 static void
00470 refreshLinuxJoystickDevicePaths
00471 (
00472 void
00473 )
00474 {
00475         perf::Timer timer("refreshLinuxJoystickDevicePaths");
00476 
00477         // get the count of joystick entries!
00478         smart_dir dir;
00479         dir.open(s_devicePath);
00480         ASSERT(dir, "should have a valid open directory now");
00481 
00482         // go ahead and parse the directory
00483         int prefixLength = strlen(s_devicePrefix);
00484         SetString newSet;
00485         struct dirent entry;
00486         while (dir.getNextEntry(entry)) {
00487                 // only look at entries beginning with "js"
00488                 if (strncmp(s_devicePrefix, entry.d_name, prefixLength)) {
00489                         continue;       // some other prefix
00490                 }
00491 
00492                 std::string path;
00493                 appendPath(s_devicePath, entry.d_name, path);
00494 
00495                 // quick check--already know about this one?
00496                 if (s_devicePaths.end() != s_devicePaths.find(path.c_str())) {
00497                         // yes, known good path
00498                         newSet.insert(path);
00499                         continue;
00500                 }
00501 
00502                 // new path--see if this is a joystick object
00503                 smart_fd fd;
00504                 if (fd.open(path.c_str(), O_RDONLY) && isJoystickEntry(fd)) {
00505                         newSet.insert(path);
00506                 }
00507         }
00508 
00509         // copy over
00510         s_devicePaths = newSet;
00511 }
00512 
00513 
00514 
00515 smart_ptr<SourceDevice>
00516 getLinuxJoystickSourceDevice
00517 (
00518 IN const char * path
00519 )
00520 {
00521         ASSERT(path, "null");
00522 
00523         // okay, create device!
00524         smart_ptr<LJDevice> local = new LJDevice;
00525         ASSERT(local, "out of memory");
00526         local->initialize(path);
00527 
00528         // all done
00529         return local;
00530 }
00531 
00532 
00533 
00534 ////////////////////////////////////////////////////////////////////////////////
00535 //
00536 //      Factory -- class that implements the gamepad::SourceDeviceFactory
00537 //              interface for linux joysticks.
00538 //
00539 ////////////////////////////////////////////////////////////////////////////////
00540 
00541 class Factory : public SourceDeviceFactory {
00542 public:
00543         // constructor, destructor ---------------------------------------------
00544         ~Factory(void) throw() { }
00545 
00546         // public class methods ------------------------------------------------
00547         void initialize(void) { }
00548 
00549         // gamepad::SourceDeviceFactory class interface methods ----------------
00550         const char * getName(void) const throw()
00551                                 { return "Linux Joystick Factory"; }
00552         int getCount(void); 
00553         smart_ptr<SourceDevice> getSourceDevice(IN int index);
00554 
00555 private:
00556         // private typedefs ----------------------------------------------------
00557         typedef std::map<std::string, smart_ptr<SourceDevice> > device_map_t;
00558 
00559         // private member data -------------------------------------------------
00560         device_map_t    m_devices;      // instantiated devices (path -> device)
00561 };
00562 
00563 
00564 
00565 int
00566 Factory::getCount
00567 (
00568 void
00569 )
00570 {
00571         // refresh our set of device paths
00572         refreshLinuxJoystickDevicePaths();
00573 
00574         // get rid of any source devices we no longer recognize
00575         VecString badPaths;
00576         for (device_map_t::iterator i = m_devices.begin(); i != m_devices.end();
00577              ++i) {
00578                 const char * path = i->first.c_str();
00579                 if (s_devicePaths.end() == s_devicePaths.find(path)) {
00580                         // no longer recognize this path--device detached?
00581                         badPaths.push_back(path);
00582                 }
00583         }
00584         for (VecString::iterator i = badPaths.begin(); i != badPaths.end();
00585              ++i) {
00586                 device_map_t::iterator iBad = m_devices.find(*i);
00587                 ASSERT(m_devices.end() != iBad, "Just saw this in map!");
00588                 m_devices.erase(iBad);
00589         }
00590         ASSERT(m_devices.size() <= s_devicePaths.size(),
00591             "Should have devices for a subset of paths!");
00592 
00593         // return size
00594         return s_devicePaths.size();
00595 }
00596 
00597 
00598 
00599 smart_ptr<SourceDevice>
00600 Factory::getSourceDevice
00601 (
00602 IN int index
00603 )
00604 {
00605         // get path for this index
00606         const char * path = NULL;
00607         for (SetString::const_iterator i = s_devicePaths.begin();
00608              i != s_devicePaths.end(); ++i, --index) {
00609                 if (!index) {
00610                         path = i->c_str();
00611                         break;
00612                 }
00613         }
00614         if (!path) {
00615                 DPRINTF("Invalid source device index: %d", index);
00616                 return NULL;
00617         }
00618 
00619         // return device if we've already got it
00620         device_map_t::iterator i = m_devices.find(path);
00621         if (m_devices.end() != i) {
00622                 return i->second;
00623         }
00624 
00625         // don't know this device!  Add it
00626         smart_ptr<SourceDevice> device =
00627             getLinuxJoystickSourceDevice(path);
00628         ASSERT(device, "failed to create linux input source device");
00629         m_devices[path] = device;
00630         return device;
00631 }
00632 
00633 
00634 
00635 ////////////////////////////////////////////////////////////////////////////////
00636 //
00637 //      public API
00638 //
00639 ////////////////////////////////////////////////////////////////////////////////
00640 
00641 smart_ptr<SourceDeviceFactory>
00642 getLinuxJoystickSourceDeviceFactory
00643 (
00644 void
00645 )
00646 {
00647         smart_ptr<Factory> local = new Factory;
00648         ASSERT(local, "out of memory");
00649 
00650         local->initialize();
00651 
00652         return local;
00653 }
00654 
00655 
00656 
00657 };      // gamepad namespace
00658