diff --git a/test/pyxtest/proto/present.py b/test/pyxtest/proto/present.py index b2e1d888a..97fba73b2 100644 --- a/test/pyxtest/proto/present.py +++ b/test/pyxtest/proto/present.py @@ -70,6 +70,17 @@ class QueryCapabilitiesRequest: ) +@dataclass +class PresentNotify: + """xPresentNotify structure (8 bytes): window(4) + serial(4).""" + + window: int + serial: int = 0 + + def to_bytes(self, byte_order: str = "<") -> bytes: + return struct.pack(f"{byte_order}II", self.window, self.serial) + + @dataclass class PixmapRequest: """PresentPixmap request. @@ -93,10 +104,19 @@ class PixmapRequest: target_msc: int = 0 divisor: int = 0 remainder: int = 0 - # notifies follow but we omit them for testing + notifies: list[PresentNotify] | None = None def to_bytes(self, byte_order: str = "<") -> bytes: - return struct.pack( + notify_data = b"" + if self.notifies: + for n in self.notifies: + notify_data += n.to_bytes(byte_order) + + base_size = 72 # 18 words + total_size = base_size + len(notify_data) + length = total_size // 4 + + header = struct.pack( f"{byte_order}BBH" # header: opcode, sub-opcode, length f"III" # window, pixmap, serial f"II" # valid, update @@ -109,7 +129,7 @@ class PixmapRequest: f"Q", # remainder (CARD64) self.opcode, PresentPixmap, - 18, # 72 bytes = 18 words + length, self.window, self.pixmap, self.serial, @@ -125,6 +145,7 @@ class PixmapRequest: self.divisor, self.remainder, ) + return header + notify_data @dataclass diff --git a/test/pyxtest/test_present.py b/test/pyxtest/test_present.py index 2c274c11f..63fb35492 100644 --- a/test/pyxtest/test_present.py +++ b/test/pyxtest/test_present.py @@ -55,3 +55,78 @@ class TestPresentSelectInput: f"PresentSelectInput returned error(s): {errors} - " "eid not swapped → BadIDChoice" ) + + +class TestPresentNotify: + """Tests for PresentPixmap notify array byte-swap fix. + + Fix: present: Fix missing byte swaps in sproc_present_pixmap() + + The xPresentNotify array following the fixed header was not + byte-swapped at all. Each entry has window (CARD32) and serial + (CARD32) fields that need swapl(). Without swapping, a + byte-swapped client's window IDs are garbled, causing + dixLookupWindow to fail with BadWindow. + + Fixed in commit 925edb6c9e ("present: Fix missing byte swaps in + sproc_present_pixmap()"). + """ + + @pytest.mark.swapped_client + def test_present_pixmap_notifies_window_swapped(self, xserver, xclient_swapped): + """ + sproc_present_pixmap was missing byte swaps for the variable-length + xPresentNotify array. + + Send a PresentPixmap request with a notify entry whose window + field is a valid window created by this client. Without the swap, + the window ID is garbled and dixLookupWindow fails with BadWindow. + With the swap, the window ID is correctly interpreted. + + Fixed in commit 925edb6c9e ("present: Fix missing byte swaps in + sproc_present_pixmap()"). + """ + conn = xclient_swapped + + ext = conn.query_extension(Extension.PRESENT) + if not ext: + pytest.skip("Present extension not available") + + req = present.QueryVersionRequest(opcode=ext.opcode) + conn.send_request(req.to_bytes(">")) + conn.recv_response(timeout=5.0) + + win = conn.create_window() + pixmap = conn.create_pixmap() + + # The notify window is the same window as the main request window. + # With the fix, the window ID in the notify is correctly swapped + # and the lookup succeeds. Without the fix, the garbled ID causes + # BadWindow. + notify = present.PresentNotify(window=win, serial=1) + + req = present.PixmapRequest( + opcode=ext.opcode, + window=win, + pixmap=pixmap, + serial=0, + notifies=[notify], + ) + conn.send_request(req.to_bytes(">")) + responses = conn.flush_responses(timeout=1.0) + + assert xserver.is_alive, "Server crashed" + + # With the fix: either success (no error for void request) or + # a non-BadWindow error (e.g. BadMatch from the present + # implementation). The key point is no BadWindow (error 3). + # Without the fix: BadWindow because the notify's window ID + # was not byte-swapped. + bad_window_errors = [ + r for r in responses if isinstance(r, X11Error) and r.error_code == 3 + ] + assert len(bad_window_errors) == 0, ( + f"PresentPixmap returned BadWindow error(s): " + f"{bad_window_errors} - notify window IDs not " + "byte-swapped in sproc_present_pixmap" + )