1 #include <wayfire/util/log.hpp>
2 #include "../core/core-impl.hpp"
3 #include "view-impl.hpp"
4 #include "wayfire/opengl.hpp"
5 #include "wayfire/output.hpp"
6 #include "wayfire/view.hpp"
7 #include "wayfire/view-transform.hpp"
8 #include "wayfire/decorator.hpp"
9 #include "wayfire/workspace-manager.hpp"
10 #include "wayfire/render-manager.hpp"
11 #include "xdg-shell.hpp"
12 #include "../output/gtk-shell.hpp"
13 
14 #include <algorithm>
15 #include <glm/glm.hpp>
16 #include "wayfire/signal-definitions.hpp"
17 
reposition_relative_to_parent(wayfire_view view)18 static void reposition_relative_to_parent(wayfire_view view)
19 {
20     if (!view->parent)
21     {
22         return;
23     }
24 
25     auto parent_geometry = view->parent->get_wm_geometry();
26     auto wm_geometry     = view->get_wm_geometry();
27     auto scr_size = view->get_output()->get_screen_size();
28     // Guess which workspace the parent is on
29     wf::point_t center = {
30         parent_geometry.x + parent_geometry.width / 2,
31         parent_geometry.y + parent_geometry.height / 2,
32     };
33     wf::point_t parent_ws = {
34         (int)std::floor(1.0 * center.x / scr_size.width),
35         (int)std::floor(1.0 * center.y / scr_size.height),
36     };
37 
38     auto workarea = view->get_output()->render->get_ws_box(
39         view->get_output()->workspace->get_current_workspace() + parent_ws);
40     if (view->parent->is_mapped())
41     {
42         auto parent_g = view->parent->get_wm_geometry();
43         wm_geometry.x = parent_g.x + (parent_g.width - wm_geometry.width) / 2;
44         wm_geometry.y = parent_g.y + (parent_g.height - wm_geometry.height) / 2;
45     } else
46     {
47         /* if we have a parent which still isn't mapped, we cannot determine
48          * the view's position, so we center it on the screen */
49         wm_geometry.x = workarea.width / 2 - wm_geometry.width / 2;
50         wm_geometry.y = workarea.height / 2 - wm_geometry.height / 2;
51     }
52 
53     /* make sure view is visible afterwards */
54     wm_geometry = wf::clamp(wm_geometry, workarea);
55     view->move(wm_geometry.x, wm_geometry.y);
56     if ((wm_geometry.width != view->get_wm_geometry().width) ||
57         (wm_geometry.height != view->get_wm_geometry().height))
58     {
59         view->resize(wm_geometry.width, wm_geometry.height);
60     }
61 }
62 
unset_toplevel_parent(wayfire_view view)63 static void unset_toplevel_parent(wayfire_view view)
64 {
65     if (view->parent)
66     {
67         auto& container = view->parent->children;
68         auto it = std::remove(container.begin(), container.end(), view);
69         container.erase(it, container.end());
70     }
71 }
72 
find_toplevel_parent(wayfire_view view)73 static wayfire_view find_toplevel_parent(wayfire_view view)
74 {
75     while (view->parent)
76     {
77         view = view->parent;
78     }
79 
80     return view;
81 }
82 
83 /**
84  * Check whether the toplevel parent needs refocus.
85  * This may be needed because when focusing a view, its topmost child is given
86  * keyboard focus. When the parent-child relations change, it may happen that
87  * the parent needs to be focused again, this time with a different keyboard
88  * focus surface.
89  */
check_refocus_parent(wayfire_view view)90 static void check_refocus_parent(wayfire_view view)
91 {
92     view = find_toplevel_parent(view);
93     if (view->get_output() && (view->get_output()->get_active_view() == view))
94     {
95         view->get_output()->focus_view(view, false);
96     }
97 }
98 
set_toplevel_parent(wayfire_view new_parent)99 void wf::view_interface_t::set_toplevel_parent(wayfire_view new_parent)
100 {
101     auto old_parent = parent;
102     if (parent != new_parent)
103     {
104         /* Erase from the old parent */
105         unset_toplevel_parent(self());
106 
107         /* Add in the list of the new parent */
108         if (new_parent)
109         {
110             new_parent->children.insert(new_parent->children.begin(), self());
111         }
112 
113         parent = new_parent;
114         desktop_state_updated();
115     }
116 
117     if (parent)
118     {
119         /* Make sure the view is available only as a child */
120         if (this->get_output())
121         {
122             this->get_output()->workspace->remove_view(self());
123         }
124 
125         this->set_output(parent->get_output());
126         /* if the view isn't mapped, then it will be positioned properly in map() */
127         if (is_mapped())
128         {
129             reposition_relative_to_parent(self());
130         }
131 
132         check_refocus_parent(parent);
133     } else if (old_parent)
134     {
135         /* At this point, we are a regular view. We should try to position ourselves
136          * directly above the old parent */
137         if (this->get_output())
138         {
139             this->get_output()->workspace->add_view(
140                 self(), wf::LAYER_WORKSPACE);
141 
142             check_refocus_parent(old_parent);
143             this->get_output()->workspace->restack_above(self(),
144                 find_toplevel_parent(old_parent));
145         }
146     }
147 }
148 
enumerate_views(bool mapped_only)149 std::vector<wayfire_view> wf::view_interface_t::enumerate_views(
150     bool mapped_only)
151 {
152     if (!this->is_mapped() && mapped_only)
153     {
154         return {};
155     }
156 
157     std::vector<wayfire_view> result;
158     for (auto& v : this->children)
159     {
160         auto cdr = v->enumerate_views(mapped_only);
161         result.insert(result.end(), cdr.begin(), cdr.end());
162     }
163 
164     result.push_back(self());
165 
166     return result;
167 }
168 
set_role(view_role_t new_role)169 void wf::view_interface_t::set_role(view_role_t new_role)
170 {
171     role = new_role;
172     damage();
173 }
174 
to_string() const175 std::string wf::view_interface_t::to_string() const
176 {
177     return "view-" + wf::object_base_t::to_string();
178 }
179 
self()180 wayfire_view wf::view_interface_t::self()
181 {
182     return wayfire_view(this);
183 }
184 
185 /** Set the view's output. */
set_output(wf::output_t * new_output)186 void wf::view_interface_t::set_output(wf::output_t *new_output)
187 {
188     /* Make sure the view doesn't stay on the old output */
189     if (get_output() && (get_output() != new_output))
190     {
191         /* Emit view-layer-detached first */
192         get_output()->workspace->remove_view(self());
193 
194         view_detached_signal data;
195         data.view = self();
196         get_output()->emit_signal("view-disappeared", &data);
197         get_output()->emit_signal("view-detached", &data);
198     }
199 
200     _output_signal data;
201     data.output = get_output();
202 
203     surface_interface_t::set_output(new_output);
204     if ((new_output != data.output) && new_output)
205     {
206         view_attached_signal data;
207         data.view = self();
208         get_output()->emit_signal("view-attached", &data);
209     }
210 
211     emit_signal("set-output", &data);
212 
213     for (auto& view : this->children)
214     {
215         view->set_output(new_output);
216     }
217 }
218 
resize(int w,int h)219 void wf::view_interface_t::resize(int w, int h)
220 {
221     /* no-op */
222 }
223 
set_geometry(wf::geometry_t g)224 void wf::view_interface_t::set_geometry(wf::geometry_t g)
225 {
226     move(g.x, g.y);
227     resize(g.width, g.height);
228 }
229 
set_resizing(bool resizing,uint32_t edges)230 void wf::view_interface_t::set_resizing(bool resizing, uint32_t edges)
231 {
232     view_impl->update_windowed_geometry(self(), get_wm_geometry());
233     /* edges are reset on the next commit */
234     if (resizing)
235     {
236         this->view_impl->edges = edges;
237     }
238 
239     auto& in_resize = this->view_impl->in_continuous_resize;
240     in_resize += resizing ? 1 : -1;
241 
242     if (in_resize < 0)
243     {
244         LOGE("in_continuous_resize counter dropped below 0!");
245     }
246 }
247 
set_moving(bool moving)248 void wf::view_interface_t::set_moving(bool moving)
249 {
250     view_impl->update_windowed_geometry(self(), get_wm_geometry());
251     auto& in_move = this->view_impl->in_continuous_move;
252 
253     in_move += moving ? 1 : -1;
254     if (in_move < 0)
255     {
256         LOGE("in_continuous_move counter dropped below 0!");
257     }
258 }
259 
request_native_size()260 void wf::view_interface_t::request_native_size()
261 {
262     /* no-op */
263 }
264 
ping()265 void wf::view_interface_t::ping()
266 {
267     // Do nothing, specialized in the various shells
268 }
269 
close()270 void wf::view_interface_t::close()
271 {
272     /* no-op */
273 }
274 
get_wm_geometry()275 wf::geometry_t wf::view_interface_t::get_wm_geometry()
276 {
277     return get_output_geometry();
278 }
279 
get_bounding_box()280 wlr_box wf::view_interface_t::get_bounding_box()
281 {
282     return transform_region(get_untransformed_bounding_box());
283 }
284 
285 #define INVALID_COORDS(p) (std::isnan(p.x) || std::isnan(p.y))
286 
global_to_local_point(const wf::pointf_t & arg,wf::surface_interface_t * surface)287 wf::pointf_t wf::view_interface_t::global_to_local_point(const wf::pointf_t& arg,
288     wf::surface_interface_t *surface)
289 {
290     if (!is_mapped())
291     {
292         return arg;
293     }
294 
295     /* First, untransform the coordinates to make them relative to the view's
296      * internal coordinate system */
297     wf::pointf_t result = arg;
298     if (view_impl->transforms.size())
299     {
300         std::vector<wf::geometry_t> bb;
301         bb.reserve(view_impl->transforms.size());
302         auto box = get_untransformed_bounding_box();
303         bb.push_back(box);
304         view_impl->transforms.for_each([&] (auto& tr)
305         {
306             if (tr == view_impl->transforms.back())
307             {
308                 return;
309             }
310 
311             auto& transform = tr->transform;
312             box = transform->get_bounding_box(box, box);
313             bb.push_back(box);
314         });
315 
316         view_impl->transforms.for_each_reverse([&] (auto& tr)
317         {
318             if (INVALID_COORDS(result))
319             {
320                 return;
321             }
322 
323             auto& transform = tr->transform;
324             box = bb.back();
325             bb.pop_back();
326             result = transform->untransform_point(box, result);
327         });
328 
329         if (INVALID_COORDS(result))
330         {
331             return result;
332         }
333     }
334 
335     /* Make cooordinates relative to the view */
336     auto og = get_output_geometry();
337     result.x -= og.x;
338     result.y -= og.y;
339 
340     /* Go up from the surface, finding offsets */
341     while (surface && surface != this)
342     {
343         auto offset = surface->get_offset();
344         result.x -= offset.x;
345         result.y -= offset.y;
346 
347         surface = surface->priv->parent_surface;
348     }
349 
350     return result;
351 }
352 
map_input_coordinates(wf::pointf_t cursor,wf::pointf_t & local)353 wf::surface_interface_t*wf::view_interface_t::map_input_coordinates(
354     wf::pointf_t cursor, wf::pointf_t& local)
355 {
356     if (!is_mapped())
357     {
358         return nullptr;
359     }
360 
361     auto view_relative_coordinates =
362         global_to_local_point(cursor, nullptr);
363 
364     for (auto& child : enumerate_surfaces({0, 0}))
365     {
366         local.x = view_relative_coordinates.x - child.position.x;
367         local.y = view_relative_coordinates.y - child.position.y;
368 
369         if (child.surface->accepts_input(
370             std::floor(local.x), std::floor(local.y)))
371         {
372             return child.surface;
373         }
374     }
375 
376     return nullptr;
377 }
378 
is_focuseable() const379 bool wf::view_interface_t::is_focuseable() const
380 {
381     return view_impl->keyboard_focus_enabled;
382 }
383 
set_minimized(bool minim)384 void wf::view_interface_t::set_minimized(bool minim)
385 {
386     minimized = minim;
387     if (minimized)
388     {
389         view_disappeared_signal data;
390         data.view = self();
391         get_output()->emit_signal("view-disappeared", &data);
392         get_output()->workspace->add_view(self(), wf::LAYER_MINIMIZED);
393     } else
394     {
395         get_output()->workspace->add_view(self(), wf::LAYER_WORKSPACE);
396         get_output()->focus_view(self(), true);
397     }
398 
399     view_minimized_signal data;
400     data.view  = self();
401     data.state = minimized;
402     this->emit_signal("minimized", &data);
403     get_output()->emit_signal("view-minimized", &data);
404 
405     desktop_state_updated();
406 }
407 
set_sticky(bool sticky)408 void wf::view_interface_t::set_sticky(bool sticky)
409 {
410     if (this->sticky == sticky)
411     {
412         return;
413     }
414 
415     damage();
416     this->sticky = sticky;
417     damage();
418 
419     wf::view_set_sticky_signal data;
420     data.view = self();
421 
422     this->emit_signal("set-sticky", &data);
423     if (this->get_output())
424     {
425         this->get_output()->emit_signal("view-set-sticky", &data);
426     }
427 }
428 
set_tiled(uint32_t edges)429 void wf::view_interface_t::set_tiled(uint32_t edges)
430 {
431     if (edges)
432     {
433         view_impl->update_windowed_geometry(self(), get_wm_geometry());
434     }
435 
436     wf::view_tiled_signal data;
437     data.view = self();
438     data.old_edges = this->tiled_edges;
439     data.new_edges = edges;
440 
441     this->tiled_edges = edges;
442     if (view_impl->frame)
443     {
444         view_impl->frame->notify_view_tiled();
445     }
446 
447     this->emit_signal("tiled", &data);
448     if (this->get_output())
449     {
450         get_output()->emit_signal("view-tiled", &data);
451     }
452 
453     desktop_state_updated();
454 }
455 
set_fullscreen(bool full)456 void wf::view_interface_t::set_fullscreen(bool full)
457 {
458     /* When fullscreening a view, we want to store the last geometry it had
459      * before getting fullscreen so that we can restore to it */
460     if (full && !fullscreen)
461     {
462         view_impl->update_windowed_geometry(self(), get_wm_geometry());
463     }
464 
465     fullscreen = full;
466     if (view_impl->frame)
467     {
468         view_impl->frame->notify_view_fullscreen();
469     }
470 
471     view_fullscreen_signal data;
472     data.view  = self();
473     data.state = full;
474     data.desired_size = {0, 0, 0, 0};
475 
476     if (get_output())
477     {
478         get_output()->emit_signal("view-fullscreen", &data);
479     }
480 
481     this->emit_signal("fullscreen", &data);
482     desktop_state_updated();
483 }
484 
set_activated(bool active)485 void wf::view_interface_t::set_activated(bool active)
486 {
487     if (view_impl->frame)
488     {
489         view_impl->frame->notify_view_activated(active);
490     }
491 
492     activated = active;
493     desktop_state_updated();
494 }
495 
desktop_state_updated()496 void wf::view_interface_t::desktop_state_updated()
497 {
498     /* no-op */
499 }
500 
move_request()501 void wf::view_interface_t::move_request()
502 {
503     view_move_request_signal data;
504     data.view = self();
505     get_output()->emit_signal("view-move-request", &data);
506 }
507 
focus_request()508 void wf::view_interface_t::focus_request()
509 {
510     if (get_output())
511     {
512         view_focus_request_signal data;
513         data.view = self();
514         data.self_request = false;
515 
516         emit_signal("view-focus-request", &data);
517         wf::get_core().emit_signal("view-focus-request", &data);
518         if (!data.carried_out)
519         {
520             wf::get_core().focus_output(get_output());
521             get_output()->ensure_visible(self());
522             get_output()->focus_view(self(), true);
523         }
524     }
525 }
526 
resize_request(uint32_t edges)527 void wf::view_interface_t::resize_request(uint32_t edges)
528 {
529     view_resize_request_signal data;
530     data.view  = self();
531     data.edges = edges;
532     get_output()->emit_signal("view-resize-request", &data);
533 }
534 
tile_request(uint32_t edges)535 void wf::view_interface_t::tile_request(uint32_t edges)
536 {
537     if (get_output())
538     {
539         tile_request(edges, get_output()->workspace->get_current_workspace());
540     }
541 }
542 
543 /**
544  * Put a view on the given workspace.
545  */
move_to_workspace(wf::view_interface_t * view,wf::point_t workspace)546 static void move_to_workspace(wf::view_interface_t *view, wf::point_t workspace)
547 {
548     auto output = view->get_output();
549     auto wm_geometry = view->get_wm_geometry();
550     auto delta    = workspace - output->workspace->get_current_workspace();
551     auto scr_size = output->get_screen_size();
552 
553     wm_geometry.x += scr_size.width * delta.x;
554     wm_geometry.y += scr_size.height * delta.y;
555     view->move(wm_geometry.x, wm_geometry.y);
556 }
557 
update_windowed_geometry(wayfire_view self,wf::geometry_t geometry)558 void wf::view_interface_t::view_priv_impl::update_windowed_geometry(
559     wayfire_view self, wf::geometry_t geometry)
560 {
561     if (!self->is_mapped() || self->tiled_edges || this->in_continuous_move ||
562         this->in_continuous_resize)
563     {
564         return;
565     }
566 
567     this->last_windowed_geometry = geometry;
568     if (self->get_output())
569     {
570         this->windowed_geometry_workarea =
571             self->get_output()->workspace->get_workarea();
572     } else
573     {
574         this->windowed_geometry_workarea = {0, 0, -1, -1};
575     }
576 }
577 
calculate_windowed_geometry(wf::output_t * output)578 wf::geometry_t wf::view_interface_t::view_priv_impl::calculate_windowed_geometry(
579     wf::output_t *output)
580 {
581     if (!output || (windowed_geometry_workarea.width <= 0))
582     {
583         return last_windowed_geometry;
584     }
585 
586     const auto& geom     = last_windowed_geometry;
587     const auto& old_area = windowed_geometry_workarea;
588     const auto& new_area = output->workspace->get_workarea();
589     return {
590         .x = new_area.x + (geom.x - old_area.x) * new_area.width /
591             old_area.width,
592         .y = new_area.y + (geom.y - old_area.y) * new_area.height /
593             old_area.height,
594         .width  = geom.width * new_area.width / old_area.width,
595         .height = geom.height * new_area.height / old_area.height
596     };
597 }
598 
tile_request(uint32_t edges,wf::point_t workspace)599 void wf::view_interface_t::tile_request(uint32_t edges, wf::point_t workspace)
600 {
601     if (fullscreen || !get_output())
602     {
603         return;
604     }
605 
606     view_tile_request_signal data;
607     data.view  = self();
608     data.edges = edges;
609     data.workspace    = workspace;
610     data.desired_size = edges ? get_output()->workspace->get_workarea() :
611         view_impl->calculate_windowed_geometry(get_output());
612 
613     set_tiled(edges);
614     if (is_mapped())
615     {
616         get_output()->emit_signal("view-tile-request", &data);
617     }
618 
619     if (!data.carried_out)
620     {
621         if (data.desired_size.width > 0)
622         {
623             set_geometry(data.desired_size);
624         } else
625         {
626             request_native_size();
627         }
628 
629         move_to_workspace(this, workspace);
630     }
631 }
632 
minimize_request(bool state)633 void wf::view_interface_t::minimize_request(bool state)
634 {
635     if ((state == minimized) || !is_mapped())
636     {
637         return;
638     }
639 
640     view_minimize_request_signal data;
641     data.view  = self();
642     data.state = state;
643 
644     if (is_mapped())
645     {
646         get_output()->emit_signal("view-minimize-request", &data);
647         /* Some plugin (e.g animate) will take care of the request, so we need
648          * to just send proper state to foreign-toplevel clients */
649         if (data.carried_out)
650         {
651             minimized = state;
652             desktop_state_updated();
653             get_output()->refocus(self());
654         } else
655         {
656             /* Do the default minimization */
657             set_minimized(state);
658         }
659     }
660 }
661 
fullscreen_request(wf::output_t * out,bool state)662 void wf::view_interface_t::fullscreen_request(wf::output_t *out, bool state)
663 {
664     auto wo = (out ?: (get_output() ?: wf::get_core().get_active_output()));
665     if (wo)
666     {
667         fullscreen_request(wo, state,
668             wo->workspace->get_current_workspace());
669     }
670 }
671 
fullscreen_request(wf::output_t * out,bool state,wf::point_t workspace)672 void wf::view_interface_t::fullscreen_request(wf::output_t *out, bool state,
673     wf::point_t workspace)
674 {
675     auto wo = (out ?: (get_output() ?: wf::get_core().get_active_output()));
676     assert(wo);
677 
678     /* TODO: what happens if the view is moved to the other output, but not
679      * fullscreened? We should make sure that it stays visible there */
680     if (get_output() != wo)
681     {
682         wf::get_core().move_view_to_output(self(), wo, false);
683     }
684 
685     view_fullscreen_signal data;
686     data.view  = self();
687     data.state = state;
688     data.workspace    = workspace;
689     data.desired_size = get_output()->get_relative_geometry();
690 
691     if (!state)
692     {
693         data.desired_size = this->tiled_edges ?
694             this->get_output()->workspace->get_workarea() :
695             this->view_impl->calculate_windowed_geometry(get_output());
696     }
697 
698     set_fullscreen(state);
699     if (is_mapped())
700     {
701         wo->emit_signal("view-fullscreen-request", &data);
702     }
703 
704     if (!data.carried_out)
705     {
706         if (data.desired_size.width > 0)
707         {
708             set_geometry(data.desired_size);
709         } else
710         {
711             request_native_size();
712         }
713 
714         move_to_workspace(this, workspace);
715     }
716 }
717 
is_visible()718 bool wf::view_interface_t::is_visible()
719 {
720     if (view_impl->visibility_counter <= 0)
721     {
722         return false;
723     }
724 
725     if (is_mapped())
726     {
727         return true;
728     }
729 
730     /* If we have an unmapped view, then there are two cases:
731      *
732      * 1. View has been "destroyed". In this case, the view is visible as long
733      * as it has at least one reference (for ex. a plugin which shows unmap
734      * animation)
735      *
736      * 2. View hasn't been "destroyed", just unmapped. Here we need to have at
737      * least 2 references, which would mean that the view is in unmap animation.
738      */
739     if (view_impl->is_alive)
740     {
741         return view_impl->ref_cnt >= 2;
742     } else
743     {
744         return view_impl->ref_cnt >= 1;
745     }
746 }
747 
set_visible(bool visible)748 void wf::view_interface_t::set_visible(bool visible)
749 {
750     this->view_impl->visibility_counter += (visible ? 1 : -1);
751     if (this->view_impl->visibility_counter > 1)
752     {
753         LOGE("set_visible(true) called more often than set_visible(false)!");
754     }
755 
756     this->damage();
757 }
758 
damage()759 void wf::view_interface_t::damage()
760 {
761     auto bbox = get_untransformed_bounding_box();
762     view_impl->offscreen_buffer.cached_damage |= bbox;
763     view_damage_raw(self(), transform_region(bbox));
764 }
765 
get_minimize_hint()766 wlr_box wf::view_interface_t::get_minimize_hint()
767 {
768     return this->view_impl->minimize_hint;
769 }
770 
set_minimize_hint(wlr_box hint)771 void wf::view_interface_t::set_minimize_hint(wlr_box hint)
772 {
773     this->view_impl->minimize_hint = hint;
774 }
775 
should_be_decorated()776 bool wf::view_interface_t::should_be_decorated()
777 {
778     return false;
779 }
780 
get_decoration()781 nonstd::observer_ptr<wf::surface_interface_t> wf::view_interface_t::get_decoration()
782 {
783     return this->view_impl->decoration;
784 }
785 
set_decoration(surface_interface_t * frame)786 void wf::view_interface_t::set_decoration(surface_interface_t *frame)
787 {
788     if (this->view_impl->decoration == frame)
789     {
790         return;
791     }
792 
793     if (!frame)
794     {
795         damage();
796         if (view_impl->decoration)
797         {
798             this->remove_subsurface(view_impl->decoration);
799         }
800 
801         view_impl->decoration = nullptr;
802         view_impl->frame = nullptr;
803         emit_signal("decoration-changed", nullptr);
804 
805         return;
806     }
807 
808     assert(frame->priv->parent_surface == this);
809 
810     // Take wm geometry as it was before adding the frame */
811     auto wm = get_wm_geometry();
812 
813     /* First, delete old decoration if any */
814     damage();
815     if (view_impl->decoration)
816     {
817         this->remove_subsurface(view_impl->decoration);
818     }
819 
820     view_impl->decoration = frame;
821     view_impl->frame = dynamic_cast<wf::decorator_frame_t_t*>(frame);
822     assert(frame);
823 
824     /* Calculate the wm geometry of the view after adding the decoration.
825      *
826      * If the view is neither maximized nor fullscreen, then we want to expand
827      * the view geometry so that the actual view contents retain their size.
828      *
829      * For fullscreen and maximized views we want to "shrink" the view contents
830      * so that the total wm geometry remains the same as before. */
831     wf::geometry_t target_wm_geometry;
832     if (!fullscreen && !this->tiled_edges)
833     {
834         target_wm_geometry = view_impl->frame->expand_wm_geometry(wm);
835         // make sure that the view doesn't go outside of the screen or such
836         auto wa = get_output()->workspace->get_workarea();
837         auto visible = wf::geometry_intersection(target_wm_geometry, wa);
838         if (visible != target_wm_geometry)
839         {
840             target_wm_geometry.x = wm.x;
841             target_wm_geometry.y = wm.y;
842         }
843     } else if (fullscreen)
844     {
845         target_wm_geometry = get_output()->get_relative_geometry();
846     } else if (this->tiled_edges)
847     {
848         target_wm_geometry = get_output()->workspace->get_workarea();
849     }
850 
851     // notify the frame of the current size
852     view_impl->frame->notify_view_resized(get_wm_geometry());
853     // but request the target size, it will be sent to the frame on the
854     // next commit
855     set_geometry(target_wm_geometry);
856     damage();
857 
858     emit_signal("decoration-changed", nullptr);
859 }
860 
add_transformer(std::unique_ptr<wf::view_transformer_t> transformer)861 void wf::view_interface_t::add_transformer(
862     std::unique_ptr<wf::view_transformer_t> transformer)
863 {
864     add_transformer(std::move(transformer), "");
865 }
866 
add_transformer(std::unique_ptr<wf::view_transformer_t> transformer,std::string name)867 void wf::view_interface_t::add_transformer(
868     std::unique_ptr<wf::view_transformer_t> transformer, std::string name)
869 {
870     damage();
871 
872     auto tr = std::make_shared<wf::view_transform_block_t>();
873     tr->transform   = std::move(transformer);
874     tr->plugin_name = name;
875 
876     view_impl->transforms.emplace_at(std::move(tr), [&] (auto& other)
877     {
878         if (other->transform->get_z_order() >= tr->transform->get_z_order())
879         {
880             return view_impl->transforms.INSERT_BEFORE;
881         }
882 
883         return view_impl->transforms.INSERT_NONE;
884     });
885 
886     damage();
887 }
888 
get_transformer(std::string name)889 nonstd::observer_ptr<wf::view_transformer_t> wf::view_interface_t::get_transformer(
890     std::string name)
891 {
892     nonstd::observer_ptr<wf::view_transformer_t> result{nullptr};
893     view_impl->transforms.for_each([&] (auto& tr)
894     {
895         if (tr->plugin_name == name)
896         {
897             result = nonstd::make_observer(tr->transform.get());
898         }
899     });
900 
901     return result;
902 }
903 
pop_transformer(nonstd::observer_ptr<wf::view_transformer_t> transformer)904 void wf::view_interface_t::pop_transformer(
905     nonstd::observer_ptr<wf::view_transformer_t> transformer)
906 {
907     view_impl->transforms.remove_if([&] (auto& tr)
908     {
909         return tr->transform.get() == transformer.get();
910     });
911 
912     /* Since we can remove transformers while rendering the output, damaging it
913      * won't help at this stage (damage is already calculated).
914      *
915      * Instead, we directly damage the whole output for the next frame */
916     if (get_output())
917     {
918         get_output()->render->damage_whole_idle();
919     }
920 }
921 
pop_transformer(std::string name)922 void wf::view_interface_t::pop_transformer(std::string name)
923 {
924     pop_transformer(get_transformer(name));
925 }
926 
has_transformer()927 bool wf::view_interface_t::has_transformer()
928 {
929     return view_impl->transforms.size();
930 }
931 
get_untransformed_bounding_box()932 wf::geometry_t wf::view_interface_t::get_untransformed_bounding_box()
933 {
934     if (!is_mapped())
935     {
936         return view_impl->offscreen_buffer.geometry;
937     }
938 
939     auto bbox = get_output_geometry();
940     wf::region_t bounding_region = bbox;
941 
942     for (auto& child : enumerate_surfaces({bbox.x, bbox.y}))
943     {
944         auto dim = child.surface->get_size();
945         bounding_region |= {child.position.x, child.position.y,
946             dim.width, dim.height};
947     }
948 
949     return wlr_box_from_pixman_box(bounding_region.get_extents());
950 }
951 
get_bounding_box(std::string transformer)952 wlr_box wf::view_interface_t::get_bounding_box(std::string transformer)
953 {
954     return get_bounding_box(get_transformer(transformer));
955 }
956 
get_bounding_box(nonstd::observer_ptr<wf::view_transformer_t> transformer)957 wlr_box wf::view_interface_t::get_bounding_box(
958     nonstd::observer_ptr<wf::view_transformer_t> transformer)
959 {
960     return transform_region(get_untransformed_bounding_box(), transformer);
961 }
962 
transform_region(const wlr_box & region,nonstd::observer_ptr<wf::view_transformer_t> upto)963 wlr_box wf::view_interface_t::transform_region(const wlr_box& region,
964     nonstd::observer_ptr<wf::view_transformer_t> upto)
965 {
966     auto box  = region;
967     auto view = get_untransformed_bounding_box();
968 
969     bool computed_region = false;
970     view_impl->transforms.for_each([&] (auto& tr)
971     {
972         if (computed_region || (tr->transform.get() == upto.get()))
973         {
974             computed_region = true;
975 
976             return;
977         }
978 
979         box  = tr->transform->get_bounding_box(view, box);
980         view = tr->transform->get_bounding_box(view, view);
981     });
982 
983     return box;
984 }
985 
transform_region(const wlr_box & region,std::string transformer)986 wlr_box wf::view_interface_t::transform_region(const wlr_box& region,
987     std::string transformer)
988 {
989     return transform_region(region, get_transformer(transformer));
990 }
991 
transform_region(const wlr_box & region)992 wlr_box wf::view_interface_t::transform_region(const wlr_box& region)
993 {
994     return transform_region(region,
995         nonstd::observer_ptr<wf::view_transformer_t>(nullptr));
996 }
997 
transform_point(const wf::pointf_t & point)998 wf::pointf_t wf::view_interface_t::transform_point(const wf::pointf_t& point)
999 {
1000     auto result = point;
1001     auto view   = get_untransformed_bounding_box();
1002 
1003     view_impl->transforms.for_each([&] (auto& tr)
1004     {
1005         result = tr->transform->transform_point(view, result);
1006         view   = tr->transform->get_bounding_box(view, view);
1007     });
1008 
1009     return result;
1010 }
1011 
intersects_region(const wlr_box & region)1012 bool wf::view_interface_t::intersects_region(const wlr_box& region)
1013 {
1014     /* fallback to the whole transformed boundingbox, if it exists */
1015     if (!is_mapped())
1016     {
1017         return region & get_bounding_box();
1018     }
1019 
1020     auto origin = get_output_geometry();
1021     for (auto& child : enumerate_surfaces({origin.x, origin.y}))
1022     {
1023         wlr_box box = {child.position.x, child.position.y,
1024             child.surface->get_size().width, child.surface->get_size().height};
1025         box = transform_region(box);
1026 
1027         if (region & box)
1028         {
1029             return true;
1030         }
1031     }
1032 
1033     return false;
1034 }
1035 
get_transformed_opaque_region()1036 wf::region_t wf::view_interface_t::get_transformed_opaque_region()
1037 {
1038     if (!is_mapped())
1039     {
1040         return {};
1041     }
1042 
1043     auto obox = get_untransformed_bounding_box();
1044     auto og   = get_output_geometry();
1045 
1046     wf::region_t opaque;
1047     for (auto& surf : enumerate_surfaces({og.x, og.y}))
1048     {
1049         opaque |= surf.surface->get_opaque_region(surf.position);
1050     }
1051 
1052     auto bbox = obox;
1053     this->view_impl->transforms.for_each(
1054         [&] (const std::shared_ptr<view_transform_block_t> tr)
1055     {
1056         opaque = tr->transform->transform_opaque_region(bbox, opaque);
1057         bbox   = tr->transform->get_bounding_box(bbox, bbox);
1058     });
1059 
1060     return opaque;
1061 }
1062 
render_transformed(const wf::framebuffer_t & framebuffer,const wf::region_t & damage)1063 bool wf::view_interface_t::render_transformed(const wf::framebuffer_t& framebuffer,
1064     const wf::region_t& damage)
1065 {
1066     if (!is_mapped() && !view_impl->offscreen_buffer.valid())
1067     {
1068         return false;
1069     }
1070 
1071     wf::geometry_t obox = get_untransformed_bounding_box();
1072     wf::texture_t previous_texture;
1073     float texture_scale;
1074 
1075     if (is_mapped() && (enumerate_surfaces().size() == 1) && get_wlr_surface())
1076     {
1077         /* Optimized case: there is a single mapped surface.
1078          * We can directly start with its texture */
1079         previous_texture = wf::texture_t{this->get_wlr_surface()};
1080         texture_scale    = this->get_wlr_surface()->current.scale;
1081     } else
1082     {
1083         take_snapshot();
1084         previous_texture = wf::texture_t{view_impl->offscreen_buffer.tex};
1085         texture_scale    = view_impl->offscreen_buffer.scale;
1086     }
1087 
1088     /* We keep a shared_ptr to the previous transform which we executed, so that
1089      * even if it gets removed, its texture remains valid.
1090      *
1091      * NB: we do not call previous_transform's transformer functions after the
1092      * cycle is complete, because the memory might have already been freed.
1093      * We only know that the texture is still alive. */
1094     std::shared_ptr<view_transform_block_t> previous_transform = nullptr;
1095 
1096     /* final_transform is the one that should render to the screen */
1097     std::shared_ptr<view_transform_block_t> final_transform = nullptr;
1098 
1099     /* Render the view passing its snapshot through the transformers.
1100      * For each transformer except the last we render on offscreen buffers,
1101      * and the last one is rendered to the real fb. */
1102     auto& transforms = view_impl->transforms;
1103     transforms.for_each([&] (auto& transform) -> void
1104     {
1105         /* Last transform is handled separately */
1106         if (transform == transforms.back())
1107         {
1108             final_transform = transform;
1109 
1110             return;
1111         }
1112 
1113         /* Calculate size after this transform */
1114         auto transformed_box =
1115             transform->transform->get_bounding_box(obox, obox);
1116         int scaled_width  = transformed_box.width * texture_scale;
1117         int scaled_height = transformed_box.height * texture_scale;
1118 
1119         /* Prepare buffer to store result after the transform */
1120         OpenGL::render_begin();
1121         transform->fb.allocate(scaled_width, scaled_height);
1122         transform->fb.scale    = texture_scale;
1123         transform->fb.geometry = transformed_box;
1124         transform->fb.bind(); // bind buffer to clear it
1125         OpenGL::clear({0, 0, 0, 0});
1126         OpenGL::render_end();
1127 
1128         /* Actually render the transform to the next framebuffer */
1129         transform->transform->render_with_damage(previous_texture, obox,
1130             wf::region_t{transformed_box}, transform->fb);
1131 
1132         previous_transform = transform;
1133         previous_texture   = previous_transform->fb.tex;
1134         obox = transformed_box;
1135     });
1136 
1137     /* This can happen in two ways:
1138      * 1. The view is unmapped, and no snapshot
1139      * 2. The last transform was deleted while iterating, so now the last
1140      *    transform is invalid in the list
1141      *
1142      * In both cases, we simply render whatever contents we have to the
1143      * framebuffer. */
1144     if (final_transform == nullptr)
1145     {
1146         OpenGL::render_begin(framebuffer);
1147         auto matrix = framebuffer.get_orthographic_projection();
1148         gl_geometry src_geometry = {
1149             1.0f * obox.x, 1.0f * obox.y,
1150             1.0f * obox.x + 1.0f * obox.width,
1151             1.0f * obox.y + 1.0f * obox.height,
1152         };
1153 
1154         for (const auto& rect : damage)
1155         {
1156             framebuffer.logic_scissor(wlr_box_from_pixman_box(rect));
1157             OpenGL::render_transformed_texture(previous_texture, src_geometry,
1158                 {}, matrix);
1159         }
1160 
1161         OpenGL::render_end();
1162     } else
1163     {
1164         /* Regular case, just call the last transformer, but render directly
1165          * to the target framebuffer */
1166         final_transform->transform->render_with_damage(previous_texture, obox,
1167             damage, framebuffer);
1168     }
1169 
1170     return true;
1171 }
1172 
view_transform_block_t()1173 wf::view_transform_block_t::view_transform_block_t()
1174 {}
~view_transform_block_t()1175 wf::view_transform_block_t::~view_transform_block_t()
1176 {
1177     OpenGL::render_begin();
1178     this->fb.release();
1179     OpenGL::render_end();
1180 }
1181 
take_snapshot()1182 void wf::view_interface_t::take_snapshot()
1183 {
1184     if (!is_mapped())
1185     {
1186         return;
1187     }
1188 
1189     auto& offscreen_buffer = view_impl->offscreen_buffer;
1190 
1191     auto buffer_geometry = get_untransformed_bounding_box();
1192     offscreen_buffer.geometry = buffer_geometry;
1193 
1194     float scale = get_output()->handle->scale;
1195 
1196     offscreen_buffer.cached_damage &= buffer_geometry;
1197     /* Nothing has changed, the last buffer is still valid */
1198     if (offscreen_buffer.cached_damage.empty())
1199     {
1200         return;
1201     }
1202 
1203     int scaled_width  = buffer_geometry.width * scale;
1204     int scaled_height = buffer_geometry.height * scale;
1205     if ((scaled_width != offscreen_buffer.viewport_width) ||
1206         (scaled_height != offscreen_buffer.viewport_height))
1207     {
1208         offscreen_buffer.cached_damage |= buffer_geometry;
1209     }
1210 
1211     OpenGL::render_begin();
1212     offscreen_buffer.allocate(scaled_width, scaled_height);
1213     offscreen_buffer.scale = scale;
1214     offscreen_buffer.bind();
1215     for (auto& box : offscreen_buffer.cached_damage)
1216     {
1217         offscreen_buffer.logic_scissor(wlr_box_from_pixman_box(box));
1218         OpenGL::clear({0, 0, 0, 0});
1219     }
1220 
1221     OpenGL::render_end();
1222 
1223     auto output_geometry = get_output_geometry();
1224     auto children = enumerate_surfaces({output_geometry.x, output_geometry.y});
1225     for (auto& child : wf::reverse(children))
1226     {
1227         wlr_box child_box{
1228             child.position.x,
1229             child.position.y,
1230             child.surface->get_size().width,
1231             child.surface->get_size().height
1232         };
1233 
1234         child.surface->simple_render(offscreen_buffer,
1235             child.position.x, child.position.y,
1236             offscreen_buffer.cached_damage & child_box);
1237     }
1238 
1239     offscreen_buffer.cached_damage.clear();
1240 }
1241 
view_interface_t()1242 wf::view_interface_t::view_interface_t()
1243 {
1244     this->view_impl = std::make_unique<wf::view_interface_t::view_priv_impl>();
1245     take_ref();
1246 }
1247 
take_ref()1248 void wf::view_interface_t::take_ref()
1249 {
1250     ++view_impl->ref_cnt;
1251 }
1252 
unref()1253 void wf::view_interface_t::unref()
1254 {
1255     --view_impl->ref_cnt;
1256     if (view_impl->ref_cnt <= 0)
1257     {
1258         destruct();
1259     }
1260 }
1261 
initialize()1262 void wf::view_interface_t::initialize()
1263 {}
1264 
deinitialize()1265 void wf::view_interface_t::deinitialize()
1266 {
1267     auto children = this->children;
1268     for (auto ch : children)
1269     {
1270         ch->set_toplevel_parent(nullptr);
1271     }
1272 
1273     set_decoration(nullptr);
1274 
1275     this->clear_subsurfaces();
1276     this->view_impl->transforms.clear();
1277     this->_clear_data();
1278 
1279     OpenGL::render_begin();
1280     this->view_impl->offscreen_buffer.release();
1281     OpenGL::render_end();
1282 }
1283 
~view_interface_t()1284 wf::view_interface_t::~view_interface_t()
1285 {
1286     /* Note: at this point, it is invalid to call most functions */
1287     unset_toplevel_parent(self());
1288 }
1289 
damage_surface_box(const wlr_box & box)1290 void wf::view_interface_t::damage_surface_box(const wlr_box& box)
1291 {
1292     auto obox = get_output_geometry();
1293 
1294     auto damaged = box;
1295     damaged.x += obox.x;
1296     damaged.y += obox.y;
1297     view_impl->offscreen_buffer.cached_damage |= damaged;
1298     view_damage_raw(self(), transform_region(damaged));
1299 }
1300 
view_damage_raw(wayfire_view view,const wlr_box & box)1301 void wf::view_damage_raw(wayfire_view view, const wlr_box& box)
1302 {
1303     auto output = view->get_output();
1304     if (!output)
1305     {
1306         return;
1307     }
1308 
1309     /* Sticky views are visible on all workspaces. */
1310     if (view->sticky)
1311     {
1312         auto wsize = output->workspace->get_workspace_grid_size();
1313         auto cws   = output->workspace->get_current_workspace();
1314 
1315         /* Damage only the visible region of the shell view.
1316          * This prevents hidden panels from spilling damage onto other workspaces */
1317         wlr_box ws_box = output->get_relative_geometry();
1318         wlr_box visible_damage = geometry_intersection(box, ws_box);
1319         for (int i = 0; i < wsize.width; i++)
1320         {
1321             for (int j = 0; j < wsize.height; j++)
1322             {
1323                 const int dx = (i - cws.x) * ws_box.width;
1324                 const int dy = (j - cws.y) * ws_box.height;
1325                 output->render->damage(visible_damage + wf::point_t{dx, dy});
1326             }
1327         }
1328     } else
1329     {
1330         output->render->damage(box);
1331     }
1332 
1333     view->emit_signal("region-damaged", nullptr);
1334 }
1335 
destruct()1336 void wf::view_interface_t::destruct()
1337 {
1338     view_impl->is_alive = false;
1339     wf::get_core_impl().erase_view(self());
1340 }
1341