Handle fullscreen requests before map
517b92d49563b7ab4d1d51f771c2425cb3119609
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) |