diff --git a/plugins/debuggercommon/midebuggerplugin.cpp b/plugins/debuggercommon/midebuggerplugin.cpp index ebf5be44ff..8dbe34e643 100644 --- a/plugins/debuggercommon/midebuggerplugin.cpp +++ b/plugins/debuggercommon/midebuggerplugin.cpp @@ -1,274 +1,310 @@ /* * Common code for MI debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2016 Aetf * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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. If not, see . * */ #include "midebuggerplugin.h" #include "midebugjobs.h" #include "dialogs/processselection.h" #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include -#include #include -#include #include using namespace KDevelop; using namespace KDevMI; +class KDevMI::DBusProxy : public QObject +{ + Q_OBJECT + +public: + DBusProxy(const QString& service, const QString& name, QObject* parent) + : QObject(parent), + m_dbusInterface(service, QStringLiteral("/debugger")), + m_name(name), m_valid(true) + {} + + ~DBusProxy() + { + if (m_valid) { + m_dbusInterface.call(QStringLiteral("debuggerClosed"), m_name); + } + } + + QDBusInterface* interface() + { + return &m_dbusInterface; + } + + void Invalidate() + { + m_valid = false; + } + +public Q_SLOTS: + void debuggerAccepted(const QString& name) + { + if (name == m_name) { + emit debugProcess(this); + } + } + + void debuggingFinished() + { + m_dbusInterface.call(QStringLiteral("debuggingFinished"), m_name); + } + +Q_SIGNALS: + void debugProcess(DBusProxy*); + +private: + QDBusInterface m_dbusInterface; + QString m_name; + bool m_valid; +}; + +const QString drkonqiservice = QLatin1String("org.kde.drkonqi"); + MIDebuggerPlugin::MIDebuggerPlugin(const QString &componentName, const QString& displayName, QObject *parent) - : KDevelop::IPlugin(componentName, parent) + : KDevelop::IPlugin(componentName, parent), m_displayName(displayName) { core()->debugController()->initializeUi(); - setupActions(displayName); + setupActions(); setupDBus(); } -void MIDebuggerPlugin::setupActions(const QString& displayName) +void MIDebuggerPlugin::setupActions() { KActionCollection* ac = actionCollection(); QAction * action = new QAction(this); action->setIcon(QIcon::fromTheme(QStringLiteral("core"))); - action->setText(i18n("Examine Core File with %1", displayName)); + action->setText(i18n("Examine Core File with %1", m_displayName)); action->setWhatsThis(i18n("Examine core file" "

This loads a core file, which is typically created " "after the application has crashed, e.g. with a " "segmentation fault. The core file contains an " "image of the program memory at the time it crashed, " "allowing you to do a post-mortem analysis.

")); connect(action, &QAction::triggered, this, &MIDebuggerPlugin::slotExamineCore); ac->addAction(QStringLiteral("debug_core"), action); #if KF5SysGuard_FOUND action = new QAction(this); action->setIcon(QIcon::fromTheme(QStringLiteral("connect_creating"))); - action->setText(i18n("Attach to Process with %1", displayName)); + action->setText(i18n("Attach to Process with %1", m_displayName)); action->setWhatsThis(i18n("Attach to process" "

Attaches the debugger to a running process.

")); connect(action, &QAction::triggered, this, &MIDebuggerPlugin::slotAttachProcess); ac->addAction(QStringLiteral("debug_attach"), action); #endif } void MIDebuggerPlugin::setupDBus() { - m_drkonqiMap = new QSignalMapper(this); - connect(m_drkonqiMap, static_cast(&QSignalMapper::mapped), - this, &MIDebuggerPlugin::slotDebugExternalProcess); - QDBusConnectionInterface* dbusInterface = QDBusConnection::sessionBus().interface(); for (const auto &service : dbusInterface->registeredServiceNames().value()) { - slotDBusServiceRegistered(service); + slotDBusOwnerChanged(service, QString(), QString('n')); } - QDBusServiceWatcher* watcher = new QDBusServiceWatcher(this); - connect(watcher, &QDBusServiceWatcher::serviceRegistered, - this, &MIDebuggerPlugin::slotDBusServiceRegistered); - connect(watcher, &QDBusServiceWatcher::serviceUnregistered, - this, &MIDebuggerPlugin::slotDBusServiceUnregistered); + connect(dbusInterface, &QDBusConnectionInterface::serviceOwnerChanged, + this, &MIDebuggerPlugin::slotDBusOwnerChanged); } void MIDebuggerPlugin::unload() { unloadToolViews(); } MIDebuggerPlugin::~MIDebuggerPlugin() { } -void MIDebuggerPlugin::slotDBusServiceRegistered(const QString& service) +void MIDebuggerPlugin::slotDBusOwnerChanged(const QString& service, const QString& oldOwner, const QString& newOwner) { - if (service.startsWith(QLatin1String("org.kde.drkonqi"))) { + if (oldOwner.isEmpty() && service.startsWith(drkonqiservice)) { + if (m_drkonqis.contains(service)) { + return; + } // New registration - QDBusInterface* drkonqiInterface = new QDBusInterface(service, QStringLiteral("/krashinfo"), - QString(), QDBusConnection::sessionBus(), - this); - m_drkonqis.insert(service, drkonqiInterface); - - connect(drkonqiInterface, SIGNAL(acceptDebuggingApplication()), m_drkonqiMap, SLOT(map())); - m_drkonqiMap->setMapping(drkonqiInterface, drkonqiInterface); - - drkonqiInterface->call(QStringLiteral("registerDebuggingApplication"), i18n("KDevelop")); - } -} - -void MIDebuggerPlugin::slotDBusServiceUnregistered(const QString& service) -{ - if (service.startsWith(QLatin1String("org.kde.drkonqi"))) { + const QString name = i18n("KDevelop (%1) - %2", m_displayName, core()->activeSession()->name()); + auto drkonqiProxy = new DBusProxy(service, name, this); + m_drkonqis.insert(service, drkonqiProxy); + connect(drkonqiProxy->interface(), SIGNAL(acceptDebuggingApplication(const QString&)), + drkonqiProxy, SLOT(debuggerAccepted(const QString&))); + connect(drkonqiProxy, &DBusProxy::debugProcess, + this, &MIDebuggerPlugin::slotDebugExternalProcess); + + drkonqiProxy->interface()->call(QStringLiteral("registerDebuggingApplication"), name); + } else if (newOwner.isEmpty() && service.startsWith(drkonqiservice)) { // Deregistration - if (m_drkonqis.contains(service)) - delete m_drkonqis.take(service); + if (m_drkonqis.contains(service)) { + auto proxy = m_drkonqis.take(service); + proxy->Invalidate(); + delete proxy; + } } } -void MIDebuggerPlugin::slotDebugExternalProcess(QObject* interface) +void MIDebuggerPlugin::slotDebugExternalProcess(DBusProxy* proxy) { - auto dbusInterface = static_cast(interface); - - QDBusReply reply = dbusInterface->call(QStringLiteral("pid")); + QDBusReply reply = proxy->interface()->call(QStringLiteral("pid")); if (reply.isValid()) { - attachProcess(reply.value()); - QTimer::singleShot(500, this, &MIDebuggerPlugin::slotCloseDrKonqi); - - m_drkonqi = m_drkonqis.key(dbusInterface); + connect(attachProcess(reply.value()), &KJob::result, + proxy, &DBusProxy::debuggingFinished); } core()->uiController()->activeMainWindow()->raise(); } -void MIDebuggerPlugin::slotCloseDrKonqi() -{ - if (!m_drkonqi.isEmpty()) { - QDBusInterface drkonqiInterface(m_drkonqi, QStringLiteral("/MainApplication"), QStringLiteral("org.kde.KApplication")); - drkonqiInterface.call(QStringLiteral("quit")); - m_drkonqi.clear(); - } -} - ContextMenuExtension MIDebuggerPlugin::contextMenuExtension(Context* context, QWidget* parent) { ContextMenuExtension menuExt = IPlugin::contextMenuExtension(context, parent); if (context->type() != KDevelop::Context::EditorContext) return menuExt; EditorContext *econtext = dynamic_cast(context); if (!econtext) return menuExt; QString contextIdent = econtext->currentWord(); if (!contextIdent.isEmpty()) { QString squeezed = KStringHandler::csqueeze(contextIdent, 30); QAction* action = new QAction(parent); action->setText(i18n("Evaluate: %1", squeezed)); action->setWhatsThis(i18n("Evaluate expression" "

Shows the value of the expression under the cursor.

")); connect(action, &QAction::triggered, this, [this, contextIdent](){ emit addWatchVariable(contextIdent); }); menuExt.addAction(ContextMenuExtension::DebugGroup, action); action = new QAction(parent); action->setText(i18n("Watch: %1", squeezed)); action->setWhatsThis(i18n("Watch expression" "

Adds the expression under the cursor to the Variables/Watch list.

")); connect(action, &QAction::triggered, this, [this, contextIdent](){ emit evaluateExpression(contextIdent); }); menuExt.addAction(ContextMenuExtension::DebugGroup, action); } return menuExt; } void MIDebuggerPlugin::slotExamineCore() { showStatusMessage(i18n("Choose a core file to examine..."), 1000); if (core()->debugController()->currentSession() != nullptr) { KMessageBox::ButtonCode answer = KMessageBox::warningYesNo( core()->uiController()->activeMainWindow(), i18n("A program is already being debugged. Do you want to abort the " "currently running debug session and continue?")); if (answer == KMessageBox::No) return; } MIExamineCoreJob *job = new MIExamineCoreJob(this, core()->runController()); core()->runController()->registerJob(job); // job->start() is called in registerJob } #if KF5SysGuard_FOUND void MIDebuggerPlugin::slotAttachProcess() { showStatusMessage(i18n("Choose a process to attach to..."), 1000); if (core()->debugController()->currentSession() != nullptr) { KMessageBox::ButtonCode answer = KMessageBox::warningYesNo( core()->uiController()->activeMainWindow(), i18n("A program is already being debugged. Do you want to abort the " "currently running debug session and continue?")); if (answer == KMessageBox::No) return; } QPointer dlg = new ProcessSelectionDialog(core()->uiController()->activeMainWindow()); if (!dlg->exec() || !dlg->pidSelected()) { delete dlg; return; } // TODO: move check into process selection dialog int pid = dlg->pidSelected(); delete dlg; if (QApplication::applicationPid() == pid) KMessageBox::error(core()->uiController()->activeMainWindow(), i18n("Not attaching to process %1: cannot attach the debugger to itself.", pid)); else attachProcess(pid); } #endif -void MIDebuggerPlugin::attachProcess(int pid) +MIAttachProcessJob* MIDebuggerPlugin::attachProcess(int pid) { MIAttachProcessJob *job = new MIAttachProcessJob(this, pid, core()->runController()); core()->runController()->registerJob(job); // job->start() is called in registerJob + + return job; } QString MIDebuggerPlugin::statusName() const { return i18n("Debugger"); } void MIDebuggerPlugin::showStatusMessage(const QString& msg, int timeout) { emit showMessage(this, msg, timeout); } + +#include "midebuggerplugin.moc" diff --git a/plugins/debuggercommon/midebuggerplugin.h b/plugins/debuggercommon/midebuggerplugin.h index 7591abf4fd..185930f0a3 100644 --- a/plugins/debuggercommon/midebuggerplugin.h +++ b/plugins/debuggercommon/midebuggerplugin.h @@ -1,156 +1,153 @@ /* * Common code for MI debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2007 Hamish Rodda * Copyright 2016 Aetf * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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. If not, see . * */ #ifndef MIDEBUGGERPLUGIN_H #define MIDEBUGGERPLUGIN_H #include #include #include #include #include #include -class QDBusInterface; -class QSignalMapper; class QUrl; namespace KDevelop { class Context; } namespace KDevMI { +class MIAttachProcessJob; class MIDebugSession; +class DBusProxy; class MIDebuggerPlugin : public KDevelop::IPlugin, public KDevelop::IStatus { Q_OBJECT Q_INTERFACES(KDevelop::IStatus) public: MIDebuggerPlugin(const QString& componentName, const QString& displayName, QObject *parent); ~MIDebuggerPlugin() override; void unload() override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; virtual MIDebugSession *createSession() = 0; virtual void setupToolViews() = 0; /** * The implementation should be sure it's safe to call * even when tool views are already unloaded. */ virtual void unloadToolViews() = 0; //BEGIN IStatus public: QString statusName() const override; Q_SIGNALS: void clearMessage(KDevelop::IStatus*) override; void showMessage(KDevelop::IStatus*, const QString & message, int timeout = 0) override; void hideProgress(KDevelop::IStatus*) override; void showProgress(KDevelop::IStatus*, int minimum, int maximum, int value) override; void showErrorMessage(const QString&, int) override; //END IStatus Q_SIGNALS: void reset(); void stopDebugger(); void attachTo(int pid); void coreFile(const QString& core); void runUntil(const QUrl &url, int line); void jumpTo(const QUrl &url, int line); void addWatchVariable(const QString& var); void evaluateExpression(const QString& expr); void raiseDebuggerConsoleViews(); protected Q_SLOTS: - void slotDebugExternalProcess(QObject* interface); + void slotDebugExternalProcess(DBusProxy* proxy); void slotExamineCore(); #if KF5SysGuard_FOUND void slotAttachProcess(); #endif - void slotDBusServiceRegistered(const QString& service); - void slotDBusServiceUnregistered(const QString& service); - void slotCloseDrKonqi(); + void slotDBusOwnerChanged(const QString& service, const QString& oldOwner, const QString& newOwner); protected: - void setupActions(const QString& displayName); + void setupActions(); void setupDBus(); - void attachProcess(int pid); + MIAttachProcessJob* attachProcess(int pid); void showStatusMessage(const QString& msg, int timeout); private: - QHash m_drkonqis; - QSignalMapper* m_drkonqiMap; - QString m_drkonqi; + QHash m_drkonqis; + const QString m_displayName; }; template class DebuggerToolFactory : public KDevelop::IToolViewFactory { public: DebuggerToolFactory(Plugin * plugin, const QString &id, Qt::DockWidgetArea defaultArea) : m_plugin(plugin), m_id(id), m_defaultArea(defaultArea) {} QWidget* create(QWidget *parent = nullptr) override { return new T(m_plugin, parent); } QString id() const override { return m_id; } Qt::DockWidgetArea defaultPosition() override { return m_defaultArea; } void viewCreated(Sublime::View* view) override { if (view->widget()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("requestRaise()")) != -1) QObject::connect(view->widget(), SIGNAL(requestRaise()), view, SLOT(requestRaise())); } private: Plugin * m_plugin; QString m_id; Qt::DockWidgetArea m_defaultArea; }; } // end of namespace KDevMI #endif // MIDEBUGGERPLUGIN_H