Windows更新导致Python Xlib屏幕绘制功能失效
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

