diff --git a/include/common/scene-helpers.h b/include/common/scene-helpers.h index 477c4e6b..11697a94 100644 --- a/include/common/scene-helpers.h +++ b/include/common/scene-helpers.h @@ -20,12 +20,4 @@ struct wlr_scene_node *lab_wlr_scene_get_prev_node(struct wlr_scene_node *node); /* A variant of wlr_scene_output_commit() that respects wlr_output->pending */ bool lab_wlr_scene_output_commit(struct wlr_scene_output *scene_output); -enum magnify_dir { - MAGNIFY_INCREASE, - MAGNIFY_DECREASE -}; - -void magnify_toggle(void); -void magnify_set_scale(enum magnify_dir dir); - #endif /* LABWC_SCENE_HELPERS_H */ diff --git a/include/magnifier.h b/include/magnifier.h new file mode 100644 index 00000000..407fb04d --- /dev/null +++ b/include/magnifier.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_MAGNIFIER_H +#define LABWC_MAGNIFIER_H + +#include +#include + +struct server; +struct output; + +enum magnify_dir { + MAGNIFY_INCREASE, + MAGNIFY_DECREASE +}; + +void magnify_toggle(struct server *server); +void magnify_set_scale(struct server *server, enum magnify_dir dir); +bool output_wants_magnification(struct output *output); +void magnify(struct output *output, struct wlr_buffer *output_buffer, + struct wlr_box *damage); +bool is_magnify_on(void); + +#endif /* LABWC_MAGNIFIER_H */ diff --git a/src/action.c b/src/action.c index e083704f..68086bf9 100644 --- a/src/action.c +++ b/src/action.c @@ -13,9 +13,9 @@ #include "common/parse-bool.h" #include "common/spawn.h" #include "common/string-helpers.h" -#include "common/scene-helpers.h" #include "debug.h" #include "labwc.h" +#include "magnifier.h" #include "menu/menu.h" #include "osd.h" #include "output-virtual.h" @@ -1054,13 +1054,13 @@ actions_run(struct view *activator, struct server *server, } break; case ACTION_TYPE_TOGGLE_MAGNIFY: - magnify_toggle(); + magnify_toggle(server); break; case ACTION_TYPE_ZOOM_IN: - magnify_set_scale(MAGNIFY_INCREASE); + magnify_set_scale(server, MAGNIFY_INCREASE); break; case ACTION_TYPE_ZOOM_OUT: - magnify_set_scale(MAGNIFY_DECREASE); + magnify_set_scale(server, MAGNIFY_DECREASE); break; case ACTION_TYPE_INVALID: wlr_log(WLR_ERROR, "Not executing unknown action"); diff --git a/src/common/scene-helpers.c b/src/common/scene-helpers.c index ef430561..f09cd9c0 100644 --- a/src/common/scene-helpers.c +++ b/src/common/scene-helpers.c @@ -5,16 +5,7 @@ #include #include #include "common/scene-helpers.h" -#include "labwc.h" -#include "theme.h" - -#include -#include -#include -#include "common/macros.h" - -static bool magnify_on; -static double mag_scale = 0.0, mag_increment = 0.0; +#include "magnifier.h" struct wlr_surface * lab_wlr_surface_from_node(struct wlr_scene_node *node) @@ -44,285 +35,6 @@ lab_wlr_scene_get_prev_node(struct wlr_scene_node *node) return prev; } -static double constrain(double lower, double in, double upper) -{ - if (in < lower) { - return lower; - } - if (in > upper) { - return upper; - } - return in; -} - -static void -magnify(struct output *output, struct wlr_buffer *output_buffer, struct wlr_box *damage) -{ - int width, height; - double x, y; - struct wlr_box border_box, dst_box; - struct wlr_fbox src_box; - bool fullscreen = false; - - /* 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; - struct theme *theme = server->theme; - 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; - 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) { - return; - } - - if (mag_scale == 0.0) { - mag_scale = theme->mag_scale; - } - if (mag_increment == 0.0) { - mag_increment = theme->mag_increment; - } - - if (fullscreen) { - // 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... - //int width = output_buffer->width * 2 + 1; - //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); - } - 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 */ - if (fullscreen) { - 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); - } - - /* 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); - } - - src_box.width = width / mag_scale; - src_box.height = height / mag_scale; - dst_box.width = width; - dst_box.height = height; - - if (fullscreen) { - src_box.x = constrain(0.0, ox - (ox / mag_scale), - width * (mag_scale - 1.0) / mag_scale); - src_box.y = constrain(0.0, oy - (oy / mag_scale), - height * (mag_scale - 1.0) / mag_scale); - dst_box.x = 0; - dst_box.y = 0; - } else { - src_box.x = width * (mag_scale - 1.0) / (2.0 * mag_scale); - src_box.y = height * (mag_scale - 1.0) / (2.0 * mag_scale); - dst_box.x = ox - (width / 2); - dst_box.y = oy - (height / 2); - } - - opts = (struct wlr_render_texture_options) { - .texture = tmp_texture, - .src_box = src_box, - .dst_box = dst_box, - .alpha = NULL, - .clip = NULL, - .filter_mode = theme->mag_filter ? WLR_SCALE_FILTER_BILINEAR - : WLR_SCALE_FILTER_NEAREST, - }; - 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; - if (!magnify_on) { - 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 - */ - -void -magnify_toggle(void) -{ - if (magnify_on) { - magnify_on = false; - } else { - magnify_on = true; - } -} - -/* - * Increases and decreases magnification scale - */ - -void -magnify_set_scale(enum magnify_dir dir) -{ - if (dir == MAGNIFY_INCREASE) { - if (magnify_on) { - mag_scale += mag_increment; - } else { - magnify_on = true; - mag_scale = 1.0 + mag_increment; - } - } else { - if (magnify_on && mag_scale > 1.0 + mag_increment) { - mag_scale -= mag_increment; - } else { - magnify_on = false; - } - } -} - /* * This is a copy of wlr_scene_output_commit() * as it doesn't use the pending state at all. @@ -339,11 +51,11 @@ lab_wlr_scene_output_commit(struct wlr_scene_output *scene_output) if (!wlr_output->needs_frame && !pixman_region32_not_empty( &scene_output->damage_ring.current) && !wants_magnification - && last_mag != magnify_on) { + && last_mag != is_magnify_on()) { return false; } - last_mag = magnify_on; + last_mag = is_magnify_on(); if (!wlr_scene_output_build_state(scene_output, state, NULL)) { wlr_log(WLR_ERROR, "Failed to build output state for %s", @@ -352,7 +64,7 @@ lab_wlr_scene_output_commit(struct wlr_scene_output *scene_output) } struct wlr_box additional_damage = {0}; - if (state->buffer && magnify_on) { + if (state->buffer && is_magnify_on()) { magnify(output, state->buffer, &additional_damage); } diff --git a/src/magnifier.c b/src/magnifier.c new file mode 100644 index 00000000..56485495 --- /dev/null +++ b/src/magnifier.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include "magnifier.h" +#include "labwc.h" +#include "theme.h" +#include "common/macros.h" + +bool magnify_on; +double mag_scale = 0.0, mag_increment = 0.0; + +#define CLAMP(in, lower, upper) MAX(MIN(in, upper), lower) + +void +magnify(struct output *output, struct wlr_buffer *output_buffer, struct wlr_box *damage) +{ + int width, height; + double x, y; + struct wlr_box border_box, dst_box; + struct wlr_fbox src_box; + bool fullscreen = false; + + /* 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; + struct theme *theme = server->theme; + 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; + 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) { + return; + } + + if (mag_scale == 0.0) { + mag_scale = theme->mag_scale; + } + if (mag_increment == 0.0) { + mag_increment = theme->mag_increment; + } + + if (fullscreen) { + 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); + } + 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 */ + if (fullscreen) { + 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); + } + + /* 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); + } + + src_box.width = width / mag_scale; + src_box.height = height / mag_scale; + dst_box.width = width; + dst_box.height = height; + + if (fullscreen) { + src_box.x = CLAMP(ox - (ox / mag_scale), 0.0, + width * (mag_scale - 1.0) / mag_scale); + src_box.y = CLAMP(oy - (oy / mag_scale), 0.0, + height * (mag_scale - 1.0) / mag_scale); + dst_box.x = 0; + dst_box.y = 0; + } else { + src_box.x = width * (mag_scale - 1.0) / (2.0 * mag_scale); + src_box.y = height * (mag_scale - 1.0) / (2.0 * mag_scale); + dst_box.x = ox - (width / 2); + dst_box.y = oy - (height / 2); + } + + opts = (struct wlr_render_texture_options) { + .texture = tmp_texture, + .src_box = src_box, + .dst_box = dst_box, + .alpha = NULL, + .clip = NULL, + .filter_mode = theme->mag_filter ? WLR_SCALE_FILTER_BILINEAR + : WLR_SCALE_FILTER_NEAREST, + }; + 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); +} + +bool +output_wants_magnification(struct output *output) +{ + static double x = -1; + static double y = -1; + struct wlr_cursor *cursor = output->server->seat.cursor; + if (!magnify_on) { + 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 + */ + +void +magnify_toggle(struct server *server) +{ + struct output *output = output_nearest_to_cursor(server); + + if (magnify_on) { + magnify_on = false; + } else { + magnify_on = true; + } + + if (output) { + wlr_output_schedule_frame(output->wlr_output); + } +} + +/* + * Increases and decreases magnification scale + */ + +void +magnify_set_scale(struct server *server, enum magnify_dir dir) +{ + struct output *output = output_nearest_to_cursor(server); + + if (dir == MAGNIFY_INCREASE) { + if (magnify_on) { + mag_scale += mag_increment; + } else { + magnify_on = true; + mag_scale = 1.0 + mag_increment; + } + } else { + if (magnify_on && mag_scale > 1.0 + mag_increment) { + mag_scale -= mag_increment; + } else { + magnify_on = false; + } + } + + if (output) { + wlr_output_schedule_frame(output->wlr_output); + } +} + +/* + * Report whether magnification is enabled + */ + +bool +is_magnify_on(void) +{ + return magnify_on; +} + diff --git a/src/meson.build b/src/meson.build index a7450d91..fab82b2a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -9,6 +9,7 @@ labwc_sources = files( 'idle.c', 'interactive.c', 'layers.c', + 'magnifier.c', 'main.c', 'node.c', 'osd.c',