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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions