Skip to content

python-xlib fails to parse X server Visual/Depth info on Void Linux (Python 3.11/3.13) #282

@Schaekker

Description

@Schaekker

Bug Report: python-xlib fails to retrieve standard X server Visual/Depth information

Description:
I am encountering a persistent and unusual issue where python-xlib (both 0.33 and 0.29) on Void Linux (using Python 3.13 and Python 3.11) is unable to properly retrieve and expose X server visual and depth information, leading to AttributeError on expected properties and an inability to find the default visual in screen.allowed_depths.

My X server is functioning normally and reports standard visual information via xdpyinfo.

Environment:

  • Operating System: Void Linux (x86_64)
  • Python Version (tested): Python 3.13.0) and Python 3.11.12_2
  • python-xlib Version (tested): 0.33 and 0.29
  • X Server: "Xorg"
  • Desktop Environment/Window Manager: Openbox

Steps to Reproduce:

  1. Create a Python virtual environment (tested with both Python 3.13 and Python 3.11).
  2. Install python-xlib==0.33 (then later tried 0.29) into the environment:
    pip install python-xlib==0.33 (then pip install python-xlib==0.29)
  3. Ran Python script test_xlib.py to check basic screen properties.
  4. Ran Python script center_dot.py which attempts to find and use the default visual.

Expected Behavior:
The python-xlib library should successfully access properties like screen.default_visual, screen.default_depth, root.get_geometry().visual, and visual_obj.id within screen.allowed_depths. It should be able to identify and use the default visual (0x20 / 32) and depth (24) reported by xdpyinfo.

Actual Behavior / Error Messages:


1. Output from test_xlib.py (with python-xlib 0.33):

Successfully connected to X server.
Screen width: 1920
Screen height: 1080
Root visual ID (screen.root_visual): 32
Root depth (screen.root_depth): 24
AttributeError trying to get screen properties: get_visual_from_id
This indicates a deep issue with python-xlib's interaction with your X server.
Disconnected from X server.

2. Output from center_dot.py (after attempts with getattr workaround, python-xlib 0.33, Python 3.13):
CRITICAL: Could not find a matching Visual object for ID 32 and depth 24. Exiting.

(Note: This occurred after the AttributeError: id was bypassed by getattr.)

3. Output from center_dot.py (with python-xlib 0.29, Python 3.11):

Warning: 'screen.default_visual' or 'screen.default_depth' not found directly. Falling back to root geometry.
Traceback (most recent call last):
File "/home/XXX/pyxdg_env_py311/lib/python3.11/site-packages/Xlib/protocol/rq.py", line 1294, in getattr
return self._data[attr]

KeyError: 'default_visual'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/home/XXXX/python_scripts/center_dot.py", line 34, in create_centered_dot_overlay
selected_visual = screen.default_visual
^^^^^^^^^^^^^^^^^^^^^
File "/home/XXXX/pyxdg_env_py311/lib/python3.11/site-packages/Xlib/protocol/rq.py", line 1298, in getattr
raise AttributeError(attr)
AttributeError: default_visual

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/home/XXXX/pyxdg_env_py311/lib/python3.11/site-packages/Xlib/protocol/rq.py", line 1294, in getattr
return self._data[attr]
~~~~~~~~~~^^^^^^
KeyError: 'visual'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/home/XXXX/python_scripts/center_dot.py", line 144, in <module>
create_centered_dot_overlay()
File "/home/XXXX/python_scripts/center_dot.py", line 41, in create_centered_dot_overlay
if isinstance(geometry.visual, int):
^^^^^^^^^^^^^^^
File "/home/XXXX/pyxdg_env_py311/lib/python3.11/site-packages/Xlib/protocol/rq.py", line 1298, in getattr
raise AttributeError(attr)
AttributeError: visual

**4. Output from `xdpyinfo | grep -E "visual|depths"` (showing correct X server data):**

depths (7):     24, 1, 4, 8, 15, 16, 32
number of visuals:    156
default visual id:  0x20
visual:
visual id:      0x20

**5. Python Script `test_xlib.py` (as provided earlier):**

```python
# ~/python_scripts/test_xlib.py
#!/usr/bin/env python3
import Xlib.display
import Xlib.X

try:
    display = Xlib.display.Display()
    screen = display.screen()
    root = screen.root

    print("Successfully connected to X server.")
    print(f"Screen width: {root.get_geometry().width}")
    print(f"Screen height: {root.get_geometry().height}")

    try:
        print(f"Root visual ID (screen.root_visual): {screen.root_visual}")
        print(f"Root depth (screen.root_depth): {screen.root_depth}")

        # This is the line that failed last:
        test_visual_object = screen.get_visual_from_id(screen.root_visual)
        print(f"Got visual object from ID. Its ID is: {test_visual_object.id}")

    except AttributeError as e:
        print(f"AttributeError trying to get screen properties: {e}")
        print("This indicates a deep issue with python-xlib's interaction with your X server.")
    except Exception as e:
        print(f"An unexpected error occurred while getting screen properties: {e}")

    display.close()
    print("Disconnected from X server.")

except Xlib.error.DisplayNameError:
    print("ERROR: Cannot open display. Is X server running and DISPLAY environment variable set?")
except Exception as e:
 print(f"An unexpected error occurred during Xlib initialization: {e}")

# ~/python_scripts/center_dot.py
#!/usr/bin/env python3
import Xlib.display
import Xlib.X
import Xlib.protocol.event
from Xlib.ext import xfixes
import time

def create_centered_dot_overlay():
    display = Xlib.display.Display()
    screen = display.screen()
    root = screen.root

    screen_width = root.get_geometry().width
    screen_height = root.get_geometry().height
    center_x = screen_width // 2
    center_y = screen_height // 2

    TARGET_VISUAL_ID_DEC = 32
    TARGET_DEPTH = 24

    selected_visual = None
    found_depth = None

    print(f"Searching for visual ID {TARGET_VISUAL_ID_DEC} with depth {TARGET_DEPTH}...")
    for depth_info in screen.allowed_depths:
        print(f"  Checking depth: {depth_info.depth}")
        if depth_info.depth == TARGET_DEPTH:
            for visual_obj in depth_info.visuals:
                visual_id_val = getattr(visual_obj, 'id', None)
                if visual_id_val is not None and visual_id_val == TARGET_VISUAL_ID_DEC:
                    selected_visual = visual_obj
                    found_depth = depth_info.depth
                    print(f"  --> Found matching visual: ID {selected_visual.id}, Depth {found_depth}")
                    break
            if selected_visual:
                break

    if selected_visual is None:
        print(f"CRITICAL: Could not find a matching Visual object for ID {TARGET_VISUAL_ID_DEC} and depth {TARGET_DEPTH} within screen.allowed_depths. Exiting.")
        print("This indicates a severe incompatibility or issue with your python-xlib installation or X server configuration.")
        display.close()
        exit(1)

    print(f"Using visual ID: {selected_visual.id} with depth: {found_depth}")
    print("Transparency might not work without a compositing manager (Picom/Compton) and a suitable visual.")

    window = root.create_window(
        0, 0, screen_width, screen_height, 0,
        found_depth,
        Xlib.X.InputOutput,
        selected_visual,
        Xlib.X.CWBackPixel | Xlib.X.CWEventMask | Xlib.X.CWOverrideRedirect | Xlib.X.CWColormap,
        {
            'background_pixel': 0,
            'event_mask': Xlib.X.ExposureMask,
            'override_redirect': True,
            'colormap': display.create_colormap(root, selected_visual, Xlib.X.AllocNone)
        }
    )

    NET_WM_STATE = display.intern_atom('_NET_WM_STATE')
    _NET_WM_STATE_ABOVE = display.intern_atom('_NET_WM_STATE_ABOVE')
    window.change_property(
        Xlib.X.ChangePropertyModeReplace,
        NET_WM_STATE,
        Xlib.display.None_,
        32,
        [_NET_WM_STATE_ABOVE]
    )
    event = Xlib.protocol.event.ClientMessage(
        window=window,
        client_type=NET_WM_STATE,
        data=(32, (_NET_WM_STATE_ABOVE, 1, 0, 0, 0))
    )
    root.send_event(event, Xlib.X.SubstructureNotifyMask | Xlib.X.SubstructureRedirectMask)

    empty_region = xfixes.create_region(display)
    xfixes.set_window_shape_region(display, window.id, Xlib.X.ShapeInput, 0, 0, empty_region)
    empty_region.free()

    window.map_window()
    display.sync()

    dot_color = screen.black_pixel
    gc = window.create_gc(
        foreground=dot_color,
        function=Xlib.X.GXcopy
    )

    try:
        while True:
            event = display.next_event()
            if event.type == Xlib.X.Expose:
                dot_size = 3
                window.fill_arc(gc, center_x - dot_size, center_y - dot_size,
                                dot_size * 2, dot_size * 2, 0, 360 * 64)
                display.flush()
            time.sleep(0.01)
    except KeyboardInterrupt:
        print("\nExiting dot overlay.")
    finally:
        gc.free()
        window.destroy()
        display.close()

if __name__ == "__main__":
    create_centered_dot_overlay()


xdpyinfo shows correct X server data.
attempted different python-xlib versions and Python interpreter versions without success.
issue isolated to python-xlib's internal handling of X server responses.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions