1 #include "wayfire/core.hpp"
2 #include "../core/core-impl.hpp"
3 #include "../output/gtk-shell.hpp"
4 #include "view-impl.hpp"
5 #include "wayfire/decorator.hpp"
6 #include "wayfire/signal-definitions.hpp"
7 #include "wayfire/workspace-manager.hpp"
8 #include <wayfire/util/log.hpp>
9 
10 #include "xdg-shell.hpp"
11 
wlr_view_t()12 wf::wlr_view_t::wlr_view_t() :
13     wf::wlr_surface_base_t(this), wf::view_interface_t()
14 {}
15 
set_role(view_role_t new_role)16 void wf::wlr_view_t::set_role(view_role_t new_role)
17 {
18     view_interface_t::set_role(new_role);
19     if (new_role != wf::VIEW_ROLE_TOPLEVEL)
20     {
21         destroy_toplevel();
22     }
23 }
24 
handle_app_id_changed(std::string new_app_id)25 void wf::wlr_view_t::handle_app_id_changed(std::string new_app_id)
26 {
27     this->app_id = new_app_id;
28     toplevel_send_app_id();
29 
30     app_id_changed_signal data;
31     data.view = self();
32     emit_signal("app-id-changed", &data);
33 }
34 
get_app_id()35 std::string wf::wlr_view_t::get_app_id()
36 {
37     return this->app_id;
38 }
39 
handle_title_changed(std::string new_title)40 void wf::wlr_view_t::handle_title_changed(std::string new_title)
41 {
42     this->title = new_title;
43     toplevel_send_title();
44 
45     title_changed_signal data;
46     data.view = self();
47     emit_signal("title-changed", &data);
48 }
49 
get_title()50 std::string wf::wlr_view_t::get_title()
51 {
52     return this->title;
53 }
54 
handle_minimize_hint(wf::surface_interface_t * relative_to,const wlr_box & hint)55 void wf::wlr_view_t::handle_minimize_hint(wf::surface_interface_t *relative_to,
56     const wlr_box & hint)
57 {
58     auto relative_to_view =
59         dynamic_cast<view_interface_t*>(relative_to);
60     if (!relative_to_view)
61     {
62         LOGE("Setting minimize hint to unknown surface. Wayfire currently"
63              "supports only setting hints relative to views.");
64 
65         return;
66     }
67 
68     if (relative_to_view->get_output() != get_output())
69     {
70         LOGE("Minimize hint set to surface on a different output, "
71              "problems might arise");
72         /* TODO: translate coordinates in case minimize hint is on another output */
73     }
74 
75     auto box = relative_to_view->get_output_geometry();
76     box.x    += hint.x;
77     box.y    += hint.y;
78     box.width = hint.width;
79     box.height = hint.height;
80 
81     set_minimize_hint(box);
82 }
83 
get_transformed_opaque_region()84 wf::region_t wf::wlr_view_t::get_transformed_opaque_region()
85 {
86     auto& maximal_shrink_constraint =
87         wf::surface_interface_t::impl::active_shrink_constraint;
88     int saved_shrink_constraint = maximal_shrink_constraint;
89 
90     /* Fullscreen views take up the whole screen, so plugins can't request
91      * padding for them (nothing below is visible).
92      *
93      * In this case, we hijack the maximal_shrink_constraint, but we must
94      * restore it immediately after subtracting the opaque region */
95     if (this->fullscreen)
96     {
97         maximal_shrink_constraint = 0;
98     }
99 
100     auto region = wf::view_interface_t::get_transformed_opaque_region();
101     maximal_shrink_constraint = saved_shrink_constraint;
102 
103     return region;
104 }
105 
set_position(int x,int y,wf::geometry_t old_geometry,bool send_signal)106 void wf::wlr_view_t::set_position(int x, int y,
107     wf::geometry_t old_geometry, bool send_signal)
108 {
109     auto obox = get_output_geometry();
110     auto wm   = get_wm_geometry();
111 
112     view_geometry_changed_signal data;
113     data.view = self();
114     data.old_geometry = old_geometry;
115 
116     view_damage_raw(self(), last_bounding_box);
117     /* obox.x - wm.x is the current difference in the output and wm geometry */
118     geometry.x = x + obox.x - wm.x;
119     geometry.y = y + obox.y - wm.y;
120 
121     /* Make sure that if we move the view while it is unmapped, its snapshot
122      * is still valid coordinates */
123     if (view_impl->offscreen_buffer.valid())
124     {
125         view_impl->offscreen_buffer.geometry.x += x - data.old_geometry.x;
126         view_impl->offscreen_buffer.geometry.y += y - data.old_geometry.y;
127     }
128 
129     damage();
130 
131     if (send_signal)
132     {
133         emit_signal("geometry-changed", &data);
134         wf::get_core().emit_signal("view-geometry-changed", &data);
135         if (get_output())
136         {
137             get_output()->emit_signal("view-geometry-changed", &data);
138         }
139     }
140 
141     last_bounding_box = get_bounding_box();
142 }
143 
move(int x,int y)144 void wf::wlr_view_t::move(int x, int y)
145 {
146     set_position(x, y, get_wm_geometry(), true);
147 }
148 
adjust_anchored_edge(wf::dimensions_t new_size)149 void wf::wlr_view_t::adjust_anchored_edge(wf::dimensions_t new_size)
150 {
151     if (view_impl->edges)
152     {
153         auto wm = get_wm_geometry();
154         if (view_impl->edges & WLR_EDGE_LEFT)
155         {
156             wm.x += geometry.width - new_size.width;
157         }
158 
159         if (view_impl->edges & WLR_EDGE_TOP)
160         {
161             wm.y += geometry.height - new_size.height;
162         }
163 
164         set_position(wm.x, wm.y,
165             get_wm_geometry(), false);
166     }
167 }
168 
update_size()169 void wf::wlr_view_t::update_size()
170 {
171     if (!is_mapped())
172     {
173         return;
174     }
175 
176     auto current_size = get_size();
177     if ((current_size.width == geometry.width) &&
178         (current_size.height == geometry.height))
179     {
180         return;
181     }
182 
183     /* Damage current size */
184     view_damage_raw(self(), last_bounding_box);
185     adjust_anchored_edge(current_size);
186 
187     view_geometry_changed_signal data;
188     data.view = self();
189     data.old_geometry = get_wm_geometry();
190 
191     geometry.width  = current_size.width;
192     geometry.height = current_size.height;
193 
194     /* Damage new size */
195     last_bounding_box = get_bounding_box();
196     view_damage_raw(self(), last_bounding_box);
197     emit_signal("geometry-changed", &data);
198     wf::get_core().emit_signal("view-geometry-changed", &data);
199     if (get_output())
200     {
201         get_output()->emit_signal("view-geometry-changed", &data);
202     }
203 
204     if (view_impl->frame)
205     {
206         view_impl->frame->notify_view_resized(get_wm_geometry());
207     }
208 }
209 
should_resize_client(wf::dimensions_t request,wf::dimensions_t current_geometry)210 bool wf::wlr_view_t::should_resize_client(
211     wf::dimensions_t request, wf::dimensions_t current_geometry)
212 {
213     /*
214      * Do not send a configure if the client will retain its size.
215      * This is needed if a client starts with one size and immediately resizes
216      * again.
217      *
218      * If we do configure it with the given size, then it will think that we
219      * are requesting the given size, and won't resize itself again.
220      */
221     if (this->last_size_request == wf::dimensions_t{0, 0})
222     {
223         return request != current_geometry;
224     } else
225     {
226         return request != last_size_request;
227     }
228 }
229 
get_output_geometry()230 wf::geometry_t wf::wlr_view_t::get_output_geometry()
231 {
232     return geometry;
233 }
234 
get_wm_geometry()235 wf::geometry_t wf::wlr_view_t::get_wm_geometry()
236 {
237     if (view_impl->frame)
238     {
239         return view_impl->frame->expand_wm_geometry(geometry);
240     } else
241     {
242         return geometry;
243     }
244 }
245 
get_keyboard_focus_surface()246 wlr_surface*wf::wlr_view_t::get_keyboard_focus_surface()
247 {
248     if (is_mapped() && view_impl->keyboard_focus_enabled)
249     {
250         return surface;
251     }
252 
253     return NULL;
254 }
255 
should_be_decorated()256 bool wf::wlr_view_t::should_be_decorated()
257 {
258     return role == wf::VIEW_ROLE_TOPLEVEL && !has_client_decoration;
259 }
260 
set_decoration_mode(bool use_csd)261 void wf::wlr_view_t::set_decoration_mode(bool use_csd)
262 {
263     bool was_decorated = should_be_decorated();
264     this->has_client_decoration = use_csd;
265     if ((was_decorated != should_be_decorated()) && is_mapped())
266     {
267         wf::view_decoration_state_updated_signal data;
268         data.view = self();
269 
270         this->emit_signal("decoration-state-updated", &data);
271         if (get_output())
272         {
273             get_output()->emit_signal("view-decoration-state-updated", &data);
274         }
275     }
276 }
277 
set_output(wf::output_t * wo)278 void wf::wlr_view_t::set_output(wf::output_t *wo)
279 {
280     auto old_output = get_output();
281     toplevel_update_output(old_output, false);
282     view_interface_t::set_output(wo);
283     toplevel_update_output(wo, true);
284 
285     /* send enter/leave events */
286     if (this->is_mapped())
287     {
288         update_output(old_output, wo);
289     }
290 }
291 
commit()292 void wf::wlr_view_t::commit()
293 {
294     wlr_surface_base_t::commit();
295     update_size();
296 
297     /* Clear the resize edges.
298      * This is must be done here because if the user(or plugin) resizes too fast,
299      * the shell client might still haven't configured the surface, and in this
300      * case the next commit(here) needs to still have access to the gravity */
301     if (!view_impl->in_continuous_resize)
302     {
303         view_impl->edges = 0;
304     }
305 
306     this->last_bounding_box = get_bounding_box();
307 }
308 
map(wlr_surface * surface)309 void wf::wlr_view_t::map(wlr_surface *surface)
310 {
311     wlr_surface_base_t::map(surface);
312     if (wf::get_core_impl().uses_csd.count(surface))
313     {
314         this->has_client_decoration = wf::get_core_impl().uses_csd[surface];
315     }
316 
317     update_size();
318 
319     if (role == VIEW_ROLE_TOPLEVEL)
320     {
321         if (!parent)
322         {
323             get_output()->workspace->add_view(self(), wf::LAYER_WORKSPACE);
324         }
325 
326         get_output()->focus_view(self(), true);
327     }
328 
329     damage();
330     emit_view_map();
331     /* Might trigger repositioning */
332     set_toplevel_parent(this->parent);
333 }
334 
unmap()335 void wf::wlr_view_t::unmap()
336 {
337     damage();
338     emit_view_pre_unmap();
339 
340     destroy_toplevel();
341 
342     // Unset decoration when unmapping, since the policy is to always remove
343     // all subsurfaces when a view is unmapped.
344     set_decoration(nullptr);
345 
346     wlr_surface_base_t::unmap();
347     emit_view_unmap();
348 }
349 
emit_view_map_signal(wayfire_view view,bool has_position)350 void wf::emit_view_map_signal(wayfire_view view, bool has_position)
351 {
352     wf::view_mapped_signal data;
353     data.view = view;
354     data.is_positioned = has_position;
355     view->get_output()->emit_signal("view-mapped", &data);
356     view->emit_signal("mapped", &data);
357 }
358 
emit_ping_timeout_signal(wayfire_view view)359 void wf::emit_ping_timeout_signal(wayfire_view view)
360 {
361     wf::view_ping_timeout_signal data;
362     data.view = view;
363     view->emit_signal("ping-timeout", &data);
364 }
365 
emit_view_map()366 void wf::view_interface_t::emit_view_map()
367 {
368     emit_view_map_signal(self(), false);
369 }
370 
emit_view_unmap()371 void wf::view_interface_t::emit_view_unmap()
372 {
373     view_unmapped_signal data;
374     data.view = self();
375 
376     if (get_output())
377     {
378         get_output()->emit_signal("view-unmapped", &data);
379         get_output()->emit_signal("view-disappeared", &data);
380     }
381 
382     emit_signal("unmapped", &data);
383 }
384 
emit_view_pre_unmap()385 void wf::view_interface_t::emit_view_pre_unmap()
386 {
387     view_pre_unmap_signal data;
388     data.view = self();
389 
390     if (get_output())
391     {
392         get_output()->emit_signal("view-pre-unmapped", &data);
393     }
394 
395     emit_signal("pre-unmapped", &data);
396 }
397 
destroy()398 void wf::wlr_view_t::destroy()
399 {
400     view_impl->is_alive = false;
401     /* Drop the internal reference created in surface_interface_t */
402     unref();
403 }
404 
create_toplevel()405 void wf::wlr_view_t::create_toplevel()
406 {
407     if (toplevel_handle)
408     {
409         return;
410     }
411 
412     /* We don't want to create toplevels for shell views or xwayland menus */
413     if (role != VIEW_ROLE_TOPLEVEL)
414     {
415         return;
416     }
417 
418     toplevel_handle = wlr_foreign_toplevel_handle_v1_create(
419         wf::get_core().protocols.toplevel_manager);
420 
421     toplevel_handle_v1_maximize_request.set_callback([&] (void *data)
422     {
423         auto ev =
424             static_cast<wlr_foreign_toplevel_handle_v1_maximized_event*>(data);
425         tile_request(ev->maximized ? wf::TILED_EDGES_ALL : 0);
426     });
427     toplevel_handle_v1_minimize_request.set_callback([&] (void *data)
428     {
429         auto ev =
430             static_cast<wlr_foreign_toplevel_handle_v1_minimized_event*>(data);
431         minimize_request(ev->minimized);
432     });
433     toplevel_handle_v1_activate_request.set_callback(
434         [&] (void*) { focus_request(); });
435     toplevel_handle_v1_close_request.set_callback([&] (void*) { close(); });
436 
437     toplevel_handle_v1_set_rectangle_request.set_callback([&] (void *data)
438     {
439         auto ev = static_cast<
440             wlr_foreign_toplevel_handle_v1_set_rectangle_event*>(data);
441         auto surface = wf_view_from_void(ev->surface->data);
442         handle_minimize_hint(surface, {ev->x, ev->y, ev->width, ev->height});
443     });
444 
445     toplevel_handle_v1_maximize_request.connect(
446         &toplevel_handle->events.request_maximize);
447     toplevel_handle_v1_minimize_request.connect(
448         &toplevel_handle->events.request_minimize);
449     toplevel_handle_v1_activate_request.connect(
450         &toplevel_handle->events.request_activate);
451     toplevel_handle_v1_set_rectangle_request.connect(
452         &toplevel_handle->events.set_rectangle);
453     toplevel_handle_v1_close_request.connect(
454         &toplevel_handle->events.request_close);
455 
456     toplevel_send_title();
457     toplevel_send_app_id();
458     toplevel_send_state();
459     toplevel_update_output(get_output(), true);
460 }
461 
destroy_toplevel()462 void wf::wlr_view_t::destroy_toplevel()
463 {
464     if (!toplevel_handle)
465     {
466         return;
467     }
468 
469     toplevel_handle_v1_maximize_request.disconnect();
470     toplevel_handle_v1_activate_request.disconnect();
471     toplevel_handle_v1_minimize_request.disconnect();
472     toplevel_handle_v1_set_rectangle_request.disconnect();
473     toplevel_handle_v1_close_request.disconnect();
474 
475     wlr_foreign_toplevel_handle_v1_destroy(toplevel_handle);
476     toplevel_handle = nullptr;
477 }
478 
toplevel_send_title()479 void wf::wlr_view_t::toplevel_send_title()
480 {
481     if (!toplevel_handle)
482     {
483         return;
484     }
485 
486     wlr_foreign_toplevel_handle_v1_set_title(toplevel_handle,
487         get_title().c_str());
488 }
489 
toplevel_send_app_id()490 void wf::wlr_view_t::toplevel_send_app_id()
491 {
492     if (!toplevel_handle)
493     {
494         return;
495     }
496 
497     std::string app_id;
498 
499     auto default_app_id   = get_app_id();
500     auto gtk_shell_app_id = wf_gtk_shell_get_custom_app_id(
501         wf::get_core_impl().gtk_shell, surface->resource);
502 
503     std::string app_id_mode =
504         wf::option_wrapper_t<std::string>("workarounds/app_id_mode");
505 
506     if ((app_id_mode == "gtk-shell") && (gtk_shell_app_id.length() > 0))
507     {
508         app_id = gtk_shell_app_id;
509     } else if (app_id_mode == "full")
510     {
511         app_id = default_app_id + " " + gtk_shell_app_id;
512     } else
513     {
514         app_id = default_app_id;
515     }
516 
517     wlr_foreign_toplevel_handle_v1_set_app_id(toplevel_handle, app_id.c_str());
518 }
519 
toplevel_send_state()520 void wf::wlr_view_t::toplevel_send_state()
521 {
522     if (!toplevel_handle)
523     {
524         return;
525     }
526 
527     wlr_foreign_toplevel_handle_v1_set_maximized(toplevel_handle,
528         tiled_edges == TILED_EDGES_ALL);
529     wlr_foreign_toplevel_handle_v1_set_activated(toplevel_handle, activated);
530     wlr_foreign_toplevel_handle_v1_set_minimized(toplevel_handle, minimized);
531 
532     /* update parent as well */
533     wf::wlr_view_t *parent_ptr = dynamic_cast<wf::wlr_view_t*>(parent.get());
534     wlr_foreign_toplevel_handle_v1_set_parent(toplevel_handle,
535         parent_ptr ? parent_ptr->toplevel_handle : nullptr);
536 }
537 
toplevel_update_output(wf::output_t * wo,bool enter)538 void wf::wlr_view_t::toplevel_update_output(wf::output_t *wo, bool enter)
539 {
540     if (!wo || !toplevel_handle)
541     {
542         return;
543     }
544 
545     if (enter)
546     {
547         wlr_foreign_toplevel_handle_v1_output_enter(
548             toplevel_handle, wo->handle);
549     } else
550     {
551         wlr_foreign_toplevel_handle_v1_output_leave(
552             toplevel_handle, wo->handle);
553     }
554 }
555 
desktop_state_updated()556 void wf::wlr_view_t::desktop_state_updated()
557 {
558     toplevel_send_state();
559 }
560 
init_desktop_apis()561 void wf::init_desktop_apis()
562 {
563     init_xdg_shell();
564     init_layer_shell();
565 
566     wf::option_wrapper_t<bool> xwayland_enabled("core/xwayland");
567     if (xwayland_enabled == 1)
568     {
569         init_xwayland();
570     }
571 }
572 
wf_surface_from_void(void * handle)573 wf::surface_interface_t*wf::wf_surface_from_void(void *handle)
574 {
575     return static_cast<wf::surface_interface_t*>(handle);
576 }
577 
wf_view_from_void(void * handle)578 wf::view_interface_t*wf::wf_view_from_void(void *handle)
579 {
580     return static_cast<wf::view_interface_t*>(handle);
581 }
582 
compositor_surface_from_surface(wf::surface_interface_t * surface)583 wf::compositor_surface_t*wf::compositor_surface_from_surface(
584     wf::surface_interface_t *surface)
585 {
586     return dynamic_cast<wf::compositor_surface_t*>(surface);
587 }
588 
interactive_view_from_view(wf::view_interface_t * view)589 wf::compositor_interactive_view_t*wf::interactive_view_from_view(
590     wf::view_interface_t *view)
591 {
592     return dynamic_cast<wf::compositor_interactive_view_t*>(view);
593 }
594 
wl_surface_to_wayfire_view(wl_resource * resource)595 wayfire_view wf::wl_surface_to_wayfire_view(wl_resource *resource)
596 {
597     auto surface = (wlr_surface*)wl_resource_get_user_data(resource);
598 
599     void *handle = NULL;
600     if (wlr_surface_is_xdg_surface(surface))
601     {
602         handle = wlr_xdg_surface_from_wlr_surface(surface)->data;
603     }
604 
605     if (wlr_surface_is_layer_surface(surface))
606     {
607         handle = wlr_layer_surface_v1_from_wlr_surface(surface)->data;
608     }
609 
610 #if WF_HAS_XWAYLAND
611     if (wlr_surface_is_xwayland_surface(surface))
612     {
613         handle = wlr_xwayland_surface_from_wlr_surface(surface)->data;
614     }
615 
616 #endif
617 
618     wf::view_interface_t *view = wf::wf_view_from_void(handle);
619 
620     return view ? view->self() : nullptr;
621 }
622