Why is Atspi.DeviceEvent data the same after every keystroke event?

Viewed 78

I'm really struggling to get AT-SPI to work within a Vala application.

I have it able to notice that a key has been pressed via Atspi.register_keystroke_listener, but can't for the life of me get it to pass anything useful to the callback function. On each key press it returns exactly the same data regardless of the key pressed, and the stroke.event_string never seems to have anything in it.

The following is a stripped down demo application that shows the problem.

public class Demo.Application : Gtk.Application {
    private static Application? _app = null;

    private Atspi.DeviceListenerCB listener_cb;
    private Atspi.DeviceListener listener;

    public Application () {
        Object (
            application_id: "com.bytepixie.snippetpixie",
            flags: ApplicationFlags.HANDLES_COMMAND_LINE
        );
    }

    protected override void activate () {
        message ("Activated");

        Atspi.init();

        listener_cb = (Atspi.DeviceListenerCB) on_key_released_event;
        listener = new Atspi.DeviceListener ((owned) listener_cb);

        try {
            Atspi.register_keystroke_listener (listener, null, 0, Atspi.EventType.KEY_RELEASED_EVENT, Atspi.KeyListenerSyncType.ALL_WINDOWS | Atspi.KeyListenerSyncType.CANCONSUME);
        } catch (Error e) {
            message ("Could not keystroke listener: %s", e.message);
            Atspi.exit ();
            quit ();
        }
    }

    private bool on_key_released_event (Atspi.DeviceEvent stroke) {
        message ("id: %u, hw_code: %d, modifiers: %d, timestamp: %u, event_string: %s, is_text: %s",
            stroke.id,
            stroke.hw_code,
            stroke.modifiers,
            stroke.timestamp,
            stroke.event_string,
            stroke.is_text.to_string ()
        );

        return false;
    }

    public override int command_line (ApplicationCommandLine command_line) {
        hold ();
        activate ();
        return 0;
    }

    public static new Application get_default () {
        if (_app == null) {
            _app = new Application ();
        }
        return _app;
    }

    public static int main (string[] args) {
        var app = get_default ();
        return app.run (args);
    }
}

When compiled and run, and then the keys "qwerty" are pressed, I get the following.

ian@ians-apollo:~/Documents/atspi-test$ valac demo.vala --pkg gtk+-3.0 --pkg atspi-2
ian@ians-apollo:~/Documents/atspi-test$ ./demo 
** Message: 18:35:59.373: demo.vala:15: Activated

(demo:18257): GLib-GObject-CRITICAL **: 18:35:59.456: g_object_unref: assertion 'G_IS_OBJECT (object)' failed
** Message: 18:36:00.716: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
q** Message: 18:36:01.046: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
w** Message: 18:36:01.477: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
e** Message: 18:36:01.837: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
r** Message: 18:36:02.187: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
t** Message: 18:36:02.583: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
y** Message: 18:36:10.587: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true

You can see the "qwerty" at the beginning of each line in the console as I'm not consuming the key strokes, but there's no difference in the data output each time.

What am I missing? Is there some sort of caching going on that needs to be cleared after each event?

1 Answers

It took a while to figure this one out and the demo was very helpful. In essence the C function signature for the callback is the wrong way around.

Reading the C documentation for AtspiDeviceListenerCB the function signature should be:

gboolean
(*AtspiDeviceListenerCB) (const AtspiDeviceEvent *stroke,
                          void *user_data);

The user_data is after stroke.

In the example Vala program, on_key_released_event is a method of Demo.Application. Vala will place the instance reference as the first parameter of the method in the generated C. Using the --ccode switch with valac shows the following in the generated C:

static gboolean demo_application_on_key_released_event (DemoApplication* self,
                                                 AtspiDeviceEvent* stroke);

The solution is to tell the Vala compiler to place the instance reference at a different position. In the example program this means changing:

private bool on_key_released_event (Atspi.DeviceEvent stroke) {

to

[CCode (instance_pos = -1)]
private bool on_key_released_event (Atspi.DeviceEvent stroke) {

The CCode attribute detail instance_pos could be another value, but -1 places the instance parameter as the last parameter in the function signature. We could have used 2 instead. For more on changing the position of generated C function arguments see the Vala Writing Bindings Manually document.

Another solution would have been not to use the instance data at all and use DeviceListener.simple instead.

It would be nice to think there is enough information available to the Vala compiler to work out that an object's method that is used as a callback should have the instance parameter in a different position in the generated C. I've not taken the time to investigate that possibility though.

Related