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_surface.h"
21 #include "xdg_shell_surface_p.h"
22
23 #include "surface_p.h"
24 #include "xdg_shell.h"
25 #include "xdg_shell_p.h"
26 #include "xdg_shell_popup.h"
27 #include "xdg_shell_popup_p.h"
28 #include "xdg_shell_positioner_p.h"
29 #include "xdg_shell_toplevel.h"
30 #include "xdg_shell_toplevel_p.h"
31
32 #include "wayland/resource.h"
33
34 namespace Wrapland::Server
35 {
36
37 const struct xdg_surface_interface XdgShellSurface::Private::s_interface = {
38 destroyCallback,
39 getTopLevelCallback,
40 getPopupCallback,
41 setWindowGeometryCallback,
42 ackConfigureCallback,
43 };
44
Private(Client * client,uint32_t version,uint32_t id,XdgShell * shell,Surface * surface,XdgShellSurface * q)45 XdgShellSurface::Private::Private(Client* client,
46 uint32_t version,
47 uint32_t id,
48 XdgShell* shell,
49 Surface* surface,
50 XdgShellSurface* q)
51 : Wayland::Resource<XdgShellSurface>(client,
52 version,
53 id,
54 &xdg_surface_interface,
55 &s_interface,
56 q)
57 , m_shell(shell)
58 , m_surface(surface)
59 {
60 }
61
getTopLevelCallback(wl_client * wlClient,wl_resource * wlResource,uint32_t id)62 void XdgShellSurface::Private::getTopLevelCallback([[maybe_unused]] wl_client* wlClient,
63 wl_resource* wlResource,
64 uint32_t id)
65 {
66 auto priv = handle(wlResource)->d_ptr;
67
68 if (!priv->check_creation_error()) {
69 return;
70 }
71 auto topLevel = new XdgShellToplevel(priv->version(), id, priv->handle());
72 priv->toplevel = topLevel;
73
74 priv->m_surface->d_ptr->shellSurface = priv->handle();
75 QObject::connect(topLevel,
76 &XdgShellToplevel::resourceDestroyed,
77 priv->m_surface,
78 [surface = priv->m_surface] { surface->d_ptr->shellSurface = nullptr; });
79
80 Q_EMIT priv->m_shell->toplevelCreated(topLevel);
81 }
82
getPopupCallback(wl_client * wlClient,wl_resource * wlResource,uint32_t id,wl_resource * wlParent,wl_resource * wlPositioner)83 void XdgShellSurface::Private::getPopupCallback([[maybe_unused]] wl_client* wlClient,
84 wl_resource* wlResource,
85 uint32_t id,
86 wl_resource* wlParent,
87 wl_resource* wlPositioner)
88 {
89 auto priv = handle(wlResource)->d_ptr;
90
91 if (!priv->check_creation_error()) {
92 return;
93 }
94
95 auto positioner = priv->m_shell->d_ptr->getPositioner(wlPositioner);
96 if (!positioner) {
97 priv->postError(XDG_WM_BASE_ERROR_INVALID_POSITIONER, "Invalid positioner");
98 return;
99 }
100
101 // TODO(romangg): Allow to set parent surface via side-channel (see protocol description).
102 auto parent = wlParent ? priv->m_shell->d_ptr->getSurface(wlParent) : nullptr;
103 if (wlParent && !parent) {
104 priv->postError(XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT, "Invalid popup parent");
105 return;
106 }
107
108 auto popup = new XdgShellPopup(priv->version(), id, priv->handle(), parent);
109
110 popup->d_ptr->parent = parent;
111 popup->d_ptr->initialSize = positioner->initialSize();
112 popup->d_ptr->anchorRect = positioner->anchorRect();
113 popup->d_ptr->anchorEdge = positioner->anchorEdge();
114 popup->d_ptr->gravity = positioner->gravity();
115 popup->d_ptr->constraintAdjustments = positioner->constraintAdjustments();
116 popup->d_ptr->anchorOffset = positioner->anchorOffset();
117
118 priv->popup = popup;
119
120 priv->m_surface->d_ptr->shellSurface = priv->handle();
121 QObject::connect(popup,
122 &XdgShellPopup::resourceDestroyed,
123 priv->m_surface,
124 [surface = priv->m_surface] { surface->d_ptr->shellSurface = nullptr; });
125
126 Q_EMIT priv->m_shell->popupCreated(popup);
127 }
128
setWindowGeometryCallback(wl_client * wlClient,wl_resource * wlResource,int32_t x,int32_t y,int32_t width,int32_t height)129 void XdgShellSurface::Private::setWindowGeometryCallback([[maybe_unused]] wl_client* wlClient,
130 wl_resource* wlResource,
131 int32_t x,
132 int32_t y,
133 int32_t width,
134 int32_t height)
135 {
136 auto priv = handle(wlResource)->d_ptr;
137
138 if (!priv->toplevel && !priv->popup) {
139 priv->postError(XDG_SURFACE_ERROR_NOT_CONSTRUCTED, "No role object constructed.");
140 return;
141 }
142
143 if (width < 0 || height < 0) {
144 priv->postError(XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE,
145 "Tried to set invalid xdg-surface geometry");
146 return;
147 }
148
149 priv->pending_state.window_geometry = QRect(x, y, width, height);
150 priv->pending_state.window_geometry_set = true;
151 }
152
ackConfigureCallback(wl_client * wlClient,wl_resource * wlResource,uint32_t serial)153 void XdgShellSurface::Private::ackConfigureCallback([[maybe_unused]] wl_client* wlClient,
154 wl_resource* wlResource,
155 uint32_t serial)
156 {
157 auto priv = handle(wlResource)->d_ptr;
158
159 if (!priv->toplevel && !priv->popup) {
160 priv->postError(XDG_SURFACE_ERROR_NOT_CONSTRUCTED, "No role object constructed.");
161 return;
162 }
163
164 if (priv->toplevel) {
165 priv->toplevel->d_ptr->ackConfigure(serial);
166 } else if (priv->popup) {
167 priv->popup->d_ptr->ackConfigure(serial);
168 }
169 }
170
check_creation_error()171 bool XdgShellSurface::Private::check_creation_error()
172 {
173 if (m_surface->d_ptr->has_role()) {
174 postError(XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED, "Surface already has a role.");
175 return false;
176 }
177 if (m_surface->d_ptr->had_buffer_attached) {
178 postError(XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED,
179 "Creation after a buffer was already attached.");
180 return false;
181 }
182 return true;
183 }
184
XdgShellSurface(Client * client,uint32_t version,uint32_t id,XdgShell * shell,Surface * surface)185 XdgShellSurface::XdgShellSurface(Client* client,
186 uint32_t version,
187 uint32_t id,
188 XdgShell* shell,
189 Surface* surface)
190 : QObject(nullptr)
191 , d_ptr(new Private(client, version, id, shell, surface, this))
192 {
193 }
194
configurePending() const195 bool XdgShellSurface::configurePending() const
196 {
197 return !d_ptr->configureSerials.empty();
198 }
199
surface() const200 Surface* XdgShellSurface::surface() const
201 {
202 return d_ptr->m_surface;
203 }
204
commit()205 void XdgShellSurface::commit()
206 {
207 auto const geo_set = d_ptr->pending_state.window_geometry_set;
208
209 if (geo_set) {
210 d_ptr->current_state.window_geometry = d_ptr->pending_state.window_geometry;
211 d_ptr->current_state.window_geometry_set = true;
212 }
213
214 d_ptr->pending_state = Private::state{};
215
216 if (d_ptr->toplevel) {
217 d_ptr->toplevel->d_ptr->commit();
218 }
219
220 if (geo_set) {
221 Q_EMIT window_geometry_changed(d_ptr->current_state.window_geometry);
222 }
223 }
224
window_geometry() const225 QRect XdgShellSurface::window_geometry() const
226 {
227 auto const bounds_geo = surface()->expanse();
228
229 if (!d_ptr->current_state.window_geometry_set) {
230 return bounds_geo;
231 }
232
233 return d_ptr->current_state.window_geometry.intersected(bounds_geo);
234 }
235
window_margins() const236 QMargins XdgShellSurface::window_margins() const
237 {
238 auto const window_geo = window_geometry();
239
240 QMargins margins;
241
242 margins.setLeft(window_geo.left());
243 margins.setTop(window_geo.top());
244
245 auto const surface_size = surface()->size();
246
247 margins.setRight(surface_size.width() - window_geo.right() - 1);
248 margins.setBottom(surface_size.height() - window_geo.bottom() - 1);
249
250 return margins;
251 }
252
253 }
254