tych0 / xcffib

A drop-in replacement for xpyb based on cffi
Apache License 2.0
94 stars 26 forks source link

How do I use `SendExtensionEvent`? #158

Open elParaguayo opened 12 months ago

elParaguayo commented 12 months ago

I've got an open PR in qtile (https://github.com/qtile/qtile/pull/4557) using PointerBarriers from xfixes and xinput. I'd like to write a test for this and have had limited success using FakeInput from xtest. I was hoping I could just send a synthetic event but have had no joy with that either.

I see xinput has SendExtensionEvent which sounds promising but I'm not clear how to use it. Do I need to put the event into a buffer object rather than passing the packed data? I also have no idea what classes (https://gitlab.freedesktop.org/xorg/proto/xcbproto/-/blob/master/src/xinput.xml?ref_type=heads#L2725) are meant to be.

Lastly, I'm also concerned that this won't at all as the notes on xcbproto suggest we can't send xge events which would include the BarrierHitEvent.

Any tips would be greatly appreciated.

tych0 commented 12 months ago

Yeah, I agree that it looks like BarrierHitEvent won't work since its opcode is too high. What problems were you having with SendEvent, though? The man page says it should work with extension events too.

elParaguayo commented 12 months ago

Thanks for the reply.

The problem is my handle_BarrierHit method is not called at all after sending the event. Current code is here: https://github.com/elParaguayo/qtile/blob/pointer-barrier-test/test/backend/x11/test_pointer_barriers.py

I'm not entirely sure what I should put for the mask value here (I've tried with xcffib.xinput.XIEventMask.BarrierHit and that doesn't work either).

tych0 commented 12 months ago

Hum, I'll take a little bit later today. I think you have to use xproto.EventMask.* with SendEvent, but it's not obvious to me which one of those barrier corresponds to:

https://tronche.com/gui/x/xlib/events/mask.html

Maybe try to set all the bits and see?

elParaguayo commented 12 months ago

Didn't work with all of those set either sadly.

elParaguayo commented 12 months ago

I've got an idea what the problem might be. Let me test something.

elParaguayo commented 12 months ago

Didn't work. I tried to use the GeGenericEvent (that gets hoisted to the correct event by xcffib) and send that instead but it's still not working. That's also an xge event though...

tych0 commented 12 months ago

Yeah, just reading the code it looks like it maybe won't work, in spite of what the man page says. Sorry, I didn't get a chance to look last night, perhaps later this week.

elParaguayo commented 12 months ago

No problem at all. Appreciate you taking a look. Happy to work on a fix if there is a problem within xcffib.

tych0 commented 11 months ago

Hi, just wanted to say that I have not forgotten about this :). Here is a smaller xcffib test case:

diff --git a/test/test_barrier_hit.py b/test/test_barrier_hit.py
new file mode 100644
index 0000000..718cc7a
--- /dev/null
+++ b/test/test_barrier_hit.py
@@ -0,0 +1,40 @@
+import xcffib
+import xcffib.xproto
+import xcffib.xinput
+
+from .conftest import XcffibTest
+
+class TestPythonCode:
+
+    def test_send_BarrierHitEvent(self, xcffib_test):
+        conn = xcffib_test
+        barrier = 5
+        event = xcffib.xinput.BarrierHitEvent.synthetic(
+            2,
+            xcffib.xproto.Time.CurrentTime,
+            1,
+            conn.default_screen.root,
+            conn.default_screen.root,
+            barrier,
+            0,
+            0,
+            11,
+            799 << 16,
+            100 << 16,
+            (0, 0),
+            (0, 0)
+        )
+        mask = xcffib.xproto.EventMask.NoEvent
+        xinput = xcffib_test.conn(xcffib.xinput.key)
+        print(conn.conn._event_offsets.offsets)
+        xinput.SendExtensionEventChecked(
+            conn.default_screen.root,
+            1, # device id
+            True,
+            1,
+            1,
+            [event.pack()],
+            [66+24]
+        ).check()
+        conn.conn.core.GetInputFocus().reply()
+        assert False

Which I've been using with these (manual) patches to the bindings:

--- xinput.py   2023-11-17 07:34:29.110212168 -0700
+++ xinput.py.patched   2023-11-17 07:34:38.778375457 -0700
@@ -3896,11 +3896,11 @@
         unpacker.pad(FP3232)
         self.dy = FP3232(unpacker)
         self.bufsize = unpacker.offset - base
     def pack(self):
         buf = io.BytesIO()
-        buf.write(struct.pack("=B", 25))
+        buf.write(struct.pack("=B", 66+25))
         buf.write(struct.pack("=x2xHIIIIIIIH2xii", self.deviceid, self.time, self.eventid, self.root, self.event, self.barrier, self.dtime, self.flags, self.sourceid, self.root_x, self.root_y))
         buf.write(self.dx.pack() if hasattr(self.dx, "pack") else FP3232.synthetic(*self.dx).pack())
         buf.write(self.dy.pack() if hasattr(self.dy, "pack") else FP3232.synthetic(*self.dy).pack())
         buf_len = len(buf.getvalue())
         if buf_len < 32:
@@ -4333,9 +4333,9 @@
         buf.write(xcffib.pack_list(barriers, BarrierReleasePointerInfo))
         return self.send_request(61, buf, is_checked=is_checked)
     def SendExtensionEvent(self, destination, device_id, propagate, num_classes, num_events, events, classes, is_checked=False):
         buf = io.BytesIO()
         buf.write(struct.pack("=xx2xIBBHB3x", destination, device_id, propagate, num_classes, num_events))
-        buf.write(xcffib.pack_list(events, EventForSend))
+        buf.write(xcffib.pack_list(events, BarrierHitEvent))
         buf.write(xcffib.pack_list(classes, "I"))
         return self.send_request(31, buf, is_checked=is_checked)
 xcffib._add_ext(key, xinputExtension, _events, _errors)

I've managed to crash xtrace,

[433633.963184] xtrace[249393]: segfault at c ip 00005558f33296f7 sp 00007ffd89f4d5f0 error 4 in xtrace[5558f3326000+10000]

so I guess they're not quite as good as the actual x server, which renders a LengthError. In any case, the x server really doesn't accept variable length events here, so the comment from the xml is right.

googling around a little bit, these patches do not seem like they ever landed: https://xorg-devel.x.narkive.com/OPQdsFiP/handling-genericevents-in-xsendevent#post9

so maybe it's just not possible right now? anyway, I will keep digging. maybe we need to write some xserver code to make it work.