/* Event Axes * * Demonstrates advanced handling of event information from exotic * input devices. * * On one hand, this snippet demonstrates management of input axes, * those contain additional information for the pointer other than * X/Y coordinates. * * Input axes are dependent on hardware devices, on linux/unix you * can see the device axes through xinput list . Each time * a different hardware device is used to move the pointer, the * master device will be updated to match the axes it provides, * these changes can be tracked through GdkDevice::changed, or * checking gdk_event_get_source_device(). * * On the other hand, this demo handles basic multitouch events, * each event coming from an specific touchpoint will contain a * GdkEventSequence that's unique for its lifetime, so multiple * touchpoints can be tracked. */ #include typedef struct { GdkDevice *last_source; GHashTable *axes; /* axis label atom -> value */ GdkRGBA color; gdouble x; gdouble y; } AxesInfo; typedef struct { AxesInfo *pointer_info; GHashTable *touch_info; /* GdkEventSequence -> AxesInfo */ } EventData; const gchar *colors[] = { "black", "orchid", "fuchsia", "indigo", "thistle", "sienna", "azure", "plum", "lime", "navy", "maroon", "burlywood" }; static guint cur_color = 0; static AxesInfo * axes_info_new (void) { AxesInfo *info; info = g_new0 (AxesInfo, 1); gdk_rgba_parse (&info->color, colors[cur_color]); info->axes = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_free); cur_color = (cur_color + 1) % G_N_ELEMENTS (colors); return info; } static void axes_info_free (AxesInfo *info) { g_hash_table_destroy (info->axes); g_free (info); } static gboolean axes_info_lookup (AxesInfo *info, const gchar *axis_label, gdouble *value) { gdouble *val; GdkAtom atom; atom = gdk_atom_intern (axis_label, FALSE); if (atom == GDK_NONE) return FALSE; val = g_hash_table_lookup (info->axes, GDK_ATOM_TO_POINTER (atom)); if (!val) return FALSE; *value = *val; return TRUE; } static EventData * event_data_new (void) { EventData *data; data = g_new0 (EventData, 1); data->touch_info = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) axes_info_free); return data; } static void event_data_free (EventData *data) { if (data->pointer_info) axes_info_free (data->pointer_info); g_hash_table_destroy (data->touch_info); g_free (data); } static void update_axes_from_event (GdkEvent *event, EventData *data) { GdkDevice *device, *source_device; GdkEventSequence *sequence; gdouble x, y, value; GList *l, *axes; AxesInfo *info; device = gdk_event_get_device (event); source_device = gdk_event_get_source_device (event); sequence = gdk_event_get_event_sequence (event); if (event->type == GDK_TOUCH_END || event->type == GDK_TOUCH_CANCEL) { g_hash_table_remove (data->touch_info, sequence); return; } else if (event->type == GDK_LEAVE_NOTIFY) { if (data->pointer_info) axes_info_free (data->pointer_info); data->pointer_info = NULL; return; } if (!sequence) { if (!data->pointer_info) data->pointer_info = axes_info_new (); info = data->pointer_info; } else { info = g_hash_table_lookup (data->touch_info, sequence); if (!info) { info = axes_info_new (); g_hash_table_insert (data->touch_info, sequence, info); } } if (info->last_source != source_device) { g_hash_table_remove_all (info->axes); info->last_source = source_device; } if (event->type == GDK_TOUCH_BEGIN || event->type == GDK_TOUCH_UPDATE || event->type == GDK_MOTION_NOTIFY || event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) { axes = gdk_device_list_axes (device); if (sequence && event->touch.emulating_pointer) { if (data->pointer_info) axes_info_free (data->pointer_info); data->pointer_info = NULL; } for (l = axes; l; l = l->next) { gdouble *ptr; /* All those event types are compatible wrt axes position in the struct */ if (!gdk_device_get_axis_value (device, event->motion.axes, l->data, &value)) continue; ptr = g_new0 (gdouble, 1); *ptr = value; g_hash_table_insert (info->axes, GDK_ATOM_TO_POINTER (l->data), ptr); } g_list_free (axes); } if (gdk_event_get_coords (event, &x, &y)) { info->x = x; info->y = y; } } static gboolean event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { update_axes_from_event (event, user_data); gtk_widget_queue_draw (widget); return FALSE; } static void render_arrow (cairo_t *cr, gdouble x_diff, gdouble y_diff, const gchar *label) { cairo_save (cr); cairo_set_source_rgb (cr, 0, 0, 0); cairo_new_path (cr); cairo_move_to (cr, 0, 0); cairo_line_to (cr, x_diff, y_diff); cairo_stroke (cr); cairo_move_to (cr, x_diff, y_diff); cairo_show_text (cr, label); cairo_restore (cr); } static void draw_axes_info (cairo_t *cr, AxesInfo *info, GtkAllocation *allocation) { gdouble pressure, tilt_x, tilt_y, wheel; cairo_save (cr); cairo_set_line_width (cr, 1); gdk_cairo_set_source_rgba (cr, &info->color); cairo_move_to (cr, 0, info->y); cairo_line_to (cr, allocation->width, info->y); cairo_move_to (cr, info->x, 0); cairo_line_to (cr, info->x, allocation->height); cairo_stroke (cr); cairo_translate (cr, info->x, info->y); if (axes_info_lookup (info, "Abs Pressure", &pressure)) { cairo_pattern_t *pattern; pattern = cairo_pattern_create_radial (0, 0, 0, 0, 0, 100); cairo_pattern_add_color_stop_rgba (pattern, pressure, 1, 0, 0, pressure); cairo_pattern_add_color_stop_rgba (pattern, 1, 0, 0, 1, 0); cairo_set_source (cr, pattern); cairo_arc (cr, 0, 0, 100, 0, 2 * G_PI); cairo_fill (cr); cairo_pattern_destroy (pattern); } if (axes_info_lookup (info, "Abs Tilt X", &tilt_x) && axes_info_lookup (info, "Abs Tilt Y", &tilt_y)) render_arrow (cr, tilt_x * 100, tilt_y * 100, "Tilt"); if (axes_info_lookup (info, "Abs Wheel", &wheel)) { cairo_save (cr); cairo_set_line_width (cr, 10); cairo_set_source_rgba (cr, 0, 0, 0, 0.5); cairo_new_sub_path (cr); cairo_arc (cr, 0, 0, 100, 0, wheel * 2 * G_PI); cairo_stroke (cr); cairo_restore (cr); } cairo_restore (cr); } static void draw_device_info (GtkWidget *widget, cairo_t *cr, GdkEventSequence *sequence, gint *y, AxesInfo *info) { PangoLayout *layout; GString *string; gint height; cairo_save (cr); string = g_string_new (NULL); g_string_append_printf (string, "Source: %s", gdk_device_get_name (info->last_source)); if (sequence) g_string_append_printf (string, "\nSequence: %d", GPOINTER_TO_UINT (sequence)); cairo_move_to (cr, 10, *y); layout = gtk_widget_create_pango_layout (widget, string->str); pango_cairo_show_layout (cr, layout); cairo_stroke (cr); pango_layout_get_pixel_size (layout, NULL, &height); gdk_cairo_set_source_rgba (cr, &info->color); cairo_set_line_width (cr, 10); cairo_move_to (cr, 0, *y); *y = *y + height; cairo_line_to (cr, 0, *y); cairo_stroke (cr); cairo_restore (cr); g_object_unref (layout); g_string_free (string, TRUE); } static gboolean draw_cb (GtkWidget *widget, cairo_t *cr, gpointer user_data) { EventData *data = user_data; GtkAllocation allocation; AxesInfo *touch_info; GHashTableIter iter; gpointer key, value; gint y = 0; gtk_widget_get_allocation (widget, &allocation); /* Draw Abs info */ if (data->pointer_info) draw_axes_info (cr, data->pointer_info, &allocation); g_hash_table_iter_init (&iter, data->touch_info); while (g_hash_table_iter_next (&iter, NULL, &value)) { touch_info = value; draw_axes_info (cr, touch_info, &allocation); } /* Draw name, color legend and misc data */ if (data->pointer_info) draw_device_info (widget, cr, NULL, &y, data->pointer_info); g_hash_table_iter_init (&iter, data->touch_info); while (g_hash_table_iter_next (&iter, &key, &value)) { touch_info = value; draw_device_info (widget, cr, key, &y, touch_info); } return FALSE; } GtkWidget * do_event_axes (GtkWidget *toplevel) { static GtkWidget *window = NULL; EventData *event_data; GtkWidget *box; if (!window) { window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Event Axes"); gtk_window_set_default_size (GTK_WINDOW (window), 400, 400); g_signal_connect (window, "destroy", G_CALLBACK (gtk_widget_destroyed), &window); box = gtk_event_box_new (); gtk_container_add (GTK_CONTAINER (window), box); gtk_widget_add_events (box, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_TOUCH_MASK); event_data = event_data_new (); g_object_set_data_full (G_OBJECT (box), "gtk-demo-event-data", event_data, (GDestroyNotify) event_data_free); g_signal_connect (box, "event", G_CALLBACK (event_cb), event_data); g_signal_connect (box, "draw", G_CALLBACK (draw_cb), event_data); } if (!gtk_widget_get_visible (window)) gtk_widget_show_all (window); else gtk_widget_destroy (window); return window; }