11.3. Creating a Device

This section provides detailed information on creating a new device, using the dial and button box as a sample.

Creating a new SoXt device class requires these steps:

First, the constructor for the device obtains a list of the input devices currently attached to the display. In our example, it loops through the list and looks for a device named “dial+buttons.” If found, it opens that device (using XOpenDevice).

Next, the device queries the X Server for the event types it generates. (Recall that these const values are available only at runtime because they are part of the X input extension.) The DeviceMotionNotify() function returns the event class and event type for motion events. The DeviceButtonPress() and DeviceButtonRelease() functions return the event class and event type for the button-press and button-release events.

In its enable() method, the DialNButton device calls XSelectExtensionEvent() to register interest in its event classes with the X Server.

The device also must inform Inventor's main loop about the extension events it is prepared to translate. To do this, it calls addExtensionEventHandler() on SoXt main loop and passes in the event types (obtained earlier with DeviceMotionNotify(), DeviceButtonPress(), and DeviceButtonRelease()).

The DialNButton translateEvent() method contains two routines:

First, the translateMotionEvent() sets the position, time, and the state of the Shift, Control, and Alt keys at the time of the event. Then, it sets the dial and value. The variables in the XDeviceMotionEvent are stored differently for different devices. Check the documentation for your device driver for information on how data is stored in this event. The XDeviceMotionEvent for the dial box stores which dial was turned in its first_axis field, and the value of that dial in its axis_data[0] variable.

As described in the section called “Translating Events”, the translateEvent() method creates the SoEvent SoEvent SoEvent and then sets the time, state of the modifier keys, and so on.

Example 11.4, “ DialNButton.h ” shows the code for DialNButton.h.

Example 11.4.  DialNButton.h

#include <X11/X.h>
#include <X11/extensions/XInput.h>
#include <Inventor/Xt/devices/SoXtDevice.h>
#include <Inventor/events/SoButtonEvent.h>

class ButtonBoxEvent;
class DialEvent;

class DialNButton : public SoXtDevice {
  public:
   // The first constructor uses the display set when 
   // SoXt::init is called.
   DialNButton();
   DialNButton(Display *d);
   ~DialNButton();
   
   // These functions will enable/disable this device for the 
   // widget. The callback function f will be invoked when 
   // events occur in w. data is the clientData which will be
   // passed.
   virtual void        enable(Widget w, XtEventHandler f,
                          XtPointer data, Window win = NULL);
   virtual void        disable(Widget w, XtEventHandler f,
                          XtPointer data);
   
   // This converts an X event into an SoEvent,
   // returning NULL if the event is not from this device.
   //
      virtual const SoEvent * translateEvent(XAnyEvent *xevent);
   
   // Return whether or not the dial+button device exists for use.
   // The first uses the display set when SoXt::init is called.
   static SbBool        exists() 
                           { return exists(SoXt::getDisplay()); }
   static SbBool        exists(Display *d);
   
  protected:
   // Initialize the device.
   static void          init(Display *d);
   static SbBool        firstTime;
   
   // These event types are retrieved from the X server at run
   // time.
   static int           motionEventType;
   static int           buttonPressEventType;
   static int           buttonReleaseEventType;
   
   // Event classes passed to XSelectExtensionEvent.
   static XEventClass   eventClasses[3];//max of 3 event classes
   static int           eventTypes[3]; // max of 3 event types
   
   // Device id is set at runtime.
   static XDevice       *device;
   
   // Inventor events generated by this device.
   ButtonBoxEvent       *buttonEvent;
   DialEvent            *dialEvent;

   // Event translators!
   DialEvent       *translateMotionEvent(XDeviceMotionEvent *me);
   ButtonBoxEvent  *translateButtonEvent(
                       XDeviceButtonEvent *be,
                       SoButtonEvent::State whichState);
};

Example 11.5, “ DialNButton.c++ shows the source code for DialNButton.c++.

Example 11.5.  DialNButton.c++

#include <X11/Xlib.h>
#include <X11/extensions/XI.h>

#include <Inventor/SbTime.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/events/SoButtonEvent.h>

#include "ButtonBoxEvent.h"
#include "DialEvent.h"
#include "DialNButton.h"

extern "C" {
XDeviceInfo *XListInputDevices(Display *, int *);
XDevice     *XOpenDevice(Display *, XID);
int         XSelectExtensionEvent(Display *, Window, XEventClass *, int);
}

#define DEVICE_NAME "dial+buttons"

// There are 3 event classes for this device:
// motion, button down, button up.
static const int numEventClasses = 3;

// Static members
SbBool DialNButton::firstTime = TRUE;
int DialNButton::motionEventType;
int DialNButton::buttonPressEventType;
int DialNButton::buttonReleaseEventType;
XEventClass DialNButton::eventClasses[3];
int DialNButton::eventTypes[3];
XDevice *DialNButton::device;

// Description:
//  Initialize the dial+button device. 
//  We only need to do this once.

void
DialNButton::init(Display *display)
{    
   // If already initialized, return.
   if (! firstTime) 
      return;
   
   firstTime = FALSE;
   
   // Get the list of input devices that are attached to the
   // display now.
   XDeviceInfoPtr  list;
   int              numDevices;
   
   list = (XDeviceInfoPtr) XListInputDevices(display, &numDevices);
   
   // Now run through the list looking for the dial+button
   // device.
   device = NULL;
   for (int i = 0; (i < numDevices) && (device == NULL); i++) {
     // Open the device - the device id is set at runtime.
     if (strcmp(list[i].name, DEVICE_NAME) == 0) {
       device = XOpenDevice(display, list[i].id);
     }
   }
   
   // Make sure we found the device
   if (device == NULL) {
     fprintf(stderr, "DialNButton::init",
       "Sorry there is no dial and button attached to this display");
     return;
   }
   
   // Query the event types and classes
   unsigned long eventClass;
   
   DeviceMotionNotify(device, motionEventType, eventClass);
   eventClasses[0] = eventClass;
   eventTypes[0] = motionEventType;
   
   DeviceButtonPress(device, buttonPressEventType, eventClass);
   eventClasses[1] = eventClass;
   eventTypes[1] = buttonPressEventType;

   DeviceButtonRelease(device, buttonReleaseEventType, 
                       eventClass);
   eventClasses[2] = eventClass;
   eventTypes[2] = buttonReleaseEventType; 
   
   // Init all dial values to 0
   static int vals[8] = {0, 0, 0, 0, 0, 0, 0, 0};
   XSetDeviceValuators(display, device, vals, 0, 8);  
}

// Constructor using default display
DialNButton::DialNButton()
{    
   init(SoXt::getDisplay());

   buttonEvent = new ButtonBoxEvent;
   dialEvent = new DialEvent;
}
   
// Constructor
DialNButton::DialNButton(Display *d)
{    
   init(d);

   buttonEvent = new ButtonBoxEvent;
   dialEvent = new DialEvent;
}
   
// Destructor
DialNButton::~DialNButton()
{
   delete buttonEvent;
   delete dialEvent;
}

// Returns whether the dial+button device exists for use or
// not.
SbBool
DialNButton::exists(Display *display)
{
   // Get the list of input devices that are attached to the
   // display now.
   XDeviceInfoPtr  list;
   int              numDevices;
   
   list = (XDeviceInfoPtr) XListInputDevices(display, &numDevices);
   
   // Now run through the list looking for the dial + button
   // device.
   for (int i = 0; (i < numDevices) &&
               (strcmp(list[i].name, DEVICE_NAME) != 0); i++)
     ; // keep looping

   // If we broke out of the loop before i reached numDevices,
   // then the dial + button does in fact exist. 
   return (i < numDevices);
}

// This selects input for dial + button device events which
// occur in w.
// The callback routine is proc, and the callback data is
// clientData.
void
DialNButton::enable(
   Widget w,
   XtEventHandler proc, 
   XtPointer clientData,
   Window window)
{
   if (numEventClasses == 0) 
      return;
   
   Display *display = XtDisplay(w);
   if (display == NULL) {
     fprintf(stderr, "DialNButton::enable",
             "SoXt::init not properly called (Display is NULL).");
     return;
   }
   
   if (w == NULL) {
     fprintf(stderr, "DialNButton::enable",
             "widget is NULL.");
     return;
   }
   
   if (window == NULL) {
     fprintf(stderr, "DialNButton::enable",
             "widget must be realized (Window is NULL).");
     return;
   }
   
   // Select extension events for the dial + button which the
   // user wants.
   XSelectExtensionEvent(display, window,
                         eventClasses, numEventClasses);
   
   // Tell Inventor about these extension events!
   for (int i = 0; i < numEventClasses; i++)
     SoXt::addExtensionEventHandler(
       w, eventTypes[i], proc, clientData);
}

// This unselects input for dial + button device events which
// occur in w,
// i.e. dial + button events will no longer be recognized.
void
DialNButton::disable(
   Widget w,
   XtEventHandler proc, 
   XtPointer clientData)
{
   // Tell Inventor to forget about these classes.
   for (int i = 0; i < numEventClasses; i++)
     SoXt::removeExtensionEventHandler(
       w, eventTypes[i], proc, clientData);
}

// Translate X events into Inventor events.
const SoEvent *
DialNButton::translateEvent(XAnyEvent *xevent)
{
   SoEvent *event = NULL;
   
   // See if this is a dial + button event.
   if (xevent->type == motionEventType) {
     XDeviceMotionEvent *me = (XDeviceMotionEvent *) xevent;
     if (me->deviceid == device->device_id)
       event = translateMotionEvent(me);
   }
   else if (xevent->type == buttonPressEventType) {
     XDeviceButtonEvent *be = (XDeviceButtonEvent *) xevent;
     if (be->deviceid == device->device_id)
       event = translateButtonEvent(be, SoButtonEvent::DOWN);
   }
   else if (xevent->type == buttonReleaseEventType) {
     XDeviceButtonEvent *be = (XDeviceButtonEvent *) xevent;
     if (be->deviceid == device->device_id)
       event = translateButtonEvent(be, SoButtonEvent::UP);
   }
   
   return event;
}

// This returns a DialEvent for the passed X event.
DialEvent *
DialNButton::translateMotionEvent(XDeviceMotionEvent *me)
{
   setEventPosition(dialEvent, me->x, me->y);
   dialEvent->setTime(SbTime(0, 1000*me->time));
   dialEvent->setShiftDown(me->state & ShiftMask);
   dialEvent->setCtrlDown(me->state & ControlMask);
   dialEvent->setAltDown(me->state & Mod1Mask);
   
   // the dial that turned is stored as first_axis in the X event.
   // the value is always in axis_data[0].
   dialEvent->setDial(me->first_axis);
   dialEvent->setValue(me->axis_data[0]);
   
   return dialEvent;
}

// This returns a ButtonBoxEvent for the passed X event.
ButtonBoxEvent *
DialNButton::translateButtonEvent(
   XDeviceButtonEvent *be,
   SoButtonEvent::State whichState)
{
   setEventPosition(buttonEvent, be->x, be->y);
   buttonEvent->setTime(SbTime(0, 1000*be->time));
   buttonEvent->setShiftDown(be->state & ShiftMask);
   buttonEvent->setCtrlDown(be->state & ControlMask);
   buttonEvent->setAltDown(be->state & Mod1Mask);
   
   // Set which button along with its state.
   buttonEvent->setButton(be->button);
   buttonEvent->setState(whichState);
   
   return buttonEvent;
}