diff --git a/applets/systemtray/CMakeLists.txt b/applets/systemtray/CMakeLists.txt index 4b5540b7f..a9325d195 100644 --- a/applets/systemtray/CMakeLists.txt +++ b/applets/systemtray/CMakeLists.txt @@ -1,32 +1,31 @@ add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.private.systemtray\") plasma_install_package(package org.kde.plasma.private.systemtray) set(systemtray_SRCS systemtray.cpp ) ecm_qt_declare_logging_category(systemtray_SRCS HEADER debug.h IDENTIFIER SYSTEM_TRAY CATEGORY_NAME kde.systemtray DEFAULT_SEVERITY Info) add_library(org.kde.plasma.private.systemtray MODULE ${systemtray_SRCS}) kcoreaddons_desktop_to_json(org.kde.plasma.private.systemtray package/metadata.desktop) target_link_libraries(org.kde.plasma.private.systemtray Qt5::Gui Qt5::Quick KF5::Plasma Qt5::DBus - KF5::IconThemes KF5::XmlGui KF5::I18n) install(TARGETS org.kde.plasma.private.systemtray DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets) add_subdirectory(container) if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/klipper/CMakeLists.txt b/klipper/CMakeLists.txt index 6676a75cd..f0474788b 100644 --- a/klipper/CMakeLists.txt +++ b/klipper/CMakeLists.txt @@ -1,108 +1,106 @@ set(KLIPPER_VERSION_STRING ${PROJECT_VERSION}) add_definitions(-DTRANSLATION_DOMAIN=\"klipper\") add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII") add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) set(libklipper_common_SRCS klipper.cpp urlgrabber.cpp configdialog.cpp history.cpp historyitem.cpp historymodel.cpp historystringitem.cpp klipperpopup.cpp popupproxy.cpp historyimageitem.cpp historyurlitem.cpp actionstreewidget.cpp editactiondialog.cpp clipcommandprocess.cpp ) ecm_qt_declare_logging_category(libklipper_common_SRCS HEADER klipper_debug.h IDENTIFIER KLIPPER_LOG CATEGORY_NAME org.kde.klipper) find_package(KF5Prison ${KF5_MIN_VERSION}) set_package_properties(KF5Prison PROPERTIES DESCRIPTION "Prison library" URL "http://projects.kde.org/prison" TYPE OPTIONAL PURPOSE "Needed to create mobile barcodes from clipboard data" ) set(HAVE_PRISON ${KF5Prison_FOUND}) configure_file(config-klipper.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-klipper.h ) ki18n_wrap_ui(libklipper_common_SRCS generalconfig.ui actionsconfig.ui editactiondialog.ui) kconfig_add_kcfg_files(libklipper_common_SRCS klippersettings.kcfgc) set(klipper_KDEINIT_SRCS ${libklipper_common_SRCS} main.cpp tray.cpp) kf5_add_kdeinit_executable(klipper ${klipper_KDEINIT_SRCS}) target_link_libraries(kdeinit_klipper Qt5::Concurrent KF5::ConfigGui KF5::CoreAddons KF5::DBusAddons KF5::GlobalAccel - KF5::IconThemes KF5::KIOWidgets KF5::Notifications KF5::Service KF5::TextWidgets KF5::WindowSystem KF5::WidgetsAddons KF5::XmlGui ${ZLIB_LIBRARY} ) if (X11_FOUND) target_link_libraries(kdeinit_klipper XCB::XCB Qt5::X11Extras) endif() if (HAVE_PRISON) target_link_libraries(kdeinit_klipper KF5::Prison) endif () install(TARGETS kdeinit_klipper ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(TARGETS klipper ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(PROGRAMS org.kde.klipper.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(PROGRAMS klipper.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) # Plasma Data Engine set(plasma_engine_clipboard_SRCS ${libklipper_common_SRCS} clipboardengine.cpp clipboardservice.cpp clipboardjob.cpp) add_library(plasma_engine_clipboard MODULE ${plasma_engine_clipboard_SRCS}) kcoreaddons_desktop_to_json(plasma_engine_clipboard plasma-dataengine-clipboard.desktop) target_link_libraries(plasma_engine_clipboard Qt5::Concurrent Qt5::DBus Qt5::Widgets # QAction KF5::ConfigGui KF5::CoreAddons # KUrlMimeData KF5::GlobalAccel - KF5::IconThemes KF5::KIOWidgets # PreviewJob KF5::Plasma KF5::Notifications KF5::Service KF5::TextWidgets # KTextEdit KF5::WidgetsAddons # KMessageBox KF5::WindowSystem KF5::XmlGui # KActionCollection ${ZLIB_LIBRARY} ) if (X11_FOUND) target_link_libraries(plasma_engine_clipboard XCB::XCB Qt5::X11Extras) endif() if (HAVE_PRISON) target_link_libraries(plasma_engine_clipboard KF5::Prison) endif () install(TARGETS plasma_engine_clipboard DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/dataengine) install(FILES plasma-dataengine-clipboard.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(FILES org.kde.plasma.clipboard.operations DESTINATION ${PLASMA_DATA_INSTALL_DIR}/services) if(BUILD_TESTING) add_subdirectory(autotests) endif() install( FILES klipper.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) diff --git a/klipper/editactiondialog.cpp b/klipper/editactiondialog.cpp index 3814ade77..66cefcf81 100644 --- a/klipper/editactiondialog.cpp +++ b/klipper/editactiondialog.cpp @@ -1,390 +1,385 @@ /* This file is part of the KDE project Copyright (C) 2009 by Dmitry Suzdalev This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "editactiondialog.h" +#include #include #include #include "klipper_debug.h" #include -#include #include #include #include "urlgrabber.h" #include "ui_editactiondialog.h" namespace { static QString output2text(ClipCommand::Output output) { switch(output) { case ClipCommand::IGNORE: return QString(i18n("Ignore")); case ClipCommand::REPLACE: return QString(i18n("Replace Clipboard")); case ClipCommand::ADD: return QString(i18n("Add to Clipboard")); } return QString(); } } /** * Show dropdown of editing Output part of commands */ class ActionOutputDelegate : public QItemDelegate { public: ActionOutputDelegate(QObject* parent = nullptr) : QItemDelegate(parent){ } QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const override { QComboBox* editor = new QComboBox(parent); editor->setInsertPolicy(QComboBox::NoInsert); editor->addItem(output2text(ClipCommand::IGNORE), QVariant::fromValue(ClipCommand::IGNORE)); editor->addItem(output2text(ClipCommand::REPLACE), QVariant::fromValue(ClipCommand::REPLACE)); editor->addItem(output2text(ClipCommand::ADD), QVariant::fromValue(ClipCommand::ADD)); return editor; } void setEditorData(QWidget* editor, const QModelIndex& index) const override { QComboBox* ed = static_cast(editor); QVariant data(index.model()->data(index, Qt::EditRole)); ed->setCurrentIndex(static_cast(data.value())); } void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override { QComboBox* ed = static_cast(editor); model->setData(index, ed->itemData(ed->currentIndex())); } void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const override { editor->setGeometry(option.rect); } }; class ActionDetailModel : public QAbstractTableModel { public: ActionDetailModel(ClipAction* action, QObject* parent = nullptr); QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; const QList& commands() const { return m_commands; } void addCommand(const ClipCommand& command); void removeCommand(const QModelIndex& index); private: enum column_t { COMMAND_COL = 0, OUTPUT_COL = 1, DESCRIPTION_COL = 2 }; QList m_commands; QVariant displayData(ClipCommand* command, column_t column) const; QVariant editData(ClipCommand* command, column_t column) const; QVariant decorationData(ClipCommand* command, column_t column) const; void setIconForCommand(ClipCommand& cmd); }; ActionDetailModel::ActionDetailModel(ClipAction* action, QObject* parent): QAbstractTableModel(parent), m_commands(action->commands()) { } Qt::ItemFlags ActionDetailModel::flags(const QModelIndex& /*index*/) const { return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; } void ActionDetailModel::setIconForCommand(ClipCommand& cmd) { // let's try to update icon of the item according to command QString command = cmd.command; if ( command.contains( QLatin1Char(' ') ) ) { // get first word command = command.section( QLatin1Char(' '), 0, 0 ); } - QPixmap iconPix = KIconLoader::global()->loadIcon( - command, KIconLoader::Small, 0, - KIconLoader::DefaultState, - QStringList(), nullptr, true /* canReturnNull */ ); - - if ( !iconPix.isNull() ) { + if (QIcon::hasThemeIcon(command)) { cmd.icon = command; } else { cmd.icon.clear(); } } bool ActionDetailModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (role == Qt::EditRole) { ClipCommand cmd = m_commands.at(index.row()); switch (static_cast(index.column())) { case COMMAND_COL: cmd.command = value.toString(); setIconForCommand(cmd); break; case OUTPUT_COL: cmd.output = value.value(); break; case DESCRIPTION_COL: cmd.description = value.toString(); break; } m_commands.replace(index.row(), cmd); emit dataChanged(index, index); return true; } return false; } int ActionDetailModel::columnCount(const QModelIndex& /*parent*/) const { return 3; } int ActionDetailModel::rowCount(const QModelIndex&) const { return m_commands.count(); } QVariant ActionDetailModel::displayData(ClipCommand* command, ActionDetailModel::column_t column) const { switch (column) { case COMMAND_COL: return command->command; case OUTPUT_COL: return output2text(command->output); case DESCRIPTION_COL: return command->description; } return QVariant(); } QVariant ActionDetailModel::decorationData(ClipCommand* command, ActionDetailModel::column_t column) const { switch (column) { case COMMAND_COL: return command->icon.isEmpty() ? QIcon::fromTheme( QStringLiteral("system-run") ) : QIcon::fromTheme( command->icon ); case OUTPUT_COL: case DESCRIPTION_COL: break; } return QVariant(); } QVariant ActionDetailModel::editData(ClipCommand* command, ActionDetailModel::column_t column) const { switch (column) { case COMMAND_COL: return command->command; case OUTPUT_COL: return QVariant::fromValue(command->output); case DESCRIPTION_COL: return command->description; } return QVariant(); } QVariant ActionDetailModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch(static_cast(section)) { case COMMAND_COL: return i18n("Command"); case OUTPUT_COL: return i18n("Output Handling"); case DESCRIPTION_COL: return i18n("Description"); } } return QAbstractTableModel::headerData(section, orientation, role); } QVariant ActionDetailModel::data(const QModelIndex& index, int role) const { const int column = index.column(); const int row = index.row(); ClipCommand cmd = m_commands.at(row); switch (role) { case Qt::DisplayRole: return displayData(&cmd, static_cast(column)); case Qt::DecorationRole: return decorationData(&cmd, static_cast(column)); case Qt::EditRole: return editData(&cmd, static_cast(column)); } return QVariant(); } void ActionDetailModel::addCommand(const ClipCommand& command) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_commands << command; endInsertRows(); } void ActionDetailModel::removeCommand(const QModelIndex& index) { int row = index.row(); beginRemoveRows(QModelIndex(), row, row); m_commands.removeAt(row); endRemoveRows(); } EditActionDialog::EditActionDialog(QWidget* parent) : QDialog(parent) { setWindowTitle(i18n("Action Properties")); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); buttons->button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttons, &QDialogButtonBox::accepted, this, &EditActionDialog::slotAccepted); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); QWidget* dlgWidget = new QWidget(this); m_ui = new Ui::EditActionDialog; m_ui->setupUi(dlgWidget); m_ui->leRegExp->setClearButtonEnabled(true); m_ui->leDescription->setClearButtonEnabled(true); m_ui->pbAddCommand->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_ui->pbRemoveCommand->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); // For some reason, the default row height is 30 pixel. Set it to the minimum sectionSize instead, // which is the font height+struts. m_ui->twCommandList->verticalHeader()->setDefaultSectionSize(m_ui->twCommandList->verticalHeader()->minimumSectionSize()); m_ui->twCommandList->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(dlgWidget); layout->addWidget(buttons); connect(m_ui->pbAddCommand, &QPushButton::clicked, this, &EditActionDialog::onAddCommand); connect(m_ui->pbRemoveCommand, &QPushButton::clicked, this, &EditActionDialog::onRemoveCommand); const KConfigGroup grp = KSharedConfig::openConfig()->group("EditActionDialog"); KWindowConfig::restoreWindowSize(windowHandle(), grp); QByteArray hdrState = grp.readEntry("ColumnState", QByteArray()); if (!hdrState.isEmpty()) { qCDebug(KLIPPER_LOG) << "Restoring column state"; m_ui->twCommandList->horizontalHeader()->restoreState(QByteArray::fromBase64(hdrState)); } // do this after restoreState() m_ui->twCommandList->horizontalHeader()->setHighlightSections(false); } EditActionDialog::~EditActionDialog() { delete m_ui; } void EditActionDialog::setAction(ClipAction* act, int commandIdxToSelect) { m_action = act; m_model = new ActionDetailModel(act, this); m_ui->twCommandList->setModel(m_model); m_ui->twCommandList->setItemDelegateForColumn(1, new ActionOutputDelegate); connect(m_ui->twCommandList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EditActionDialog::onSelectionChanged); updateWidgets( commandIdxToSelect ); } void EditActionDialog::updateWidgets(int commandIdxToSelect) { if (!m_action) { qCDebug(KLIPPER_LOG) << "no action to edit was set"; return; } m_ui->leRegExp->setText(m_action->regExp()); m_ui->automatic->setChecked(m_action->automatic()); m_ui->leDescription->setText(m_action->description()); if (commandIdxToSelect != -1) { m_ui->twCommandList->setCurrentIndex( m_model->index( commandIdxToSelect ,0 ) ); } // update Remove button onSelectionChanged(); } void EditActionDialog::saveAction() { if (!m_action) { qCDebug(KLIPPER_LOG) << "no action to edit was set"; return; } m_action->setRegExp( m_ui->leRegExp->text() ); m_action->setDescription( m_ui->leDescription->text() ); m_action->setAutomatic( m_ui->automatic->isChecked() ); m_action->clearCommands(); foreach ( const ClipCommand& cmd, m_model->commands() ){ m_action->addCommand( cmd ); } } void EditActionDialog::slotAccepted() { saveAction(); qCDebug(KLIPPER_LOG) << "Saving dialogue state"; KConfigGroup grp = KSharedConfig::openConfig()->group("EditActionDialog"); KWindowConfig::saveWindowSize(windowHandle(), grp); grp.writeEntry("ColumnState", m_ui->twCommandList->horizontalHeader()->saveState().toBase64()); accept(); } void EditActionDialog::onAddCommand() { m_model->addCommand(ClipCommand(i18n( "new command" ), i18n( "Command Description" ), true, QLatin1String("") )); m_ui->twCommandList->edit( m_model->index( m_model->rowCount()-1, 0 )); } void EditActionDialog::onRemoveCommand() { m_model->removeCommand(m_ui->twCommandList->selectionModel()->currentIndex()); } void EditActionDialog::onSelectionChanged() { m_ui->pbRemoveCommand->setEnabled( m_ui->twCommandList->selectionModel() && m_ui->twCommandList->selectionModel()->hasSelection() ); } diff --git a/klipper/urlgrabber.cpp b/klipper/urlgrabber.cpp index 28d8ce0bc..03097f3cf 100644 --- a/klipper/urlgrabber.cpp +++ b/klipper/urlgrabber.cpp @@ -1,487 +1,483 @@ /* This file is part of the KDE project Copyright (C) (C) 2000,2001,2002 by Carsten Pfeiffer This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "urlgrabber.h" #include #include "klipper_debug.h" #include #include +#include #include #include #include #include #include #include -#include #include #include #include #include #include "klippersettings.h" #include "clipcommandprocess.h" // TODO: script-interface? #include "history.h" #include "historystringitem.h" URLGrabber::URLGrabber(History* history): m_myCurrentAction(nullptr), m_myMenu(nullptr), m_myPopupKillTimer(new QTimer( this )), m_myPopupKillTimeout(8), m_stripWhiteSpace(true), m_history(history) { m_myPopupKillTimer->setSingleShot( true ); connect(m_myPopupKillTimer, &QTimer::timeout, this, &URLGrabber::slotKillPopupMenu); // testing /* ClipAction *action; action = new ClipAction( "^http:\\/\\/", "Web-URL" ); action->addCommand("kfmclient exec %s", "Open with Konqi", true); action->addCommand("netscape -no-about-splash -remote \"openURL(%s, new-window)\"", "Open with Netscape", true); m_myActions->append( action ); action = new ClipAction( "^mailto:", "Mail-URL" ); action->addCommand("kmail --composer %s", "Launch kmail", true); m_myActions->append( action ); action = new ClipAction( "^\\/.+\\.jpg$", "Jpeg-Image" ); action->addCommand("kuickshow %s", "Launch KuickShow", true); action->addCommand("kview %s", "Launch KView", true); m_myActions->append( action ); */ } URLGrabber::~URLGrabber() { qDeleteAll(m_myActions); m_myActions.clear(); delete m_myMenu; } // // Called from Klipper::slotRepeatAction, i.e. by pressing Ctrl-Alt-R // shortcut. I.e. never from clipboard monitoring // void URLGrabber::invokeAction( HistoryItemConstPtr item ) { m_myClipItem = item; actionMenu( item, false ); } void URLGrabber::setActionList( const ActionList& list ) { qDeleteAll(m_myActions); m_myActions.clear(); m_myActions = list; } void URLGrabber::matchingMimeActions(const QString& clipData) { QUrl url(clipData); KConfigGroup cg(KSharedConfig::openConfig(), "Actions"); if(!cg.readEntry("EnableMagicMimeActions",true)) { //qCDebug(KLIPPER_LOG) << "skipping mime magic due to configuration"; return; } if(!url.isValid()) { //qCDebug(KLIPPER_LOG) << "skipping mime magic due to invalid url"; return; } if(url.isRelative()) { //openinng a relative path will just not work. what path should be used? //qCDebug(KLIPPER_LOG) << "skipping mime magic due to relative url"; return; } if(url.isLocalFile()) { if ( clipData == QLatin1String("//")) { //qCDebug(KLIPPER_LOG) << "skipping mime magic due to C++ comment //"; return; } if(!QFile::exists(url.toLocalFile())) { //qCDebug(KLIPPER_LOG) << "skipping mime magic due to nonexistent localfile"; return; } } // try to figure out if clipData contains a filename QMimeDatabase db; QMimeType mimetype = db.mimeTypeForUrl(url); // let's see if we found some reasonable mimetype. // If we do we'll populate menu with actions for apps // that can handle that mimetype // first: if clipboard contents starts with http, let's assume it's "text/html". // That is even if we've url like "http://www.kde.org/somescript.pl", we'll // still treat that as html page, because determining a mimetype using kio // might take a long time, and i want this function to be quick! if ( ( clipData.startsWith( QLatin1String("http://") ) || clipData.startsWith( QLatin1String("https://") ) ) && mimetype.name() != QLatin1String("text/html") ) { mimetype = db.mimeTypeForName(QStringLiteral("text/html")); } if ( !mimetype.isDefault() ) { KService::List lst = KMimeTypeTrader::self()->query( mimetype.name(), QStringLiteral("Application") ); if ( !lst.isEmpty() ) { ClipAction* action = new ClipAction( QString(), mimetype.comment() ); foreach( const KService::Ptr &service, lst ) { action->addCommand( ClipCommand( QString(), service->name(), true, service->icon(), ClipCommand::IGNORE, service->storageId() ) ); } m_myMatches.append( action ); } } } const ActionList& URLGrabber::matchingActions( const QString& clipData, bool automatically_invoked ) { m_myMatches.clear(); matchingMimeActions(clipData); // now look for matches in custom user actions foreach (ClipAction* action, m_myActions) { if ( action->matches( clipData ) && (action->automatic() || !automatically_invoked) ) { m_myMatches.append( action ); } } return m_myMatches; } void URLGrabber::checkNewData( HistoryItemConstPtr item ) { // qCDebug(KLIPPER_LOG) << "** checking new data: " << clipData; actionMenu( item, true ); // also creates m_myMatches } void URLGrabber::actionMenu( HistoryItemConstPtr item, bool automatically_invoked ) { if (!item) { qWarning("Attempt to invoke URLGrabber without an item"); return; } QString text(item->text()); if (m_stripWhiteSpace) { text = text.trimmed(); } ActionList matchingActionsList = matchingActions( text, automatically_invoked ); if (!matchingActionsList.isEmpty()) { // don't react on blacklisted (e.g. konqi's/netscape's urls) unless the user explicitly asked for it if ( automatically_invoked && isAvoidedWindow() ) { return; } m_myCommandMapper.clear(); m_myPopupKillTimer->stop(); m_myMenu = new QMenu; connect(m_myMenu, &QMenu::triggered, this, &URLGrabber::slotItemSelected); foreach (ClipAction* clipAct, matchingActionsList) { m_myMenu->addSection(QIcon::fromTheme( QStringLiteral("klipper") ), i18n("%1 - Actions For: %2", clipAct->description(), KStringHandler::csqueeze(text, 45))); QList cmdList = clipAct->commands(); int listSize = cmdList.count(); for (int i=0; isetData(id); action->setText(item); if (!command.icon.isEmpty()) action->setIcon(QIcon::fromTheme(command.icon)); m_myCommandMapper.insert(id, qMakePair(clipAct,i)); m_myMenu->addAction(action); } } // only insert this when invoked via clipboard monitoring, not from an // explicit Ctrl-Alt-R if ( automatically_invoked ) { m_myMenu->addSeparator(); QAction *disableAction = new QAction(i18n("Disable This Popup"), this); connect(disableAction, &QAction::triggered, this, &URLGrabber::sigDisablePopup); m_myMenu->addAction(disableAction); } m_myMenu->addSeparator(); QAction *cancelAction = new QAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("&Cancel"), this); connect(cancelAction, &QAction::triggered, m_myMenu, &QMenu::hide); m_myMenu->addAction(cancelAction); m_myClipItem = item; if ( m_myPopupKillTimeout > 0 ) m_myPopupKillTimer->start( 1000 * m_myPopupKillTimeout ); emit sigPopup( m_myMenu ); } } void URLGrabber::slotItemSelected(QAction* action) { if (m_myMenu) m_myMenu->hide(); // deleted by the timer or the next action QString id = action->data().toString(); if (id.isEmpty()) { qCDebug(KLIPPER_LOG) << "Klipper: no command associated"; return; } // first is action ptr, second is command index QPair actionCommand = m_myCommandMapper.value(id); if (actionCommand.first) execute(actionCommand.first, actionCommand.second); else qCDebug(KLIPPER_LOG) << "Klipper: cannot find associated action"; } void URLGrabber::execute( const ClipAction* action, int cmdIdx ) const { if (!action) { qCDebug(KLIPPER_LOG) << "Action object is null"; return; } ClipCommand command = action->command(cmdIdx); if ( command.isEnabled ) { QString text(m_myClipItem->text()); if (m_stripWhiteSpace) { text = text.trimmed(); } if( !command.serviceStorageId.isEmpty()) { KService::Ptr service = KService::serviceByStorageId( command.serviceStorageId ); KRun::runApplication( *service, QList< QUrl >() << QUrl( text ), nullptr ); } else { ClipCommandProcess* proc = new ClipCommandProcess(*action, command, text, m_history, m_myClipItem); if (proc->program().isEmpty()) { delete proc; proc = nullptr; } else { proc->start(); } } } } void URLGrabber::loadSettings() { m_stripWhiteSpace = KlipperSettings::stripWhiteSpace(); m_myAvoidWindows = KlipperSettings::noActionsForWM_CLASS(); m_myPopupKillTimeout = KlipperSettings::timeoutForActionPopups(); qDeleteAll(m_myActions); m_myActions.clear(); KConfigGroup cg(KSharedConfig::openConfig(), "General"); int num = cg.readEntry("Number of Actions", 0); QString group; for ( int i = 0; i < num; i++ ) { group = QStringLiteral("Action_%1").arg( i ); m_myActions.append( new ClipAction( KSharedConfig::openConfig(), group ) ); } } void URLGrabber::saveSettings() const { KConfigGroup cg(KSharedConfig::openConfig(), "General"); cg.writeEntry( "Number of Actions", m_myActions.count() ); int i = 0; QString group; foreach (ClipAction* action, m_myActions) { group = QStringLiteral("Action_%1").arg( i ); action->save( KSharedConfig::openConfig(), group ); ++i; } KlipperSettings::setNoActionsForWM_CLASS(m_myAvoidWindows); } // find out whether the active window's WM_CLASS is in our avoid-list bool URLGrabber::isAvoidedWindow() const { const WId active = KWindowSystem::activeWindow(); if (!active) { return false; } KWindowInfo info(active, NET::Properties(), NET::WM2WindowClass); return m_myAvoidWindows.contains(QString::fromLatin1(info.windowClassName())); } void URLGrabber::slotKillPopupMenu() { if ( m_myMenu && m_myMenu->isVisible() ) { if ( m_myMenu->geometry().contains( QCursor::pos() ) && m_myPopupKillTimeout > 0 ) { m_myPopupKillTimer->start( 1000 * m_myPopupKillTimeout ); return; } } if ( m_myMenu ) { m_myMenu->deleteLater(); m_myMenu = nullptr; } } /////////////////////////////////////////////////////////////////////////// //////// ClipCommand::ClipCommand(const QString&_command, const QString& _description, bool _isEnabled, const QString& _icon, Output _output, const QString& _serviceStorageId) : command(_command), description(_description), isEnabled(_isEnabled), output(_output), serviceStorageId( _serviceStorageId) { if (!_icon.isEmpty()) icon = _icon; else { // try to find suitable icon QString appName = command.section( QLatin1Char(' '), 0, 0 ); if ( !appName.isEmpty() ) { - QPixmap iconPix = KIconLoader::global()->loadIcon( - appName, KIconLoader::Small, 0, - KIconLoader::DefaultState, - QStringList(), nullptr, true /* canReturnNull */ ); - if ( !iconPix.isNull() ) + if (QIcon::hasThemeIcon(appName)) icon = appName; else icon.clear(); } } } ClipAction::ClipAction( const QString& regExp, const QString& description, bool automatic ) : m_myRegExp( regExp ), m_myDescription( description ), m_automatic(automatic) { } ClipAction::ClipAction( KSharedConfigPtr kc, const QString& group ) : m_myRegExp( kc->group(group).readEntry("Regexp") ), m_myDescription (kc->group(group).readEntry("Description") ), m_automatic(kc->group(group).readEntry("Automatic", QVariant(true)).toBool() ) { KConfigGroup cg(kc, group); int num = cg.readEntry( "Number of commands", 0 ); // read the commands for ( int i = 0; i < num; i++ ) { QString _group = group + QStringLiteral("/Command_%1"); KConfigGroup _cg(kc, _group.arg(i)); addCommand( ClipCommand(_cg.readPathEntry( "Commandline", QString() ), _cg.readEntry( "Description" ), // i18n'ed _cg.readEntry( "Enabled" , false), _cg.readEntry( "Icon"), static_cast(_cg.readEntry( "Output", QVariant(ClipCommand::IGNORE)).toInt()))); } } ClipAction::~ClipAction() { m_myCommands.clear(); } void ClipAction::addCommand( const ClipCommand& cmd ) { if ( cmd.command.isEmpty() && cmd.serviceStorageId.isEmpty() ) return; m_myCommands.append( cmd ); } void ClipAction::replaceCommand( int idx, const ClipCommand& cmd ) { if ( idx < 0 || idx >= m_myCommands.count() ) { qCDebug(KLIPPER_LOG) << "wrong command index given"; return; } m_myCommands.replace(idx, cmd); } // precondition: we're in the correct action's group of the KConfig object void ClipAction::save( KSharedConfigPtr kc, const QString& group ) const { KConfigGroup cg(kc, group); cg.writeEntry( "Description", description() ); cg.writeEntry( "Regexp", regExp() ); cg.writeEntry( "Number of commands", m_myCommands.count() ); cg.writeEntry( "Automatic", automatic() ); int i=0; // now iterate over all commands of this action foreach (const ClipCommand& cmd, m_myCommands) { QString _group = group + QStringLiteral("/Command_%1"); KConfigGroup cg(kc, _group.arg(i)); cg.writePathEntry( "Commandline", cmd.command ); cg.writeEntry( "Description", cmd.description ); cg.writeEntry( "Enabled", cmd.isEnabled ); cg.writeEntry( "Icon", cmd.icon ); cg.writeEntry( "Output", static_cast(cmd.output) ); ++i; } } diff --git a/logout-greeter/CMakeLists.txt b/logout-greeter/CMakeLists.txt index c7d9067d4..45682e57a 100644 --- a/logout-greeter/CMakeLists.txt +++ b/logout-greeter/CMakeLists.txt @@ -1,31 +1,30 @@ set(LOGOUT_GREETER_SRCS main.cpp greeter.cpp shutdowndlg.cpp) ecm_qt_declare_logging_category(LOGOUT_GREETER_SRCS HEADER debug.h IDENTIFIER LOGOUT_GREETER CATEGORY_NAME kde.logout_greeter DEFAULT_SEVERITY Info) qt5_add_dbus_adaptor( LOGOUT_GREETER_SRCS ../ksmserver/org.kde.LogoutPrompt.xml greeter.h Greeter) qt5_add_dbus_interface( LOGOUT_GREETER_SRCS ../ksmserver/org.kde.KSMServerInterface.xml ksmserveriface) add_executable(ksmserver-logout-greeter ${LOGOUT_GREETER_SRCS}) target_link_libraries(ksmserver-logout-greeter PW::KWorkspace Qt5::Widgets Qt5::Quick Qt5::X11Extras KF5::Declarative - KF5::IconThemes KF5::I18n KF5::Package KF5::QuickAddons KF5::KDELibs4Support # Solid/PowerManagement KF5::WaylandClient ${X11_LIBRARIES} ) install(TARGETS ksmserver-logout-greeter DESTINATION ${KDE_INSTALL_LIBEXECDIR}) kdbusaddons_generate_dbus_service_file(ksmserver-logout-greeter org.kde.LogoutPrompt ${KDE_INSTALL_FULL_LIBEXECDIR}) if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/logout-greeter/shutdowndlg.cpp b/logout-greeter/shutdowndlg.cpp index 171ea803b..9623faf64 100644 --- a/logout-greeter/shutdowndlg.cpp +++ b/logout-greeter/shutdowndlg.cpp @@ -1,335 +1,334 @@ /***************************************************************** Copyright 2000 Matthias Ettrich Copyright 2007 Urs Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************/ #include "shutdowndlg.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 #include #include #include #include #include #include #include #include #include #include #include #include #include static const QString s_login1Service = QStringLiteral("org.freedesktop.login1"); static const QString s_login1Path = QStringLiteral("/org/freedesktop/login1"); static const QString s_dbusPropertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties"); static const QString s_login1ManagerInterface = QStringLiteral("org.freedesktop.login1.Manager"); static const QString s_login1RebootToFirmwareSetup = QStringLiteral("RebootToFirmwareSetup"); Q_DECLARE_METATYPE(Solid::PowerManagement::SleepState) KSMShutdownDlg::KSMShutdownDlg(QWindow* parent, bool maysd, KWorkSpace::ShutdownType sdtype, KWayland::Client::PlasmaShell *plasmaShell) : QuickViewSharedEngine(parent), m_result(false), m_waylandPlasmaShell(plasmaShell) // this is a WType_Popup on purpose. Do not change that! Not // having a popup here has severe side effects. { // window stuff setClearBeforeRendering(true); setColor(QColor(Qt::transparent)); setResizeMode(KQuickAddons::QuickViewSharedEngine::SizeRootObjectToView); // Qt doesn't set this on unmanaged windows //FIXME: or does it? if (KWindowSystem::isPlatformX11()) { XChangeProperty( QX11Info::display(), winId(), XInternAtom( QX11Info::display(), "WM_WINDOW_ROLE", False ), XA_STRING, 8, PropModeReplace, (unsigned char *)"logoutdialog", strlen( "logoutdialog" )); XClassHint classHint; classHint.res_name = const_cast("ksmserver"); classHint.res_class = const_cast("ksmserver"); XSetClassHint(QX11Info::display(), winId(), &classHint); } //QQuickView *windowContainer = QQuickView::createWindowContainer(m_view, this); //windowContainer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); QQmlContext *context = rootContext(); context->setContextProperty(QStringLiteral("maysd"), maysd); context->setContextProperty(QStringLiteral("sdtype"), sdtype); QQmlPropertyMap *mapShutdownType = new QQmlPropertyMap(this); mapShutdownType->insert(QStringLiteral("ShutdownTypeDefault"), QVariant::fromValue(KWorkSpace::ShutdownTypeDefault)); mapShutdownType->insert(QStringLiteral("ShutdownTypeNone"), QVariant::fromValue(KWorkSpace::ShutdownTypeNone)); mapShutdownType->insert(QStringLiteral("ShutdownTypeReboot"), QVariant::fromValue(KWorkSpace::ShutdownTypeReboot)); mapShutdownType->insert(QStringLiteral("ShutdownTypeHalt"), QVariant::fromValue(KWorkSpace::ShutdownTypeHalt)); mapShutdownType->insert(QStringLiteral("ShutdownTypeLogout"), QVariant::fromValue(KWorkSpace::ShutdownTypeLogout)); context->setContextProperty(QStringLiteral("ShutdownType"), mapShutdownType); QQmlPropertyMap *mapSpdMethods = new QQmlPropertyMap(this); QSet spdMethods = Solid::PowerManagement::supportedSleepStates(); mapSpdMethods->insert(QStringLiteral("StandbyState"), QVariant::fromValue(spdMethods.contains(Solid::PowerManagement::StandbyState))); mapSpdMethods->insert(QStringLiteral("SuspendState"), QVariant::fromValue(spdMethods.contains(Solid::PowerManagement::SuspendState))); mapSpdMethods->insert(QStringLiteral("HibernateState"), QVariant::fromValue(spdMethods.contains(Solid::PowerManagement::HibernateState))); context->setContextProperty(QStringLiteral("spdMethods"), mapSpdMethods); context->setContextProperty(QStringLiteral("canLogout"), KAuthorized::authorize(QStringLiteral("logout"))); // Trying to access a non-existent context property throws an error, always create the property and then update it later context->setContextProperty("rebootToFirmwareSetup", false); QDBusMessage message = QDBusMessage::createMethodCall(s_login1Service, s_login1Path, s_dbusPropertiesInterface, QStringLiteral("Get")); message.setArguments({s_login1ManagerInterface, s_login1RebootToFirmwareSetup}); QDBusPendingReply call = QDBusConnection::systemBus().asyncCall(message); QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this); connect(callWatcher, &QDBusPendingCallWatcher::finished, context, [context](QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; watcher->deleteLater(); if (reply.value().toBool()) { context->setContextProperty("rebootToFirmwareSetup", true); } }); // TODO KF6 remove, used to read "BootManager" from kdmrc context->setContextProperty(QStringLiteral("bootManager"), QStringLiteral("None")); //TODO KF6 remove. Unused context->setContextProperty(QStringLiteral("choose"), false); // TODO KF6 remove, used to call KDisplayManager::bootOptions QStringList rebootOptions; int def = 0; QQmlPropertyMap *rebootOptionsMap = new QQmlPropertyMap(this); rebootOptionsMap->insert(QStringLiteral("options"), QVariant::fromValue(rebootOptions)); rebootOptionsMap->insert(QStringLiteral("default"), QVariant::fromValue(def)); context->setContextProperty(QStringLiteral("rebootOptions"), rebootOptionsMap); // engine stuff KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); kdeclarative.setupBindings(); // windowContainer->installEventFilter(this); } void KSMShutdownDlg::init() { rootContext()->setContextProperty(QStringLiteral("screenGeometry"), screen()->geometry()); QString fileName; QString fileUrl; KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); if (!packageName.isEmpty()) { package.setPath(packageName); } fileName = package.filePath("logoutmainscript"); if (QFile::exists(fileName)) { setSource(package.fileUrl("logoutmainscript")); } else { qCWarning(LOGOUT_GREETER) << "Couldn't find a theme for the Shutdown dialog" << fileName; return; } if(!errors().isEmpty()) { qCWarning(LOGOUT_GREETER) << errors(); } connect(rootObject(), SIGNAL(logoutRequested()), SLOT(slotLogout())); connect(rootObject(), SIGNAL(haltRequested()), SLOT(slotHalt())); connect(rootObject(), SIGNAL(suspendRequested(int)), SLOT(slotSuspend(int)) ); connect(rootObject(), SIGNAL(rebootRequested()), SLOT(slotReboot())); connect(rootObject(), SIGNAL(rebootRequested2(int)), SLOT(slotReboot(int)) ); connect(rootObject(), SIGNAL(cancelRequested()), SLOT(reject())); connect(rootObject(), SIGNAL(lockScreenRequested()), SLOT(slotLockScreen())); connect(screen(), &QScreen::geometryChanged, this, [this] { setGeometry(screen()->geometry()); }); //decide in backgroundcontrast whether doing things darker or lighter //set backgroundcontrast here, because in QEvent::PlatformSurface //is too early and we don't have the root object yet const QColor backgroundColor = rootObject() ? rootObject()->property("backgroundColor").value() : QColor(); KWindowEffects::enableBackgroundContrast(winId(), true, 0.4, (backgroundColor.value() > 128 ? 1.6 : 0.3), 1.7); KQuickAddons::QuickViewSharedEngine::showFullScreen(); requestActivate(); KWindowSystem::setState(winId(), NET::SkipTaskbar|NET::SkipPager); setKeyboardGrabEnabled(true); } void KSMShutdownDlg::resizeEvent(QResizeEvent *e) { KQuickAddons::QuickViewSharedEngine::resizeEvent( e ); if( KWindowSystem::compositingActive()) { //TODO: reenable window mask when we are without composite? // clearMask(); } else { // setMask(m_view->mask()); } } bool KSMShutdownDlg::event(QEvent *e) { if (e->type() == QEvent::PlatformSurface) { switch (static_cast(e)->surfaceEventType()) { case QPlatformSurfaceEvent::SurfaceCreated: setupWaylandIntegration(); KWindowEffects::enableBlurBehind(winId(), true); break; case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: delete m_shellSurface; m_shellSurface = nullptr; break; } } return KQuickAddons::QuickViewSharedEngine::event(e); } void KSMShutdownDlg::setupWaylandIntegration() { if (m_shellSurface) { // already setup return; } using namespace KWayland::Client; if (!m_waylandPlasmaShell) { return; } Surface *s = Surface::fromWindow(this); if (!s) { return; } m_shellSurface = m_waylandPlasmaShell->createSurface(s, this); // TODO: set a proper window type to indicate to KWin that this is the logout dialog // maybe we need a dedicated type for it? m_shellSurface->setPosition(geometry().topLeft()); } void KSMShutdownDlg::slotLogout() { m_shutdownType = KWorkSpace::ShutdownTypeNone; accept(); } void KSMShutdownDlg::slotReboot() { // no boot option selected -> current m_bootOption.clear(); m_shutdownType = KWorkSpace::ShutdownTypeReboot; accept(); } void KSMShutdownDlg::slotReboot(int opt) { if (int(rebootOptions.size()) > opt) m_bootOption = rebootOptions[opt]; m_shutdownType = KWorkSpace::ShutdownTypeReboot; accept(); } void KSMShutdownDlg::slotLockScreen() { m_bootOption.clear(); QDBusMessage call = QDBusMessage::createMethodCall(QStringLiteral("org.kde.screensaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("Lock")); QDBusConnection::sessionBus().asyncCall(call); reject(); } void KSMShutdownDlg::slotHalt() { m_bootOption.clear(); m_shutdownType = KWorkSpace::ShutdownTypeHalt; accept(); } void KSMShutdownDlg::slotSuspend(int spdMethod) { m_bootOption.clear(); switch (spdMethod) { case Solid::PowerManagement::StandbyState: case Solid::PowerManagement::SuspendState: Solid::PowerManagement::requestSleep(Solid::PowerManagement::SuspendState, nullptr, nullptr); break; case Solid::PowerManagement::HibernateState: Solid::PowerManagement::requestSleep(Solid::PowerManagement::HibernateState, nullptr, nullptr); break; } reject(); } void KSMShutdownDlg::accept() { emit accepted(); } void KSMShutdownDlg::reject() { emit rejected(); } diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt index a032c9365..3523cfbc5 100644 --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -1,102 +1,101 @@ configure_file(config-ktexteditor.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ktexteditor.h ) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-plasma.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-plasma.h) add_definitions(-DPLASMA_DEPRECATED=) set(scripting_SRC scripting/appinterface.cpp scripting/applet.cpp scripting/containment.cpp scripting/configgroup.cpp scripting/panel.cpp scripting/scriptengine.cpp scripting/scriptengine_v1.cpp scripting/widget.cpp ) set(plasmashell_dbusXML dbus/org.kde.PlasmaShell.xml) qt5_add_dbus_adaptor(scripting_SRC ${plasmashell_dbusXML} shellcorona.h ShellCorona plasmashelladaptor) ecm_qt_declare_logging_category(plasmashell HEADER debug.h IDENTIFIER PLASMASHELL CATEGORY_NAME kde.plasmashell DEFAULT_SEVERITY Info) set (plasma_shell_SRCS alternativeshelper.cpp main.cpp containmentconfigview.cpp currentcontainmentactionsmodel.cpp desktopview.cpp panelview.cpp panelconfigview.cpp panelshadows.cpp shellcorona.cpp standaloneappcorona osd.cpp coronatesthelper.cpp debug.cpp screenpool.cpp softwarerendernotifier.cpp ${scripting_SRC} ) set(krunner_xml ${plasma-workspace_SOURCE_DIR}/krunner/dbus/org.kde.krunner.App.xml) qt5_add_dbus_interface(plasma_shell_SRCS ${krunner_xml} krunner_interface) add_executable(plasmashell ${plasma_shell_SRCS} ) target_link_libraries(plasmashell Qt5::Quick Qt5::DBus KF5::KIOCore KF5::WindowSystem KF5::Crash KF5::Plasma KF5::PlasmaQuick KF5::Solid KF5::Declarative KF5::I18n - KF5::IconThemes KF5::Activities KF5::GlobalAccel KF5::CoreAddons KF5::DBusAddons KF5::QuickAddons KF5::XmlGui KF5::Package KF5::WaylandClient KF5::Notifications PW::KWorkspace ) if (TARGET KUserFeedbackCore) target_link_libraries(plasmashell KUserFeedbackCore) target_compile_definitions(plasmashell PRIVATE -DWITH_KUSERFEEDBACKCORE) endif() target_include_directories(plasmashell PRIVATE "${CMAKE_BINARY_DIR}") target_compile_definitions(plasmashell PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") if(HAVE_X11) target_link_libraries(plasmashell ${X11_LIBRARIES} ${XCB_LIBRARIES} ) target_link_libraries(plasmashell Qt5::X11Extras) endif() configure_file(org.kde.plasmashell.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.desktop @ONLY) install(TARGETS plasmashell ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) install( FILES dbus/org.kde.PlasmaShell.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install(FILES scripting/plasma-layouttemplate.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) add_subdirectory(packageplugins) if(BUILD_TESTING) add_subdirectory(autotests) endif()