Fix output re-enabling via wlr-randr

273d34f66e2af5f49782bbba303818996e2fb55d
Alexis Sellier committed ago 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