1 #include "wayfire/debug.hpp"
2 #include <wayfire/util/log.hpp>
3 #include <wayfire/nonstd/wlroots-full.hpp>
4 #include <wayfire/render-manager.hpp>
5 #include "wayfire/core.hpp"
6 #include "wayfire/output.hpp"
7 #include "wayfire/workspace-manager.hpp"
8 #include "wayfire/decorator.hpp"
9 #include "wayfire/output-layout.hpp"
10 #include "wayfire/signal-definitions.hpp"
11 #include "../core/core-impl.hpp"
12 #include "../core/seat/cursor.hpp"
13 #include "../core/seat/input-manager.hpp"
14 #include "view-impl.hpp"
15
16 #if WF_HAS_XWAYLAND
17
18 enum class xwayland_view_type_t
19 {
20 NORMAL,
21 UNMANAGED,
22 DND,
23 };
24
25 class wayfire_xwayland_view_base : public wf::wlr_view_t
26 {
27 protected:
28 static xcb_atom_t _NET_WM_WINDOW_TYPE_NORMAL;
29 static xcb_atom_t _NET_WM_WINDOW_TYPE_DIALOG;
30 static xcb_atom_t _NET_WM_WINDOW_TYPE_SPLASH;
31 static xcb_atom_t _NET_WM_WINDOW_TYPE_DND;
32
load_atom(xcb_connection_t * connection,xcb_atom_t & atom,const std::string & name)33 static void load_atom(xcb_connection_t *connection,
34 xcb_atom_t& atom, const std::string& name)
35 {
36 auto cookie = xcb_intern_atom(connection, 0, name.length(), name.c_str());
37
38 xcb_generic_error_t *error = NULL;
39 xcb_intern_atom_reply_t *reply;
40 reply = xcb_intern_atom_reply(connection, cookie, &error);
41
42 bool success = !error && reply;
43 if (success)
44 {
45 atom = reply->atom;
46 }
47
48 free(reply);
49 free(error);
50 }
51
52 public:
load_atoms(const char * server_name)53 static bool load_atoms(const char *server_name)
54 {
55 auto connection = xcb_connect(server_name, NULL);
56 if (!connection || xcb_connection_has_error(connection))
57 {
58 return false;
59 }
60
61 load_atom(connection, _NET_WM_WINDOW_TYPE_NORMAL,
62 "_NET_WM_WINDOW_TYPE_NORMAL");
63 load_atom(connection, _NET_WM_WINDOW_TYPE_DIALOG,
64 "_NET_WM_WINDOW_TYPE_DIALOG");
65 load_atom(connection, _NET_WM_WINDOW_TYPE_SPLASH,
66 "_NET_WM_WINDOW_TYPE_SPLASH");
67 load_atom(connection, _NET_WM_WINDOW_TYPE_DND,
68 "_NET_WM_WINDOW_TYPE_DND");
69
70 xcb_disconnect(connection);
71 return true;
72 }
73
74 protected:
75 wf::wl_listener_wrapper on_destroy, on_unmap, on_map, on_configure,
76 on_set_title, on_set_app_id, on_or_changed, on_set_decorations,
77 on_ping_timeout, on_set_window_type;
78
79 wlr_xwayland_surface *xw;
80 /** The geometry requested by the client */
81 bool self_positioned = false;
82
83 wf::signal_connection_t output_geometry_changed{[this] (wf::signal_data_t*)
__anonb729c48a0102() 84 {
85 if (is_mapped())
86 {
87 auto wm_geometry = get_wm_geometry();
88 move(wm_geometry.x, wm_geometry.y);
89 }
90 }
91 };
92
has_type(xcb_atom_t type)93 bool has_type(xcb_atom_t type)
94 {
95 for (size_t i = 0; i < xw->window_type_len; i++)
96 {
97 if (xw->window_type[i] == type)
98 {
99 return true;
100 }
101 }
102
103 return false;
104 }
105
is_dialog()106 bool is_dialog()
107 {
108 if (has_type(_NET_WM_WINDOW_TYPE_DIALOG) ||
109 (xw->parent && (xw->window_type_len == 0)))
110 {
111 return true;
112 } else
113 {
114 return false;
115 }
116 }
117
118 /**
119 * Determine whether the view should be treated as override-redirect or not.
120 */
is_unmanaged()121 bool is_unmanaged()
122 {
123 if (xw->override_redirect)
124 {
125 return true;
126 }
127
128 /** Example: Android Studio dialogs */
129 if (xw->parent && !this->is_dialog() &&
130 !this->has_type(_NET_WM_WINDOW_TYPE_NORMAL))
131 {
132 return true;
133 }
134
135 return false;
136 }
137
138 /**
139 * Determine whether the view should be treated as a drag icon.
140 */
is_dnd()141 bool is_dnd()
142 {
143 return this->has_type(_NET_WM_WINDOW_TYPE_DND);
144 }
145
146 /**
147 * Get the current implementation type.
148 */
149 virtual xwayland_view_type_t get_current_impl_type() const = 0;
150
151 public:
wayfire_xwayland_view_base(wlr_xwayland_surface * xww)152 wayfire_xwayland_view_base(wlr_xwayland_surface *xww) :
153 wlr_view_t(), xw(xww)
154 {}
155
initialize()156 virtual void initialize() override
157 {
158 wf::wlr_view_t::initialize();
159 on_map.set_callback([&] (void*) { map(xw->surface); });
160 on_unmap.set_callback([&] (void*) { unmap(); });
161 on_destroy.set_callback([&] (void*) { destroy(); });
162 on_configure.set_callback([&] (void *data)
163 {
164 auto ev = static_cast<wlr_xwayland_surface_configure_event*>(data);
165 wf::point_t output_origin = {0, 0};
166 if (get_output())
167 {
168 output_origin = {
169 get_output()->get_relative_geometry().x,
170 get_output()->get_relative_geometry().y
171 };
172 }
173
174 if (!is_mapped())
175 {
176 /* If the view is not mapped yet, let it be configured as it
177 * wishes. We will position it properly in ::map() */
178 wlr_xwayland_surface_configure(xw,
179 ev->x, ev->y, ev->width, ev->height);
180
181 if ((ev->mask & XCB_CONFIG_WINDOW_X) &&
182 (ev->mask & XCB_CONFIG_WINDOW_Y))
183 {
184 this->self_positioned = true;
185 this->geometry.x = ev->x - output_origin.x;
186 this->geometry.y = ev->y - output_origin.y;
187 }
188
189 return;
190 }
191
192 /**
193 * Regular Xwayland windows are not allowed to change their position
194 * after mapping, in which respect they behave just like Wayland apps.
195 *
196 * However, OR views or special views which do not have NORMAL type
197 * should be allowed to move around the screen.
198 */
199 bool enable_custom_position = xw->override_redirect ||
200 (xw->window_type_len > 0 &&
201 xw->window_type[0] != _NET_WM_WINDOW_TYPE_NORMAL);
202
203 if ((ev->mask & XCB_CONFIG_WINDOW_X) &&
204 (ev->mask & XCB_CONFIG_WINDOW_Y) &&
205 enable_custom_position)
206 {
207 /* override-redirect views generally have full freedom. */
208 self_positioned = true;
209 configure_request({ev->x, ev->y, ev->width, ev->height});
210
211 return;
212 }
213
214 /* Use old x/y values */
215 ev->x = geometry.x + output_origin.x;
216 ev->y = geometry.y + output_origin.y;
217 configure_request(wlr_box{ev->x, ev->y, ev->width, ev->height});
218 });
219 on_set_title.set_callback([&] (void*)
220 {
221 handle_title_changed(nonull(xw->title));
222 });
223 on_set_app_id.set_callback([&] (void*)
224 {
225 handle_app_id_changed(nonull(xw->class_t));
226 });
227 on_or_changed.set_callback([&] (void*)
228 {
229 recreate_view();
230 });
231 on_set_decorations.set_callback([&] (void*)
232 {
233 update_decorated();
234 });
235 on_ping_timeout.set_callback([&] (void*)
236 {
237 wf::emit_ping_timeout_signal(self());
238 });
239 on_set_window_type.set_callback([&] (void*)
240 {
241 recreate_view();
242 });
243
244 handle_title_changed(nonull(xw->title));
245 handle_app_id_changed(nonull(xw->class_t));
246 update_decorated();
247
248 on_map.connect(&xw->events.map);
249 on_unmap.connect(&xw->events.unmap);
250 on_destroy.connect(&xw->events.destroy);
251 on_configure.connect(&xw->events.request_configure);
252 on_set_title.connect(&xw->events.set_title);
253 on_set_app_id.connect(&xw->events.set_class);
254 on_or_changed.connect(&xw->events.set_override_redirect);
255 on_ping_timeout.connect(&xw->events.ping_timeout);
256 on_set_decorations.connect(&xw->events.set_decorations);
257 on_set_window_type.connect(&xw->events.set_window_type);
258 }
259
260 /**
261 * Destroy the view, and create a new one with the correct type -
262 * unmanaged(override-redirect), DnD or normal.
263 *
264 * No-op if the view already has the correct type.
265 */
266 virtual void recreate_view();
267
destroy()268 virtual void destroy() override
269 {
270 this->xw = nullptr;
271 output_geometry_changed.disconnect();
272
273 on_map.disconnect();
274 on_unmap.disconnect();
275 on_destroy.disconnect();
276 on_configure.disconnect();
277 on_set_title.disconnect();
278 on_set_app_id.disconnect();
279 on_or_changed.disconnect();
280 on_ping_timeout.disconnect();
281 on_set_decorations.disconnect();
282 on_set_window_type.disconnect();
283
284 wf::wlr_view_t::destroy();
285 }
286
ping()287 virtual void ping() override
288 {
289 if (xw)
290 {
291 wlr_xwayland_surface_ping(xw);
292 }
293 }
294
should_be_decorated()295 virtual bool should_be_decorated() override
296 {
297 return (wf::wlr_view_t::should_be_decorated() &&
298 !has_type(_NET_WM_WINDOW_TYPE_SPLASH));
299 }
300
301 /* Translates geometry from X client configure requests to wayfire
302 * coordinate system. The X coordinate system treats all outputs
303 * as one big desktop, whereas wayfire treats the current workspace
304 * of an output as 0,0 and everything else relative to that. This
305 * means that we must take care when placing xwayland clients that
306 * request a configure after initial mapping, while not on the
307 * current workspace.
308 *
309 * @param output The view's output
310 * @param ws_offset The view's workspace minus the current workspace
311 * @param geometry The configure geometry as requested by the client
312 *
313 * @return Geometry with a position that is within the view's workarea.
314 * The workarea is the workspace where the view was initially mapped.
315 * Newly mapped views are placed on the current workspace.
316 */
translate_geometry_to_output(wf::output_t * output,wf::point_t ws_offset,wf::geometry_t g)317 wf::geometry_t translate_geometry_to_output(wf::output_t *output,
318 wf::point_t ws_offset,
319 wf::geometry_t g)
320 {
321 auto outputs = wf::get_core().output_layout->get_outputs();
322 auto og = output->get_layout_geometry();
323 auto from = wf::get_core().output_layout->get_output_at(
324 g.x + g.width / 2 + og.x, g.y + g.height / 2 + og.y);
325 if (!from)
326 {
327 return g;
328 }
329
330 auto lg = from->get_layout_geometry();
331 g.x += (og.x - lg.x) + ws_offset.x * og.width;
332 g.y += (og.y - lg.y) + ws_offset.y * og.height;
333 if (!this->is_mapped())
334 {
335 g.x *= (float)og.width / lg.width;
336 g.y *= (float)og.height / lg.height;
337 }
338
339 return g;
340 }
341
configure_request(wf::geometry_t configure_geometry)342 virtual void configure_request(wf::geometry_t configure_geometry)
343 {
344 /* Wayfire positions views relative to their output, but Xwayland
345 * windows have a global positioning. So, we need to make sure that we
346 * always transform between output-local coordinates and global
347 * coordinates. Additionally, when clients send a configure request
348 * after they have already been mapped, we keep the view on the
349 * workspace where its center point was from last configure, in
350 * case the current workspace is not where the view lives */
351 auto o = get_output();
352 if (o)
353 {
354 auto view_workarea = (fullscreen ?
355 o->get_relative_geometry() : o->workspace->get_workarea());
356 auto og = o->get_layout_geometry();
357 configure_geometry.x -= og.x;
358 configure_geometry.y -= og.y;
359
360 auto view = this->self();
361 while (view->parent)
362 {
363 view = view->parent;
364 }
365
366 auto vg = view->get_wm_geometry();
367
368 // View workspace relative to current workspace
369 wf::point_t view_ws = {0, 0};
370 if (view->is_mapped())
371 {
372 view_ws = {
373 (int)std::floor((vg.x + vg.width / 2.0) / og.width),
374 (int)std::floor((vg.y + vg.height / 2.0) / og.height),
375 };
376
377 view_workarea.x += og.width * view_ws.x;
378 view_workarea.y += og.height * view_ws.y;
379 }
380
381 configure_geometry = translate_geometry_to_output(
382 o, view_ws, configure_geometry);
383 configure_geometry = wf::clamp(configure_geometry, view_workarea);
384 }
385
386 if (view_impl->frame)
387 {
388 configure_geometry =
389 view_impl->frame->expand_wm_geometry(configure_geometry);
390 }
391
392 set_geometry(configure_geometry);
393 }
394
update_decorated()395 void update_decorated()
396 {
397 uint32_t csd_flags = WLR_XWAYLAND_SURFACE_DECORATIONS_NO_TITLE |
398 WLR_XWAYLAND_SURFACE_DECORATIONS_NO_BORDER;
399 this->set_decoration_mode(xw->decorations & csd_flags);
400 }
401
close()402 virtual void close() override
403 {
404 if (xw)
405 {
406 wlr_xwayland_surface_close(xw);
407 }
408
409 wf::wlr_view_t::close();
410 }
411
set_activated(bool active)412 void set_activated(bool active) override
413 {
414 if (xw)
415 {
416 wlr_xwayland_surface_activate(xw, active);
417 }
418
419 wf::wlr_view_t::set_activated(active);
420 }
421
set_geometry(wf::geometry_t geometry)422 void set_geometry(wf::geometry_t geometry) override
423 {
424 wlr_view_t::move(geometry.x, geometry.y);
425 resize(geometry.width, geometry.height);
426 }
427
send_configure(int width,int height)428 void send_configure(int width, int height)
429 {
430 if (!xw)
431 {
432 return;
433 }
434
435 if ((width < 0) || (height < 0))
436 {
437 /* such a configure request would freeze xwayland.
438 * This is most probably a bug somewhere in the compositor. */
439 LOGE("Configuring a xwayland surface with width/height <0");
440
441 return;
442 }
443
444 auto output_geometry = get_output_geometry();
445
446 int configure_x = output_geometry.x;
447 int configure_y = output_geometry.y;
448
449 if (get_output())
450 {
451 auto real_output = get_output()->get_layout_geometry();
452 configure_x += real_output.x;
453 configure_y += real_output.y;
454 }
455
456 wlr_xwayland_surface_configure(xw,
457 configure_x, configure_y, width, height);
458 }
459
send_configure()460 void send_configure()
461 {
462 send_configure(last_size_request.width, last_size_request.height);
463 }
464
move(int x,int y)465 void move(int x, int y) override
466 {
467 wf::wlr_view_t::move(x, y);
468 if (!view_impl->in_continuous_move)
469 {
470 send_configure();
471 }
472 }
473
set_output(wf::output_t * wo)474 virtual void set_output(wf::output_t *wo) override
475 {
476 output_geometry_changed.disconnect();
477 wlr_view_t::set_output(wo);
478
479 if (wo)
480 {
481 wo->connect_signal("output-configuration-changed",
482 &output_geometry_changed);
483 }
484
485 /* Update the real position */
486 if (is_mapped())
487 {
488 send_configure();
489 }
490 }
491 };
492
493 xcb_atom_t wayfire_xwayland_view_base::_NET_WM_WINDOW_TYPE_NORMAL;
494 xcb_atom_t wayfire_xwayland_view_base::_NET_WM_WINDOW_TYPE_DIALOG;
495 xcb_atom_t wayfire_xwayland_view_base::_NET_WM_WINDOW_TYPE_SPLASH;
496 xcb_atom_t wayfire_xwayland_view_base::_NET_WM_WINDOW_TYPE_DND;
497
498 class wayfire_unmanaged_xwayland_view : public wayfire_xwayland_view_base
499 {
500 protected:
501 wf::wl_listener_wrapper on_set_geometry;
502
503 public:
504 wayfire_unmanaged_xwayland_view(wlr_xwayland_surface *xww);
505
506 int global_x, global_y;
507
508 void map(wlr_surface *surface) override;
509 void destroy() override;
510
511 bool should_be_decorated() override;
512
get_current_impl_type() const513 xwayland_view_type_t get_current_impl_type() const override
514 {
515 return xwayland_view_type_t::UNMANAGED;
516 }
517
~wayfire_unmanaged_xwayland_view()518 ~wayfire_unmanaged_xwayland_view()
519 {}
520 };
521
522 class wayfire_xwayland_view : public wayfire_xwayland_view_base
523 {
524 wf::wl_listener_wrapper on_request_move, on_request_resize,
525 on_request_maximize, on_request_minimize, on_request_activate,
526 on_request_fullscreen, on_set_parent, on_set_hints;
527
528 public:
wayfire_xwayland_view(wlr_xwayland_surface * xww)529 wayfire_xwayland_view(wlr_xwayland_surface *xww) :
530 wayfire_xwayland_view_base(xww)
531 {}
532
initialize()533 virtual void initialize() override
534 {
535 LOGE("new xwayland surface ", xw->title,
536 " class: ", xw->class_t, " instance: ", xw->instance);
537 wayfire_xwayland_view_base::initialize();
538
539 on_request_move.set_callback([&] (void*) { move_request(); });
540 on_request_resize.set_callback([&] (auto data)
541 {
542 auto ev = static_cast<wlr_xwayland_resize_event*>(data);
543 resize_request(ev->edges);
544 });
545 on_request_activate.set_callback([&] (void*)
546 {
547 if (!this->activated)
548 {
549 wf::view_focus_request_signal data;
550 data.view = self();
551 data.self_request = true;
552 emit_signal("view-focus-request", &data);
553 wf::get_core().emit_signal("view-focus-request", &data);
554 }
555 });
556
557 on_request_maximize.set_callback([&] (void*)
558 {
559 tile_request((xw->maximized_horz && xw->maximized_vert) ?
560 wf::TILED_EDGES_ALL : 0);
561 });
562 on_request_fullscreen.set_callback([&] (void*)
563 {
564 fullscreen_request(get_output(), xw->fullscreen);
565 });
566 on_request_minimize.set_callback([&] (void *data)
567 {
568 auto ev = (wlr_xwayland_minimize_event*)data;
569 minimize_request(ev->minimize);
570 });
571
572 on_set_parent.set_callback([&] (void*)
573 {
574 /* Menus, etc. with TRANSIENT_FOR but not dialogs */
575 if (is_unmanaged())
576 {
577 recreate_view();
578
579 return;
580 }
581
582 auto parent = xw->parent ?
583 wf::wf_view_from_void(xw->parent->data)->self() : nullptr;
584
585 // Make sure the parent is mapped, and that we are not a toplevel view
586 if (parent)
587 {
588 if (!parent->is_mapped() ||
589 this->has_type(_NET_WM_WINDOW_TYPE_NORMAL))
590 {
591 parent = nullptr;
592 }
593 }
594
595 set_toplevel_parent(parent);
596 });
597
598 on_set_hints.set_callback([&] (void*)
599 {
600 wf::view_hints_changed_signal data;
601 data.view = this;
602 if (xw->hints_urgency)
603 {
604 data.demands_attention = true;
605 }
606
607 wf::get_core().emit_signal("view-hints-changed", &data);
608 this->emit_signal("hints-changed", &data);
609 });
610 on_set_parent.connect(&xw->events.set_parent);
611 on_set_hints.connect(&xw->events.set_hints);
612
613 on_request_move.connect(&xw->events.request_move);
614 on_request_resize.connect(&xw->events.request_resize);
615 on_request_activate.connect(&xw->events.request_activate);
616 on_request_maximize.connect(&xw->events.request_maximize);
617 on_request_minimize.connect(&xw->events.request_minimize);
618 on_request_fullscreen.connect(&xw->events.request_fullscreen);
619
620 xw->data = dynamic_cast<wf::view_interface_t*>(this);
621 // set initial parent
622 on_set_parent.emit(nullptr);
623 }
624
destroy()625 virtual void destroy() override
626 {
627 on_set_parent.disconnect();
628 on_set_hints.disconnect();
629 on_request_move.disconnect();
630 on_request_resize.disconnect();
631 on_request_activate.disconnect();
632 on_request_maximize.disconnect();
633 on_request_minimize.disconnect();
634 on_request_fullscreen.disconnect();
635
636 wayfire_xwayland_view_base::destroy();
637 }
638
emit_view_map()639 void emit_view_map() override
640 {
641 /* Some X clients position themselves on map, and others let the window
642 * manager determine this. We try to heuristically guess which of the
643 * two cases we're dealing with by checking whether we have recevied
644 * a valid ConfigureRequest before mapping */
645 bool client_self_positioned = self_positioned;
646 emit_view_map_signal(self(), client_self_positioned);
647 }
648
map(wlr_surface * surface)649 void map(wlr_surface *surface) override
650 {
651 view_impl->keyboard_focus_enabled =
652 wlr_xwayland_or_surface_wants_focus(xw);
653
654 if (xw->maximized_horz && xw->maximized_vert)
655 {
656 if ((xw->width > 0) && (xw->height > 0))
657 {
658 /* Save geometry which the window has put itself in */
659 wf::geometry_t save_geometry = {
660 xw->x, xw->y, xw->width, xw->height
661 };
662
663 /* Make sure geometry is properly visible on the view output */
664 save_geometry = wf::clamp(save_geometry,
665 get_output()->workspace->get_workarea());
666 view_impl->update_windowed_geometry(self(), save_geometry);
667 }
668
669 tile_request(wf::TILED_EDGES_ALL);
670 }
671
672 if (xw->fullscreen)
673 {
674 fullscreen_request(get_output(), true);
675 }
676
677 if (!this->tiled_edges && !xw->fullscreen)
678 {
679 configure_request({xw->x, xw->y, xw->width, xw->height});
680 }
681
682 wf::wlr_view_t::map(surface);
683 create_toplevel();
684 }
685
commit()686 void commit() override
687 {
688 if (!xw->has_alpha)
689 {
690 pixman_region32_union_rect(
691 &surface->opaque_region, &surface->opaque_region,
692 0, 0, surface->current.width, surface->current.height);
693 }
694
695 wf::wlr_view_t::commit();
696
697 /* Avoid loops where the client wants to have a certain size but the
698 * compositor keeps trying to resize it */
699 last_size_request = wf::dimensions(geometry);
700 }
701
set_moving(bool moving)702 void set_moving(bool moving) override
703 {
704 wf::wlr_view_t::set_moving(moving);
705
706 /* We don't send updates while in continuous move, because that means
707 * too much configure requests. Instead, we set it at the end */
708 if (!view_impl->in_continuous_move)
709 {
710 send_configure();
711 }
712 }
713
resize(int w,int h)714 void resize(int w, int h) override
715 {
716 if (view_impl->frame)
717 {
718 view_impl->frame->calculate_resize_size(w, h);
719 }
720
721 wf::dimensions_t current_size = {
722 get_output_geometry().width,
723 get_output_geometry().height
724 };
725 if (!should_resize_client({w, h}, current_size))
726 {
727 return;
728 }
729
730 this->last_size_request = {w, h};
731 send_configure(w, h);
732 }
733
request_native_size()734 virtual void request_native_size() override
735 {
736 if (!is_mapped() || !xw->size_hints)
737 {
738 return;
739 }
740
741 if ((xw->size_hints->base_width > 0) && (xw->size_hints->base_height > 0))
742 {
743 this->last_size_request = {
744 xw->size_hints->base_width,
745 xw->size_hints->base_height
746 };
747 send_configure();
748 }
749 }
750
set_tiled(uint32_t edges)751 void set_tiled(uint32_t edges) override
752 {
753 wf::wlr_view_t::set_tiled(edges);
754 if (xw)
755 {
756 wlr_xwayland_surface_set_maximized(xw, !!edges);
757 }
758 }
759
toplevel_send_app_id()760 virtual void toplevel_send_app_id() override
761 {
762 if (!toplevel_handle)
763 {
764 return;
765 }
766
767 /* Xwayland windows have two "app-id"s - the class and the instance.
768 * Some apps' icons can be found by looking up the class, for others
769 * the instance. So, just like the workaround for gtk-shell, we can
770 * send both the instance and the class to clients, so that they can
771 * find the appropriate icons. */
772 std::string app_id;
773 auto default_app_id = get_app_id();
774 auto instance_app_id = nonull(xw->instance);
775
776 std::string app_id_mode =
777 wf::option_wrapper_t<std::string>("workarounds/app_id_mode");
778 if (app_id_mode == "full")
779 {
780 app_id = default_app_id + " " + instance_app_id;
781 } else
782 {
783 app_id = default_app_id;
784 }
785
786 wlr_foreign_toplevel_handle_v1_set_app_id(
787 toplevel_handle, app_id.c_str());
788 }
789
set_fullscreen(bool full)790 void set_fullscreen(bool full) override
791 {
792 wf::wlr_view_t::set_fullscreen(full);
793 if (xw)
794 {
795 wlr_xwayland_surface_set_fullscreen(xw, full);
796 }
797 }
798
set_minimized(bool minimized)799 void set_minimized(bool minimized) override
800 {
801 wf::wlr_view_t::set_minimized(minimized);
802 if (xw)
803 {
804 wlr_xwayland_surface_set_minimized(xw, minimized);
805 }
806 }
807
get_current_impl_type() const808 xwayland_view_type_t get_current_impl_type() const override
809 {
810 return xwayland_view_type_t::NORMAL;
811 }
812 };
813
wayfire_unmanaged_xwayland_view(wlr_xwayland_surface * xww)814 wayfire_unmanaged_xwayland_view::wayfire_unmanaged_xwayland_view(
815 wlr_xwayland_surface *xww) :
816 wayfire_xwayland_view_base(xww)
817 {
818 LOGE("new unmanaged xwayland surface ", xw->title, " class: ", xw->class_t,
819 " instance: ", xw->instance);
820
821 xw->data = this;
822 role = wf::VIEW_ROLE_UNMANAGED;
823
824 on_set_geometry.set_callback([&] (void*)
825 {
826 /* Xwayland O-R views manage their position on their own. So we need to
827 * update their position on each commit, if the position changed. */
828 if ((global_x != xw->x) || (global_y != xw->y))
829 {
830 geometry.x = global_x = xw->x;
831 geometry.y = global_y = xw->y;
832
833 if (get_output())
834 {
835 auto real_output = get_output()->get_layout_geometry();
836 geometry.x -= real_output.x;
837 geometry.y -= real_output.y;
838 }
839
840 wf::wlr_view_t::move(geometry.x, geometry.y);
841 }
842 });
843
844 on_set_geometry.connect(&xw->events.set_geometry);
845 }
846
map(wlr_surface * surface)847 void wayfire_unmanaged_xwayland_view::map(wlr_surface *surface)
848 {
849 /* move to the output where our center is
850 * FIXME: this is a bad idea, because a dropdown menu might get sent to
851 * an incorrect output. However, no matter how we calculate the real
852 * output, we just can't be 100% compatible because in X all windows are
853 * positioned in a global coordinate space */
854 auto wo = wf::get_core().output_layout->get_output_at(
855 xw->x + surface->current.width / 2, xw->y + surface->current.height / 2);
856
857 if (!wo)
858 {
859 /* if surface center is outside of anything, try to check the output
860 * where the pointer is */
861 auto gc = wf::get_core().get_cursor_position();
862 wo = wf::get_core().output_layout->get_output_at(gc.x, gc.y);
863 }
864
865 if (!wo)
866 {
867 wo = wf::get_core().get_active_output();
868 }
869
870 assert(wo);
871
872 auto real_output_geometry = wo->get_layout_geometry();
873
874 global_x = xw->x;
875 global_y = xw->y;
876 wf::wlr_view_t::move(xw->x - real_output_geometry.x,
877 xw->y - real_output_geometry.y);
878
879 if (wo != get_output())
880 {
881 if (get_output())
882 {
883 get_output()->workspace->remove_view(self());
884 }
885
886 set_output(wo);
887 }
888
889 damage();
890
891 /* We update the keyboard focus before emitting the map event, so that
892 * plugins can detect that this view can have keyboard focus.
893 *
894 * Note: only actual override-redirect views should get their focus disabled */
895 view_impl->keyboard_focus_enabled = (!xw->override_redirect ||
896 wlr_xwayland_or_surface_wants_focus(xw));
897
898 get_output()->workspace->add_view(self(), wf::LAYER_UNMANAGED);
899 wf::wlr_view_t::map(surface);
900
901 if (view_impl->keyboard_focus_enabled)
902 {
903 get_output()->focus_view(self(), true);
904 }
905 }
906
should_be_decorated()907 bool wayfire_unmanaged_xwayland_view::should_be_decorated()
908 {
909 return (!xw->override_redirect && !this->has_client_decoration);
910 }
911
destroy()912 void wayfire_unmanaged_xwayland_view::destroy()
913 {
914 on_set_geometry.disconnect();
915 wayfire_xwayland_view_base::destroy();
916 }
917
918 // Xwayland DnD view
919 static wayfire_view dnd_view;
920
921 class wayfire_dnd_xwayland_view : public wayfire_unmanaged_xwayland_view
922 {
923 protected:
924 wf::wl_listener_wrapper on_set_geometry;
925
926 public:
927 using wayfire_unmanaged_xwayland_view::wayfire_unmanaged_xwayland_view;
928
get_current_impl_type() const929 xwayland_view_type_t get_current_impl_type() const override
930 {
931 return xwayland_view_type_t::DND;
932 }
933
simple_render(const wf::framebuffer_t & fb,int x,int y,const wf::region_t & damage)934 void simple_render(const wf::framebuffer_t& fb,
935 int x, int y, const wf::region_t& damage) override
936 {
937 wayfire_unmanaged_xwayland_view::simple_render(fb, x, y, damage);
938
939 timespec repaint_ended;
940 clockid_t presentation_clock =
941 wlr_backend_get_presentation_clock(wf::get_core_impl().backend);
942 clock_gettime(presentation_clock, &repaint_ended);
943 send_frame_done(repaint_ended);
944 }
945
destruct()946 void destruct() override
947 {
948 LOGD("Destroying a Xwayland drag icon");
949 if (dnd_view.get() == this)
950 {
951 dnd_view = nullptr;
952 }
953
954 wayfire_unmanaged_xwayland_view::destruct();
955 }
956
deinitialize()957 void deinitialize() override
958 {
959 wayfire_unmanaged_xwayland_view::deinitialize();
960 }
961
damage_surface_box(const wlr_box &)962 void damage_surface_box(const wlr_box&) override
963 {
964 damage();
965 }
966
967 wf::geometry_t last_global_bbox = {0, 0, 0, 0};
968
damage()969 void damage() override
970 {
971 if (!get_output())
972 {
973 return;
974 }
975
976 auto bbox = get_bounding_box() +
977 wf::origin(this->get_output()->get_layout_geometry());
978
979 for (auto& output : wf::get_core().output_layout->get_outputs())
980 {
981 auto local_bbox = bbox + -wf::origin(output->get_layout_geometry());
982 output->render->damage(local_bbox);
983 local_bbox = last_global_bbox +
984 -wf::origin(output->get_layout_geometry());
985 output->render->damage(local_bbox);
986 }
987
988 last_global_bbox = bbox;
989 }
990
map(wlr_surface * surface)991 void map(wlr_surface *surface) override
992 {
993 LOGD("Mapping a Xwayland drag icon");
994 this->set_output(wf::get_core().get_active_output());
995 wayfire_xwayland_view_base::map(surface);
996 this->damage();
997 }
998 };
999
recreate_view()1000 void wayfire_xwayland_view_base::recreate_view()
1001 {
1002 xwayland_view_type_t target_type = xwayland_view_type_t::NORMAL;
1003 if (this->is_dnd())
1004 {
1005 target_type = xwayland_view_type_t::DND;
1006 } else if (this->is_unmanaged())
1007 {
1008 target_type = xwayland_view_type_t::UNMANAGED;
1009 }
1010
1011 if (target_type == this->get_current_impl_type())
1012 {
1013 // Nothing changed
1014 return;
1015 }
1016
1017 /*
1018 * Copy xw and mapped status into the stack, because "this" may be destroyed
1019 * at some point of this function.
1020 */
1021 auto xw_surf = this->xw;
1022 bool was_mapped = is_mapped();
1023
1024 // destroy the view (unmap + destroy)
1025 if (was_mapped)
1026 {
1027 unmap();
1028 }
1029
1030 destroy();
1031
1032 // Create the new view.
1033 // Take care! The new_view pointer is passed to core as unique_ptr
1034 wayfire_xwayland_view_base *new_view;
1035 switch (target_type)
1036 {
1037 case xwayland_view_type_t::DND:
1038 new_view = new wayfire_dnd_xwayland_view(xw_surf);
1039 ::dnd_view = new_view;
1040 break;
1041
1042 case xwayland_view_type_t::UNMANAGED:
1043 new_view = new wayfire_unmanaged_xwayland_view(xw_surf);
1044 wf::get_core().add_view(std::unique_ptr<view_interface_t>(new_view));
1045 break;
1046
1047 case xwayland_view_type_t::NORMAL:
1048 new_view = new wayfire_xwayland_view(xw_surf);
1049 break;
1050 }
1051
1052 wf::get_core().add_view(std::unique_ptr<view_interface_t>(new_view));
1053 if (was_mapped)
1054 {
1055 new_view->map(xw_surf->surface);
1056 }
1057 }
1058
1059 static wlr_xwayland *xwayland_handle = nullptr;
1060 #endif
1061
init_xwayland()1062 void wf::init_xwayland()
1063 {
1064 #if WF_HAS_XWAYLAND
1065 static wf::wl_listener_wrapper on_created;
1066 static wf::wl_listener_wrapper on_ready;
1067
1068 static signal_connection_t on_shutdown{[&] (void*)
1069 {
1070 wlr_xwayland_destroy(xwayland_handle);
1071 }
1072 };
1073
1074 on_created.set_callback([] (void *data)
1075 {
1076 auto xsurf = (wlr_xwayland_surface*)data;
1077 if (xsurf->override_redirect)
1078 {
1079 wf::get_core().add_view(
1080 std::make_unique<wayfire_unmanaged_xwayland_view>(xsurf));
1081 } else
1082 {
1083 wf::get_core().add_view(
1084 std::make_unique<wayfire_xwayland_view>(xsurf));
1085 }
1086 });
1087
1088 on_ready.set_callback([&] (void *data)
1089 {
1090 if (!wayfire_xwayland_view_base::load_atoms(xwayland_handle->display_name))
1091 {
1092 LOGE("Failed to load Xwayland atoms.");
1093 } else
1094 {
1095 LOGD("Successfully loaded Xwayland atoms.");
1096 }
1097
1098 wlr_xwayland_set_seat(xwayland_handle,
1099 wf::get_core().get_current_seat());
1100 xwayland_update_default_cursor();
1101 });
1102
1103 xwayland_handle = wlr_xwayland_create(wf::get_core().display,
1104 wf::get_core_impl().compositor, false);
1105
1106 if (xwayland_handle)
1107 {
1108 on_created.connect(&xwayland_handle->events.new_surface);
1109 on_ready.connect(&xwayland_handle->events.ready);
1110 wf::get_core().connect_signal("shutdown", &on_shutdown);
1111 }
1112
1113 #endif
1114 }
1115
xwayland_update_default_cursor()1116 void wf::xwayland_update_default_cursor()
1117 {
1118 #if WF_HAS_XWAYLAND
1119 if (!xwayland_handle)
1120 {
1121 return;
1122 }
1123
1124 auto xc = wf::get_core_impl().seat->cursor->xcursor;
1125 auto cursor = wlr_xcursor_manager_get_xcursor(xc, "left_ptr", 1);
1126 if (cursor && (cursor->image_count > 0))
1127 {
1128 auto image = cursor->images[0];
1129 wlr_xwayland_set_cursor(xwayland_handle, image->buffer,
1130 image->width * 4, image->width, image->height,
1131 image->hotspot_x, image->hotspot_y);
1132 }
1133
1134 #endif
1135 }
1136
1137 #if WF_HAS_XWAYLAND
1138 // Private wlroots interface!
1139 // This only works because we have fixed wlroots version 0.12
1140 static constexpr uint32_t ATOM_LAST = 65; // wlroots 0.12 has 65 atoms
1141 struct _wlr_xwm
1142 {
1143 struct wlr_xwayland *xwayland;
1144 struct wl_event_source *event_source;
1145 struct wlr_seat *seat;
1146 uint32_t ping_timeout;
1147
1148 xcb_atom_t atoms[ATOM_LAST];
1149 xcb_connection_t *xcb_conn;
1150 };
1151
surface_restack(struct wlr_xwayland_surface * surface,struct wlr_xwayland_surface * sibling,enum xcb_stack_mode_t mode)1152 void surface_restack(struct wlr_xwayland_surface *surface,
1153 struct wlr_xwayland_surface *sibling, enum xcb_stack_mode_t mode)
1154 {
1155 _wlr_xwm *xwm = (_wlr_xwm*)surface->xwm;
1156 uint32_t values[2];
1157 size_t idx = 0;
1158 uint32_t flags = XCB_CONFIG_WINDOW_STACK_MODE;
1159
1160 if (sibling != NULL)
1161 {
1162 values[idx++] = sibling->window_id;
1163 flags |= XCB_CONFIG_WINDOW_SIBLING;
1164 }
1165
1166 values[idx++] = mode;
1167
1168 xcb_configure_window(xwm->xcb_conn, surface->window_id, flags, values);
1169 xcb_flush(xwm->xcb_conn);
1170 }
1171
1172 #endif
1173
xwayland_bring_to_front(wlr_surface * surface)1174 void wf::xwayland_bring_to_front(wlr_surface *surface)
1175 {
1176 #if WF_HAS_XWAYLAND
1177 if (wlr_surface_is_xwayland_surface(surface))
1178 {
1179 auto xw = wlr_xwayland_surface_from_wlr_surface(surface);
1180 surface_restack(xw, NULL, XCB_STACK_MODE_ABOVE);
1181 }
1182
1183 #endif
1184 }
1185
xwayland_get_display()1186 std::string wf::xwayland_get_display()
1187 {
1188 #if WF_HAS_XWAYLAND
1189
1190 return xwayland_handle ? nonull(xwayland_handle->display_name) : "";
1191 #else
1192
1193 return "";
1194 #endif
1195 }
1196
get_xwayland_drag_icon()1197 wayfire_view wf::get_xwayland_drag_icon()
1198 {
1199 #if WF_HAS_XWAYLAND
1200 if (dnd_view && dnd_view->is_mapped() && dnd_view->get_output())
1201 {
1202 return dnd_view.get();
1203 }
1204
1205 #endif
1206
1207 return nullptr;
1208 }
1209