1 /*
2  * The MIT License (MIT)
3  *
4  * Copyright (c) 2020 Ilia Bozhinov
5  * Copyright (c) 2020 Scott Moreau
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in all
15  * copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  */
25 
26 #include <map>
27 #include <wayfire/core.hpp>
28 #include <wayfire/plugin.hpp>
29 #include <wayfire/output.hpp>
30 #include <wayfire/view-transform.hpp>
31 #include <wayfire/compositor-view.hpp>
32 #include <wayfire/workspace-manager.hpp>
33 #include <wayfire/signal-definitions.hpp>
34 
35 
36 class fullscreen_subsurface : public wf::surface_interface_t,
37     public wf::compositor_surface_t
38 {
39   public:
40     bool _mapped = true;
41 
fullscreen_subsurface(wayfire_view view)42     fullscreen_subsurface(wayfire_view view) :
43         wf::surface_interface_t(), wf::compositor_surface_t()
44     {}
45 
~fullscreen_subsurface()46     ~fullscreen_subsurface()
47     {}
48 
on_pointer_enter(int x,int y)49     void on_pointer_enter(int x, int y) override
50     {
51         wf::get_core().set_cursor("default");
52     }
53 
accepts_input(int32_t sx,int32_t sy)54     bool accepts_input(int32_t sx, int32_t sy) override
55     {
56         return wlr_box{0, 0, 1, 1} & wf::point_t{sx, sy};
57     }
58 
is_mapped() const59     bool is_mapped() const override
60     {
61         return _mapped;
62     }
63 
get_offset()64     wf::point_t get_offset() override
65     {
66         return {-1, 0};
67     }
68 
get_size() const69     wf::dimensions_t get_size() const override
70     {
71         return {1, 1};
72     }
73 
simple_render(const wf::framebuffer_t & fb,int x,int y,const wf::region_t & damage)74     void simple_render(const wf::framebuffer_t& fb, int x, int y,
75         const wf::region_t& damage) override
76     {
77         /* fully transparent */ }
78 };
79 
80 class fullscreen_transformer : public wf::view_2D
81 {
82   public:
83     wayfire_view view;
84     wlr_box transformed_view_box;
85     wf::option_wrapper_t<bool> transparent_behind_views{
86         "force-fullscreen/transparent_behind_views"};
87 
fullscreen_transformer(wayfire_view view)88     fullscreen_transformer(wayfire_view view) : wf::view_2D(view)
89     {
90         this->view = view;
91     }
92 
~fullscreen_transformer()93     ~fullscreen_transformer()
94     {}
95 
get_workspace(wlr_box og)96     wf::point_t get_workspace(wlr_box og)
97     {
98         wf::point_t ws;
99         auto vg = view->get_wm_geometry();
100         auto tg = transformed_view_box;
101         wf::pointf_t center{vg.x + tg.width / 2.0, vg.y + tg.height / 2.0};
102 
103         ws.x = std::floor(center.x / og.width);
104         ws.y = std::floor(center.y / og.height);
105 
106         return ws;
107     }
108 
109     /* TODO: transform_point */
get_bounding_box(wf::geometry_t geometry,wf::geometry_t box)110     wf::geometry_t get_bounding_box(
111         wf::geometry_t geometry, wf::geometry_t box) override
112     {
113         auto output = view->get_output();
114         auto bbox   = wf::view_2D::get_bounding_box(geometry, box);
115 
116         if (!output)
117         {
118             return bbox;
119         }
120 
121         wf::geometry_t wm = view->get_wm_geometry();
122         wf::point_t subsurface = {wm.x - 1, wm.y};
123         auto og = output->get_relative_geometry();
124         auto ws = get_workspace(og);
125         if (box & subsurface)
126         {
127             og.x += ws.x * og.width;
128             og.y += ws.y * og.height;
129 
130             return og;
131         } else
132         {
133             bbox.x += ws.x * og.width;
134             bbox.y += ws.y * og.height;
135 
136             return bbox;
137         }
138     }
139 
get_relative_transformed_view(wlr_box & og)140     wlr_box get_relative_transformed_view(wlr_box& og)
141     {
142         auto ws   = get_workspace(og);
143         auto bbox = transformed_view_box;
144         bbox.x += ws.x * og.width;
145         bbox.y += ws.y * og.height;
146         og.x   += ws.x * og.width;
147         og.y   += ws.y * og.height;
148 
149         return bbox;
150     }
151 
untransform_point(wf::geometry_t geometry,wf::pointf_t point)152     wf::pointf_t untransform_point(
153         wf::geometry_t geometry, wf::pointf_t point) override
154     {
155         auto output = view->get_output();
156         auto default_point = wf::view_2D::untransform_point(geometry, point);
157 
158         if (!output)
159         {
160             return default_point;
161         }
162 
163         auto og   = output->get_relative_geometry();
164         auto bbox = get_relative_transformed_view(og);
165 
166         if (!(bbox & point))
167         {
168             auto region = ((wf::region_t{og} ^ wf::region_t{bbox}) &
169                 wf::region_t{{(int)point.x, (int)point.y, 1, 1}});
170             if (!region.empty())
171             {
172                 auto wm = view->get_wm_geometry();
173 
174                 return wf::pointf_t{wm.x - 1.0, wm.y * 1.0};
175             }
176         }
177 
178         return default_point;
179     }
180 
render_box(wf::texture_t src_tex,wlr_box src_box,wlr_box scissor_box,const wf::framebuffer_t & target_fb)181     void render_box(wf::texture_t src_tex, wlr_box src_box,
182         wlr_box scissor_box, const wf::framebuffer_t& target_fb) override
183     {
184         auto output = view->get_output();
185 
186         if (!output)
187         {
188             return;
189         }
190 
191         auto og = output->get_relative_geometry();
192         wf::region_t scissor_region{scissor_box};
193 
194         if (transparent_behind_views)
195         {
196             auto bbox = get_relative_transformed_view(og);
197             scissor_region ^= wf::region_t{bbox};
198         }
199 
200         for (auto& b : scissor_region)
201         {
202             OpenGL::render_begin(target_fb);
203             target_fb.logic_scissor(wlr_box_from_pixman_box(b));
204             OpenGL::clear({0, 0, 0, 1});
205             OpenGL::render_end();
206         }
207 
208         wf::view_2D::render_box(src_tex, src_box, scissor_box, target_fb);
209     }
210 };
211 
212 class fullscreen_background
213 {
214   public:
215     wf::geometry_t saved_geometry;
216     wf::geometry_t undecorated_geometry;
217     fullscreen_transformer *transformer;
218     fullscreen_subsurface *black_border = nullptr;
219 
fullscreen_background(wayfire_view view)220     fullscreen_background(wayfire_view view)
221     {}
222 
~fullscreen_background()223     ~fullscreen_background()
224     {}
225 };
226 
227 class wayfire_force_fullscreen;
228 
229 std::map<wf::output_t*,
230     wayfire_force_fullscreen*> wayfire_force_fullscreen_instances;
231 
232 class wayfire_force_fullscreen : public wf::plugin_interface_t
233 {
234     std::string background_name;
235     bool motion_connected = false;
236     std::map<wayfire_view, std::unique_ptr<fullscreen_background>> backgrounds;
237     wf::option_wrapper_t<bool> preserve_aspect{"force-fullscreen/preserve_aspect"};
238     wf::option_wrapper_t<bool> constrain_pointer{"force-fullscreen/constrain_pointer"};
239     wf::option_wrapper_t<double> scale_x_factor{"force-fullscreen/x_skew"};
240     wf::option_wrapper_t<double> scale_y_factor{"force-fullscreen/y_skew"};
241     wf::option_wrapper_t<std::string> constraint_area{
242         "force-fullscreen/constraint_area"};
243     wf::option_wrapper_t<bool> transparent_behind_views{
244         "force-fullscreen/transparent_behind_views"};
245     wf::option_wrapper_t<wf::keybinding_t> key_toggle_fullscreen{
246         "force-fullscreen/key_toggle_fullscreen"};
247 
248   public:
init()249     void init() override
250     {
251         this->grab_interface->name = "force-fullscreen";
252         this->grab_interface->capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR;
253         background_name = this->grab_interface->name;
254 
255         output->add_key(key_toggle_fullscreen, &on_toggle_fullscreen);
256         transparent_behind_views.set_callback(option_changed);
257         wayfire_force_fullscreen_instances[output] = this;
258         constrain_pointer.set_callback(constrain_pointer_option_changed);
259         preserve_aspect.set_callback(option_changed);
260         scale_x_factor.set_callback(skew_changed);
261         scale_y_factor.set_callback(skew_changed);
262     }
263 
ensure_subsurface(wayfire_view view)264     void ensure_subsurface(wayfire_view view)
265     {
266         auto pair = backgrounds.find(view);
267 
268         if (pair == backgrounds.end())
269         {
270             return;
271         }
272 
273         auto& background = pair->second;
274 
275         if (!background->black_border)
276         {
277             std::unique_ptr<fullscreen_subsurface> subsurface =
278                 std::make_unique<fullscreen_subsurface>(view);
279             nonstd::observer_ptr<fullscreen_subsurface> ptr{subsurface};
280             view->add_subsurface(std::move(subsurface), true);
281             background->black_border = ptr.get();
282         }
283     }
284 
destroy_subsurface(wayfire_view view)285     void destroy_subsurface(wayfire_view view)
286     {
287         auto pair = backgrounds.find(view);
288 
289         if (pair == backgrounds.end())
290         {
291             return;
292         }
293 
294         auto& background = pair->second;
295 
296         if (background->black_border)
297         {
298             wf::emit_map_state_change(background->black_border);
299             background->black_border->_mapped = false;
300             view->remove_subsurface(background->black_border);
301             background->black_border = nullptr;
302         }
303     }
304 
setup_transform(wayfire_view view)305     void setup_transform(wayfire_view view)
306     {
307         auto og = output->get_relative_geometry();
308         auto vg = view->get_wm_geometry();
309 
310         double scale_x = (double)og.width / vg.width;
311         double scale_y = (double)og.height / vg.height;
312         double translation_x = (og.width - vg.width) / 2.0;
313         double translation_y = (og.height - vg.height) / 2.0;
314 
315         if (preserve_aspect)
316         {
317             scale_x = scale_y = std::min(scale_x, scale_y);
318         }
319 
320         scale_x += (scale_x_factor * 0.01);
321         scale_y += (scale_y_factor * 0.01);
322 
323         wlr_box box;
324         box.width  = std::floor((vg.width - 2) * scale_x);
325         box.height = std::floor((vg.height - 2) * scale_y);
326         box.x = std::ceil((og.width - box.width) / 2.0);
327         box.y = std::ceil((og.height - box.height) / 2.0);
328 
329         if (preserve_aspect)
330         {
331             ensure_subsurface(view);
332             scale_x += 1.0 / vg.width;
333             translation_x -= 1.0;
334         } else
335         {
336             destroy_subsurface(view);
337         }
338 
339         backgrounds[view]->transformer->transformed_view_box = box;
340         backgrounds[view]->transformer->scale_x = scale_x;
341         backgrounds[view]->transformer->scale_y = scale_y;
342         backgrounds[view]->transformer->translation_x = translation_x;
343         backgrounds[view]->transformer->translation_y = translation_y;
344 
345         view->damage();
346     }
347 
update_backgrounds()348     void update_backgrounds()
349     {
350         for (auto& b : backgrounds)
351         {
352             destroy_subsurface(b.first);
353             setup_transform(b.first);
354         }
355     }
356 
toggle_fullscreen(wayfire_view view)357     bool toggle_fullscreen(wayfire_view view)
358     {
359         if (!output->can_activate_plugin(grab_interface))
360         {
361             return false;
362         }
363 
364         wlr_box saved_geometry = view->get_wm_geometry();
365 
366         auto background = backgrounds.find(view);
367         bool fullscreen = background == backgrounds.end() ? true : false;
368 
369         view->set_fullscreen(fullscreen);
370 
371         wlr_box undecorated_geometry = view->get_wm_geometry();
372 
373         if (!fullscreen)
374         {
375             deactivate(view);
376 
377             return true;
378         }
379 
380         activate(view);
381 
382         background = backgrounds.find(view);
383         if (background == backgrounds.end())
384         {
385             /* Should never happen */
386             deactivate(view);
387 
388             return true;
389         }
390 
391         background->second->undecorated_geometry = undecorated_geometry;
392         background->second->saved_geometry = saved_geometry;
393 
394         setup_transform(view);
395 
396         return true;
397     }
398 
399     wf::key_callback on_toggle_fullscreen = [=] (auto)
__anon60bb74470102(auto) 400     {
401         auto view = output->get_active_view();
402 
403         if (!view || (view->role == wf::VIEW_ROLE_DESKTOP_ENVIRONMENT))
404         {
405             return false;
406         }
407 
408         return toggle_fullscreen(view);
409     };
410 
activate(wayfire_view view)411     void activate(wayfire_view view)
412     {
413         view->move(0, 0);
414         backgrounds[view] = std::make_unique<fullscreen_background>(view);
415         backgrounds[view]->transformer = new fullscreen_transformer(view);
416         view->add_transformer(std::unique_ptr<fullscreen_transformer>(backgrounds[
417             view]->transformer), background_name);
418         output->connect_signal("output-configuration-changed",
419             &output_config_changed);
420         wf::get_core().connect_signal("view-pre-moved-to-output",
421             &view_output_changed);
422         output->connect_signal("view-fullscreen-request", &view_fullscreened);
423         view->connect_signal("geometry-changed", &view_geometry_changed);
424         output->connect_signal("view-unmapped", &view_unmapped);
425         output->connect_signal("view-focused", &view_focused);
426         if (constrain_pointer)
427         {
428             connect_motion_signal();
429         }
430     }
431 
deactivate(wayfire_view view)432     void deactivate(wayfire_view view)
433     {
434         auto background = backgrounds.find(view);
435 
436         if (background == backgrounds.end())
437         {
438             return;
439         }
440 
441         if (backgrounds.size() == 1)
442         {
443             view_geometry_changed.disconnect();
444             output_config_changed.disconnect();
445             view_output_changed.disconnect();
446             view_fullscreened.disconnect();
447             view_unmapped.disconnect();
448             disconnect_motion_signal();
449             view_focused.disconnect();
450         }
451 
452         auto og = output->get_relative_geometry();
453         auto ws = background->second->transformer->get_workspace(og);
454         view->move(
455             background->second->saved_geometry.x + ws.x * og.width,
456             background->second->saved_geometry.y + ws.y * og.height);
457         if (view->get_transformer(background_name))
458         {
459             view->pop_transformer(background_name);
460         }
461 
462         destroy_subsurface(view);
463         backgrounds.erase(view);
464     }
465 
connect_motion_signal()466     void connect_motion_signal()
467     {
468         if (motion_connected)
469         {
470             return;
471         }
472 
473         wf::get_core().connect_signal("pointer_motion", &on_motion_event);
474         motion_connected = true;
475     }
476 
disconnect_motion_signal()477     void disconnect_motion_signal()
478     {
479         if (!motion_connected)
480         {
481             return;
482         }
483 
484         wf::get_core().disconnect_signal("pointer_motion", &on_motion_event);
485         motion_connected = false;
486     }
487 
update_motion_signal(wayfire_view view)488     void update_motion_signal(wayfire_view view)
489     {
490         if (view && (view->get_output() == output) && constrain_pointer &&
491             (backgrounds.find(view) != backgrounds.end()))
492         {
493             connect_motion_signal();
494 
495             return;
496         }
497 
498         disconnect_motion_signal();
499     }
500 
501     wf::config::option_base_t::updated_callback_t constrain_pointer_option_changed =
502         [=] ()
__anon60bb74470202() 503     {
504         auto view = output->get_active_view();
505 
506         update_motion_signal(view);
507     };
508 
509     wf::config::option_base_t::updated_callback_t option_changed = [=] ()
__anon60bb74470302() 510     {
511         update_backgrounds();
512     };
513 
514     wf::config::option_base_t::updated_callback_t skew_changed =
515         [=] ()
__anon60bb74470402() 516     {
517         for (auto& b : backgrounds)
518         {
519             auto v = b.first;
520             setup_transform(v);
521         }
522     };
523 
524     wf::signal_callback_t on_motion_event = [=] (wf::signal_data_t *data)
__anon60bb74470502(wf::signal_data_t *data) 525     {
526         auto ev = static_cast<
527             wf::input_event_signal<wlr_event_pointer_motion>*>(data);
528 
529         if (wf::get_core().get_active_output() != output)
530         {
531             return;
532         }
533 
534         if (!output->can_activate_plugin(grab_interface))
535         {
536             return;
537         }
538 
539         auto cursor = wf::get_core().get_cursor_position();
540         auto last_cursor = cursor;
541         auto og = output->get_layout_geometry();
542 
543         cursor.x += ev->event->delta_x;
544         cursor.y += ev->event->delta_y;
545 
546         for (auto& b : backgrounds)
547         {
548             auto view = output->get_active_view();
549             wlr_box box;
550 
551             box    = b.second->transformer->transformed_view_box;
552             box.x += og.x;
553             box.y += og.y;
554 
555             if (std::string(constraint_area) == "output")
556             {
557                 box = og;
558             }
559 
560             if ((b.first == view) &&
561                 !(box & wf::pointf_t{cursor.x, cursor.y}))
562             {
563                 wlr_box_closest_point(&box, cursor.x, cursor.y,
564                     &cursor.x, &cursor.y);
565                 ev->event->delta_x = ev->event->unaccel_dx =
566                     cursor.x - last_cursor.x;
567                 ev->event->delta_y = ev->event->unaccel_dy =
568                     cursor.y - last_cursor.y;
569 
570                 return;
571             }
572         }
573     };
574 
575     wf::signal_connection_t output_config_changed{[this] (wf::signal_data_t *data)
__anon60bb74470602() 576         {
577             wf::output_configuration_changed_signal *signal =
578                 static_cast<wf::output_configuration_changed_signal*>(data);
579 
580             if (!signal->changed_fields)
581             {
582                 return;
583             }
584 
585             if (signal->changed_fields & wf::OUTPUT_SOURCE_CHANGE)
586             {
587                 return;
588             }
589 
590             update_backgrounds();
591         }
592     };
593 
594     wf::signal_connection_t view_output_changed{[this] (wf::signal_data_t *data)
__anon60bb74470702() 595         {
596             auto signal = static_cast<wf::view_pre_moved_to_output_signal*>(data);
597             auto view   = signal->view;
598             auto background = backgrounds.find(view);
599 
600             if (background == backgrounds.end())
601             {
602                 return;
603             }
604 
605             toggle_fullscreen(view);
606 
607             auto instance = wayfire_force_fullscreen_instances[signal->new_output];
608             instance->toggle_fullscreen(view);
609         }
610     };
611 
612     wf::signal_connection_t view_focused{[this] (wf::signal_data_t *data)
__anon60bb74470802() 613         {
614             auto view = get_signaled_view(data);
615 
616             update_motion_signal(view);
617         }
618     };
619 
620     wf::signal_connection_t view_unmapped{[this] (wf::signal_data_t *data)
__anon60bb74470902() 621         {
622             auto view = get_signaled_view(data);
623             auto background = backgrounds.find(view);
624 
625             if (background == backgrounds.end())
626             {
627                 return;
628             }
629 
630             toggle_fullscreen(view);
631         }
632     };
633 
634     wf::signal_connection_t view_fullscreened{[this] (wf::signal_data_t *data)
__anon60bb74470a02() 635         {
636             auto signal = static_cast<wf::view_fullscreen_signal*>(data);
637             auto view   = signal->view;
638             auto background = backgrounds.find(view);
639 
640             if (background == backgrounds.end())
641             {
642                 return;
643             }
644 
645             if (signal->state || signal->carried_out)
646             {
647                 return;
648             }
649 
650             toggle_fullscreen(view);
651 
652             signal->carried_out = true;
653         }
654     };
655 
656     wf::signal_connection_t view_geometry_changed{[this] (wf::signal_data_t *data)
__anon60bb74470b02() 657         {
658             auto view = get_signaled_view(data);
659             auto background = backgrounds.find(view);
660 
661             if (background == backgrounds.end())
662             {
663                 return;
664             }
665 
666             view->resize(
667                 background->second->undecorated_geometry.width,
668                 background->second->undecorated_geometry.height);
669             setup_transform(view);
670         }
671     };
672 
fini()673     void fini() override
674     {
675         output->rem_binding(&on_toggle_fullscreen);
676         wayfire_force_fullscreen_instances.erase(output);
677 
678         for (auto& b : backgrounds)
679         {
680             toggle_fullscreen(b.first);
681         }
682     }
683 };
684 
685 DECLARE_WAYFIRE_PLUGIN(wayfire_force_fullscreen);
686