Wait for configured buffers before resizing

6d7f2ee7f873bb2ccfd1de6ccc2b26040662f9a5
Open an image in imv while it is tiled: the window can flicker between
two sizes and briefly expose the wallpaper. imv acknowledges a configure
before attaching the matching buffer, so swm was treating pixels from
the old buffer as the resize result. Keep the requested geometry pending
until a commit contains the configured buffer.
Alexis Sellier committed ago 1 parent c22e5b90
swm.c +7 -0
2257 2257
        if (c->decoration)
2258 2258
            request_decoration_mode(&c->set_decoration_mode, c->decoration);
2259 2259
        wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, 0, 0);
2260 2260
        return;
2261 2261
    }
2262 +
    /* A configure acknowledgement may be committed before the client attaches
2263 +
     * the matching buffer. Keep the pending layout geometry until pixels for
2264 +
     * that configure arrive; otherwise the old buffer can trigger a resize
2265 +
     * loop between its dimensions and the requested dimensions. */
2266 +
    if (c->resize &&
2267 +
        !(client_surface(c)->current.committed & WLR_SURFACE_STATE_BUFFER))
2268 +
        return;
2262 2269
    old_geom = c->geom;
2263 2270
2264 2271
    /* Floating applications may choose their own size. Applications may also
2265 2272
     * round a compositor-requested size. Match the frame to the committed
2266 2273
     * window geometry in both cases. */
test/clients.py +43 -0
199 199
        self.toplevel.destroy()
200 200
        self.xdg_surface.destroy()
201 201
        self.surface.destroy()
202 202
203 203
204 +
class DeferredConfigureWindow(Window):
205 +
    """A window which acknowledges post-map configures before drawing them."""
206 +
207 +
    def __init__(self, *args, **kwargs):
208 +
        self.mapped = False
209 +
        self.deferred = False
210 +
        super().__init__(*args, **kwargs)
211 +
212 +
    def _surface_configure(self, proxy, serial: int) -> None:
213 +
        proxy.ack_configure(serial)
214 +
        if self.mapped:
215 +
            self.surface.commit()
216 +
            self.deferred = True
217 +
        else:
218 +
            self.draw()
219 +
            self.mapped = True
220 +
221 +
222 +
def xdg_deferred_configure(arguments: list[str]) -> None:
223 +
    """Acknowledge a tiled resize before attaching its matching buffer."""
224 +
225 +
    if arguments:
226 +
        raise Failure("xdg-deferred-configure takes no arguments")
227 +
    connection = Connection(WlCompositor, WlShm, XdgWmBase)
228 +
    window = DeferredConfigureWindow(
229 +
        connection,
230 +
        "deferred-configure",
231 +
        0xFF8844AA,
232 +
        report=Path(os.environ["SWM_CLIENT_REPORT"]),
233 +
    )
234 +
    while window.running:
235 +
        connection.display.dispatch(block=True)
236 +
        if window.deferred:
237 +
            window.deferred = False
238 +
            time.sleep(0.05)
239 +
            window.draw()
240 +
            connection.display.flush()
241 +
    window.destroy()
242 +
    connection.close()
243 +
244 +
245 +
204 246
def xdg_client(arguments: list[str]) -> None:
205 247
    """Run one persistent XDG window."""
206 248
207 249
    color = int(arguments[0], 16) if arguments else 0xFF336699
208 250
    title = arguments[1] if len(arguments) > 1 else "swm-test"
1030 1072
    connection.disconnect()
1031 1073
1032 1074
1033 1075
ROLES = {
1034 1076
    "xdg": xdg_client,
1077 +
    "xdg-deferred-configure": xdg_deferred_configure,
1035 1078
    "xdg-resize": xdg_resize,
1036 1079
    "xdg-lifecycle": xdg_lifecycle,
1037 1080
    "transient": transient,
1038 1081
    "keyboard": keyboard,
1039 1082
    "pointer": pointer,
test/run.py +26 -0
371 371
        virtual_keyboard(36)
372 372
        wait_title("two" if before == "one" else "one")
373 373
374 374
    test("window publication and focus", publication)
375 375
376 +
    def deferred_configure() -> None:
377 +
        report = Path(os.environ["SWM_TEST_DIR"]) / "deferred-configure.geometry"
378 +
        env = os.environ.copy()
379 +
        env["SWM_CLIENT_REPORT"] = str(report)
380 +
        client = spawn(
381 +
            client_log("deferred-configure"),
382 +
            *client_command("xdg-deferred-configure"),
383 +
            env=env,
384 +
        )
385 +
        wait_title("deferred-configure")
386 +
        eventually(
387 +
            "deferred configure buffer",
388 +
            lambda: report.exists() and report.stat().st_size,
389 +
        )
390 +
        settled = eventually(
391 +
            "settled deferred configure geometry",
392 +
            lambda: published_geometry("deferred-configure"),
393 +
        )
394 +
        consistently(
395 +
            "stable deferred configure geometry",
396 +
            lambda: published_geometry("deferred-configure") == settled,
397 +
        )
398 +
        terminate(client)
399 +
400 +
    test("configure acknowledgement before buffer", deferred_configure)
401 +
376 402
    def client_resize() -> None:
377 403
        report = Path(os.environ["SWM_TEST_DIR"]) / "client-resize.geometry"
378 404
        trigger = Path(os.environ["SWM_TEST_DIR"]) / "client-resize.trigger"
379 405
        committed = Path(os.environ["SWM_TEST_DIR"]) / "client-resize.committed"
380 406
        env = os.environ.copy()