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