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