File indexing completed on 2024-05-12 05:37:16

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