glut_input.c revision c041511d
1
2/* Copyright (c) Mark J. Kilgard, 1994, 1997, 1998. */
3
4/* This program is freely distributable without licensing fees
5   and is provided without guarantee or warrantee expressed or
6   implied. This program is -not- in the public domain. */
7
8#ifdef __VMS
9#include <GL/vms_x_fix.h>
10#endif
11
12#include <assert.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16
17#if !defined(_WIN32)
18#include <X11/Xlib.h>
19#if defined(__vms)
20#include <X11/XInput.h>
21#else
22#include <X11/extensions/XInput.h>
23#endif
24#include <X11/Xutil.h>
25#else
26#ifdef __MINGW32__
27#include <GL/gl.h>
28#endif
29#include <windows.h>
30#ifndef __CYGWIN32__
31#include <mmsystem.h>  /* Win32 Multimedia API header. */
32#endif
33#endif /* !_WIN32 */
34
35#include "glutint.h"
36
37int __glutNumDials = 0;
38int __glutNumSpaceballButtons = 0;
39int __glutNumButtonBoxButtons = 0;
40int __glutNumTabletButtons = 0;
41int __glutNumMouseButtons = 3;  /* Good guess. */
42XDevice *__glutTablet = NULL;
43XDevice *__glutDials = NULL;
44XDevice *__glutSpaceball = NULL;
45
46int __glutHasJoystick = 0;
47int __glutNumJoystickButtons = 0;
48int __glutNumJoystickAxes = 0;
49
50#if !defined(_WIN32)
51typedef struct _Range {
52  int min;
53  int range;
54} Range;
55
56#define NUM_SPACEBALL_AXIS	6
57#define NUM_TABLET_AXIS		2
58#define NUM_DIALS_AXIS		8
59
60Range __glutSpaceballRange[NUM_SPACEBALL_AXIS];
61Range __glutTabletRange[NUM_TABLET_AXIS];
62int *__glutDialsResolution;
63
64/* Safely assumes 0 is an illegal event type for X Input
65   extension events. */
66int __glutDeviceMotionNotify = 0;
67int __glutDeviceButtonPress = 0;
68int __glutDeviceButtonPressGrab = 0;
69int __glutDeviceButtonRelease = 0;
70int __glutDeviceStateNotify = 0;
71
72static int
73normalizeTabletPos(int axis, int rawValue)
74{
75  assert(rawValue >= __glutTabletRange[axis].min);
76  assert(rawValue <= __glutTabletRange[axis].min
77    + __glutTabletRange[axis].range);
78  /* Normalize rawValue to between 0 and 4000. */
79  return ((rawValue - __glutTabletRange[axis].min) * 4000) /
80    __glutTabletRange[axis].range;
81}
82
83static int
84normalizeDialAngle(int axis, int rawValue)
85{
86  /* XXX Assumption made that the resolution of the device is
87     number of clicks for one complete dial revolution.  This
88     is true for SGI's dial & button box. */
89  return (rawValue * 360.0) / __glutDialsResolution[axis];
90}
91
92static int
93normalizeSpaceballAngle(int axis, int rawValue)
94{
95  assert(rawValue >= __glutSpaceballRange[axis].min);
96  assert(rawValue <= __glutSpaceballRange[axis].min +
97    __glutSpaceballRange[axis].range);
98  /* Normalize rawValue to between -1800 and 1800. */
99  return ((rawValue - __glutSpaceballRange[axis].min) * 3600) /
100    __glutSpaceballRange[axis].range - 1800;
101}
102
103static int
104normalizeSpaceballDelta(int axis, int rawValue)
105{
106  assert(rawValue >= __glutSpaceballRange[axis].min);
107  assert(rawValue <= __glutSpaceballRange[axis].min +
108    __glutSpaceballRange[axis].range);
109  /* Normalize rawValue to between -1000 and 1000. */
110  return ((rawValue - __glutSpaceballRange[axis].min) * 2000) /
111    __glutSpaceballRange[axis].range - 1000;
112}
113
114static void
115queryTabletPos(GLUTwindow * window)
116{
117  XDeviceState *state;
118  XInputClass *any;
119  XValuatorState *v;
120  int i;
121
122  state = XQueryDeviceState(__glutDisplay, __glutTablet);
123  any = state->data;
124  for (i = 0; i < state->num_classes; i++) {
125#if defined(__cplusplus) || defined(c_plusplus)
126    switch (any->c_class) {
127#else
128    switch (any->class) {
129#endif
130    case ValuatorClass:
131      v = (XValuatorState *) any;
132      if (v->num_valuators < 2)
133        goto end;
134      if (window->tabletPos[0] == -1)
135        window->tabletPos[0] = normalizeTabletPos(0, v->valuators[0]);
136      if (window->tabletPos[1] == -1)
137        window->tabletPos[1] = normalizeTabletPos(1, v->valuators[1]);
138    }
139    any = (XInputClass *) ((char *) any + any->length);
140  }
141end:
142  XFreeDeviceState(state);
143}
144
145static void
146tabletPosChange(GLUTwindow * window, int first, int count, int *data)
147{
148  int i, value, genEvent = 0;
149
150  for (i = first; i < first + count; i++) {
151    switch (i) {
152    case 0:            /* X axis */
153    case 1:            /* Y axis */
154      value = normalizeTabletPos(i, data[i - first]);
155      if (value != window->tabletPos[i]) {
156        window->tabletPos[i] = value;
157        genEvent = 1;
158      }
159      break;
160    }
161  }
162  if (window->tabletPos[0] == -1 || window->tabletPos[1] == -1)
163    queryTabletPos(window);
164  if (genEvent)
165    window->tabletMotion(window->tabletPos[0], window->tabletPos[1]);
166}
167#endif /* !_WIN32 */
168
169static int
170__glutProcessDeviceEvents(XEvent * event)
171{
172#if !defined(_WIN32)
173  GLUTwindow *window;
174
175  /* XXX Ugly code fan out. */
176
177  /* Can't use switch/case since X Input event types are
178     dynamic. */
179
180  if (__glutDeviceMotionNotify && event->type == __glutDeviceMotionNotify) {
181    XDeviceMotionEvent *devmot = (XDeviceMotionEvent *) event;
182
183    window = __glutGetWindow(devmot->window);
184    if (window) {
185      if (__glutTablet
186        && devmot->deviceid == __glutTablet->device_id
187        && window->tabletMotion) {
188        tabletPosChange(window, devmot->first_axis, devmot->axes_count,
189          devmot->axis_data);
190      } else if (__glutDials
191          && devmot->deviceid == __glutDials->device_id
192        && window->dials) {
193        int i, first = devmot->first_axis, count = devmot->axes_count;
194
195        for (i = first; i < first + count; i++)
196          window->dials(i + 1,
197            normalizeDialAngle(i, devmot->axis_data[i - first]));
198      } else if (__glutSpaceball
199        && devmot->deviceid == __glutSpaceball->device_id) {
200        /* XXX Assume that space ball motion events come in as
201           all the first 6 axes.  Assume first 3 axes are XYZ
202           translations; second 3 axes are XYZ rotations. */
203        if (devmot->first_axis == 0 && devmot->axes_count == 6) {
204          if (window->spaceMotion)
205            window->spaceMotion(
206              normalizeSpaceballDelta(0, devmot->axis_data[0]),
207              normalizeSpaceballDelta(1, devmot->axis_data[1]),
208              normalizeSpaceballDelta(2, devmot->axis_data[2]));
209          if (window->spaceRotate)
210            window->spaceRotate(
211              normalizeSpaceballAngle(3, devmot->axis_data[3]),
212              normalizeSpaceballAngle(4, devmot->axis_data[4]),
213              normalizeSpaceballAngle(5, devmot->axis_data[5]));
214        }
215      }
216      return 1;
217    }
218  } else if (__glutDeviceButtonPress
219    && event->type == __glutDeviceButtonPress) {
220    XDeviceButtonEvent *devbtn = (XDeviceButtonEvent *) event;
221
222    window = __glutGetWindow(devbtn->window);
223    if (window) {
224      if (__glutTablet
225        && devbtn->deviceid == __glutTablet->device_id
226        && window->tabletButton
227        && devbtn->first_axis == 0
228        && devbtn->axes_count == 2) {
229        tabletPosChange(window, devbtn->first_axis, devbtn->axes_count,
230          devbtn->axis_data);
231        window->tabletButton(devbtn->button, GLUT_DOWN,
232          window->tabletPos[0], window->tabletPos[1]);
233      } else if (__glutDials
234          && devbtn->deviceid == __glutDials->device_id
235        && window->buttonBox) {
236        window->buttonBox(devbtn->button, GLUT_DOWN);
237      } else if (__glutSpaceball
238          && devbtn->deviceid == __glutSpaceball->device_id
239        && window->spaceButton) {
240        window->spaceButton(devbtn->button, GLUT_DOWN);
241      }
242      return 1;
243    }
244  } else if (__glutDeviceButtonRelease
245    && event->type == __glutDeviceButtonRelease) {
246    XDeviceButtonEvent *devbtn = (XDeviceButtonEvent *) event;
247
248    window = __glutGetWindow(devbtn->window);
249    if (window) {
250      if (__glutTablet
251        && devbtn->deviceid == __glutTablet->device_id
252        && window->tabletButton
253        && devbtn->first_axis == 0
254        && devbtn->axes_count == 2) {
255        tabletPosChange(window, devbtn->first_axis, devbtn->axes_count,
256          devbtn->axis_data);
257        window->tabletButton(devbtn->button, GLUT_UP,
258          window->tabletPos[0], window->tabletPos[1]);
259      } else if (__glutDials
260          && devbtn->deviceid == __glutDials->device_id
261        && window->buttonBox) {
262        window->buttonBox(devbtn->button, GLUT_UP);
263      } else if (__glutSpaceball
264          && devbtn->deviceid == __glutSpaceball->device_id
265        && window->spaceButton) {
266        window->spaceButton(devbtn->button, GLUT_UP);
267      }
268      return 1;
269    }
270  }
271#else
272  {
273    JOYINFOEX info;
274    JOYCAPS joyCaps;
275
276    memset(&info, 0, sizeof(JOYINFOEX));
277    info.dwSize = sizeof(JOYINFOEX);
278    info.dwFlags = JOY_RETURNALL;
279
280    if (joyGetPosEx(JOYSTICKID1,&info) != JOYERR_NOERROR) {
281      __glutHasJoystick = 1;
282      joyGetDevCaps(JOYSTICKID1, &joyCaps, sizeof(joyCaps));
283      __glutNumJoystickButtons = joyCaps.wNumButtons;
284      __glutNumJoystickAxes = joyCaps.wNumAxes;
285    } else {
286      __glutHasJoystick = 0;
287      __glutNumJoystickButtons = 0;
288      __glutNumJoystickAxes = 0;
289    }
290  }
291#endif /* !_WIN32 */
292  return 0;
293}
294
295static GLUTeventParser eventParser =
296{__glutProcessDeviceEvents, NULL};
297
298static void
299addDeviceEventParser(void)
300{
301  static Bool been_here = False;
302
303  if (been_here)
304    return;
305  been_here = True;
306  __glutRegisterEventParser(&eventParser);
307}
308
309static int
310probeDevices(void)
311{
312  static Bool been_here = False;
313  static int support;
314#if !defined(_WIN32)
315  XExtensionVersion *version;
316  XDeviceInfoPtr device_info, device;
317  XAnyClassPtr any;
318  XButtonInfoPtr b;
319  XValuatorInfoPtr v;
320  XAxisInfoPtr a;
321  int num_dev = 0, btns = 0, dials = 0;
322  int i, j, k;
323#endif /* !_WIN32 */
324
325  if (been_here) {
326    return support;
327  }
328  been_here = True;
329
330#if !defined(_WIN32)
331  version = XGetExtensionVersion(__glutDisplay, "XInputExtension");
332  /* Ugh.  XInput extension API forces annoying cast of a pointer
333     to a long so it can be compared with the NoSuchExtension
334     value (#defined to 1). */
335  if (version == NULL || ((long) version) == NoSuchExtension) {
336    support = 0;
337    return support;
338  }
339  XFree(version);
340  device_info = XListInputDevices(__glutDisplay, &num_dev);
341  if (device_info) {
342    for (i = 0; i < num_dev; i++) {
343      /* XXX These are SGI names for these devices;
344         unfortunately, no good standard exists for standard
345         types of X input extension devices. */
346
347      device = &device_info[i];
348      any = (XAnyClassPtr) device->inputclassinfo;
349
350      if (!__glutSpaceball && !strcmp(device->name, "spaceball")) {
351        v = NULL;
352        b = NULL;
353        for (j = 0; j < device->num_classes; j++) {
354#if defined(__cplusplus) || defined(c_plusplus)
355          switch (any->c_class) {
356#else
357          switch (any->class) {
358#endif
359          case ButtonClass:
360            b = (XButtonInfoPtr) any;
361            btns = b->num_buttons;
362            break;
363          case ValuatorClass:
364            v = (XValuatorInfoPtr) any;
365            /* Sanity check: at least 6 valuators? */
366            if (v->num_axes < NUM_SPACEBALL_AXIS)
367              goto skip_device;
368            a = (XAxisInfoPtr) ((char *) v + sizeof(XValuatorInfo));
369            for (k = 0; k < NUM_SPACEBALL_AXIS; k++, a++) {
370              __glutSpaceballRange[k].min = a->min_value;
371              __glutSpaceballRange[k].range = a->max_value - a->min_value;
372            }
373            break;
374          }
375          any = (XAnyClassPtr) ((char *) any + any->length);
376        }
377        if (v) {
378          __glutSpaceball = XOpenDevice(__glutDisplay, device->id);
379          if (__glutSpaceball) {
380            __glutNumSpaceballButtons = btns;
381            addDeviceEventParser();
382          }
383        }
384      } else if (!__glutDials && !strcmp(device->name, "dial+buttons")) {
385        v = NULL;
386        b = NULL;
387        for (j = 0; j < device->num_classes; j++) {
388#if defined(__cplusplus) || defined(c_plusplus)
389          switch (any->c_class) {
390#else
391          switch (any->class) {
392#endif
393          case ButtonClass:
394            b = (XButtonInfoPtr) any;
395            btns = b->num_buttons;
396            break;
397          case ValuatorClass:
398            v = (XValuatorInfoPtr) any;
399            /* Sanity check: at least 8 valuators? */
400            if (v->num_axes < NUM_DIALS_AXIS)
401              goto skip_device;
402            dials = v->num_axes;
403            __glutDialsResolution = (int *) malloc(sizeof(int) * dials);
404            a = (XAxisInfoPtr) ((char *) v + sizeof(XValuatorInfo));
405            for (k = 0; k < dials; k++, a++) {
406              __glutDialsResolution[k] = a->resolution;
407            }
408            break;
409          }
410          any = (XAnyClassPtr) ((char *) any + any->length);
411        }
412        if (v) {
413          __glutDials = XOpenDevice(__glutDisplay, device->id);
414          if (__glutDials) {
415            __glutNumButtonBoxButtons = btns;
416            __glutNumDials = dials;
417            addDeviceEventParser();
418          }
419        }
420      } else if (!__glutTablet && !strcmp(device->name, "tablet")) {
421        v = NULL;
422        b = NULL;
423        for (j = 0; j < device->num_classes; j++) {
424#if defined(__cplusplus) || defined(c_plusplus)
425          switch (any->c_class) {
426#else
427          switch (any->class) {
428#endif
429          case ButtonClass:
430            b = (XButtonInfoPtr) any;
431            btns = b->num_buttons;
432            break;
433          case ValuatorClass:
434            v = (XValuatorInfoPtr) any;
435            /* Sanity check: exactly 2 valuators? */
436            if (v->num_axes != NUM_TABLET_AXIS)
437              goto skip_device;
438            a = (XAxisInfoPtr) ((char *) v + sizeof(XValuatorInfo));
439            for (k = 0; k < NUM_TABLET_AXIS; k++, a++) {
440              __glutTabletRange[k].min = a->min_value;
441              __glutTabletRange[k].range = a->max_value - a->min_value;
442            }
443            break;
444          }
445          any = (XAnyClassPtr) ((char *) any + any->length);
446        }
447        if (v) {
448          __glutTablet = XOpenDevice(__glutDisplay, device->id);
449          if (__glutTablet) {
450            __glutNumTabletButtons = btns;
451            addDeviceEventParser();
452          }
453        }
454      } else if (!strcmp(device->name, "mouse")) {
455        for (j = 0; j < device->num_classes; j++) {
456#if defined(__cplusplus) || defined(c_plusplus)
457          if (any->c_class == ButtonClass) {
458#else
459          if (any->class == ButtonClass) {
460#endif
461            b = (XButtonInfoPtr) any;
462            __glutNumMouseButtons = b->num_buttons;
463          }
464          any = (XAnyClassPtr) ((char *) any + any->length);
465        }
466      }
467    skip_device:;
468    }
469    XFreeDeviceList(device_info);
470  }
471#else /* _WIN32 */
472  __glutNumMouseButtons = GetSystemMetrics(SM_CMOUSEBUTTONS);
473#endif /* !_WIN32 */
474  /* X Input extension might be supported, but only if there is
475     a tablet, dials, or spaceball do we claim devices are
476     supported. */
477  support = __glutTablet || __glutDials || __glutSpaceball;
478  return support;
479}
480
481void
482__glutUpdateInputDeviceMask(GLUTwindow * window)
483{
484#if !defined(_WIN32)
485  /* 5 (dial and buttons) + 5 (tablet locator and buttons) + 5
486     (Spaceball buttons and axis) = 15 */
487  XEventClass eventList[15];
488  int rc, numEvents;
489
490  rc = probeDevices();
491  if (rc) {
492    numEvents = 0;
493    if (__glutTablet) {
494      if (window->tabletMotion) {
495        DeviceMotionNotify(__glutTablet, __glutDeviceMotionNotify,
496          eventList[numEvents]);
497        numEvents++;
498      }
499      if (window->tabletButton) {
500        DeviceButtonPress(__glutTablet, __glutDeviceButtonPress,
501          eventList[numEvents]);
502        numEvents++;
503        DeviceButtonPressGrab(__glutTablet, __glutDeviceButtonPressGrab,
504          eventList[numEvents]);
505        numEvents++;
506        DeviceButtonRelease(__glutTablet, __glutDeviceButtonRelease,
507          eventList[numEvents]);
508        numEvents++;
509      }
510      if (window->tabletMotion || window->tabletButton) {
511        DeviceStateNotify(__glutTablet, __glutDeviceStateNotify,
512          eventList[numEvents]);
513        numEvents++;
514      }
515    }
516    if (__glutDials) {
517      if (window->dials) {
518        DeviceMotionNotify(__glutDials, __glutDeviceMotionNotify,
519          eventList[numEvents]);
520        numEvents++;
521      }
522      if (window->buttonBox) {
523        DeviceButtonPress(__glutDials, __glutDeviceButtonPress,
524          eventList[numEvents]);
525        numEvents++;
526        DeviceButtonPressGrab(__glutDials, __glutDeviceButtonPressGrab,
527          eventList[numEvents]);
528        numEvents++;
529        DeviceButtonRelease(__glutDials, __glutDeviceButtonRelease,
530          eventList[numEvents]);
531        numEvents++;
532      }
533      if (window->dials || window->buttonBox) {
534        DeviceStateNotify(__glutDials, __glutDeviceStateNotify,
535          eventList[numEvents]);
536        numEvents++;
537      }
538    }
539    if (__glutSpaceball) {
540      if (window->spaceMotion || window->spaceRotate) {
541        DeviceMotionNotify(__glutSpaceball, __glutDeviceMotionNotify,
542          eventList[numEvents]);
543        numEvents++;
544      }
545      if (window->spaceButton) {
546        DeviceButtonPress(__glutSpaceball, __glutDeviceButtonPress,
547          eventList[numEvents]);
548        numEvents++;
549        DeviceButtonPressGrab(__glutSpaceball, __glutDeviceButtonPressGrab,
550          eventList[numEvents]);
551        numEvents++;
552        DeviceButtonRelease(__glutSpaceball, __glutDeviceButtonRelease,
553          eventList[numEvents]);
554        numEvents++;
555      }
556      if (window->spaceMotion || window->spaceRotate || window->spaceButton) {
557        DeviceStateNotify(__glutSpaceball, __glutDeviceStateNotify,
558          eventList[numEvents]);
559        numEvents++;
560      }
561    }
562#if 0
563    if (window->children) {
564      GLUTwindow *child = window->children;
565
566      do {
567        XChangeDeviceDontPropagateList(__glutDisplay, child->win,
568          numEvents, eventList, AddToList);
569        child = child->siblings;
570      } while (child);
571    }
572#endif
573    XSelectExtensionEvent(__glutDisplay, window->win,
574      eventList, numEvents);
575    if (window->overlay) {
576      XSelectExtensionEvent(__glutDisplay, window->overlay->win,
577        eventList, numEvents);
578    }
579  } else {
580    /* X Input extension not supported; no chance for exotic
581       input devices. */
582  }
583#endif /* !_WIN32 */
584}
585
586/* CENTRY */
587int GLUTAPIENTRY
588glutDeviceGet(GLenum param)
589{
590  probeDevices();
591  switch (param) {
592  case GLUT_HAS_KEYBOARD:
593  case GLUT_HAS_MOUSE:
594    /* Assume window system always has mouse and keyboard. */
595    return 1;
596  case GLUT_HAS_SPACEBALL:
597    return __glutSpaceball != NULL;
598  case GLUT_HAS_DIAL_AND_BUTTON_BOX:
599    return __glutDials != NULL;
600  case GLUT_HAS_TABLET:
601    return __glutTablet != NULL;
602  case GLUT_NUM_MOUSE_BUTTONS:
603    return __glutNumMouseButtons;
604  case GLUT_NUM_SPACEBALL_BUTTONS:
605    return __glutNumSpaceballButtons;
606  case GLUT_NUM_BUTTON_BOX_BUTTONS:
607    return __glutNumButtonBoxButtons;
608  case GLUT_NUM_DIALS:
609    return __glutNumDials;
610  case GLUT_NUM_TABLET_BUTTONS:
611    return __glutNumTabletButtons;
612  case GLUT_DEVICE_IGNORE_KEY_REPEAT:
613    return __glutCurrentWindow->ignoreKeyRepeat;
614#ifndef _WIN32
615  case GLUT_DEVICE_KEY_REPEAT:
616    {
617      XKeyboardState state;
618
619      XGetKeyboardControl(__glutDisplay, &state);
620      return state.global_auto_repeat;
621    }
622  case GLUT_JOYSTICK_POLL_RATE:
623    return 0;
624#else
625  case GLUT_DEVICE_KEY_REPEAT:
626    /* Win32 cannot globally disable key repeat. */
627    return GLUT_KEY_REPEAT_ON;
628  case GLUT_JOYSTICK_POLL_RATE:
629    return __glutCurrentWindow->joyPollInterval;
630#endif
631  case GLUT_HAS_JOYSTICK:
632    return __glutHasJoystick;
633  case GLUT_JOYSTICK_BUTTONS:
634    return __glutNumJoystickButtons;
635  case GLUT_JOYSTICK_AXES:
636    return __glutNumJoystickAxes;
637  default:
638    __glutWarning("invalid glutDeviceGet parameter: %d", param);
639    return -1;
640  }
641}
642/* ENDCENTRY */
643