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