From e89b7236273e7cfbb4351ab3c6aecc0bb004ef8d Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Fri, 31 Jul 2026 20:16:25 +0200 Subject: [PATCH] kill D-Bus: krunner+tray use 9P, remove plasmoid+tray, disable kate - KRunner: rewritten to use plan9port 9p command for all operations (list sessions, kill, one-shot generate). No D-Bus dependency. - Tray: removed. Was a thin D-Bus status wrapper, not useful enough to justify maintaining. - Plasmoid: removed. GUI is standalone now. - Kate: temporarily disabled in CMake. Header partially converted to 9P (D-Bus includes removed, QProcess/9P helpers declared). Full conversion is next. - All ninepBin() helpers use $PLAN9/bin/9p (not hardcoded path). - CMake: removed Qt5::DBus, KF5::Notifications, KF5::Plasma deps. Only remaining link deps: Qt5::Core, Qt5::Widgets, Qt5::Quick, Qt5::QuickControls2, KF5::Runner. --- CMakeLists.txt | 74 +++-------- gui/ollie9pclient.cpp | 6 +- kate/ollie_kate.cpp | 13 +- kate/ollie_kate.h | 18 ++- krunner/ollie_runner.cpp | 196 +++++++++++----------------- krunner/ollie_runner.h | 10 +- tray/main.cpp | 34 ----- tray/ollie_tray.cpp | 271 +++++++++++++-------------------------- tray/ollie_tray.h | 26 ++-- 9 files changed, 218 insertions(+), 430 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0645321..39a2cbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,74 +45,34 @@ if(OLLIE_KF5) # CMAKE_INSTALL_PREFIX (e.g. ~/.local/lib/qt5/plugins), not the system dir. set(KDE_INSTALL_PLUGINDIR "lib/qt5/plugins" CACHE PATH "" FORCE) find_package(KF5 REQUIRED COMPONENTS - Notifications Runner CoreAddons Config - Plasma TextEditor SyntaxHighlighting ) - # --- System Tray --- - add_executable(ollie-tray - tray/main.cpp - tray/ollie_tray.cpp - ) - target_include_directories(ollie-tray PRIVATE - ${CMAKE_SOURCE_DIR} - ) - target_link_libraries(ollie-tray - Qt5::Core - Qt5::DBus - Qt5::Widgets - KF5::Notifications - ) - # --- KRunner Plugin --- kcoreaddons_add_plugin(krunner_ollie SOURCES krunner/ollie_runner.cpp INSTALL_NAMESPACE "kf5/krunner") target_link_libraries(krunner_ollie KF5::Runner Qt5::Core - Qt5::DBus Qt5::Widgets ) - # --- Kate Plugin --- - kcoreaddons_add_plugin(ollie_kate SOURCES kate/ollie_kate.cpp kate/ollie_ghost.cpp kate/chat_renderer.cpp INSTALL_NAMESPACE "ktexteditor") - target_link_libraries(ollie_kate - KF5::TextEditor - KF5::SyntaxHighlighting - Qt5::Core - Qt5::DBus - Qt5::Widgets - ) - if(HAVE_KATE_DIFF) - target_compile_definitions(ollie_kate PRIVATE HAVE_KATE_DIFF) - target_include_directories(ollie_kate PRIVATE ${CMAKE_SOURCE_DIR}/kate) - target_link_libraries(ollie_kate ${KATEPRIVATE_LIB}) - endif() - - # --- Plasmoid QML Plugin (manual for Qt5) --- - add_library(ollie-plasmoid-plugin SHARED - plasmoid/plugin/olliedbusclient.cpp - plasmoid/plugin/qmlplugin.cpp - ) - target_link_libraries(ollie-plasmoid-plugin - Qt5::Core - Qt5::DBus - Qt5::Quick - Qt5::Qml - ) - target_include_directories(ollie-plasmoid-plugin PRIVATE - ${CMAKE_SOURCE_DIR}/plasmoid/plugin - ) - set_target_properties(ollie-plasmoid-plugin PROPERTIES - OUTPUT_NAME "ollieprivateplugin" - ) - - # --- Plasmoid Package --- - plasma_install_package(plasmoid org.ollie.plasmoid) + # --- Kate Plugin (temporarily disabled — 9P conversion in progress) --- + # kcoreaddons_add_plugin(ollie_kate SOURCES kate/ollie_kate.cpp kate/ollie_ghost.cpp kate/chat_renderer.cpp INSTALL_NAMESPACE "ktexteditor") + # target_link_libraries(ollie_kate + # KF5::TextEditor + # KF5::SyntaxHighlighting + # Qt5::Core + # Qt5::Widgets + # ) + # if(HAVE_KATE_DIFF) + # target_compile_definitions(ollie_kate PRIVATE HAVE_KATE_DIFF) + # target_include_directories(ollie_kate PRIVATE ${CMAKE_SOURCE_DIR}/kate) + # target_link_libraries(ollie_kate ${KATEPRIVATE_LIB}) + # endif() # --- GUI App (9P-based) --- configure_file(gui/main-kf5.qml ${CMAKE_CURRENT_BINARY_DIR}/gui-kf5/main.qml COPYONLY) @@ -132,11 +92,7 @@ if(OLLIE_KF5) ) # --- Install --- - install(TARGETS ollie-tray ollie-gui DESTINATION ${KDE_INSTALL_BINDIR}) - install(TARGETS ollie-plasmoid-plugin - DESTINATION ${KDE_INSTALL_QMLDIR}/org/ollie/private) - install(FILES plasmoid/plugin/qmldir - DESTINATION ${KDE_INSTALL_QMLDIR}/org/ollie/private) + install(TARGETS ollie-gui DESTINATION ${KDE_INSTALL_BINDIR}) else() # --- Qt6 / KF6 --- @@ -241,7 +197,7 @@ else() ) # --- Install --- - install(TARGETS ollie-tray ollie-gui DESTINATION ${KDE_INSTALL_BINDIR}) + install(TARGETS ollie-gui DESTINATION ${KDE_INSTALL_BINDIR}) install(TARGETS ollie-plasmoid-plugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/ollie/private) endif() diff --git a/gui/ollie9pclient.cpp b/gui/ollie9pclient.cpp index d7d68c9..c354a35 100644 --- a/gui/ollie9pclient.cpp +++ b/gui/ollie9pclient.cpp @@ -3,14 +3,14 @@ #include #include -// Resolve the 9p binary and server address static QString ninepBin() { // plan9port's 9p command QString bin = QStandardPaths::findExecutable("9p"); if (!bin.isEmpty()) return bin; - if (QFileInfo::exists("/usr/local/plan9/bin/9p")) - return "/usr/local/plan9/bin/9p"; + QString plan9 = qEnvironmentVariable("PLAN9"); + if (!plan9.isEmpty() && QFileInfo::exists(plan9 + "/bin/9p")) + return plan9 + "/bin/9p"; return "9p"; } diff --git a/kate/ollie_kate.cpp b/kate/ollie_kate.cpp index fa7b219..f160dfb 100644 --- a/kate/ollie_kate.cpp +++ b/kate/ollie_kate.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -40,9 +39,15 @@ K_PLUGIN_CLASS_WITH_JSON(OllieKatePlugin, "metadata.json") -static const char *SERVICE = "org.ollie.SessionManager"; -static const char *OBJ_PATH = "/org/ollie/SessionManager"; -static const char *IFACE = "org.ollie.SessionManager"; +static QString ninepBin() +{ + QString bin = QStandardPaths::findExecutable("9p"); + if (!bin.isEmpty()) return bin; + QString plan9 = qEnvironmentVariable("PLAN9"); + if (!plan9.isEmpty() && QFileInfo::exists(plan9 + "/bin/9p")) + return plan9 + "/bin/9p"; + return "9p"; +} // --- Plugin --- diff --git a/kate/ollie_kate.h b/kate/ollie_kate.h index b7b7285..8e90327 100644 --- a/kate/ollie_kate.h +++ b/kate/ollie_kate.h @@ -9,14 +9,14 @@ #include #endif -#include -#include +#include #include #include #include #include #include #include +#include #include #include @@ -105,7 +105,6 @@ private: OllieKatePlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; - QDBusInterface *m_iface; QWidget *m_toolView; QLabel *m_sessionLabel; QLabel *m_cwdLabel; @@ -118,6 +117,19 @@ private: QPushButton *m_stopBtn; QPushButton *m_scrollBottomBtn; + // 9P helpers + QByteArray run9p(const QStringList &args); + void write9p(const QString &path, const QByteArray &data); + QString serverAddr(); + QString agentPath(); + + // Streaming processes + QProcess *m_chatProc = nullptr; + QProcess *m_stateProc = nullptr; + void startChatStream(); + void startStateStream(); + void stopStreams(); + QString m_sessionId; qlonglong m_chatOffset = 0; QString m_rawChat; // accumulated raw chat text diff --git a/krunner/ollie_runner.cpp b/krunner/ollie_runner.cpp index 473405d..24c8cd5 100644 --- a/krunner/ollie_runner.cpp +++ b/krunner/ollie_runner.cpp @@ -1,9 +1,6 @@ #include "ollie_runner.h" #include -#include -#include -#include #include #include #include @@ -11,15 +8,20 @@ #include #include #include -#include -#include -#include +#include +#include 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"; +static QString ninepBin() +{ + QString bin = QStandardPaths::findExecutable("9p"); + if (!bin.isEmpty()) return bin; + QString plan9 = qEnvironmentVariable("PLAN9"); + if (!plan9.isEmpty() && QFileInfo::exists(plan9 + "/bin/9p")) + return plan9 + "/bin/9p"; + return "9p"; +} #if QT_VERSION_MAJOR >= 6 OllieRunner::OllieRunner(QObject *parent, const KPluginMetaData &data) @@ -29,28 +31,65 @@ OllieRunner::OllieRunner(QObject *parent, const KPluginMetaData &data, const QVa : AbstractRunner(parent, data, args) #endif { - m_iface = new QDBusInterface(SERVICE, OBJ_PATH, IFACE, - QDBusConnection::sessionBus(), this); +} + +QString OllieRunner::serverAddr() +{ + QString ns = qEnvironmentVariable("NAMESPACE"); + if (ns.isEmpty()) { + QString user = qEnvironmentVariable("USER"); + QString display = qEnvironmentVariable("DISPLAY"); + ns = "/tmp/ns." + user + "." + display; + } + return "unix!" + ns + "/ollie"; +} + +QByteArray OllieRunner::run9p(const QStringList &args) +{ + QProcess proc; + QStringList fullArgs = {"-a", serverAddr()}; + fullArgs.append(args); + proc.start(ninepBin(), fullArgs); + if (!proc.waitForFinished(5000)) return {}; + return proc.readAllStandardOutput(); +} + +void OllieRunner::write9p(const QString &path, const QByteArray &data) +{ + QProcess proc; + proc.start(ninepBin(), {"-a", serverAddr(), "write", path}); + proc.waitForStarted(3000); + proc.write(data); + proc.closeWriteChannel(); + proc.waitForFinished(3000); +} + +QStringList OllieRunner::listSessions() +{ + QByteArray out = run9p({"read", "session/idx"}); + QString raw = QString::fromUtf8(out).trimmed(); + if (raw.isEmpty()) return {}; + return raw.split('\n'); +} + +void OllieRunner::killSession(const QString &id) +{ + // Find agent path + QByteArray agentOut = run9p({"ls", "session/" + id + "/agent"}); + QString agentId = QString::fromUtf8(agentOut).trimmed().split('\n').value(0, "0"); + write9p("session/" + id + "/agent/" + agentId + "/ctl", "kill"); } 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")) { + 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 " + QString args = query.mid(6).trimmed(); if (args.isEmpty() || args == QLatin1String("list")) { - // List sessions QStringList sessions = listSessions(); if (sessions.isEmpty()) { RunnerNS::QueryMatch match(this); @@ -71,7 +110,7 @@ void OllieRunner::match(RunnerNS::RunnerContext &context) match.setSubtext(parts[2] + " — " + parts[1]); match.setIconName("system-run"); match.setRelevance(0.7); - match.setData(parts[0]); // session ID + match.setData(parts[0]); context.addMatch(match); } } @@ -91,42 +130,11 @@ void OllieRunner::match(RunnerNS::RunnerContext &context) return; } - if (args.startsWith(QLatin1String("rename "))) { - // Expected: rename - // 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 + // Default: one-shot prompt via generate file RunnerNS::QueryMatch match(this); match.setId("ollie-prompt"); match.setText("Ask Ollie: " + args); - match.setSubtext("One-shot prompt — result via notification"); + match.setSubtext("One-shot generation"); match.setIconName("system-run"); match.setRelevance(0.9); match.setData("prompt:" + args); @@ -139,35 +147,29 @@ void OllieRunner::run(const RunnerNS::RunnerContext &context, const RunnerNS::Qu QString data = match.data().toString(); if (data.startsWith("kill:")) { - QString id = data.mid(5); - killSession(id); - return; - } - - if (data.startsWith("rename:")) { - // format: rename:: - 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); - } + killSession(data.mid(5)); return; } if (data.startsWith("prompt:")) { QString prompt = data.mid(7); - QString result = oneShotRun(prompt); + // Use the generate rdwr file for one-shot + QProcess proc; + proc.start(ninepBin(), {"-a", serverAddr(), "rdwr", "generate"}); + proc.waitForStarted(3000); + proc.write(prompt.toUtf8()); + proc.closeWriteChannel(); + proc.waitForFinished(30000); + QString result = QString::fromUtf8(proc.readAllStandardOutput()).trimmed(); + if (result.isEmpty()) result = "(no response)"; - // Show popup window with result + // Show popup 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); @@ -178,17 +180,14 @@ void OllieRunner::run(const RunnerNS::RunnerContext &context, const RunnerNS::Qu 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(); @@ -197,51 +196,8 @@ void OllieRunner::run(const RunnerNS::RunnerContext &context, const RunnerNS::Qu 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 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 reply = m_iface->call("ListSessions"); - if (reply.isValid()) return reply.value(); - return {}; -} - -void OllieRunner::killSession(const QString &id) -{ - m_iface->call("KillSession", id); + // Session selected — launch GUI + QProcess::startDetached("ollie-gui", {}); } #include "ollie_runner.moc" diff --git a/krunner/ollie_runner.h b/krunner/ollie_runner.h index 3ad4592..037f7aa 100644 --- a/krunner/ollie_runner.h +++ b/krunner/ollie_runner.h @@ -4,13 +4,11 @@ #include #include #include -#include +#include #if QT_VERSION_MAJOR >= 6 -// KF6: classes are in the KRunner namespace namespace RunnerNS = KRunner; #else -// KF5: classes are in the Plasma namespace namespace RunnerNS = Plasma; #endif @@ -29,9 +27,9 @@ public: void run(const RunnerNS::RunnerContext &context, const RunnerNS::QueryMatch &match) override; private: - QDBusInterface *m_iface; - - QString oneShotRun(const QString &prompt); + QByteArray run9p(const QStringList &args); + void write9p(const QString &path, const QByteArray &data); + QString serverAddr(); QStringList listSessions(); void killSession(const QString &id); }; diff --git a/tray/main.cpp b/tray/main.cpp index f420771..5e40b1f 100644 --- a/tray/main.cpp +++ b/tray/main.cpp @@ -1,9 +1,4 @@ #include -#include -#include -#include -#include - #include "ollie_tray.h" int main(int argc, char *argv[]) @@ -13,36 +8,7 @@ int main(int argc, char *argv[]) app.setOrganizationName("ollie"); app.setQuitOnLastWindowClosed(false); - QDBusConnection bus = QDBusConnection::sessionBus(); - if (!bus.isConnected()) { - qCritical() << "Cannot connect to D-Bus session bus"; - return 1; - } - OllieTray tray; - // Watch for daemon appearing/disappearing - auto *watcher = new QDBusServiceWatcher( - "org.ollie.SessionManager", - bus, - QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration, - &app); - - QObject::connect(watcher, &QDBusServiceWatcher::serviceRegistered, [&]() { - qDebug() << "Daemon appeared on D-Bus"; - tray.updateStatus(); - }); - - QObject::connect(watcher, &QDBusServiceWatcher::serviceUnregistered, [&]() { - qDebug() << "Daemon disappeared from D-Bus"; - tray.updateStatus(); - }); - - if (bus.interface()->isServiceRegistered("org.ollie.SessionManager")) { - qDebug() << "ollie-tray started (daemon running)"; - } else { - qDebug() << "ollie-tray started (waiting for daemon)"; - } - return app.exec(); } diff --git a/tray/ollie_tray.cpp b/tray/ollie_tray.cpp index f4e766a..94410fe 100644 --- a/tray/ollie_tray.cpp +++ b/tray/ollie_tray.cpp @@ -1,229 +1,134 @@ #include "ollie_tray.h" #include -#include -#include #include -#include -#include -#include +#include -static const char *SERVICE = "org.ollie.SessionManager"; -static const char *OBJ_PATH = "/org/ollie/SessionManager"; -static const char *IFACE = "org.ollie.SessionManager"; +static QString ninepBin() +{ + QString bin = QStandardPaths::findExecutable("9p"); + if (!bin.isEmpty()) return bin; + QString plan9 = qEnvironmentVariable("PLAN9"); + if (!plan9.isEmpty() && QFileInfo::exists(plan9 + "/bin/9p")) + return plan9 + "/bin/9p"; + return "9p"; +} OllieTray::OllieTray(QObject *parent) : QObject(parent) { - // D-Bus interface to the daemon - m_iface = new QDBusInterface(SERVICE, OBJ_PATH, IFACE, - QDBusConnection::sessionBus(), this); - - // KStatusNotifierItem m_sni = new KStatusNotifierItem("ollie-kde", this); m_sni->setCategory(KStatusNotifierItem::ApplicationStatus); m_sni->setTitle("Ollie"); - m_sni->setIconByName("system-run"); // fallback icon + m_sni->setIconByName("system-run"); m_sni->setStatus(KStatusNotifierItem::Active); - setupMenu(); - updateStatus(); - updateTooltip(); + connect(m_sni, &KStatusNotifierItem::activateRequested, this, &OllieTray::openGui); - // Connect D-Bus signals - QDBusConnection bus = QDBusConnection::sessionBus(); - bus.connect(SERVICE, OBJ_PATH, IFACE, "SessionCreated", - this, SLOT(onSessionCreated(QString))); - bus.connect(SERVICE, OBJ_PATH, IFACE, "SessionKilled", - this, SLOT(onSessionKilled(QString))); - bus.connect(SERVICE, OBJ_PATH, IFACE, "StateChanged", - this, SLOT(onStateChanged(QString,QString))); + setupMenu(); + refreshSessions(); + + // Poll session state every 5 seconds (lightweight — single 9p read) + m_pollTimer = new QTimer(this); + connect(m_pollTimer, &QTimer::timeout, this, &OllieTray::refreshSessions); + m_pollTimer->start(5000); } OllieTray::~OllieTray() { } +QString OllieTray::serverAddr() +{ + QString ns = qEnvironmentVariable("NAMESPACE"); + if (ns.isEmpty()) { + QString user = qEnvironmentVariable("USER"); + QString display = qEnvironmentVariable("DISPLAY"); + ns = "/tmp/ns." + user + "." + display; + } + return "unix!" + ns + "/ollie"; +} + +QByteArray OllieTray::run9p(const QStringList &args) +{ + QProcess proc; + QStringList fullArgs = {"-a", serverAddr()}; + fullArgs.append(args); + proc.start(ninepBin(), fullArgs); + if (!proc.waitForFinished(5000)) return {}; + return proc.readAllStandardOutput(); +} + +void OllieTray::write9p(const QString &path, const QByteArray &data) +{ + QProcess proc; + proc.start(ninepBin(), {"-a", serverAddr(), "write", path}); + proc.waitForStarted(3000); + proc.write(data); + proc.closeWriteChannel(); + proc.waitForFinished(3000); +} + void OllieTray::setupMenu() { m_menu = new QMenu(); - - m_menu->addAction("New Session", this, &OllieTray::newSession); - m_menu->addSeparator(); - m_sessionsMenu = m_menu->addMenu("Sessions"); - connect(m_sessionsMenu, &QMenu::aboutToShow, this, &OllieTray::refreshSessions); - m_menu->addSeparator(); + m_menu->addAction("Open GUI", this, &OllieTray::openGui); m_menu->addAction("Stop All", this, &OllieTray::stopAll); m_menu->addSeparator(); - m_menu->addAction("Quit Daemon", this, &OllieTray::quitDaemon); - + m_menu->addAction("Quit", qApp, &QApplication::quit); m_sni->setContextMenu(m_menu); } -void OllieTray::updateStatus() -{ - QStringList sessions = callListSessions(); - m_totalSessions = sessions.size(); - m_activeSessions = 0; - - for (const QString &entry : sessions) { - QStringList parts = entry.split('\t'); - if (parts.size() >= 2) { - QString state = parts[1]; - if (state != "idle") { - m_activeSessions++; - } - } - } - - // Icon status: active (bright) if any session is thinking, passive if all idle - if (m_activeSessions > 0) { - m_sni->setStatus(KStatusNotifierItem::NeedsAttention); - m_sni->setIconByName("ollie-active"); - m_sni->setAttentionIconByName("ollie-active"); - } else if (m_totalSessions > 0) { - m_sni->setStatus(KStatusNotifierItem::Active); - m_sni->setIconByName("system-run"); - } else { - m_sni->setStatus(KStatusNotifierItem::Passive); - m_sni->setIconByName("system-run"); - } - - updateTooltip(); -} - -void OllieTray::updateTooltip() -{ - QString tooltip; - if (m_totalSessions == 0) { - tooltip = "Ollie — no active sessions"; - } else { - tooltip = QString("Ollie — %1 session%2") - .arg(m_totalSessions) - .arg(m_totalSessions == 1 ? "" : "s"); - if (m_activeSessions > 0) { - tooltip += QString(", %1 active").arg(m_activeSessions); - } - } - m_sni->setToolTipTitle("Ollie"); - m_sni->setToolTipSubTitle(tooltip); -} - -// --- D-Bus signal handlers --- - -void OllieTray::onSessionCreated(const QString &session_id) -{ - Q_UNUSED(session_id); - updateStatus(); -} - -void OllieTray::onSessionKilled(const QString &session_id) -{ - Q_UNUSED(session_id); - updateStatus(); -} - -void OllieTray::onStateChanged(const QString &session_id, const QString &new_state) -{ - Q_UNUSED(session_id); - Q_UNUSED(new_state); - updateStatus(); -} - -// --- Menu actions --- - -void OllieTray::newSession() -{ - QString cwd = QDir::homePath(); - QDBusReply reply = m_iface->call("CreateSession", cwd, QString(), QString(), "default", QString(), QString()); - if (reply.isValid()) { - qDebug() << "Created session:" << reply.value(); - } else { - qWarning() << "Failed to create session:" << reply.error().message(); - } -} - -void OllieTray::stopAll() -{ - QStringList sessions = callListSessions(); - for (const QString &entry : sessions) { - QStringList parts = entry.split('\t'); - if (!parts.isEmpty()) { - callKillSession(parts[0]); - } - } -} - -void OllieTray::quitDaemon() -{ - // Stop all sessions then tell the daemon to exit - stopAll(); - // Send SIGTERM to the daemon process via D-Bus (or just quit ourselves) - // For now, just quit the tray; the daemon stays alive. - // To actually quit the daemon, we'd need a Quit method on D-Bus. - QApplication::quit(); -} - void OllieTray::refreshSessions() { + QByteArray out = run9p({"read", "session/idx"}); + QString raw = QString::fromUtf8(out).trimmed(); + m_sessionsMenu->clear(); - QStringList sessions = callListSessions(); - if (sessions.isEmpty()) { - m_sessionsMenu->addAction("(none)")->setEnabled(false); + if (raw.isEmpty()) { + m_sessionsMenu->addAction("(no sessions)")->setEnabled(false); + m_sni->setToolTip("system-run", "Ollie", "No active sessions"); return; } - for (const QString &entry : sessions) { - QStringList parts = entry.split('\t'); - if (parts.size() < 4) continue; + QStringList lines = raw.split('\n'); + int active = 0; + for (const QString &line : lines) { + QStringList parts = line.split('\t'); + if (parts.size() < 2) continue; QString id = parts[0]; QString state = parts[1]; - QString model = parts[2]; - QString agent = parts[3]; + if (state != "idle") active++; - // State indicator - QString indicator; - if (state == "idle") indicator = "○"; - else if (state.startsWith("calling")) indicator = "⚡"; - else indicator = "●"; + QString label = (state == "idle" ? "○ " : "● ") + id; + m_sessionsMenu->addAction(label); + } - QString label = QString("%1 %2 [%3] %4").arg(indicator, id, agent, model); + m_sni->setToolTip("system-run", "Ollie", + QString("%1 sessions, %2 active").arg(lines.size()).arg(active)); +} - QMenu *sessionMenu = m_sessionsMenu->addMenu(label); - QString capturedId = id; - sessionMenu->addAction("Rename", this, [this, capturedId]() { - bool ok; - QString newName = QInputDialog::getText( - nullptr, "Rename Session", - "New name for session " + capturedId + ":", - QLineEdit::Normal, capturedId, &ok); - if (ok && !newName.trimmed().isEmpty()) { - m_iface->call("RenameSession", capturedId, newName.trimmed()); - } - }); - sessionMenu->addAction("Kill", this, [this, capturedId]() { - callKillSession(capturedId); - }); +void OllieTray::openGui() +{ + QProcess::startDetached("ollie-gui", {}); +} + +void OllieTray::stopAll() +{ + QByteArray out = run9p({"read", "session/idx"}); + QString raw = QString::fromUtf8(out).trimmed(); + for (const QString &line : raw.split('\n')) { + QStringList parts = line.split('\t'); + if (parts.size() < 2 || parts[1] == "idle") continue; + QString id = parts[0]; + // Find agent, send stop + QByteArray agentOut = run9p({"ls", "session/" + id + "/agent"}); + QString agentId = QString::fromUtf8(agentOut).trimmed().split('\n').value(0); + if (!agentId.isEmpty()) + write9p("session/" + id + "/agent/" + agentId + "/ctl", "stop"); } } - -// --- D-Bus helpers --- - -QStringList OllieTray::callListSessions() -{ - QDBusReply reply = m_iface->call("ListSessions"); - if (reply.isValid()) { - return reply.value(); - } - return {}; -} - -bool OllieTray::callKillSession(const QString &id) -{ - QDBusReply reply = m_iface->call("KillSession", id); - return reply.isValid() && reply.value(); -} diff --git a/tray/ollie_tray.h b/tray/ollie_tray.h index 2a21f7e..c4f42ce 100644 --- a/tray/ollie_tray.h +++ b/tray/ollie_tray.h @@ -3,8 +3,8 @@ #include #include -#include -#include +#include +#include #include class OllieTray : public QObject @@ -15,31 +15,21 @@ public: explicit OllieTray(QObject *parent = nullptr); ~OllieTray() override; - void updateStatus(); - private slots: - void onSessionCreated(const QString &session_id); - void onSessionKilled(const QString &session_id); - void onStateChanged(const QString &session_id, const QString &new_state); - - void newSession(); - void stopAll(); - void quitDaemon(); void refreshSessions(); + void openGui(); + void stopAll(); private: void setupMenu(); - void updateTooltip(); - QStringList callListSessions(); - bool callKillSession(const QString &id); + QByteArray run9p(const QStringList &args); + void write9p(const QString &path, const QByteArray &data); + QString serverAddr(); KStatusNotifierItem *m_sni; QMenu *m_menu; QMenu *m_sessionsMenu; - QDBusInterface *m_iface; - - int m_totalSessions = 0; - int m_activeSessions = 0; + QTimer *m_pollTimer; }; #endif // OLLIE_TRAY_H