Search code examples
pythonx11xlibxorg

selective RECORD using python xlib?


I'm trying to implement clickable areas on the root window, using python xlib's RECORD extension. this is what i've got so far:

import sys
import os

from Xlib import X, XK, display
from Xlib.ext import record
from Xlib.protocol import rq

local_dpy = display.Display()
record_dpy = display.Display()

buttons={(0,200,0,200,"echo Hep")}
def record_callback(reply):
    if reply.category != record.FromServer:
        return
    if reply.client_swapped:
        print "* received swapped protocol data, cowardly ignored"
        return
    if not len(reply.data) or ord(reply.data[0]) < 2:
        # not an event
        return

    data = reply.data
    while len(data):
        event, data = rq.EventField(None).parse_binary_value(data, record_dpy.display, None, None)

        if event.type == X.ButtonRelease:
            print "ButtonRelease", event
            if(event.detail==1):
                for btn in buttons:
                  if(event.root_x>=btn[0] and event.root_x<=btn[1]):
                      if(event.root_y>=btn[2] and event.root_y<=btn[3]):
                          os.system(btn[4])

# Check if the extension is present
if not record_dpy.has_extension("RECORD"):
    print "RECORD extension not found"
    sys.exit(1)
r = record_dpy.record_get_version(0, 0)
print "RECORD extension version %d.%d" % (r.major_version, r.minor_version)

ctx = record_dpy.record_create_context(
        0,
        [record.CurrentClients],
        [{
                'core_requests': (0, 0),
                'core_replies': (0, 0),
                'ext_requests': (0, 0, 0, 0),
                'ext_replies': (0, 0, 0, 0),
                'delivered_events': (0, 0),
                'device_events': (X.KeyPress, X.MotionNotify),
                'errors': (0, 0),
                'client_started': False,
                'client_died': False,
        }])

record_dpy.record_enable_context(ctx, record_callback)
record_dpy.record_free_context(ctx)

the problem is: I don't know if (and how) it's possible to let RECORD listen for root window events only, or to filter the received events.

example: clicking the root window:

Xlib.protocol.request.QueryExtension
Xlib.protocol.request.QueryExtension
RECORD extension version 1.13
ButtonRelease Xlib.protocol.event.ButtonRelease(event_y = 0, state = 256, type = 5, child = 0, detail = 1, window = <Xlib.display.Window 0x00000000>, same_screen = 0, time = 795133824, root_y = 76, root_x = 76, root = <Xlib.display.Window 0x00000000>, event_x = 0, sequence_number = 0)
Hep

clicking firefox's toolbar:

Xlib.protocol.request.QueryExtension
Xlib.protocol.request.QueryExtension
RECORD extension version 1.13
ButtonRelease Xlib.protocol.event.ButtonRelease(event_y = 0, state = 256, type = 5, child = 0, detail = 1, window = <Xlib.display.Window 0x00000000>, same_screen = 0, time = 795205475, root_y = 61, root_x = 92, root = <Xlib.display.Window 0x00000000>, event_x = 0, sequence_number = 0)
Hep

so i somehow have to filter the events by: checking if the event came from the root window (the events all have NULL windows, see above...), or checking if another window is above the clicked area (so I can't have clicked the root window below).

because the event doesn't provide window information, I think Ill do the second option, but i don't know how to…


Solution

  • found it out myself:

    def record_callback(reply):
        if reply.category != record.FromServer:
            return
        if reply.client_swapped:
            print "* received swapped protocol data, cowardly ignored"
            return
        if not len(reply.data) or ord(reply.data[0]) < 2:
            # not an event
            return
    
        data = reply.data
        clients=local_dpy.screen().root.query_tree().children
        while len(data):
            event, data = rq.EventField(None).parse_binary_value(data, record_dpy.display, None, None)
            if event.type == X.ButtonRelease:
                if(event.detail==1):
                    for btn in buttons:
                      if(event.root_x>=btn[0] and event.root_x<=btn[1]):
                          if(event.root_y>=btn[2] and event.root_y<=btn[3]):
                              doit=1
                              for win in clients:
                                  if win==local_dpy.screen().root.query_pointer().child:
                                      doit=2
                              if doit==1:
                                  os.system(btn[4])
    

    the idea is to check if the window under the pointer is one of the root window's clients.