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