#include "ollie_tray.h" #include #include #include 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) { m_sni = new KStatusNotifierItem("ollie-kde", this); m_sni->setCategory(KStatusNotifierItem::ApplicationStatus); m_sni->setTitle("Ollie"); m_sni->setIconByName("system-run"); m_sni->setStatus(KStatusNotifierItem::Active); connect(m_sni, &KStatusNotifierItem::activateRequested, this, &OllieTray::openGui); 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_sessionsMenu = m_menu->addMenu("Sessions"); 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", qApp, &QApplication::quit); m_sni->setContextMenu(m_menu); } void OllieTray::refreshSessions() { QByteArray out = run9p({"read", "session/idx"}); QString raw = QString::fromUtf8(out).trimmed(); m_sessionsMenu->clear(); if (raw.isEmpty()) { m_sessionsMenu->addAction("(no sessions)")->setEnabled(false); m_sni->setToolTip("system-run", "Ollie", "No active sessions"); return; } 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]; if (state != "idle") active++; QString label = (state == "idle" ? "○ " : "● ") + id; m_sessionsMenu->addAction(label); } m_sni->setToolTip("system-run", "Ollie", QString("%1 sessions, %2 active").arg(lines.size()).arg(active)); } 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"); } }