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