1/*
2 SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6#include "shm_pool.h"
7#include "buffer_p.h"
8#include "event_queue.h"
9#include "logging.h"
10#include "wayland_pointer_p.h"
11// Qt
12#include <QDebug>
13#include <QImage>
14// system
15#include <fcntl.h>
16#include <sys/mman.h>
17#include <unistd.h>
18// wayland
19#include <wayland-client-protocol.h>
20
21namespace KWayland
22{
23namespace Client
24{
25class Q_DECL_HIDDEN ShmPool::Private
26{
27public:
28 Private(ShmPool *q);
29 bool createPool();
30 bool resizePool(int32_t newSize);
31 QList<QSharedPointer<Buffer>>::iterator getBuffer(const QSize &size, int32_t stride, Buffer::Format format);
32 WaylandPointer<wl_shm, wl_shm_destroy> shm;
33 WaylandPointer<wl_shm_pool, wl_shm_pool_destroy> pool;
34 void *poolData = nullptr;
35 int fd = -1;
36 int32_t size = 1024;
37 bool valid = false;
38 int offset = 0;
39 QList<QSharedPointer<Buffer>> buffers;
40 EventQueue *queue = nullptr;
41
42private:
43 ShmPool *q;
44};
45
46ShmPool::Private::Private(ShmPool *q)
47 : q(q)
48{
49}
50
51ShmPool::ShmPool(QObject *parent)
52 : QObject(parent)
53 , d(new Private(this))
54{
55}
56
57ShmPool::~ShmPool()
58{
59 release();
60}
61
62void ShmPool::release()
63{
64 d->buffers.clear();
65 if (d->poolData) {
66 munmap(addr: d->poolData, len: d->size);
67 d->poolData = nullptr;
68 }
69 if (d->fd != -1) {
70 close(fd: d->fd);
71 d->fd = -1;
72 }
73 d->pool.release();
74 d->shm.release();
75 d->valid = false;
76 d->offset = 0;
77}
78
79void ShmPool::destroy()
80{
81 for (auto b : d->buffers) {
82 b->d->destroy();
83 }
84 d->buffers.clear();
85 if (d->poolData) {
86 munmap(addr: d->poolData, len: d->size);
87 d->poolData = nullptr;
88 }
89 if (d->fd != -1) {
90 close(fd: d->fd);
91 d->fd = -1;
92 }
93 d->pool.destroy();
94 d->shm.destroy();
95 d->valid = false;
96 d->offset = 0;
97}
98
99void ShmPool::setup(wl_shm *shm)
100{
101 Q_ASSERT(shm);
102 Q_ASSERT(!d->shm);
103 d->shm.setup(pointer: shm);
104 d->valid = d->createPool();
105}
106
107void ShmPool::setEventQueue(EventQueue *queue)
108{
109 d->queue = queue;
110}
111
112EventQueue *ShmPool::eventQueue()
113{
114 return d->queue;
115}
116
117bool ShmPool::Private::createPool()
118{
119#if HAVE_MEMFD
120 fd = memfd_create(name: "kwayland-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
121 if (fd >= 0) {
122 fcntl(fd: fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
123 } else
124#endif
125 {
126 char templateName[] = "/tmp/kwayland-shared-XXXXXX";
127 fd = mkstemp(template: templateName);
128 if (fd >= 0) {
129 unlink(name: templateName);
130
131 int flags = fcntl(fd: fd, F_GETFD);
132 if (flags == -1 || fcntl(fd: fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
133 close(fd: fd);
134 fd = -1;
135 }
136 }
137 }
138
139 if (fd == -1) {
140 qCDebug(KWAYLAND_CLIENT) << "Could not open temporary file for Shm pool";
141 return false;
142 }
143
144 if (ftruncate(fd: fd, length: size) < 0) {
145 qCDebug(KWAYLAND_CLIENT) << "Could not set size for Shm pool file";
146 return false;
147 }
148 poolData = mmap(addr: nullptr, len: size, PROT_READ | PROT_WRITE, MAP_SHARED, fd: fd, offset: 0);
149 pool.setup(pointer: wl_shm_create_pool(wl_shm: shm, fd, size));
150
151 if (poolData == MAP_FAILED || !pool) {
152 qCDebug(KWAYLAND_CLIENT) << "Creating Shm pool failed";
153 return false;
154 }
155 return true;
156}
157
158bool ShmPool::Private::resizePool(int32_t newSize)
159{
160 if (ftruncate(fd: fd, length: newSize) < 0) {
161 qCDebug(KWAYLAND_CLIENT) << "Could not set new size for Shm pool file";
162 return false;
163 }
164 wl_shm_pool_resize(wl_shm_pool: pool, size: newSize);
165 munmap(addr: poolData, len: size);
166 poolData = mmap(addr: nullptr, len: newSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd: fd, offset: 0);
167 size = newSize;
168 if (poolData == MAP_FAILED) {
169 qCDebug(KWAYLAND_CLIENT) << "Resizing Shm pool failed";
170 return false;
171 }
172 Q_EMIT q->poolResized();
173 return true;
174}
175
176namespace
177{
178static Buffer::Format toBufferFormat(const QImage &image)
179{
180 switch (image.format()) {
181 case QImage::Format_ARGB32_Premultiplied:
182 return Buffer::Format::ARGB32;
183 case QImage::Format_RGB32:
184 return Buffer::Format::RGB32;
185 case QImage::Format_ARGB32:
186 qCWarning(KWAYLAND_CLIENT) << "Unsupported image format: " << image.format() << ". expect slow performance. Use QImage::Format_ARGB32_Premultiplied";
187 return Buffer::Format::ARGB32;
188 default:
189 qCWarning(KWAYLAND_CLIENT) << "Unsupported image format: " << image.format() << ". expect slow performance.";
190 return Buffer::Format::ARGB32;
191 }
192}
193}
194
195Buffer::Ptr ShmPool::createBuffer(const QImage &image)
196{
197 if (image.isNull() || !d->valid) {
198 return QWeakPointer<Buffer>();
199 }
200 auto format = toBufferFormat(image);
201 auto it = d->getBuffer(size: image.size(), stride: image.bytesPerLine(), format);
202 if (it == d->buffers.end()) {
203 return QWeakPointer<Buffer>();
204 }
205 if (format == Buffer::Format::ARGB32 && image.format() != QImage::Format_ARGB32_Premultiplied) {
206 auto imageCopy = image.convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
207 (*it)->copy(src: imageCopy.bits());
208 } else {
209 (*it)->copy(src: image.bits());
210 }
211 return QWeakPointer<Buffer>(*it);
212}
213
214Buffer::Ptr ShmPool::createBuffer(const QSize &size, int32_t stride, const void *src, Buffer::Format format)
215{
216 if (size.isEmpty() || !d->valid) {
217 return QWeakPointer<Buffer>();
218 }
219 auto it = d->getBuffer(size, stride, format);
220 if (it == d->buffers.end()) {
221 return QWeakPointer<Buffer>();
222 }
223 (*it)->copy(src);
224 return QWeakPointer<Buffer>(*it);
225}
226
227namespace
228{
229static wl_shm_format toWaylandFormat(Buffer::Format format)
230{
231 switch (format) {
232 case Buffer::Format::ARGB32:
233 return WL_SHM_FORMAT_ARGB8888;
234 case Buffer::Format::RGB32:
235 return WL_SHM_FORMAT_XRGB8888;
236 }
237 abort();
238}
239}
240
241Buffer::Ptr ShmPool::getBuffer(const QSize &size, int32_t stride, Buffer::Format format)
242{
243 auto it = d->getBuffer(size, stride, format);
244 if (it == d->buffers.end()) {
245 return QWeakPointer<Buffer>();
246 }
247 return QWeakPointer<Buffer>(*it);
248}
249
250QList<QSharedPointer<Buffer>>::iterator ShmPool::Private::getBuffer(const QSize &s, int32_t stride, Buffer::Format format)
251{
252 for (auto it = buffers.begin(); it != buffers.end(); ++it) {
253 auto buffer = *it;
254 if (!buffer->isReleased() || buffer->isUsed()) {
255 continue;
256 }
257 if (buffer->size() != s || buffer->stride() != stride || buffer->format() != format) {
258 continue;
259 }
260 buffer->setReleased(false);
261 return it;
262 }
263 const int32_t byteCount = s.height() * stride;
264 if (offset + byteCount > size) {
265 if (!resizePool(newSize: size + byteCount)) {
266 return buffers.end();
267 }
268 }
269 // we don't have a buffer which we could reuse - need to create a new one
270 wl_buffer *native = wl_shm_pool_create_buffer(wl_shm_pool: pool, offset, width: s.width(), height: s.height(), stride, format: toWaylandFormat(format));
271 if (!native) {
272 return buffers.end();
273 }
274 if (queue) {
275 queue->addProxy(proxy: native);
276 }
277 Buffer *buffer = new Buffer(q, native, s, stride, offset, format);
278 offset += byteCount;
279 auto it = buffers.insert(before: buffers.end(), t: QSharedPointer<Buffer>(buffer));
280 return it;
281}
282
283bool ShmPool::isValid() const
284{
285 return d->valid;
286}
287
288void *ShmPool::poolAddress() const
289{
290 return d->poolData;
291}
292
293wl_shm *ShmPool::shm()
294{
295 return d->shm;
296}
297
298}
299}
300
301#include "moc_shm_pool.cpp"
302

source code of kwayland/src/client/shm_pool.cpp