Handling both GestureStylus and GestureDrag for stylus

Just started with Gtk. I took the the Drawing in Response to Input example from the getting started tutorial and tried to extend that to also handle input from a stylus. I have this working including reading the pen pressure. However, I run into the issue that I don’t see an easy way to handle both a mouse and a stylus.

The stylus generates both events for the GestureStylus and the GestureDrag. The mouse only generates events for the GestureDrag. So I need the GestureDrag to only handle events when there a no events from the GestureStylus; (I don’t want to handle the same event twice). I can think of ways to do this, for example using the device id (a device that generates event on the GestureStylus can be ignored by the GestureDrag), but I can imagine that this is a common issue and that there are more clean ways of handling this.

I did look at overview of GTK input handling, but that didn’t really seem to help. They do mention ‘grouped gestures’ which might be relevant, but for me it is unclear how that works.

How can I ignore the GestureDrag events for a stylus and not for a mouse?

It seems you should be able to use Gdk.Event.get_pointer_emulated but that only seems to return true for the fake events from touch devices, not from a pen device. Not sure if that would be considered a bug.

The other thing you can do is check if the event’s device appears in the seat’s list of devices, emulated devices won’t appear there.

Thanks. Either I am doing something wrong (probably) or it isn’t working or both.

Using the example I linked to above as a starting point, I rewrote the signal handlers for the Drag Gesture as

static void drag_begin (GtkGestureDrag *gesture, double x, double y,
    GtkWidget *area) {
  std::cout << "drag_begin\n";
}

static void drag_update (GtkGestureDrag *gesture, double x, double y,
    GtkWidget *area) {
  std::cout << "drag_update\n";
  GdkDevice* device = gtk_gesture_get_device(GTK_GESTURE(gesture));
  std::cout << "  device = " << device << "\n";
  // Source should distuingish between mouse and pen
  std::cout << "  device_source = " << gdk_device_get_source(device) << "\n";

  GdkEventSequence* sequence = gtk_gesture_get_last_updated_sequence (GTK_GESTURE(gesture));
  GdkEvent* event =  gtk_gesture_get_last_event(GTK_GESTURE(gesture), sequence);
  std::cout << "  emulated = " << gdk_event_get_pointer_emulated(event) << "\n";
}

static void drag_end (GtkGestureDrag *gesture, double x, double y,
    GtkWidget *area) {
  std::cout << "drag_end\n";
}

And those for the Stylus Gesture as:

static void stylus_down(GtkGestureStylus* gesture, double x, double y, GtkWidget* area) {
  std::cout << "stylus_down\n";
}

static void stylus_motion(GtkGestureStylus* gesture, double x, double y, GtkWidget* area) {
  std::cout << "stylus_motion\n";
  GdkDevice* device = gtk_gesture_get_device(GTK_GESTURE(gesture));
  std::cout << "  device = " << device << "\n";
  std::cout << "  device_source = " << gdk_device_get_source(device) << "\n";

  GdkEventSequence* sequence = gtk_gesture_get_last_updated_sequence (GTK_GESTURE(gesture));
  GdkEvent* event =  gtk_gesture_get_last_event(GTK_GESTURE(gesture), sequence);
  std::cout << "  emulated = " << gdk_event_get_pointer_emulated(event) << "\n";
}

static void stylus_up(GtkGestureStylus* gesture, double x, double y, GtkWidget* area) {
  std::cout << "stylus_up\n";
}

For a mouse and touchpad this gives me the following output:

drag_begin
drag_update
  device = 0x55811fbb1a00
  device_source = 0
  emulated = 0
...
drag_end

For a drawing tablet:

drag_begin
stylus_down
drag_update
  device = 0x55811fbb1a00
  device_source = 0
  emulated = 0
stylus_motion
  device = 0x55811fbb1a00
  device_source = 0
  emulated = 0
...
drag_end
stylus_up

What I don’t understand is that the address of the device is exactly the same for each device (mouse, touchpad, pen). And perhaps because of that the source and pointer_emulated are also exactly the same. Am I calling these functions the wrong way? Do I misunderstand what these functions are supposed to do?

Hmm now that I look, what I said before about the devices only seems to apply on Wayland and macOS. With those you can see it as a fake device using a name similar to “Logical pointer for [device]”. No idea how to detect that on X11 or Windows, from your example it looks like those use the same device.

Ah, yes I forgot to mention the system I am on. That is indeed X11. I will try on wayland tomorrow to see if that makes a difference. Not that I wouldn’t want to be able to do this also in X11.

You want to use GestureStylus for drawing, but as that only works for pens you also need a GestureDrag, which supports also the mouse.

Ignoring devices other than GDK_SOURCE_MOUSE / GDK_SOURCE_TOUCHSCREEN from your GestureDrag signal handlers should just work. Another (equivalent) way is to check for NULL GdkDeviceTool, see gdk_device_get_device_tool.

Anyway I think that you can get away by denying the GestureDrag as soon as the GestureStylus is claimed:

static void
first_gesture_begin_cb (GtkGesture       *first_gesture,
                        GdkEventSequence *sequence,
                        gpointer          user_data)
{
  gtk_gesture_set_sequence_state (first_gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
  gtk_gesture_set_sequence_state (second_gesture, sequence, GTK_EVENT_SEQUENCE_DENIED);
}

static void
second_gesture_begin_cb (GtkGesture       *second_gesture,
                         GdkEventSequence *sequence,
                         gpointer          user_data)
{
  if (gtk_gesture_get_sequence_state (first_gesture, sequence) == GTK_EVENT_SEQUENCE_CLAIMED)
    gtk_gesture_set_sequence_state (second_gesture, sequence, GTK_EVENT_SEQUENCE_DENIED);
}

See gtk_gesture_set_sequence_state

For reference: HowDoI/Gestures - GNOME Wiki!

Thanks for all of the replies. Took a while to puzzle it all together, but it seems to be working now:

The handlers for the drag events now look something like:

static void drag_begin(GtkGestureDrag *gesture, double x, double y,
    GtkWidget *area) {
  // First check if the event sequence is already handled by the stylus gesture; if not
  // we will handle is here; if it is deny it
  GdkEventSequence* sequence = gtk_gesture_get_last_updated_sequence(GTK_GESTURE(gesture));
  if (gtk_gesture_get_sequence_state(GTK_GESTURE(stylus), sequence) == GTK_EVENT_SEQUENCE_CLAIMED) {
    gtk_gesture_set_sequence_state(GTK_GESTURE(gesture), sequence, GTK_EVENT_SEQUENCE_DENIED);
  } else {
    // handle event
  }
}

Those of the stylus as:

static void stylus_down(GtkGestureStylus* gesture, double x, double y, GtkWidget* area) {
  double pressure;
  // handle event
  // Claim the event sequence; the drag gesture should not handle it
  GdkEventSequence* sequence = gtk_gesture_get_last_updated_sequence(GTK_GESTURE(gesture));
  gtk_gesture_set_sequence_state(GTK_GESTURE(gesture), sequence, GTK_EVENT_SEQUENCE_CLAIMED);
}

I also had to add the stylus gesture after adding the drag gesture as event controller to the drawing area. Am I right in observing the the lastly added event controller is called first?

The full example can be found as a gist here: gtk4_custom_draw_example_with_stylus.cpp · GitHub . Please let me know if you see me doing stupid things.

Thanks again for the help.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.