1 /*
2     SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.1-or-later
5 */
6 
7 #include "kwindowshadow_p_x11.h"
8 
9 #include <QX11Info>
10 
11 static const QByteArray s_atomName = QByteArrayLiteral("_KDE_NET_WM_SHADOW");
12 
create()13 bool KWindowShadowTilePrivateX11::create()
14 {
15     xcb_connection_t *connection = QX11Info::connection();
16     xcb_window_t rootWindow = QX11Info::appRootWindow();
17 
18     const uint16_t width = uint16_t(image.width());
19     const uint16_t height = uint16_t(image.height());
20     const uint8_t depth = uint8_t(image.depth());
21 
22     pixmap = xcb_generate_id(connection);
23     gc = xcb_generate_id(connection);
24 
25     xcb_create_pixmap(connection, depth, pixmap, rootWindow, width, height);
26     xcb_create_gc(connection, gc, pixmap, 0, nullptr);
27 
28     xcb_put_image(connection, //
29                   XCB_IMAGE_FORMAT_Z_PIXMAP,
30                   pixmap,
31                   gc,
32                   width,
33                   height,
34                   0,
35                   0,
36                   0,
37                   depth,
38                   image.sizeInBytes(),
39                   image.constBits());
40 
41     return true;
42 }
43 
destroy()44 void KWindowShadowTilePrivateX11::destroy()
45 {
46     xcb_connection_t *connection = QX11Info::connection();
47     if (connection) {
48         xcb_free_pixmap(connection, pixmap);
49         xcb_free_gc(connection, gc);
50     }
51     pixmap = XCB_PIXMAP_NONE;
52     gc = XCB_NONE;
53 }
54 
get(const KWindowShadowTile * tile)55 KWindowShadowTilePrivateX11 *KWindowShadowTilePrivateX11::get(const KWindowShadowTile *tile)
56 {
57     KWindowShadowTilePrivate *d = KWindowShadowTilePrivate::get(tile);
58     return static_cast<KWindowShadowTilePrivateX11 *>(d);
59 }
60 
lookupAtom(const QByteArray & atomName)61 static xcb_atom_t lookupAtom(const QByteArray &atomName)
62 {
63     xcb_connection_t *connection = QX11Info::connection();
64     if (!connection) {
65         return XCB_ATOM_NONE;
66     }
67 
68     xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(connection, //
69                                                                     false,
70                                                                     atomName.size(),
71                                                                     atomName.constData());
72     xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, atomCookie, nullptr);
73 
74     if (!reply) {
75         return XCB_ATOM_NONE;
76     }
77 
78     xcb_atom_t atom = reply->atom;
79     free(reply);
80 
81     return atom;
82 }
83 
nativeHandleForTile(const KWindowShadowTile::Ptr & tile)84 static xcb_pixmap_t nativeHandleForTile(const KWindowShadowTile::Ptr &tile)
85 {
86     const auto d = KWindowShadowTilePrivateX11::get(tile.data());
87     return d->pixmap;
88 }
89 
create()90 bool KWindowShadowPrivateX11::create()
91 {
92     xcb_connection_t *connection = QX11Info::connection();
93 
94     const xcb_atom_t atom = lookupAtom(s_atomName);
95     if (atom == XCB_ATOM_NONE) {
96         return false;
97     }
98 
99     QVector<quint32> data(12);
100     int i = 0;
101 
102     // Unfortunately we cannot use handle of XCB_PIXMAP_NONE for missing shadow tiles because
103     // KWin expects **all** shadow tile handles to be valid. Maybe we could address this small
104     // inconvenience and then remove the empty tile stuff.
105 
106     if (topTile) {
107         data[i++] = nativeHandleForTile(topTile);
108     } else {
109         data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
110     }
111 
112     if (topRightTile) {
113         data[i++] = nativeHandleForTile(topRightTile);
114     } else {
115         data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
116     }
117 
118     if (rightTile) {
119         data[i++] = nativeHandleForTile(rightTile);
120     } else {
121         data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
122     }
123 
124     if (bottomRightTile) {
125         data[i++] = nativeHandleForTile(bottomRightTile);
126     } else {
127         data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
128     }
129 
130     if (bottomTile) {
131         data[i++] = nativeHandleForTile(bottomTile);
132     } else {
133         data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
134     }
135 
136     if (bottomLeftTile) {
137         data[i++] = nativeHandleForTile(bottomLeftTile);
138     } else {
139         data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
140     }
141 
142     if (leftTile) {
143         data[i++] = nativeHandleForTile(leftTile);
144     } else {
145         data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
146     }
147 
148     if (topLeftTile) {
149         data[i++] = nativeHandleForTile(topLeftTile);
150     } else {
151         data[i++] = nativeHandleForTile(getOrCreateEmptyTile());
152     }
153 
154     if (topLeftTile || topTile || topRightTile) {
155         data[i++] = uint32_t(padding.top());
156     } else {
157         data[i++] = 1;
158     }
159 
160     if (topRightTile || rightTile || bottomRightTile) {
161         data[i++] = uint32_t(padding.right());
162     } else {
163         data[i++] = 1;
164     }
165 
166     if (bottomRightTile || bottomTile || bottomLeftTile) {
167         data[i++] = uint32_t(padding.bottom());
168     } else {
169         data[i++] = 1;
170     }
171 
172     if (bottomLeftTile || leftTile || topLeftTile) {
173         data[i++] = uint32_t(padding.left());
174     } else {
175         data[i++] = 1;
176     }
177 
178     xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window->winId(), atom, XCB_ATOM_CARDINAL, 32, data.size(), data.constData());
179     xcb_flush(connection);
180 
181     return true;
182 }
183 
destroy()184 void KWindowShadowPrivateX11::destroy()
185 {
186     emptyTile = nullptr;
187 
188     // For some reason, QWindow changes visibility of QSurface::surfaceHandle().
189     const QSurface *surface = window;
190 
191     // Attempting to uninstall the shadow after the platform window had been destroyed.
192     if (!(surface && surface->surfaceHandle())) {
193         return;
194     }
195 
196     xcb_connection_t *connection = QX11Info::connection();
197 
198     const xcb_atom_t atom = lookupAtom(s_atomName);
199     if (atom == XCB_ATOM_NONE) {
200         return;
201     }
202 
203     xcb_delete_property(connection, window->winId(), atom);
204 }
205 
getOrCreateEmptyTile()206 KWindowShadowTile::Ptr KWindowShadowPrivateX11::getOrCreateEmptyTile()
207 {
208     if (!emptyTile) {
209         QImage image(QSize(1, 1), QImage::Format_ARGB32);
210         image.fill(Qt::transparent);
211 
212         emptyTile = KWindowShadowTile::Ptr::create();
213         emptyTile->setImage(image);
214         emptyTile->create();
215     }
216 
217     return emptyTile;
218 }
219