1 /****************************************************************************
2 Copyright © 2020 Roman Gilg <subdiff@gmail.com>
3 
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) version 3, or any
8 later version accepted by the membership of KDE e.V. (or its
9 successor approved by the membership of KDE e.V.), which shall
10 act as a proxy defined in Section 6 of version 3 of the license.
11 
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 Lesser General Public License for more details.
16 
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library.  If not, see <http://www.gnu.org/licenses/>.
19 ****************************************************************************/
20 #include "xdg_shell.h"
21 #include "xdg_shell_p.h"
22 
23 #include "display.h"
24 #include "surface_p.h"
25 #include "xdg_shell_positioner_p.h"
26 #include "xdg_shell_surface.h"
27 #include "xdg_shell_surface_p.h"
28 #include "xdg_shell_toplevel_p.h"
29 
30 #include <cassert>
31 #include <map>
32 
33 namespace Wrapland::Server
34 {
35 
Private(XdgShell * q,Display * display)36 XdgShell::Private::Private(XdgShell* q, Display* display)
37     : XdgShellGlobal(q, display, &xdg_wm_base_interface, &s_interface)
38 {
39 }
40 
41 const struct xdg_wm_base_interface XdgShell::Private::s_interface = {
42     resourceDestroyCallback,
43     cb<createPositionerCallback>,
44     cb<getXdgSurfaceCallback>,
45     cb<pongCallback>,
46 };
47 
prepareUnbind(XdgShellBind * bind)48 void XdgShell::Private::prepareUnbind(XdgShellBind* bind)
49 {
50     if (auto it = bindsObjects.find(bind); it != bindsObjects.end()) {
51         auto& surfaces = it->second.surfaces;
52         auto& positioners = it->second.positioners;
53         for (auto surface : surfaces) {
54             // We remove the bind, so disconnect destroy connection.
55             QObject::disconnect(surface, &XdgShellSurface::resourceDestroyed, handle(), nullptr);
56         }
57         for (auto positioner : positioners) {
58             // We remove the bind, so disconnect destroy connection.
59             QObject::disconnect(
60                 positioner, &XdgShellPositioner::resourceDestroyed, handle(), nullptr);
61         }
62         if (!surfaces.empty()) {
63             bind->post_error(XDG_WM_BASE_ERROR_DEFUNCT_SURFACES,
64                              "xdg_wm_base destroyed before surfaces");
65         }
66         bindsObjects.erase(it);
67     }
68 }
69 
createPositionerCallback(XdgShellBind * bind,uint32_t id)70 void XdgShell::Private::createPositionerCallback(XdgShellBind* bind, uint32_t id)
71 {
72     auto priv = bind->global()->handle()->d_ptr.get();
73 
74     auto positioner = new XdgShellPositioner(bind->client()->handle(), bind->version(), id);
75 
76     auto bindsIt = priv->bindsObjects.find(bind);
77     if (bindsIt == priv->bindsObjects.end()) {
78         BindResources b;
79         b.positioners.push_back(positioner);
80         priv->bindsObjects[bind] = b;
81     } else {
82         (*bindsIt).second.positioners.push_back(positioner);
83     }
84 
85     QObject::connect(
86         positioner, &XdgShellPositioner::resourceDestroyed, priv->handle(), [bindsIt, positioner] {
87             auto& positioners = (*bindsIt).second.positioners;
88             positioners.erase(std::remove(positioners.begin(), positioners.end(), positioner),
89                               positioners.end());
90         });
91 }
92 
getXdgSurfaceCallback(XdgShellBind * bind,uint32_t id,wl_resource * wlSurface)93 void XdgShell::Private::getXdgSurfaceCallback(XdgShellBind* bind,
94                                               uint32_t id,
95                                               wl_resource* wlSurface)
96 {
97     auto priv = bind->global()->handle()->d_ptr.get();
98 
99     auto surface = Surface::Private::handle(wlSurface);
100 
101     auto bindsIt = priv->bindsObjects.find(bind);
102     if (bindsIt != priv->bindsObjects.end()) {
103         auto const& surfaces = (*bindsIt).second.surfaces;
104         auto surfaceIt = std::find_if(surfaces.cbegin(), surfaces.cend(), [surface](auto s) {
105             return surface == s->surface();
106         });
107         if (surfaceIt != surfaces.cend()) {
108             bind->post_error(XDG_WM_BASE_ERROR_ROLE, "XDG Surface already created");
109             return;
110         }
111     }
112 
113     auto shellSurface = new XdgShellSurface(
114         bind->client()->handle(), bind->version(), id, priv->handle(), surface);
115 
116     if (bindsIt == priv->bindsObjects.end()) {
117         BindResources b;
118         b.surfaces.push_back(shellSurface);
119         priv->bindsObjects[bind] = b;
120     } else {
121         (*bindsIt).second.surfaces.push_back(shellSurface);
122     }
123 
124     QObject::connect(shellSurface,
125                      &XdgShellSurface::resourceDestroyed,
126                      priv->handle(),
127                      [priv, bind, shellSurface] {
128                          auto bindsIt = priv->bindsObjects.find(bind);
129 
130                          auto& surfaces = bindsIt->second.surfaces;
131                          auto surfaceIt = std::find(surfaces.begin(), surfaces.end(), shellSurface);
132                          assert(surfaceIt != surfaces.end());
133                          surfaces.erase(surfaceIt);
134                      });
135 }
136 
pongCallback(XdgShellBind * bind,uint32_t serial)137 void XdgShell::Private::pongCallback(XdgShellBind* bind, uint32_t serial)
138 {
139     auto priv = bind->global()->handle()->d_ptr.get();
140 
141     auto timerIt = priv->pingTimers.find(serial);
142     if (timerIt != priv->pingTimers.end() && (*timerIt).second->isActive()) {
143         delete (*timerIt).second;
144         priv->pingTimers.erase(timerIt);
145         Q_EMIT priv->handle()->pongReceived(serial);
146     }
147 }
148 
149 constexpr int pingTime = 1000;
150 
setupTimer(uint32_t serial)151 void XdgShell::Private::setupTimer(uint32_t serial)
152 {
153     auto pingTimer = new QTimer();
154     pingTimer->setSingleShot(false);
155     pingTimer->setInterval(pingTime);
156 
157     int attempt = 0;
158 
159     connect(pingTimer, &QTimer::timeout, handle(), [this, serial, attempt]() mutable {
160         ++attempt;
161 
162         if (attempt == 1) {
163             Q_EMIT handle()->pingDelayed(serial);
164             return;
165         }
166         Q_EMIT handle()->pingTimeout(serial);
167         auto timerIt = pingTimers.find(serial);
168         if (timerIt != pingTimers.end()) {
169             delete (*timerIt).second;
170             pingTimers.erase(timerIt);
171         }
172     });
173 
174     pingTimers[serial] = pingTimer;
175     pingTimer->start();
176 }
177 
ping(Client * client)178 uint32_t XdgShell::Private::ping(Client* client)
179 {
180     // Find bind for this surface:
181     auto bindIt
182         = std::find_if(bindsObjects.cbegin(), bindsObjects.cend(), [client](auto const& bind) {
183               return bind.first->client()->handle() == client;
184           });
185 
186     if (bindIt == bindsObjects.cend()) {
187         return 0;
188     }
189 
190     const uint32_t pingSerial = display()->handle()->nextSerial();
191 
192     send<xdg_wm_base_send_ping>((*bindIt).first, pingSerial);
193 
194     setupTimer(pingSerial);
195     return pingSerial;
196 }
197 
getSurface(wl_resource * wlSurface)198 XdgShellSurface* XdgShell::Private::getSurface(wl_resource* wlSurface)
199 {
200     XdgShellSurface* foundSurface = nullptr;
201 
202     for (auto const& bind : bindsObjects) {
203         auto const& surfaces = bind.second.surfaces;
204         for (auto const& surface : surfaces) {
205             if (surface->d_ptr->resource() == wlSurface) {
206                 foundSurface = surface;
207                 break;
208             }
209         }
210         if (foundSurface) {
211             break;
212         }
213     }
214 
215     return foundSurface;
216 }
217 
getToplevel(wl_resource * wlToplevel)218 XdgShellToplevel* XdgShell::Private::getToplevel(wl_resource* wlToplevel)
219 {
220     XdgShellSurface* foundSurface = nullptr;
221 
222     for (auto const& bind : bindsObjects) {
223         auto const& surfaces = bind.second.surfaces;
224         for (auto const& surface : surfaces) {
225             if (surface->d_ptr->toplevel
226                 && surface->d_ptr->toplevel->d_ptr->resource() == wlToplevel) {
227                 foundSurface = surface;
228                 break;
229             }
230         }
231         if (foundSurface) {
232             break;
233         }
234     }
235 
236     if (foundSurface && foundSurface->d_ptr->toplevel) {
237         return foundSurface->d_ptr->toplevel;
238     }
239     return nullptr;
240 }
241 
getPositioner(wl_resource * wlPositioner)242 XdgShellPositioner* XdgShell::Private::getPositioner(wl_resource* wlPositioner)
243 {
244     XdgShellPositioner* foundPositioner = nullptr;
245 
246     for (auto const& bind : bindsObjects) {
247         auto const& positioners = bind.second.positioners;
248         for (auto const& positioner : positioners) {
249             if (positioner->d_ptr->resource() == wlPositioner) {
250                 foundPositioner = positioner;
251                 break;
252             }
253         }
254         if (foundPositioner) {
255             break;
256         }
257     }
258 
259     return foundPositioner;
260 }
261 
XdgShell(Display * display,QObject * parent)262 XdgShell::XdgShell(Display* display, QObject* parent)
263     : QObject(parent)
264     , d_ptr(new Private(this, display))
265 {
266     d_ptr->create();
267 }
268 
269 XdgShell::~XdgShell() = default;
270 
ping(Client * client)271 uint32_t XdgShell::ping(Client* client)
272 {
273     return d_ptr->ping(client);
274 }
275 
276 }
277