#include "ollie_tray.h" #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"; 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->setStatus(KStatusNotifierItem::Active); setupMenu(); updateStatus(); updateTooltip(); // 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))); } OllieTray::~OllieTray() { } 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("Stop All", this, &OllieTray::stopAll); m_menu->addSeparator(); m_menu->addAction("Quit Daemon", this, &OllieTray::quitDaemon); 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"); 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() { m_sessionsMenu->clear(); QStringList sessions = callListSessions(); if (sessions.isEmpty()) { m_sessionsMenu->addAction("(none)")->setEnabled(false); return; } for (const QString &entry : sessions) { QStringList parts = entry.split('\t'); if (parts.size() < 4) continue; QString id = parts[0]; QString state = parts[1]; QString model = parts[2]; QString agent = parts[3]; // State indicator QString indicator; if (state == "idle") indicator = "○"; else if (state.startsWith("calling")) indicator = "⚡"; else indicator = "●"; QString label = QString("%1 %2 [%3] %4").arg(indicator, id.left(12), agent, model); QMenu *sessionMenu = m_sessionsMenu->addMenu(label); QString capturedId = id; sessionMenu->addAction("Kill", this, [this, capturedId]() { callKillSession(capturedId); }); } } // --- 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(); }