finefoot
finefoot

Reputation: 11302

Find out mouse button state via Xlib in Python

I can determine the current mouse pointer position with:

from Xlib.display import Display
display = Display()
qp = display.screen().root.query_pointer()
print(qp.root_x, qp.root_y)

How do I get the current mouse button states like left/right button pressed/released via Xlib, too? (Or if this is not possible - why not?)

Upvotes: 3

Views: 1289

Answers (2)

user3180528
user3180528

Reputation: 51

If the X server has the RECORD extension enabled, it can be used to find out the current mouse button state. However, it won't provide the information directly: The individual button up and down events have to be tracked.

This also means that the current state will only be known, once the program has received at least one first event on mouse button up or down.

python-xlib provides the following example script at https://github.com/python-xlib/python-xlib/blob/master/examples/record_demo.py

from __future__ import print_function

import sys
import os

# Change path so we find Xlib
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

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

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


def lookup_keysym(keysym):
    for name in dir(XK):
        if name[:3] == "XK_" and getattr(XK, name) == keysym:
            return name[3:]
    return "[%d]" % keysym


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 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 in [X.KeyPress, X.KeyRelease]:
            pr = event.type == X.KeyPress and "Press" or "Release"

            keysym = local_dpy.keycode_to_keysym(event.detail, 0)
            if not keysym:
                print("KeyCode%s" % pr, event.detail)
            else:
                print("KeyStr%s" % pr, lookup_keysym(keysym))

            if event.type == X.KeyPress and keysym == XK.XK_Escape:
                local_dpy.record_disable_context(ctx)
                local_dpy.flush()
                return
        elif event.type == X.ButtonPress:
            print("ButtonPress", event.detail)
        elif event.type == X.ButtonRelease:
            print("ButtonRelease", event.detail)
        elif event.type == X.MotionNotify:
            print("MotionNotify", event.root_x, event.root_y)


# 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))

# Create a recording context; we only want key and mouse events
ctx = record_dpy.record_create_context(
    0,
    [record.AllClients],
    [{
        '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,
    }])

# Enable the context; this only returns after a call to record_disable_context,
# while calling the callback function in the meantime
record_dpy.record_enable_context(ctx, record_callback)

# Finally free the context
record_dpy.record_free_context(ctx)

Upvotes: 1

Naoyuki Tai
Naoyuki Tai

Reputation: 443

Your X window must support XInput extension. Real X works but getting to mouse button doesn't work if the X server doesn't support the extension like VNC server.

If the X server supports it, then you can get to the mouse state as follows:

from Xlib.display import Display
from Xlib.ext import xinput

display = Display()

import time

while True:
    buttons = []

    for device_info in display.xinput_query_device(xinput.AllDevices).devices:
        if not device_info.enabled:
            continue
        if xinput.ButtonClass not in [ device_class.type for device_class in device_info.classes ]:
            continue
        buttons.append(device_info)

    for button in buttons:
        for device_class in button.classes:
            if xinput.ButtonClass == device_class.type:
                if device_class.state[0]:
                    print('Device {name} - Primary button down'.format(name=button.name))
    time.sleep(1)

I'm not 100% sure as the docs are not found anywhere, but I'm pretty sure device_class.state[0] is primary (left button), 1 is middle, and 2 is right button.

You can probably find out the button number assignment spec here

EDIT:

Why there are two for loops - First I wrote the "buttons" part outside of forever loop. But, "hey, you can plug in mouse any time."

You'll find that, there are many devices including "virtual" ones. On laptop, touchpad does work as button too so in your app, if you want to know the real mouse's buttons, you may have to pick a device from name. Again, there is no good docs so you probably have to decipher the device class object. You can find the xinput as /usr/lib/python3/dist-packages/Xlib/ext/xinput.py. (Adjust it accordingly if you are using Python2.) Good luck.

Upvotes: 1

Related Questions