From 952d04b78dafee510b2f43605fde504dfb1a426b Mon Sep 17 00:00:00 2001 From: Simon Long Date: Wed, 1 May 2024 13:39:40 +0100 Subject: [PATCH] First attempt at integration with nmouse and keyboard bindings --- include/common/scene-helpers.h | 8 + include/config/default-bindings.h | 21 +++ include/config/rcxml.h | 3 + src/action.c | 22 +++ src/common/scene-helpers.c | 239 +++++++++++++++++++++++++++++- src/config/rcxml.c | 2 + 6 files changed, 294 insertions(+), 1 deletion(-) diff --git a/include/common/scene-helpers.h b/include/common/scene-helpers.h index 11697a94..0a4a5733 100644 --- a/include/common/scene-helpers.h +++ b/include/common/scene-helpers.h @@ -20,4 +20,12 @@ 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/config/default-bindings.h b/include/config/default-bindings.h index 535ee25a..afe5d28e 100644 --- a/include/config/default-bindings.h +++ b/include/config/default-bindings.h @@ -134,6 +134,9 @@ static struct key_combos { .name = "command", .value = "brightnessctl set 10%-", }, + }, { + .binding = "C-A-m", + .action = "ToggleMagnify", }, { .binding = NULL, }, @@ -441,6 +444,24 @@ static struct mouse_combos { .button = "Middle", .event = "Press", .action = "Raise", + }, { + .context = "All", + .button = "W-Up", + .event = "Scroll", + .action = "SetMagnification", + .attributes[0] = { + .name = "scale", + .value = "up", + }, + }, { + .context = "All", + .button = "W-Down", + .event = "Scroll", + .action = "SetMagnification", + .attributes[0] = { + .name = "scale", + .value = "down", + }, }, { .context = NULL, }, diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 60081db0..0c4ba683 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -60,6 +60,9 @@ struct rcxml { bool focus_follow_mouse_requires_movement; bool raise_on_focus; + bool magnify; + int magnification; + /* theme */ char *theme_name; int corner_radius; diff --git a/src/action.c b/src/action.c index bf94e0a0..26c2ec14 100644 --- a/src/action.c +++ b/src/action.c @@ -13,6 +13,7 @@ #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 "menu/menu.h" @@ -110,6 +111,8 @@ enum action_type { ACTION_TYPE_SHADE, ACTION_TYPE_UNSHADE, ACTION_TYPE_TOGGLE_SHADE, + ACTION_TYPE_TOGGLE_MAGNIFY, + ACTION_TYPE_SET_MAGNIFICATION }; const char *action_names[] = { @@ -163,6 +166,8 @@ const char *action_names[] = { "Shade", "Unshade", "ToggleShade", + "ToggleMagnify", + "SetMagnification", NULL }; @@ -415,6 +420,12 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char goto cleanup; } break; + case ACTION_TYPE_SET_MAGNIFICATION: + if (!strcmp(argument, "scale")) { + action_arg_add_str(action, argument, content); + goto cleanup; + } + break; } wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s'", @@ -1046,6 +1057,17 @@ actions_run(struct view *activator, struct server *server, view_set_shade(view, false); } break; + case ACTION_TYPE_TOGGLE_MAGNIFY: + magnify_toggle(); + break; + case ACTION_TYPE_SET_MAGNIFICATION: + const char *dir = action_get_str(action, "scale", NULL); + if (!strcmp(dir, "up")) { + magnify_set_scale(MAGNIFY_INCREASE); + } else if (!strcmp(dir, "down")) { + magnify_set_scale(MAGNIFY_DECREASE); + } + break; case ACTION_TYPE_INVALID: wlr_log(WLR_ERROR, "Not executing unknown action"); break; diff --git a/src/common/scene-helpers.c b/src/common/scene-helpers.c index 6c7ed9ea..2fef4653 100644 --- a/src/common/scene-helpers.c +++ b/src/common/scene-helpers.c @@ -5,6 +5,12 @@ #include #include #include "common/scene-helpers.h" +#include "labwc.h" + +#include +#include +#include +#include "common/macros.h" struct wlr_surface * lab_wlr_surface_from_node(struct wlr_scene_node *node) @@ -34,6 +40,218 @@ lab_wlr_scene_get_prev_node(struct wlr_scene_node *node) return prev; } + +/* TODO: move to rc. (or theme?) settings */ +#define magnifier_border 1 /* in pixels */ +#define magnifier_crop_size 100 /* how many pixels in each direction of the cursor */ + +static void +magnify(struct output *output, struct wlr_buffer *output_buffer, struct wlr_box *damage) +{ + /* 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 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; + + /* TODO: refactor, to use rc. settings */ + int width = magnifier_crop_size * 2 + 1; + int height = width; + double x = ox - magnifier_crop_size; + double y = oy - magnifier_crop_size; + 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 */ + struct wlr_box border_box = { + .x = ox - (width / 2 + magnifier_border) * rc.magnification, + .y = oy - (height / 2 + magnifier_border) * rc.magnification, + .width = (width + magnifier_border * 2) * rc.magnification, + .height = (height + magnifier_border * 2) * rc.magnification, + }; + struct wlr_render_rect_options bg_opts = { + .box = border_box, + /* TODO: make this a rc. setting */ + .color = (struct wlr_render_color) { 1, 0, 0, 1 }, + .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); + } + opts = (struct wlr_render_texture_options) { + .texture = tmp_texture, + .src_box = (struct wlr_fbox) { 0, 0, width, height }, + .dst_box = (struct wlr_box) { + .x = ox - (width / 2) * rc.magnification, + .y = oy - (height / 2) * rc.magnification, + .width = width * rc.magnification, + .height = height * rc.magnification, + }, + .alpha = NULL, + .clip = NULL, + //.filter_mode = WLR_SCALE_FILTER_NEAREST, + .filter_mode = WLR_SCALE_FILTER_BILINEAR, + }; + 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 (!rc.magnify) { + 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 (rc.magnify) { + rc.magnify = false; + } else { + rc.magnify = true; + } +} + +/* + * Increases and decreases magnification scale + */ + +void magnify_set_scale (enum magnify_dir dir) +{ + if (dir == MAGNIFY_INCREASE) { + if (rc.magnify) { + rc.magnification++; + } else { + rc.magnify = true; + rc.magnification = 2; + } + } else { + if (rc.magnify && rc.magnification > 2) { + rc.magnification--; + } else { + rc.magnify = false; + } + } +} + /* * This is a copy of wlr_scene_output_commit() * as it doesn't use the pending state at all. @@ -44,16 +262,31 @@ 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; + struct output *output = wlr_output->data; + bool wants_magnification = output_wants_magnification(output); + static bool last_mag = false; + static int last_scale = 2; if (!wlr_output->needs_frame && !pixman_region32_not_empty( - &scene_output->damage_ring.current)) { + &scene_output->damage_ring.current) && !wants_magnification + && last_mag != rc.magnify && last_scale != rc.magnification) { return false; } + + last_mag = rc.magnify; + last_scale = rc.magnification; + 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; } + + struct wlr_box additional_damage = {0}; + if (state->buffer && rc.magnify) { + magnify(output, state->buffer, &additional_damage); + } + if (!wlr_output_commit(wlr_output)) { wlr_log(WLR_INFO, "Failed to commit output %s", wlr_output->name); @@ -66,5 +299,9 @@ lab_wlr_scene_output_commit(struct wlr_scene_output *scene_output) * again. */ wlr_damage_ring_rotate(&scene_output->damage_ring); + + if (!wlr_box_empty(&additional_damage)) { + wlr_damage_ring_add_box(&scene_output->damage_ring, &additional_damage); + } return true; } diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 0d844119..b8b90695 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1204,6 +1204,8 @@ rcxml_init(void) rc.focus_follow_mouse = false; rc.focus_follow_mouse_requires_movement = true; rc.raise_on_focus = false; + rc.magnify = false; + rc.magnification = 2; rc.doubleclick_time = 500; rc.scroll_factor = 1.0;