mirror of
https://github.com/labwc/labwc.git
synced 2025-11-05 13:29:58 -05:00
It is not really useful for other output backends and just results in no outputs being enabled at all. (This is mainly an annoyance for developers normally running with drm but occasionally nested.)
1165 lines
33 KiB
C
1165 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/direction.h"
|
|
#include "common/macros.h"
|
|
#include "common/mem.h"
|
|
#include "common/scene-helpers.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_hide(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 view_edge edge, bool wrap)
|
|
{
|
|
if (!output_is_usable(output)) {
|
|
wlr_log(WLR_ERROR,
|
|
"output is not usable, cannot find adjacent output");
|
|
return NULL;
|
|
}
|
|
|
|
enum wlr_direction direction;
|
|
if (!direction_from_view_edge(edge, &direction)) {
|
|
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;
|
|
new_output = wlr_output_layout_adjacent_output(layout, direction,
|
|
current_output, lx, ly);
|
|
|
|
/*
|
|
* Optionally wrap around from top-to-bottom or left-to-right, and vice
|
|
* versa.
|
|
*/
|
|
if (wrap && !new_output) {
|
|
new_output = wlr_output_layout_farthest_output(layout,
|
|
direction_get_opposite(direction), 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;
|
|
}
|