Handle fullscreen requests before map

517b92d49563b7ab4d1d51f771c2425cb3119609
Alexis Sellier committed ago 1 parent 634b9946
swm.c +13 -4
4258 4258
        c->bw             = client_border_width(c);
4259 4259
        c->geom.width    += 2 * ((int)c->bw - (int)oldbw);
4260 4260
        c->geom.height   += 2 * ((int)c->bw - (int)oldbw);
4261 4261
        restore_client(c);
4262 4262
    }
4263 +
    /* A fullscreen request can precede the first mapped buffer. Its state is
4264 +
     * already configured; place and size the new scene without scheduling a
4265 +
     * redundant fullscreen-state configure from the map callback. */
4266 +
    if (c->is_fullscreen) {
4267 +
        c->prev = c->geom;
4268 +
        wlr_scene_node_reparent(&c->scene->node, layers[LAYER_FULLSCREEN]);
4269 +
        resize(c, c->mon->m, 0);
4270 +
    }
4263 4271
    /* Publish the window to taskbars and switchers. */
4264 4272
    c->ftl = wlr_foreign_toplevel_handle_v1_create(ftl_mgr);
4265 4273
4266 4274
    if (c->ftl) {
4267 4275
        LISTEN(&c->ftl->events.request_activate, &c->ftl_activate, ftl_activate_notify);
4276 4284
        }
4277 4285
    );
4278 4286
    if (c->extftl)
4279 4287
        c->extftl->data = c;
4280 4288
    ftl_sync(c);
4281 -
    /* A managed window can receive keyboard focus only after it is visible. */
4282 -
    if (c->ws && c->ws->mon)
4283 -
        focus_client(c, 1);
4289 +
    /* Publish the mapped scene before activating it. This mirrors the map
4290 +
     * transaction used by other compositors: initial fullscreen placement,
4291 +
     * visibility, then focus. */
4284 4292
    c->pending_map = 0;
4285 -
4286 4293
    if (c->mon)
4287 4294
        arrange(c->mon);
4295 +
    if (c->ws && c->ws->mon)
4296 +
        focus_client(c, 1);
4288 4297
    print_status();
4289 4298
4290 4299
    /* A new parentless window takes over a fullscreen window's display. */
4291 4300
    if (p)
4292 4301
        return;
test/clients.py +31 -0
326 326
    window.destroy()
327 327
    fallback.destroy()
328 328
    connection.close()
329 329
330 330
331 +
def premap_fullscreen(arguments: list[str]) -> None:
332 +
    """Map a toplevel which requested fullscreen before its initial commit."""
333 +
334 +
    connection = Connection(WlCompositor, WlShm, XdgWmBase)
335 +
    for index in range(2):
336 +
        window = Window(
337 +
            connection,
338 +
            f"swm-premap-fullscreen-{index}",
339 +
            0xFF224466 + index,
340 +
            fullscreen=True,
341 +
        )
342 +
        connection.roundtrips(4)
343 +
        if not window.fullscreen:
344 +
            raise Failure("pre-map fullscreen request was not configured")
345 +
        if window.width <= 320 or window.height <= 200:
346 +
            raise Failure(
347 +
                f"pre-map fullscreen retained initial size {window.width}x{window.height}"
348 +
            )
349 +
350 +
        # A post-map state change proves that the compositor survived the map
351 +
        # callback and continues processing this toplevel's protocol requests.
352 +
        window.toplevel.unset_fullscreen()
353 +
        connection.roundtrips(4)
354 +
        if window.fullscreen:
355 +
            raise Failure("pre-map fullscreen window did not leave fullscreen")
356 +
        window.destroy()
357 +
        connection.roundtrips(2)
358 +
    connection.close()
359 +
360 +
331 361
def transient(arguments: list[str]) -> None:
332 362
    """Exercise popup, parented, and fullscreen XDG surfaces."""
333 363
334 364
    count = int(arguments[0]) if arguments else 4
335 365
    connection = Connection(WlCompositor, WlShm, XdgWmBase)
1075 1105
ROLES = {
1076 1106
    "xdg": xdg_client,
1077 1107
    "xdg-deferred-configure": xdg_deferred_configure,
1078 1108
    "xdg-resize": xdg_resize,
1079 1109
    "xdg-lifecycle": xdg_lifecycle,
1110 +
    "premap-fullscreen": premap_fullscreen,
1080 1111
    "transient": transient,
1081 1112
    "keyboard": keyboard,
1082 1113
    "pointer": pointer,
1083 1114
    "foreign": foreign,
1084 1115
    "workspace": workspace,
test/run.py +3 -0
444 444
            consistently(
445 445
                f"client-selected floating size {index}",
446 446
                lambda expected=expected: published_geometry("client-resize")[2:] == expected,
447 447
            )
448 448
        terminate(client)
449 +
        eventually("focus after client-selected resize closes", lambda: current_title() in {"one", "two"})
449 450
450 451
    test("client-selected floating resize", client_resize)
451 452
452 453
    def window_control() -> None:
453 454
        target = current_title()
686 687
        protocol("workspace", 1)
687 688
        eventually("workspace restored after floating toggle", lambda: current_title() in {"one", "two"})
688 689
689 690
    test("max-stack floating transition", max_stack_floating)
690 691
692 +
    test("pre-map fullscreen lifecycle", lambda: protocol("premap-fullscreen"))
693 +
691 694
    def lifecycle() -> None:
692 695
        terminate(one)
693 696
        terminate(two)
694 697
        protocol("xdg-lifecycle", 2)
695 698
        protocol("transient", 2)