2022-02-21 03:18:38 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
|
|
|
|
|
|
#include <assert.h>
|
2023-01-30 05:30:24 +01:00
|
|
|
#include <wlr/types/wlr_output.h>
|
2022-02-21 03:18:38 +01:00
|
|
|
#include <wlr/types/wlr_scene.h>
|
2023-01-30 05:30:24 +01:00
|
|
|
#include <wlr/util/log.h>
|
2023-01-31 03:35:13 +01:00
|
|
|
#include "common/scene-helpers.h"
|
2024-05-01 13:39:40 +01:00
|
|
|
#include "labwc.h"
|
2024-05-03 15:22:18 +01:00
|
|
|
#include "theme.h"
|
2024-05-01 13:39:40 +01:00
|
|
|
|
|
|
|
|
#include <wlr/render/pass.h>
|
|
|
|
|
#include <wlr/types/wlr_buffer.h>
|
|
|
|
|
#include <wlr/render/drm_format_set.h>
|
|
|
|
|
#include "common/macros.h"
|
2022-02-21 03:18:38 +01:00
|
|
|
|
2024-05-02 18:12:28 +01:00
|
|
|
static bool magnify_on;
|
2024-05-03 15:22:18 +01:00
|
|
|
static int mag_scale = 0;
|
2024-05-02 18:12:28 +01:00
|
|
|
|
2022-05-26 00:39:04 +02:00
|
|
|
struct wlr_surface *
|
|
|
|
|
lab_wlr_surface_from_node(struct wlr_scene_node *node)
|
|
|
|
|
{
|
|
|
|
|
struct wlr_scene_buffer *buffer;
|
|
|
|
|
struct wlr_scene_surface *scene_surface;
|
|
|
|
|
|
|
|
|
|
if (node && node->type == WLR_SCENE_NODE_BUFFER) {
|
|
|
|
|
buffer = wlr_scene_buffer_from_node(node);
|
2023-02-07 14:28:40 -05:00
|
|
|
scene_surface = wlr_scene_surface_try_from_buffer(buffer);
|
2022-05-26 00:39:04 +02:00
|
|
|
if (scene_surface) {
|
|
|
|
|
return scene_surface->surface;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2022-04-26 23:56:27 +02:00
|
|
|
|
|
|
|
|
struct wlr_scene_node *
|
|
|
|
|
lab_wlr_scene_get_prev_node(struct wlr_scene_node *node)
|
|
|
|
|
{
|
|
|
|
|
assert(node);
|
|
|
|
|
struct wlr_scene_node *prev;
|
|
|
|
|
prev = wl_container_of(node->link.prev, node, link);
|
|
|
|
|
if (&prev->link == &node->parent->children) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
return prev;
|
|
|
|
|
}
|
2023-01-30 05:30:24 +01:00
|
|
|
|
2024-05-08 08:54:20 +01:00
|
|
|
static double constrain(double lower, double in, double upper)
|
2024-05-08 08:45:02 +01:00
|
|
|
{
|
|
|
|
|
if (in < lower) {
|
|
|
|
|
return lower;
|
|
|
|
|
}
|
|
|
|
|
if (in > upper) {
|
|
|
|
|
return upper;
|
|
|
|
|
}
|
|
|
|
|
return in;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-01 13:39:40 +01:00
|
|
|
static void
|
|
|
|
|
magnify(struct output *output, struct wlr_buffer *output_buffer, struct wlr_box *damage)
|
|
|
|
|
{
|
2024-05-07 14:27:42 +01:00
|
|
|
int width, height;
|
|
|
|
|
double x, y;
|
|
|
|
|
struct wlr_box border_box, dst_box;
|
|
|
|
|
struct wlr_fbox src_box;
|
2024-05-08 08:54:20 +01:00
|
|
|
bool fullscreen = false;
|
2024-05-07 14:27:42 +01:00
|
|
|
|
2024-05-01 13:39:40 +01:00
|
|
|
/* Reuse a single scratch buffer */
|
|
|
|
|
static struct wlr_buffer *tmp_buffer = NULL;
|
|
|
|
|
static struct wlr_texture *tmp_texture = NULL;
|
|
|
|
|
|
|
|
|
|
/* TODO: This looks way too complicated to just get the used format */
|
|
|
|
|
struct wlr_drm_format wlr_drm_format = {0};
|
|
|
|
|
struct wlr_shm_attributes shm_attribs = {0};
|
|
|
|
|
struct wlr_dmabuf_attributes dma_attribs = {0};
|
|
|
|
|
if (wlr_buffer_get_dmabuf(output_buffer, &dma_attribs)) {
|
|
|
|
|
wlr_drm_format.format = dma_attribs.format;
|
|
|
|
|
wlr_drm_format.len = 1;
|
|
|
|
|
wlr_drm_format.modifiers = &dma_attribs.modifier;
|
|
|
|
|
} else if (wlr_buffer_get_shm(output_buffer, &shm_attribs)) {
|
|
|
|
|
wlr_drm_format.format = shm_attribs.format;
|
|
|
|
|
} else {
|
|
|
|
|
wlr_log(WLR_ERROR, "Failed to read buffer format");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Fetch scale-adjusted cursor coordinates */
|
|
|
|
|
struct server *server = output->server;
|
2024-05-03 15:22:18 +01:00
|
|
|
struct theme *theme = server->theme;
|
2024-05-01 13:39:40 +01:00
|
|
|
struct wlr_cursor *cursor = server->seat.cursor;
|
|
|
|
|
double ox = cursor->x;
|
|
|
|
|
double oy = cursor->y;
|
|
|
|
|
wlr_output_layout_output_coords(server->output_layout, output->wlr_output, &ox, &oy);
|
|
|
|
|
ox *= output->wlr_output->scale;
|
|
|
|
|
oy *= output->wlr_output->scale;
|
2024-05-08 08:54:20 +01:00
|
|
|
if (theme->mag_width == -1 || theme->mag_height == -1) {
|
|
|
|
|
fullscreen = true;
|
|
|
|
|
}
|
|
|
|
|
if ((ox < 0 || oy < 0 || ox >= output_buffer->width || oy >= output_buffer->height)
|
|
|
|
|
&& fullscreen) {
|
2024-05-07 14:27:42 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2024-05-01 13:39:40 +01:00
|
|
|
|
2024-05-03 15:22:18 +01:00
|
|
|
if (!mag_scale) {
|
|
|
|
|
mag_scale = theme->mag_scale;
|
|
|
|
|
}
|
2024-05-07 14:27:42 +01:00
|
|
|
|
2024-05-08 08:54:20 +01:00
|
|
|
if (fullscreen) {
|
2024-05-07 14:32:59 +01:00
|
|
|
// The lines below were the first attempt at enabling fullscreen (with no
|
|
|
|
|
// other changes required). They appeared to work with a 4K monitor set to
|
|
|
|
|
// 1080p, but when the monitor was set to native 4K, they resulted in a
|
|
|
|
|
// corrupt magnifier. Keeping here for now in case they are useful later...
|
2024-05-07 14:30:39 +01:00
|
|
|
//int width = output_buffer->width * 2 + 1;
|
2024-05-07 14:27:42 +01:00
|
|
|
//int height = output_buffer->height * 2 + 1;
|
|
|
|
|
//double x = ox - ((width - 1) / 2.0);
|
|
|
|
|
//double y = oy - ((height - 1) / 2.0);
|
|
|
|
|
width = output_buffer->width;
|
|
|
|
|
height = output_buffer->height;
|
|
|
|
|
x = 0;
|
|
|
|
|
y = 0;
|
|
|
|
|
} else {
|
|
|
|
|
width = theme->mag_width + 1;
|
|
|
|
|
height = theme->mag_height + 1;
|
|
|
|
|
x = ox - (theme->mag_width / 2.0);
|
|
|
|
|
y = oy - (theme->mag_height / 2.0);
|
|
|
|
|
}
|
2024-05-01 13:39:40 +01:00
|
|
|
double cropped_width = width;
|
|
|
|
|
double cropped_height = height;
|
|
|
|
|
double dst_x = 0;
|
|
|
|
|
double dst_y = 0;
|
|
|
|
|
|
|
|
|
|
/* Ensure everything is kept within output boundaries */
|
|
|
|
|
if (x < 0) {
|
|
|
|
|
cropped_width += x;
|
|
|
|
|
dst_x = x * -1;
|
|
|
|
|
x = 0;
|
|
|
|
|
}
|
|
|
|
|
if (y < 0) {
|
|
|
|
|
cropped_height += y;
|
|
|
|
|
dst_y = y * -1;
|
|
|
|
|
y = 0;
|
|
|
|
|
}
|
|
|
|
|
cropped_width = MIN(cropped_width, (double)output_buffer->width - x);
|
|
|
|
|
cropped_height = MIN(cropped_height, (double)output_buffer->height - y);
|
|
|
|
|
|
|
|
|
|
/* (Re)create the temporary buffer if required */
|
|
|
|
|
if (tmp_buffer && (tmp_buffer->width != width || tmp_buffer->height != height)) {
|
|
|
|
|
wlr_log(WLR_ERROR, "tmp buffer size changed, dropping");
|
|
|
|
|
assert(tmp_texture);
|
|
|
|
|
wlr_texture_destroy(tmp_texture);
|
|
|
|
|
wlr_buffer_drop(tmp_buffer);
|
|
|
|
|
tmp_buffer = NULL;
|
|
|
|
|
tmp_texture = NULL;
|
|
|
|
|
}
|
|
|
|
|
if (!tmp_buffer) {
|
|
|
|
|
tmp_buffer = wlr_allocator_create_buffer(
|
|
|
|
|
server->allocator, width, height, &wlr_drm_format);
|
|
|
|
|
}
|
|
|
|
|
if (!tmp_buffer) {
|
|
|
|
|
wlr_log(WLR_ERROR, "Failed to allocate temporary magnifier buffer");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Extract source region into temporary buffer */
|
|
|
|
|
|
|
|
|
|
struct wlr_render_pass *tmp_render_pass = wlr_renderer_begin_buffer_pass(
|
|
|
|
|
server->renderer, tmp_buffer, NULL);
|
|
|
|
|
|
|
|
|
|
/* FIXME, try to re-use the existing output texture instead */
|
|
|
|
|
wlr_buffer_lock(output_buffer);
|
|
|
|
|
struct wlr_texture *output_texture = wlr_texture_from_buffer(
|
|
|
|
|
server->renderer, output_buffer);
|
|
|
|
|
assert(output_texture);
|
|
|
|
|
|
|
|
|
|
struct wlr_render_texture_options opts = {
|
|
|
|
|
.texture = output_texture,
|
|
|
|
|
.src_box = (struct wlr_fbox) {
|
|
|
|
|
x, y, cropped_width, cropped_height },
|
|
|
|
|
.dst_box = (struct wlr_box) {
|
|
|
|
|
dst_x, dst_y, cropped_width, cropped_height },
|
|
|
|
|
.alpha = NULL,
|
|
|
|
|
};
|
|
|
|
|
wlr_render_pass_add_texture(tmp_render_pass, &opts);
|
|
|
|
|
if (!wlr_render_pass_submit(tmp_render_pass)) {
|
|
|
|
|
wlr_log(WLR_ERROR, "Failed to extract magnifier source region");
|
|
|
|
|
wlr_texture_destroy(output_texture);
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
wlr_texture_destroy(output_texture);
|
|
|
|
|
|
|
|
|
|
/* Render to the output buffer itself */
|
|
|
|
|
tmp_render_pass = wlr_renderer_begin_buffer_pass(
|
|
|
|
|
server->renderer, output_buffer, NULL);
|
|
|
|
|
|
|
|
|
|
/* Borders */
|
2024-05-08 08:54:20 +01:00
|
|
|
if (fullscreen) {
|
2024-05-07 14:27:42 +01:00
|
|
|
border_box.x = 0;
|
|
|
|
|
border_box.y = 0;
|
|
|
|
|
border_box.width = width;
|
|
|
|
|
border_box.height = height;
|
|
|
|
|
} else {
|
|
|
|
|
border_box.x = ox - (width / 2 + theme->mag_border_width);
|
|
|
|
|
border_box.y = oy - (height / 2 + theme->mag_border_width);
|
|
|
|
|
border_box.width = (width + theme->mag_border_width * 2);
|
|
|
|
|
border_box.height = (height + theme->mag_border_width * 2);
|
|
|
|
|
struct wlr_render_rect_options bg_opts = {
|
|
|
|
|
.box = border_box,
|
|
|
|
|
.color = (struct wlr_render_color) {
|
|
|
|
|
.r = theme->mag_border_color[0],
|
|
|
|
|
.g = theme->mag_border_color[1],
|
|
|
|
|
.b = theme->mag_border_color[2],
|
|
|
|
|
.a = theme->mag_border_color[3]
|
|
|
|
|
},
|
|
|
|
|
.clip = NULL,
|
|
|
|
|
};
|
|
|
|
|
wlr_render_pass_add_rect(tmp_render_pass, &bg_opts);
|
|
|
|
|
}
|
2024-05-01 13:39:40 +01:00
|
|
|
|
|
|
|
|
/* Paste the magnified result back into the output buffer */
|
|
|
|
|
if (!tmp_texture) {
|
|
|
|
|
tmp_texture = wlr_texture_from_buffer(server->renderer, tmp_buffer);
|
|
|
|
|
assert(tmp_texture);
|
|
|
|
|
}
|
2024-05-07 14:27:42 +01:00
|
|
|
|
2024-05-07 15:26:03 +01:00
|
|
|
src_box.width = width / mag_scale;
|
|
|
|
|
src_box.height = height / mag_scale;
|
|
|
|
|
dst_box.width = width;
|
|
|
|
|
dst_box.height = height;
|
|
|
|
|
|
2024-05-08 08:54:20 +01:00
|
|
|
if (fullscreen) {
|
|
|
|
|
src_box.x = constrain(0.0, ox - (ox / mag_scale),
|
2024-05-08 08:45:02 +01:00
|
|
|
width * (mag_scale - 1.0) / mag_scale);
|
2024-05-08 08:54:20 +01:00
|
|
|
src_box.y = constrain(0.0, oy - (oy / mag_scale),
|
2024-05-08 08:45:02 +01:00
|
|
|
height * (mag_scale - 1.0) / mag_scale);
|
2024-05-07 14:30:39 +01:00
|
|
|
dst_box.x = 0;
|
|
|
|
|
dst_box.y = 0;
|
2024-05-07 14:27:42 +01:00
|
|
|
} else {
|
2024-05-07 14:30:39 +01:00
|
|
|
src_box.x = width * (mag_scale - 1) / (2 * mag_scale);
|
|
|
|
|
src_box.y = height * (mag_scale - 1) / (2 * mag_scale);
|
|
|
|
|
dst_box.x = ox - (width / 2);
|
|
|
|
|
dst_box.y = oy - (height / 2);
|
2024-05-07 14:27:42 +01:00
|
|
|
}
|
|
|
|
|
|
2024-05-01 13:39:40 +01:00
|
|
|
opts = (struct wlr_render_texture_options) {
|
|
|
|
|
.texture = tmp_texture,
|
2024-05-07 14:27:42 +01:00
|
|
|
.src_box = src_box,
|
|
|
|
|
.dst_box = dst_box,
|
2024-05-01 13:39:40 +01:00
|
|
|
.alpha = NULL,
|
|
|
|
|
.clip = NULL,
|
2024-05-03 15:35:40 +01:00
|
|
|
.filter_mode = theme->mag_filter ? WLR_SCALE_FILTER_BILINEAR
|
|
|
|
|
: WLR_SCALE_FILTER_NEAREST,
|
2024-05-01 13:39:40 +01:00
|
|
|
};
|
|
|
|
|
wlr_render_pass_add_texture(tmp_render_pass, &opts);
|
|
|
|
|
if (!wlr_render_pass_submit(tmp_render_pass)) {
|
|
|
|
|
wlr_log(WLR_ERROR, "Failed to submit render pass");
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* And finally mark the extra damage */
|
|
|
|
|
*damage = border_box;
|
|
|
|
|
damage->width += 1;
|
|
|
|
|
damage->height += 1;
|
|
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
|
wlr_buffer_unlock(output_buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
output_wants_magnification(struct output *output)
|
|
|
|
|
{
|
|
|
|
|
static double x = -1;
|
|
|
|
|
static double y = -1;
|
|
|
|
|
struct wlr_cursor *cursor = output->server->seat.cursor;
|
2024-05-02 18:12:28 +01:00
|
|
|
if (!magnify_on) {
|
2024-05-01 13:39:40 +01:00
|
|
|
x = -1;
|
|
|
|
|
y = -1;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (cursor->x == x && cursor->y == y) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
x = cursor->x;
|
|
|
|
|
y = cursor->y;
|
|
|
|
|
return output_nearest_to_cursor(output->server) == output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Toggles magnification on and off
|
|
|
|
|
*/
|
|
|
|
|
|
2024-05-03 13:32:20 +01:00
|
|
|
void
|
|
|
|
|
magnify_toggle(void)
|
2024-05-01 13:39:40 +01:00
|
|
|
{
|
2024-05-02 18:12:28 +01:00
|
|
|
if (magnify_on) {
|
|
|
|
|
magnify_on = false;
|
2024-05-01 13:39:40 +01:00
|
|
|
} else {
|
2024-05-02 18:12:28 +01:00
|
|
|
magnify_on = true;
|
2024-05-01 13:39:40 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Increases and decreases magnification scale
|
|
|
|
|
*/
|
|
|
|
|
|
2024-05-03 13:32:20 +01:00
|
|
|
void
|
|
|
|
|
magnify_set_scale(enum magnify_dir dir)
|
2024-05-01 13:39:40 +01:00
|
|
|
{
|
|
|
|
|
if (dir == MAGNIFY_INCREASE) {
|
2024-05-02 18:12:28 +01:00
|
|
|
if (magnify_on) {
|
2024-05-03 15:22:18 +01:00
|
|
|
mag_scale++;
|
2024-05-01 13:39:40 +01:00
|
|
|
} else {
|
2024-05-02 18:12:28 +01:00
|
|
|
magnify_on = true;
|
2024-05-03 15:22:18 +01:00
|
|
|
mag_scale = 2;
|
2024-05-01 13:39:40 +01:00
|
|
|
}
|
|
|
|
|
} else {
|
2024-05-03 15:22:18 +01:00
|
|
|
if (magnify_on && mag_scale > 2) {
|
|
|
|
|
mag_scale--;
|
2024-05-01 13:39:40 +01:00
|
|
|
} else {
|
2024-05-02 18:12:28 +01:00
|
|
|
magnify_on = false;
|
2024-05-01 13:39:40 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-30 05:30:24 +01:00
|
|
|
/*
|
|
|
|
|
* This is a copy of wlr_scene_output_commit()
|
|
|
|
|
* as it doesn't use the pending state at all.
|
|
|
|
|
*/
|
|
|
|
|
bool
|
|
|
|
|
lab_wlr_scene_output_commit(struct wlr_scene_output *scene_output)
|
|
|
|
|
{
|
|
|
|
|
assert(scene_output);
|
|
|
|
|
struct wlr_output *wlr_output = scene_output->output;
|
|
|
|
|
struct wlr_output_state *state = &wlr_output->pending;
|
2024-05-01 13:39:40 +01:00
|
|
|
struct output *output = wlr_output->data;
|
|
|
|
|
bool wants_magnification = output_wants_magnification(output);
|
|
|
|
|
static bool last_mag = false;
|
2023-01-30 05:30:24 +01:00
|
|
|
|
|
|
|
|
if (!wlr_output->needs_frame && !pixman_region32_not_empty(
|
2024-05-01 13:39:40 +01:00
|
|
|
&scene_output->damage_ring.current) && !wants_magnification
|
2024-05-02 19:04:37 +01:00
|
|
|
&& last_mag != magnify_on) {
|
2023-01-30 05:30:24 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
2024-05-01 13:39:40 +01:00
|
|
|
|
2024-05-02 18:12:28 +01:00
|
|
|
last_mag = magnify_on;
|
2024-05-01 13:39:40 +01:00
|
|
|
|
2023-01-30 05:30:24 +01:00
|
|
|
if (!wlr_scene_output_build_state(scene_output, state, NULL)) {
|
|
|
|
|
wlr_log(WLR_ERROR, "Failed to build output state for %s",
|
|
|
|
|
wlr_output->name);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-05-01 13:39:40 +01:00
|
|
|
|
|
|
|
|
struct wlr_box additional_damage = {0};
|
2024-05-02 18:12:28 +01:00
|
|
|
if (state->buffer && magnify_on) {
|
2024-05-01 13:39:40 +01:00
|
|
|
magnify(output, state->buffer, &additional_damage);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-30 05:30:24 +01:00
|
|
|
if (!wlr_output_commit(wlr_output)) {
|
2024-04-08 16:29:20 +02:00
|
|
|
wlr_log(WLR_INFO, "Failed to commit output %s",
|
2023-01-30 05:30:24 +01:00
|
|
|
wlr_output->name);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
* FIXME: Remove the following line as soon as
|
|
|
|
|
* https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4253
|
|
|
|
|
* is merged. At that point wlr_scene handles damage tracking internally
|
|
|
|
|
* again.
|
|
|
|
|
*/
|
|
|
|
|
wlr_damage_ring_rotate(&scene_output->damage_ring);
|
2024-05-01 13:39:40 +01:00
|
|
|
|
|
|
|
|
if (!wlr_box_empty(&additional_damage)) {
|
|
|
|
|
wlr_damage_ring_add_box(&scene_output->damage_ring, &additional_damage);
|
|
|
|
|
}
|
2023-01-30 05:30:24 +01:00
|
|
|
return true;
|
|
|
|
|
}
|