2021-09-24 21:45:48 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2021-01-09 22:51:20 +00:00
|
|
|
/*
|
|
|
|
|
* output.c: labwc output and rendering
|
|
|
|
|
*
|
|
|
|
|
* Copyright (C) 2019-2021 Johan Malm
|
|
|
|
|
* Copyright (C) 2020 The Sway authors
|
|
|
|
|
*/
|
|
|
|
|
|
2020-10-15 21:03:04 +01:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
2024-01-22 15:21:18 +01:00
|
|
|
#include <assert.h>
|
2023-03-05 17:16:23 +01:00
|
|
|
#include <strings.h>
|
2023-12-09 12:01:11 +03:00
|
|
|
#include <wlr/backend/drm.h>
|
|
|
|
|
#include <wlr/backend/headless.h>
|
2024-01-22 15:21:18 +01:00
|
|
|
#include <wlr/backend/wayland.h>
|
2022-02-11 23:12:45 +00:00
|
|
|
#include <wlr/types/wlr_buffer.h>
|
2022-03-26 04:51:54 +00:00
|
|
|
#include <wlr/types/wlr_drm_lease_v1.h>
|
2022-03-15 21:30:25 +00:00
|
|
|
#include <wlr/types/wlr_output.h>
|
2020-09-29 19:53:46 +01:00
|
|
|
#include <wlr/types/wlr_xdg_output_v1.h>
|
2022-02-20 13:15:58 +00:00
|
|
|
#include <wlr/types/wlr_scene.h>
|
2021-01-09 22:51:20 +00:00
|
|
|
#include <wlr/util/region.h>
|
2021-07-23 21:15:55 +01:00
|
|
|
#include <wlr/util/log.h>
|
2023-10-20 18:34:14 -04:00
|
|
|
#include "common/macros.h"
|
2022-09-16 18:41:02 -04:00
|
|
|
#include "common/mem.h"
|
2023-01-30 05:30:24 +01:00
|
|
|
#include "common/scene-helpers.h"
|
2019-12-26 21:37:31 +00:00
|
|
|
#include "labwc.h"
|
2021-03-08 21:56:57 +00:00
|
|
|
#include "layers.h"
|
2022-03-03 18:20:16 +00:00
|
|
|
#include "node.h"
|
2022-07-06 16:57:25 +02:00
|
|
|
#include "regions.h"
|
2022-11-21 10:10:39 -05:00
|
|
|
#include "view.h"
|
2023-10-24 13:49:15 -04:00
|
|
|
#include "xwayland.h"
|
2019-12-26 21:37:31 +00:00
|
|
|
|
2024-01-08 22:58:58 +02:00
|
|
|
static bool
|
|
|
|
|
get_tearing_preference(struct output *output)
|
|
|
|
|
{
|
|
|
|
|
struct server *server = output->server;
|
|
|
|
|
|
|
|
|
|
/* Never allow tearing when disabled */
|
|
|
|
|
if (!rc.allow_tearing) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Tearing is only allowed for the output with the active view */
|
|
|
|
|
if (!server->active_view || server->active_view->output != output) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If the active view requests tearing, or it is toggled on with action, allow it */
|
|
|
|
|
return server->active_view->tearing_hint;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-09 22:51:20 +00:00
|
|
|
static void
|
2022-02-11 23:12:45 +00:00
|
|
|
output_frame_notify(struct wl_listener *listener, void *data)
|
2021-01-09 22:51:20 +00:00
|
|
|
{
|
2023-12-08 22:08:46 +00:00
|
|
|
/*
|
|
|
|
|
* This function is called every time an output is ready to display a
|
|
|
|
|
* frame - which is typically at 60 Hz.
|
|
|
|
|
*/
|
2022-02-11 23:12:45 +00:00
|
|
|
struct output *output = wl_container_of(listener, output, frame);
|
2023-02-16 12:24:27 -05:00
|
|
|
if (!output_is_usable(output)) {
|
2021-01-09 22:51:20 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2023-12-01 17:07:05 +00:00
|
|
|
|
|
|
|
|
struct wlr_output *wlr_output = output->wlr_output;
|
|
|
|
|
struct server *server = output->server;
|
|
|
|
|
|
|
|
|
|
if (output->gamma_lut_changed) {
|
|
|
|
|
struct wlr_output_state pending;
|
|
|
|
|
wlr_output_state_init(&pending);
|
|
|
|
|
if (!wlr_scene_output_build_state(output->scene_output, &pending, NULL)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
output->gamma_lut_changed = false;
|
|
|
|
|
struct wlr_gamma_control_v1 *gamma_control =
|
|
|
|
|
wlr_gamma_control_manager_v1_get_control(
|
|
|
|
|
server->gamma_control_manager_v1, wlr_output);
|
|
|
|
|
if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) {
|
|
|
|
|
wlr_output_state_finish(&pending);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!wlr_output_commit_state(output->wlr_output, &pending)) {
|
|
|
|
|
wlr_gamma_control_v1_send_failed_and_destroy(gamma_control);
|
|
|
|
|
wlr_output_state_finish(&pending);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wlr_damage_ring_rotate(&output->scene_output->damage_ring);
|
|
|
|
|
wlr_output_state_finish(&pending);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-08 22:58:58 +02:00
|
|
|
output->wlr_output->pending.tearing_page_flip =
|
|
|
|
|
get_tearing_preference(output);
|
|
|
|
|
lab_wlr_scene_output_commit(output->scene_output);
|
2023-12-05 16:19:04 +00:00
|
|
|
|
|
|
|
|
struct timespec now = { 0 };
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
|
wlr_scene_output_send_frame_done(output->scene_output, &now);
|
2021-01-09 22:51:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
output_destroy_notify(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
2021-08-25 20:45:39 +01:00
|
|
|
struct output *output = wl_container_of(listener, output, destroy);
|
2022-07-07 19:05:54 +02:00
|
|
|
regions_evacuate_output(output);
|
2022-09-13 08:15:33 +02:00
|
|
|
regions_destroy(&output->server->seat, &output->regions);
|
2021-08-25 20:45:39 +01:00
|
|
|
wl_list_remove(&output->link);
|
2022-02-11 23:12:45 +00:00
|
|
|
wl_list_remove(&output->frame.link);
|
2021-08-25 20:45:39 +01:00
|
|
|
wl_list_remove(&output->destroy.link);
|
2023-01-30 05:30:24 +01:00
|
|
|
wl_list_remove(&output->request_state.link);
|
2024-01-09 06:58:35 +01:00
|
|
|
seat_output_layout_changed(&output->server->seat);
|
2022-08-24 21:12:24 +02:00
|
|
|
|
2023-09-16 22:25:41 +01:00
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(output->layer_tree); i++) {
|
2022-11-15 09:38:15 +08:00
|
|
|
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);
|
2023-03-30 22:19:05 +01:00
|
|
|
wlr_scene_node_destroy(&output->session_lock_tree->node);
|
2023-05-17 13:13:04 +02:00
|
|
|
if (output->workspace_osd) {
|
|
|
|
|
wlr_scene_node_destroy(&output->workspace_osd->node);
|
|
|
|
|
output->workspace_osd = NULL;
|
|
|
|
|
}
|
2022-11-15 09:38:15 +08:00
|
|
|
|
2022-08-24 21:12:24 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-02 15:05:26 +01:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
|
2023-09-21 21:33:12 -04:00
|
|
|
/*
|
|
|
|
|
* output->scene_output (if still around at this point) is
|
|
|
|
|
* destroyed automatically when the wlr_output is destroyed
|
|
|
|
|
*/
|
2022-08-17 13:36:33 +08:00
|
|
|
free(output);
|
2021-01-09 22:51:20 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-30 05:30:24 +01:00
|
|
|
static void
|
|
|
|
|
output_request_state_notify(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 (!wlr_output_commit_state(output->wlr_output, event->state)) {
|
|
|
|
|
wlr_log(WLR_ERROR, "Backend requested a new state that could not be applied");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-20 01:17:22 -04:00
|
|
|
static void do_output_layout_change(struct server *server);
|
|
|
|
|
|
2023-01-23 07:15:55 +01:00
|
|
|
static bool
|
|
|
|
|
can_reuse_mode(struct wlr_output *wlr_output)
|
|
|
|
|
{
|
|
|
|
|
return wlr_output->current_mode && wlr_output_test(wlr_output);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 21:33:12 -04:00
|
|
|
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;
|
|
|
|
|
}
|
2023-12-13 17:37:14 -05:00
|
|
|
/*
|
|
|
|
|
* 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);
|
2023-09-21 21:33:12 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-29 19:53:46 +01:00
|
|
|
static void
|
|
|
|
|
new_output_notify(struct wl_listener *listener, void *data)
|
2020-05-29 22:18:03 +01:00
|
|
|
{
|
2021-08-22 14:14:50 +01:00
|
|
|
/*
|
|
|
|
|
* This event is rasied by the backend when a new output (aka display
|
|
|
|
|
* or monitor) becomes available.
|
|
|
|
|
*/
|
2020-05-29 22:18:03 +01:00
|
|
|
struct server *server = wl_container_of(listener, server, new_output);
|
|
|
|
|
struct wlr_output *wlr_output = data;
|
|
|
|
|
|
2023-12-09 12:01:11 +03:00
|
|
|
/* Name virtual output */
|
|
|
|
|
if (wlr_output_is_headless(wlr_output) && server->headless.pending_output_name[0] != '\0') {
|
|
|
|
|
wlr_output_set_name(wlr_output, server->headless.pending_output_name);
|
|
|
|
|
server->headless.pending_output_name[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-26 04:51:54 +00:00
|
|
|
/*
|
2022-06-16 05:56:38 +00:00
|
|
|
* 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.
|
|
|
|
|
*/
|
2023-12-09 12:01:11 +03:00
|
|
|
if (server->drm_lease_manager && wlr_output_is_drm(wlr_output)) {
|
2022-06-16 05:56:38 +00:00
|
|
|
wlr_drm_lease_v1_manager_offer_output(
|
|
|
|
|
server->drm_lease_manager, wlr_output);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Don't configure any non-desktop displays, such as VR headsets;
|
2022-03-26 04:51:54 +00:00
|
|
|
*/
|
|
|
|
|
if (wlr_output->non_desktop) {
|
|
|
|
|
wlr_log(WLR_DEBUG, "Not configuring non-desktop output");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-26 19:27:50 +00:00
|
|
|
/*
|
|
|
|
|
* Configures the output created by the backend to use our allocator
|
2024-01-04 19:42:26 +02:00
|
|
|
* and our renderer. Must be done once, before committing the output
|
2021-11-26 19:27:50 +00:00
|
|
|
*/
|
|
|
|
|
if (!wlr_output_init_render(wlr_output, server->allocator,
|
|
|
|
|
server->renderer)) {
|
2021-11-21 07:01:16 +00:00
|
|
|
wlr_log(WLR_ERROR, "unable to init output renderer");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-26 10:52:09 -05:00
|
|
|
wlr_log(WLR_DEBUG, "enable output");
|
|
|
|
|
wlr_output_enable(wlr_output, true);
|
|
|
|
|
|
2023-01-23 07:15:55 +01:00
|
|
|
/*
|
|
|
|
|
* Try to re-use the existing mode if configured to do so.
|
|
|
|
|
* Failing that, try to set the preferred mode.
|
|
|
|
|
*/
|
|
|
|
|
struct wlr_output_mode *preferred_mode = NULL;
|
|
|
|
|
if (!rc.reuse_output_mode || !can_reuse_mode(wlr_output)) {
|
|
|
|
|
wlr_log(WLR_DEBUG, "set preferred mode");
|
|
|
|
|
/* The mode is a tuple of (width, height, refresh rate). */
|
|
|
|
|
preferred_mode = wlr_output_preferred_mode(wlr_output);
|
|
|
|
|
wlr_output_set_mode(wlr_output, preferred_mode);
|
|
|
|
|
}
|
2021-08-22 14:14:50 +01:00
|
|
|
|
2020-05-29 22:18:03 +01:00
|
|
|
/*
|
2021-08-22 14:14:50 +01:00
|
|
|
* 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
|
2020-05-29 22:18:03 +01:00
|
|
|
*/
|
2021-08-22 14:14:50 +01:00
|
|
|
if (!wlr_output_test(wlr_output)) {
|
2021-08-25 20:45:39 +01:00
|
|
|
wlr_log(WLR_DEBUG,
|
|
|
|
|
"preferred mode rejected, falling back to another mode");
|
2021-08-22 14:14:50 +01:00
|
|
|
struct wlr_output_mode *mode;
|
|
|
|
|
wl_list_for_each(mode, &wlr_output->modes, link) {
|
|
|
|
|
if (mode == preferred_mode) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
wlr_output_set_mode(wlr_output, mode);
|
|
|
|
|
if (wlr_output_test(wlr_output)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-29 22:18:03 +01:00
|
|
|
}
|
|
|
|
|
|
2023-12-30 17:35:41 +02:00
|
|
|
if (rc.adaptive_sync == LAB_ADAPTIVE_SYNC_ENABLED) {
|
|
|
|
|
output_enable_adaptive_sync(wlr_output, true);
|
2023-04-30 14:32:45 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-22 14:14:50 +01:00
|
|
|
wlr_output_commit(wlr_output);
|
|
|
|
|
|
2022-09-18 15:22:26 -04:00
|
|
|
struct output *output = znew(*output);
|
2020-05-29 22:18:03 +01:00
|
|
|
output->wlr_output = wlr_output;
|
2021-10-20 22:32:46 +01:00
|
|
|
wlr_output->data = output;
|
2020-05-29 22:18:03 +01:00
|
|
|
output->server = server;
|
2021-07-12 21:39:09 +01:00
|
|
|
wlr_output_effective_resolution(wlr_output,
|
|
|
|
|
&output->usable_area.width, &output->usable_area.height);
|
2020-05-29 22:18:03 +01:00
|
|
|
wl_list_insert(&server->outputs, &output->link);
|
2021-01-09 22:51:20 +00:00
|
|
|
|
2020-09-29 19:53:46 +01:00
|
|
|
output->destroy.notify = output_destroy_notify;
|
|
|
|
|
wl_signal_add(&wlr_output->events.destroy, &output->destroy);
|
2022-02-11 23:12:45 +00:00
|
|
|
output->frame.notify = output_frame_notify;
|
|
|
|
|
wl_signal_add(&wlr_output->events.frame, &output->frame);
|
2021-01-09 22:51:20 +00:00
|
|
|
|
2023-01-30 05:30:24 +01:00
|
|
|
output->request_state.notify = output_request_state_notify;
|
|
|
|
|
wl_signal_add(&wlr_output->events.request_state, &output->request_state);
|
|
|
|
|
|
2022-07-06 16:57:25 +02:00
|
|
|
wl_list_init(&output->regions);
|
|
|
|
|
|
2022-03-03 17:56:38 +00:00
|
|
|
/*
|
|
|
|
|
* Create layer-trees (background, bottom, top and overlay) and
|
|
|
|
|
* a layer-popup-tree.
|
|
|
|
|
*/
|
2023-09-16 22:25:41 +01:00
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(output->layer_tree); i++) {
|
2022-02-20 13:15:58 +00:00
|
|
|
output->layer_tree[i] =
|
2022-06-05 15:17:35 +02:00
|
|
|
wlr_scene_tree_create(&server->scene->tree);
|
2022-03-03 18:20:16 +00:00
|
|
|
node_descriptor_create(&output->layer_tree[i]->node,
|
|
|
|
|
LAB_NODE_DESC_TREE, NULL);
|
2022-02-20 13:15:58 +00:00
|
|
|
}
|
2022-06-05 15:17:35 +02:00
|
|
|
output->layer_popup_tree = wlr_scene_tree_create(&server->scene->tree);
|
2022-03-03 18:20:16 +00:00
|
|
|
node_descriptor_create(&output->layer_popup_tree->node,
|
|
|
|
|
LAB_NODE_DESC_TREE, NULL);
|
2022-06-05 15:17:35 +02:00
|
|
|
output->osd_tree = wlr_scene_tree_create(&server->scene->tree);
|
2022-04-20 17:45:10 +01:00
|
|
|
node_descriptor_create(&output->osd_tree->node,
|
|
|
|
|
LAB_NODE_DESC_TREE, NULL);
|
2023-03-30 22:19:05 +01:00
|
|
|
output->session_lock_tree = wlr_scene_tree_create(&server->scene->tree);
|
|
|
|
|
node_descriptor_create(&output->session_lock_tree->node,
|
|
|
|
|
LAB_NODE_DESC_TREE, NULL);
|
2022-03-03 17:56:38 +00:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Set the z-positions to achieve the following order (from top to
|
|
|
|
|
* bottom):
|
2023-04-27 18:55:57 +03:00
|
|
|
* - session lock layer
|
2022-03-03 17:56:38 +00:00
|
|
|
* - layer-shell popups
|
|
|
|
|
* - overlay layer
|
|
|
|
|
* - top layer
|
|
|
|
|
* - views
|
|
|
|
|
* - bottom layer
|
|
|
|
|
* - background layer
|
|
|
|
|
*/
|
2022-02-20 13:15:58 +00:00
|
|
|
wlr_scene_node_lower_to_bottom(&output->layer_tree[1]->node);
|
|
|
|
|
wlr_scene_node_lower_to_bottom(&output->layer_tree[0]->node);
|
|
|
|
|
wlr_scene_node_raise_to_top(&output->layer_tree[2]->node);
|
|
|
|
|
wlr_scene_node_raise_to_top(&output->layer_tree[3]->node);
|
2022-03-03 17:56:38 +00:00
|
|
|
wlr_scene_node_raise_to_top(&output->layer_popup_tree->node);
|
2023-04-27 18:55:57 +03:00
|
|
|
wlr_scene_node_raise_to_top(&output->session_lock_tree->node);
|
2021-03-22 21:25:51 +00:00
|
|
|
|
2022-09-20 01:17:22 -04:00
|
|
|
/*
|
|
|
|
|
* 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++;
|
|
|
|
|
|
2023-09-21 21:33:12 -04:00
|
|
|
add_output_to_layout(server, output);
|
2022-09-20 01:17:22 -04:00
|
|
|
|
2023-01-01 18:12:20 +01:00
|
|
|
/* Create regions from config */
|
|
|
|
|
regions_reconfigure_output(output);
|
|
|
|
|
|
2023-03-30 22:19:05 +01:00
|
|
|
if (server->session_lock) {
|
|
|
|
|
session_lock_output_create(server->session_lock, output);
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-20 01:17:22 -04:00
|
|
|
server->pending_output_layout_change--;
|
|
|
|
|
do_output_layout_change(server);
|
2024-01-09 06:58:35 +01:00
|
|
|
seat_output_layout_changed(&output->server->seat);
|
2020-09-29 19:53:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
output_init(struct server *server)
|
|
|
|
|
{
|
2023-12-01 17:07:05 +00:00
|
|
|
server->gamma_control_manager_v1 =
|
|
|
|
|
wlr_gamma_control_manager_v1_create(server->wl_display);
|
|
|
|
|
|
2020-09-29 19:53:46 +01:00
|
|
|
server->new_output.notify = new_output_notify;
|
|
|
|
|
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();
|
|
|
|
|
if (!server->output_layout) {
|
|
|
|
|
wlr_log(WLR_ERROR, "unable to create output layout");
|
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
|
}
|
2023-09-21 21:33:12 -04:00
|
|
|
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);
|
|
|
|
|
}
|
2020-09-29 19:53:46 +01:00
|
|
|
|
|
|
|
|
/* Enable screen recording with wf-recorder */
|
|
|
|
|
wlr_xdg_output_manager_v1_create(server->wl_display,
|
|
|
|
|
server->output_layout);
|
|
|
|
|
|
|
|
|
|
wl_list_init(&server->outputs);
|
2021-02-27 23:15:02 -05:00
|
|
|
|
|
|
|
|
output_manager_init(server);
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-31 17:30:55 -05:00
|
|
|
static void
|
|
|
|
|
output_update_for_layout_change(struct server *server)
|
|
|
|
|
{
|
2023-01-06 19:54:51 +01:00
|
|
|
output_update_all_usable_areas(server, /*layout_changed*/ true);
|
2023-10-04 23:54:27 -04:00
|
|
|
session_lock_update_for_layout_change();
|
2021-12-31 17:30:55 -05:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* "Move" each wlr_output_cursor (in per-output coordinates) to
|
2023-01-26 01:21:43 +01:00
|
|
|
* align with the seat cursor. Re-set the cursor image so that
|
|
|
|
|
* the cursor isn't invisible on new outputs.
|
2021-12-31 17:30:55 -05:00
|
|
|
*/
|
|
|
|
|
wlr_cursor_move(server->seat.cursor, NULL, 0, 0);
|
2023-01-26 01:21:43 +01:00
|
|
|
cursor_update_image(&server->seat);
|
2021-12-31 17:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
2024-02-22 21:22:28 +01:00
|
|
|
static bool
|
2021-08-25 20:45:39 +01:00
|
|
|
output_config_apply(struct server *server,
|
2021-03-06 18:30:53 +00:00
|
|
|
struct wlr_output_configuration_v1 *config)
|
2021-02-27 23:15:02 -05:00
|
|
|
{
|
2024-02-22 21:22:28 +01:00
|
|
|
bool success = true;
|
2022-09-20 01:17:22 -04:00
|
|
|
server->pending_output_layout_change++;
|
2021-02-27 23:15:02 -05:00
|
|
|
|
|
|
|
|
struct wlr_output_configuration_head_v1 *head;
|
|
|
|
|
wl_list_for_each(head, &config->heads, link) {
|
|
|
|
|
struct wlr_output *o = head->state.output;
|
2022-05-01 01:23:31 +02:00
|
|
|
struct output *output = output_from_wlr_output(server, o);
|
2022-06-16 05:56:38 +00:00
|
|
|
bool output_enabled = head->state.enabled && !output->leased;
|
|
|
|
|
bool need_to_add = output_enabled && !o->enabled;
|
|
|
|
|
bool need_to_remove = !output_enabled && o->enabled;
|
2021-02-27 23:15:02 -05:00
|
|
|
|
2022-06-16 05:56:38 +00:00
|
|
|
wlr_output_enable(o, output_enabled);
|
|
|
|
|
if (output_enabled) {
|
2023-09-30 16:53:10 +09:00
|
|
|
/* Output specific actions only */
|
2021-08-25 20:45:39 +01:00
|
|
|
if (head->state.mode) {
|
2021-02-27 23:15:02 -05:00
|
|
|
wlr_output_set_mode(o, head->state.mode);
|
|
|
|
|
} else {
|
|
|
|
|
int32_t width = head->state.custom_mode.width;
|
|
|
|
|
int32_t height = head->state.custom_mode.height;
|
|
|
|
|
int32_t refresh = head->state.custom_mode.refresh;
|
2021-09-21 22:05:56 +01:00
|
|
|
wlr_output_set_custom_mode(o, width,
|
|
|
|
|
height, refresh);
|
2021-02-27 23:15:02 -05:00
|
|
|
}
|
|
|
|
|
wlr_output_set_scale(o, head->state.scale);
|
|
|
|
|
wlr_output_set_transform(o, head->state.transform);
|
2024-02-07 23:17:17 +02:00
|
|
|
output_enable_adaptive_sync(o, head->state.adaptive_sync_enabled);
|
2021-02-27 23:15:02 -05:00
|
|
|
}
|
2022-05-01 01:23:31 +02:00
|
|
|
if (!wlr_output_commit(o)) {
|
2024-02-22 21:22:28 +01:00
|
|
|
/*
|
|
|
|
|
* FIXME: This is only part of the story, we should revert
|
|
|
|
|
* all previously commited 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;
|
2022-05-01 01:23:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Only do Layout specific actions if the commit went trough */
|
|
|
|
|
if (need_to_add) {
|
2023-09-21 21:33:12 -04:00
|
|
|
add_output_to_layout(server, output);
|
2022-05-01 01:23:31 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-16 05:56:38 +00:00
|
|
|
if (output_enabled) {
|
2023-01-29 09:58:42 +01:00
|
|
|
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) {
|
2023-01-24 03:57:10 +01:00
|
|
|
/*
|
|
|
|
|
* This overrides the automatic layout
|
|
|
|
|
*
|
|
|
|
|
* wlr_output_layout_add() in fact means _move()
|
|
|
|
|
*/
|
|
|
|
|
wlr_output_layout_add(server->output_layout, o,
|
2023-01-29 09:58:42 +01:00
|
|
|
head->state.x, head->state.y);
|
|
|
|
|
}
|
2022-05-01 01:23:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (need_to_remove) {
|
2022-07-07 19:05:54 +02:00
|
|
|
regions_evacuate_output(output);
|
2023-09-21 21:33:12 -04:00
|
|
|
/*
|
|
|
|
|
* 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);
|
2022-05-01 01:23:31 +02:00
|
|
|
wlr_output_layout_remove(server->output_layout, o);
|
|
|
|
|
output->scene_output = NULL;
|
|
|
|
|
}
|
2021-02-27 23:15:02 -05:00
|
|
|
}
|
|
|
|
|
|
2022-09-20 01:17:22 -04:00
|
|
|
server->pending_output_layout_change--;
|
2022-07-09 00:42:04 +02:00
|
|
|
do_output_layout_change(server);
|
2024-02-22 21:22:28 +01:00
|
|
|
return success;
|
2021-02-27 23:15:02 -05:00
|
|
|
}
|
2021-02-28 00:08:47 -05:00
|
|
|
|
2021-08-25 20:45:39 +01:00
|
|
|
static bool
|
|
|
|
|
verify_output_config_v1(const struct wlr_output_configuration_v1 *config)
|
2021-02-28 00:08:47 -05:00
|
|
|
{
|
2024-01-22 15:21:18 +01:00
|
|
|
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_drm(head->state.output) && refresh == 0) {
|
|
|
|
|
/*
|
|
|
|
|
* wlroots has a bug which causes a divide by zero
|
|
|
|
|
* when setting the refresh rate to 0 on a DRM output.
|
|
|
|
|
* It is already fixed but not part of an official 0.17.x
|
|
|
|
|
* release. Even it would be we still need to carry the
|
|
|
|
|
* fix here to prevent older 0.17.x releases from crashing.
|
|
|
|
|
*
|
|
|
|
|
* https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3791
|
|
|
|
|
*/
|
|
|
|
|
err_msg = "DRM backend does not support a refresh rate of 0";
|
|
|
|
|
goto custom_mode_failed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (wlr_output_is_wl(head->state.output) && refresh != 0) {
|
|
|
|
|
/* Wayland backend does not support refresh rates */
|
2024-02-22 21:24:05 +01:00
|
|
|
err_msg = "Wayland backend refresh rates unsupported";
|
2024-01-22 15:21:18 +01:00
|
|
|
goto custom_mode_failed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-22 21:24:05 +01:00
|
|
|
if (wlr_output_is_wl(head->state.output)
|
|
|
|
|
&& !head->state.adaptive_sync_enabled) {
|
|
|
|
|
err_msg = "Wayland backend requires adaptive sync";
|
|
|
|
|
goto custom_mode_failed;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-22 15:21:18 +01:00
|
|
|
/*
|
|
|
|
|
* 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 (!wlr_output_test_state(head->state.output, &output_state)) {
|
|
|
|
|
wlr_output_state_finish(&output_state);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
wlr_output_state_finish(&output_state);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-28 00:08:47 -05:00
|
|
|
return true;
|
2024-01-22 15:21:18 +01:00
|
|
|
|
|
|
|
|
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;
|
2021-02-28 00:08:47 -05:00
|
|
|
}
|
|
|
|
|
|
2024-01-22 15:22:26 +01:00
|
|
|
static void
|
|
|
|
|
handle_output_manager_test(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct server *server = wl_container_of(listener, server, output_manager_test);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-25 20:45:39 +01:00
|
|
|
static void
|
|
|
|
|
handle_output_manager_apply(struct wl_listener *listener, void *data)
|
2021-02-27 23:15:02 -05:00
|
|
|
{
|
2021-09-21 22:05:56 +01:00
|
|
|
struct server *server =
|
|
|
|
|
wl_container_of(listener, server, output_manager_apply);
|
2021-02-27 23:15:02 -05:00
|
|
|
struct wlr_output_configuration_v1 *config = data;
|
2021-02-28 00:08:47 -05:00
|
|
|
|
|
|
|
|
bool config_is_good = verify_output_config_v1(config);
|
|
|
|
|
|
2024-02-22 21:22:28 +01:00
|
|
|
if (config_is_good && output_config_apply(server, config)) {
|
2021-02-28 00:08:47 -05:00
|
|
|
wlr_output_configuration_v1_send_succeeded(config);
|
|
|
|
|
} else {
|
|
|
|
|
wlr_output_configuration_v1_send_failed(config);
|
|
|
|
|
}
|
2021-02-27 23:15:02 -05:00
|
|
|
wlr_output_configuration_v1_destroy(config);
|
2021-12-17 10:20:57 -05:00
|
|
|
struct output *output;
|
|
|
|
|
wl_list_for_each(output, &server->outputs, link) {
|
|
|
|
|
wlr_xcursor_manager_load(server->seat.xcursor_manager,
|
|
|
|
|
output->wlr_output->scale);
|
|
|
|
|
}
|
2022-12-20 20:15:23 +00:00
|
|
|
|
|
|
|
|
/* Re-set cursor image in case scale changed */
|
|
|
|
|
cursor_update_focus(server);
|
2023-01-26 01:21:43 +01:00
|
|
|
cursor_update_image(&server->seat);
|
2021-02-27 23:15:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2024-01-04 19:42:26 +02:00
|
|
|
* Take the way outputs are currently configured/laid out and turn that into
|
2021-02-27 23:15:02 -05:00
|
|
|
* a struct that we send to clients via the wlr_output_configuration v1
|
|
|
|
|
* interface
|
|
|
|
|
*/
|
2021-08-25 20:45:39 +01:00
|
|
|
static struct
|
|
|
|
|
wlr_output_configuration_v1 *create_output_config(struct server *server)
|
2021-02-27 23:15:02 -05:00
|
|
|
{
|
2021-09-21 22:05:56 +01:00
|
|
|
struct wlr_output_configuration_v1 *config =
|
|
|
|
|
wlr_output_configuration_v1_create();
|
2021-08-25 20:45:39 +01:00
|
|
|
if (!config) {
|
2021-07-23 21:15:55 +01:00
|
|
|
wlr_log(WLR_ERROR, "wlr_output_configuration_v1_create()");
|
2021-02-27 23:15:02 -05:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct output *output;
|
|
|
|
|
wl_list_for_each(output, &server->outputs, link) {
|
2021-03-06 18:30:53 +00:00
|
|
|
struct wlr_output_configuration_head_v1 *head =
|
|
|
|
|
wlr_output_configuration_head_v1_create(config,
|
|
|
|
|
output->wlr_output);
|
2021-08-25 20:45:39 +01:00
|
|
|
if (!head) {
|
2021-09-21 22:05:56 +01:00
|
|
|
wlr_log(WLR_ERROR,
|
|
|
|
|
"wlr_output_configuration_head_v1_create()");
|
2021-02-27 23:15:02 -05:00
|
|
|
wlr_output_configuration_v1_destroy(config);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2022-02-14 20:20:16 +00:00
|
|
|
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;
|
2021-02-27 23:15:02 -05:00
|
|
|
} else {
|
2021-07-23 21:15:55 +01:00
|
|
|
wlr_log(WLR_ERROR, "failed to get output layout box");
|
2021-02-27 23:15:02 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return config;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-25 20:45:39 +01:00
|
|
|
static void
|
2022-07-09 00:42:04 +02:00
|
|
|
do_output_layout_change(struct server *server)
|
2021-08-25 20:45:39 +01:00
|
|
|
{
|
2022-09-20 01:17:22 -04:00
|
|
|
if (!server->pending_output_layout_change) {
|
2021-09-21 22:05:56 +01:00
|
|
|
struct wlr_output_configuration_v1 *config =
|
|
|
|
|
create_output_config(server);
|
2021-08-25 20:45:39 +01:00
|
|
|
if (config) {
|
2021-09-21 22:05:56 +01:00
|
|
|
wlr_output_manager_v1_set_configuration(
|
|
|
|
|
server->output_manager, config);
|
2021-02-27 23:15:02 -05:00
|
|
|
} else {
|
2021-09-21 22:05:56 +01:00
|
|
|
wlr_log(WLR_ERROR,
|
|
|
|
|
"wlr_output_manager_v1_set_configuration()");
|
2021-02-27 23:15:02 -05:00
|
|
|
}
|
2021-12-31 17:30:55 -05:00
|
|
|
output_update_for_layout_change(server);
|
2021-02-27 23:15:02 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-09 00:42:04 +02:00
|
|
|
static void
|
|
|
|
|
handle_output_layout_change(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct server *server =
|
|
|
|
|
wl_container_of(listener, server, output_layout_change);
|
|
|
|
|
do_output_layout_change(server);
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-01 17:07:05 +00:00
|
|
|
static void
|
|
|
|
|
handle_gamma_control_set_gamma(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct server *server = wl_container_of(listener, server, gamma_control_set_gamma);
|
|
|
|
|
const struct wlr_gamma_control_manager_v1_set_gamma_event *event = data;
|
|
|
|
|
|
|
|
|
|
struct output *output = event->output->data;
|
2023-12-02 15:05:26 +01:00
|
|
|
if (!output_is_usable(output)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-12-01 17:07:05 +00:00
|
|
|
output->gamma_lut_changed = true;
|
|
|
|
|
wlr_output_schedule_frame(output->wlr_output);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-25 20:45:39 +01:00
|
|
|
void
|
|
|
|
|
output_manager_init(struct server *server)
|
2021-02-27 23:15:02 -05:00
|
|
|
{
|
|
|
|
|
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,
|
2021-03-06 18:30:53 +00:00
|
|
|
&server->output_layout_change);
|
2021-02-27 23:15:02 -05:00
|
|
|
|
|
|
|
|
server->output_manager_apply.notify = handle_output_manager_apply;
|
|
|
|
|
wl_signal_add(&server->output_manager->events.apply,
|
2021-03-06 18:30:53 +00:00
|
|
|
&server->output_manager_apply);
|
2023-12-01 17:07:05 +00:00
|
|
|
|
2024-01-22 15:22:26 +01:00
|
|
|
server->output_manager_test.notify = handle_output_manager_test;
|
|
|
|
|
wl_signal_add(&server->output_manager->events.test,
|
|
|
|
|
&server->output_manager_test);
|
|
|
|
|
|
2023-12-01 17:07:05 +00:00
|
|
|
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);
|
2020-05-29 22:18:03 +01:00
|
|
|
}
|
2021-07-12 16:44:30 +01:00
|
|
|
|
|
|
|
|
struct output *
|
|
|
|
|
output_from_wlr_output(struct server *server, struct wlr_output *wlr_output)
|
|
|
|
|
{
|
|
|
|
|
struct output *output;
|
2022-11-03 19:58:21 +00:00
|
|
|
wl_list_for_each(output, &server->outputs, link) {
|
2021-07-12 16:44:30 +01:00
|
|
|
if (output->wlr_output == wlr_output) {
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2021-07-13 21:50:02 +01:00
|
|
|
|
2023-03-05 17:16:23 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-03 15:22:54 -05:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-16 12:24:27 -05:00
|
|
|
bool
|
|
|
|
|
output_is_usable(struct output *output)
|
|
|
|
|
{
|
|
|
|
|
/* output_is_usable(NULL) is safe and returns false */
|
|
|
|
|
return output && output->wlr_output->enabled && !output->leased;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-16 12:43:59 -05:00
|
|
|
/* returns true if usable area changed */
|
|
|
|
|
static bool
|
|
|
|
|
update_usable_area(struct output *output)
|
|
|
|
|
{
|
|
|
|
|
struct wlr_box old = output->usable_area;
|
|
|
|
|
layers_arrange(output);
|
|
|
|
|
|
2022-11-22 05:11:50 -05:00
|
|
|
#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
|
2022-11-16 12:43:59 -05:00
|
|
|
return !wlr_box_equal(&old, &output->usable_area);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
output_update_usable_area(struct output *output)
|
|
|
|
|
{
|
|
|
|
|
if (update_usable_area(output)) {
|
2023-01-01 18:12:20 +01:00
|
|
|
regions_update_geometry(output);
|
2023-10-24 13:49:15 -04:00
|
|
|
#if HAVE_XWAYLAND
|
|
|
|
|
xwayland_update_workarea(output->server);
|
|
|
|
|
#endif
|
2022-11-16 12:43:59 -05:00
|
|
|
desktop_arrange_all_views(output->server);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2023-01-06 19:54:51 +01:00
|
|
|
output_update_all_usable_areas(struct server *server, bool layout_changed)
|
2022-11-16 12:43:59 -05:00
|
|
|
{
|
|
|
|
|
bool usable_area_changed = false;
|
|
|
|
|
struct output *output;
|
|
|
|
|
|
|
|
|
|
wl_list_for_each(output, &server->outputs, link) {
|
2022-07-06 16:57:25 +02:00
|
|
|
if (update_usable_area(output)) {
|
|
|
|
|
usable_area_changed = true;
|
2023-01-01 18:12:20 +01:00
|
|
|
regions_update_geometry(output);
|
2022-07-06 16:57:25 +02:00
|
|
|
} else if (layout_changed) {
|
2023-01-01 18:12:20 +01:00
|
|
|
regions_update_geometry(output);
|
2022-07-06 16:57:25 +02:00
|
|
|
}
|
2022-11-16 12:43:59 -05:00
|
|
|
}
|
2023-01-06 19:54:51 +01:00
|
|
|
if (usable_area_changed || layout_changed) {
|
2023-10-24 13:49:15 -04:00
|
|
|
#if HAVE_XWAYLAND
|
|
|
|
|
xwayland_update_workarea(server);
|
|
|
|
|
#endif
|
2022-11-16 12:43:59 -05:00
|
|
|
desktop_arrange_all_views(server);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-21 22:04:54 +01:00
|
|
|
struct wlr_box
|
|
|
|
|
output_usable_area_in_layout_coords(struct output *output)
|
|
|
|
|
{
|
2022-12-26 23:09:33 +01:00
|
|
|
if (!output) {
|
|
|
|
|
return (struct wlr_box){0};
|
|
|
|
|
}
|
2021-07-21 22:04:54 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-05 23:53:01 +02:00
|
|
|
struct wlr_box
|
|
|
|
|
output_usable_area_scaled(struct output *output)
|
|
|
|
|
{
|
|
|
|
|
if (!output) {
|
|
|
|
|
return (struct wlr_box){0};
|
|
|
|
|
}
|
|
|
|
|
struct wlr_box usable = output_usable_area_in_layout_coords(output);
|
|
|
|
|
if (usable.height == output->wlr_output->height
|
|
|
|
|
&& output->wlr_output->scale != 1) {
|
|
|
|
|
usable.height /= output->wlr_output->scale;
|
|
|
|
|
}
|
|
|
|
|
if (usable.width == output->wlr_output->width
|
|
|
|
|
&& output->wlr_output->scale != 1) {
|
|
|
|
|
usable.width /= output->wlr_output->scale;
|
|
|
|
|
}
|
|
|
|
|
return usable;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-06 04:46:11 +00:00
|
|
|
void
|
|
|
|
|
handle_output_power_manager_set_mode(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
2023-10-11 00:08:52 -04:00
|
|
|
struct server *server = wl_container_of(listener, server,
|
|
|
|
|
output_power_manager_set_mode);
|
2022-03-06 04:46:11 +00:00
|
|
|
struct wlr_output_power_v1_set_mode_event *event = data;
|
|
|
|
|
|
|
|
|
|
switch (event->mode) {
|
|
|
|
|
case ZWLR_OUTPUT_POWER_V1_MODE_OFF:
|
|
|
|
|
wlr_output_enable(event->output, false);
|
|
|
|
|
wlr_output_commit(event->output);
|
|
|
|
|
break;
|
|
|
|
|
case ZWLR_OUTPUT_POWER_V1_MODE_ON:
|
|
|
|
|
wlr_output_enable(event->output, true);
|
2022-03-15 21:30:25 +00:00
|
|
|
if (!wlr_output_test(event->output)) {
|
|
|
|
|
wlr_output_rollback(event->output);
|
|
|
|
|
}
|
2022-03-06 04:46:11 +00:00
|
|
|
wlr_output_commit(event->output);
|
2023-10-11 00:08:52 -04:00
|
|
|
/*
|
|
|
|
|
* Re-set the cursor image so that the cursor
|
|
|
|
|
* isn't invisible on the newly enabled output.
|
|
|
|
|
*/
|
|
|
|
|
cursor_update_image(&server->seat);
|
2022-03-06 04:46:11 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-09 12:01:11 +03:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
output_add_virtual(struct server *server, const char *output_name)
|
|
|
|
|
{
|
|
|
|
|
if (output_name) {
|
|
|
|
|
/* Prevent creating outputs with the same name */
|
|
|
|
|
struct output *output;
|
|
|
|
|
wl_list_for_each(output, &server->outputs, link) {
|
|
|
|
|
if (wlr_output_is_headless(output->wlr_output) &&
|
|
|
|
|
!strcmp(output->wlr_output->name, output_name)) {
|
|
|
|
|
wlr_log(WLR_DEBUG,
|
|
|
|
|
"refusing to create virtual output with duplicate name");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-11 12:33:06 +03:00
|
|
|
snprintf(server->headless.pending_output_name,
|
|
|
|
|
sizeof(server->headless.pending_output_name), "%s", output_name);
|
2023-12-09 12:01:11 +03:00
|
|
|
} else {
|
|
|
|
|
server->headless.pending_output_name[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
* Setting it to (0, 0) here disallows changing resolution from tools like
|
|
|
|
|
* wlr-randr (returns error)
|
|
|
|
|
*/
|
|
|
|
|
wlr_headless_add_output(server->headless.backend, 1920, 1080);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
output_remove_virtual(struct server *server, const char *output_name)
|
|
|
|
|
{
|
|
|
|
|
struct output *output;
|
|
|
|
|
wl_list_for_each(output, &server->outputs, link) {
|
|
|
|
|
if (wlr_output_is_headless(output->wlr_output)) {
|
|
|
|
|
if (output_name) {
|
|
|
|
|
/*
|
|
|
|
|
* Given virtual output name, find and destroy virtual output by
|
|
|
|
|
* that name.
|
|
|
|
|
*/
|
|
|
|
|
if (!strcmp(output->wlr_output->name, output_name)) {
|
|
|
|
|
wlr_output_destroy(output->wlr_output);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/*
|
|
|
|
|
* When virtual output name was no supplied by user, simply
|
|
|
|
|
* destroy the first virtual output found.
|
|
|
|
|
*/
|
|
|
|
|
wlr_output_destroy(output->wlr_output);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-30 17:35:41 +02:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
output_enable_adaptive_sync(struct wlr_output *output, bool enabled)
|
|
|
|
|
{
|
|
|
|
|
wlr_output_enable_adaptive_sync(output, enabled);
|
|
|
|
|
if (!wlr_output_test(output)) {
|
|
|
|
|
wlr_output_enable_adaptive_sync(output, false);
|
|
|
|
|
wlr_log(WLR_DEBUG,
|
|
|
|
|
"failed to enable adaptive sync for output %s", output->name);
|
|
|
|
|
} else {
|
|
|
|
|
wlr_log(WLR_INFO, "adaptive sync %sabled for output %s",
|
|
|
|
|
enabled ? "en" : "dis", output->name);
|
|
|
|
|
}
|
|
|
|
|
}
|