219 lines
6.0 KiB
C++
219 lines
6.0 KiB
C++
#include "ollie_tray.h"
|
|
|
|
#include <QApplication>
|
|
#include <QDBusReply>
|
|
#include <QDBusConnection>
|
|
#include <QDir>
|
|
#include <QIcon>
|
|
#include <QDebug>
|
|
|
|
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<QString> 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<QStringList> reply = m_iface->call("ListSessions");
|
|
if (reply.isValid()) {
|
|
return reply.value();
|
|
}
|
|
return {};
|
|
}
|
|
|
|
bool OllieTray::callKillSession(const QString &id)
|
|
{
|
|
QDBusReply<bool> reply = m_iface->call("KillSession", id);
|
|
return reply.isValid() && reply.value();
|
|
}
|