File indexing completed on 2024-05-12 17:08:58

0001 /*
0002     SPDX-FileCopyrightText: 2009 Chani Armitage <chani@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "menu.h"
0008 
0009 #include <QAction>
0010 #include <QCheckBox>
0011 #include <QDBusPendingReply>
0012 #include <QVBoxLayout>
0013 
0014 #include <KActionCollection>
0015 #include <KActivities/Consumer>
0016 #include <KAuthorized>
0017 #include <KGlobalAccel>
0018 #include <KIO/CommandLauncherJob>
0019 #include <KLocalizedString>
0020 #include <KService>
0021 #include <KTerminalLauncherJob>
0022 #include <QDebug>
0023 #include <QIcon>
0024 
0025 #include <Plasma/Containment>
0026 #include <Plasma/Corona>
0027 
0028 #include "krunner_interface.h"
0029 #include "kworkspace.h"
0030 #include <sessionmanagement.h>
0031 
0032 ContextMenu::ContextMenu(QObject *parent, const QVariantList &args)
0033     : Plasma::ContainmentActions(parent, args)
0034     , m_session(new SessionManagement(this))
0035 {
0036 }
0037 
0038 ContextMenu::~ContextMenu()
0039 {
0040 }
0041 
0042 void ContextMenu::restore(const KConfigGroup &config)
0043 {
0044     Plasma::Containment *c = containment();
0045     Q_ASSERT(c);
0046 
0047     m_actions.clear();
0048     m_actionOrder.clear();
0049     QHash<QString, bool> actions;
0050     QSet<QString> disabled;
0051 
0052     // clang-format off
0053     // because it really wants to mangle this nice aligned list
0054     if (c->containmentType() == Plasma::Types::PanelContainment || c->containmentType() == Plasma::Types::CustomPanelContainment) {
0055         m_actionOrder << QStringLiteral("add widgets")
0056                       << QStringLiteral("_context")
0057                       << QStringLiteral("configure")
0058                       << QStringLiteral("remove");
0059     } else {
0060         actions.insert(QStringLiteral("configure shortcuts"), false);
0061         m_actionOrder << QStringLiteral("configure")
0062                       << QStringLiteral("_display_settings")
0063                       << QStringLiteral("configure shortcuts")
0064                       << QStringLiteral("_sep1")
0065                       << QStringLiteral("_context")
0066                       << QStringLiteral("_open_terminal")
0067                       << QStringLiteral("_run_command")
0068                       << QStringLiteral("add widgets")
0069                       << QStringLiteral("_add panel")
0070                       << QStringLiteral("manage activities")
0071                       << QStringLiteral("remove")
0072                       << QStringLiteral("edit mode")
0073                       << QStringLiteral("_sep2")
0074                       << QStringLiteral("_lock_screen")
0075                       << QStringLiteral("_logout")
0076                       << QStringLiteral("_sep3")
0077                       << QStringLiteral("_wallpaper");
0078         disabled.insert(QStringLiteral("configure shortcuts"));
0079         disabled.insert(QStringLiteral("_open_terminal"));
0080         disabled.insert(QStringLiteral("_run_command"));
0081         disabled.insert(QStringLiteral("run associated application"));
0082         disabled.insert(QStringLiteral("_lock_screen"));
0083         disabled.insert(QStringLiteral("_logout"));
0084     }
0085     // clang-format on
0086 
0087     for (const QString &name : qAsConst(m_actionOrder)) {
0088         actions.insert(name, !disabled.contains(name));
0089     }
0090 
0091     QHashIterator<QString, bool> it(actions);
0092     while (it.hasNext()) {
0093         it.next();
0094         m_actions.insert(it.key(), config.readEntry(it.key(), it.value()));
0095     }
0096 
0097     // everything below should only happen once, so check for it
0098     if (!m_runCommandAction) {
0099         m_runCommandAction = new QAction(i18nc("plasma_containmentactions_contextmenu", "Show KRunner"), this);
0100         m_runCommandAction->setIcon(QIcon::fromTheme(QStringLiteral("plasma-search")));
0101         m_runCommandAction->setShortcut(KGlobalAccel::self()->globalShortcut(QStringLiteral("krunner.desktop"), QStringLiteral("_launch")).value(0));
0102         connect(m_runCommandAction, &QAction::triggered, this, &ContextMenu::runCommand);
0103 
0104         m_openTerminalAction = new QAction(i18n("Open Terminal"), this);
0105         m_openTerminalAction->setIcon(QIcon::fromTheme("utilities-terminal"));
0106         connect(m_openTerminalAction, &QAction::triggered, this, &ContextMenu::openTerminal);
0107 
0108         m_lockScreenAction = new QAction(i18nc("plasma_containmentactions_contextmenu", "Lock Screen"), this);
0109         m_lockScreenAction->setIcon(QIcon::fromTheme(QStringLiteral("system-lock-screen")));
0110         m_lockScreenAction->setShortcut(KGlobalAccel::self()->globalShortcut(QStringLiteral("ksmserver"), QStringLiteral("Lock Session")).value(0));
0111         m_lockScreenAction->setEnabled(m_session->canLock());
0112         connect(m_session, &SessionManagement::canLockChanged, this, [this]() {
0113             m_lockScreenAction->setEnabled(m_session->canLock());
0114         });
0115         connect(m_lockScreenAction, &QAction::triggered, m_session, &SessionManagement::lock);
0116 
0117         m_logoutAction = new QAction(i18nc("plasma_containmentactions_contextmenu", "Leaveā€¦"), this);
0118         m_logoutAction->setIcon(QIcon::fromTheme(QStringLiteral("system-log-out")));
0119         m_logoutAction->setShortcut(KGlobalAccel::self()->globalShortcut(QStringLiteral("ksmserver"), QStringLiteral("Log Out")).value(0));
0120         m_logoutAction->setEnabled(m_session->canLogout());
0121         connect(m_session, &SessionManagement::canLogoutChanged, this, [this]() {
0122             m_logoutAction->setEnabled(m_session->canLogout());
0123         });
0124         connect(m_logoutAction, &QAction::triggered, this, &ContextMenu::startLogout);
0125 
0126         m_configureDisplaysAction = new QAction(i18nc("plasma_containmentactions_contextmenu", "Configure Display Settingsā€¦"), this);
0127         m_configureDisplaysAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-display")));
0128         connect(m_configureDisplaysAction, &QAction::triggered, this, &ContextMenu::configureDisplays);
0129 
0130         m_separator1 = new QAction(this);
0131         m_separator1->setSeparator(true);
0132         m_separator2 = new QAction(this);
0133         m_separator2->setSeparator(true);
0134         m_separator3 = new QAction(this);
0135         m_separator3->setSeparator(true);
0136     }
0137 }
0138 
0139 QList<QAction *> ContextMenu::contextualActions()
0140 {
0141     Plasma::Containment *c = containment();
0142     Q_ASSERT(c);
0143     QList<QAction *> actions;
0144     foreach (const QString &name, m_actionOrder) {
0145         if (!m_actions.value(name)) {
0146             continue;
0147         }
0148 
0149         if (name == QLatin1String("_context")) {
0150             actions << c->contextualActions();
0151         }
0152         if (name == QLatin1String("_wallpaper")) {
0153             if (!c->wallpaper().isEmpty()) {
0154                 QObject *wallpaperGraphicsObject = c->property("wallpaperGraphicsObject").value<QObject *>();
0155                 if (wallpaperGraphicsObject) {
0156                     actions << wallpaperGraphicsObject->property("contextualActions").value<QList<QAction *>>();
0157                 }
0158             }
0159         } else if (QAction *a = action(name)) {
0160             // Bug 364292: show "Remove this Panel" option only when panelcontroller is opened
0161             if (name != QLatin1String("remove") || c->isUserConfiguring()
0162                 || (c->containmentType() != Plasma::Types::PanelContainment && c->containmentType() != Plasma::Types::CustomPanelContainment
0163                     && c->corona()->immutability() != Plasma::Types::Mutable)) {
0164                 actions << a;
0165             }
0166         }
0167     }
0168 
0169     return actions;
0170 }
0171 
0172 QAction *ContextMenu::action(const QString &name)
0173 {
0174     Plasma::Containment *c = containment();
0175     Q_ASSERT(c);
0176     if (name == QLatin1String("_sep1")) {
0177         return m_separator1;
0178     } else if (name == QLatin1String("_sep2")) {
0179         return m_separator2;
0180     } else if (name == QLatin1String("_sep3")) {
0181         return m_separator3;
0182     } else if (name == QLatin1String("_add panel")) {
0183         if (c->corona() && c->corona()->immutability() == Plasma::Types::Mutable) {
0184             return c->corona()->actions()->action(QStringLiteral("add panel"));
0185         }
0186     } else if (name == QLatin1String("_run_command")) {
0187         if (KAuthorized::authorizeAction(QStringLiteral("run_command")) && KAuthorized::authorize(QStringLiteral("run_command"))) {
0188             return m_runCommandAction;
0189         }
0190     } else if (name == QLatin1String("_open_terminal")) {
0191         if (KAuthorized::authorizeAction(QStringLiteral("shell_access"))) {
0192             return m_openTerminalAction;
0193         }
0194     } else if (name == QLatin1String("_lock_screen")) {
0195         if (KAuthorized::authorizeAction(QStringLiteral("lock_screen"))) {
0196             return m_lockScreenAction;
0197         }
0198     } else if (name == QLatin1String("_logout")) {
0199         if (KAuthorized::authorize(QStringLiteral("logout"))) {
0200             return m_logoutAction;
0201         }
0202     } else if (name == QLatin1String("_display_settings")) {
0203         if (KAuthorized::authorizeControlModule(QStringLiteral("kcm_kscreen.desktop")) && KService::serviceByStorageId(QStringLiteral("kcm_kscreen"))) {
0204             return m_configureDisplaysAction;
0205         }
0206     } else if (name == QLatin1String("edit mode")) {
0207         if (c->corona()) {
0208             return c->corona()->actions()->action(QStringLiteral("edit mode"));
0209         }
0210     } else if (name == QLatin1String("manage activities")) {
0211         if (c->corona()) {
0212             // Don't show the action if there's only one activity since in this
0213             // case it's clear that the user doesn't use activities
0214             if (KActivities::Consumer().activities().length() == 1) {
0215                 return nullptr;
0216             }
0217 
0218             return c->corona()->actions()->action(QStringLiteral("manage activities"));
0219         }
0220     } else {
0221         // FIXME: remove action: make removal of current activity possible
0222         return c->actions()->action(name);
0223     }
0224     return nullptr;
0225 }
0226 
0227 void ContextMenu::openTerminal()
0228 {
0229     if (!KAuthorized::authorizeAction(QStringLiteral("shell_access"))) {
0230         return;
0231     }
0232     auto job = new KTerminalLauncherJob(QString());
0233     job->setWorkingDirectory(QDir::homePath());
0234     job->start();
0235 }
0236 
0237 void ContextMenu::runCommand()
0238 {
0239     if (!KAuthorized::authorizeAction(QStringLiteral("run_command"))) {
0240         return;
0241     }
0242 
0243     QString interface(QStringLiteral("org.kde.krunner"));
0244     org::kde::krunner::App krunner(interface, QStringLiteral("/App"), QDBusConnection::sessionBus());
0245     krunner.display();
0246 }
0247 
0248 void ContextMenu::startLogout()
0249 {
0250     KConfig config(QStringLiteral("ksmserverrc"));
0251     const auto group = config.group("General");
0252     switch (group.readEntry("shutdownType", int(KWorkSpace::ShutdownTypeNone))) {
0253     case int(KWorkSpace::ShutdownTypeHalt):
0254         m_session->requestShutdown();
0255         break;
0256     case int(KWorkSpace::ShutdownTypeReboot):
0257         m_session->requestReboot();
0258         break;
0259     default:
0260         m_session->requestLogout();
0261         break;
0262     }
0263 }
0264 
0265 // FIXME: this function contains some code copied from KCMShell::openSystemSettings()
0266 // which is not publicly available to C++ code right now. Eventually we should
0267 // move that code into KIO so it's accessible to everyone, and then call that
0268 // function instead of this one
0269 void ContextMenu::configureDisplays()
0270 {
0271     const QString systemSettings = QStringLiteral("systemsettings");
0272     const QString kscreenKCM = QStringLiteral("kcm_kscreen");
0273 
0274     KIO::CommandLauncherJob *job = nullptr;
0275 
0276     // Open in System Settings if it's available
0277     if (KService::serviceByDesktopName(systemSettings)) {
0278         job = new KIO::CommandLauncherJob(QStringLiteral("systemsettings5"), {kscreenKCM});
0279         job->setDesktopName(systemSettings);
0280     } else {
0281         job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), {kscreenKCM});
0282     }
0283     job->start();
0284 }
0285 
0286 QWidget *ContextMenu::createConfigurationInterface(QWidget *parent)
0287 {
0288     QWidget *widget = new QWidget(parent);
0289     QVBoxLayout *lay = new QVBoxLayout();
0290     widget->setLayout(lay);
0291     widget->setWindowTitle(i18nc("plasma_containmentactions_contextmenu", "Configure Contextual Menu Plugin"));
0292     m_buttons = new QButtonGroup(widget);
0293     m_buttons->setExclusive(false);
0294 
0295     foreach (const QString &name, m_actionOrder) {
0296         QCheckBox *item = nullptr;
0297 
0298         if (name == QLatin1String("_context")) {
0299             item = new QCheckBox(widget);
0300             // FIXME better text
0301             item->setText(i18nc("plasma_containmentactions_contextmenu", "[Other Actions]"));
0302         } else if (name == QLatin1String("_wallpaper")) {
0303             item = new QCheckBox(widget);
0304             item->setText(i18nc("plasma_containmentactions_contextmenu", "Wallpaper Actions"));
0305             item->setIcon(QIcon::fromTheme(QStringLiteral("user-desktop")));
0306         } else if (name == QLatin1String("_sep1") || name == QLatin1String("_sep2") || name == QLatin1String("_sep3")) {
0307             item = new QCheckBox(widget);
0308             item->setText(i18nc("plasma_containmentactions_contextmenu", "[Separator]"));
0309         } else {
0310             QAction *a = action(name);
0311             if (a) {
0312                 item = new QCheckBox(widget);
0313                 item->setText(a->text());
0314                 item->setIcon(a->icon());
0315             }
0316         }
0317 
0318         if (item) {
0319             item->setChecked(m_actions.value(name));
0320             item->setProperty("actionName", name);
0321             lay->addWidget(item);
0322             m_buttons->addButton(item);
0323         }
0324     }
0325 
0326     return widget;
0327 }
0328 
0329 void ContextMenu::configurationAccepted()
0330 {
0331     QList<QAbstractButton *> buttons = m_buttons->buttons();
0332     QListIterator<QAbstractButton *> it(buttons);
0333     while (it.hasNext()) {
0334         QAbstractButton *b = it.next();
0335         if (b) {
0336             m_actions.insert(b->property("actionName").toString(), b->isChecked());
0337         }
0338     }
0339 }
0340 
0341 void ContextMenu::save(KConfigGroup &config)
0342 {
0343     QHashIterator<QString, bool> it(m_actions);
0344     while (it.hasNext()) {
0345         it.next();
0346         config.writeEntry(it.key(), it.value());
0347     }
0348 }
0349 
0350 K_PLUGIN_CLASS_WITH_JSON(ContextMenu, "plasma-containmentactions-contextmenu.json")
0351 
0352 #include "menu.moc"
0353 #include "moc_menu.cpp"