diff --git a/src/Gui/ExportMenu.cpp b/src/Gui/ExportMenu.cpp index 4efa5f7..b29085d 100644 --- a/src/Gui/ExportMenu.cpp +++ b/src/Gui/ExportMenu.cpp @@ -1,212 +1,206 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ExportMenu.h" -#include -#include -#include -#include -#include -#include +#include "Config.h" #include -#include -#include #include #include - -#include "Config.h" +#include #ifdef KIPI_FOUND #include -#include -#include "KipiInterface/KSGKipiInterface.h" #endif +#include +#include +#include + ExportMenu::ExportMenu(QWidget *parent) : QMenu(parent), #ifdef PURPOSE_FOUND mUpdatedImageAvailable(false), mPurposeMenu(new Purpose::Menu(this)), #endif mExportManager(ExportManager::instance()) { QTimer::singleShot(300, this, &ExportMenu::populateMenu); } void ExportMenu::populateMenu() { #ifdef PURPOSE_FOUND loadPurposeMenu(); #endif #ifdef KIPI_FOUND mKipiMenu = addMenu(i18n("More Online Services")); mKipiMenu->addAction(i18n("Please wait...")); mKipiMenuLoaded = false; connect(mKipiMenu, &QMenu::aboutToShow, this, &ExportMenu::loadKipiItems); #endif addSeparator(); getKServiceItems(); } void ExportMenu::imageUpdated() { #ifdef PURPOSE_FOUND // mark cached image as stale mUpdatedImageAvailable = true; mPurposeMenu->clear(); #endif } void ExportMenu::getKServiceItems() { // populate all locally installed applications and services // which can handle images first const KService::List services = KMimeTypeTrader::self()->query(QStringLiteral("image/png")); Q_FOREACH (auto service, services) { QString name = service->name().replace(QLatin1Char('&'), QLatin1String("&&")); QAction *action = new QAction(QIcon::fromTheme(service->icon()), name, nullptr); connect(action, &QAction::triggered, [=]() { QList whereIs({ mExportManager->tempSave() }); KRun::runService(*service, whereIs, parentWidget(), true); }); addAction(action); } // now let the user manually chose an application to open the // image with addSeparator(); QAction *openWith = new QAction(this); openWith->setText(i18n("Other Application")); openWith->setIcon(QIcon::fromTheme(QStringLiteral("document-share"))); openWith->setShortcuts(KStandardShortcut::open()); connect(openWith, &QAction::triggered, [=]() { QList whereIs({ mExportManager->tempSave() }); KRun::displayOpenWithDialog(whereIs, parentWidget(), true); }); addAction(openWith); } #ifdef KIPI_FOUND void ExportMenu::loadKipiItems() { if (!mKipiMenuLoaded) { QTimer::singleShot(500, this, &ExportMenu::getKipiItems); mKipiMenuLoaded = true; } } void ExportMenu::getKipiItems() { mKipiMenu->clear(); mKipiInterface = new KSGKipiInterface(this); KIPI::PluginLoader *loader = new KIPI::PluginLoader; loader->setInterface(mKipiInterface); loader->init(); KIPI::PluginLoader::PluginList pluginList = loader->pluginList(); Q_FOREACH (const auto &pluginInfo, pluginList) { if (!(pluginInfo->shouldLoad())) { continue; } KIPI::Plugin *plugin = pluginInfo->plugin(); if (!(plugin)) { qWarning() << i18n("KIPI plugin from library %1 failed to load", pluginInfo->library()); continue; } plugin->setup(&mDummyWidget); QList actions = plugin->actions(); QSet exportActions; Q_FOREACH (auto action, actions) { KIPI::Category category = plugin->category(action); if (category == KIPI::ExportPlugin) { exportActions += action; } else if (category == KIPI::ImagesPlugin && pluginInfo->library().contains(QStringLiteral("kipiplugin_sendimages"))) { exportActions += action; } } Q_FOREACH (auto action, exportActions) { mKipiMenu->addAction(action); } } // If there are no export actions, then perhaps the kipi-plugins package is not installed. if (mKipiMenu->isEmpty()) { mKipiMenu->addAction(i18n("No KIPI plugins available"))->setEnabled(false); } } #endif #ifdef PURPOSE_FOUND void ExportMenu::loadPurposeMenu() { // attach the menu QAction *purposeMenu = addMenu(mPurposeMenu); purposeMenu->setText(i18n("Share")); // set up the callback signal connect(mPurposeMenu, &Purpose::Menu::finished, this, [this](const QJsonObject &output, int error, const QString &message) { if (error) { emit imageShared(true, message); } else { emit imageShared(false, output[QStringLiteral("url")].toString()); } }); // update available options based on the latest picture connect(mPurposeMenu, &QMenu::aboutToShow, this, &ExportMenu::loadPurposeItems); } void ExportMenu::loadPurposeItems() { if (!mUpdatedImageAvailable) { return; } // updated image available, we lazily load it now QString dataUri = ExportManager::instance()->pixmapDataUri(); mUpdatedImageAvailable = false; mPurposeMenu->model()->setInputData(QJsonObject { { QStringLiteral("mimeType"), QStringLiteral("image/png") }, { QStringLiteral("urls"), QJsonArray({ dataUri }) } }); mPurposeMenu->model()->setPluginType(QStringLiteral("Export")); mPurposeMenu->reload(); } #endif diff --git a/src/Gui/KSMainWindow.cpp b/src/Gui/KSMainWindow.cpp index 248f05c..02d25ef 100644 --- a/src/Gui/KSMainWindow.cpp +++ b/src/Gui/KSMainWindow.cpp @@ -1,397 +1,392 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KSMainWindow.h" + #include "Config.h" +#include "SettingsDialog/SettingsDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include #include +#include #include - #ifdef XCB_FOUND #include -#include #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "SettingsDialog/SettingsDialog.h" -#include "ExportMenu.h" -#include "ExportManager.h" -#include "SpectacleConfig.h" static const int DEFAULT_WINDOW_HEIGHT = 420; static const int DEFAULT_WINDOW_WIDTH = 840; static const int MAXIMUM_WINDOW_WIDTH = 1000; KSMainWindow::KSMainWindow(bool onClickAvailable, QWidget *parent) : QDialog(parent), mKSWidget(new KSWidget), mDivider(new QFrame), mDialogButtonBox(new QDialogButtonBox), mConfigureButton(new QToolButton), mToolsButton(new QPushButton), mSendToButton(new QPushButton), mClipboardButton(new QToolButton), mSaveButton(new QToolButton), mSaveMenu(new QMenu), mSaveAsAction(new QAction(nullptr)), mSaveAction(new QAction(nullptr)), mMessageWidget(new KMessageWidget), mToolsMenu(new QMenu), mScreenRecorderToolsMenu(new QMenu), mExportMenu(new ExportMenu(this)), mOnClickAvailable(onClickAvailable) { // before we do anything, we need to set a window property // that skips the close/hide window animation on kwin. this // fixes a ghost image of the spectacle window that appears // on subsequent screenshots taken with the take new screenshot // button // // credits for this goes to Thomas Lübking #ifdef XCB_FOUND if (KWindowSystem::isPlatformX11()) { // create a window if we haven't already. note that the QWidget constructor // should already have done this if (winId() == 0) { create(0, true, true); } // do the xcb shenanigans xcb_connection_t *xcbConn = QX11Info::connection(); const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SKIP_CLOSE_ANIMATION"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(xcbConn, false, effectName.length(), effectName.constData()); QScopedPointer atom(xcb_intern_atom_reply(xcbConn, atomCookie, nullptr)); if (atom.isNull()) { goto done; } uint32_t value = 1; xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, winId(), atom->atom, XCB_ATOM_CARDINAL, 32, 1, &value); } #endif done: QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); } KSMainWindow::~KSMainWindow() {} // GUI init void KSMainWindow::init() { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); // window properties setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); QPoint location = guiConfig.readEntry("window-position", QPoint(50, 50)); move(location); // change window title on save connect(ExportManager::instance(), &ExportManager::imageSaved, this, &KSMainWindow::setScreenshotWindowTitle); // the KSGWidget connect(mKSWidget, &KSWidget::newScreenshotRequest, this, &KSMainWindow::captureScreenshot); connect(mKSWidget, &KSWidget::dragInitiated, this, &KSMainWindow::dragAndDropRequest); // the Button Bar mDialogButtonBox->setStandardButtons(QDialogButtonBox::Help); mConfigureButton->setDefaultAction(KStandardAction::preferences(this, SLOT(showPreferencesDialog()), this)); mConfigureButton->setText(i18n("Configure...")); mConfigureButton->setToolTip(i18n("Change Spectacle's settings.")); mConfigureButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mDialogButtonBox->addButton(mConfigureButton, QDialogButtonBox::ResetRole); KGuiItem::assign(mToolsButton, KGuiItem(i18n("Tools"))); mToolsButton->setIcon(QIcon::fromTheme(QStringLiteral("application-menu"))); mDialogButtonBox->addButton(mToolsButton, QDialogButtonBox::ActionRole); mToolsButton->setMenu(mToolsMenu); KGuiItem::assign(mSendToButton, KGuiItem(i18n("Export"))); mSendToButton->setIcon(QIcon::fromTheme(QStringLiteral("document-share"))); mDialogButtonBox->addButton(mSendToButton, QDialogButtonBox::ActionRole); mClipboardButton->setDefaultAction(KStandardAction::copy(this, SLOT(sendToClipboard()), this)); mClipboardButton->setText(i18n("Copy To Clipboard")); mClipboardButton->setToolTip(i18n("Copy the current screenshot image to the clipboard.")); mClipboardButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mDialogButtonBox->addButton(mClipboardButton, QDialogButtonBox::ActionRole); mSaveButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mSaveButton->setMenu(mSaveMenu); mSaveButton->setPopupMode(QToolButton::MenuButtonPopup); mDialogButtonBox->addButton(mSaveButton, QDialogButtonBox::ActionRole); // the help menu KHelpMenu *helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true); mDialogButtonBox->button(QDialogButtonBox::Help)->setMenu(helpMenu->menu()); // the tools menu mToolsMenu->addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Screenshots Folder"), this, &KSMainWindow::openScreenshotsFolder); mToolsMenu->addAction(KStandardAction::print(this, &KSMainWindow::showPrintDialog, this)); mScreenRecorderToolsMenu = mToolsMenu->addMenu(i18n("Record Screen")); connect(mScreenRecorderToolsMenu, &QMenu::aboutToShow, [this]() { mScreenrecorderToolsMenuFactory.reset(new KMoreToolsMenuFactory(QStringLiteral("spectacle/screenrecorder-tools"))); mScreenRecorderToolsMenu->clear(); mScreenrecorderToolsMenuFactory->fillMenuFromGroupingNames(mScreenRecorderToolsMenu, { QStringLiteral("screenrecorder") }); } ); // the save menu mSaveAsAction = KStandardAction::saveAs(this, &KSMainWindow::saveAs, this); mSaveAction = KStandardAction::save(this, &KSMainWindow::save, this); mSaveMenu->addAction(mSaveAsAction); mSaveMenu->addAction(mSaveAction); setDefaultSaveAction(); // message widget connect(mMessageWidget, &KMessageWidget::linkActivated, this, [](const QString &str) { QDesktopServices::openUrl(QUrl(str)); } ); // layouts mDivider->setFrameShape(QFrame::HLine); mDivider->setLineWidth(2); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(mKSWidget); layout->addWidget(mMessageWidget); layout->addWidget(mDivider); layout->addWidget(mDialogButtonBox); mMessageWidget->hide(); // populate our send-to actions mSendToButton->setMenu(mExportMenu); connect(mExportMenu, &ExportMenu::imageShared, this, &KSMainWindow::showImageSharedFeedback); // disable onClick mode if not available on the platform if (!mOnClickAvailable) { mKSWidget->disableOnClick(); } resize(QSize(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT).expandedTo(minimumSize())); // Allow Ctrl+Q to quit the app QAction *actionQuit = KStandardAction::quit(qApp, &QApplication::quit, this); actionQuit->setShortcut(QKeySequence::Quit); addAction(actionQuit); // done with the init } int KSMainWindow::windowWidth(const QPixmap &pixmap) const { // Calculates what the width of the window should be for the captured image to perfectly fit // the area reserved for the image, with the height already set. const float pixmapAspectRatio = (float)pixmap.width() / pixmap.height(); const int imageHeight = mKSWidget->height() - 2 * layout()->spacing(); const int imageWidth = pixmapAspectRatio * imageHeight; int alignedWindowWidth = qMin(mKSWidget->imagePaddingWidth() + imageWidth, MAXIMUM_WINDOW_WIDTH); alignedWindowWidth += layout()->contentsMargins().left() + layout()->contentsMargins().right(); alignedWindowWidth += 2; // margins is removing 1 - 1 pixel for some reason return alignedWindowWidth; } void KSMainWindow::setDefaultSaveAction() { switch (SpectacleConfig::instance()->lastUsedSaveMode()) { case SaveMode::SaveAs: mSaveButton->setDefaultAction(mSaveAsAction); break; case SaveMode::Save: mSaveButton->setDefaultAction(mSaveAction); break; } } // overrides void KSMainWindow::moveEvent(QMoveEvent *event) { Q_UNUSED(event); KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); guiConfig.writeEntry("window-position", pos()); guiConfig.sync(); } // slots void KSMainWindow::captureScreenshot(ImageGrabber::GrabMode mode, int timeout, bool includePointer, bool includeDecorations) { hide(); emit newScreenshotRequest(mode, timeout, includePointer, includeDecorations); } void KSMainWindow::setScreenshotAndShow(const QPixmap &pixmap) { mKSWidget->setScreenshotPixmap(pixmap); mExportMenu->imageUpdated(); setWindowTitle(i18nc("Unsaved Screenshot", "Unsaved[*]")); setWindowModified(true); show(); resize(QSize(windowWidth(pixmap), DEFAULT_WINDOW_HEIGHT)); } void KSMainWindow::showPrintDialog() { QPrinter *printer = new QPrinter(QPrinter::HighResolution); QPrintDialog printDialog(printer, this); if (printDialog.exec() == QDialog::Accepted) { ExportManager::instance()->doPrint(printer); return; } delete printer; } void KSMainWindow::openScreenshotsFolder() { // Highlight last screenshot in file manager if user saved at least once, // or open default directory as determined by save button QUrl location = ExportManager::instance()->lastSavePath(); if (!ExportManager::instance()->isFileExists(location)) { switch(SpectacleConfig::instance()->lastUsedSaveMode()) { case SaveMode::Save: location = QUrl::fromLocalFile(ExportManager::instance()->saveLocation() + QStringLiteral("/")); break; case SaveMode::SaveAs: location = SpectacleConfig::instance()->lastSaveAsLocation(); // already has a "/" at the end break; } if (!ExportManager::instance()->isFileExists(location)) { location = QUrl(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + QStringLiteral("/")); } } KIO::highlightInFileManager({location}); } void KSMainWindow::showImageSharedFeedback(bool error, const QString &message) { if (error) { mMessageWidget->setMessageType(KMessageWidget::Error); mMessageWidget->setText(i18n("There was a problem sharing the image: %1", message)); mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); } else { mMessageWidget->setMessageType(KMessageWidget::Positive); if (message.isEmpty()) mMessageWidget->setText(i18n("Image shared")); else mMessageWidget->setText(i18n("You can find the shared image at: %1", message)); mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); } mMessageWidget->animatedShow(); QTimer::singleShot(20000, mMessageWidget, &KMessageWidget::animatedHide); } void KSMainWindow::sendToClipboard() { ExportManager::instance()->doCopyToClipboard(); if (SpectacleConfig::instance()->quitAfterSaveOrCopyChecked()) { qApp->setQuitOnLastWindowClosed(false); hide(); QTimer::singleShot(250, qApp, &QApplication::quit); } mMessageWidget->setMessageType(KMessageWidget::Information); mMessageWidget->setText(i18n("The screenshot has been copied to the clipboard.")); mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); mMessageWidget->animatedShow(); QTimer::singleShot(10000, mMessageWidget, &KMessageWidget::animatedHide); } void KSMainWindow::showPreferencesDialog() { SettingsDialog prefDialog(this); prefDialog.exec(); } void KSMainWindow::setScreenshotWindowTitle(QUrl location) { setWindowTitle(location.fileName()); setWindowModified(false); } void KSMainWindow::save() { SpectacleConfig::instance()->setLastUsedSaveMode(SaveMode::Save); setDefaultSaveAction(); if (SpectacleConfig::instance()->quitAfterSaveOrCopyChecked()) { ExportManager::instance()->doSave(QUrl(), true); qApp->setQuitOnLastWindowClosed(false); hide(); } else { ExportManager::instance()->doSave(); } } void KSMainWindow::saveAs() { SpectacleConfig::instance()->setLastUsedSaveMode(SaveMode::SaveAs); setDefaultSaveAction(); if (SpectacleConfig::instance()->quitAfterSaveOrCopyChecked()) { if (ExportManager::instance()->doSaveAs(this, true)) { qApp->setQuitOnLastWindowClosed(false); hide(); } } else { ExportManager::instance()->doSaveAs(this, false); } } diff --git a/src/Gui/KSWidget.cpp b/src/Gui/KSWidget.cpp index 81382c8..84128ac 100644 --- a/src/Gui/KSWidget.cpp +++ b/src/Gui/KSWidget.cpp @@ -1,237 +1,235 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KSWidget.h" -#include -#include -#include -#include -#include +#include "KSImageWidget.h" +#include "SmartSpinBox.h" +#include "SpectacleConfig.h" + +#include + #include +#include +#include +#include #include #include #include #include -#include - -#include "KSImageWidget.h" -#include "SmartSpinBox.h" - -#include "SpectacleConfig.h" KSWidget::KSWidget(QWidget *parent) : QWidget(parent) { // get a handle to the configuration manager SpectacleConfig *configManager = SpectacleConfig::instance(); // we'll init the widget that holds the image first mImageWidget = new KSImageWidget(this); mImageWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(mImageWidget, &KSImageWidget::dragInitiated, this, &KSWidget::dragInitiated); // the capture mode options first mCaptureModeLabel = new QLabel(i18n("Capture Mode"), this); mCaptureArea = new QComboBox(this); mCaptureArea->insertItem(1, i18n("Full Screen (All Monitors)"), ImageGrabber::FullScreen); mCaptureArea->insertItem(2, i18n("Current Screen"), ImageGrabber::CurrentScreen); mCaptureArea->insertItem(3, i18n("Active Window"), ImageGrabber::ActiveWindow); mCaptureArea->insertItem(4, i18n("Window Under Cursor"), ImageGrabber::WindowUnderCursor); mCaptureArea->insertItem(5, i18n("Rectangular Region"), ImageGrabber::RectangularRegion); mCaptureArea->setMinimumWidth(240); connect(mCaptureArea, static_cast(&QComboBox::currentIndexChanged), this, &KSWidget::captureModeChanged); mDelayMsec = new SmartSpinBox(this); mDelayMsec->setDecimals(1); mDelayMsec->setSingleStep(1.0); mDelayMsec->setMinimum(0.0); mDelayMsec->setMaximum(999.9); mDelayMsec->setSpecialValueText(i18n("No Delay")); mDelayMsec->setMinimumWidth(160); connect(mDelayMsec, static_cast(&SmartSpinBox::valueChanged), configManager, &SpectacleConfig::setCaptureDelay); mCaptureOnClick = new QCheckBox(i18n("On Click"), this); mCaptureOnClick->setToolTip(i18n("Wait for a mouse click before capturing the screenshot image")); connect(mCaptureOnClick, &QCheckBox::stateChanged, this, &KSWidget::onClickStateChanged); connect(mCaptureOnClick, &QCheckBox::clicked, configManager, &SpectacleConfig::setOnClickChecked); mDelayLayout = new QHBoxLayout; mDelayLayout->addWidget(mDelayMsec); mDelayLayout->addWidget(mCaptureOnClick); mCaptureModeForm = new QFormLayout; mCaptureModeForm->addRow(i18n("Area:"), mCaptureArea); mCaptureModeForm->addRow(i18n("Delay:"), mDelayLayout); mCaptureModeForm->setContentsMargins(24, 0, 0, 0); // options (mouse pointer, window decorations, quit after saving or copying) mContentOptionsLabel = new QLabel(this); mContentOptionsLabel->setText(i18n("Options")); mMousePointer = new QCheckBox(i18n("Include mouse pointer"), this); mMousePointer->setToolTip(i18n("Show the mouse cursor in the screenshot image")); connect(mMousePointer, &QCheckBox::clicked, configManager, &SpectacleConfig::setIncludePointerChecked); mWindowDecorations = new QCheckBox(i18n("Include window titlebar and borders"), this); mWindowDecorations->setToolTip(i18n("Show the window title bar, the minimize/maximize/close buttons, and the window border")); mWindowDecorations->setEnabled(false); connect(mWindowDecorations, &QCheckBox::clicked, configManager, &SpectacleConfig::setIncludeDecorationsChecked); mCaptureTransientOnly = new QCheckBox(i18n("Capture the current pop-up only"), this); mCaptureTransientOnly->setToolTip(i18n("Capture only the current pop-up window (like a menu, tooltip etc).\n" "If disabled, the pop-up is captured along with the parent window")); mCaptureTransientOnly->setEnabled(false); connect(mCaptureTransientOnly, &QCheckBox::clicked, configManager, &SpectacleConfig::setCaptureTransientWindowOnlyChecked); mQuitAfterSaveOrCopy = new QCheckBox(i18n("Quit after Save or Copy"), this); mQuitAfterSaveOrCopy->setToolTip(i18n("Quit Spectacle after saving or copying the image")); connect(mQuitAfterSaveOrCopy, &QCheckBox::clicked, configManager, &SpectacleConfig::setQuitAfterSaveOrCopyChecked); mContentOptionsForm = new QVBoxLayout; mContentOptionsForm->addWidget(mMousePointer); mContentOptionsForm->addWidget(mWindowDecorations); mContentOptionsForm->addWidget(mCaptureTransientOnly); mContentOptionsForm->addWidget(mQuitAfterSaveOrCopy); mContentOptionsForm->setContentsMargins(24, 0, 0, 0); // the take a new screenshot button mTakeScreenshotButton = new QPushButton(i18n("Take a New Screenshot"), this); mTakeScreenshotButton->setIcon(QIcon::fromTheme(QStringLiteral("spectacle"))); mTakeScreenshotButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mTakeScreenshotButton->setFocus(); connect(mTakeScreenshotButton, &QPushButton::clicked, this, &KSWidget::newScreenshotClicked); QShortcut *shortcut = new QShortcut(QKeySequence(QKeySequence::New), mTakeScreenshotButton); auto clickFunc = [&]() { mTakeScreenshotButton->animateClick(100); QTimer::singleShot(100, mTakeScreenshotButton, &QPushButton::click); }; connect(shortcut, &QShortcut::activated, clickFunc); // finally, finish up the layouts mRightLayout = new QVBoxLayout; mRightLayout->addStretch(1); mRightLayout->addWidget(mCaptureModeLabel); mRightLayout->addLayout(mCaptureModeForm); mRightLayout->addStretch(1); mRightLayout->addWidget(mContentOptionsLabel); mRightLayout->addLayout(mContentOptionsForm); mRightLayout->addStretch(10); mRightLayout->addWidget(mTakeScreenshotButton, 1, Qt::AlignHCenter); mRightLayout->setContentsMargins(10, 0, 0, 10); mMainLayout = new QGridLayout(this); mMainLayout->addWidget(mImageWidget, 0, 0, 1, 1); mMainLayout->addLayout(mRightLayout, 0, 1, 1, 1); mMainLayout->setColumnMinimumWidth(0, 320); mMainLayout->setColumnMinimumWidth(1, 320); // and read in the saved checkbox states and capture mode indices mMousePointer->setChecked (configManager->includePointerChecked()); mWindowDecorations->setChecked (configManager->includeDecorationsChecked()); mCaptureOnClick->setChecked (configManager->onClickChecked()); mCaptureTransientOnly->setChecked (configManager->captureTransientWindowOnlyChecked()); mQuitAfterSaveOrCopy->setChecked (configManager->quitAfterSaveOrCopyChecked()); mCaptureArea->setCurrentIndex (configManager->captureMode()); mDelayMsec->setValue (configManager->captureDelay()); // done } int KSWidget::imagePaddingWidth() const { int rightLayoutLeft = 0; int rightLayoutRight = 0; int mainLayoutRight = 0; mRightLayout->getContentsMargins(&rightLayoutLeft, nullptr, &rightLayoutRight, nullptr); mMainLayout->getContentsMargins(nullptr, nullptr, &mainLayoutRight, nullptr); int paddingWidth = (rightLayoutLeft + rightLayoutRight + mainLayoutRight); paddingWidth += mRightLayout->contentsRect().width(); paddingWidth += 2 * SpectacleImage::SHADOW_RADIUS; // image drop shadow return paddingWidth; } // public slots void KSWidget::setScreenshotPixmap(const QPixmap &pixmap) { mImageWidget->setScreenshot(pixmap); } void KSWidget::disableOnClick() { mCaptureOnClick->setEnabled(false); mDelayMsec->setEnabled(true); } // private slots void KSWidget::newScreenshotClicked() { int delay = mCaptureOnClick->isChecked() ? -1 : (mDelayMsec->value() * 1000); ImageGrabber::GrabMode mode = static_cast(mCaptureArea->currentData().toInt()); if (mode == ImageGrabber::WindowUnderCursor && !(mCaptureTransientOnly->isChecked())) { mode = ImageGrabber::TransientWithParent; } emit newScreenshotRequest(mode, delay, mMousePointer->isChecked(), mWindowDecorations->isChecked()); } void KSWidget::onClickStateChanged(int state) { if (state == Qt::Checked) { mDelayMsec->setEnabled(false); } else if (state == Qt::Unchecked) { mDelayMsec->setEnabled(true); } } void KSWidget::captureModeChanged(int index) { SpectacleConfig::instance()->setCaptureMode(index); ImageGrabber::GrabMode mode = static_cast(mCaptureArea->itemData(index).toInt()); switch (mode) { case ImageGrabber::WindowUnderCursor: mWindowDecorations->setEnabled(true); mCaptureTransientOnly->setEnabled(true); break; case ImageGrabber::ActiveWindow: mWindowDecorations->setEnabled(true); mCaptureTransientOnly->setEnabled(false); break; default: mWindowDecorations->setEnabled(false); mCaptureTransientOnly->setEnabled(false); } } diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp index 2e8ea0b..ee22c53 100644 --- a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp +++ b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp @@ -1,104 +1,103 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "GeneralOptionsPage.h" -#include -#include -#include -#include +#include "SpectacleConfig.h" #include -#include "SpectacleConfig.h" +#include +#include +#include GeneralOptionsPage::GeneralOptionsPage(QWidget *parent) : SettingsPage(parent) { // preamble and stuff QVBoxLayout *mainLayout = new QVBoxLayout(this); // copy save path to clipboard mCopyPathToClipboard = new QCheckBox(i18n("Copy save location to the clipboard"), this); connect(mCopyPathToClipboard, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); mainLayout->addWidget(mCopyPathToClipboard, 1); // Rectangular Region settings QGroupBox *rrGroup = new QGroupBox(i18n("Rectangular Region")); QVBoxLayout *rrLayout = new QVBoxLayout; rrGroup->setLayout(rrLayout); // use light background mUseLightBackground = new QCheckBox(i18n("Use light background"), this); connect(mUseLightBackground, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); mainLayout->addWidget(mUseLightBackground, 1); // remember Rectangular Region box mRememberRect = new QCheckBox(i18n("Remember selected area"), this); connect(mRememberRect, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); QVBoxLayout *rrCLayout = new QVBoxLayout; // rrCLayout->setContentsMargins(15, 10, 0, 10); rrCLayout->addWidget(mUseLightBackground); rrCLayout->addWidget(mRememberRect); rrLayout->addLayout(rrCLayout); mainLayout->addWidget(rrGroup, 1); // read in the data resetChanges(); // finish up with the main layout mainLayout->addStretch(4); setLayout(mainLayout); } void GeneralOptionsPage::markDirty(bool checked) { Q_UNUSED(checked); mChangesMade = true; } void GeneralOptionsPage::saveChanges() { SpectacleConfig *cfgManager = SpectacleConfig::instance(); cfgManager->setUseLightRegionMaskColour(mUseLightBackground->checkState() == Qt::Checked); cfgManager->setRememberLastRectangularRegion(mRememberRect->checkState() == Qt::Checked); cfgManager->setCopySaveLocationToClipboard(mCopyPathToClipboard->checkState() == Qt::Checked); mChangesMade = false; } void GeneralOptionsPage::resetChanges() { SpectacleConfig *cfgManager = SpectacleConfig::instance(); mUseLightBackground->setChecked(cfgManager->useLightRegionMaskColour()); mRememberRect->setChecked(cfgManager->rememberLastRectangularRegion()); mCopyPathToClipboard->setChecked(cfgManager->copySaveLocationToClipboard()); mChangesMade = false; } diff --git a/src/Gui/SettingsDialog/SaveOptionsPage.cpp b/src/Gui/SettingsDialog/SaveOptionsPage.cpp index 23cdb19..e9354a9 100644 --- a/src/Gui/SettingsDialog/SaveOptionsPage.cpp +++ b/src/Gui/SettingsDialog/SaveOptionsPage.cpp @@ -1,178 +1,176 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SaveOptionsPage.h" +#include "SpectacleConfig.h" + +#include +#include + #include #include #include #include -#include -#include #include #include -#include -#include - -#include "SpectacleConfig.h" - SaveOptionsPage::SaveOptionsPage(QWidget *parent) : SettingsPage(parent) { // set up the layout. start with the directory QGroupBox *dirGroup = new QGroupBox(i18n("Default Save Location"), this); QVBoxLayout *dirLayout = new QVBoxLayout; dirGroup->setLayout(dirLayout); QHBoxLayout *urlRequesterLayout = new QHBoxLayout; urlRequesterLayout->addWidget(new QLabel(i18n("Location:"), this)); mUrlRequester = new KUrlRequester; mUrlRequester->setMode(KFile::Directory); connect(mUrlRequester, &KUrlRequester::textChanged, this, &SaveOptionsPage::markDirty); urlRequesterLayout->addWidget(mUrlRequester); dirLayout->addLayout(urlRequesterLayout); // filename chooser text field QGroupBox *fmtGroup = new QGroupBox(i18n("Default Save Filename")); QVBoxLayout *fmtLayout = new QVBoxLayout; fmtGroup->setLayout(fmtLayout); QHBoxLayout *saveNameLayout = new QHBoxLayout; saveNameLayout->addWidget(new QLabel(i18n("Filename:"), this)); mSaveNameFormat = new QLineEdit; connect(mSaveNameFormat, &QLineEdit::textEdited, this, &SaveOptionsPage::markDirty); connect(mSaveNameFormat, &QLineEdit::textEdited, [&](const QString &newText) { QString fmt; Q_FOREACH(auto item, QImageWriter::supportedImageFormats()) { fmt = QString::fromLocal8Bit(item); if (newText.endsWith(QLatin1Char('.') + fmt, Qt::CaseInsensitive)) { QString txtCopy = newText; txtCopy.chop(fmt.length() + 1); mSaveNameFormat->setText(txtCopy); mSaveImageFormat->setCurrentIndex(mSaveImageFormat->findText(fmt.toUpper())); } } }); saveNameLayout->addWidget(mSaveNameFormat); mSaveImageFormat = new QComboBox; mSaveImageFormat->addItems([&](){ QStringList items; Q_FOREACH(auto fmt, QImageWriter::supportedImageFormats()) { items.append(QString::fromLocal8Bit(fmt).toUpper()); } return items; }()); connect(mSaveImageFormat, &QComboBox::currentTextChanged, this, &SaveOptionsPage::markDirty); saveNameLayout->addWidget(mSaveImageFormat); fmtLayout->addLayout(saveNameLayout); // now the save filename format layout const QString helpText = i18n( "

You can use the following placeholders in the filename, which will be replaced " "with actual text when the file is saved:

" "
" "%Y: Year (4 digit)
" "%y: Year (2 digit)
" "%M: Month
" "%D: Day
" "%H: Hour
" "%m: Minute
" "%S: Second
" "%T: Window title" "
" ); QLabel *fmtHelpText = new QLabel(helpText, this); fmtHelpText->setWordWrap(true); fmtHelpText->setTextFormat(Qt::RichText); fmtHelpText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); fmtLayout->addWidget(fmtHelpText); // read in the data resetChanges(); // finish up with the main layout QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(dirGroup); mainLayout->addWidget(fmtGroup); mainLayout->addStretch(4); setLayout(mainLayout); } void SaveOptionsPage::markDirty(const QString &text) { Q_UNUSED(text); mChangesMade = true; } void SaveOptionsPage::saveChanges() { // bring up the configuration reader SpectacleConfig *cfgManager = SpectacleConfig::instance(); // save the data cfgManager->setAutoSaveLocation(mUrlRequester->url().toDisplayString(QUrl::PreferLocalFile)); cfgManager->setAutoSaveFilenameFormat(mSaveNameFormat->text()); cfgManager->setSaveImageFormat(mSaveImageFormat->currentText().toLower()); // done mChangesMade = false; } void SaveOptionsPage::resetChanges() { // bring up the configuration reader SpectacleConfig *cfgManager = SpectacleConfig::instance(); // read in the data mSaveNameFormat->setText(cfgManager->autoSaveFilenameFormat()); mUrlRequester->setUrl(QUrl::fromUserInput(cfgManager->autoSaveLocation())); // read in the save image format and calculate its index { int index = mSaveImageFormat->findText(cfgManager->saveImageFormat().toUpper()); if (index >= 0) { mSaveImageFormat->setCurrentIndex(index); } } // done mChangesMade = false; } diff --git a/src/Gui/SettingsDialog/SettingsDialog.cpp b/src/Gui/SettingsDialog/SettingsDialog.cpp index 104bc72..3d5069f 100644 --- a/src/Gui/SettingsDialog/SettingsDialog.cpp +++ b/src/Gui/SettingsDialog/SettingsDialog.cpp @@ -1,88 +1,86 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SettingsDialog.h" -#include -#include -#include +#include "GeneralOptionsPage.h" +#include "SaveOptionsPage.h" #include -#include -#include "SaveOptionsPage.h" -#include "GeneralOptionsPage.h" +#include +#include SettingsDialog::SettingsDialog(QWidget *parent) : KPageDialog(parent) { // set up window options and geometry setWindowTitle(i18n("Configure")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); resize(500, 470); // init all pages QMetaObject::invokeMethod(this, "initPages", Qt::QueuedConnection); } void SettingsDialog::initPages() { KPageWidgetItem *generalOptions = new KPageWidgetItem(new GeneralOptionsPage(this), i18n("General")); generalOptions->setHeader(i18n("General")); generalOptions->setIcon(QIcon::fromTheme(QStringLiteral("view-preview"))); // This is what Dolphin uses for the icon on its General page... addPage(generalOptions); mPages.insert(generalOptions); KPageWidgetItem *saveOptions = new KPageWidgetItem(new SaveOptionsPage(this), i18n("Save")); saveOptions->setHeader(i18n("Save")); saveOptions->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); addPage(saveOptions); mPages.insert(saveOptions); connect(this, &SettingsDialog::currentPageChanged, this, &SettingsDialog::onPageChanged); } void SettingsDialog::accept() { Q_FOREACH(auto page, mPages) { SettingsPage *pageWidget = dynamic_cast(page->widget()); if (pageWidget) { pageWidget->saveChanges(); } } done(QDialog::Accepted); } void SettingsDialog::onPageChanged(KPageWidgetItem *current, KPageWidgetItem *before) { Q_UNUSED(current); SettingsPage *pageWidget = dynamic_cast(before->widget()); if (pageWidget && (pageWidget->changesMade())) { QMessageBox::StandardButton response = QMessageBox::question(this, i18n("Apply Unsaved Changes"), i18n("You have made changes to the settings in this tab. Do you want to apply those changes?")); if (response == QMessageBox::Yes) { pageWidget->saveChanges(); } else { pageWidget->resetChanges(); } } } diff --git a/src/Main.cpp b/src/Main.cpp index beedcac..376612f 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -1,161 +1,155 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -#include -#include -#include -#include -#include -#include -#include +#include "Config.h" +#include "SpectacleCore.h" +#include "SpectacleDBusAdapter.h" #include -#include #include +#include -#include "SpectacleCore.h" -#include "SpectacleDBusAdapter.h" -#include "ExportManager.h" -#include "Config.h" +#include +#include int main(int argc, char **argv) { // set up the application QApplication app(argc, argv); app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); KLocalizedString::setApplicationDomain("spectacle"); KAboutData aboutData(QStringLiteral("spectacle"), i18n("Spectacle"), QStringLiteral(SPECTACLE_VERSION) + QStringLiteral(" - ") + QStringLiteral(SPECTACLE_CODENAME), i18n("KDE Screenshot Utility"), KAboutLicense::GPL_V2, i18n("(C) 2015 Boudhayan Gupta")); aboutData.addAuthor(QStringLiteral("Boudhayan Gupta"), QString(), QStringLiteral("bgupta@kde.org")); aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails")); KAboutData::setApplicationData(aboutData); app.setWindowIcon(QIcon::fromTheme(QStringLiteral("spectacle"))); // set up the command line options parser QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); aboutData.setupCommandLine(&parser); parser.addOptions({ {{QStringLiteral("f"), QStringLiteral("fullscreen")}, i18n("Capture the entire desktop (default)")}, {{QStringLiteral("m"), QStringLiteral("current")}, i18n("Capture the current monitor")}, {{QStringLiteral("a"), QStringLiteral("activewindow")}, i18n("Capture the active window")}, {{QStringLiteral("u"), QStringLiteral("windowundercursor")}, i18n("Capture the window currently under the cursor, including parents of pop-up menus")}, {{QStringLiteral("t"), QStringLiteral("transientonly")}, i18n("Capture the window currently under the cursor, excluding parents of pop-up menus")}, {{QStringLiteral("r"), QStringLiteral("region")}, i18n("Capture a rectangular region of the screen")}, {{QStringLiteral("g"), QStringLiteral("gui")}, i18n("Start in GUI mode (default)")}, {{QStringLiteral("b"), QStringLiteral("background")}, i18n("Take a screenshot and exit without showing the GUI")}, {{QStringLiteral("s"), QStringLiteral("dbus")}, i18n("Start in DBus-Activation mode")}, {{QStringLiteral("n"), QStringLiteral("nonotify")}, i18n("In background mode, do not pop up a notification when the screenshot is taken")}, {{QStringLiteral("o"), QStringLiteral("output")}, i18n("In background mode, save image to specified file"), QStringLiteral("fileName")}, {{QStringLiteral("d"), QStringLiteral("delay")}, i18n("In background mode, delay before taking the shot (in milliseconds)"), QStringLiteral("delayMsec")}, {{QStringLiteral("w"), QStringLiteral("onclick")}, i18n("Wait for a click before taking screenshot. Invalidates delay")} }); parser.process(app); aboutData.processCommandLine(&parser); // extract the capture mode ImageGrabber::GrabMode grabMode = ImageGrabber::FullScreen; if (parser.isSet(QStringLiteral("current"))) { grabMode = ImageGrabber::CurrentScreen; } else if (parser.isSet(QStringLiteral("activewindow"))) { grabMode = ImageGrabber::ActiveWindow; } else if (parser.isSet(QStringLiteral("region"))) { grabMode = ImageGrabber::RectangularRegion; } else if (parser.isSet(QStringLiteral("windowundercursor"))) { grabMode = ImageGrabber::TransientWithParent; } else if (parser.isSet(QStringLiteral("transientonly"))) { grabMode = ImageGrabber::WindowUnderCursor; } // are we running in background or dbus mode? SpectacleCore::StartMode startMode = SpectacleCore::GuiMode; bool notify = true; qint64 delayMsec = 0; QString fileName = QString(); if (parser.isSet(QStringLiteral("background"))) { startMode = SpectacleCore::BackgroundMode; } else if (parser.isSet(QStringLiteral("dbus"))) { startMode = SpectacleCore::DBusMode; } switch (startMode) { case SpectacleCore::BackgroundMode: if (parser.isSet(QStringLiteral("nonotify"))) { notify = false; } if (parser.isSet(QStringLiteral("output"))) { fileName = parser.value(QStringLiteral("output")); } if (parser.isSet(QStringLiteral("delay"))) { bool ok = false; qint64 delayValue = parser.value(QStringLiteral("delay")).toLongLong(&ok); if (ok) { delayMsec = delayValue; } } if (parser.isSet(QStringLiteral("onclick"))) { delayMsec = -1; } case SpectacleCore::DBusMode: app.setQuitOnLastWindowClosed(false); case SpectacleCore::GuiMode: break; } // release the kraken SpectacleCore core(startMode, grabMode, fileName, delayMsec, notify); QObject::connect(&core, &SpectacleCore::allDone, qApp, &QApplication::quit); // create the dbus connections new KDBusService(KDBusService::Multiple, &core); SpectacleDBusAdapter *dbusAdapter = new SpectacleDBusAdapter(&core); QObject::connect(&core, &SpectacleCore::grabFailed, dbusAdapter, &SpectacleDBusAdapter::ScreenshotFailed); QObject::connect(ExportManager::instance(), &ExportManager::imageSaved, [&](const QUrl savedAt) { emit dbusAdapter->ScreenshotTaken(savedAt.toLocalFile()); }); QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), &core); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Spectacle")); // fire it up return app.exec(); } diff --git a/src/PlatformBackends/KWinWaylandImageGrabber.cpp b/src/PlatformBackends/KWinWaylandImageGrabber.cpp index 2dec87d..afac371 100644 --- a/src/PlatformBackends/KWinWaylandImageGrabber.cpp +++ b/src/PlatformBackends/KWinWaylandImageGrabber.cpp @@ -1,169 +1,168 @@ /* * Copyright (C) 2016 Martin Graesslin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KWinWaylandImageGrabber.h" #include #include #include #include - -#include #include +#include #include -#include +#include static int readData(int fd, QByteArray &data) { // implementation based on QtWayland file qwaylanddataoffer.cpp char buf[4096]; int retryCount = 0; int n; while (true) { n = QT_READ(fd, buf, sizeof buf); // give user 30 sec to click a window, afterwards considered as error if (n == -1 && (errno == EAGAIN) && ++retryCount < 30000) { usleep(1000); } else { break; } } if (n > 0) { data.append(buf, n); n = readData(fd, data); } return n; } static QImage readImage(int pipeFd) { QByteArray content; if (readData(pipeFd, content) != 0) { close(pipeFd); return QImage(); } close(pipeFd); QDataStream ds(content); QImage image; ds >> image; return image; }; KWinWaylandImageGrabber::KWinWaylandImageGrabber(QObject *parent) : ImageGrabber(parent) { } KWinWaylandImageGrabber::~KWinWaylandImageGrabber() = default; bool KWinWaylandImageGrabber::onClickGrabSupported() const { return true; } void KWinWaylandImageGrabber::grabFullScreen() { grab(Mode::FullScreen, mCapturePointer); } void KWinWaylandImageGrabber::grabCurrentScreen() { grab(Mode::CurrentScreen, mCapturePointer); } void KWinWaylandImageGrabber::grabActiveWindow() { // unsupported emit pixmapChanged(QPixmap()); } void KWinWaylandImageGrabber::grabRectangularRegion() { // unsupported emit pixmapChanged(QPixmap()); } void KWinWaylandImageGrabber::grabWindowUnderCursor() { int mask = 0; if (mCaptureDecorations) { mask = 1; } if (mCapturePointer) { mask |= 1 << 1; } grab(Mode::Window, mask); } void KWinWaylandImageGrabber::grabTransientWithParent() { // unsupported, perform grab window under cursor grabWindowUnderCursor(); } QPixmap KWinWaylandImageGrabber::blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) { Q_UNUSED(x) Q_UNUSED(y) Q_UNUSED(width) Q_UNUSED(height) return pixmap; } void KWinWaylandImageGrabber::startReadImage(int readPipe) { QFutureWatcher *watcher = new QFutureWatcher(this); QObject::connect(watcher, &QFutureWatcher::finished, this, [watcher, this] { watcher->deleteLater(); const QImage img = watcher->result(); emit pixmapChanged(QPixmap::fromImage(img)); } ); watcher->setFuture(QtConcurrent::run(readImage, readPipe)); } template void KWinWaylandImageGrabber::callDBus(Mode mode, int writeFd, T argument) { QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); static const QMap s_hash = { {Mode::Window, QStringLiteral("interactive")}, {Mode::CurrentScreen, QStringLiteral("screenshotScreen")}, {Mode::FullScreen, QStringLiteral("screenshotFullscreen")} }; auto it = s_hash.find(mode); Q_ASSERT(it != s_hash.end()); interface.asyncCall(it.value(), QVariant::fromValue(QDBusUnixFileDescriptor(writeFd)), argument); } template void KWinWaylandImageGrabber::grab(Mode mode, T argument) { int pipeFds[2]; if (pipe2(pipeFds, O_CLOEXEC|O_NONBLOCK) != 0) { emit imageGrabFailed(); return; } callDBus(mode, pipeFds[1], argument); startReadImage(pipeFds[0]); close(pipeFds[1]); } diff --git a/src/PlatformBackends/X11ImageGrabber.cpp b/src/PlatformBackends/X11ImageGrabber.cpp index 6e12a42..6fc7305 100644 --- a/src/PlatformBackends/X11ImageGrabber.cpp +++ b/src/PlatformBackends/X11ImageGrabber.cpp @@ -1,751 +1,752 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * Contains code from kxutils.cpp, part of KWindowSystem. Copyright * notices reproduced below: * * Copyright (C) 2008 Lubos Lunak (l.lunak@kde.org) * Copyright (C) 2013 Martin Gräßlin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "X11ImageGrabber.h" -#include -#include +#include + #include -#include #include -#include -#include -#include -#include +#include #include +#include +#include +#include #include +#include +#include #include #include #include #include #include #include #include #include X11ImageGrabber::X11ImageGrabber(QObject *parent) : ImageGrabber(parent) { mNativeEventFilter = new OnClickEventFilter(this); } X11ImageGrabber::~X11ImageGrabber() { delete mNativeEventFilter; } // for onClick grab OnClickEventFilter::OnClickEventFilter(X11ImageGrabber *grabber) : QAbstractNativeEventFilter(), mImageGrabber(grabber) {} bool OnClickEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { Q_UNUSED(result); if (eventType == "xcb_generic_event_t") { xcb_generic_event_t *ev = static_cast(message); switch (ev->response_type & ~0x80) { case XCB_BUTTON_RELEASE: // uninstall the eventfilter and release the mouse qApp->removeNativeEventFilter(this); xcb_ungrab_pointer(QX11Info::connection(), XCB_TIME_CURRENT_TIME); // decide whether to grab or abort. regrab the mouse // on mouse-wheel events { xcb_button_release_event_t *ev2 = static_cast(message); if (ev2->detail == 1) { QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); } else if (ev2->detail < 4) { emit mImageGrabber->imageGrabFailed(); } else { QMetaObject::invokeMethod(mImageGrabber, "doOnClickGrab", Qt::QueuedConnection); } } return true; default: return false; } } return false; } bool X11ImageGrabber::onClickGrabSupported() const { return true; } void X11ImageGrabber::doOnClickGrab() { // get the cursor image xcb_cursor_t xcbCursor = XCB_CURSOR_NONE; xcb_cursor_context_t *xcbCursorCtx; xcb_screen_t *xcbAppScreen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); if (xcb_cursor_context_new(QX11Info::connection(), xcbAppScreen, &xcbCursorCtx) >= 0) { QVector cursorNames = { QByteArrayLiteral("cross"), QByteArrayLiteral("crosshair"), QByteArrayLiteral("diamond-cross"), QByteArrayLiteral("cross-reverse") }; Q_FOREACH (const QByteArray &cursorName, cursorNames) { xcb_cursor_t cursor = xcb_cursor_load_cursor(xcbCursorCtx, cursorName.constData()); if (cursor != XCB_CURSOR_NONE) { xcbCursor = cursor; break; } } } // grab the cursor xcb_grab_pointer_cookie_t grabPointerCookie = xcb_grab_pointer_unchecked( QX11Info::connection(), // xcb connection 0, // deliver events to owner? nope QX11Info::appRootWindow(), // window to grab pointer for (root) XCB_EVENT_MASK_BUTTON_RELEASE, // which events do I want XCB_GRAB_MODE_SYNC, // pointer grab mode XCB_GRAB_MODE_ASYNC, // keyboard grab mode (why is this even here) XCB_NONE, // confine pointer to which window (none) xcbCursor, // cursor to change to for the duration of grab XCB_TIME_CURRENT_TIME // do this right now ); CScopedPointer grabPointerReply(xcb_grab_pointer_reply(QX11Info::connection(), grabPointerCookie, NULL)); // if the grab failed, take the screenshot right away if (grabPointerReply->status != XCB_GRAB_STATUS_SUCCESS) { return doImageGrab(); } // fix things if our pointer grab causes a lockup xcb_allow_events(QX11Info::connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); // and install our event filter qApp->installNativeEventFilter(mNativeEventFilter); // done. clean stuff up xcb_cursor_context_free(xcbCursorCtx); xcb_free_cursor(QX11Info::connection(), xcbCursor); } // image conversion routine QPixmap X11ImageGrabber::convertFromNative(xcb_image_t *xcbImage) { QImage::Format format = QImage::Format_Invalid; switch (xcbImage->depth) { case 1: format = QImage::Format_MonoLSB; break; case 16: format = QImage::Format_RGB16; break; case 24: format = QImage::Format_RGB32; break; case 30: format = QImage::Format_BGR30; break; case 32: format = QImage::Format_ARGB32_Premultiplied; break; default: return QPixmap(); // we don't know } // The RGB32 format requires data format 0xffRRGGBB, ensure that this fourth byte really is 0xff if (format == QImage::Format_RGB32) { quint32 *data = reinterpret_cast(xcbImage->data); for (int i = 0; i < xcbImage->width * xcbImage->height; i++) { data[i] |= 0xff000000; } } QImage image(xcbImage->data, xcbImage->width, xcbImage->height, format); if (image.isNull()) { return QPixmap(); } // work around an abort in QImage::color if (image.format() == QImage::Format_MonoLSB) { image.setColorCount(2); image.setColor(0, QColor(Qt::white).rgb()); image.setColor(1, QColor(Qt::black).rgb()); } // Image is ready. Since the backing data from xcbImage could be freed // before the QPixmap goes away, a deep copy is necessary. return QPixmap::fromImage(image).copy(); } // utility functions // Note: x, y, width and height are measured in device pixels QPixmap X11ImageGrabber::blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) { // If the cursor position lies outside the area, do not bother drawing a cursor. QPoint cursorPos = getNativeCursorPosition(); QRect screenRect(x, y, width, height); if (!screenRect.contains(cursorPos)) { return pixmap; } // now we can get the image and start processing xcb_connection_t *xcbConn = QX11Info::connection(); xcb_xfixes_get_cursor_image_cookie_t cursorCookie = xcb_xfixes_get_cursor_image_unchecked(xcbConn); CScopedPointer cursorReply(xcb_xfixes_get_cursor_image_reply(xcbConn, cursorCookie, NULL)); if (cursorReply.isNull()) { return pixmap; } quint32 *pixelData = xcb_xfixes_get_cursor_image_cursor_image(cursorReply.data()); if (!pixelData) { return pixmap; } // process the image into a QImage QImage cursorImage = QImage((quint8 *)pixelData, cursorReply->width, cursorReply->height, QImage::Format_ARGB32_Premultiplied); // a small fix for the cursor position for fancier cursors cursorPos -= QPoint(cursorReply->xhot, cursorReply->yhot); // now we translate the cursor point to our screen rectangle cursorPos -= QPoint(x, y); // and do the painting QPixmap blendedPixmap = pixmap; QPainter painter(&blendedPixmap); painter.drawImage(cursorPos, cursorImage); // and done return blendedPixmap; } QPixmap X11ImageGrabber::getPixmapFromDrawable(xcb_drawable_t drawableId, const QRect &rect) { xcb_connection_t *xcbConn = QX11Info::connection(); // proceed to get an image based on the geometry (in device pixels) QScopedPointer xcbImage( xcb_image_get( xcbConn, drawableId, rect.x(), rect.y(), rect.width(), rect.height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP ) ); // too bad, the capture failed. if (xcbImage.isNull()) { return QPixmap(); } // now process the image QPixmap nativePixmap = convertFromNative(xcbImage.data()); return nativePixmap; } // finalize the grabbed pixmap where we know the absolute position QPixmap X11ImageGrabber::postProcessPixmap(QPixmap pixmap, QRect rect, bool blendPointer) { if (!(blendPointer)) { // note: this may be the null pixmap if an error occurred. return pixmap; } return blendCursorImage(pixmap, rect.x(), rect.y(), rect.width(), rect.height()); } QPixmap X11ImageGrabber::getToplevelPixmap(QRect rect, bool blendPointer) { xcb_window_t rootWindow = QX11Info::appRootWindow(); // Treat a null rect as an alias for capturing fullscreen if (!rect.isValid()) { rect = getDrawableGeometry(rootWindow); } else { QRegion screenRegion; for (auto screen : QGuiApplication::screens()) { QRect screenRect = screen->geometry(); // Do not use setSize() here, because QSize::operator*=() // performs qRound() which can result in xcb_image_get() failing const qreal dpr = screen->devicePixelRatio(); screenRect.setHeight(qFloor(screenRect.height() * dpr)); screenRect.setWidth(qFloor(screenRect.width() * dpr)); screenRegion += screenRect; } rect = (screenRegion & rect).boundingRect(); } QPixmap nativePixmap = getPixmapFromDrawable(rootWindow, rect); return postProcessPixmap(nativePixmap, rect, blendPointer); } QPixmap X11ImageGrabber::getWindowPixmap(xcb_window_t window, bool blendPointer) { xcb_connection_t *xcbConn = QX11Info::connection(); // first get geometry information for our window xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry_unchecked(xcbConn, window); CScopedPointer geomReply(xcb_get_geometry_reply(xcbConn, geomCookie, NULL)); QRect rect(geomReply->x, geomReply->y, geomReply->width, geomReply->height); // then proceed to get an image QPixmap nativePixmap = getPixmapFromDrawable(window, rect); // Translate window coordinates to global ones. xcb_get_geometry_cookie_t geomRootCookie = xcb_get_geometry_unchecked(xcbConn, geomReply->root); CScopedPointer geomRootReply(xcb_get_geometry_reply(xcbConn, geomRootCookie, NULL)); xcb_translate_coordinates_cookie_t translateCookie = xcb_translate_coordinates_unchecked( xcbConn, window, geomReply->root, geomRootReply->x, geomRootReply->y); CScopedPointer translateReply( xcb_translate_coordinates_reply(xcbConn, translateCookie, NULL)); // Adjust local to global coordinates. rect.moveRight(rect.x() + translateReply->dst_x); rect.moveTop(rect.y() + translateReply->dst_y); // If the window capture failed, try to obtain one from the full screen. if (nativePixmap.isNull()) { return getToplevelPixmap(rect, blendPointer); } return postProcessPixmap(nativePixmap, rect, blendPointer); } bool X11ImageGrabber::isKWinAvailable() { if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.KWin"))) { QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Effects"), QStringLiteral("org.kde.kwin.Effects")); QDBusReply reply = interface.call(QStringLiteral("isEffectLoaded"), QStringLiteral("screenshot")); return reply.value(); } return false; } void X11ImageGrabber::KWinDBusScreenshotHelper(quint64 pixmapId) { // obtain width and height and grab an image (x and y are always zero for pixmaps) QRect rect = getDrawableGeometry((xcb_drawable_t)pixmapId); mPixmap = getPixmapFromDrawable((xcb_drawable_t)pixmapId, rect); if (!mPixmap.isNull()) { emit pixmapChanged(mPixmap); return; } // Cannot retrieve pixmap from KWin, just fallback to fullscreen capture. We // could try to detect the original action (window under cursor or active // window), but that is too complex for this edge case. grabFullScreen(); } void X11ImageGrabber::rectangleSelectionCancelled() { QObject *sender = QObject::sender(); sender->disconnect(); sender->deleteLater(); emit imageGrabFailed(); } void X11ImageGrabber::rectangleSelectionConfirmed(const QPixmap &pixmap, const QRect ®ion) { QObject *sender = QObject::sender(); sender->disconnect(); sender->deleteLater(); if (mCapturePointer) { mPixmap = blendCursorImage(pixmap, region.x(), region.y(), region.width(), region.height()); } else { mPixmap = pixmap; } emit pixmapChanged(mPixmap); } // grabber methods void X11ImageGrabber::updateWindowTitle(xcb_window_t window) { QString windowTitle = KWindowSystem::readNameProperty(window, XA_WM_NAME); emit windowTitleChanged(windowTitle); } void X11ImageGrabber::grabFullScreen() { mPixmap = getToplevelPixmap(QRect(), mCapturePointer); emit pixmapChanged(mPixmap); } void X11ImageGrabber::grabTransientWithParent() { xcb_window_t curWin = getRealWindowUnderCursor(); updateWindowTitle(curWin); // grab the image early mPixmap = getToplevelPixmap(QRect(), false); // now that we know we have a transient window, let's // find other possible transient windows and the app window itself. QRegion clipRegion; QSet transients; xcb_window_t parentWinId = curWin; const QRect desktopRect(0, 0, 1, 1); do { // find parent window and add the window to the visible region xcb_window_t winId = parentWinId; QRect winRect; parentWinId = getTransientWindowParent(winId, winRect); transients << winId; // Don't include the 1x1 pixel sized desktop window in the top left corner that is present // if the window is a QDialog without a parent. // BUG: 376350 if (winRect != desktopRect) { clipRegion += winRect; } // Continue walking only if this is a transient window (having a parent) } while (parentWinId != XCB_WINDOW_NONE && !transients.contains(parentWinId)); // All parents are known now, find other transient children. // Assume that the lowest window is behind everything else, then if a new // transient window is discovered, its children can then also be found. QList winList = KWindowSystem::stackingOrder(); for (auto winId : winList) { QRect winRect; xcb_window_t parentWinId = getTransientWindowParent(winId, winRect); // if the parent should be displayed, then show the child too if (transients.contains(parentWinId)) { if (!transients.contains(winId)) { transients << winId; clipRegion += winRect; } } } // we can probably go ahead and generate the image now QImage tempImage(mPixmap.size(), QImage::Format_ARGB32); tempImage.fill(Qt::transparent); QPainter tempPainter(&tempImage); tempPainter.setClipRegion(clipRegion); tempPainter.drawPixmap(0, 0, mPixmap); tempPainter.end(); mPixmap = QPixmap::fromImage(tempImage).copy(clipRegion.boundingRect()); // why stop here, when we can render a 20px drop shadow all around it QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect; effect->setOffset(0); effect->setBlurRadius(20); QGraphicsPixmapItem *item = new QGraphicsPixmapItem; item->setPixmap(mPixmap); item->setGraphicsEffect(effect); QImage shadowImage(mPixmap.size() + QSize(40, 40), QImage::Format_ARGB32); shadowImage.fill(Qt::transparent); QPainter shadowPainter(&shadowImage); QGraphicsScene scene; scene.addItem(item); scene.render(&shadowPainter, QRectF(), QRectF(-20, -20, mPixmap.width() + 40, mPixmap.height() + 40)); shadowPainter.end(); // we can finish up now mPixmap = QPixmap::fromImage(shadowImage); if (mCapturePointer) { QPoint topLeft = clipRegion.boundingRect().topLeft() - QPoint(20, 20); mPixmap = blendCursorImage(mPixmap, topLeft.x(), topLeft.y(), mPixmap.width(), mPixmap.height()); } emit pixmapChanged(mPixmap); } void X11ImageGrabber::grabActiveWindow() { xcb_window_t activeWindow = KWindowSystem::activeWindow(); updateWindowTitle(activeWindow); // if KWin is available, use the KWin DBus interfaces if (mCaptureDecorations && isKWinAvailable()) { QDBusConnection bus = QDBusConnection::sessionBus(); bus.connect(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot"), QStringLiteral("screenshotCreated"), this, SLOT(KWinDBusScreenshotHelper(quint64))); QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); int mask = 1; if (mCapturePointer) { mask |= 1 << 1; } interface.call(QStringLiteral("screenshotForWindow"), (quint64)activeWindow, mask); return; } // otherwise, use the native functionality return grabApplicationWindowHelper(activeWindow); } void X11ImageGrabber::grabWindowUnderCursor() { const xcb_window_t windowUnderCursor = getRealWindowUnderCursor(); updateWindowTitle(windowUnderCursor); // if KWin is available, use the KWin DBus interfaces if (mCaptureDecorations && isKWinAvailable()) { QDBusConnection bus = QDBusConnection::sessionBus(); bus.connect(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot"), QStringLiteral("screenshotCreated"), this, SLOT(KWinDBusScreenshotHelper(quint64))); QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); int mask = 1; if (mCapturePointer) { mask |= 1 << 1; } interface.call(QStringLiteral("screenshotWindowUnderCursor"), mask); return; } // else, go native return grabApplicationWindowHelper(windowUnderCursor); } void X11ImageGrabber::grabApplicationWindowHelper(xcb_window_t window) { // if the user doesn't want decorations captured, we're in luck. This is // the easiest bit mPixmap = getWindowPixmap(window, mCapturePointer); if (!mCaptureDecorations || window == QX11Info::appRootWindow()) { emit pixmapChanged(mPixmap); return; } // if the user wants the window decorations, things get a little tricky. // we can't simply get a handle to the window manager frame window and // just grab it, because some compositing window managers (yes, kwin // included) do not render the window onto the frame but keep it in a // separate opengl buffer, so grabbing this window is going to simply // give us a transparent image with the frame and titlebar. // all is not lost. what we need to do is grab the image of the entire // desktop, find the geometry of the window including its frame, and // crop the root image accordingly. KWindowInfo info(window, NET::WMFrameExtents); if (info.valid()) { QRect frameGeom = info.frameGeometry(); mPixmap = getToplevelPixmap(frameGeom, mCapturePointer); } // fallback is window without the frame emit pixmapChanged(mPixmap); } QRect X11ImageGrabber::getDrawableGeometry(xcb_drawable_t drawable) { xcb_connection_t *xcbConn = QX11Info::connection(); xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry_unchecked(xcbConn, drawable); CScopedPointer geomReply(xcb_get_geometry_reply(xcbConn, geomCookie, NULL)); if (geomReply.isNull()) { return QRect(); } return QRect(geomReply->x, geomReply->y, geomReply->width, geomReply->height); } void X11ImageGrabber::grabCurrentScreen() { QPoint cursorPosition = QCursor::pos(); for (auto screen : QGuiApplication::screens()) { QRect screenRect = screen->geometry(); if (!screenRect.contains(cursorPosition)) { continue; } // The screen origin is in native pixels, but the size is device-dependent. Convert these also to native pixels. QRect nativeScreenRect(screenRect.topLeft(), screenRect.size() * screen->devicePixelRatio()); mPixmap = getToplevelPixmap(nativeScreenRect, mCapturePointer); emit pixmapChanged(mPixmap); return; } // No screen found with our cursor, fallback to capturing full screen grabFullScreen(); } void X11ImageGrabber::grabRectangularRegion() { QuickEditor *editor = new QuickEditor(getToplevelPixmap(QRect(), false)); connect(editor, &QuickEditor::grabDone, this, &X11ImageGrabber::rectangleSelectionConfirmed); connect(editor, &QuickEditor::grabCancelled, this, &X11ImageGrabber::rectangleSelectionCancelled); } xcb_window_t X11ImageGrabber::getRealWindowUnderCursor() { xcb_connection_t *xcbConn = QX11Info::connection(); xcb_window_t curWin = QX11Info::appRootWindow(); const QByteArray atomName("WM_STATE"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(xcbConn, 0, atomName.length(), atomName.constData()); xcb_query_pointer_cookie_t pointerCookie = xcb_query_pointer_unchecked(xcbConn, curWin); CScopedPointer atomReply(xcb_intern_atom_reply(xcbConn, atomCookie, NULL)); CScopedPointer pointerReply(xcb_query_pointer_reply(xcbConn, pointerCookie, NULL)); if (atomReply->atom == XCB_ATOM_NONE) { return QX11Info::appRootWindow(); } // now start testing QStack windowStack; windowStack.push(pointerReply->child); while (!windowStack.isEmpty()) { curWin = windowStack.pop(); // next, check if our window has the WM_STATE peoperty set on // the window. if yes, return the window - we have found it xcb_get_property_cookie_t propertyCookie = xcb_get_property_unchecked(xcbConn, 0, curWin, atomReply->atom, XCB_ATOM_ANY, 0, 0); CScopedPointer propertyReply(xcb_get_property_reply(xcbConn, propertyCookie, NULL)); if (propertyReply->type != XCB_ATOM_NONE) { return curWin; } // if we're here, this means the window is not the real window // we should start looking at its children xcb_query_tree_cookie_t treeCookie = xcb_query_tree_unchecked(xcbConn, curWin); CScopedPointer treeReply(xcb_query_tree_reply(xcbConn, treeCookie, NULL)); xcb_window_t *winChildren = xcb_query_tree_children(treeReply.data()); int winChildrenLength = xcb_query_tree_children_length(treeReply.data()); for (int i = winChildrenLength - 1; i >= 0; i--) { windowStack.push(winChildren[i]); } } // return the window. it has geometry information for a crop return pointerReply->child; } // obtain the size of the given window, returning the window ID of the parent xcb_window_t X11ImageGrabber::getTransientWindowParent(xcb_window_t winId, QRect &outRect) { NET::Properties properties = mCaptureDecorations ? NET::WMFrameExtents : NET::WMGeometry; KWindowInfo winInfo(winId, properties, NET::WM2TransientFor); // add the current window to the image if (mCaptureDecorations) { outRect = winInfo.frameGeometry(); } else { outRect = winInfo.geometry(); } return winInfo.transientFor(); } QPoint X11ImageGrabber::getNativeCursorPosition() { // QCursor::pos() is not used because it requires additional calculations. // Its value is the offset to the origin of the current screen is in // device-independent pixels while the origin itself uses native pixels. xcb_connection_t *xcbConn = QX11Info::connection(); xcb_query_pointer_cookie_t pointerCookie = xcb_query_pointer_unchecked(xcbConn, QX11Info::appRootWindow()); CScopedPointer pointerReply(xcb_query_pointer_reply(xcbConn, pointerCookie, NULL)); return QPoint(pointerReply->root_x, pointerReply->root_y); } diff --git a/src/QuickEditor/QuickEditor.cpp b/src/QuickEditor/QuickEditor.cpp index f6af2ef..78824dc 100644 --- a/src/QuickEditor/QuickEditor.cpp +++ b/src/QuickEditor/QuickEditor.cpp @@ -1,146 +1,141 @@ /* * Copyright (C) 2016 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "QuickEditor.h" -#include -#include -#include -#include +#include "SpectacleConfig.h" +#include + +#include #include #include #include #include -#include - -#include -#include - -#include "SpectacleConfig.h" +#include struct QuickEditor::ImageStore : public QQuickImageProvider { ImageStore(const QPixmap &pixmap) : QQuickImageProvider(QQuickImageProvider::Pixmap), mPixmap(pixmap) {} QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) Q_DECL_OVERRIDE { Q_UNUSED(id); if (size) { *size = mPixmap.size(); } if (requestedSize.isEmpty()) { return mPixmap; } return mPixmap.scaled(requestedSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } QPixmap mPixmap; }; struct QuickEditor::QuickEditorPrivate { KDeclarative::KDeclarative *mDecl; QQuickView *mQuickView; QQmlEngine *mQmlEngine; QRect mGrabRect; QSharedPointer mCurrentGrabResult; }; QuickEditor::QuickEditor(const QPixmap &pixmap, QObject *parent) : QObject(parent), mImageStore(new ImageStore(pixmap)), d_ptr(new QuickEditorPrivate) { Q_D(QuickEditor); d->mQmlEngine = new QQmlEngine(); d->mDecl = new KDeclarative::KDeclarative; d->mDecl->setDeclarativeEngine(d->mQmlEngine); d->mDecl->setupBindings(); d->mQmlEngine->addImageProvider(QStringLiteral("snapshot"), mImageStore); d->mQuickView = new QQuickView(d->mQmlEngine, 0); d->mQuickView->setClearBeforeRendering(false); d->mQuickView->setSource(QUrl(QStringLiteral("qrc:///QuickEditor/EditorRoot.qml"))); d->mQuickView->setFlags(Qt::BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool); d->mQuickView->setGeometry(0, 0, pixmap.width(), pixmap.height()); d->mQuickView->showFullScreen(); // connect up the signals QQuickItem *rootItem = d->mQuickView->rootObject(); connect(rootItem, SIGNAL(acceptImage(int, int, int, int)), this, SLOT(acceptImageHandler(int, int, int, int))); connect(rootItem, SIGNAL(cancelImage()), this, SIGNAL(grabCancelled())); // set up initial config SpectacleConfig *config = SpectacleConfig::instance(); if (config->rememberLastRectangularRegion()) { auto pixelRatio = d->mQuickView->devicePixelRatio(); QRect cropRegion = config->cropRegion(); if (!cropRegion.isEmpty()) { QMetaObject::invokeMethod( rootItem, "setInitialSelection", Q_ARG(QVariant, cropRegion.x() / pixelRatio), Q_ARG(QVariant, cropRegion.y() / pixelRatio), Q_ARG(QVariant, cropRegion.width() / pixelRatio), Q_ARG(QVariant, cropRegion.height() / pixelRatio) ); } } if (config->useLightRegionMaskColour()) { rootItem->setProperty("maskColour", QColor(255, 255, 255, 100)); rootItem->setProperty("strokeColour", QColor(96, 96, 96, 255)); } } QuickEditor::~QuickEditor() { Q_D(QuickEditor); delete d->mQuickView; delete d->mDecl; delete d->mQmlEngine; delete d_ptr; } void QuickEditor::acceptImageHandler(int x, int y, int width, int height) { Q_D(QuickEditor); if ((x == -1) && (y == -1) && (width == -1) && (height == -1)) { SpectacleConfig::instance()->setCropRegion(QRect()); emit grabCancelled(); return; } auto pixelRatio = d->mQuickView->devicePixelRatio(); d->mGrabRect = QRect(x * pixelRatio, y * pixelRatio, width * pixelRatio, height * pixelRatio); SpectacleConfig::instance()->setCropRegion(d->mGrabRect); d->mQuickView->hide(); emit grabDone(mImageStore->mPixmap.copy(d->mGrabRect), d->mGrabRect); } diff --git a/src/SpectacleConfig.cpp b/src/SpectacleConfig.cpp index cbbcb64..4f9905f 100644 --- a/src/SpectacleConfig.cpp +++ b/src/SpectacleConfig.cpp @@ -1,257 +1,255 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SpectacleConfig.h" -#include - SpectacleConfig::SpectacleConfig(QObject *parent) : QObject(parent) { mConfig = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); mGeneralConfig = KConfigGroup(mConfig, "General"); mGuiConfig = KConfigGroup(mConfig, "GuiConfig"); } SpectacleConfig::~SpectacleConfig() {} SpectacleConfig* SpectacleConfig::instance() { static SpectacleConfig instance; return &instance; } // lastSaveAsLocation QUrl SpectacleConfig::lastSaveAsLocation() const { return mGeneralConfig.readEntry(QStringLiteral("lastSaveAsLocation"), QUrl::fromUserInput(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + QStringLiteral("/"))); } void SpectacleConfig::setLastSaveAsLocation(const QUrl &location) { mGeneralConfig.writeEntry(QStringLiteral("lastSaveAsLocation"), location); mGeneralConfig.sync(); } // cropRegion QRect SpectacleConfig::cropRegion() const { return mGuiConfig.readEntry(QStringLiteral("cropRegion"), QRect()); } void SpectacleConfig::setCropRegion(const QRect ®ion) { mGuiConfig.writeEntry(QStringLiteral("cropRegion"), region); mGuiConfig.sync(); } // onclick bool SpectacleConfig::onClickChecked() const { return mGuiConfig.readEntry(QStringLiteral("onClickChecked"), false); } void SpectacleConfig::setOnClickChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("onClickChecked"), enabled); mGuiConfig.sync(); } // include pointer bool SpectacleConfig::includePointerChecked() const { return mGuiConfig.readEntry(QStringLiteral("includePointer"), true); } void SpectacleConfig::setIncludePointerChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("includePointer"), enabled); mGuiConfig.sync(); } // include decorations bool SpectacleConfig::includeDecorationsChecked() const { return mGuiConfig.readEntry(QStringLiteral("includeDecorations"), true); } void SpectacleConfig::setIncludeDecorationsChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("includeDecorations"), enabled); mGuiConfig.sync(); } // capture transient window only bool SpectacleConfig::captureTransientWindowOnlyChecked() const { return mGuiConfig.readEntry(QStringLiteral("transientOnly"), false); } void SpectacleConfig::setCaptureTransientWindowOnlyChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("transientOnly"), enabled); mGuiConfig.sync(); } // quit after saving, copying, or exporting the image bool SpectacleConfig::quitAfterSaveOrCopyChecked() const { return mGuiConfig.readEntry(QStringLiteral("quitAfterSaveCopyExport"), false); } void SpectacleConfig::setQuitAfterSaveOrCopyChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("quitAfterSaveCopyExport"), enabled); mGuiConfig.sync(); } // capture delay qreal SpectacleConfig::captureDelay() const { return mGuiConfig.readEntry(QStringLiteral("captureDelay"), 0.0); } void SpectacleConfig::setCaptureDelay(qreal delay) { mGuiConfig.writeEntry(QStringLiteral("captureDelay"), delay); mGuiConfig.sync(); } // capture mode int SpectacleConfig::captureMode() const { return mGuiConfig.readEntry(QStringLiteral("captureModeIndex"), 0); } void SpectacleConfig::setCaptureMode(int index) { mGuiConfig.writeEntry(QStringLiteral("captureModeIndex"), index); mGuiConfig.sync(); } // remember last rectangular region bool SpectacleConfig::rememberLastRectangularRegion() const { return mGuiConfig.readEntry(QStringLiteral("rememberLastRectangularRegion"), false); } void SpectacleConfig::setRememberLastRectangularRegion(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("rememberLastRectangularRegion"), enabled); mGuiConfig.sync(); } // use light region mask colour bool SpectacleConfig::useLightRegionMaskColour() const { return mGuiConfig.readEntry(QStringLiteral("useLightMaskColour"), false); } void SpectacleConfig::setUseLightRegionMaskColour(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("useLightMaskColour"), enabled); mGuiConfig.sync(); } // last used save mode SaveMode SpectacleConfig::lastUsedSaveMode() const { switch (mGuiConfig.readEntry(QStringLiteral("lastUsedSaveMode"), 0)) { default: case 0: return SaveMode::SaveAs; case 1: return SaveMode::Save; } } void SpectacleConfig::setLastUsedSaveMode(SaveMode mode) { mGuiConfig.writeEntry(QStringLiteral("lastUsedSaveMode"), static_cast(mode)); mGuiConfig.sync(); } // autosave filename format QString SpectacleConfig::autoSaveFilenameFormat() const { return mGeneralConfig.readEntry(QStringLiteral("save-filename-format"), QStringLiteral("Screenshot_%Y%M%D_%H%m%S")); } void SpectacleConfig::setAutoSaveFilenameFormat(const QString &format) { mGeneralConfig.writeEntry(QStringLiteral("save-filename-format"), format); mGeneralConfig.sync(); } // autosave location QString SpectacleConfig::autoSaveLocation() const { return mGeneralConfig.readPathEntry(QStringLiteral("default-save-location"), QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); } void SpectacleConfig::setAutoSaveLocation(const QString &location) { mGeneralConfig.writePathEntry(QStringLiteral("default-save-location"), location); mGeneralConfig.sync(); } // copy save location to clipboard bool SpectacleConfig::copySaveLocationToClipboard() const { return mGeneralConfig.readEntry(QStringLiteral("copySaveLocation"), false); } void SpectacleConfig::setCopySaveLocationToClipboard(bool enabled) { mGeneralConfig.writeEntry(QStringLiteral("copySaveLocation"), enabled); mGeneralConfig.sync(); } // autosave image format QString SpectacleConfig::saveImageFormat() const { return mGeneralConfig.readEntry(QStringLiteral("default-save-image-format"), QStringLiteral("png")); } void SpectacleConfig::setSaveImageFormat(const QString &saveFmt) { mGeneralConfig.writeEntry(QStringLiteral("default-save-image-format"), saveFmt); mGeneralConfig.sync(); } diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp index 83720a8..97bda74 100644 --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -1,305 +1,302 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SpectacleCore.h" -#include "SpectacleConfig.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include #include "Config.h" #include "PlatformBackends/DummyImageGrabber.h" #ifdef XCB_FOUND #include "PlatformBackends/X11ImageGrabber.h" #endif #include "PlatformBackends/KWinWaylandImageGrabber.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + SpectacleCore::SpectacleCore(StartMode startMode, ImageGrabber::GrabMode grabMode, QString &saveFileName, qint64 delayMsec, bool notifyOnGrab, QObject *parent) : QObject(parent), mExportManager(ExportManager::instance()), mStartMode(startMode), mNotify(notifyOnGrab), mImageGrabber(nullptr), mMainWindow(nullptr), isGuiInited(false) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); if (!(saveFileName.isEmpty() || saveFileName.isNull())) { if (QDir::isRelativePath(saveFileName)) { saveFileName = QDir::current().absoluteFilePath(saveFileName); } setFilename(saveFileName); } #ifdef XCB_FOUND if (KWindowSystem::isPlatformX11()) { mImageGrabber = new X11ImageGrabber; } #endif if (!mImageGrabber && KWindowSystem::isPlatformWayland()) { mImageGrabber = new KWinWaylandImageGrabber; } if (!mImageGrabber) { mImageGrabber = new DummyImageGrabber; } setGrabMode(grabMode); mImageGrabber->setCapturePointer(guiConfig.readEntry("includePointer", true)); mImageGrabber->setCaptureDecorations(guiConfig.readEntry("includeDecorations", true)); if ((!(mImageGrabber->onClickGrabSupported())) && (delayMsec < 0)) { delayMsec = 0; } connect(mExportManager, &ExportManager::errorMessage, this, &SpectacleCore::showErrorMessage); connect(this, &SpectacleCore::errorMessage, this, &SpectacleCore::showErrorMessage); connect(mImageGrabber, &ImageGrabber::pixmapChanged, this, &SpectacleCore::screenshotUpdated); connect(mImageGrabber, &ImageGrabber::windowTitleChanged, mExportManager, &ExportManager::setWindowTitle); connect(mImageGrabber, &ImageGrabber::imageGrabFailed, this, &SpectacleCore::screenshotFailed); connect(mExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doCopyPath); connect(mExportManager, &ExportManager::forceNotify, this, &SpectacleCore::doNotify); switch (startMode) { case DBusMode: break; case BackgroundMode: { int msec = (KWindowSystem::compositingActive() ? 200 : 50) + delayMsec; QTimer::singleShot(msec, mImageGrabber, &ImageGrabber::doImageGrab); } break; case GuiMode: initGui(); break; } } SpectacleCore::~SpectacleCore() { if (mMainWindow) { delete mMainWindow; } } // Q_PROPERTY stuff QString SpectacleCore::filename() const { return mFileNameString; } void SpectacleCore::setFilename(const QString &filename) { mFileNameString = filename; mFileNameUrl = QUrl::fromUserInput(filename); } ImageGrabber::GrabMode SpectacleCore::grabMode() const { return mImageGrabber->grabMode(); } void SpectacleCore::setGrabMode(const ImageGrabber::GrabMode &grabMode) { mImageGrabber->setGrabMode(grabMode); mExportManager->setGrabMode(grabMode); } // Slots void SpectacleCore::dbusStartAgent() { qApp->setQuitOnLastWindowClosed(true); if (!(mStartMode == GuiMode)) { mStartMode = GuiMode; return initGui(); } } void SpectacleCore::takeNewScreenshot(const ImageGrabber::GrabMode &mode, const int &timeout, const bool &includePointer, const bool &includeDecorations) { setGrabMode(mode); mImageGrabber->setCapturePointer(includePointer); mImageGrabber->setCaptureDecorations(includeDecorations); if (timeout < 0) { mImageGrabber->doOnClickGrab(); return; } // when compositing is enabled, we need to give it enough time for the window // to disappear and all the effects are complete before we take the shot. there's // no way of knowing how long the disappearing effects take, but as per default // settings (and unless the user has set an extremely slow effect), 200 // milliseconds is a good amount of wait time. const int msec = KWindowSystem::compositingActive() ? 200 : 50; QTimer::singleShot(timeout + msec, mImageGrabber, &ImageGrabber::doImageGrab); } void SpectacleCore::showErrorMessage(const QString &errString) { qDebug() << "ERROR: " << errString; if (mStartMode == GuiMode) { KMessageBox::error(0, errString); } } void SpectacleCore::screenshotUpdated(const QPixmap &pixmap) { mExportManager->setPixmap(pixmap); switch (mStartMode) { case BackgroundMode: case DBusMode: { if (mNotify) { connect(mExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doNotify); } QUrl savePath = (mStartMode == BackgroundMode && mFileNameUrl.isValid() && mFileNameUrl.isLocalFile()) ? mFileNameUrl : QUrl(); mExportManager->doSave(savePath); // if we notify, we emit allDone only if the user either dismissed the notification or pressed // the "Open" button, otherwise the app closes before it can react to it. if (!mNotify) { emit allDone(); } } break; case GuiMode: mMainWindow->setScreenshotAndShow(pixmap); } } void SpectacleCore::screenshotFailed() { switch (mStartMode) { case BackgroundMode: showErrorMessage(i18n("Screenshot capture canceled or failed")); case DBusMode: emit grabFailed(); emit allDone(); return; case GuiMode: mMainWindow->show(); } } void SpectacleCore::doNotify(const QUrl &savedAt) { KNotification *notify = new KNotification(QStringLiteral("newScreenshotSaved")); switch(mImageGrabber->grabMode()) { case ImageGrabber::GrabMode::FullScreen: notify->setTitle(i18nc("The entire screen area was captured, heading", "Full Screen Captured")); break; case ImageGrabber::GrabMode::CurrentScreen: notify->setTitle(i18nc("The current screen was captured, heading", "Current Screen Captured")); break; case ImageGrabber::GrabMode::ActiveWindow: notify->setTitle(i18nc("The active window was captured, heading", "Active Window Captured")); break; case ImageGrabber::GrabMode::WindowUnderCursor: notify->setTitle(i18nc("The window under the mouse was captured, heading", "Window Under Cursor Captured")); break; case ImageGrabber::GrabMode::RectangularRegion: notify->setTitle(i18nc("A rectangular region was captured, heading", "Rectangular Region Captured")); break; default: break; } const QString &path = savedAt.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); // a speaking message is prettier than a URL, special case for the default pictures location if (path == QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)) { notify->setText(i18nc("Placeholder is filename", "A screenshot was saved as '%1' to your Pictures folder.", savedAt.fileName())); } else { notify->setText(i18n("A screenshot was saved as '%1' to '%2'.", savedAt.fileName(), path)); } notify->setActions({i18nc("Open the screenshot we just saved", "Open")}); notify->setUrls({savedAt}); connect(notify, &KNotification::action1Activated, this, [this, savedAt] { new KRun(savedAt, nullptr); QTimer::singleShot(250, this, &SpectacleCore::allDone); }); connect(notify, &QObject::destroyed, this, &SpectacleCore::allDone); notify->sendEvent(); } void SpectacleCore::doCopyPath(const QUrl &savedAt) { if (SpectacleConfig::instance()->copySaveLocationToClipboard()) { qApp->clipboard()->setText(savedAt.toLocalFile()); } } void SpectacleCore::doStartDragAndDrop() { QUrl tempFile = mExportManager->tempSave(); if (!tempFile.isValid()) { return; } QMimeData *mimeData = new QMimeData; mimeData->setUrls(QList { tempFile }); mimeData->setData(QStringLiteral("application/x-kde-suggestedfilename"), QFile::encodeName(tempFile.fileName())); QDrag *dragHandler = new QDrag(this); dragHandler->setMimeData(mimeData); dragHandler->setPixmap(mExportManager->pixmap().scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); dragHandler->exec(Qt::CopyAction); } // Private void SpectacleCore::initGui() { if (!isGuiInited) { mMainWindow = new KSMainWindow(mImageGrabber->onClickGrabSupported()); connect(mMainWindow, &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot); connect(mMainWindow, &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop); isGuiInited = true; QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); } }