labwc/src/output.c

460 lines
13 KiB
C
Raw Normal View History

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
*/
#define _POSIX_C_SOURCE 200809L
2021-06-30 20:12:58 +01:00
#include <assert.h>
#include <wlr/types/wlr_buffer.h>
#include <wlr/types/wlr_drm_lease_v1.h>
#include <wlr/types/wlr_output.h>
2020-09-29 19:53:46 +01:00
#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_scene.h>
2021-01-09 22:51:20 +00:00
#include <wlr/util/region.h>
#include <wlr/util/log.h>
#include "common/mem.h"
2019-12-26 21:37:31 +00:00
#include "labwc.h"
#include "layers.h"
#include "node.h"
2019-12-26 21:37:31 +00:00
2021-01-09 22:51:20 +00:00
static void
output_frame_notify(struct wl_listener *listener, void *data)
2021-01-09 22:51:20 +00:00
{
struct output *output = wl_container_of(listener, output, frame);
2021-01-09 22:51:20 +00:00
if (!output->wlr_output->enabled) {
return;
}
wlr_scene_output_commit(output->scene_output);
2020-05-29 22:18:03 +01: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);
wl_list_remove(&output->link);
wl_list_remove(&output->frame.link);
2021-08-25 20:45:39 +01:00
wl_list_remove(&output->destroy.link);
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);
}
}
2022-08-17 13:36:33 +08:00
free(output);
2021-01-09 22:51:20 +00: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
{
/*
* 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;
/*
* 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.
*/
if (server->drm_lease_manager) {
wlr_drm_lease_v1_manager_offer_output(
server->drm_lease_manager, wlr_output);
}
/*
* 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;
}
2021-11-26 19:27:50 +00:00
/*
* Configures the output created by the backend to use our allocator
* and our renderer. Must be done once, before commiting the output
*/
if (!wlr_output_init_render(wlr_output, server->allocator,
server->renderer)) {
wlr_log(WLR_ERROR, "unable to init output renderer");
return;
}
wlr_log(WLR_DEBUG, "enable output");
wlr_output_enable(wlr_output, true);
/* The mode is a tuple of (width, height, refresh rate). */
wlr_log(WLR_DEBUG, "set preferred mode");
struct wlr_output_mode *preferred_mode =
wlr_output_preferred_mode(wlr_output);
wlr_output_set_mode(wlr_output, preferred_mode);
2020-05-29 22:18:03 +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
*/
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");
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
}
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;
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);
output->frame.notify = output_frame_notify;
wl_signal_add(&wlr_output->events.frame, &output->frame);
2021-01-09 22:51:20 +00:00
/*
* Create layer-trees (background, bottom, top and overlay) and
* a layer-popup-tree.
*/
int nr_layers = sizeof(output->layers) / sizeof(output->layers[0]);
for (int i = 0; i < nr_layers; i++) {
wl_list_init(&output->layers[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);
/*
* Set the z-positions to achieve the following order (from top to
* bottom):
* - 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);
wlr_scene_node_raise_to_top(&output->layer_tree[2]->node);
wlr_scene_node_raise_to_top(&output->layer_tree[3]->node);
wlr_scene_node_raise_to_top(&output->layer_popup_tree->node);
2021-10-22 20:23:09 +01:00
if (rc.adaptive_sync) {
wlr_log(WLR_INFO, "enable adaptive sync on %s",
wlr_output->name);
wlr_output_enable_adaptive_sync(wlr_output, true);
}
2020-05-29 22:18:03 +01:00
wlr_output_layout_add_auto(server->output_layout, wlr_output);
output->scene_output = wlr_scene_get_scene_output(server->scene, wlr_output);
assert(output->scene_output);
2020-09-29 19:53:46 +01:00
}
void
output_init(struct server *server)
{
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);
}
wlr_scene_attach_output_layout(server->scene, server->output_layout);
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);
output_manager_init(server);
}
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
static void
output_update_for_layout_change(struct server *server)
{
/* Adjust window positions/sizes */
desktop_arrange_all_views(server);
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
/*
* "Move" each wlr_output_cursor (in per-output coordinates) to
* align with the seat cursor. Set a default cursor image so
* that the cursor isn't invisible on new outputs.
*
* TODO: remember the most recent cursor image (see cursor.c)
* and set that rather than XCURSOR_DEFAULT
*/
wlr_cursor_move(server->seat.cursor, NULL, 0, 0);
wlr_xcursor_manager_set_cursor_image(server->seat.xcursor_manager,
XCURSOR_DEFAULT, server->seat.cursor);
}
static void do_output_layout_change(struct server *server);
2021-08-25 20:45:39 +01:00
static void
output_config_apply(struct server *server,
2021-03-06 18:30:53 +00:00
struct wlr_output_configuration_v1 *config)
{
server->pending_output_config = config;
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);
bool output_enabled = head->state.enabled && !output->leased;
bool need_to_add = output_enabled && !o->enabled;
bool need_to_remove = !output_enabled && o->enabled;
wlr_output_enable(o, output_enabled);
if (output_enabled) {
/* Output specifc actions only */
2021-08-25 20:45:39 +01:00
if (head->state.mode) {
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);
}
wlr_output_set_scale(o, head->state.scale);
wlr_output_set_transform(o, head->state.transform);
}
if (!wlr_output_commit(o)) {
wlr_log(WLR_ERROR, "Output config commit failed");
continue;
}
/* Only do Layout specific actions if the commit went trough */
if (need_to_add) {
wlr_output_layout_add_auto(server->output_layout, o);
output->scene_output = wlr_scene_get_scene_output(server->scene, o);
assert(output->scene_output);
}
if (output_enabled) {
wlr_output_layout_move(server->output_layout, o,
head->state.x, head->state.y);
}
if (need_to_remove) {
wlr_output_layout_remove(server->output_layout, o);
output->scene_output = NULL;
}
}
server->pending_output_config = NULL;
do_output_layout_change(server);
}
2021-08-25 20:45:39 +01:00
static bool
verify_output_config_v1(const struct wlr_output_configuration_v1 *config)
{
2021-08-25 20:45:39 +01:00
/* TODO implement */
return true;
}
2021-08-25 20:45:39 +01:00
static void
handle_output_manager_apply(struct wl_listener *listener, void *data)
{
2021-09-21 22:05:56 +01:00
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);
2021-08-25 20:45:39 +01:00
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);
}
}
/*
* Take the way outputs are currently configured/layed out and turn that into
* 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-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) {
wlr_log(WLR_ERROR, "wlr_output_configuration_v1_create()");
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()");
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;
}
2021-08-25 20:45:39 +01:00
static void
do_output_layout_change(struct server *server)
2021-08-25 20:45:39 +01:00
{
2022-04-04 20:53:36 +01:00
bool done_changing = !server->pending_output_config;
2021-08-25 20:45:39 +01:00
if (done_changing) {
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);
} else {
2021-09-21 22:05:56 +01:00
wlr_log(WLR_ERROR,
"wlr_output_manager_v1_set_configuration()");
}
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
if (output) {
layers_arrange(output);
}
}
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
output_update_for_layout_change(server);
}
}
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);
}
2021-08-25 20:45:39 +01:00
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,
2021-03-06 18:30:53 +00:00
&server->output_layout_change);
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);
2020-05-29 22:18:03 +01:00
}
struct output *
output_from_wlr_output(struct server *server, struct wlr_output *wlr_output)
{
struct output *output;
2021-08-25 20:45:39 +01:00
wl_list_for_each (output, &server->outputs, link) {
if (output->wlr_output == wlr_output) {
return output;
}
}
return NULL;
}
struct wlr_box
output_usable_area_in_layout_coords(struct output *output)
{
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;
}
struct wlr_box
output_usable_area_from_cursor_coords(struct server *server)
{
struct wlr_output *wlr_output;
wlr_output = wlr_output_layout_output_at(server->output_layout,
server->seat.cursor->x, server->seat.cursor->y);
struct output *output = output_from_wlr_output(server, wlr_output);
return output_usable_area_in_layout_coords(output);
}
2022-03-06 04:46:11 +00:00
void
handle_output_power_manager_set_mode(struct wl_listener *listener, void *data)
{
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);
if (!wlr_output_test(event->output)) {
wlr_output_rollback(event->output);
}
2022-03-06 04:46:11 +00:00
wlr_output_commit(event->output);
break;
}
}