diff --git a/kemu/CMakeLists.txt b/kemu/CMakeLists.txt index 9f94de86..eea54d9d 100644 --- a/kemu/CMakeLists.txt +++ b/kemu/CMakeLists.txt @@ -13,8 +13,15 @@ set(kemu_sources ) add_executable(kemu ${kemu_sources}) -target_link_libraries(kemu ${KDE4_KDEUI_LIBS} ${KDE4_KFILE_LIBS}) +target_link_libraries(kemu + ${KDE4_KDEUI_LIBS} + ${KDE4_KFILE_LIBS} + ${QT_QTDBUS_LIBRARY} +) install(TARGETS kemu DESTINATION ${KDE4_BIN_INSTALL_DIR}) install(FILES kemuui.rc DESTINATION ${KDE4_DATA_INSTALL_DIR}/kemu) install(PROGRAMS kemu.desktop DESTINATION ${KDE4_XDG_APPS_INSTALL_DIR}) + +add_subdirectory(kded) +add_subdirectory(krunner) diff --git a/kemu/kded/CMakeLists.txt b/kemu/kded/CMakeLists.txt new file mode 100644 index 00000000..abf97f2c --- /dev/null +++ b/kemu/kded/CMakeLists.txt @@ -0,0 +1,26 @@ +set(kded_kemu_SRCS + kded_kemu.cpp + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kemu.xml +) + +qt4_generate_dbus_interface(kded_kemu.h org.kde.kemu.xml) + +kde4_add_plugin(kded_kemu ${kded_kemu_SRCS}) +target_link_libraries(kded_kemu ${KDE4_KDECORE_LIBS}) + +install( + TARGETS kded_kemu + DESTINATION ${KDE4_PLUGIN_INSTALL_DIR} +) + +install( + FILES kemu.desktop + DESTINATION ${KDE4_SERVICES_INSTALL_DIR}/kded +) + +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kemu.xml + DESTINATION ${KDE4_DBUS_INTERFACES_INSTALL_DIR} +) + + diff --git a/kemu/kded/kded_kemu.cpp b/kemu/kded/kded_kemu.cpp new file mode 100644 index 00000000..ede10acf --- /dev/null +++ b/kemu/kded/kded_kemu.cpp @@ -0,0 +1,171 @@ +/* This file is part of the KDE libraries + Copyright (C) 2021 Ivailo Monev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include + +#include "kded_kemu.h" +#include "kpluginfactory.h" + +K_PLUGIN_FACTORY(KEmuModuleFactory, registerPlugin();) +K_EXPORT_PLUGIN(KEmuModuleFactory("kemu")) + +KEmuModule::KEmuModule(QObject *parent, const QList&) + : KDEDModule(parent) +{ +} + +KEmuModule::~KEmuModule() +{ + foreach(QProcess* machineProcess, m_machines) { + const QString machine = m_machines.key(machineProcess); + kDebug() << "stopping machine" << machine; + machineProcess->terminate(); + machineProcess->deleteLater(); + m_machines.remove(machine); + } +} + +bool KEmuModule::start(const QString &machine) +{ + if (m_machines.contains(machine)) { + emit error(i18n("Machine is already running: %1", machine)); + return false; + } + + KSettings settings("kemu", KSettings::SimpleConfig); + const bool enable = settings.value(machine + "/enable").toBool(); + if (!enable) { + emit error(i18n("Machine is not enabled: %1", machine)); + return false; + } + + kDebug() << "loading machine settings" << machine; + const QUrl cdrom = settings.value(machine + "/cdrom").toUrl(); + const QUrl harddisk = settings.value(machine + "/harddisk").toUrl(); + const QString system = settings.value(machine + "/system").toString(); + const QString video = settings.value(machine + "/video", "virtio").toString(); + const QString audio = settings.value(machine + "/audio", "ac97").toString(); + const int ram = settings.value(machine + "/ram", 512).toInt(); + const int cpu = settings.value(machine + "/cpu", 1).toInt(); + const bool kvm = settings.value(machine + "/kvm", false).toBool(); + const bool acpi = settings.value(machine + "/acpi", false).toBool(); + const QString args = settings.value(machine + "/args").toString(); + + if (cdrom.isEmpty() && harddisk.isEmpty()) { + emit error(i18n("Either CD-ROM or Hard Disk image must be set")); + return false; + } + + kDebug() << "starting machine" << machine; + QStringList machineArgs; + machineArgs << "-name" << machine; + if (!cdrom.isEmpty()) { + machineArgs << "-cdrom" << cdrom.toString(); + } + if (!harddisk.isEmpty()) { + machineArgs << "-hda" << harddisk.toString(); + } + machineArgs << "-vga" << video; + machineArgs << "-soundhw" << audio; + machineArgs << "-m" << QString::number(ram); + machineArgs << "-smp" << QString::number(cpu); + if (kvm) { + machineArgs << "-enable-kvm"; + } + if (!acpi) { + machineArgs << "-no-acpi"; + } + if (!args.isEmpty()) { + foreach (const QString argument, args.split(" ")) { + machineArgs << argument; + } + } + + QProcess* machineProcess = new QProcess(this); + machineProcess->setProcessChannelMode(QProcess::MergedChannels); + m_machines.insert(machine, machineProcess); + connect(machineProcess, SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(_machineFinished(int,QProcess::ExitStatus))); + machineProcess->start(system, machineArgs); + const bool success = machineProcess->waitForStarted(); + emit started(machine); + return success; +} + +bool KEmuModule::stop(const QString &machine) +{ + if (!m_machines.contains(machine)) { + emit error(i18n("Machine is not running: %1", machine)); + return false; + } + + kDebug() << "stopping machine" << machine; + QProcess* machineProcess = m_machines.take(machine); + machineProcess->terminate(); + machineProcess->deleteLater(); + return true; +} + +bool KEmuModule::isRunning(const QString &machine) const +{ + return m_machines.contains(machine); +} + +QStringList KEmuModule::running() const +{ + return m_machines.keys(); +} + +QStringList KEmuModule::machines() const +{ + QStringList addedMachines; + KSettings settings("kemu", KSettings::SimpleConfig); + foreach(const QString key, settings.keys()) { + const int sepindex = key.indexOf("/"); + if (sepindex < 1) { + continue; + } + QString machine = key.left(sepindex); + if (addedMachines.contains(machine)) { + continue; + } + if (settings.value(machine + "/enable").toBool() == true) { + addedMachines.append(machine); + } else { + kDebug() << "garbage machine" << machine; + } + } + return addedMachines; +} + +void KEmuModule::_machineFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + QProcess *machineProcess = qobject_cast(sender()); + disconnect(machineProcess, SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(_machineFinished(int,QProcess::ExitStatus))); + emit stopped(exitCode, QString(machineProcess->readAll())); + + const QString machine = m_machines.key(machineProcess); + m_machines.remove(machine); + machineProcess->deleteLater(); +} + +#include "moc_kded_kemu.cpp" diff --git a/kemu/kded/kded_kemu.h b/kemu/kded/kded_kemu.h new file mode 100644 index 00000000..d635e176 --- /dev/null +++ b/kemu/kded/kded_kemu.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE libraries + Copyright (C) 2021 Ivailo Monev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDED_KEMU_H +#define KDED_KEMU_H + +#include +#include + +#include "kdedmodule.h" + +class KEmuModule: public KDEDModule +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kemu") + +public: + KEmuModule(QObject *parent, const QList&); + ~KEmuModule(); + +public Q_SLOTS: + Q_SCRIPTABLE bool start(const QString &machine); + Q_SCRIPTABLE bool stop(const QString &machine); + + Q_SCRIPTABLE bool isRunning(const QString &machine) const; + Q_SCRIPTABLE QStringList running() const; + Q_SCRIPTABLE QStringList machines() const; + +Q_SIGNALS: + Q_SCRIPTABLE void started(const QString &machine) const; + Q_SCRIPTABLE void stopped(int status, const QString &error) const; + Q_SCRIPTABLE void error(const QString &error) const; + +private Q_SLOTS: + void _machineFinished(int exitCode, QProcess::ExitStatus exitStatus); + +private: + QMap m_machines; +}; +#endif // KDED_KEMU_H diff --git a/kemu/kded/kemu.desktop b/kemu/kded/kemu.desktop new file mode 100644 index 00000000..c86fde2a --- /dev/null +++ b/kemu/kded/kemu.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Icon=applications-engineering +Name=QEMU virtual machine manager +Comment=QEMU virtual machine manager service +Type=Service +X-KDE-ServiceTypes=KDEDModule +X-KDE-Library=kemu +X-KDE-DBus-ModuleName=kemu +X-KDE-Kded-autoload=false +X-KDE-Kded-load-on-demand=true +OnlyShowIn=KDE; diff --git a/kemu/kemumainwindow.cpp b/kemu/kemumainwindow.cpp index 1d3acd2b..940b7dca 100644 --- a/kemu/kemumainwindow.cpp +++ b/kemu/kemumainwindow.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -34,7 +33,8 @@ #include "ui_kemu.h" KEmuMainWindow::KEmuMainWindow(QWidget *parent, Qt::WindowFlags flags) - : KXmlGuiWindow(parent, flags), m_loading(false), m_installed(false), m_kemuui(new Ui_KEmuWindow) + : KXmlGuiWindow(parent, flags), m_loading(false), m_installed(false), m_kemuui(new Ui_KEmuWindow), + m_interface(new QDBusInterface("org.kde.kded", "/modules/kemu", "org.kde.kemu")) { m_kemuui->setupUi(this); @@ -84,9 +84,6 @@ KEmuMainWindow::KEmuMainWindow(QWidget *parent, Qt::WindowFlags flags) } m_settings = new KSettings("kemu", KSettings::SimpleConfig); -#ifndef QT_KATIE - foreach(const QString machine, m_settings->childGroups()) { -#else QStringList addedMachines; foreach(const QString key, m_settings->keys()) { const int sepindex = key.indexOf("/"); @@ -98,7 +95,6 @@ KEmuMainWindow::KEmuMainWindow(QWidget *parent, Qt::WindowFlags flags) continue; } addedMachines.append(machine); -#endif if (m_settings->value(machine + "/enable").toBool() == true) { m_kemuui->machinesList->insertItem(machine); machineLoad(machine); @@ -142,6 +138,10 @@ KEmuMainWindow::KEmuMainWindow(QWidget *parent, Qt::WindowFlags flags) connect(m_kemuui->KVMCheckBox, SIGNAL(stateChanged(int)), this, SLOT(machineSave(int))); connect(m_kemuui->ACPICheckBox, SIGNAL(stateChanged(int)), this, SLOT(machineSave(int))); connect(m_kemuui->argumentsLineEdit, SIGNAL(textChanged(QString)), this, SLOT(machineSave(QString))); + + connect(m_interface, SIGNAL(started(QString)), this, SLOT(machineStarted(QString))); + connect(m_interface, SIGNAL(stopped(int,QString)), this, SLOT(machineStopped(int,QString))); + connect(m_interface, SIGNAL(error(QString)), this, SLOT(machineError(QString))); } KEmuMainWindow::~KEmuMainWindow() @@ -150,16 +150,9 @@ KEmuMainWindow::~KEmuMainWindow() const QString lastSelected = m_kemuui->machinesList->currentText(); if (!lastSelected.isEmpty()) { m_settings->setValue("lastselected", lastSelected); + m_settings->sync(); } - m_settings->sync(); m_settings->deleteLater(); - foreach(QProcess* machineProcess, m_machines) { - const QString machine = m_machines.key(machineProcess); - kDebug() << "stopping machine" << machine; - machineProcess->terminate(); - machineProcess->deleteLater(); - m_machines.remove(machine); - } delete m_kemuui; } @@ -198,30 +191,15 @@ void KEmuMainWindow::quit() qApp->quit(); } -void KEmuMainWindow::closeEvent(QCloseEvent *event) -{ - const int running = m_machines.size(); - if (running != 0) { - const QMessageBox::StandardButton answer = QMessageBox::question(this, - i18n("Stop machines and quit?"), - i18n("There are still %1 machines running, do you really want to quit?", running), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - if (answer != QMessageBox::Yes) { - event->ignore(); - return; - } - } - event->accept(); -} - void KEmuMainWindow::updateStatus() { - if (m_machines.size() == 0) { + const QStringList running = m_interface->call("running").arguments().at(0).toStringList(); + if (running.size() == 0) { statusBar()->showMessage(i18n("No machines running")); } else { QString machineNames; bool firstMachine = true; - foreach (const QString name, m_machines.keys()) { + foreach (const QString name, running) { if (firstMachine) { machineNames += name; firstMachine = false; @@ -229,9 +207,21 @@ void KEmuMainWindow::updateStatus() machineNames += ", " + name; } } - const QString statusText = i18n("Machines running: %1 (%2)", machineNames, m_machines.size()); + const QString statusText = i18n("Machines running: %1 (%2)", machineNames, running.size()); statusBar()->showMessage(statusText); } + + const QString machine = m_kemuui->machinesList->currentText(); + const bool isrunning = m_interface->call("isRunning", machine).arguments().at(0).toBool(); + if (isrunning) { + kDebug() << "machine is running" << machine; + m_kemuui->startStopButton->setText(i18n("Stop")); + m_kemuui->startStopButton->setIcon(KIcon("system-shutdown")); + } else { + kDebug() << "machine is stopped" << machine; + m_kemuui->startStopButton->setText(i18n("Start")); + m_kemuui->startStopButton->setIcon(KIcon("system-run")); + } } void KEmuMainWindow::machineSave(const QString ignored) @@ -261,7 +251,6 @@ void KEmuMainWindow::machineSave(int ignored) machineSave(QString()); } - void KEmuMainWindow::machineLoad(const QString machine) { m_loading = true; @@ -283,6 +272,8 @@ void KEmuMainWindow::machineLoad(const QString machine) m_kemuui->ACPICheckBox->setChecked(m_settings->value(machine + "/acpi", false).toBool()); m_kemuui->argumentsLineEdit->setText(m_settings->value(machine + "/args").toString()); m_loading = false; + + updateStatus(); } void KEmuMainWindow::machineChanged(QItemSelection ignored, QItemSelection ignored2) @@ -296,15 +287,6 @@ void KEmuMainWindow::machineChanged(QItemSelection ignored, QItemSelection ignor m_kemuui->startStopButton->setEnabled(m_installed); m_kemuui->groupBox->setEnabled(true); - if (m_machines.contains(machine)) { - kDebug() << "machine is running" << machine; - m_kemuui->startStopButton->setText(i18n("Stop")); - m_kemuui->startStopButton->setIcon(KIcon("system-shutdown")); - } else { - kDebug() << "machine is stopped" << machine; - m_kemuui->startStopButton->setText(i18n("Start")); - m_kemuui->startStopButton->setIcon(KIcon("system-run")); - } machineLoad(machine); } else { m_kemuui->startStopButton->setEnabled(false); @@ -312,24 +294,30 @@ void KEmuMainWindow::machineChanged(QItemSelection ignored, QItemSelection ignor } } -void KEmuMainWindow::machineFinished(int exitCode, QProcess::ExitStatus exitStatus) +void KEmuMainWindow::machineStarted(const QString machine) +{ + Q_UNUSED(machine); + + updateStatus(); +} + +void KEmuMainWindow::machineStopped(int exitCode, const QString error) { - QProcess *machineProcess = qobject_cast(sender()); - disconnect(machineProcess, SIGNAL(finished(int,QProcess::ExitStatus)), - this, SLOT(machineFinished(int,QProcess::ExitStatus))); if (exitCode != 0) { QMessageBox::warning(this, i18n("QEMU error"), - i18n("An error occured:\n%1", QString(machineProcess->readAll()))); + i18n("An error occured:\n%1", error)); } - m_kemuui->startStopButton->setText(i18n("Start")); - m_kemuui->startStopButton->setIcon(KIcon("system-run")); - const QString machine = m_machines.key(machineProcess); - m_machines.remove(machine); - machineProcess->deleteLater(); updateStatus(); } +void KEmuMainWindow::machineError(const QString error) +{ + QMessageBox::warning(this, i18n("KEmu error"), + i18n("An error occured:\n%1", error)); + updateStatus(); +} + void KEmuMainWindow::addMachine(const QString machine) { m_settings->setValue(machine + "/enable", true); @@ -341,69 +329,27 @@ void KEmuMainWindow::startStopMachine() { const QString machine = m_kemuui->machinesList->currentText(); if (!machine.isEmpty()) { - if (m_machines.contains(machine)) { + const bool isrunning = m_interface->call("isRunning", machine).arguments().at(0).toBool(); + if (isrunning) { kDebug() << "stopping machine" << machine; - QProcess* machineProcess = m_machines.take(machine); - machineProcess->terminate(); - machineProcess->deleteLater(); - m_kemuui->startStopButton->setText(i18n("Start")); - m_kemuui->startStopButton->setIcon(KIcon("system-run")); + m_interface->call("stop", machine); } else { kDebug() << "starting machine" << machine; - QStringList machineArgs; - machineArgs << "-name" << machine; - const QString CDRom = m_kemuui->CDROMInput->url().prettyUrl(); - if (!CDRom.isEmpty()) { - machineArgs << "-cdrom" << CDRom; - } - const QString HardDisk = m_kemuui->HardDiskInput->url().prettyUrl(); - if (!HardDisk.isEmpty()) { - machineArgs << "-hda" << HardDisk; - } - if (CDRom.isEmpty() && HardDisk.isEmpty()) { - QMessageBox::warning(this, i18n("Requirements not met"), - i18n("Either CD-ROM or Hard Disk image must be set")); - return; - } - machineArgs << "-vga" << m_kemuui->videoComboBox->currentText(); - machineArgs << "-soundhw" << m_kemuui->audioComboBox->currentText(); - machineArgs << "-m" << QString::number(m_kemuui->RAMInput->value()); - machineArgs << "-smp" << QString::number(m_kemuui->CPUInput->value()); - if (m_kemuui->KVMCheckBox->isEnabled() && m_kemuui->KVMCheckBox->isChecked()) { - machineArgs << "-enable-kvm"; - } - if (!m_kemuui->ACPICheckBox->isChecked()) { - machineArgs << "-no-acpi"; - } - const QString extraArgs = m_kemuui->argumentsLineEdit->text(); - if (!extraArgs.isEmpty()) { - foreach (const QString argument, extraArgs.split(" ")) { - machineArgs << argument; - } - } - QProcess* machineProcess = new QProcess(this); - machineProcess->setProcessChannelMode(QProcess::MergedChannels); - m_kemuui->startStopButton->setText(i18n("Stop")); - m_kemuui->startStopButton->setIcon(KIcon("system-shutdown")); - m_machines.insert(machine, machineProcess); - connect(machineProcess, SIGNAL(finished(int,QProcess::ExitStatus)), - this, SLOT(machineFinished(int,QProcess::ExitStatus))); - machineProcess->start(m_kemuui->systemComboBox->currentText(), machineArgs); + m_interface->call("start", machine); } - - updateStatus(); } } void KEmuMainWindow::removeMachine(const QString machine) { - if (m_machines.contains(machine)) { + const bool isrunning = m_interface->call("isRunning", machine).arguments().at(0).toBool(); + if (isrunning) { kDebug() << "stopping machine" << machine; - QProcess* machineProcess = m_machines.take(machine); - machineProcess->terminate(); - machineProcess->deleteLater(); + m_interface->call("stop", machine); } kDebug() << "removing machine" << machine; m_settings->setValue(machine + "/enable", false); m_settings->sync(); + + updateStatus(); } diff --git a/kemu/kemumainwindow.h b/kemu/kemumainwindow.h index d3153f8b..e7e02e65 100644 --- a/kemu/kemumainwindow.h +++ b/kemu/kemumainwindow.h @@ -21,9 +21,10 @@ #include #include -#include #include #include +#include +#include QT_BEGIN_NAMESPACE class Ui_KEmuWindow; @@ -40,15 +41,14 @@ public slots: void createHardDisk(); void quit(); -protected: - virtual void closeEvent(QCloseEvent *event); - private slots: void machineLoad(const QString machine); void machineSave(const QString ignored); void machineSave(int ignored); void machineChanged(QItemSelection ignored, QItemSelection ignored2); - void machineFinished(int exitCode, QProcess::ExitStatus exitStatus); + void machineStarted(const QString machine); + void machineStopped(int exitCode, const QString error); + void machineError(const QString error); void addMachine(const QString machine); void startStopMachine(); void removeMachine(const QString machine); @@ -59,8 +59,8 @@ private: bool m_loading; bool m_installed; Ui_KEmuWindow *m_kemuui; - QSettings *m_settings; - QHash m_machines; + KSettings *m_settings; + QDBusInterface *m_interface; }; #endif // KEMUMAINWINDOW_H \ No newline at end of file diff --git a/kemu/krunner/CMakeLists.txt b/kemu/krunner/CMakeLists.txt new file mode 100644 index 00000000..4bc5e29e --- /dev/null +++ b/kemu/krunner/CMakeLists.txt @@ -0,0 +1,19 @@ +set(krunner_kemu_SRCS + krunner_kemu.cpp +) + +kde4_add_plugin(krunner_kemu ${krunner_kemu_SRCS}) +target_link_libraries(krunner_kemu + ${KDE4_KDEUI_LIBS} + ${KDE4_PLASMA_LIBS} +) + +install( + TARGETS krunner_kemu + DESTINATION ${KDE4_PLUGIN_INSTALL_DIR} +) + +install( + FILES plasma-runner-kemu.desktop + DESTINATION ${KDE4_SERVICES_INSTALL_DIR} +) diff --git a/kemu/krunner/krunner_kemu.cpp b/kemu/krunner/krunner_kemu.cpp new file mode 100644 index 00000000..cee07acf --- /dev/null +++ b/kemu/krunner/krunner_kemu.cpp @@ -0,0 +1,115 @@ +/* This file is part of the KDE libraries + Copyright (C) 2021 Ivailo Monev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include + +#include "krunner_kemu.h" + +KEmuControlRunner::KEmuControlRunner(QObject *parent, const QVariantList& args) + : Plasma::AbstractRunner(parent, args) +{ + Q_UNUSED(args); + + setObjectName(QLatin1String("QEMU virtual machine manager runner")); + setSpeed(AbstractRunner::SlowSpeed); + + connect(this, SIGNAL(prepare()), this, SLOT(prep())); +} + +KEmuControlRunner::~KEmuControlRunner() +{ +} + +void KEmuControlRunner::prep() +{ + QList syntaxes; + + syntaxes << Plasma::RunnerSyntax("vm start :q:", i18n("Starts virtual machine")); + syntaxes << Plasma::RunnerSyntax("vm stop :q:", i18n("Stops virtual machine")); + + setSyntaxes(syntaxes); +} + +void KEmuControlRunner::match(Plasma::RunnerContext &context) +{ + const QString term = context.query(); + if (term.length() < 7) { + return; + } + + QDBusInterface interface("org.kde.kded", "/modules/kemu", "org.kde.kemu"); + const QStringList machines = interface.call("machines").arguments().at(0).toStringList(); + if (term.startsWith("vm start")) { + foreach (const QString &machine, machines) { + const bool isrunning = interface.call("isRunning", machine).arguments().at(0).toBool(); + if (!isrunning) { + Plasma::QueryMatch match(this); + match.setType(Plasma::QueryMatch::PossibleMatch); + match.setIcon(KIcon(QLatin1String("system-run"))); + match.setText(i18n("Start %1 virtual machine", machine)); + match.setData(QStringList() << "start" << machine); + context.addMatch(term, match); + } + } + } else if (term.startsWith("vm stop")) { + foreach (const QString &machine, machines) { + const bool isrunning = interface.call("isRunning", machine).arguments().at(0).toBool(); + if (isrunning) { + Plasma::QueryMatch match(this); + match.setType(Plasma::QueryMatch::PossibleMatch); + match.setIcon(KIcon(QLatin1String("system-shutdown"))); + match.setText(i18n("Stop %1 virtual machine", machine)); + match.setData(QStringList() << "stop" << machine); + context.addMatch(term, match); + } + } + } +} + +void KEmuControlRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) +{ + Q_UNUSED(context) + + const QStringList data = match.data().toStringList(); + Q_ASSERT(data.size() == 2); + const QString command = data.at(0); + const QString machine = data.at(1); + + QDBusInterface interface("org.kde.kded", "/modules/kemu", "org.kde.kemu"); + if (command == "start") { + QDBusReply reply = interface.call("start", machine); + if (!reply.value()) { + KMessageBox::error(nullptr, i18n("Unable to start VM"), + i18n("Unable to start %1 virtual machine.", machine)); + } + } else if (command == "stop") { + QDBusReply reply = interface.call("stop", machine); + if (!reply.value()) { + KMessageBox::error(nullptr, i18n("Unable to stop VM"), + i18n("Unable to stop %1 virtual machine.", machine)); + } + } else { + kWarning() << "invalid command" << command; + } +} + +#include "moc_krunner_kemu.cpp" diff --git a/kemu/krunner/krunner_kemu.h b/kemu/krunner/krunner_kemu.h new file mode 100644 index 00000000..d4c6fff3 --- /dev/null +++ b/kemu/krunner/krunner_kemu.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE libraries + Copyright (C) 2021 Ivailo Monev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KRUNNER_KEMU_H +#define KRUNNER_KEMU_H + +#include + +#include +#include + +class KEmuControlRunner : public Plasma::AbstractRunner +{ + Q_OBJECT + +public: + KEmuControlRunner(QObject *parent, const QVariantList& args); + ~KEmuControlRunner(); + + void match(Plasma::RunnerContext &context); + void run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match); + +private slots: + void prep(); +}; + +K_EXPORT_PLASMA_RUNNER(kemucontrol, KEmuControlRunner) + +#endif diff --git a/kemu/krunner/plasma-runner-kemu.desktop b/kemu/krunner/plasma-runner-kemu.desktop new file mode 100644 index 00000000..3aad2439 --- /dev/null +++ b/kemu/krunner/plasma-runner-kemu.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Name=Control QEMU virtual machines +Comment=Allows to control virtual machines +X-KDE-ServiceTypes=Plasma/Runner +Type=Service +Icon=applications-engineering +X-KDE-Library=krunner_kemu +X-KDE-PluginInfo-Author=Ivailo Monev +X-KDE-PluginInfo-Email=xakepa10@gmail.com +X-KDE-PluginInfo-Name=QEMU virtual machine manager runner +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true