You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows更新导致Python Xlib屏幕绘制功能失效

Fixing Rectangle Drawing Failure When Underlying Windows Update in Your Python Xlib Screenshot Tool

Hey there! I’ve dealt with this exact Xlib quirk before—let’s break down why your selection rectangle disappears when windows below it update, and how to fix it.

The Root Cause: X11’s "No Retained Mode" Behavior

X11 doesn’t automatically save the graphics you draw on windows (like the root window). When a window below your selection updates, it redraws its portion of the screen, overwriting your rectangle. Since your code isn’t listening for screen redraw events, the rectangle never comes back.

Solutions to Try (From Quick Fix to Robust Overhaul)

1. Listen for Expose Events to Trigger Redraws

The simplest fix is to add code that catches Expose events (sent when a screen region is uncovered/needs redrawing) and re-renders your rectangle. Here’s how to tweak your event loop:

# Inside your event handling loop
while True:
    event = disp.next_event()
    if event.type == X.Expose:
        # Re-draw your selection rectangle if it was active
        if is_dragging:
            draw_rectangle(start_x, start_y, end_x, end_y)
    # ... handle mouse press/release/motion events as before

This works, but it’s a band-aid—if multiple windows update quickly, you might still see flickering.

2. Use an Overlay Window (The Reliable Fix)

The best approach is to draw your selection on a dedicated overlay window that sits on top of all other windows. This window is independent of underlying windows’ redraw cycles, so updates below won’t affect your rectangle.

Here’s a stripped-down example of how to implement this:

from Xlib import X, display
from PIL import Image

class SelectionTool:
    def __init__(self):
        self.disp = display.Display()
        self.screen = self.disp.screen()
        self.root = self.screen.root

        # Create a full-screen overlay window
        self.overlay = self.root.create_window(
            0, 0, self.screen.width_in_pixels, self.screen.height_in_pixels,
            0, self.screen.root_depth, X.InputOutput, X.CopyFromParent,
            event_mask=X.ExposureMask | X.ButtonPressMask | X.ButtonReleaseMask | X.PointerMotionMask,
            override_redirect=True  # Bypass window manager, stay on top
        )
        self.overlay.map()

        # Setup graphics context for drawing
        self.gc = self.overlay.create_gc(
            foreground=self.screen.white_pixel,
            line_width=2,
            line_style=X.LineSolid
        )

        self.start_pos = (0, 0)
        self.end_pos = (0, 0)
        self.dragging = False

    def draw_selection(self):
        # Clear previous rectangle first
        bg_gc = self.overlay.create_gc(foreground=self.screen.black_pixel)
        x1, y1 = self.start_pos
        x2, y2 = self.end_pos
        self.overlay.rectangle(bg_gc, min(x1,x2), min(y1,y2), abs(x2-x1), abs(y2-y1))
        bg_gc.free()

        # Draw new selection rectangle
        if self.dragging:
            self.overlay.rectangle(self.gc, min(x1,x2), min(y1,y2), abs(x2-x1), abs(y2-y1))
        self.disp.flush()

    def run(self):
        while True:
            event = self.disp.next_event()
            if event.type == X.Expose:
                self.draw_selection()
            elif event.type == X.ButtonPress and event.detail == 1:
                self.start_pos = (event.root_x, event.root_y)
                self.dragging = True
            elif event.type == X.ButtonRelease and event.detail == 1:
                self.dragging = False
                self.capture_region()
            elif event.type == X.MotionNotify and self.dragging:
                self.end_pos = (event.root_x, event.root_y)
                self.draw_selection()

    def capture_region(self):
        x1, y1 = self.start_pos
        x2, y2 = self.end_pos
        x = min(x1, x2)
        y = min(y1, y2)
        width = abs(x2 - x1)
        height = abs(y2 - y1)

        # Capture the region using Xlib
        image = self.root.get_image(x, y, width, height, X.ZPixmap, 0xffffffff)
        img = Image.frombytes("RGB", (width, height), image.data, "raw", "BGRX")
        img.save("screenshot.png")

if __name__ == "__main__":
    tool = SelectionTool()
    tool.run()

3. Key Improvements in This Code

  • Override Redirect: Ensures the overlay window stays on top and isn’t managed by your window manager.
  • Independent Drawing Context: The overlay’s graphics are separate from the root window, so underlying updates don’t erase your selection.
  • Expose Event Handling: Still handles redraws in case the overlay itself is temporarily covered (like by a popup).

Quick Debug Tip

Use the xev command in your terminal to monitor X11 events. This will help you confirm if Expose events are being sent when windows update, and verify your code is catching them correctly.

内容的提问来源于stack exchange,提问作者Ajubs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:08:17