labwc/src/output.c
tokyo4j 66e26cd28a overlay: refactor
- Use a single `lab_scene_rect` for both overlay background and outlines,
  like I described in the TODO comment in ffd4005.
- Simplify the resource management by destroying the overlay tree when
  it's hidden. I think its overhead is pretty minimal.
- Share a single `lab_scene_rect` for both region/edge overlays.
2025-08-31 13:24:51 +01:00

1167 lines
33 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* output.c: labwc output and rendering
*
* Copyright (C) 2019-2021 Johan Malm
* Copyright (C) 2020 The Sway authors
*/
#define _POSIX_C_SOURCE 200809L
#include "output.h"
#include <assert.h>
#include <strings.h>
#include <wlr/backend/drm.h>
#include <wlr/backend/wayland.h>
#include <wlr/types/wlr_buffer.h>
#include <wlr/types/wlr_drm_lease_v1.h>
#include <wlr/types/wlr_gamma_control_v1.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_management_v1.h>
#include <wlr/types/wlr_output_power_management_v1.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/region.h>
#include <wlr/util/log.h>
#include "common/macros.h"
#include "common/mem.h"
#include "common/scene-helpers.h"
#include "config/rcxml.h"
#include "labwc.h"
#include "layers.h"
#include "node.h"
#include "output-state.h"
#include "output-virtual.h"
#include "protocols/cosmic-workspaces.h"
#include "protocols/ext-workspace.h"
#include "regions.h"
#include "session-lock.h"
#include "view.h"
#include "xwayland.h"
bool
output_get_tearing_allowance(struct output *output)
{
struct server *server = output->server;
/* never allow tearing when disabled */
if (!rc.allow_tearing) {
return false;
}
struct view *view = server->active_view;
/* tearing is only allowed for the output with the active view */
if (!view || view->output != output) {
return false;
}
/* allow tearing for any window when requested or forced */
if (rc.allow_tearing == LAB_TEARING_ENABLED) {
if (view->force_tearing == LAB_STATE_UNSPECIFIED) {
return view->tearing_hint;
} else {
return view->force_tearing == LAB_STATE_ENABLED;
}
}
/* remaining tearing options apply only to full-screen windows */
if (!view->fullscreen) {
return false;
}
if (view->force_tearing == LAB_STATE_UNSPECIFIED) {
/* honor the tearing hint or the fullscreen-force preference */
return view->tearing_hint ||
rc.allow_tearing == LAB_TEARING_FULLSCREEN_FORCED;
}
/* honor tearing as requested by action */
return view->force_tearing == LAB_STATE_ENABLED;
}
static void
output_apply_gamma(struct output *output)
{
assert(output);
assert(output->gamma_lut_changed);
struct server *server = output->server;
struct wlr_scene_output *scene_output = output->scene_output;
struct wlr_output_state pending;
wlr_output_state_init(&pending);
output->gamma_lut_changed = false;
struct wlr_gamma_control_v1 *gamma_control =
wlr_gamma_control_manager_v1_get_control(
server->gamma_control_manager_v1,
output->wlr_output);
if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) {
wlr_output_state_finish(&pending);
return;
}
if (!lab_wlr_scene_output_commit(scene_output, &pending)) {
wlr_gamma_control_v1_send_failed_and_destroy(gamma_control);
}
wlr_output_state_finish(&pending);
}
static void
handle_output_frame(struct wl_listener *listener, void *data)
{
/*
* This function is called every time an output is ready to display a
* frame - which is typically at 60 Hz.
*/
struct output *output = wl_container_of(listener, output, frame);
if (!output_is_usable(output)) {
return;
}
/*
* skip painting the session when it exists but is not active.
*/
if (output->server->session && !output->server->session->active) {
return;
}
if (!output->scene_output) {
/*
* TODO: This is a short term fix for issue #1667,
* a proper fix would require restructuring
* the life cycle of scene outputs, e.g.
* creating them on handle_new_output() only.
*/
wlr_log(WLR_INFO, "Failed to render new frame: no scene-output");
return;
}
if (output->gamma_lut_changed) {
/*
* We are not mixing the gamma state with
* other pending output changes to make it
* easier to handle a failed output commit
* due to gamma without impacting other
* unrelated output changes.
*/
output_apply_gamma(output);
} else {
struct wlr_scene_output *scene_output = output->scene_output;
struct wlr_output_state *pending = &output->pending;
pending->tearing_page_flip = output_get_tearing_allowance(output);
lab_wlr_scene_output_commit(scene_output, pending);
}
struct timespec now = { 0 };
clock_gettime(CLOCK_MONOTONIC, &now);
wlr_scene_output_send_frame_done(output->scene_output, &now);
}
static void
handle_output_destroy(struct wl_listener *listener, void *data)
{
struct output *output = wl_container_of(listener, output, destroy);
struct seat *seat = &output->server->seat;
regions_evacuate_output(output);
regions_destroy(seat, &output->regions);
if (seat->overlay.active.output == output) {
overlay_finish(seat);
}
wl_list_remove(&output->link);
wl_list_remove(&output->frame.link);
wl_list_remove(&output->destroy.link);
wl_list_remove(&output->request_state.link);
seat_output_layout_changed(seat);
for (size_t i = 0; i < ARRAY_SIZE(output->layer_tree); i++) {
wlr_scene_node_destroy(&output->layer_tree[i]->node);
}
wlr_scene_node_destroy(&output->layer_popup_tree->node);
wlr_scene_node_destroy(&output->osd_tree->node);
wlr_scene_node_destroy(&output->session_lock_tree->node);
if (output->workspace_osd) {
wlr_scene_node_destroy(&output->workspace_osd->node);
output->workspace_osd = NULL;
}
struct view *view;
struct server *server = output->server;
wl_list_for_each(view, &server->views, link) {
if (view->output == output) {
view_on_output_destroy(view);
}
}
wlr_output_state_finish(&output->pending);
/*
* Ensure that we don't accidentally try to dereference
* the output pointer in some output event handler like
* set_gamma.
*/
output->wlr_output->data = NULL;
/*
* output->scene_output (if still around at this point) is
* destroyed automatically when the wlr_output is destroyed
*/
free(output);
}
static void
handle_output_request_state(struct wl_listener *listener, void *data)
{
/* This ensures nested backends can be resized */
struct 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,
event->state->mode);
break;
case WLR_OUTPUT_STATE_MODE_CUSTOM:
wlr_output_state_set_custom_mode(&output->pending,
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 do_output_layout_change(struct server *server);
static void
add_output_to_layout(struct server *server, struct 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);
}
lab_cosmic_workspace_group_output_enter(
server->workspaces.cosmic_group, output->wlr_output);
lab_ext_workspace_group_output_enter(
server->workspaces.ext_group, output->wlr_output);
/* (Re-)create regions from config */
regions_reconfigure_output(output);
/* Create lock surface if needed */
if (server->session_lock_manager->locked) {
session_lock_output_create(server->session_lock_manager, 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);
}
/*
* Try to re-use the existing mode if configured to do so.
* Failing that, try to set the preferred mode.
*/
if (rc.reuse_output_mode && wlr_output->current_mode) {
wlr_log(WLR_DEBUG, "testing current mode %dx%d@%d",
wlr_output->current_mode->width,
wlr_output->current_mode->height,
wlr_output->current_mode->refresh);
if (wlr_output_test_state(wlr_output, state)) {
return true;
}
}
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
configure_new_output(struct server *server, struct output *output)
{
struct wlr_output *wlr_output = output->wlr_output;
wlr_log(WLR_DEBUG, "enable output %s", wlr_output->name);
wlr_output_state_set_enabled(&output->pending, true);
if (!output_test_auto(wlr_output, &output->pending,
/* 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.
*/
}
if (rc.adaptive_sync == LAB_ADAPTIVE_SYNC_ENABLED) {
output_enable_adaptive_sync(output, true);
}
output_state_commit(output);
wlr_output_effective_resolution(wlr_output,
&output->usable_area.width, &output->usable_area.height);
/*
* Wait until wlr_output_layout_add_auto() returns before
* calling do_output_layout_change(); this ensures that the
* wlr_output_cursor is created for the new output.
*/
server->pending_output_layout_change++;
add_output_to_layout(server, output);
server->pending_output_layout_change--;
}
static void
handle_new_output(struct wl_listener *listener, void *data)
{
/*
* This event is raised by the backend when a new output (aka display
* or monitor) becomes available.
*/
struct server *server = wl_container_of(listener, server, new_output);
struct wlr_output *wlr_output = data;
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
if (output->wlr_output == wlr_output) {
/*
* This is a duplicated notification.
* We may end up here when a virtual output
* was added before the headless backend was
* started up.
*/
return;
}
}
if (wlr_output_is_wl(wlr_output)) {
char title[64];
snprintf(title, sizeof(title), "%s - %s", "labwc", wlr_output->name);
wlr_wl_output_set_title(wlr_output, title);
wlr_wl_output_set_app_id(wlr_output, "labwc");
}
/*
* We offer any display as available for lease, some apps like
* gamescope want to take ownership of a display when they can
* to use planes and present directly.
* This is also useful for debugging the DRM parts of
* another compositor.
*
* All drm leasing is disabled due to a UAF bug in wlroots.
* We assume that the fix will be backported to 0.19.1 and thus
* check for a version >= 0.19.1. See following link for the fix status:
* https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5104
*
* TODO: remove once labwc starts tracking 0.20.x and the fix has been merged.
*/
#if LAB_WLR_VERSION_AT_LEAST(0, 19, 1)
if (server->drm_lease_manager && wlr_output_is_drm(wlr_output)) {
wlr_drm_lease_v1_manager_offer_output(
server->drm_lease_manager, wlr_output);
}
#endif
/*
* Don't configure any non-desktop displays, such as VR headsets;
*/
if (wlr_output->non_desktop) {
wlr_log(WLR_DEBUG, "Not configuring non-desktop output");
return;
}
/*
* Configures the output created by the backend to use our allocator
* and our renderer. Must be done once, before committing the output
*/
if (!wlr_output_init_render(wlr_output, server->allocator,
server->renderer)) {
wlr_log(WLR_ERROR, "unable to init output renderer");
return;
}
output = znew(*output);
output->wlr_output = wlr_output;
wlr_output->data = output;
output->server = server;
output_state_init(output);
wl_list_insert(&server->outputs, &output->link);
output->destroy.notify = handle_output_destroy;
wl_signal_add(&wlr_output->events.destroy, &output->destroy);
output->frame.notify = handle_output_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
output->request_state.notify = handle_output_request_state;
wl_signal_add(&wlr_output->events.request_state, &output->request_state);
wl_list_init(&output->regions);
wl_array_init(&output->osd_scene.items);
/*
* Create layer-trees (background, bottom, top and overlay) and
* a layer-popup-tree.
*/
for (size_t i = 0; i < ARRAY_SIZE(output->layer_tree); i++) {
output->layer_tree[i] =
wlr_scene_tree_create(&server->scene->tree);
node_descriptor_create(&output->layer_tree[i]->node,
LAB_NODE_DESC_TREE, NULL);
}
output->layer_popup_tree = wlr_scene_tree_create(&server->scene->tree);
node_descriptor_create(&output->layer_popup_tree->node,
LAB_NODE_DESC_TREE, NULL);
output->osd_tree = wlr_scene_tree_create(&server->scene->tree);
node_descriptor_create(&output->osd_tree->node,
LAB_NODE_DESC_TREE, NULL);
output->session_lock_tree = wlr_scene_tree_create(&server->scene->tree);
node_descriptor_create(&output->session_lock_tree->node,
LAB_NODE_DESC_TREE, NULL);
/*
* Set the z-positions to achieve the following order (from top to
* bottom):
* - session lock layer
* - window switcher osd
* - (compositor menu)
* - layer-shell popups
* - overlay layer
* - top layer
* - (views)
* - bottom layer
* - background layer
*/
wlr_scene_node_lower_to_bottom(&output->layer_tree[1]->node);
wlr_scene_node_lower_to_bottom(&output->layer_tree[0]->node);
struct wlr_scene_node *menu_node = &server->menu_tree->node;
wlr_scene_node_place_below(&output->layer_tree[2]->node, menu_node);
wlr_scene_node_place_below(&output->layer_tree[3]->node, menu_node);
wlr_scene_node_place_below(&output->layer_popup_tree->node, menu_node);
wlr_scene_node_raise_to_top(&output->osd_tree->node);
wlr_scene_node_raise_to_top(&output->session_lock_tree->node);
/*
* autoEnableOutputs=no only makes sense for outputs that can be
* hotplugged - currently only drm outputs. With wl/x11/headless
* it would result in no outputs being enabled at all. This check
* might need tweaking if wlroots adds other output backends.
*/
if (rc.auto_enable_outputs || !wlr_output_is_drm(wlr_output)) {
configure_new_output(server, output);
}
do_output_layout_change(server);
}
void
output_init(struct server *server)
{
server->gamma_control_manager_v1 =
wlr_gamma_control_manager_v1_create(server->wl_display);
server->new_output.notify = handle_new_output;
wl_signal_add(&server->backend->events.new_output, &server->new_output);
/*
* Create an output layout, which is a wlroots utility for working with
* an arrangement of screens in a physical layout.
*/
server->output_layout = wlr_output_layout_create(server->wl_display);
if (!server->output_layout) {
wlr_log(WLR_ERROR, "unable to create output layout");
exit(EXIT_FAILURE);
}
server->scene_layout = wlr_scene_attach_output_layout(server->scene,
server->output_layout);
if (!server->scene_layout) {
wlr_log(WLR_ERROR, "unable to create scene layout");
exit(EXIT_FAILURE);
}
/* Enable screen recording with wf-recorder */
wlr_xdg_output_manager_v1_create(server->wl_display,
server->output_layout);
wl_list_init(&server->outputs);
output_manager_init(server);
}
static void output_manager_finish(struct server *server);
void
output_finish(struct server *server)
{
wl_list_remove(&server->new_output.link);
output_manager_finish(server);
}
static void
output_update_for_layout_change(struct server *server)
{
output_update_all_usable_areas(server, /*layout_changed*/ true);
session_lock_update_for_layout_change(server);
/*
* "Move" each wlr_output_cursor (in per-output coordinates) to
* align with the seat cursor. Re-set the cursor image so that
* the cursor isn't invisible on new outputs.
*/
wlr_cursor_move(server->seat.cursor, NULL, 0, 0);
cursor_update_image(&server->seat);
}
static bool
output_config_apply(struct server *server,
struct wlr_output_configuration_v1 *config)
{
bool success = true;
server->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 output *output = output_from_wlr_output(server, o);
struct wlr_output_state *os = &output->pending;
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);
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) {
regions_evacuate_output(output);
lab_cosmic_workspace_group_output_leave(
server->workspaces.cosmic_group, output->wlr_output);
lab_ext_workspace_group_output_leave(
server->workspaces.ext_group, output->wlr_output);
/*
* 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;
}
}
server->pending_output_layout_change--;
do_output_layout_change(server);
return success;
}
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;
}
static void
handle_output_manager_test(struct wl_listener *listener, void *data)
{
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);
}
static void
handle_output_manager_apply(struct wl_listener *listener, void *data)
{
struct 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);
}
/* Re-set cursor image in case scale changed */
cursor_update_focus(server);
cursor_update_image(&server->seat);
}
/*
* Take the way outputs are currently configured/laid out and turn that into
* a struct that we send to clients via the wlr_output_configuration v1
* interface
*/
static struct
wlr_output_configuration_v1 *create_output_config(struct 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 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;
}
static void
do_output_layout_change(struct server *server)
{
if (!server->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()");
}
output_update_for_layout_change(server);
seat_output_layout_changed(&server->seat);
}
}
static void
handle_output_layout_change(struct wl_listener *listener, void *data)
{
struct server *server =
wl_container_of(listener, server, output_layout_change);
/* Prevents unnecessary layout recalculations */
server->pending_output_layout_change++;
output_virtual_update_fallback(server);
server->pending_output_layout_change--;
do_output_layout_change(server);
}
static void
handle_gamma_control_set_gamma(struct wl_listener *listener, void *data)
{
const struct wlr_gamma_control_manager_v1_set_gamma_event *event = data;
struct output *output = event->output->data;
if (!output_is_usable(output)) {
return;
}
output->gamma_lut_changed = true;
wlr_output_schedule_frame(output->wlr_output);
}
void
output_manager_init(struct server *server)
{
server->output_manager = wlr_output_manager_v1_create(server->wl_display);
server->output_layout_change.notify = handle_output_layout_change;
wl_signal_add(&server->output_layout->events.change,
&server->output_layout_change);
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->gamma_control_set_gamma.notify = handle_gamma_control_set_gamma;
wl_signal_add(&server->gamma_control_manager_v1->events.set_gamma,
&server->gamma_control_set_gamma);
}
static void
output_manager_finish(struct server *server)
{
wl_list_remove(&server->output_layout_change.link);
wl_list_remove(&server->output_manager_apply.link);
wl_list_remove(&server->output_manager_test.link);
wl_list_remove(&server->gamma_control_set_gamma.link);
}
struct output *
output_from_wlr_output(struct server *server, struct wlr_output *wlr_output)
{
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
if (output->wlr_output == wlr_output) {
return output;
}
}
return NULL;
}
struct output *
output_from_name(struct server *server, const char *name)
{
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
if (!output_is_usable(output) || !output->wlr_output->name) {
continue;
}
if (!strcasecmp(name, output->wlr_output->name)) {
return output;
}
}
return NULL;
}
struct output *
output_nearest_to(struct server *server, int lx, int ly)
{
double closest_x, closest_y;
wlr_output_layout_closest_point(server->output_layout, NULL, lx, ly,
&closest_x, &closest_y);
return output_from_wlr_output(server,
wlr_output_layout_output_at(server->output_layout,
closest_x, closest_y));
}
struct output *
output_nearest_to_cursor(struct server *server)
{
return output_nearest_to(server, server->seat.cursor->x,
server->seat.cursor->y);
}
struct output *
output_get_adjacent(struct output *output, enum lab_edge edge, bool wrap)
{
if (!output_is_usable(output)) {
wlr_log(WLR_ERROR,
"output is not usable, cannot find adjacent output");
return NULL;
}
/* Allow only up/down/left/right */
if (!lab_edge_is_cardinal(edge)) {
return NULL;
}
struct wlr_box box = output_usable_area_in_layout_coords(output);
int lx = box.x + box.width / 2;
int ly = box.y + box.height / 2;
/* Determine any adjacent output in the appropriate direction */
struct wlr_output *new_output = NULL;
struct wlr_output *current_output = output->wlr_output;
struct wlr_output_layout *layout = output->server->output_layout;
/* Cast from enum lab_edge to enum wlr_direction is safe */
new_output = wlr_output_layout_adjacent_output(layout,
(enum wlr_direction)edge, current_output, lx, ly);
/*
* Optionally wrap around from top-to-bottom or left-to-right, and vice
* versa.
*/
if (wrap && !new_output) {
enum lab_edge opposite = lab_edge_invert(edge);
new_output = wlr_output_layout_farthest_output(layout,
(enum wlr_direction)opposite, current_output, lx, ly);
}
/*
* When "adjacent" output is the same as the original, there is no
* adjacent
*/
if (!new_output || new_output == current_output) {
return NULL;
}
output = output_from_wlr_output(output->server, new_output);
if (!output_is_usable(output)) {
wlr_log(WLR_ERROR, "invalid output in layout");
return NULL;
}
return output;
}
bool
output_is_usable(struct output *output)
{
/* output_is_usable(NULL) is safe and returns false */
return output && output->wlr_output->enabled;
}
/* returns true if usable area changed */
static bool
update_usable_area(struct output *output)
{
struct wlr_box old = output->usable_area;
layers_arrange(output);
#if HAVE_XWAYLAND
struct view *view;
wl_list_for_each(view, &output->server->views, link) {
if (view->mapped && view->type == LAB_XWAYLAND_VIEW) {
xwayland_adjust_usable_area(view,
output->server->output_layout,
output->wlr_output, &output->usable_area);
}
}
#endif
return !wlr_box_equal(&old, &output->usable_area);
}
void
output_update_usable_area(struct output *output)
{
if (update_usable_area(output)) {
regions_update_geometry(output);
#if HAVE_XWAYLAND
xwayland_update_workarea(output->server);
#endif
desktop_arrange_all_views(output->server);
}
}
void
output_update_all_usable_areas(struct server *server, bool layout_changed)
{
bool usable_area_changed = false;
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
if (update_usable_area(output)) {
usable_area_changed = true;
regions_update_geometry(output);
} else if (layout_changed) {
regions_update_geometry(output);
}
}
if (usable_area_changed || layout_changed) {
#if HAVE_XWAYLAND
xwayland_update_workarea(server);
#endif
desktop_arrange_all_views(server);
}
}
struct wlr_box
output_usable_area_in_layout_coords(struct output *output)
{
if (!output) {
return (struct wlr_box){0};
}
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;
return box;
}
void
handle_output_power_manager_set_mode(struct wl_listener *listener, void *data)
{
struct server *server = wl_container_of(listener, server,
output_power_manager_set_mode);
struct wlr_output_power_v1_set_mode_event *event = data;
struct 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, 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, 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;
}
}
void
output_enable_adaptive_sync(struct output *output, bool enabled)
{
wlr_output_state_set_adaptive_sync_enabled(&output->pending, enabled);
if (!wlr_output_test_state(output->wlr_output, &output->pending)) {
wlr_output_state_set_adaptive_sync_enabled(&output->pending, 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);
}
}
float
output_max_scale(struct server *server)
{
/* Never return less than 1, in case outputs are disabled */
float scale = 1;
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
if (output_is_usable(output)) {
scale = MAX(scale, output->wlr_output->scale);
}
}
return scale;
}