kemu: implement runner to control KEmu virtual machines

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2021-08-07 22:12:09 +03:00
parent 31ed908a24
commit 13de02bdd6
11 changed files with 519 additions and 112 deletions

View file

@ -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)

26
kemu/kded/CMakeLists.txt Normal file
View file

@ -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}
)

171
kemu/kded/kded_kemu.cpp Normal file
View file

@ -0,0 +1,171 @@
/* This file is part of the KDE libraries
Copyright (C) 2021 Ivailo Monev <xakepa10@gmail.com>
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 <QUrl>
#include <KDebug>
#include <KLocale>
#include <ksettings.h>
#include "kded_kemu.h"
#include "kpluginfactory.h"
K_PLUGIN_FACTORY(KEmuModuleFactory, registerPlugin<KEmuModule>();)
K_EXPORT_PLUGIN(KEmuModuleFactory("kemu"))
KEmuModule::KEmuModule(QObject *parent, const QList<QVariant>&)
: 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<QProcess*>(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"

55
kemu/kded/kded_kemu.h Normal file
View file

@ -0,0 +1,55 @@
/* This file is part of the KDE libraries
Copyright (C) 2021 Ivailo Monev <xakepa10@gmail.com>
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 <QMap>
#include <QProcess>
#include "kdedmodule.h"
class KEmuModule: public KDEDModule
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kemu")
public:
KEmuModule(QObject *parent, const QList<QVariant>&);
~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<QString,QProcess*> m_machines;
};
#endif // KDED_KEMU_H

11
kemu/kded/kemu.desktop Normal file
View file

@ -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;

View file

@ -25,7 +25,6 @@
#include <KStandardDirs>
#include <KStatusBar>
#include <KDebug>
#include <ksettings.h>
#include <QApplication>
#include <QMessageBox>
#include <QThread>
@ -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<QProcess*>(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();
}

View file

@ -21,9 +21,10 @@
#include <KXmlGuiWindow>
#include <QListWidgetItem>
#include <QSettings>
#include <QProcess>
#include <QCloseEvent>
#include <QDBusInterface>
#include <ksettings.h>
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<QString,QProcess*> m_machines;
KSettings *m_settings;
QDBusInterface *m_interface;
};
#endif // KEMUMAINWINDOW_H

View file

@ -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}
)

View file

@ -0,0 +1,115 @@
/* This file is part of the KDE libraries
Copyright (C) 2021 Ivailo Monev <xakepa10@gmail.com>
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 <QDBusInterface>
#include <QDBusReply>
#include <KDebug>
#include <KIcon>
#include <KMessageBox>
#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<Plasma::RunnerSyntax> 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<bool> 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<bool> 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"

View file

@ -0,0 +1,44 @@
/* This file is part of the KDE libraries
Copyright (C) 2021 Ivailo Monev <xakepa10@gmail.com>
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 <plasma/abstractrunner.h>
#include <KIcon>
#include <KUrl>
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

View file

@ -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