KWin
Loading...
Searching...
No Matches
screenshotdbusinterface2.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2010 Martin Gräßlin <mgraesslin@kde.org>
3 SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
4 SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
10#include "core/output.h"
12#include "screenshot2adaptor.h"
13#include "screenshotlogging.h"
15#include "utils/serviceutils.h"
16
17#include <KLocalizedString>
18
19#include <QDBusConnection>
20#include <QDBusConnectionInterface>
21#include <QThreadPool>
22
23#include <errno.h>
24#include <fcntl.h>
25#include <poll.h>
26#include <string.h>
27#include <unistd.h>
28
29namespace KWin
30{
31
32class ScreenShotWriter2 : public QRunnable
33{
34public:
35 ScreenShotWriter2(FileDescriptor &&fileDescriptor, const QImage &image)
36 : m_fileDescriptor(std::move(fileDescriptor))
37 , m_image(image)
38 {
39 }
40
41 void run() override
42 {
43 const int flags = fcntl(m_fileDescriptor.get(), F_GETFL, 0);
44 if (flags == -1) {
45 qCWarning(KWIN_SCREENSHOT) << "failed to get screenshot fd flags:" << strerror(errno);
46 return;
47 }
48 if (!(flags & O_NONBLOCK)) {
49 if (fcntl(m_fileDescriptor.get(), F_SETFL, flags | O_NONBLOCK) == -1) {
50 qCWarning(KWIN_SCREENSHOT) << "failed to make screenshot fd non blocking:" << strerror(errno);
51 return;
52 }
53 }
54
55 QFile file;
56 if (!file.open(m_fileDescriptor.get(), QIODevice::WriteOnly)) {
57 qCWarning(KWIN_SCREENSHOT) << Q_FUNC_INFO << "failed to open pipe:" << file.errorString();
58 return;
59 }
60
61 const QByteArrayView buffer(m_image.constBits(), m_image.sizeInBytes());
62 qint64 remainingSize = buffer.size();
63
64 pollfd pfds[1];
65 pfds[0].fd = m_fileDescriptor.get();
66 pfds[0].events = POLLOUT;
67
68 while (true) {
69 const int ready = poll(pfds, 1, 60000);
70 if (ready < 0) {
71 if (errno != EINTR) {
72 qCWarning(KWIN_SCREENSHOT) << Q_FUNC_INFO << "poll() failed:" << strerror(errno);
73 return;
74 }
75 } else if (ready == 0) {
76 qCWarning(KWIN_SCREENSHOT) << Q_FUNC_INFO << "timed out writing to pipe";
77 return;
78 } else if (!(pfds[0].revents & POLLOUT)) {
79 qCWarning(KWIN_SCREENSHOT) << Q_FUNC_INFO << "pipe is broken";
80 return;
81 } else {
82 const char *chunk = buffer.constData() + (buffer.size() - remainingSize);
83 const qint64 writtenCount = file.write(chunk, remainingSize);
84
85 if (writtenCount < 0) {
86 qCWarning(KWIN_SCREENSHOT) << Q_FUNC_INFO << "write() failed:" << file.errorString();
87 return;
88 }
89
90 remainingSize -= writtenCount;
91 if (writtenCount == 0 || remainingSize == 0) {
92 return;
93 }
94 }
95 }
96 }
97
98protected:
100 QImage m_image;
101};
102
103static ScreenShotFlags screenShotFlagsFromOptions(const QVariantMap &options)
104{
105 ScreenShotFlags flags = ScreenShotFlags();
106
107 const QVariant includeDecoration = options.value(QStringLiteral("include-decoration"));
108 if (includeDecoration.toBool()) {
110 }
111
112 const QVariant includeShadow = options.value(QStringLiteral("include-shadow"), true);
113 if (includeShadow.toBool()) {
115 }
116
117 const QVariant includeCursor = options.value(QStringLiteral("include-cursor"));
118 if (includeCursor.toBool()) {
120 }
121
122 const QVariant nativeResolution = options.value(QStringLiteral("native-resolution"));
123 if (nativeResolution.toBool()) {
125 }
126
127 return flags;
128}
129
130static const QString s_dbusServiceName = QStringLiteral("org.kde.KWin.ScreenShot2");
131static const QString s_dbusInterface = QStringLiteral("org.kde.KWin.ScreenShot2");
132static const QString s_dbusObjectPath = QStringLiteral("/org/kde/KWin/ScreenShot2");
133
134static const QString s_errorNotAuthorized = QStringLiteral("org.kde.KWin.ScreenShot2.Error.NoAuthorized");
135static const QString s_errorNotAuthorizedMessage = QStringLiteral("The process is not authorized to take a screenshot");
136static const QString s_errorCancelled = QStringLiteral("org.kde.KWin.ScreenShot2.Error.Cancelled");
137static const QString s_errorCancelledMessage = QStringLiteral("Screenshot got cancelled");
138static const QString s_errorInvalidWindow = QStringLiteral("org.kde.KWin.ScreenShot2.Error.InvalidWindow");
139static const QString s_errorInvalidWindowMessage = QStringLiteral("Invalid window requested");
140static const QString s_errorInvalidArea = QStringLiteral("org.kde.KWin.ScreenShot2.Error.InvalidArea");
141static const QString s_errorInvalidAreaMessage = QStringLiteral("Invalid area requested");
142static const QString s_errorInvalidScreen = QStringLiteral("org.kde.KWin.ScreenShot2.Error.InvalidScreen");
143static const QString s_errorInvalidScreenMessage = QStringLiteral("Invalid screen requested");
144static const QString s_errorFileDescriptor = QStringLiteral("org.kde.KWin.ScreenShot2.Error.FileDescriptor");
145static const QString s_errorFileDescriptorMessage = QStringLiteral("No valid file descriptor");
146
147class ScreenShotSource2 : public QObject
148{
149 Q_OBJECT
150
151public:
152 explicit ScreenShotSource2(const QFuture<QImage> &future);
153
154 bool isCancelled() const;
155 bool isCompleted() const;
156 void marshal(ScreenShotSinkPipe2 *sink);
157
158 virtual QVariantMap attributes() const;
159
160Q_SIGNALS:
161 void cancelled();
162 void completed();
163
164private:
165 QFuture<QImage> m_future;
166 QFutureWatcher<QImage> *m_watcher;
167};
168
170{
171 Q_OBJECT
172
173public:
174 ScreenShotSourceScreen2(ScreenShotEffect *effect, Output *screen, ScreenShotFlags flags);
175
176 QVariantMap attributes() const override;
177
178private:
179 QString m_name;
180};
181
183{
184 Q_OBJECT
185
186public:
187 ScreenShotSourceArea2(ScreenShotEffect *effect, const QRect &area, ScreenShotFlags flags);
188};
189
191{
192 Q_OBJECT
193
194public:
195 ScreenShotSourceWindow2(ScreenShotEffect *effect, EffectWindow *window, ScreenShotFlags flags);
196
197 QVariantMap attributes() const override;
198
199private:
200 QUuid m_internalId;
201};
202
203class ScreenShotSinkPipe2 : public QObject
204{
205 Q_OBJECT
206
207public:
208 ScreenShotSinkPipe2(int fileDescriptor, QDBusMessage replyMessage);
209
210 void cancel();
211 void flush(const QImage &image, const QVariantMap &attributes);
212
213private:
214 QDBusMessage m_replyMessage;
215 FileDescriptor m_fileDescriptor;
216};
217
218ScreenShotSource2::ScreenShotSource2(const QFuture<QImage> &future)
219 : m_future(future)
220{
221 m_watcher = new QFutureWatcher<QImage>(this);
224 m_watcher->setFuture(m_future);
225}
226
228{
229 return m_future.isCanceled();
230}
231
233{
234 return m_future.isFinished();
235}
236
238{
239 return QVariantMap();
240}
241
243{
244 sink->flush(m_future.result(), attributes());
245}
246
248 Output *screen,
249 ScreenShotFlags flags)
250 : ScreenShotSource2(effect->scheduleScreenShot(screen, flags))
251 , m_name(screen->name())
252{
253}
254
256{
257 return QVariantMap{
258 {QStringLiteral("screen"), m_name},
259 };
260}
261
263 const QRect &area,
264 ScreenShotFlags flags)
265 : ScreenShotSource2(effect->scheduleScreenShot(area, flags))
266{
267}
268
270 EffectWindow *window,
271 ScreenShotFlags flags)
272 : ScreenShotSource2(effect->scheduleScreenShot(window, flags))
273 , m_internalId(window->internalId())
274{
275}
276
278{
279 return QVariantMap{
280 {QStringLiteral("windowId"), m_internalId.toString()},
281 };
282}
283
284ScreenShotSinkPipe2::ScreenShotSinkPipe2(int fileDescriptor, QDBusMessage replyMessage)
285 : m_replyMessage(replyMessage)
286 , m_fileDescriptor(fileDescriptor)
287{
288}
289
291{
292 QDBusConnection::sessionBus().send(m_replyMessage.createErrorReply(s_errorCancelled,
293 s_errorCancelledMessage));
294}
295
296void ScreenShotSinkPipe2::flush(const QImage &image, const QVariantMap &attributes)
297{
298 if (!m_fileDescriptor.isValid()) {
299 return;
300 }
301
302 // Note that the type of the data stored in the vardict matters. Be careful.
303 QVariantMap results = attributes;
304 results.insert(QStringLiteral("type"), QStringLiteral("raw"));
305 results.insert(QStringLiteral("format"), quint32(image.format()));
306 results.insert(QStringLiteral("width"), quint32(image.width()));
307 results.insert(QStringLiteral("height"), quint32(image.height()));
308 results.insert(QStringLiteral("stride"), quint32(image.bytesPerLine()));
309 results.insert(QStringLiteral("scale"), double(image.devicePixelRatio()));
310 QDBusConnection::sessionBus().send(m_replyMessage.createReply(results));
311
312 auto writer = new ScreenShotWriter2(std::move(m_fileDescriptor), image);
313 writer->setAutoDelete(true);
314 QThreadPool::globalInstance()->start(writer);
315}
316
318 : QObject(effect)
319 , m_effect(effect)
320{
321 new ScreenShot2Adaptor(this);
322
323 QDBusConnection::sessionBus().registerObject(s_dbusObjectPath, this);
324 QDBusConnection::sessionBus().registerService(s_dbusServiceName);
325}
326
328{
329 QDBusConnection::sessionBus().unregisterService(s_dbusServiceName);
330 QDBusConnection::sessionBus().unregisterObject(s_dbusObjectPath);
331}
332
334{
335 return 4;
336}
337
338bool ScreenShotDBusInterface2::checkPermissions() const
339{
340 if (!calledFromDBus()) {
341 return false;
342 }
343
344 static bool permissionCheckDisabled = qEnvironmentVariableIntValue("KWIN_SCREENSHOT_NO_PERMISSION_CHECKS") == 1;
345 if (permissionCheckDisabled) {
346 return true;
347 }
348
349 const QDBusReply<uint> reply = connection().interface()->servicePid(message().service());
350 if (reply.isValid()) {
351 const uint pid = reply.value();
352 const auto interfaces = KWin::fetchRestrictedDBusInterfacesFromPid(pid);
353 if (!interfaces.contains(s_dbusInterface)) {
354 sendErrorReply(s_errorNotAuthorized, s_errorNotAuthorizedMessage);
355 return false;
356 }
357 } else {
358 return false;
359 }
360
361 return true;
362}
363
365 QDBusUnixFileDescriptor pipe)
366{
367 if (!checkPermissions()) {
368 return QVariantMap();
369 }
370
371 EffectWindow *window = effects->activeWindow();
372 if (!window) {
373 sendErrorReply(s_errorInvalidWindow, s_errorInvalidWindowMessage);
374 return QVariantMap();
375 }
376
377 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0);
378 if (fileDescriptor == -1) {
379 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage);
380 return QVariantMap();
381 }
382
383 takeScreenShot(window, screenShotFlagsFromOptions(options),
384 new ScreenShotSinkPipe2(fileDescriptor, message()));
385
386 setDelayedReply(true);
387 return QVariantMap();
388}
389
390QVariantMap ScreenShotDBusInterface2::CaptureWindow(const QString &handle,
391 const QVariantMap &options,
392 QDBusUnixFileDescriptor pipe)
393{
394 if (!checkPermissions()) {
395 return QVariantMap();
396 }
397
398 EffectWindow *window = effects->findWindow(QUuid(handle));
399 if (!window) {
400 bool ok;
401 const int winId = handle.toInt(&ok);
402 if (ok) {
403 window = effects->findWindow(winId);
404 } else {
405 qCWarning(KWIN_SCREENSHOT) << "Invalid handle:" << handle;
406 }
407 }
408 if (!window) {
409 sendErrorReply(s_errorInvalidWindow, s_errorInvalidWindowMessage);
410 return QVariantMap();
411 }
412
413 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0);
414 if (fileDescriptor == -1) {
415 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage);
416 return QVariantMap();
417 }
418
419 takeScreenShot(window, screenShotFlagsFromOptions(options),
420 new ScreenShotSinkPipe2(fileDescriptor, message()));
421
422 setDelayedReply(true);
423 return QVariantMap();
424}
425
426QVariantMap ScreenShotDBusInterface2::CaptureArea(int x, int y, int width, int height,
427 const QVariantMap &options,
428 QDBusUnixFileDescriptor pipe)
429{
430 if (!checkPermissions()) {
431 return QVariantMap();
432 }
433
434 const QRect area(x, y, width, height);
435 if (area.isEmpty()) {
436 sendErrorReply(s_errorInvalidArea, s_errorInvalidAreaMessage);
437 return QVariantMap();
438 }
439
440 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0);
441 if (fileDescriptor == -1) {
442 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage);
443 return QVariantMap();
444 }
445
446 takeScreenShot(area, screenShotFlagsFromOptions(options),
447 new ScreenShotSinkPipe2(fileDescriptor, message()));
448
449 setDelayedReply(true);
450 return QVariantMap();
451}
452
453QVariantMap ScreenShotDBusInterface2::CaptureScreen(const QString &name,
454 const QVariantMap &options,
455 QDBusUnixFileDescriptor pipe)
456{
457 if (!checkPermissions()) {
458 return QVariantMap();
459 }
460
461 Output *screen = effects->findScreen(name);
462 if (!screen) {
463 sendErrorReply(s_errorInvalidScreen, s_errorInvalidScreenMessage);
464 return QVariantMap();
465 }
466
467 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0);
468 if (fileDescriptor == -1) {
469 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage);
470 return QVariantMap();
471 }
472
473 takeScreenShot(screen, screenShotFlagsFromOptions(options),
474 new ScreenShotSinkPipe2(fileDescriptor, message()));
475
476 setDelayedReply(true);
477 return QVariantMap();
478}
479
481 QDBusUnixFileDescriptor pipe)
482{
483 if (!checkPermissions()) {
484 return QVariantMap();
485 }
486
487 Output *screen = effects->activeScreen();
488 if (!screen) {
489 sendErrorReply(s_errorInvalidScreen, s_errorInvalidScreenMessage);
490 return QVariantMap();
491 }
492
493 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0);
494 if (fileDescriptor == -1) {
495 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage);
496 return QVariantMap();
497 }
498
499 takeScreenShot(screen, screenShotFlagsFromOptions(options),
500 new ScreenShotSinkPipe2(fileDescriptor, message()));
501
502 setDelayedReply(true);
503 return QVariantMap();
504}
505
507 const QVariantMap &options,
508 QDBusUnixFileDescriptor pipe)
509{
510 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0);
511 if (fileDescriptor == -1) {
512 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage);
513 return QVariantMap();
514 }
515
516 const QDBusMessage replyMessage = message();
517
518 if (kind == 0) {
521
522 if (!window) {
523 close(fileDescriptor);
524
525 QDBusConnection bus = QDBusConnection::sessionBus();
526 bus.send(replyMessage.createErrorReply(s_errorCancelled, s_errorCancelledMessage));
527 } else {
528 takeScreenShot(window, screenShotFlagsFromOptions(options),
529 new ScreenShotSinkPipe2(fileDescriptor, replyMessage));
530 }
531 });
532 effects->showOnScreenMessage(i18n("Select window to screen shot with left click or enter.\n"
533 "Escape or right click to cancel."),
534 QStringLiteral("spectacle"));
535 } else {
536 effects->startInteractivePositionSelection([=, this](const QPointF &point) {
538
539 if (point == QPoint(-1, -1)) {
540 close(fileDescriptor);
541
542 QDBusConnection bus = QDBusConnection::sessionBus();
543 bus.send(replyMessage.createErrorReply(s_errorCancelled, s_errorCancelledMessage));
544 } else {
545 Output *screen = effects->screenAt(point.toPoint());
546 takeScreenShot(screen, screenShotFlagsFromOptions(options),
547 new ScreenShotSinkPipe2(fileDescriptor, replyMessage));
548 }
549 });
550 effects->showOnScreenMessage(i18n("Create screen shot with left click or enter.\n"
551 "Escape or right click to cancel."),
552 QStringLiteral("spectacle"));
553 }
554
555 setDelayedReply(true);
556 return QVariantMap();
557}
558
559QVariantMap ScreenShotDBusInterface2::CaptureWorkspace(const QVariantMap &options, QDBusUnixFileDescriptor pipe)
560{
561 if (!checkPermissions()) {
562 return QVariantMap();
563 }
564
565 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0);
566 if (fileDescriptor == -1) {
567 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage);
568 return QVariantMap();
569 }
570
571 takeScreenShot(effects->virtualScreenGeometry(), screenShotFlagsFromOptions(options),
572 new ScreenShotSinkPipe2(fileDescriptor, message()));
573
574 setDelayedReply(true);
575 return QVariantMap();
576}
577
578void ScreenShotDBusInterface2::bind(ScreenShotSinkPipe2 *sink, ScreenShotSource2 *source)
579{
580 connect(source, &ScreenShotSource2::cancelled, sink, [sink, source]() {
581 sink->cancel();
582
583 sink->deleteLater();
584 source->deleteLater();
585 });
586
587 connect(source, &ScreenShotSource2::completed, sink, [sink, source]() {
588 source->marshal(sink);
589
590 sink->deleteLater();
591 source->deleteLater();
592 });
593}
594
595void ScreenShotDBusInterface2::takeScreenShot(Output *screen, ScreenShotFlags flags,
596 ScreenShotSinkPipe2 *sink)
597{
598 bind(sink, new ScreenShotSourceScreen2(m_effect, screen, flags));
599}
600
601void ScreenShotDBusInterface2::takeScreenShot(const QRect &area, ScreenShotFlags flags,
602 ScreenShotSinkPipe2 *sink)
603{
604 bind(sink, new ScreenShotSourceArea2(m_effect, area, flags));
605}
606
607void ScreenShotDBusInterface2::takeScreenShot(EffectWindow *window, ScreenShotFlags flags,
608 ScreenShotSinkPipe2 *sink)
609{
610 bind(sink, new ScreenShotSourceWindow2(m_effect, window, flags));
611}
612
613} // namespace KWin
614
615#include "screenshotdbusinterface2.moc"
616
617#include "moc_screenshotdbusinterface2.cpp"
Representation of a window used by/for Effect classes.
Q_SCRIPTABLE KWin::EffectWindow * findWindow(WId id) const
void startInteractivePositionSelection(std::function< void(const QPointF &)> callback)
void showOnScreenMessage(const QString &message, const QString &iconName=QString())
Output * screenAt(const QPoint &point) const
Output * findScreen(const QString &name) const
KWin::EffectWindow * activeWindow
void startInteractiveWindowSelection(std::function< void(KWin::EffectWindow *)> callback)
void hideOnScreenMessage(OnScreenMessageHideFlags flags=OnScreenMessageHideFlags())
KWin::Output * activeScreen
QVariantMap CaptureActiveScreen(const QVariantMap &options, QDBusUnixFileDescriptor pipe)
QVariantMap CaptureWorkspace(const QVariantMap &options, QDBusUnixFileDescriptor pipe)
QVariantMap CaptureInteractive(uint kind, const QVariantMap &options, QDBusUnixFileDescriptor pipe)
QVariantMap CaptureScreen(const QString &name, const QVariantMap &options, QDBusUnixFileDescriptor pipe)
QVariantMap CaptureActiveWindow(const QVariantMap &options, QDBusUnixFileDescriptor pipe)
QVariantMap CaptureWindow(const QString &handle, const QVariantMap &options, QDBusUnixFileDescriptor pipe)
QVariantMap CaptureArea(int x, int y, int width, int height, const QVariantMap &options, QDBusUnixFileDescriptor pipe)
ScreenShotDBusInterface2(ScreenShotEffect *effect)
ScreenShotSinkPipe2(int fileDescriptor, QDBusMessage replyMessage)
void flush(const QImage &image, const QVariantMap &attributes)
void marshal(ScreenShotSinkPipe2 *sink)
ScreenShotSource2(const QFuture< QImage > &future)
virtual QVariantMap attributes() const
ScreenShotSourceArea2(ScreenShotEffect *effect, const QRect &area, ScreenShotFlags flags)
QVariantMap attributes() const override
ScreenShotSourceScreen2(ScreenShotEffect *effect, Output *screen, ScreenShotFlags flags)
QVariantMap attributes() const override
ScreenShotSourceWindow2(ScreenShotEffect *effect, EffectWindow *window, ScreenShotFlags flags)
ScreenShotWriter2(FileDescriptor &&fileDescriptor, const QImage &image)
KWIN_EXPORT xcb_connection_t * connection()
Definition xcb.h:19
Options * options
Definition main.cpp:73
@ ScreenShotIncludeShadow
Include the window shadow.
Definition screenshot.h:29
@ ScreenShotNativeResolution
Take the screenshot at the native resolution.
Definition screenshot.h:28
@ ScreenShotIncludeCursor
Include the cursor.
Definition screenshot.h:27
@ ScreenShotIncludeDecoration
Include window titlebar and borders.
Definition screenshot.h:26
EffectsHandler * effects