File indexing completed on 2024-04-28 04:38:34

0001 /*
0002     SPDX-FileCopyrightText: 1999-2001 John Birch <jbb@kdevelop.org>
0003     SPDX-FileCopyrightText: 2001 Bernd Gehrmann <bernd@kdevelop.org>
0004     SPDX-FileCopyrightText: 2006 Vladimir Prus <ghost@cs.msu.su>
0005     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0006     SPDX-FileCopyrightText: 2016 Aetf <aetf@unlimitedcodeworks.xyz>
0007 
0008     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0009 */
0010 
0011 #include "midebuggerplugin.h"
0012 
0013 #include "midebugjobs.h"
0014 #include "dialogs/processselection.h"
0015 
0016 #include <interfaces/context.h>
0017 #include <interfaces/contextmenuextension.h>
0018 #include <interfaces/icore.h>
0019 #include <interfaces/idebugcontroller.h>
0020 #include <interfaces/iruncontroller.h>
0021 #include <interfaces/iuicontroller.h>
0022 #include <language/interfaces/editorcontext.h>
0023 #include <sublime/message.h>
0024 #include <isession.h>
0025 
0026 #include <KActionCollection>
0027 #include <KLocalizedString>
0028 #include <KMessageBox>
0029 #include <KMessageBox_KDevCompat>
0030 #include <KParts/MainWindow>
0031 #include <KStringHandler>
0032 
0033 #include <QAction>
0034 #include <QApplication>
0035 #include <QDBusConnection>
0036 #include <QDBusConnectionInterface>
0037 #include <QDBusInterface>
0038 #include <QDBusServiceWatcher>
0039 #include <QPointer>
0040 #include <QTimer>
0041 
0042 using namespace KDevelop;
0043 using namespace KDevMI;
0044 
0045 class KDevMI::DBusProxy : public QObject
0046 {
0047     Q_OBJECT
0048 
0049 public:
0050     DBusProxy(const QString& service, const QString& name, QObject* parent)
0051         : QObject(parent),
0052           m_dbusInterface(service, QStringLiteral("/debugger")),
0053           m_name(name), m_valid(true)
0054     {}
0055 
0056     ~DBusProxy() override
0057     {
0058         if (m_valid) {
0059             m_dbusInterface.call(QStringLiteral("debuggerClosed"), m_name);
0060         }
0061     }
0062 
0063     QDBusInterface* interface()
0064     {
0065         return &m_dbusInterface;
0066     }
0067 
0068     void Invalidate()
0069     {
0070         m_valid = false;
0071     }
0072 
0073 public Q_SLOTS:
0074     void debuggerAccepted(const QString& name)
0075     {
0076         if (name == m_name) {
0077             emit debugProcess(this);
0078         }
0079     }
0080 
0081     void debuggingFinished()
0082     {
0083         m_dbusInterface.call(QStringLiteral("debuggingFinished"), m_name);
0084     }
0085 
0086 Q_SIGNALS:
0087     void debugProcess(DBusProxy*);
0088 
0089 private:
0090     QDBusInterface m_dbusInterface;
0091     QString m_name;
0092     bool m_valid;
0093 };
0094 
0095 MIDebuggerPlugin::MIDebuggerPlugin(const QString &componentName, const QString& displayName, QObject *parent)
0096     : KDevelop::IPlugin(componentName, parent), m_displayName(displayName)
0097 {
0098     core()->debugController()->initializeUi();
0099 
0100     setupActions();
0101     setupDBus();
0102 }
0103 
0104 void MIDebuggerPlugin::setupActions()
0105 {
0106     KActionCollection* ac = actionCollection();
0107 
0108     auto * action = new QAction(this);
0109     action->setIcon(QIcon::fromTheme(QStringLiteral("core")));
0110     action->setText(i18nc("@action", "Examine Core File with %1", m_displayName));
0111     action->setWhatsThis(i18nc("@info:whatsthis",
0112                               "<b>Examine core file</b>"
0113                               "<p>This loads a core file, which is typically created "
0114                               "after the application has crashed, e.g. with a "
0115                               "segmentation fault. The core file contains an "
0116                               "image of the program memory at the time it crashed, "
0117                               "allowing you to do a post-mortem analysis.</p>"));
0118     connect(action, &QAction::triggered, this, &MIDebuggerPlugin::slotExamineCore);
0119     ac->addAction(QStringLiteral("debug_core"), action);
0120 
0121 #if HAVE_KSYSGUARD
0122     action = new QAction(this);
0123     action->setIcon(QIcon::fromTheme(QStringLiteral("connect_creating")));
0124     action->setText(i18nc("@action", "Attach to Process with %1", m_displayName));
0125     action->setWhatsThis(i18nc("@info:whatsthis",
0126                               "<b>Attach to process</b>"
0127                               "<p>Attaches the debugger to a running process.</p>"));
0128     connect(action, &QAction::triggered, this, &MIDebuggerPlugin::slotAttachProcess);
0129     ac->addAction(QStringLiteral("debug_attach"), action);
0130 #endif
0131 }
0132 
0133 void MIDebuggerPlugin::setupDBus()
0134 {
0135     auto serviceRegistered = [this](const QString& service) {
0136         if (m_drkonqis.contains(service))
0137             return;
0138         // New registration
0139         const QString name = i18n("KDevelop (%1) - %2", m_displayName, core()->activeSession()->name());
0140         auto drkonqiProxy = new DBusProxy(service, name, this);
0141         m_drkonqis.insert(service, drkonqiProxy);
0142         connect(drkonqiProxy->interface(), SIGNAL(acceptDebuggingApplication(QString)),
0143                 drkonqiProxy, SLOT(debuggerAccepted(QString)));
0144         connect(drkonqiProxy, &DBusProxy::debugProcess,
0145                 this, &MIDebuggerPlugin::slotDebugExternalProcess);
0146 
0147         drkonqiProxy->interface()->call(QStringLiteral("registerDebuggingApplication"), name, QCoreApplication::applicationPid());
0148     };
0149     auto serviceUnregistered = [this](const QString& service) {
0150         // Deregistration
0151         if (auto* proxy = m_drkonqis.take(service)) {
0152             proxy->Invalidate();
0153             delete proxy;
0154         }
0155     };
0156 
0157     m_watcher = new QDBusServiceWatcher(QStringLiteral("org.kde.drkonqi*"), QDBusConnection::sessionBus(),
0158                                         QDBusServiceWatcher::WatchForOwnerChange, this);
0159     connect(m_watcher, &QDBusServiceWatcher::serviceRegistered, this, serviceRegistered);
0160     connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, serviceUnregistered);
0161 
0162     auto registeredServiceNames = QDBusConnection::sessionBus().interface()->registeredServiceNames();
0163     if (!registeredServiceNames.isValid()) {
0164         return;
0165     }
0166     for (const auto &serviceName : registeredServiceNames.value()) {
0167         if (serviceName.startsWith(QStringLiteral("org.kde.drkonqi."))) {
0168             serviceRegistered(serviceName);
0169         }
0170     }
0171 }
0172 
0173 void MIDebuggerPlugin::unload()
0174 {
0175     qDeleteAll(m_drkonqis.values());
0176     m_drkonqis.clear();
0177     unloadToolViews();
0178 }
0179 
0180 MIDebuggerPlugin::~MIDebuggerPlugin() { }
0181 
0182 void MIDebuggerPlugin::slotDebugExternalProcess(DBusProxy* proxy)
0183 {
0184     QDBusReply<int> reply = proxy->interface()->call(QStringLiteral("pid"));
0185     if (reply.isValid()) {
0186         connect(attachProcess(reply.value()), &KJob::result,
0187                 proxy, &DBusProxy::debuggingFinished);
0188     }
0189 
0190     if (auto* mainWindow = core()->uiController()->activeMainWindow()) {
0191         mainWindow->raise();
0192     }
0193 }
0194 
0195 ContextMenuExtension MIDebuggerPlugin::contextMenuExtension(Context* context, QWidget* parent)
0196 {
0197     ContextMenuExtension menuExt = IPlugin::contextMenuExtension(context, parent);
0198 
0199     if (context->type() != KDevelop::Context::EditorContext)
0200         return menuExt;
0201 
0202     auto *econtext = dynamic_cast<EditorContext*>(context);
0203     if (!econtext)
0204         return menuExt;
0205 
0206     QString contextIdent = econtext->currentWord();
0207 
0208     if (!contextIdent.isEmpty())
0209     {
0210         QString squeezed = KStringHandler::csqueeze(contextIdent, 30);
0211 
0212         auto* action = new QAction(parent);
0213         action->setText(i18nc("@action:inmenu", "Evaluate: %1", squeezed));
0214         action->setWhatsThis(i18nc("@info:whatsthis",
0215                                   "<b>Evaluate expression</b>"
0216                                   "<p>Shows the value of the expression under the cursor.</p>"));
0217         connect(action, &QAction::triggered, this, [this, contextIdent](){
0218             emit addWatchVariable(contextIdent);
0219         });
0220         menuExt.addAction(ContextMenuExtension::DebugGroup, action);
0221 
0222         action = new QAction(parent);
0223         action->setText(i18nc("@action:inmenu", "Watch: %1", squeezed));
0224         action->setWhatsThis(i18nc("@info:whatsthis",
0225                                   "<b>Watch expression</b>"
0226                                   "<p>Adds the expression under the cursor to the Variables/Watch list.</p>"));
0227         connect(action, &QAction::triggered, this, [this, contextIdent](){
0228             emit evaluateExpression(contextIdent);
0229         });
0230         menuExt.addAction(ContextMenuExtension::DebugGroup, action);
0231     }
0232 
0233     return menuExt;
0234 }
0235 
0236 void MIDebuggerPlugin::slotExamineCore()
0237 {
0238     showStatusMessage(i18n("Choose a core file to examine..."), 1000);
0239 
0240     if (core()->debugController()->currentSession() != nullptr) {
0241         KMessageBox::ButtonCode answer = KMessageBox::warningTwoActions(
0242             core()->uiController()->activeMainWindow(),
0243             i18n("A program is already being debugged. Do you want to abort the "
0244                  "currently running debug session and continue?"),
0245             {}, KGuiItem(i18nc("@action:button", "Abort Current Session"), QStringLiteral("application-exit")),
0246             KStandardGuiItem::cancel());
0247         if (answer == KMessageBox::SecondaryAction)
0248             return;
0249     }
0250     auto *job = new MIExamineCoreJob(this, core()->runController());
0251     core()->runController()->registerJob(job);
0252     // job->start() is called in registerJob
0253 }
0254 
0255 #if HAVE_KSYSGUARD
0256 void MIDebuggerPlugin::slotAttachProcess()
0257 {
0258     showStatusMessage(i18n("Choose a process to attach to..."), 1000);
0259 
0260     if (core()->debugController()->currentSession() != nullptr) {
0261         KMessageBox::ButtonCode answer = KMessageBox::warningTwoActions(
0262             core()->uiController()->activeMainWindow(),
0263             i18n("A program is already being debugged. Do you want to abort the "
0264                  "currently running debug session and continue?"),
0265             {}, KGuiItem(i18nc("@action:button", "Abort Current Session"), QStringLiteral("application-exit")),
0266             KStandardGuiItem::cancel());
0267         if (answer == KMessageBox::SecondaryAction)
0268             return;
0269     }
0270 
0271     QPointer<ProcessSelectionDialog> dlg = new ProcessSelectionDialog(core()->uiController()->activeMainWindow());
0272     if (!dlg->exec() || !dlg->pidSelected()) {
0273         delete dlg;
0274         return;
0275     }
0276 
0277     // TODO: move check into process selection dialog
0278     int pid = dlg->pidSelected();
0279     delete dlg;
0280     if (QApplication::applicationPid() == pid) {
0281         const QString messageText =
0282             i18n("Not attaching to process %1: cannot attach the debugger to itself.", pid);
0283         auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
0284         ICore::self()->uiController()->postMessage(message);
0285     }
0286     else
0287         attachProcess(pid);
0288 }
0289 #endif
0290 
0291 MIAttachProcessJob* MIDebuggerPlugin::attachProcess(int pid)
0292 {
0293     auto *job = new MIAttachProcessJob(this, pid, core()->runController());
0294     core()->runController()->registerJob(job);
0295     // job->start() is called in registerJob
0296 
0297     return job;
0298 }
0299 
0300 QString MIDebuggerPlugin::statusName() const
0301 {
0302     return i18n("Debugger");
0303 }
0304 
0305 void MIDebuggerPlugin::showStatusMessage(const QString& msg, int timeout)
0306 {
0307     emit showMessage(this, msg, timeout);
0308 }
0309 
0310 #include "midebuggerplugin.moc"
0311 #include "moc_midebuggerplugin.cpp"