labwc/src/output.c
John Lindgren a837fefc80 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
2022-01-01 16:20:13 +00:00

1265 lines
35 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;
struct view *view;
double ox, oy;
int width, height;
};
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 = output->renderer;
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_fbox *src_box, const struct wlr_box *dst_box,
const float matrix[static 9])
{
struct wlr_renderer *renderer = wlr_output->renderer;
pixman_region32_t damage;
pixman_region32_init(&damage);
pixman_region32_union_rect(&damage, &damage, dst_box->x, dst_box->y,
dst_box->width, dst_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]);
const float alpha = 1.0f;
if (src_box != NULL) {
wlr_render_subtexture_with_matrix(renderer, texture, src_box, matrix, alpha);
} else {
wlr_render_texture_with_matrix(renderer, texture, matrix, alpha);
}
}
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;
}
struct wlr_fbox src_box;
wlr_surface_get_buffer_source_box(surface, &src_box);
struct wlr_box proj_box = *_box;
scale_box(&proj_box, wlr_output->scale);
float matrix[9];
enum wl_output_transform transform =
wlr_output_transform_invert(surface->current.transform);
wlr_matrix_project_box(matrix, &proj_box, transform, 0.0,
wlr_output->transform_matrix);
struct wlr_box dst_box = *_box;
scale_box(&dst_box, wlr_output->scale);
render_texture(wlr_output, output_damage, texture, &src_box, &dst_box, matrix);
}
void
output_drag_icon_for_each_surface(struct output *output, struct seat *seat,
surface_iterator_func_t iterator, void *user_data)
{
if (!seat->drag_icon || !seat->drag_icon->mapped) {
return;
}
double ox = seat->cursor->x, oy = seat->cursor->y;
wlr_output_layout_output_coords(output->server->output_layout,
output->wlr_output, &ox, &oy);
output_surface_for_each_surface(output, seat->drag_icon->surface,
ox, oy, iterator, user_data);
}
static void
render_drag_icon(struct output *output, pixman_region32_t *damage)
{
struct render_data data = {
.damage = damage,
};
output_drag_icon_for_each_surface(output, &output->server->seat, render_surface_iterator, &data);
}
#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;
struct wlr_surface *surface = wlr_layer_surface_v1->surface;
struct surface_iterator_data data = {
.user_iterator = iterator,
.user_data = user_data,
.output = output,
.view = NULL,
.ox = layer_surface->geo.x,
.oy = layer_surface->geo.y,
.width = surface->current.width,
.height = surface->current.height,
};
wlr_layer_surface_v1_for_each_surface(wlr_layer_surface_v1,
output_for_each_surface_iterator, &data);
}
}
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);
output_drag_icon_for_each_surface(output, &output->server->seat, 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_output->renderer;
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, NULL, &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, NULL, &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;
struct wlr_output_layout *layout = server->output_layout;
struct wlr_output_layout_output *ol_output;
wl_list_for_each(o, &server->outputs, link) {
ol_output = wlr_output_layout_get(layout, o->wlr_output);
if (!ol_output) {
continue;
}
struct wlr_box box = {
.x = ol_output->x + o->wlr_output->width
/ o->wlr_output->scale / 2,
.y = ol_output->y + o->wlr_output->height
/ o->wlr_output->scale / 2,
.width = server->osd->width,
.height = server->osd->height,
};
box.x -= server->osd->width / 2;
box.y -= server->osd->height / 2;
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_menu(struct output *output, pixman_region32_t *damage, struct menu *menu)
{
if (!menu->visible) {
return;
}
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, damage, &menu->box, theme->menu_items_bg_color);
/* items */
struct menuitem *menuitem;
wl_list_for_each (menuitem, &menu->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, damage, &menuitem->box,
theme->menu_items_active_bg_color);
render_texture(output->wlr_output, damage,
menuitem->texture.active, NULL, &box, matrix);
} else {
render_texture(output->wlr_output, damage,
menuitem->texture.inactive, NULL, &box, matrix);
}
/* render submenus */
if (menuitem->submenu) {
render_menu(output, damage, menuitem->submenu);
}
}
}
static void
output_layer_for_each_popup_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;
struct wlr_surface *surface = wlr_layer_surface_v1->surface;
struct surface_iterator_data data = {
.user_iterator = iterator,
.user_data = user_data,
.output = output,
.view = NULL,
.ox = layer_surface->geo.x,
.oy = layer_surface->geo.y,
.width = surface->current.width,
.height = surface->current.height,
};
wlr_layer_surface_v1_for_each_popup_surface(wlr_layer_surface_v1,
output_for_each_surface_iterator, &data);
}
}
static void
render_layer_popups(struct output *output, pixman_region32_t *damage,
struct wl_list *layer_surfaces)
{
struct render_data data = {
.damage = damage,
};
output_layer_for_each_popup_surface(output, layer_surfaces,
render_surface_iterator, &data);
}
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_output->renderer;
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_drag_icon(output, damage);
render_layer_toplevel(output, damage,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
render_layer_popups(output, damage,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]);
render_layer_popups(output, damage,
&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
render_layer_popups(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_menu(output, damage, server->rootmenu);
}
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;
/*
* 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);
/*
* 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;
wlr_output->data = 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 (rc.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);
}
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_update_for_layout_change(struct server *server)
{
/* Adjust window positions/sizes */
struct view *view;
wl_list_for_each(view, &server->views, link) {
view_adjust_for_layout_change(view);
}
/*
* "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);
/* Redraw everything */
damage_all_outputs(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;
output_update_for_layout_change(server);
}
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);
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
*/
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()");
}
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
if (output) {
arrange_layers(output);
}
}
output_update_for_layout_change(server);
}
}
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);
}