Fix output re-enabling via wlr-randr
273d34f66e2af5f49782bbba303818996e2fb55d
1 parent
e622edb0
swm.c
+38 -10
| 308 | 308 | struct wl_listener destroy; |
|
| 309 | 309 | struct wl_listener request_state; |
|
| 310 | 310 | struct wl_listener output_bind; |
|
| 311 | 311 | struct wl_listener destroy_lock_surface; |
|
| 312 | 312 | struct wlr_session_lock_surface_v1 *lock_surface; |
|
| 313 | - | struct wlr_box m; /* Full display area within the layout. */ |
|
| 314 | - | struct wlr_box w; /* Area available for regular windows. */ |
|
| 313 | + | struct wlr_box m; /* Full display area within the layout. */ |
|
| 314 | + | struct wlr_box w; /* Area available for regular windows. */ |
|
| 315 | + | int layout_x; /* Last configured layout position. */ |
|
| 316 | + | int layout_y; |
|
| 315 | 317 | struct wl_list layers[4]; /* Layer surfaces on this display. */ |
|
| 316 | 318 | workspace_t *ws; /* Workspace shown on this display. */ |
|
| 317 | 319 | workspace_t *previous_workspace; /* Workspace shown before the current one. */ |
|
| 318 | 320 | int gamma_lut_changed; |
|
| 319 | 321 | int asleep; |
| 4543 | 4545 | } |
|
| 4544 | 4546 | ||
| 4545 | 4547 | /* Validate a requested display configuration and optionally apply it. */ |
|
| 4546 | 4548 | void output_manager_apply_or_test(struct wlr_output_configuration_v1 *config, bool test) { |
|
| 4547 | 4549 | /* Test or apply a display change requested by a tool such as wlr-randr. |
|
| 4550 | + | * Enabled outputs need a rendered buffer for backend modeset validation. |
|
| 4548 | 4551 | * update_monitors() updates local state after the layout reports the change. */ |
|
| 4549 | 4552 | struct wlr_output_configuration_head_v1 *config_head; |
|
| 4550 | 4553 | size_t states_len = wl_list_length(&config->heads), i = 0; |
|
| 4551 | - | int ok; |
|
| 4554 | + | int ok = 1; |
|
| 4552 | 4555 | ||
| 4553 | 4556 | if (!states_len || states_len > MAX_MONITORS) { |
|
| 4554 | 4557 | wlr_output_configuration_v1_send_failed(config); |
|
| 4555 | 4558 | wlr_output_configuration_v1_destroy(config); |
|
| 4556 | 4559 | return; |
|
| 4557 | 4560 | } |
|
| 4558 | 4561 | wl_list_for_each(config_head, &config->heads, link) { |
|
| 4559 | - | output_state_pool[i].output = config_head->state.output; |
|
| 4562 | + | struct wlr_output *wlr_output = config_head->state.output; |
|
| 4563 | + | monitor_t *m = wlr_output->data; |
|
| 4564 | + | ||
| 4565 | + | output_state_pool[i].output = wlr_output; |
|
| 4560 | 4566 | wlr_output_state_init(&output_state_pool[i].base); |
|
| 4567 | + | /* A disabled output may have no current mode. Treat an otherwise |
|
| 4568 | + | * unspecified enable request as selecting its preferred mode. */ |
|
| 4569 | + | if (config_head->state.enabled && !config_head->state.mode && |
|
| 4570 | + | (config_head->state.custom_mode.width <= 0 || |
|
| 4571 | + | config_head->state.custom_mode.height <= 0)) |
|
| 4572 | + | config_head->state.mode = wlr_output_preferred_mode(wlr_output); |
|
| 4561 | 4573 | wlr_output_head_v1_state_apply(&config_head->state, &output_state_pool[i].base); |
|
| 4562 | 4574 | i++; |
|
| 4575 | + | ||
| 4576 | + | if (config_head->state.enabled && |
|
| 4577 | + | (!m || !m->scene_output || |
|
| 4578 | + | !wlr_scene_output_build_state( |
|
| 4579 | + | m->scene_output, &output_state_pool[i - 1].base, nullptr |
|
| 4580 | + | ))) { |
|
| 4581 | + | ok = 0; |
|
| 4582 | + | break; |
|
| 4583 | + | } |
|
| 4563 | 4584 | } |
|
| 4564 | - | ok = wlr_backend_test(backend, output_state_pool, states_len); |
|
| 4585 | + | if (ok) |
|
| 4586 | + | ok = wlr_backend_test(backend, output_state_pool, states_len); |
|
| 4565 | 4587 | ||
| 4566 | 4588 | if (ok && !test) |
|
| 4567 | 4589 | ok = wlr_backend_commit(backend, output_state_pool, states_len); |
|
| 4568 | 4590 | ||
| 4569 | - | for (i = 0; i < states_len; i++) |
|
| 4570 | - | wlr_output_state_finish(&output_state_pool[i].base); |
|
| 4591 | + | while (i > 0) |
|
| 4592 | + | wlr_output_state_finish(&output_state_pool[--i].base); |
|
| 4571 | 4593 | ||
| 4572 | 4594 | if (ok && !test) { |
|
| 4573 | 4595 | wl_list_for_each(config_head, &config->heads, link) { |
|
| 4574 | 4596 | struct wlr_output *wlr_output = config_head->state.output; |
|
| 4575 | 4597 | monitor_t *m = wlr_output->data; |
|
| 4576 | 4598 | ||
| 4577 | 4599 | /* An applied output-management configuration supersedes DPMS. */ |
|
| 4578 | 4600 | m->asleep = 0; |
|
| 4579 | - | /* Don't move disabled outputs or positions which did not change. */ |
|
| 4601 | + | /* Re-add newly enabled outputs even when their requested position |
|
| 4602 | + | * equals the cleared local geometry. */ |
|
| 4580 | 4603 | if (wlr_output->enabled && |
|
| 4581 | - | (m->m.x != config_head->state.x || m->m.y != config_head->state.y)) |
|
| 4604 | + | (!wlr_output_layout_get(output_layout, wlr_output) || |
|
| 4605 | + | m->m.x != config_head->state.x || m->m.y != config_head->state.y)) |
|
| 4582 | 4606 | wlr_output_layout_add( |
|
| 4583 | 4607 | output_layout, wlr_output, config_head->state.x, config_head->state.y |
|
| 4584 | 4608 | ); |
|
| 4585 | 4609 | } |
|
| 4586 | 4610 | } |
| 6323 | 6347 | wl_list_for_each(m, &mons, link) { |
|
| 6324 | 6348 | if (m->wlr_output->enabled || m->asleep) |
|
| 6325 | 6349 | continue; |
|
| 6326 | 6350 | config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); |
|
| 6327 | 6351 | config_head->state.enabled = 0; |
|
| 6352 | + | config_head->state.x = m->layout_x; |
|
| 6353 | + | config_head->state.y = m->layout_y; |
|
| 6328 | 6354 | /* Remove the display so the pointer cannot enter it. */ |
|
| 6329 | 6355 | wlr_output_layout_remove(output_layout, m->wlr_output); |
|
| 6330 | 6356 | close_monitor(m); |
|
| 6331 | 6357 | m->m = m->w = (struct wlr_box){}; |
|
| 6332 | 6358 | } |
| 6350 | 6376 | continue; |
|
| 6351 | 6377 | config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); |
|
| 6352 | 6378 | ||
| 6353 | 6379 | /* Read the display's position in the combined desktop. */ |
|
| 6354 | 6380 | wlr_output_layout_get_box(output_layout, m->wlr_output, &m->m); |
|
| 6355 | - | m->w = m->m; |
|
| 6381 | + | m->layout_x = m->m.x; |
|
| 6382 | + | m->layout_y = m->m.y; |
|
| 6383 | + | m->w = m->m; |
|
| 6356 | 6384 | wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); |
|
| 6357 | 6385 | ||
| 6358 | 6386 | wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); |
|
| 6359 | 6387 | wlr_scene_rect_set_size(m->fullscreen_bg, m->m.width, m->m.height); |
|
| 6360 | 6388 |
test/clients.py
+20 -3
| 564 | 564 | ||
| 565 | 565 | def output_management(arguments: list[str]) -> None: |
|
| 566 | 566 | """Exercise output-management through the installed reference client.""" |
|
| 567 | 567 | ||
| 568 | 568 | command = ["wlr-randr"] |
|
| 569 | - | if arguments and arguments[0] in {"disable-first", "disable-second"}: |
|
| 570 | - | output = "HEADLESS-1" if arguments[0] == "disable-first" else "HEADLESS-2" |
|
| 571 | - | command += ["--output", output, "--off"] |
|
| 569 | + | if arguments and arguments[0] in { |
|
| 570 | + | "configure-second", |
|
| 571 | + | "disable-first", |
|
| 572 | + | "disable-second", |
|
| 573 | + | "enable-second", |
|
| 574 | + | }: |
|
| 575 | + | action = arguments[0] |
|
| 576 | + | output = "HEADLESS-1" if action == "disable-first" else "HEADLESS-2" |
|
| 577 | + | command += ["--output", output] |
|
| 578 | + | if action == "configure-second": |
|
| 579 | + | command += ["--pos", "120,80", "--scale", "2", "--transform", "90"] |
|
| 580 | + | else: |
|
| 581 | + | command += ["--off" if action.startswith("disable") else "--on"] |
|
| 582 | + | if action in {"configure-second", "enable-second"}: |
|
| 583 | + | subprocess.run([*command, "--dryrun"], check=True) |
|
| 572 | 584 | else: |
|
| 573 | 585 | command += ["--output", "HEADLESS-1", "--on"] |
|
| 574 | 586 | subprocess.run([*command, "--dryrun"], check=True) |
|
| 575 | 587 | subprocess.run(command, check=True) |
|
| 588 | + | if arguments and arguments[0] == "enable-second": |
|
| 589 | + | state = subprocess.run(["wlr-randr"], check=True, text=True, capture_output=True).stdout |
|
| 590 | + | for expected in ("Position: 120,80", "Transform: 90", "Scale: 2.000000"): |
|
| 591 | + | if expected not in state: |
|
| 592 | + | raise Failure(f"re-enabled output lost configuration: {expected}") |
|
| 576 | 593 | ||
| 577 | 594 | ||
| 578 | 595 | def layer(arguments: list[str]) -> None: |
|
| 579 | 596 | """Create, configure, map, and destroy layer-shell surfaces.""" |
|
| 580 | 597 |
test/run.py
+3 -0
| 683 | 683 | wait_title("multi") |
|
| 684 | 684 | virtual_keyboard(105, 4) |
|
| 685 | 685 | wait_title("") |
|
| 686 | 686 | virtual_keyboard(105, 1) |
|
| 687 | 687 | wait_title("multi") |
|
| 688 | + | protocol("output-management", "configure-second") |
|
| 688 | 689 | protocol("output-management", "disable-second") |
|
| 689 | 690 | wait_title("multi") |
|
| 691 | + | protocol("output-management", "enable-second") |
|
| 692 | + | wait_title("multi") |
|
| 690 | 693 | ||
| 691 | 694 | test("multiple outputs", multioutput) |
|
| 692 | 695 | terminate(client) |
|
| 693 | 696 | ||
| 694 | 697 |