1 /* 2 * The MIT License (MIT) 3 * 4 * Copyright (c) 2020 Scott Moreau 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in all 14 * copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 */ 24 25 #include "wayfire/core.hpp" 26 #include "wayfire/view.hpp" 27 #include "wayfire/plugin.hpp" 28 #include "wayfire/output.hpp" 29 #include "wayfire/signal-definitions.hpp" 30 #include "wayfire/workspace-manager.hpp" 31 #include "wayfire/output-layout.hpp" 32 #include "wayfire/compositor-surface.hpp" 33 #include "wayfire/compositor-view.hpp" 34 #include "wayfire/render-manager.hpp" 35 #include "wayfire/opengl.hpp" 36 #include <glm/gtc/matrix_transform.hpp> 37 38 extern "C" 39 { 40 #define static 41 #include <wlr/config.h> 42 #include <wlr/render/gles2.h> 43 #include <wlr/render/wlr_renderer.h> 44 #include <wlr/types/wlr_matrix.h> 45 #undef static 46 } 47 48 #include <wayfire/util/log.hpp> 49 50 51 class mag_view_t : public wf::color_rect_view_t 52 { 53 wf::option_wrapper_t<int> default_height{"mag/default_height"}; 54 55 public: 56 wf::framebuffer_t mag_tex; 57 mag_view_t(wf::output_t * output,float aspect)58 mag_view_t(wf::output_t *output, float aspect) : 59 wf::color_rect_view_t() 60 { 61 set_output(output); 62 63 set_geometry({100, 100, (int)(default_height * aspect), default_height}); 64 65 this->role = wf::VIEW_ROLE_TOPLEVEL; 66 output->workspace->add_view(self(), wf::LAYER_TOP); 67 } 68 accepts_input(int32_t sx,int32_t sy)69 bool accepts_input(int32_t sx, int32_t sy) override 70 { 71 auto vg = get_wm_geometry(); 72 73 /* Allow move and resize */ 74 if ((0 < sx) && (sx < vg.width) && (0 < sy) && (sy < vg.height)) 75 { 76 return true; 77 } 78 79 return false; 80 } 81 simple_render(const wf::framebuffer_t & fb,int x,int y,const wf::region_t & damage)82 void simple_render(const wf::framebuffer_t& fb, int x, int y, 83 const wf::region_t& damage) override 84 { 85 OpenGL::render_begin(fb); 86 auto vg = get_wm_geometry(); 87 gl_geometry src_geometry = {(float)vg.x, (float)vg.y, 88 (float)vg.x + vg.width, (float)vg.y + vg.height}; 89 for (const auto& box : damage) 90 { 91 fb.logic_scissor(wlr_box_from_pixman_box(box)); 92 OpenGL::render_transformed_texture(mag_tex.tex, src_geometry, {}, 93 fb.get_orthographic_projection(), 94 glm::vec4(1.0), 0); 95 } 96 97 OpenGL::render_end(); 98 } 99 ~mag_view_t()100 virtual ~mag_view_t() 101 {} 102 }; 103 104 class wayfire_magnifier : public wf::plugin_interface_t 105 { 106 const std::string transformer_name = "mag"; 107 wf::option_wrapper_t<wf::activatorbinding_t> toggle_binding{"mag/toggle"}; 108 wf::option_wrapper_t<int> zoom_level{"mag/zoom_level"}; 109 nonstd::observer_ptr<mag_view_t> mag_view; 110 bool active, hook_set; 111 int width, height; 112 113 wf::activator_callback toggle_cb = [=] (auto) __anonfb066a6d0102(auto) 114 { 115 active = !active; 116 if (active) 117 { 118 return activate(); 119 } else 120 { 121 deactivate(); 122 123 return true; 124 } 125 }; 126 127 public: init()128 void init() override 129 { 130 grab_interface->name = transformer_name; 131 grab_interface->capabilities = 0; 132 133 output->add_activator(toggle_binding, &toggle_cb); 134 hook_set = active = false; 135 } 136 ensure_preview()137 void ensure_preview() 138 { 139 if (mag_view) 140 { 141 return; 142 } 143 144 auto og = output->get_relative_geometry(); 145 auto view = 146 std::make_unique<mag_view_t>(output, (float)og.width / og.height); 147 148 mag_view = {view}; 149 150 wf::get_core().add_view(std::move(view)); 151 } 152 activate()153 bool activate() 154 { 155 if (!output->activate_plugin(grab_interface)) 156 { 157 return false; 158 } 159 160 if (!hook_set) 161 { 162 output->render->add_effect(&post_hook, wf::OUTPUT_EFFECT_POST); 163 wlr_output_lock_software_cursors(output->handle, true); 164 hook_set = true; 165 } 166 167 ensure_preview(); 168 169 return true; 170 } 171 172 wf::effect_hook_t post_hook = [=] () __anonfb066a6d0202() 173 { 174 wlr_dmabuf_attributes dmabuf_attribs; 175 176 /* This plugin only works if this function succeeds. It will not 177 * work with the x11 backend but works with drm, for example. */ 178 if (!wlr_output_export_dmabuf(output->handle, &dmabuf_attribs)) 179 { 180 LOGE("Failed reading output contents"); 181 deactivate(); 182 active = false; 183 184 return; 185 } 186 187 auto cursor_position = output->get_cursor_position(); 188 189 auto ortho = output->render->get_target_framebuffer() 190 .get_orthographic_projection(); 191 192 // Map from OpenGL coordinates to [0, 1]x[0, 1] 193 auto cursor_transform = 194 glm::translate(glm::mat4(1.0), glm::vec3(0.5, 0.5, 0.0)) * 195 glm::scale(glm::mat4(1.0), glm::vec3{0.5, -0.5, 1.0}) * ortho; 196 197 glm::vec4 cursor = glm::vec4(cursor_position.x, cursor_position.y, 0.0, 1.0); 198 cursor = cursor_transform * cursor; 199 200 float x = cursor.x; 201 float y = cursor.y; 202 203 auto og = output->get_relative_geometry(); 204 gl_geometry src_geometry = {0, 0, (float)og.width, (float)og.height}; 205 auto transform = output->render->get_target_framebuffer().transform; 206 transform = glm::inverse(transform); 207 208 width = og.width; 209 height = og.height; 210 211 /* min and max represent the distance on either side of the pointer. 212 * The min is 0.5 and means no zoom, half the screen on either side 213 * of the pointer. max is 0.01 and means this much of the screen 214 * on either side, which is about the maximum reasonable zoom level. */ 215 float min = 0.5; 216 float max = 0.01; 217 float range = min - max; 218 float level = (1.0 - (zoom_level / 100.0)) * range + max; 219 220 /* Compute zoom_box, forcing the zoom to stay on the output */ 221 gl_geometry zoom_box; 222 223 zoom_box.x1 = x - level; 224 zoom_box.y1 = y - level; 225 zoom_box.x2 = x + level; 226 zoom_box.y2 = y + level; 227 228 if (zoom_box.x1 < 0.0) 229 { 230 zoom_box.x2 -= zoom_box.x1; 231 zoom_box.x1 = 0.0; 232 } 233 234 if (zoom_box.y1 < 0.0) 235 { 236 zoom_box.y2 -= zoom_box.y1; 237 zoom_box.y1 = 0.0; 238 } 239 240 if (zoom_box.x2 > 1.0) 241 { 242 zoom_box.x1 += 1.0 - zoom_box.x2; 243 zoom_box.x2 = 1.0; 244 } 245 246 if (zoom_box.y2 > 1.0) 247 { 248 zoom_box.y1 += 1.0 - zoom_box.y2; 249 zoom_box.y2 = 1.0; 250 } 251 252 /* Copy zoom_box part of the output to our own texture to be 253 * read by the mag_view_t. */ 254 auto wlr_texture = wlr_texture_from_dmabuf( 255 wf::get_core().renderer, &dmabuf_attribs); 256 257 wf::texture_t texture{wlr_texture}; 258 259 OpenGL::render_begin(); 260 mag_view->mag_tex.allocate(width, height); 261 mag_view->mag_tex.geometry = og; 262 mag_view->mag_tex.bind(); 263 264 OpenGL::render_transformed_texture(texture, src_geometry, zoom_box, 265 transform * mag_view->mag_tex.get_orthographic_projection(), 266 glm::vec4(1.0), 267 OpenGL::TEXTURE_USE_TEX_GEOMETRY); 268 OpenGL::render_end(); 269 270 wlr_texture_destroy(wlr_texture); 271 wlr_dmabuf_attributes_finish(&dmabuf_attribs); 272 273 mag_view->damage(); 274 }; 275 deactivate()276 void deactivate() 277 { 278 output->deactivate_plugin(grab_interface); 279 280 if (hook_set) 281 { 282 output->render->rem_effect(&post_hook); 283 wlr_output_lock_software_cursors(output->handle, false); 284 hook_set = false; 285 } 286 287 output->render->damage_whole(); 288 289 if (!mag_view) 290 { 291 return; 292 } 293 294 mag_view->close(); 295 mag_view = nullptr; 296 } 297 fini()298 void fini() override 299 { 300 deactivate(); 301 output->rem_binding(&toggle_cb); 302 } 303 }; 304 305 DECLARE_WAYFIRE_PLUGIN(wayfire_magnifier); 306