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"