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