#define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include #include #include #include #include "output.h" #include "layer.h" #include "node.h" #include "view.h" #include "session.h" /* * While an output layout change is in process, this counter is * non-zero and causes change-events from the wlr_output_layout * to be ignored (to prevent, for example, moving views in a * transitory layout state). Once the counter reaches zero, * do_output_layout_change() must be called explicitly. */ static int g_pending_output_layout_change = 0; static void output_state_init(struct diyac_output *output) { wlr_output_state_init(&output->pending_state); /* * As there is no direct way to convert an existing output * configuration to an output_state we first convert it * to a temporary output-management config and then apply * it to an empty wlr_output_state. */ struct wlr_output_configuration_v1 *backup_config = wlr_output_configuration_v1_create(); struct wlr_output_configuration_head_v1 *backup_head = wlr_output_configuration_head_v1_create( backup_config, output->wlr_output); wlr_output_head_v1_state_apply(&backup_head->state, &output->pending_state); wlr_output_configuration_v1_destroy(backup_config); } static bool output_state_commit(struct diyac_output *output) { bool committed = wlr_output_commit_state(output->wlr_output, &output->pending_state); if (committed) { wlr_output_state_finish(&output->pending_state); wlr_output_state_init(&output->pending_state); } else { wlr_log(WLR_ERROR, "Failed to commit frame"); } return committed; } static void output_frame(struct wl_listener *listener, void *data) { (void)data; /* This function is called every time an output is ready to display a frame, * generally at the output's refresh rate (e.g. 60Hz). */ struct diyac_output *output = wl_container_of(listener, output, frame); struct wlr_scene *scene = output->server->scene; struct wlr_scene_output *scene_output = wlr_scene_get_scene_output( scene, output->wlr_output); /* Render the scene if needed and commit the output */ wlr_scene_output_commit(scene_output, NULL); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); wlr_scene_output_send_frame_done(scene_output, &now); } static struct wlr_output_configuration_v1 *create_output_config(struct diyac_server *server) { struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create(); if (!config) { wlr_log(WLR_ERROR, "wlr_output_configuration_v1_create()"); return NULL; } struct diyac_output *output; wl_list_for_each(output, &server->outputs, link) { struct wlr_output_configuration_head_v1 *head = wlr_output_configuration_head_v1_create(config, output->wlr_output); if (!head) { wlr_log(WLR_ERROR, "wlr_output_configuration_head_v1_create()"); wlr_output_configuration_v1_destroy(config); return NULL; } struct wlr_box box; wlr_output_layout_get_box(server->output_layout, output->wlr_output, &box); if (!wlr_box_empty(&box)) { head->state.x = box.x; head->state.y = box.y; } else { wlr_log(WLR_ERROR, "failed to get output layout box"); } } return config; } /* returns true if usable area changed */ static bool update_usable_area(struct diyac_output *output) { struct wlr_box old = output->usable_area; diyac_layers_arrange(output); return !wlr_box_equal(&old, &output->usable_area); } static void do_output_layout_change(struct diyac_server *server) { if (!g_pending_output_layout_change) { struct wlr_output_configuration_v1 *config = create_output_config(server); if (config) { wlr_output_manager_v1_set_configuration( server->output_manager, config); } else { wlr_log(WLR_ERROR, "wlr_output_manager_v1_set_configuration()"); } struct diyac_output *output; wl_list_for_each(output, &server->outputs, link) { diyac_output_update_usable_area(output); } wlr_cursor_move(server->seat.cursor, NULL, 0, 0); } } static void output_request_state(struct wl_listener *listener, void *data) { /* This ensures nested backends can be resized */ struct diyac_output *output = wl_container_of(listener, output, request_state); const struct wlr_output_event_request_state *event = data; /* * If wlroots ever requests other state changes here we could * restore more of ddc9047a67cd53b2948f71fde1bbe9118000dd3f. */ if (event->state->committed == WLR_OUTPUT_STATE_MODE) { /* Only the mode has changed */ switch (event->state->mode_type) { case WLR_OUTPUT_STATE_MODE_FIXED: wlr_output_state_set_mode(&output->pending_state, event->state->mode); break; case WLR_OUTPUT_STATE_MODE_CUSTOM: wlr_output_state_set_custom_mode(&output->pending_state, event->state->custom_mode.width, event->state->custom_mode.height, event->state->custom_mode.refresh); break; } wlr_output_schedule_frame(output->wlr_output); return; } /* * Fallback path for everything that we didn't handle above. * The commit will cause a black frame injection so this * path causes flickering during resize of nested outputs. */ if (!wlr_output_commit_state(output->wlr_output, event->state)) { wlr_log(WLR_ERROR, "Backend requested a new state that could not be applied"); } } static void output_destroy(struct wl_listener *listener, void *data) { (void)data; struct diyac_output *output = wl_container_of(listener, output, destroy); if (output->lock_handle) { diyac_session_unlock_output(output); } wlr_scene_node_destroy(&output->scenes.background->node); wlr_scene_node_destroy(&output->scenes.bottom->node); wlr_scene_node_destroy(&output->scenes.top->node); wlr_scene_node_destroy(&output->scenes.popup->node); wlr_scene_node_destroy(&output->scenes.overlay->node); wlr_scene_node_destroy(&output->scenes.session->node); wl_list_remove(&output->frame.link); wl_list_remove(&output->request_state.link); wl_list_remove(&output->destroy.link); wl_list_remove(&output->link); struct diyac_server *server = output->server; struct diyac_view *view; wl_list_for_each(view, &server->views, link) { if (view->output == output) { /** * TODO: testing this case */ view->output = NULL; if (&server->outputs != server->outputs.next) { view->output = wl_container_of(server->outputs.next, view->output, link); diyac_view_update_geometry(view, false); } } } wlr_output_state_finish(&output->pending_state); free(output); } static bool output_test_auto(struct wlr_output *wlr_output, struct wlr_output_state *state, bool is_client_request) { wlr_log(WLR_DEBUG, "testing modes for %s", wlr_output->name); /* * When a client requests a specific mode, test only that mode. Here * we interpret a custom_mode of all zeroes as "none/any"; this is * seen e.g. with kanshi configs containing no "mode" field. In * theory, (state->committed & WLR_OUTPUT_STATE_MODE) should be zero * in this case, but this is not seen in practice. * * If the wlr_output_state did not come from a client request, then * ignore the mode/custom_mode fields which are not meaningful. */ if (is_client_request && (state->mode || state->custom_mode.width || state->custom_mode.height || state->custom_mode.refresh)) { if (state->mode) { wlr_log(WLR_DEBUG, "testing requested mode %dx%d@%d", state->mode->width, state->mode->height, state->mode->refresh); } else { wlr_log(WLR_DEBUG, "testing custom mode %dx%d@%d", state->custom_mode.width, state->custom_mode.height, state->custom_mode.refresh); } return wlr_output_test_state(wlr_output, state); } struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); if (preferred_mode) { wlr_log(WLR_DEBUG, "testing preferred mode %dx%d@%d", preferred_mode->width, preferred_mode->height, preferred_mode->refresh); wlr_output_state_set_mode(state, preferred_mode); if (wlr_output_test_state(wlr_output, state)) { return true; } } /* * Sometimes the preferred mode is not available due to hardware * constraints (e.g. GPU or cable bandwidth limitations). In these * cases it's better to fallback to lower modes than to end up with * a black screen. See sway@4cdc4ac6 */ struct wlr_output_mode *mode; wl_list_for_each(mode, &wlr_output->modes, link) { if (mode == preferred_mode) { continue; } wlr_log(WLR_DEBUG, "testing fallback mode %dx%d@%d", mode->width, mode->height, mode->refresh); wlr_output_state_set_mode(state, mode); if (wlr_output_test_state(wlr_output, state)) { return true; } } /* Reset mode if none worked (we may still try to commit) */ wlr_output_state_set_mode(state, NULL); return false; } static void add_output_to_layout(struct diyac_server *server, struct diyac_output *output) { struct wlr_output *wlr_output = output->wlr_output; struct wlr_output_layout_output *layout_output = wlr_output_layout_add_auto(server->output_layout, wlr_output); if (!layout_output) { wlr_log(WLR_ERROR, "unable to add output to layout"); return; } if (!output->scene_output) { output->scene_output = wlr_scene_output_create(server->scene, wlr_output); if (!output->scene_output) { wlr_log(WLR_ERROR, "unable to create scene output"); return; } /* * Note: wlr_scene_output_layout_add_output() is not * safe to call twice, so we call it only when initially * creating the scene_output. */ wlr_scene_output_layout_add_output(server->scene_layout, layout_output, output->scene_output); } /* Create lock surface if needed */ if (server->lock) { diyac_session_lock_output(output); } } static void diyac_server_new_output(struct wl_listener *listener, void *data) { /* This event is raised by the backend when a new output (aka a display or * monitor) becomes available. */ struct diyac_server *server = wl_container_of(listener, server, new_output); struct wlr_output *wlr_output = data; /* Configures the output created by the backend to use our allocator * and our renderer. Must be done once, before commiting the output */ wlr_output_init_render(wlr_output, server->allocator, server->renderer); /* Allocates and configures our state for this output */ struct diyac_output *output = calloc(1, sizeof(*output)); output->wlr_output = wlr_output; output->server = server; wlr_output->data = output; output->usable_area.x = 0; output->usable_area.y = 0; output->usable_area.width = wlr_output->width; output->usable_area.height = wlr_output->height; output_state_init(output); /* Sets up a listener for the frame event. */ output->frame.notify = output_frame; wl_signal_add(&wlr_output->events.frame, &output->frame); /* Sets up a listener for the state request event. */ output->request_state.notify = output_request_state; wl_signal_add(&wlr_output->events.request_state, &output->request_state); /* Sets up a listener for the destroy event. */ output->destroy.notify = output_destroy; wl_signal_add(&wlr_output->events.destroy, &output->destroy); wl_list_insert(&server->outputs, &output->link); /* * Create layer-trees (background, bottom, top and overlay) and * a layer-popup-tree. */ output->scenes.background = wlr_scene_tree_create(&server->scene->tree); output->scenes.bottom = wlr_scene_tree_create(&server->scene->tree); output->scenes.top = wlr_scene_tree_create(&server->scene->tree); output->scenes.popup = wlr_scene_tree_create(&server->scene->tree); output->scenes.overlay = wlr_scene_tree_create(&server->scene->tree); output->scenes.session = wlr_scene_tree_create(&server->scene->tree); diyac_node_descriptor_create(&output->scenes.background->node, DIYAC_NODE_TREE, NULL); diyac_node_descriptor_create(&output->scenes.bottom->node, DIYAC_NODE_TREE, NULL); diyac_node_descriptor_create(&output->scenes.top->node, DIYAC_NODE_TREE, NULL); diyac_node_descriptor_create(&output->scenes.popup->node, DIYAC_NODE_TREE, NULL); diyac_node_descriptor_create(&output->scenes.overlay->node, DIYAC_NODE_TREE, NULL); diyac_node_descriptor_create(&output->scenes.session->node, DIYAC_NODE_TREE, NULL); output->layer_tree[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND] = output->scenes.background; output->layer_tree[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM] = output->scenes.bottom; output->layer_tree[ZWLR_LAYER_SHELL_V1_LAYER_TOP] = output->scenes.top; output->layer_tree[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY] = output->scenes.overlay; output->lock_handle = NULL; /* * Set the z-positions to achieve the following order (from top to * bottom): * - session lock layer * - layer-shell popups * - overlay layer * - top layer * - views * - bottom layer * - background layer */ wlr_scene_node_lower_to_bottom(&output->scenes.bottom->node); wlr_scene_node_lower_to_bottom(&output->scenes.background->node); wlr_scene_node_raise_to_top(&output->scenes.top->node); wlr_scene_node_raise_to_top(&output->scenes.overlay->node); wlr_scene_node_raise_to_top(&output->scenes.popup->node); wlr_scene_node_raise_to_top(&output->scenes.session->node); wlr_output_state_set_enabled(&output->pending_state, true); if (!output_test_auto(wlr_output, &output->pending_state, /* is_client_request */ false)) { wlr_log(WLR_INFO, "mode test failed for output %s", wlr_output->name); /* * Continue anyway. For some reason, the test fails when * running nested, yet the following commit succeeds. */ } output_state_commit(output); wlr_output_effective_resolution(wlr_output, &output->usable_area.width, &output->usable_area.height); g_pending_output_layout_change++; add_output_to_layout(server, output); g_pending_output_layout_change--; do_output_layout_change(server); } void diyac_output_update_usable_area(struct diyac_output *output) { if (update_usable_area(output)) { // regions_update_geometry(output); diyac_arrange_all_views(output->server); // desktop_arrange_all_views(output->server); } } struct diyac_output *diyac_output_from_cursor(struct diyac_server *server) { double closest_x, closest_y; wlr_output_layout_closest_point(server->output_layout, NULL, server->seat.cursor->x, server->seat.cursor->y, &closest_x, &closest_y); struct wlr_output *output = wlr_output_layout_output_at(server->output_layout, closest_x, closest_y); if (!output) return NULL; return output->data; } void diyac_output_usable_area(struct diyac_output *output, struct wlr_box *area) { if (!area | !output) { return; } struct wlr_box box = output->usable_area; double ox = 0, oy = 0; wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy); box.x -= ox; box.y -= oy; memcpy(area, &box, sizeof(box)); } void diyac_output_full_area(struct diyac_output *output, struct wlr_box *area) { if (!area | !output) { return; } struct wlr_box box = {0}; wlr_output_effective_resolution(output->wlr_output, &box.width, &box.height); double ox = 0, oy = 0; wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy); box.x -= ox; box.y -= oy; memcpy(area, &box, sizeof(box)); } static void diyac_output_power_manager_set_mode(struct wl_listener *listener, void *data) { struct diyac_server *server = wl_container_of(listener, server, output_power_manager_set_mode); struct wlr_output_power_v1_set_mode_event *event = data; struct diyac_output *output = event->output->data; assert(output); switch (event->mode) { case ZWLR_OUTPUT_POWER_V1_MODE_OFF: if (!event->output->enabled) { return; } wlr_output_state_set_enabled(&output->pending_state, false); output_state_commit(output); break; case ZWLR_OUTPUT_POWER_V1_MODE_ON: if (event->output->enabled) { return; } wlr_output_state_set_enabled(&output->pending_state, true); output_state_commit(output); /* * Re-set the cursor image so that the cursor * isn't invisible on the newly enabled output. */ // cursor_update_image(&server->seat); break; } } static void handle_output_layout_change(struct wl_listener *listener, void *data) { (void)data; struct diyac_server *server = wl_container_of(listener, server, output_layout_change); do_output_layout_change(server); } static bool verify_output_config_v1(const struct wlr_output_configuration_v1 *config) { const char *err_msg = NULL; struct wlr_output_configuration_head_v1 *head; wl_list_for_each(head, &config->heads, link) { if (!head->state.enabled) { continue; } /* Handle custom modes */ if (!head->state.mode) { int32_t refresh = head->state.custom_mode.refresh; if (wlr_output_is_wl(head->state.output) && refresh != 0) { /* Wayland backend does not support refresh rates */ err_msg = "Wayland backend refresh rates unsupported"; goto custom_mode_failed; } } if (wlr_output_is_wl(head->state.output) && !head->state.adaptive_sync_enabled) { err_msg = "Wayland backend requires adaptive sync"; goto custom_mode_failed; } /* * Ensure the new output state can be applied on * its own and inform the client when it can not. * * Applying the changes may still fail later when * getting mixed with wlr_output->pending which * may contain further unrelated changes. */ struct wlr_output_state output_state; wlr_output_state_init(&output_state); wlr_output_head_v1_state_apply(&head->state, &output_state); if (!output_test_auto(head->state.output, &output_state, /* is_client_request */ true)) { wlr_output_state_finish(&output_state); return false; } wlr_output_state_finish(&output_state); } return true; custom_mode_failed: assert(err_msg); wlr_log(WLR_INFO, "%s (%s: %dx%d@%d)", err_msg, head->state.output->name, head->state.custom_mode.width, head->state.custom_mode.height, head->state.custom_mode.refresh); return false; } void diyac_output_enable_adaptive_sync(struct diyac_output *output, bool enabled) { wlr_output_state_set_adaptive_sync_enabled(&output->pending_state, enabled); if (!wlr_output_test_state(output->wlr_output, &output->pending_state)) { wlr_output_state_set_adaptive_sync_enabled(&output->pending_state, false); wlr_log(WLR_DEBUG, "failed to enable adaptive sync for output %s", output->wlr_output->name); } else { wlr_log(WLR_INFO, "adaptive sync %sabled for output %s", enabled ? "en" : "dis", output->wlr_output->name); } } static bool output_config_apply(struct diyac_server *server, struct wlr_output_configuration_v1 *config) { bool success = true; g_pending_output_layout_change++; struct wlr_output_configuration_head_v1 *head; wl_list_for_each(head, &config->heads, link) { struct wlr_output *o = head->state.output; struct diyac_output *output = o->data; struct wlr_output_state *os = &output->pending_state; bool output_enabled = head->state.enabled; wlr_output_state_set_enabled(os, output_enabled); if (output_enabled) { /* Output specific actions only */ if (head->state.mode) { wlr_output_state_set_mode(os, head->state.mode); } else { wlr_output_state_set_custom_mode(os, head->state.custom_mode.width, head->state.custom_mode.height, head->state.custom_mode.refresh); } /* * Try to ensure a valid mode. Ignore failures * here and just check the commit below. */ (void)output_test_auto(o, os, /* is_client_request */ true); wlr_output_state_set_scale(os, head->state.scale); wlr_output_state_set_transform(os, head->state.transform); diyac_output_enable_adaptive_sync(output, head->state.adaptive_sync_enabled); } if (!output_state_commit(output)) { /* * FIXME: This is only part of the story, we should revert * all previously committed outputs as well here. * * See https://github.com/labwc/labwc/pull/1528 */ wlr_log(WLR_INFO, "Output config commit failed: %s", o->name); success = false; break; } /* * Add or remove output from layout only if the commit went * through. Note that at startup, the output may have already * been enabled but not yet been added to the layout. */ bool was_in_layout = !!wlr_output_layout_get(server->output_layout, o); if (output_enabled) { if (!was_in_layout) { add_output_to_layout(server, output); } struct wlr_box pos = {0}; wlr_output_layout_get_box(server->output_layout, o, &pos); if (pos.x != head->state.x || pos.y != head->state.y) { /* * This overrides the automatic layout * * wlr_output_layout_add() in fact means _move() */ wlr_output_layout_add(server->output_layout, o, head->state.x, head->state.y); } } else if (was_in_layout) { /* * At time of writing, wlr_output_layout_remove() * indirectly destroys the wlr_scene_output, but * this behavior may change in future. To remove * doubt and avoid either a leak or double-free, * explicitly destroy the wlr_scene_output before * calling wlr_output_layout_remove(). */ wlr_scene_output_destroy(output->scene_output); wlr_output_layout_remove(server->output_layout, o); output->scene_output = NULL; } } g_pending_output_layout_change--; do_output_layout_change(server); return success; } static void handle_output_manager_apply(struct wl_listener *listener, void *data) { struct diyac_server *server = wl_container_of(listener, server, output_manager_apply); struct wlr_output_configuration_v1 *config = data; bool config_is_good = verify_output_config_v1(config); if (config_is_good && output_config_apply(server, config)) { wlr_output_configuration_v1_send_succeeded(config); } else { wlr_output_configuration_v1_send_failed(config); } wlr_output_configuration_v1_destroy(config); /* struct output *output; wl_list_for_each(output, &server->outputs, link) { wlr_xcursor_manager_load(server->seat.xcursor_manager, output->wlr_output->scale); } cursor_update_focus(server); cursor_update_image(&server->seat); */ } static void handle_output_manager_test(struct wl_listener *listener, void *data) { (void) listener; struct wlr_output_configuration_v1 *config = data; if (verify_output_config_v1(config)) { wlr_output_configuration_v1_send_succeeded(config); } else { wlr_output_configuration_v1_send_failed(config); } wlr_output_configuration_v1_destroy(config); } void diyac_output_init(struct diyac_server *server) { /* Creates an output layout, which a wlroots utility for working with an * arrangement of screens in a physical layout. */ server->output_layout = wlr_output_layout_create(server->wl_display); /* Configure a listener to be notified when new outputs are available on the * backend. */ wl_list_init(&server->outputs); server->new_output.notify = diyac_server_new_output; wl_signal_add(&server->backend->events.new_output, &server->new_output); server->scene_layout = wlr_scene_attach_output_layout(server->scene, server->output_layout); wlr_xdg_output_manager_v1_create(server->wl_display, server->output_layout); server->output_layout_change.notify = handle_output_layout_change; wl_signal_add(&server->output_layout->events.change, &server->output_layout_change); server->output_manager = wlr_output_manager_v1_create(server->wl_display); server->output_manager_apply.notify = handle_output_manager_apply; wl_signal_add(&server->output_manager->events.apply, &server->output_manager_apply); server->output_manager_test.notify = handle_output_manager_test; wl_signal_add(&server->output_manager->events.test, &server->output_manager_test); server->output_power_manager = wlr_output_power_manager_v1_create(server->wl_display); server->output_power_manager_set_mode.notify = diyac_output_power_manager_set_mode; wl_signal_add(&server->output_power_manager->events.set_mode, &server->output_power_manager_set_mode); }