File indexing completed on 2024-11-10 04:57:08
0001 /* 0002 SPDX-FileCopyrightText: 2010 Martin Gräßlin <mgraesslin@kde.org> 0003 SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com> 0004 SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "screenshotdbusinterface2.h" 0010 #include "core/output.h" 0011 #include "effect/effecthandler.h" 0012 #include "screenshot2adaptor.h" 0013 #include "screenshotlogging.h" 0014 #include "utils/filedescriptor.h" 0015 #include "utils/serviceutils.h" 0016 0017 #include <KLocalizedString> 0018 0019 #include <QDBusConnection> 0020 #include <QDBusConnectionInterface> 0021 #include <QThreadPool> 0022 0023 #include <errno.h> 0024 #include <fcntl.h> 0025 #include <poll.h> 0026 #include <string.h> 0027 #include <unistd.h> 0028 0029 namespace KWin 0030 { 0031 0032 class ScreenShotWriter2 : public QRunnable 0033 { 0034 public: 0035 ScreenShotWriter2(FileDescriptor &&fileDescriptor, const QImage &image) 0036 : m_fileDescriptor(std::move(fileDescriptor)) 0037 , m_image(image) 0038 { 0039 } 0040 0041 void run() override 0042 { 0043 const int flags = fcntl(m_fileDescriptor.get(), F_GETFL, 0); 0044 if (flags == -1) { 0045 qCWarning(KWIN_SCREENSHOT) << "failed to get screenshot fd flags:" << strerror(errno); 0046 return; 0047 } 0048 if (!(flags & O_NONBLOCK)) { 0049 if (fcntl(m_fileDescriptor.get(), F_SETFL, flags | O_NONBLOCK) == -1) { 0050 qCWarning(KWIN_SCREENSHOT) << "failed to make screenshot fd non blocking:" << strerror(errno); 0051 return; 0052 } 0053 } 0054 0055 QFile file; 0056 if (!file.open(m_fileDescriptor.get(), QIODevice::WriteOnly)) { 0057 qCWarning(KWIN_SCREENSHOT) << Q_FUNC_INFO << "failed to open pipe:" << file.errorString(); 0058 return; 0059 } 0060 0061 const QByteArrayView buffer(m_image.constBits(), m_image.sizeInBytes()); 0062 qint64 remainingSize = buffer.size(); 0063 0064 pollfd pfds[1]; 0065 pfds[0].fd = m_fileDescriptor.get(); 0066 pfds[0].events = POLLOUT; 0067 0068 while (true) { 0069 const int ready = poll(pfds, 1, 60000); 0070 if (ready < 0) { 0071 if (errno != EINTR) { 0072 qCWarning(KWIN_SCREENSHOT) << Q_FUNC_INFO << "poll() failed:" << strerror(errno); 0073 return; 0074 } 0075 } else if (ready == 0) { 0076 qCWarning(KWIN_SCREENSHOT) << Q_FUNC_INFO << "timed out writing to pipe"; 0077 return; 0078 } else if (!(pfds[0].revents & POLLOUT)) { 0079 qCWarning(KWIN_SCREENSHOT) << Q_FUNC_INFO << "pipe is broken"; 0080 return; 0081 } else { 0082 const char *chunk = buffer.constData() + (buffer.size() - remainingSize); 0083 const qint64 writtenCount = file.write(chunk, remainingSize); 0084 0085 if (writtenCount < 0) { 0086 qCWarning(KWIN_SCREENSHOT) << Q_FUNC_INFO << "write() failed:" << file.errorString(); 0087 return; 0088 } 0089 0090 remainingSize -= writtenCount; 0091 if (writtenCount == 0 || remainingSize == 0) { 0092 return; 0093 } 0094 } 0095 } 0096 } 0097 0098 protected: 0099 FileDescriptor m_fileDescriptor; 0100 QImage m_image; 0101 }; 0102 0103 static ScreenShotFlags screenShotFlagsFromOptions(const QVariantMap &options) 0104 { 0105 ScreenShotFlags flags = ScreenShotFlags(); 0106 0107 const QVariant includeDecoration = options.value(QStringLiteral("include-decoration")); 0108 if (includeDecoration.toBool()) { 0109 flags |= ScreenShotIncludeDecoration; 0110 } 0111 0112 const QVariant includeShadow = options.value(QStringLiteral("include-shadow"), true); 0113 if (includeShadow.toBool()) { 0114 flags |= ScreenShotIncludeShadow; 0115 } 0116 0117 const QVariant includeCursor = options.value(QStringLiteral("include-cursor")); 0118 if (includeCursor.toBool()) { 0119 flags |= ScreenShotIncludeCursor; 0120 } 0121 0122 const QVariant nativeResolution = options.value(QStringLiteral("native-resolution")); 0123 if (nativeResolution.toBool()) { 0124 flags |= ScreenShotNativeResolution; 0125 } 0126 0127 return flags; 0128 } 0129 0130 static const QString s_dbusServiceName = QStringLiteral("org.kde.KWin.ScreenShot2"); 0131 static const QString s_dbusInterface = QStringLiteral("org.kde.KWin.ScreenShot2"); 0132 static const QString s_dbusObjectPath = QStringLiteral("/org/kde/KWin/ScreenShot2"); 0133 0134 static const QString s_errorNotAuthorized = QStringLiteral("org.kde.KWin.ScreenShot2.Error.NoAuthorized"); 0135 static const QString s_errorNotAuthorizedMessage = QStringLiteral("The process is not authorized to take a screenshot"); 0136 static const QString s_errorCancelled = QStringLiteral("org.kde.KWin.ScreenShot2.Error.Cancelled"); 0137 static const QString s_errorCancelledMessage = QStringLiteral("Screenshot got cancelled"); 0138 static const QString s_errorInvalidWindow = QStringLiteral("org.kde.KWin.ScreenShot2.Error.InvalidWindow"); 0139 static const QString s_errorInvalidWindowMessage = QStringLiteral("Invalid window requested"); 0140 static const QString s_errorInvalidArea = QStringLiteral("org.kde.KWin.ScreenShot2.Error.InvalidArea"); 0141 static const QString s_errorInvalidAreaMessage = QStringLiteral("Invalid area requested"); 0142 static const QString s_errorInvalidScreen = QStringLiteral("org.kde.KWin.ScreenShot2.Error.InvalidScreen"); 0143 static const QString s_errorInvalidScreenMessage = QStringLiteral("Invalid screen requested"); 0144 static const QString s_errorFileDescriptor = QStringLiteral("org.kde.KWin.ScreenShot2.Error.FileDescriptor"); 0145 static const QString s_errorFileDescriptorMessage = QStringLiteral("No valid file descriptor"); 0146 0147 class ScreenShotSource2 : public QObject 0148 { 0149 Q_OBJECT 0150 0151 public: 0152 explicit ScreenShotSource2(const QFuture<QImage> &future); 0153 0154 bool isCancelled() const; 0155 bool isCompleted() const; 0156 void marshal(ScreenShotSinkPipe2 *sink); 0157 0158 virtual QVariantMap attributes() const; 0159 0160 Q_SIGNALS: 0161 void cancelled(); 0162 void completed(); 0163 0164 private: 0165 QFuture<QImage> m_future; 0166 QFutureWatcher<QImage> *m_watcher; 0167 }; 0168 0169 class ScreenShotSourceScreen2 : public ScreenShotSource2 0170 { 0171 Q_OBJECT 0172 0173 public: 0174 ScreenShotSourceScreen2(ScreenShotEffect *effect, Output *screen, ScreenShotFlags flags); 0175 0176 QVariantMap attributes() const override; 0177 0178 private: 0179 QString m_name; 0180 }; 0181 0182 class ScreenShotSourceArea2 : public ScreenShotSource2 0183 { 0184 Q_OBJECT 0185 0186 public: 0187 ScreenShotSourceArea2(ScreenShotEffect *effect, const QRect &area, ScreenShotFlags flags); 0188 }; 0189 0190 class ScreenShotSourceWindow2 : public ScreenShotSource2 0191 { 0192 Q_OBJECT 0193 0194 public: 0195 ScreenShotSourceWindow2(ScreenShotEffect *effect, EffectWindow *window, ScreenShotFlags flags); 0196 0197 QVariantMap attributes() const override; 0198 0199 private: 0200 QUuid m_internalId; 0201 }; 0202 0203 class ScreenShotSinkPipe2 : public QObject 0204 { 0205 Q_OBJECT 0206 0207 public: 0208 ScreenShotSinkPipe2(int fileDescriptor, QDBusMessage replyMessage); 0209 0210 void cancel(); 0211 void flush(const QImage &image, const QVariantMap &attributes); 0212 0213 private: 0214 QDBusMessage m_replyMessage; 0215 FileDescriptor m_fileDescriptor; 0216 }; 0217 0218 ScreenShotSource2::ScreenShotSource2(const QFuture<QImage> &future) 0219 : m_future(future) 0220 { 0221 m_watcher = new QFutureWatcher<QImage>(this); 0222 connect(m_watcher, &QFutureWatcher<QImage>::finished, this, &ScreenShotSource2::completed); 0223 connect(m_watcher, &QFutureWatcher<QImage>::canceled, this, &ScreenShotSource2::cancelled); 0224 m_watcher->setFuture(m_future); 0225 } 0226 0227 bool ScreenShotSource2::isCancelled() const 0228 { 0229 return m_future.isCanceled(); 0230 } 0231 0232 bool ScreenShotSource2::isCompleted() const 0233 { 0234 return m_future.isFinished(); 0235 } 0236 0237 QVariantMap ScreenShotSource2::attributes() const 0238 { 0239 return QVariantMap(); 0240 } 0241 0242 void ScreenShotSource2::marshal(ScreenShotSinkPipe2 *sink) 0243 { 0244 sink->flush(m_future.result(), attributes()); 0245 } 0246 0247 ScreenShotSourceScreen2::ScreenShotSourceScreen2(ScreenShotEffect *effect, 0248 Output *screen, 0249 ScreenShotFlags flags) 0250 : ScreenShotSource2(effect->scheduleScreenShot(screen, flags)) 0251 , m_name(screen->name()) 0252 { 0253 } 0254 0255 QVariantMap ScreenShotSourceScreen2::attributes() const 0256 { 0257 return QVariantMap{ 0258 {QStringLiteral("screen"), m_name}, 0259 }; 0260 } 0261 0262 ScreenShotSourceArea2::ScreenShotSourceArea2(ScreenShotEffect *effect, 0263 const QRect &area, 0264 ScreenShotFlags flags) 0265 : ScreenShotSource2(effect->scheduleScreenShot(area, flags)) 0266 { 0267 } 0268 0269 ScreenShotSourceWindow2::ScreenShotSourceWindow2(ScreenShotEffect *effect, 0270 EffectWindow *window, 0271 ScreenShotFlags flags) 0272 : ScreenShotSource2(effect->scheduleScreenShot(window, flags)) 0273 , m_internalId(window->internalId()) 0274 { 0275 } 0276 0277 QVariantMap ScreenShotSourceWindow2::attributes() const 0278 { 0279 return QVariantMap{ 0280 {QStringLiteral("windowId"), m_internalId.toString()}, 0281 }; 0282 } 0283 0284 ScreenShotSinkPipe2::ScreenShotSinkPipe2(int fileDescriptor, QDBusMessage replyMessage) 0285 : m_replyMessage(replyMessage) 0286 , m_fileDescriptor(fileDescriptor) 0287 { 0288 } 0289 0290 void ScreenShotSinkPipe2::cancel() 0291 { 0292 QDBusConnection::sessionBus().send(m_replyMessage.createErrorReply(s_errorCancelled, 0293 s_errorCancelledMessage)); 0294 } 0295 0296 void ScreenShotSinkPipe2::flush(const QImage &image, const QVariantMap &attributes) 0297 { 0298 if (!m_fileDescriptor.isValid()) { 0299 return; 0300 } 0301 0302 // Note that the type of the data stored in the vardict matters. Be careful. 0303 QVariantMap results = attributes; 0304 results.insert(QStringLiteral("type"), QStringLiteral("raw")); 0305 results.insert(QStringLiteral("format"), quint32(image.format())); 0306 results.insert(QStringLiteral("width"), quint32(image.width())); 0307 results.insert(QStringLiteral("height"), quint32(image.height())); 0308 results.insert(QStringLiteral("stride"), quint32(image.bytesPerLine())); 0309 results.insert(QStringLiteral("scale"), double(image.devicePixelRatio())); 0310 QDBusConnection::sessionBus().send(m_replyMessage.createReply(results)); 0311 0312 auto writer = new ScreenShotWriter2(std::move(m_fileDescriptor), image); 0313 writer->setAutoDelete(true); 0314 QThreadPool::globalInstance()->start(writer); 0315 } 0316 0317 ScreenShotDBusInterface2::ScreenShotDBusInterface2(ScreenShotEffect *effect) 0318 : QObject(effect) 0319 , m_effect(effect) 0320 { 0321 new ScreenShot2Adaptor(this); 0322 0323 QDBusConnection::sessionBus().registerObject(s_dbusObjectPath, this); 0324 QDBusConnection::sessionBus().registerService(s_dbusServiceName); 0325 } 0326 0327 ScreenShotDBusInterface2::~ScreenShotDBusInterface2() 0328 { 0329 QDBusConnection::sessionBus().unregisterService(s_dbusServiceName); 0330 QDBusConnection::sessionBus().unregisterObject(s_dbusObjectPath); 0331 } 0332 0333 int ScreenShotDBusInterface2::version() const 0334 { 0335 return 4; 0336 } 0337 0338 bool ScreenShotDBusInterface2::checkPermissions() const 0339 { 0340 if (!calledFromDBus()) { 0341 return false; 0342 } 0343 0344 static bool permissionCheckDisabled = qEnvironmentVariableIntValue("KWIN_SCREENSHOT_NO_PERMISSION_CHECKS") == 1; 0345 if (permissionCheckDisabled) { 0346 return true; 0347 } 0348 0349 const QDBusReply<uint> reply = connection().interface()->servicePid(message().service()); 0350 if (reply.isValid()) { 0351 const uint pid = reply.value(); 0352 const auto interfaces = KWin::fetchRestrictedDBusInterfacesFromPid(pid); 0353 if (!interfaces.contains(s_dbusInterface)) { 0354 sendErrorReply(s_errorNotAuthorized, s_errorNotAuthorizedMessage); 0355 return false; 0356 } 0357 } else { 0358 return false; 0359 } 0360 0361 return true; 0362 } 0363 0364 QVariantMap ScreenShotDBusInterface2::CaptureActiveWindow(const QVariantMap &options, 0365 QDBusUnixFileDescriptor pipe) 0366 { 0367 if (!checkPermissions()) { 0368 return QVariantMap(); 0369 } 0370 0371 EffectWindow *window = effects->activeWindow(); 0372 if (!window) { 0373 sendErrorReply(s_errorInvalidWindow, s_errorInvalidWindowMessage); 0374 return QVariantMap(); 0375 } 0376 0377 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0); 0378 if (fileDescriptor == -1) { 0379 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage); 0380 return QVariantMap(); 0381 } 0382 0383 takeScreenShot(window, screenShotFlagsFromOptions(options), 0384 new ScreenShotSinkPipe2(fileDescriptor, message())); 0385 0386 setDelayedReply(true); 0387 return QVariantMap(); 0388 } 0389 0390 QVariantMap ScreenShotDBusInterface2::CaptureWindow(const QString &handle, 0391 const QVariantMap &options, 0392 QDBusUnixFileDescriptor pipe) 0393 { 0394 if (!checkPermissions()) { 0395 return QVariantMap(); 0396 } 0397 0398 EffectWindow *window = effects->findWindow(QUuid(handle)); 0399 if (!window) { 0400 bool ok; 0401 const int winId = handle.toInt(&ok); 0402 if (ok) { 0403 window = effects->findWindow(winId); 0404 } else { 0405 qCWarning(KWIN_SCREENSHOT) << "Invalid handle:" << handle; 0406 } 0407 } 0408 if (!window) { 0409 sendErrorReply(s_errorInvalidWindow, s_errorInvalidWindowMessage); 0410 return QVariantMap(); 0411 } 0412 0413 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0); 0414 if (fileDescriptor == -1) { 0415 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage); 0416 return QVariantMap(); 0417 } 0418 0419 takeScreenShot(window, screenShotFlagsFromOptions(options), 0420 new ScreenShotSinkPipe2(fileDescriptor, message())); 0421 0422 setDelayedReply(true); 0423 return QVariantMap(); 0424 } 0425 0426 QVariantMap ScreenShotDBusInterface2::CaptureArea(int x, int y, int width, int height, 0427 const QVariantMap &options, 0428 QDBusUnixFileDescriptor pipe) 0429 { 0430 if (!checkPermissions()) { 0431 return QVariantMap(); 0432 } 0433 0434 const QRect area(x, y, width, height); 0435 if (area.isEmpty()) { 0436 sendErrorReply(s_errorInvalidArea, s_errorInvalidAreaMessage); 0437 return QVariantMap(); 0438 } 0439 0440 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0); 0441 if (fileDescriptor == -1) { 0442 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage); 0443 return QVariantMap(); 0444 } 0445 0446 takeScreenShot(area, screenShotFlagsFromOptions(options), 0447 new ScreenShotSinkPipe2(fileDescriptor, message())); 0448 0449 setDelayedReply(true); 0450 return QVariantMap(); 0451 } 0452 0453 QVariantMap ScreenShotDBusInterface2::CaptureScreen(const QString &name, 0454 const QVariantMap &options, 0455 QDBusUnixFileDescriptor pipe) 0456 { 0457 if (!checkPermissions()) { 0458 return QVariantMap(); 0459 } 0460 0461 Output *screen = effects->findScreen(name); 0462 if (!screen) { 0463 sendErrorReply(s_errorInvalidScreen, s_errorInvalidScreenMessage); 0464 return QVariantMap(); 0465 } 0466 0467 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0); 0468 if (fileDescriptor == -1) { 0469 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage); 0470 return QVariantMap(); 0471 } 0472 0473 takeScreenShot(screen, screenShotFlagsFromOptions(options), 0474 new ScreenShotSinkPipe2(fileDescriptor, message())); 0475 0476 setDelayedReply(true); 0477 return QVariantMap(); 0478 } 0479 0480 QVariantMap ScreenShotDBusInterface2::CaptureActiveScreen(const QVariantMap &options, 0481 QDBusUnixFileDescriptor pipe) 0482 { 0483 if (!checkPermissions()) { 0484 return QVariantMap(); 0485 } 0486 0487 Output *screen = effects->activeScreen(); 0488 if (!screen) { 0489 sendErrorReply(s_errorInvalidScreen, s_errorInvalidScreenMessage); 0490 return QVariantMap(); 0491 } 0492 0493 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0); 0494 if (fileDescriptor == -1) { 0495 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage); 0496 return QVariantMap(); 0497 } 0498 0499 takeScreenShot(screen, screenShotFlagsFromOptions(options), 0500 new ScreenShotSinkPipe2(fileDescriptor, message())); 0501 0502 setDelayedReply(true); 0503 return QVariantMap(); 0504 } 0505 0506 QVariantMap ScreenShotDBusInterface2::CaptureInteractive(uint kind, 0507 const QVariantMap &options, 0508 QDBusUnixFileDescriptor pipe) 0509 { 0510 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0); 0511 if (fileDescriptor == -1) { 0512 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage); 0513 return QVariantMap(); 0514 } 0515 0516 const QDBusMessage replyMessage = message(); 0517 0518 if (kind == 0) { 0519 effects->startInteractiveWindowSelection([=, this](EffectWindow *window) { 0520 effects->hideOnScreenMessage(EffectsHandler::OnScreenMessageHideFlag::SkipsCloseAnimation); 0521 0522 if (!window) { 0523 close(fileDescriptor); 0524 0525 QDBusConnection bus = QDBusConnection::sessionBus(); 0526 bus.send(replyMessage.createErrorReply(s_errorCancelled, s_errorCancelledMessage)); 0527 } else { 0528 takeScreenShot(window, screenShotFlagsFromOptions(options), 0529 new ScreenShotSinkPipe2(fileDescriptor, replyMessage)); 0530 } 0531 }); 0532 effects->showOnScreenMessage(i18n("Select window to screen shot with left click or enter.\n" 0533 "Escape or right click to cancel."), 0534 QStringLiteral("spectacle")); 0535 } else { 0536 effects->startInteractivePositionSelection([=, this](const QPointF &point) { 0537 effects->hideOnScreenMessage(EffectsHandler::OnScreenMessageHideFlag::SkipsCloseAnimation); 0538 0539 if (point == QPoint(-1, -1)) { 0540 close(fileDescriptor); 0541 0542 QDBusConnection bus = QDBusConnection::sessionBus(); 0543 bus.send(replyMessage.createErrorReply(s_errorCancelled, s_errorCancelledMessage)); 0544 } else { 0545 Output *screen = effects->screenAt(point.toPoint()); 0546 takeScreenShot(screen, screenShotFlagsFromOptions(options), 0547 new ScreenShotSinkPipe2(fileDescriptor, replyMessage)); 0548 } 0549 }); 0550 effects->showOnScreenMessage(i18n("Create screen shot with left click or enter.\n" 0551 "Escape or right click to cancel."), 0552 QStringLiteral("spectacle")); 0553 } 0554 0555 setDelayedReply(true); 0556 return QVariantMap(); 0557 } 0558 0559 QVariantMap ScreenShotDBusInterface2::CaptureWorkspace(const QVariantMap &options, QDBusUnixFileDescriptor pipe) 0560 { 0561 if (!checkPermissions()) { 0562 return QVariantMap(); 0563 } 0564 0565 const int fileDescriptor = fcntl(pipe.fileDescriptor(), F_DUPFD_CLOEXEC, 0); 0566 if (fileDescriptor == -1) { 0567 sendErrorReply(s_errorFileDescriptor, s_errorFileDescriptorMessage); 0568 return QVariantMap(); 0569 } 0570 0571 takeScreenShot(effects->virtualScreenGeometry(), screenShotFlagsFromOptions(options), 0572 new ScreenShotSinkPipe2(fileDescriptor, message())); 0573 0574 setDelayedReply(true); 0575 return QVariantMap(); 0576 } 0577 0578 void ScreenShotDBusInterface2::bind(ScreenShotSinkPipe2 *sink, ScreenShotSource2 *source) 0579 { 0580 connect(source, &ScreenShotSource2::cancelled, sink, [sink, source]() { 0581 sink->cancel(); 0582 0583 sink->deleteLater(); 0584 source->deleteLater(); 0585 }); 0586 0587 connect(source, &ScreenShotSource2::completed, sink, [sink, source]() { 0588 source->marshal(sink); 0589 0590 sink->deleteLater(); 0591 source->deleteLater(); 0592 }); 0593 } 0594 0595 void ScreenShotDBusInterface2::takeScreenShot(Output *screen, ScreenShotFlags flags, 0596 ScreenShotSinkPipe2 *sink) 0597 { 0598 bind(sink, new ScreenShotSourceScreen2(m_effect, screen, flags)); 0599 } 0600 0601 void ScreenShotDBusInterface2::takeScreenShot(const QRect &area, ScreenShotFlags flags, 0602 ScreenShotSinkPipe2 *sink) 0603 { 0604 bind(sink, new ScreenShotSourceArea2(m_effect, area, flags)); 0605 } 0606 0607 void ScreenShotDBusInterface2::takeScreenShot(EffectWindow *window, ScreenShotFlags flags, 0608 ScreenShotSinkPipe2 *sink) 0609 { 0610 bind(sink, new ScreenShotSourceWindow2(m_effect, window, flags)); 0611 } 0612 0613 } // namespace KWin 0614 0615 #include "screenshotdbusinterface2.moc" 0616 0617 #include "moc_screenshotdbusinterface2.cpp"