Blob Blame History Raw
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML
><HEAD
><TITLE
>Adding XInput support</TITLE
><META
NAME="GENERATOR"
CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
REL="HOME"
TITLE="GTK+ 2.0 Tutorial"
HREF="book1.html"><LINK
REL="UP"
TITLE="Scribble, A Simple Example Drawing Program"
HREF="c2422.html"><LINK
REL="PREVIOUS"
TITLE="The DrawingArea Widget, And Drawing"
HREF="x2470.html"><LINK
REL="NEXT"
TITLE="Tips For Writing GTK Applications"
HREF="c2615.html"></HEAD
><BODY
CLASS="SECT1"
BGCOLOR="#FFFFFF"
TEXT="#000000"
LINK="#0000FF"
VLINK="#840084"
ALINK="#0000FF"
><DIV
CLASS="NAVHEADER"
><TABLE
SUMMARY="Header navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TH
COLSPAN="3"
ALIGN="center"
>GTK+ 2.0 Tutorial</TH
></TR
><TR
><TD
WIDTH="10%"
ALIGN="left"
VALIGN="bottom"
><A
HREF="x2470.html"
ACCESSKEY="P"
>&#60;&#60;&#60; Previous</A
></TD
><TD
WIDTH="80%"
ALIGN="center"
VALIGN="bottom"
>Scribble, A Simple Example Drawing Program</TD
><TD
WIDTH="10%"
ALIGN="right"
VALIGN="bottom"
><A
HREF="c2615.html"
ACCESSKEY="N"
>Next &#62;&#62;&#62;</A
></TD
></TR
></TABLE
><HR
ALIGN="LEFT"
WIDTH="100%"></DIV
><DIV
CLASS="SECT1"
><H1
CLASS="SECT1"
><A
NAME="SEC-ADDINGXINPUTSUPPORT"
>Adding XInput support</A
></H1
><P
>It is now possible to buy quite inexpensive input devices such 
as drawing tablets, which allow drawing with a much greater
ease of artistic expression than does a mouse. The simplest way
to use such devices is simply as a replacement for the mouse,
but that misses out many of the advantages of these devices,
such as:</P
><P
></P
><UL
><LI
><P
> Pressure sensitivity</P
></LI
><LI
><P
> Tilt reporting</P
></LI
><LI
><P
> Sub-pixel positioning</P
></LI
><LI
><P
> Multiple inputs (for example, a stylus with a point and eraser)</P
></LI
></UL
><P
>For information about the XInput extension, see the <A
HREF="http://www.gtk.org/~otaylor/xinput/howto/index.html"
TARGET="_top"
>XInput HOWTO</A
>.</P
><P
>If we examine the full definition of, for example, the GdkEventMotion
structure, we see that it has fields to support extended device
information.</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>struct _GdkEventMotion
{
  GdkEventType type;
  GdkWindow *window;
  guint32 time;
  gdouble x;
  gdouble y;
  gdouble pressure;
  gdouble xtilt;
  gdouble ytilt;
  guint state;
  gint16 is_hint;
  GdkInputSource source;
  guint32 deviceid;
};</PRE
></TD
></TR
></TABLE
><P
><TT
CLASS="LITERAL"
>pressure</TT
> gives the pressure as a floating point number between
0 and 1. <TT
CLASS="LITERAL"
>xtilt</TT
> and <TT
CLASS="LITERAL"
>ytilt</TT
> can take on values between 
-1 and 1, corresponding to the degree of tilt in each direction.
<TT
CLASS="LITERAL"
>source</TT
> and <TT
CLASS="LITERAL"
>deviceid</TT
> specify the device for which the
event occurred in two different ways. <TT
CLASS="LITERAL"
>source</TT
> gives some simple
information about the type of device. It can take the enumeration
values:</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>GDK_SOURCE_MOUSE
GDK_SOURCE_PEN
GDK_SOURCE_ERASER
GDK_SOURCE_CURSOR</PRE
></TD
></TR
></TABLE
><P
><TT
CLASS="LITERAL"
>deviceid</TT
> specifies a unique numeric ID for the device. This can
be used to find out further information about the device using the
<TT
CLASS="LITERAL"
>gdk_input_list_devices()</TT
> call (see below). The special value
<TT
CLASS="LITERAL"
>GDK_CORE_POINTER</TT
> is used for the core pointer device. (Usually
the mouse.)</P
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2557"
>Enabling extended device information</A
></H2
><P
>To let GTK know about our interest in the extended device information,
we merely have to add a single line to our program:</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);</PRE
></TD
></TR
></TABLE
><P
>By giving the value <TT
CLASS="LITERAL"
>GDK_EXTENSION_EVENTS_CURSOR</TT
> we say that
we are interested in extension events, but only if we don't have
to draw our own cursor. See the section <A
HREF="x2529.html#SEC-FURTHERSOPHISTICATIONS"
> Further Sophistications </A
> below
for more information about drawing the cursor. We could also 
give the values <TT
CLASS="LITERAL"
>GDK_EXTENSION_EVENTS_ALL</TT
> if we were willing 
to draw our own cursor, or <TT
CLASS="LITERAL"
>GDK_EXTENSION_EVENTS_NONE</TT
> to revert
back to the default condition.</P
><P
>This is not completely the end of the story however. By default,
no extension devices are enabled. We need a mechanism to allow
users to enable and configure their extension devices. GTK provides
the InputDialog widget to automate this process. The following
procedure manages an InputDialog widget. It creates the dialog if
it isn't present, and raises it to the top otherwise.</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>void
input_dialog_destroy (GtkWidget *w, gpointer data)
{
  *((GtkWidget **)data) = NULL;
}

void
create_input_dialog ()
{
  static GtkWidget *inputd = NULL;

  if (!inputd)
    {
      inputd = gtk_input_dialog_new();

      gtk_signal_connect (GTK_OBJECT(inputd), "destroy",
			  (GtkSignalFunc)input_dialog_destroy, &#38;inputd);
      gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)-&#62;close_button),
				 "clicked",
				 (GtkSignalFunc)gtk_widget_hide,
				 GTK_OBJECT(inputd));
      gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)-&#62;save_button);

      gtk_widget_show (inputd);
    }
  else
    {
      if (!gtk_widget_get_mapped(inputd))
	gtk_widget_show(inputd);
      else
	gdk_window_raise(inputd-&#62;window);
    }
}</PRE
></TD
></TR
></TABLE
><P
>(You might want to take note of the way we handle this dialog.  By
connecting to the "destroy" signal, we make sure that we don't keep a
pointer to dialog around after it is destroyed - that could lead to a
segfault.)</P
><P
>The InputDialog has two buttons "Close" and "Save", which by default
have no actions assigned to them. In the above function we make
"Close" hide the dialog, hide the "Save" button, since we don't
implement saving of XInput options in this program.</P
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2570"
>Using extended device information</A
></H2
><P
>Once we've enabled the device, we can just use the extended 
device information in the extra fields of the event structures.
In fact, it is always safe to use this information since these
fields will have reasonable default values even when extended
events are not enabled.</P
><P
>Once change we do have to make is to call
<TT
CLASS="LITERAL"
>gdk_input_window_get_pointer()</TT
> instead of
<TT
CLASS="LITERAL"
>gdk_window_get_pointer</TT
>. This is necessary because
<TT
CLASS="LITERAL"
>gdk_window_get_pointer</TT
> doesn't return the extended device
information.</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>void gdk_input_window_get_pointer( GdkWindow       *window,
                                   guint32         deviceid,
                                   gdouble         *x,
                                   gdouble         *y,
                                   gdouble         *pressure,
                                   gdouble         *xtilt,
                                   gdouble         *ytilt,
                                   GdkModifierType *mask);</PRE
></TD
></TR
></TABLE
><P
>When calling this function, we need to specify the device ID as
well as the window. Usually, we'll get the device ID from the
<TT
CLASS="LITERAL"
>deviceid</TT
> field of an event structure. Again, this function
will return reasonable values when extension events are not
enabled. (In this case, <TT
CLASS="LITERAL"
>event-&#62;deviceid</TT
> will have the value
<TT
CLASS="LITERAL"
>GDK_CORE_POINTER</TT
>).</P
><P
>So the basic structure of our button-press and motion event handlers
doesn't change much - we just need to add code to deal with the
extended information.</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>static gboolean
button_press_event( GtkWidget *widget, GdkEventButton *event )
{
  print_button_press (event-&#62;deviceid);
  
  if (event-&#62;button == 1 &#38;&#38; pixmap != NULL)
    draw_brush (widget, event-&#62;source, event-&#62;x, event-&#62;y, event-&#62;pressure);

  return TRUE;
}

static gboolean
motion_notify_event( GtkWidget *widget, GdkEventMotion *event )
{
  gdouble x, y;
  gdouble pressure;
  GdkModifierType state;

  if (event-&#62;is_hint)
    gdk_input_window_get_pointer (event-&#62;window, event-&#62;deviceid,
				  &#38;x, &#38;y, &#38;pressure, NULL, NULL, &#38;state);
  else
    {
      x = event-&#62;x;
      y = event-&#62;y;
      pressure = event-&#62;pressure;
      state = event-&#62;state;
    }
    
  if (state &#38; GDK_BUTTON1_MASK &#38;&#38; pixmap != NULL)
    draw_brush (widget, event-&#62;source, x, y, pressure);
  
  return TRUE;
}</PRE
></TD
></TR
></TABLE
><P
>We also need to do something with the new information. Our new
<TT
CLASS="LITERAL"
>draw_brush()</TT
> function draws with a different color for
each <TT
CLASS="LITERAL"
>event-&#62;source</TT
> and changes the brush size depending
on the pressure.</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>/* Draw a rectangle on the screen, size depending on pressure,
   and color on the type of device */
static void
draw_brush (GtkWidget *widget, GdkInputSource source,
	    gdouble x, gdouble y, gdouble pressure)
{
  GdkGC *gc;
  GdkRectangle update_rect;

  switch (source)
    {
    case GDK_SOURCE_MOUSE:
      gc = widget-&#62;style-&#62;dark_gc[gtk_widget_get_state (widget)];
      break;
    case GDK_SOURCE_PEN:
      gc = widget-&#62;style-&#62;black_gc;
      break;
    case GDK_SOURCE_ERASER:
      gc = widget-&#62;style-&#62;white_gc;
      break;
    default:
      gc = widget-&#62;style-&#62;light_gc[gtk_widget_get_state (widget)];
    }

  update_rect.x = x - 10 * pressure;
  update_rect.y = y - 10 * pressure;
  update_rect.width = 20 * pressure;
  update_rect.height = 20 * pressure;
  gdk_draw_rectangle (pixmap, gc, TRUE,
		      update_rect.x, update_rect.y,
		      update_rect.width, update_rect.height);
  gtk_widget_draw (widget, &#38;update_rect);
}</PRE
></TD
></TR
></TABLE
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2588"
>Finding out more about a device</A
></H2
><P
>As an example of how to find out more about a device, our program
will print the name of the device that generates each button
press. To find out the name of a device, we call the function:</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>GList *gdk_input_list_devices               (void);</PRE
></TD
></TR
></TABLE
><P
>which returns a GList (a linked list type from the GLib library)
of GdkDeviceInfo structures. The GdkDeviceInfo structure is defined
as:</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>struct _GdkDeviceInfo
{
  guint32 deviceid;
  gchar *name;
  GdkInputSource source;
  GdkInputMode mode;
  gint has_cursor;
  gint num_axes;
  GdkAxisUse *axes;
  gint num_keys;
  GdkDeviceKey *keys;
};</PRE
></TD
></TR
></TABLE
><P
>Most of these fields are configuration information that you can ignore
unless you are implementing XInput configuration saving. The fieldwe
are interested in here is <TT
CLASS="LITERAL"
>name</TT
> which is simply the name that X
assigns to the device. The other field that isn't configuration
information is <TT
CLASS="LITERAL"
>has_cursor</TT
>. If <TT
CLASS="LITERAL"
>has_cursor</TT
> is false, then we
we need to draw our own cursor. But since we've specified
<TT
CLASS="LITERAL"
>GDK_EXTENSION_EVENTS_CURSOR</TT
>, we don't have to worry about this.</P
><P
>Our <TT
CLASS="LITERAL"
>print_button_press()</TT
> function simply iterates through
the returned list until it finds a match, then prints out
the name of the device.</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>static void
print_button_press (guint32 deviceid)
{
  GList *tmp_list;

  /* gdk_input_list_devices returns an internal list, so we shouldn't
     free it afterwards */
  tmp_list = gdk_input_list_devices();

  while (tmp_list)
    {
      GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list-&#62;data;

      if (info-&#62;deviceid == deviceid)
	{
	  printf("Button press on device '%s'\n", info-&#62;name);
	  return;
	}

      tmp_list = tmp_list-&#62;next;
    }
}</PRE
></TD
></TR
></TABLE
><P
>That completes the changes to "XInputize" our program.</P
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="SEC-FURTHERSOPHISTICATIONS"
>Further sophistications</A
></H2
><P
>Although our program now supports XInput quite well, it lacks some
features we would want in a full-featured application. First, the user
probably doesn't want to have to configure their device each time they
run the program, so we should allow them to save the device
configuration. This is done by iterating through the return of
<TT
CLASS="LITERAL"
>gdk_input_list_devices()</TT
> and writing out the configuration to a
file.</P
><P
>To restore the state next time the program is run, GDK provides
functions to change device configuration:</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>gdk_input_set_extension_events()
gdk_input_set_source()
gdk_input_set_mode()
gdk_input_set_axes()
gdk_input_set_key()</PRE
></TD
></TR
></TABLE
><P
>(The list returned from <TT
CLASS="LITERAL"
>gdk_input_list_devices()</TT
> should not be
modified directly.) An example of doing this can be found in the
drawing program gsumi. (Available from <A
HREF="http://www.msc.cornell.edu/~otaylor/gsumi/"
TARGET="_top"
>http://www.msc.cornell.edu/~otaylor/gsumi/</A
>) Eventually, it
would be nice to have a standard way of doing this for all
applications. This probably belongs at a slightly higher level than
GTK, perhaps in the GNOME library.</P
><P
>Another major omission that we have mentioned above is the lack of
cursor drawing. Platforms other than XFree86 currently do not allow
simultaneously using a device as both the core pointer and directly by
an application. See the <A
HREF="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
TARGET="_top"
>XInput-HOWTO</A
> for more information about this. This means that
applications that want to support the widest audience need to draw
their own cursor.</P
><P
>An application that draws its own cursor needs to do two things:
determine if the current device needs a cursor drawn or not, and
determine if the current device is in proximity. (If the current
device is a drawing tablet, it's a nice touch to make the cursor 
disappear when the stylus is lifted from the tablet. When the
device is touching the stylus, that is called "in proximity.")
The first is done by searching the device list, as we did
to find out the device name. The second is achieved by selecting
"proximity_out" events. An example of drawing one's own cursor is
found in the "testinput" program found in the GTK distribution.</P
></DIV
></DIV
><DIV
CLASS="NAVFOOTER"
><HR
ALIGN="LEFT"
WIDTH="100%"><TABLE
SUMMARY="Footer navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
><A
HREF="x2470.html"
ACCESSKEY="P"
>&#60;&#60;&#60; Previous</A
></TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="book1.html"
ACCESSKEY="H"
>Home</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
><A
HREF="c2615.html"
ACCESSKEY="N"
>Next &#62;&#62;&#62;</A
></TD
></TR
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
>The DrawingArea Widget, And Drawing</TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="c2422.html"
ACCESSKEY="U"
>Up</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
>Tips For Writing GTK Applications</TD
></TR
></TABLE
></DIV
></BODY
></HTML
>