This repository has been archived on 2026-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
ollie-kde/krunner/ollie_runner.cpp

248 lines
8.2 KiB
C++

#include "ollie_runner.h"
#include <KPluginFactory>
#include <QDBusReply>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDialog>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTextEdit>
#include <QPushButton>
#include <QClipboard>
#include <QApplication>
#include <QLabel>
#include <QProcess>
#include <QThread>
K_PLUGIN_CLASS_WITH_JSON(OllieRunner, "metadata.json")
static const char *SERVICE = "org.ollie.SessionManager";
static const char *OBJ_PATH = "/org/ollie/SessionManager";
static const char *IFACE = "org.ollie.SessionManager";
#if QT_VERSION_MAJOR >= 6
OllieRunner::OllieRunner(QObject *parent, const KPluginMetaData &data)
: AbstractRunner(parent, data)
#else
OllieRunner::OllieRunner(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
: AbstractRunner(parent, data, args)
#endif
{
m_iface = new QDBusInterface(SERVICE, OBJ_PATH, IFACE,
QDBusConnection::sessionBus(), this);
}
void OllieRunner::match(RunnerNS::RunnerContext &context)
{
const QString query = context.query().trimmed();
// Only activate on "ollie" prefix
if (!query.startsWith(QLatin1String("ollie ")) && query != QLatin1String("ollie")) {
return;
}
// Bail if daemon isn't running
if (!m_iface->isValid()) {
return;
}
QString args = query.mid(6).trimmed(); // everything after "ollie "
if (args.isEmpty() || args == QLatin1String("list")) {
// List sessions
QStringList sessions = listSessions();
if (sessions.isEmpty()) {
RunnerNS::QueryMatch match(this);
match.setId("ollie-no-sessions");
match.setText("No active sessions");
match.setIconName("dialog-information");
match.setRelevance(0.5);
context.addMatch(match);
} else {
for (const QString &entry : sessions) {
QStringList parts = entry.split('\t');
if (parts.size() < 4) continue;
RunnerNS::QueryMatch match(this);
match.setId("ollie-session-" + parts[0]);
QString state = parts[1] == "idle" ? "○" : "●";
match.setText(state + " " + parts[0].left(14) + " [" + parts[3] + "]");
match.setSubtext(parts[2] + " — " + parts[1]);
match.setIconName("system-run");
match.setRelevance(0.7);
match.setData(parts[0]); // session ID
context.addMatch(match);
}
}
return;
}
if (args.startsWith(QLatin1String("kill "))) {
QString target = args.mid(5).trimmed();
RunnerNS::QueryMatch match(this);
match.setId("ollie-kill-" + target);
match.setText("Kill session: " + target);
match.setSubtext("Terminate ollie session");
match.setIconName("process-stop");
match.setRelevance(0.8);
match.setData("kill:" + target);
context.addMatch(match);
return;
}
if (args.startsWith(QLatin1String("rename "))) {
// Expected: rename <prefix> <new_name>
// Prefix-matches against listed sessions so full ID isn't needed
QString rest = args.mid(7).trimmed();
int space = rest.indexOf(' ');
if (space > 0) {
QString prefix = rest.left(space);
QString newName = rest.mid(space + 1).trimmed();
if (!newName.isEmpty()) {
// Find sessions matching the prefix
QStringList sessions = listSessions();
for (const QString &entry : sessions) {
QStringList parts = entry.split('\t');
if (parts.isEmpty()) continue;
QString id = parts[0];
if (id.startsWith(prefix) || id == prefix) {
RunnerNS::QueryMatch match(this);
match.setId("ollie-rename-" + id);
match.setText("Rename: " + id + " → " + newName);
match.setSubtext("Rename ollie session");
match.setIconName("edit-rename");
match.setRelevance(0.8);
match.setData("rename:" + id + ":" + newName);
context.addMatch(match);
}
}
}
}
return;
}
// Default: one-shot prompt
RunnerNS::QueryMatch match(this);
match.setId("ollie-prompt");
match.setText("Ask Ollie: " + args);
match.setSubtext("One-shot prompt — result via notification");
match.setIconName("system-run");
match.setRelevance(0.9);
match.setData("prompt:" + args);
context.addMatch(match);
}
void OllieRunner::run(const RunnerNS::RunnerContext &context, const RunnerNS::QueryMatch &match)
{
Q_UNUSED(context);
QString data = match.data().toString();
if (data.startsWith("kill:")) {
QString id = data.mid(5);
killSession(id);
return;
}
if (data.startsWith("rename:")) {
// format: rename:<id>:<new_name>
QString rest = data.mid(7);
int sep = rest.indexOf(':');
if (sep > 0) {
QString id = rest.left(sep);
QString newName = rest.mid(sep + 1);
m_iface->call("RenameSession", id, newName);
}
return;
}
if (data.startsWith("prompt:")) {
QString prompt = data.mid(7);
QString result = oneShotRun(prompt);
// Show popup window with result
auto *dialog = new QDialog(nullptr);
dialog->setWindowTitle("Ollie — " + prompt.left(40));
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->resize(520, 380);
auto *layout = new QVBoxLayout(dialog);
auto *textEdit = new QTextEdit(dialog);
textEdit->setReadOnly(true);
textEdit->setPlainText(result);
QFont monoFont("monospace");
monoFont.setPointSize(QApplication::font().pointSize());
textEdit->setFont(monoFont);
layout->addWidget(textEdit);
auto *buttonLayout = new QHBoxLayout();
buttonLayout->addStretch();
auto *copyBtn = new QPushButton("Copy", dialog);
QObject::connect(copyBtn, &QPushButton::clicked, [result]() {
QApplication::clipboard()->setText(result);
});
buttonLayout->addWidget(copyBtn);
auto *closeBtn = new QPushButton("Close", dialog);
QObject::connect(closeBtn, &QPushButton::clicked, dialog, &QDialog::close);
buttonLayout->addWidget(closeBtn);
layout->addLayout(buttonLayout);
dialog->show();
dialog->raise();
dialog->activateWindow();
return;
}
// Clicked a session from list — open it in ollie-gui
QString sessionId = data;
QDBusInterface gui("org.ollie.Gui", "/org/ollie/Gui", "org.ollie.Gui",
QDBusConnection::sessionBus());
if (gui.isValid()) {
gui.call(QDBus::NoBlock, "SelectSession", sessionId);
} else {
// GUI not running — launch it, then ask it to select the session
QProcess::startDetached("ollie-gui", {});
// Poll for D-Bus registration (up to 3 seconds)
for (int i = 0; i < 30; ++i) {
QThread::msleep(100);
QDBusInterface retry("org.ollie.Gui", "/org/ollie/Gui", "org.ollie.Gui",
QDBusConnection::sessionBus());
if (retry.isValid()) {
retry.call(QDBus::NoBlock, "SelectSession", sessionId);
break;
}
}
}
}
// --- D-Bus helpers ---
QString OllieRunner::oneShotRun(const QString &prompt)
{
// Use the Generate one-shot D-Bus method — no session needed.
QDBusReply<QString> reply = m_iface->call("Generate", prompt, QString(), QString(), QString());
if (!reply.isValid()) {
return "Error: " + reply.error().message();
}
QString result = reply.value().trimmed();
return result.isEmpty() ? "(no response)" : result;
}
QStringList OllieRunner::listSessions()
{
QDBusReply<QStringList> reply = m_iface->call("ListSessions");
if (reply.isValid()) return reply.value();
return {};
}
void OllieRunner::killSession(const QString &id)
{
m_iface->call("KillSession", id);
}
#include "ollie_runner.moc"