labwc/src/output.c
2021-09-24 21:45:48 +01:00

1105 lines
31 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 "config.h"
#include <assert.h>
#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_output_damage.h>
#include <wlr/util/region.h>
#include <wlr/util/log.h>
#include "labwc.h"
#include "layers.h"
#include "menu/menu.h"
#include "ssd.h"
#include "theme.h"
#define DEBUG (0)
typedef void (*surface_iterator_func_t)(struct output *output,
struct wlr_surface *surface, struct wlr_box *box,
void *user_data);
struct surface_iterator_data {
surface_iterator_func_t user_iterator;
void *user_data;
struct output *output;
double ox, oy;
};
static bool
intersects_with_output(struct output *output,
struct wlr_output_layout *output_layout,
struct wlr_box *surface_box)
{
/* The resolution can change if outputs are rotated */
struct wlr_box output_box = {0};
wlr_output_effective_resolution(output->wlr_output, &output_box.width,
&output_box.height);
struct wlr_box intersection;
return wlr_box_intersection(&intersection, &output_box, surface_box);
}
static void
output_for_each_surface_iterator(struct wlr_surface *surface, int sx, int sy,
void *user_data)
{
struct surface_iterator_data *data = user_data;
struct output *output = data->output;
if (!wlr_surface_has_buffer(surface)) {
return;
}
struct wlr_box surface_box = {
.x = data->ox + sx + surface->sx,
.y = data->oy + sy + surface->sy,
.width = surface->current.width,
.height = surface->current.height,
};
if (!intersects_with_output(output, output->server->output_layout,
&surface_box)) {
return;
}
data->user_iterator(data->output, surface, &surface_box,
data->user_data);
}
void
output_surface_for_each_surface(struct output *output,
struct wlr_surface *surface, double ox, double oy,
surface_iterator_func_t iterator, void *user_data)
{
struct surface_iterator_data data = {
.user_iterator = iterator,
.user_data = user_data,
.output = output,
.ox = ox,
.oy = oy,
};
assert(surface);
wlr_surface_for_each_surface(surface,
output_for_each_surface_iterator, &data);
}
struct render_data {
pixman_region32_t *damage;
};
int
scale_length(int length, int offset, float scale)
{
return round((offset + length) * scale) - round(offset * scale);
}
void
scale_box(struct wlr_box *box, float scale)
{
box->width = scale_length(box->width, box->x, scale);
box->height = scale_length(box->height, box->y, scale);
box->x = round(box->x * scale);
box->y = round(box->y * scale);
}
static void
scissor_output(struct wlr_output *output, pixman_box32_t *rect)
{
struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
struct wlr_box box = {
.x = rect->x1,
.y = rect->y1,
.width = rect->x2 - rect->x1,
.height = rect->y2 - rect->y1,
};
int output_width, output_height;
wlr_output_transformed_resolution(output, &output_width, &output_height);
enum wl_output_transform transform =
wlr_output_transform_invert(output->transform);
wlr_box_transform(&box, &box, transform, output_width, output_height);
wlr_renderer_scissor(renderer, &box);
}
static void
render_texture(struct wlr_output *wlr_output, pixman_region32_t *output_damage,
struct wlr_texture *texture, const struct wlr_box *box,
const float matrix[static 9])
{
struct wlr_renderer *renderer =
wlr_backend_get_renderer(wlr_output->backend);
pixman_region32_t damage;
pixman_region32_init(&damage);
pixman_region32_union_rect(&damage, &damage, box->x, box->y,
box->width, box->height);
pixman_region32_intersect(&damage, &damage, output_damage);
if (!pixman_region32_not_empty(&damage)) {
goto damage_finish;
}
int nrects;
pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
for (int i = 0; i < nrects; i++) {
scissor_output(wlr_output, &rects[i]);
wlr_render_texture_with_matrix(renderer, texture, matrix, 1.0f);
}
damage_finish:
pixman_region32_fini(&damage);
}
static void
render_surface_iterator(struct output *output, struct wlr_surface *surface,
struct wlr_box *box, void *user_data)
{
struct render_data *data = user_data;
struct wlr_output *wlr_output = output->wlr_output;
pixman_region32_t *output_damage = data->damage;
struct wlr_texture *texture = wlr_surface_get_texture(surface);
if (!texture) {
wlr_log(WLR_DEBUG, "Cannot obtain surface texture");
return;
}
scale_box(box, wlr_output->scale);
float matrix[9];
enum wl_output_transform transform =
wlr_output_transform_invert(surface->current.transform);
wlr_matrix_project_box(matrix, box, transform, 0.0f,
wlr_output->transform_matrix);
render_texture(wlr_output, output_damage, texture, box, matrix);
}
#if HAVE_XWAYLAND
void
output_unmanaged_for_each_surface(struct output *output,
struct wl_list *unmanaged, surface_iterator_func_t iterator,
void *user_data)
{
struct xwayland_unmanaged *unmanaged_surface;
wl_list_for_each(unmanaged_surface, unmanaged, link) {
struct wlr_xwayland_surface *xsurface =
unmanaged_surface->xwayland_surface;
double ox = unmanaged_surface->lx, oy = unmanaged_surface->ly;
wlr_output_layout_output_coords(
output->server->output_layout, output->wlr_output, &ox, &oy);
output_surface_for_each_surface(output, xsurface->surface, ox, oy,
iterator, user_data);
}
}
static void
render_unmanaged(struct output *output, pixman_region32_t *damage,
struct wl_list *unmanaged)
{
struct render_data data = {
.damage = damage,
};
output_unmanaged_for_each_surface(output, unmanaged,
render_surface_iterator, &data);
}
#endif
static void
output_view_for_each_surface(struct output *output, struct view *view,
surface_iterator_func_t iterator, void *user_data)
{
struct surface_iterator_data data = {
.user_iterator = iterator,
.user_data = user_data,
.output = output,
.ox = view->x,
.oy = view->y,
};
wlr_output_layout_output_coords(output->server->output_layout,
output->wlr_output, &data.ox, &data.oy);
view_for_each_surface(view, output_for_each_surface_iterator, &data);
}
void
output_view_for_each_popup_surface(struct output *output, struct view *view,
surface_iterator_func_t iterator, void *user_data)
{
struct surface_iterator_data data = {
.user_iterator = iterator,
.user_data = user_data,
.output = output,
.ox = view->x,
.oy = view->y,
};
wlr_output_layout_output_coords(output->server->output_layout,
output->wlr_output, &data.ox, &data.oy);
view_for_each_popup_surface(view, output_for_each_surface_iterator, &data);
}
/* for sending frame done */
void
output_layer_for_each_surface(struct output *output,
struct wl_list *layer_surfaces, surface_iterator_func_t iterator,
void *user_data)
{
struct lab_layer_surface *layer_surface;
wl_list_for_each(layer_surface, layer_surfaces, link) {
struct wlr_layer_surface_v1 *wlr_layer_surface_v1 =
layer_surface->layer_surface;
output_surface_for_each_surface(output,
wlr_layer_surface_v1->surface, layer_surface->geo.x,
layer_surface->geo.y, iterator, user_data);
/* TODO: handle popups */
}
}
static void
output_for_each_surface(struct output *output, surface_iterator_func_t iterator,
void *user_data)
{
output_layer_for_each_surface(output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
iterator, user_data);
output_layer_for_each_surface(output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
iterator, user_data);
struct view *view;
wl_list_for_each_reverse(view, &output->server->views, link) {
if (!view->mapped) {
continue;
}
output_view_for_each_surface(output, view, iterator, user_data);
}
#if HAVE_XWAYLAND
output_unmanaged_for_each_surface(output,
&output->server->unmanaged_surfaces, iterator, user_data);
#endif
output_layer_for_each_surface(output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
iterator, user_data);
output_layer_for_each_surface(output,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
iterator, user_data);
}
struct send_frame_done_data {
struct timespec when;
};
static void
send_frame_done_iterator(struct output *output, struct wlr_surface *surface,
struct wlr_box *box, void *user_data)
{
struct send_frame_done_data *data = user_data;
wlr_surface_send_frame_done(surface, &data->when);
}
static void
send_frame_done(struct output *output, struct send_frame_done_data *data)
{
output_for_each_surface(output, send_frame_done_iterator, data);
}
void
render_rect(struct output *output, pixman_region32_t *output_damage,
const struct wlr_box *_box, float color[static 4])
{
struct wlr_output *wlr_output = output->wlr_output;
struct wlr_renderer *renderer =
wlr_backend_get_renderer(wlr_output->backend);
struct wlr_box box;
memcpy(&box, _box, sizeof(struct wlr_box));
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;
scale_box(&box, wlr_output->scale);
pixman_region32_t damage;
pixman_region32_init(&damage);
pixman_region32_union_rect(&damage, &damage, box.x, box.y,
box.width, box.height);
pixman_region32_intersect(&damage, &damage, output_damage);
bool damaged = pixman_region32_not_empty(&damage);
if (!damaged) {
goto damage_finish;
}
int nrects;
pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
for (int i = 0; i < nrects; ++i) {
scissor_output(wlr_output, &rects[i]);
wlr_render_rect(renderer, &box, color,
wlr_output->transform_matrix);
}
damage_finish:
pixman_region32_fini(&damage);
}
void
render_rect_unfilled(struct output *output, pixman_region32_t *output_damage,
const struct wlr_box *_box, float color[static 4])
{
struct wlr_box box;
memcpy(&box, _box, sizeof(struct wlr_box));
box.height = 1;
render_rect(output, output_damage, &box, color);
box.y += _box->height - 1;
render_rect(output, output_damage, &box, color);
memcpy(&box, _box, sizeof(struct wlr_box));
box.width = 1;
render_rect(output, output_damage, &box, color);
box.x += _box->width - 1;
render_rect(output, output_damage, &box, color);
}
static void
shrink(struct wlr_box *box, int size)
{
box->x += size;
box->y += size;
box->width -= 2 * size;
box->height -= 2 * size;
}
static void
render_cycle_box(struct output *output, pixman_region32_t *output_damage,
struct view *view)
{
struct wlr_box box = {
.x = view->x,
.y = view->y,
.width = view->w,
.height = view->h,
};
box.x -= view->margin.left;
box.y -= view->margin.top;
box.width += view->margin.left + view->margin.right;
box.height += view->margin.top + view->margin.bottom;
box.x += view->padding.left;
box.y += view->padding.top;
float white[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
render_rect_unfilled(output, output_damage, &box, white);
for (int i = 0; i < 4; i++) {
shrink(&box, 1);
render_rect_unfilled(output, output_damage, &box, black);
}
shrink(&box, 1);
render_rect_unfilled(output, output_damage, &box, white);
}
static void
render_icon(struct output *output, pixman_region32_t *output_damage,
struct wlr_box *box, struct wlr_texture *texture)
{
/* centre-align icon if smaller than designated box */
struct wlr_box button = {
.width = texture->width,
.height = texture->height,
};
if (box->width > button.width) {
button.x = box->x + (box->width - button.width) / 2;
} else {
button.x = box->x;
button.width = box->width;
}
if (box->height > button.height) {
button.y = box->y + (box->height - button.height) / 2;
} else {
button.y = box->y;
button.height = box->height;
}
double ox = 0, oy = 0;
wlr_output_layout_output_coords(output->server->output_layout,
output->wlr_output, &ox, &oy);
button.x += ox;
button.y += oy;
scale_box(&button, output->wlr_output->scale);
float matrix[9];
wlr_matrix_project_box(matrix, &button, WL_OUTPUT_TRANSFORM_NORMAL, 0,
output->wlr_output->transform_matrix);
render_texture(output->wlr_output, output_damage, texture, &button,
matrix);
}
void
render_texture_helper(struct output *output, pixman_region32_t *output_damage,
struct wlr_box *_box, struct wlr_texture *texture)
{
if (!texture) {
return;
}
struct wlr_box box;
memcpy(&box, _box, sizeof(struct wlr_box));
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;
scale_box(&box, output->wlr_output->scale);
float matrix[9];
wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
output->wlr_output->transform_matrix);
render_texture(output->wlr_output, output_damage, texture, &box,
matrix);
}
static void
render_osd(struct output *output, pixman_region32_t *damage,
struct server *server)
{
if (!server->osd) {
return;
}
/* show on screen display (osd) on all outputs */
struct output *o;
wl_list_for_each(o, &server->outputs, link) {
struct wlr_box usable = output_usable_area_in_layout_coords(o);
struct wlr_box box = {
.x = usable.x + (usable.width - server->osd->width) / 2,
.y = usable.y + (usable.height - server->osd->height) / 2,
.width = server->osd->width,
.height = server->osd->height,
};
render_texture_helper(output, damage, &box, server->osd);
}
}
static bool
isbutton(enum ssd_part_type type)
{
return type == LAB_SSD_BUTTON_CLOSE ||
type == LAB_SSD_BUTTON_MAXIMIZE ||
type == LAB_SSD_BUTTON_ICONIFY;
}
static void
render_deco(struct view *view, struct output *output,
pixman_region32_t *output_damage)
{
if (!view->ssd.enabled || view->fullscreen) {
return;
}
struct wlr_seat *seat = view->server->seat.seat;
bool focused = view->surface == seat->keyboard_state.focused_surface;
/* render texture or rectangle */
struct ssd_part *part;
wl_list_for_each_reverse(part, &view->ssd.parts, link) {
if (part->texture.active && *(part->texture.active)) {
struct wlr_texture *texture = focused ?
*(part->texture.active) :
*(part->texture.inactive);
render_texture_helper(output, output_damage, &part->box,
texture);
} else if (part->color.active && part->color.inactive) {
float *color = focused ?
part->color.active :
part->color.inactive;
render_rect(output, output_damage, &part->box, color);
}
}
/* button background */
struct wlr_cursor *cur = view->server->seat.cursor;
enum ssd_part_type type = ssd_at(view, cur->x, cur->y);
struct wlr_box box = ssd_visible_box(view, type);
if (isbutton(type) &&
wlr_box_contains_point(&box, cur->x, cur->y)) {
float *color = (float[4]) { 0.5, 0.5, 0.5, 0.5 };
render_rect(output, output_damage, &box, color);
}
/* buttons */
struct theme *theme = view->server->theme;
if (view->surface == seat->keyboard_state.focused_surface) {
box = ssd_visible_box(view, LAB_SSD_BUTTON_CLOSE);
render_icon(output, output_damage, &box,
theme->xbm_close_active_unpressed);
box = ssd_visible_box(view, LAB_SSD_BUTTON_MAXIMIZE);
render_icon(output, output_damage, &box,
theme->xbm_maximize_active_unpressed);
box = ssd_visible_box(view, LAB_SSD_BUTTON_ICONIFY);
render_icon(output, output_damage, &box,
theme->xbm_iconify_active_unpressed);
} else {
box = ssd_visible_box(view, LAB_SSD_BUTTON_CLOSE);
render_icon(output, output_damage, &box,
theme->xbm_close_inactive_unpressed);
box = ssd_visible_box(view, LAB_SSD_BUTTON_MAXIMIZE);
render_icon(output, output_damage, &box,
theme->xbm_maximize_inactive_unpressed);
box = ssd_visible_box(view, LAB_SSD_BUTTON_ICONIFY);
render_icon(output, output_damage, &box,
theme->xbm_iconify_inactive_unpressed);
}
}
static void
render_rootmenu(struct output *output, pixman_region32_t *output_damage)
{
struct server *server = output->server;
struct theme *theme = server->theme;
float matrix[9];
struct wlr_output_layout *output_layout = server->output_layout;
double ox = 0, oy = 0;
wlr_output_layout_output_coords(output_layout, output->wlr_output,
&ox, &oy);
/* background */
render_rect(output, output_damage, &server->rootmenu->box,
theme->menu_items_bg_color);
/* items */
struct menuitem *menuitem;
wl_list_for_each (menuitem, &server->rootmenu->menuitems, link) {
struct wlr_box box = {
.x = menuitem->box.x + menuitem->texture.offset_x + ox,
.y = menuitem->box.y + menuitem->texture.offset_y + oy,
.width = menuitem->texture.active->width,
.height = menuitem->texture.active->height,
};
scale_box(&box, output->wlr_output->scale);
wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL,
0, output->wlr_output->transform_matrix);
if (menuitem->selected) {
render_rect(output, output_damage, &menuitem->box,
theme->menu_items_active_bg_color);
render_texture(output->wlr_output, output_damage,
menuitem->texture.active, &box, matrix);
} else {
render_texture(output->wlr_output, output_damage,
menuitem->texture.inactive, &box, matrix);
}
}
}
void
output_layer_for_each_surface_toplevel(struct output *output,
struct wl_list *layer_surfaces, surface_iterator_func_t iterator,
void *user_data)
{
struct lab_layer_surface *layer_surface;
wl_list_for_each(layer_surface, layer_surfaces, link) {
struct wlr_layer_surface_v1 *wlr_layer_surface_v1 =
layer_surface->layer_surface;
output_surface_for_each_surface(output,
wlr_layer_surface_v1->surface, layer_surface->geo.x,
layer_surface->geo.y, iterator, user_data);
}
}
static void
render_layer_toplevel(struct output *output, pixman_region32_t *damage,
struct wl_list *layer_surfaces)
{
struct render_data data = {
.damage = damage,
};
output_layer_for_each_surface_toplevel(output, layer_surfaces,
render_surface_iterator, &data);
}
static void
render_view_toplevels(struct view *view, struct output *output,
pixman_region32_t *damage)
{
struct render_data data = {
.damage = damage,
};
double ox = view->x;
double oy = view->y;
wlr_output_layout_output_coords(output->server->output_layout,
output->wlr_output, &ox, &oy);
output_surface_for_each_surface(output, view->surface, ox, oy,
render_surface_iterator, &data);
}
static void
render_view_popups(struct view *view, struct output *output,
pixman_region32_t *damage)
{
struct render_data data = {
.damage = damage,
};
output_view_for_each_popup_surface(output, view,
render_surface_iterator, &data);
}
void
output_render(struct output *output, pixman_region32_t *damage)
{
struct server *server = output->server;
struct wlr_output *wlr_output = output->wlr_output;
struct wlr_renderer *renderer =
wlr_backend_get_renderer(wlr_output->backend);
if (!renderer) {
wlr_log(WLR_DEBUG, "no renderer");
return;
}
/* Calls glViewport and some other GL sanity checks */
wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
if (!pixman_region32_not_empty(damage)) {
goto renderer_end;
}
#if DEBUG
wlr_renderer_clear(renderer, (float[]){0.2f, 0.0f, 0.0f, 1.0f});
#endif
float color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
int nrects;
pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects);
for (int i = 0; i < nrects; i++) {
scissor_output(wlr_output, &rects[i]);
wlr_renderer_clear(renderer, color);
}
render_layer_toplevel(output, damage,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]);
render_layer_toplevel(output, damage,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
struct view *view;
wl_list_for_each_reverse (view, &server->views, link) {
if (!view->mapped) {
continue;
}
render_deco(view, output, damage);
render_view_toplevels(view, output, damage);
render_view_popups(view, output, damage);
}
#if HAVE_XWAYLAND
render_unmanaged(output, damage, &output->server->unmanaged_surfaces);
#endif
/* 'alt-tab' border */
if (output->server->cycle_view) {
render_cycle_box(output, damage, output->server->cycle_view);
render_osd(output, damage, output->server);
}
render_layer_toplevel(output, damage,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
render_layer_toplevel(output, damage,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]);
if (output->server->input_mode == LAB_INPUT_STATE_MENU) {
render_rootmenu(output, damage);
}
renderer_end:
/* Just in case hardware cursors not supported by GPU */
wlr_output_render_software_cursors(wlr_output, damage);
wlr_renderer_scissor(renderer, NULL);
wlr_renderer_end(renderer);
int output_width, output_height;
wlr_output_transformed_resolution(wlr_output, &output_width,
&output_height);
pixman_region32_t frame_damage;
pixman_region32_init(&frame_damage);
enum wl_output_transform transform =
wlr_output_transform_invert(wlr_output->transform);
wlr_region_transform(&frame_damage, &output->damage->current,
transform, output_width, output_height);
#ifdef DEBUG
pixman_region32_union_rect(&frame_damage, &frame_damage, 0, 0,
output_width, output_height);
#endif
wlr_output_set_damage(wlr_output, &frame_damage);
pixman_region32_fini(&frame_damage);
if (!wlr_output_commit(wlr_output)) {
wlr_log(WLR_ERROR, "could not commit output");
}
}
static void
damage_surface_iterator(struct output *output, struct wlr_surface *surface,
struct wlr_box *box, void *user_data)
{
struct wlr_output *wlr_output = output->wlr_output;
bool whole = *(bool *) user_data;
scale_box(box, output->wlr_output->scale);
if (whole) {
wlr_output_damage_add_box(output->damage, box);
} else if (pixman_region32_not_empty(&surface->buffer_damage)) {
pixman_region32_t damage;
pixman_region32_init(&damage);
wlr_surface_get_effective_damage(surface, &damage);
wlr_region_scale(&damage, &damage, wlr_output->scale);
if (ceil(wlr_output->scale) > surface->current.scale) {
wlr_region_expand(&damage, &damage,
ceil(wlr_output->scale) - surface->current.scale);
}
pixman_region32_translate(&damage, box->x, box->y);
wlr_output_damage_add(output->damage, &damage);
pixman_region32_fini(&damage);
}
}
void
output_damage_surface(struct output *output, struct wlr_surface *surface,
double lx, double ly, bool whole)
{
if (!output->wlr_output->enabled) {
return;
}
double ox = lx, oy = ly;
wlr_output_layout_output_coords(output->server->output_layout,
output->wlr_output, &ox, &oy);
output_surface_for_each_surface(output, surface, ox, oy,
damage_surface_iterator, &whole);
}
static void
output_damage_frame_notify(struct wl_listener *listener, void *data)
{
struct output *output = wl_container_of(listener, output, damage_frame);
if (!output->wlr_output->enabled) {
return;
}
bool needs_frame;
pixman_region32_t damage;
pixman_region32_init(&damage);
if (!wlr_output_damage_attach_render(output->damage,
&needs_frame, &damage)) {
return;
}
if (needs_frame) {
output_render(output, &damage);
} else {
wlr_output_rollback(output->wlr_output);
}
pixman_region32_fini(&damage);
struct send_frame_done_data frame_data = {0};
clock_gettime(CLOCK_MONOTONIC, &frame_data.when);
send_frame_done(output, &frame_data);
}
static void
output_damage_destroy_notify(struct wl_listener *listener, void *data)
{
struct output *output = wl_container_of(listener, output, damage_destroy);
wl_list_remove(&output->damage_frame.link);
wl_list_remove(&output->damage_destroy.link);
}
static void
output_destroy_notify(struct wl_listener *listener, void *data)
{
struct output *output = wl_container_of(listener, output, destroy);
wl_list_remove(&output->link);
wl_list_remove(&output->destroy.link);
}
static void
new_output_notify(struct wl_listener *listener, void *data)
{
/*
* This event is rasied 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;
/* 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);
/*
* 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
*/
if (!wlr_output_test(wlr_output)) {
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;
}
}
}
wlr_output_commit(wlr_output);
struct output *output = calloc(1, sizeof(struct output));
output->wlr_output = wlr_output;
output->server = server;
output->damage = wlr_output_damage_create(wlr_output);
wlr_output_effective_resolution(wlr_output,
&output->usable_area.width, &output->usable_area.height);
wl_list_insert(&server->outputs, &output->link);
output->destroy.notify = output_destroy_notify;
wl_signal_add(&wlr_output->events.destroy, &output->destroy);
output->damage_frame.notify = output_damage_frame_notify;
wl_signal_add(&output->damage->events.frame, &output->damage_frame);
output->damage_destroy.notify = output_damage_destroy_notify;
wl_signal_add(&output->damage->events.destroy, &output->damage_destroy);
wl_list_init(&output->layers[0]);
wl_list_init(&output->layers[1]);
wl_list_init(&output->layers[2]);
wl_list_init(&output->layers[3]);
/*
* Arrange outputs from left-to-right in the order they appear.
* TODO: support configuration in run-time
*/
if (getenv("LABWC_ADAPTIVE_SYNC")) {
wlr_log(WLR_INFO, "enable adaptive sync on %s", wlr_output->name);
wlr_output_enable_adaptive_sync(wlr_output, true);
}
wlr_output_layout_add_auto(server->output_layout, wlr_output);
wlr_output_schedule_frame(wlr_output);
}
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);
}
/* 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_config_apply(struct server *server,
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;
bool need_to_add = head->state.enabled && !o->enabled;
if (need_to_add) {
wlr_output_layout_add_auto(server->output_layout, o);
}
bool need_to_remove = !head->state.enabled && o->enabled;
if (need_to_remove) {
wlr_output_layout_remove(server->output_layout, o);
}
wlr_output_enable(o, head->state.enabled);
if (head->state.enabled) {
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;
wlr_output_set_custom_mode(o, width,
height, refresh);
}
wlr_output_layout_move(server->output_layout, o,
head->state.x, head->state.y);
wlr_output_set_scale(o, head->state.scale);
wlr_output_set_transform(o, head->state.transform);
}
wlr_output_commit(o);
}
server->pending_output_config = NULL;
}
static bool
verify_output_config_v1(const struct wlr_output_configuration_v1 *config)
{
/* TODO implement */
return true;
}
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);
}
/*
* 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
*/
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);
if (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
handle_output_layout_change(struct wl_listener *listener, void *data)
{
struct server *server =
wl_container_of(listener, server, output_layout_change);
bool done_changing = server->pending_output_config == NULL;
if (done_changing) {
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()");
}
}
}
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);
}
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 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);
}