GTK+ 2.0 教学-添加XInput支持

现在可以买到很便宜的输入设备,如手写板,用它绘图很方便。它可以用于代替滑鼠,但这样失去了这个设备的许多优点:

  • 压力敏感度
  • 倾角报告
  • 子像素定位
  • 多输入(如铅笔和橡皮擦)

关于XInput扩展的更多讯息请参见XInput HOWTO

我们看GdkEventMotion结构的完全定义,我们会发现它包含支持扩展设备讯息的栏位。

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;
};

pressure是压力,0到1之间的浮点数。xtiltytilt可以取-1到1之间的值,对应在每个方向的倾斜度数。sourcedeviceid用不同的方法指出发生事件的设备。source给出设备的简短讯息。它可以取如下列举值:

GDK_SOURCE_MOUSE
GDK_SOURCE_PEN
GDK_SOURCE_ERASER
GDK_SOURCE_CURSOR

deviceid是设备的统一数字ID。它可用于得到设备的进一步讯息,通过呼叫函式gdk_input_list_devices()。特殊值GDK_CORE_POINTER用于主要指示装置。(通常是滑鼠)

允许扩展设备讯息

为了让 GTK 知道我们对扩展设备讯息感兴趣,我们只需添加如下一行:

gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);

给定GDK_EXTENSION_EVENTS_CURSOR值说明我们对扩展事件感兴趣,且不想绘制自己的游标。关于绘制游标内容详见进一步讲解。我们也可以给出值GDK_EXTENSION_EVENTS_ALL,如果我们想绘制自己的游标。 或给出值GDK_EXTENSION_EVENTS_NONE回复到预设条件。

然而这还没有完,预设情况下,扩展设备是不允许的。我们需要一个机制让用户去允许和配置扩展设备。下面的程序处理一个InputDialog元件。

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, &inputd);
      gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button),
				 "clicked",
				 (GtkSignalFunc)gtk_widget_hide,
				 GTK_OBJECT(inputd));
      gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button);

      gtk_widget_show (inputd);
    }
  else
    {
      if (!GTK_WIDGET_MAPPED(inputd))
	gtk_widget_show(inputd);
      else
	gdk_window_raise(inputd->window);
    }
}

InputDialog有两个按钮”关闭”和”保存”,预设它们没有被指定动作。在上面的函式,我们用”关闭”隐藏对话框,隐藏”保存”按钮,因为我们在这个程序里不用它。

使用扩展设备讯息

一旦我们允许了这个设备,我们就能在事件结构中的额外栏位使用扩展设备讯息。事实上,总是可以安全的使用这个讯息,因为这些栏位值是合法的,甚至在扩展事件不允许时。

一旦改变,我们必须呼叫函式gdk_input_window_get_pointer()代替gdk_window_get_pointer。这是必要的,因为函式gdk_window_get_pointer不返回扩展设备讯息。

void gdk_input_window_get_pointer( GdkWindow       *window,
                                   guint32         deviceid,
                                   gdouble         *x,
                                   gdouble         *y,
                                   gdouble         *pressure,
                                   gdouble         *xtilt,
                                   gdouble         *ytilt,
                                   GdkModifierType *mask);

当我们呼叫这个函式时,我们需要指定设备ID和视窗。通常,我们会从一个事件结构的deviceid栏位得到设备ID。当扩展事件不允许时,这个函式也会传回合法的值。(这样event->deviceidGDK_CORE_POINTER)。

因此我们的按钮按下和滑鼠移动事件处理函式的基本结构不需要改变,我们只需要添加处理扩展讯息的代码。

static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
  print_button_press (event->deviceid);

  if (event->button == 1 && pixmap != NULL)
    draw_brush (widget, event->source, event->x, event->y, event->pressure);

  return TRUE;
}

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

  if (event->is_hint)
    gdk_input_window_get_pointer (event->window, event->deviceid,
				  &x, &y, &pressure, NULL, NULL, &state);
  else
    {
      x = event->x;
      y = event->y;
      pressure = event->pressure;
      state = event->state;
    }

  if (state & GDK_BUTTON1_MASK && pixmap != NULL)
    draw_brush (widget, event->source, x, y, pressure);

  return TRUE;
}

我们也需要对新的讯息做些事。我们的新的draw_brush()函式根据每一个event->source绘制不同的颜色,依据压力改变画刷的大小。

/* 在荧幕上画一个矩形,大小依据压力,颜色依据设备的类型 */
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->style->dark_gc[GTK_WIDGET_STATE (widget)];
      break;
    case GDK_SOURCE_PEN:
      gc = widget->style->black_gc;
      break;
    case GDK_SOURCE_ERASER:
      gc = widget->style->white_gc;
      break;
    default:
      gc = widget->style->light_gc[GTK_WIDGET_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, &update_rect);
}

得到更多关于设备的讯息

作为一个如何得到更多关于设备的讯息的范例,我们的程式在每次按钮按下时列印设备名称。用如下函式可以得到设备名称:

GList *gdk_input_list_devices               (void);

传回值是一个GdkDeviceInfo结构的GList(GLib函式库的一个链结串列型别)GdkDeviceInfo结构定义如下:

struct _GdkDeviceInfo
{
  guint32 deviceid;
  gchar *name;
  GdkInputSource source;
  GdkInputMode mode;
  gint has_cursor;
  gint num_axes;
  GdkAxisUse *axes;
  gint num_keys;
  GdkDeviceKey *keys;
};

这些栏位的大部分都是可以忽略的配置讯息,除非你要实现XInput配置保存。我们感兴趣的栏位是name,它是X分配给设备的名字。其它的不是配置讯息的栏位是has_cursor。如果has_cursor是 FALSE,我们需要自己绘制游标。但因为我们已经指定了GDK_EXTENSION_EVENTS_CURSOR,所以我们不必关心这个。

函式print_button_press()简单的重复,直到找到匹配,然后列印出设备名称。

static void
print_button_press (guint32 deviceid)
{
  GList *tmp_list;

  /* gdk_input_list_devices传回一个内部列表,因此我们后面不必释放它。*/
  tmp_list = gdk_input_list_devices();

  while (tmp_list)
    {
      GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;

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

      tmp_list = tmp_list->next;
    }
}

我们的程式已经完全添加了对XInput设备的支持。

进一步的讲解

虽然我们的程式已经很好的支持了XInput,但是它缺乏一些特性,我们想让它成为一个全功能的程式。首先,用户不想每次在程式运行时配置设备,因此我们应该允许用户保存设备配置。这是通过获取gdk_input_list_devices()的传回值,并把配置写入一个档案。

为了程式下次执时恢复状态,GDK 提供修改设备配置的函式:

gdk_input_set_extension_events()
gdk_input_set_source()
gdk_input_set_mode()
gdk_input_set_axes()
gdk_input_set_key()

(gdk_input_list_devices()传回的列表不能直接修改。)在绘图程式gsumi中可以发现它的用法。(http://www.msc.cornell.edu/~otaylor/gsumi/)其实做这个,最好使用所有应用程式标准的方法。这也许属于比 GTK 稍进阶的函式库,也许在 GNOME 函式库中。

另一个缺点是我们上面提到的,缺乏游标绘制。当前平台 XFree86 不允许同时用一个设备和主指示装置在一个应用程式中。详见XInput-HOWTO。更好的应用程式应该绘制自己的游标。

一个程式要绘制自己的游标,需要两方面:确定当前设备是否需要绘制游标,确定当前设备是否”in proximity”。(如果当前设备是手写板,最好在笔尖离开平板时不显示游标。当设备是触摸板时,那叫做”in proximity”。)首先要搜索设备列表,寻找设备名称。其次是选择 “proximity_out” 事件。它的用法见 GTK 发布中的范例程式”testinput”.

Comments are closed.