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/plasmoid/plugin/olliedbusclient.cpp

332 lines
10 KiB
C++

#include "olliedbusclient.h"
#include <QDBusReply>
#include <QDBusConnection>
#include <QDBusServiceWatcher>
#include <QDBusPendingReply>
#include <QDir>
static const char *SERVICE = "org.ollie.SessionManager";
static const char *OBJ_PATH = "/org/ollie/SessionManager";
static const char *IFACE = "org.ollie.SessionManager";
OllieDBusClient::OllieDBusClient(QObject *parent)
: QObject(parent)
{
m_iface = new QDBusInterface(SERVICE, OBJ_PATH, IFACE,
QDBusConnection::sessionBus(), this);
// Watch for service appearing/disappearing
auto *watcher = new QDBusServiceWatcher(SERVICE,
QDBusConnection::sessionBus(),
QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration,
this);
connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, [this]() {
refreshSessions();
});
connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() {
m_sessions.clear();
m_activeSessionId.clear();
m_chatText.clear();
m_chatOffset = 0;
m_activeState = "idle";
m_activeCwd.clear();
emit sessionsChanged();
emit activeSessionIdChanged();
emit chatTextChanged();
emit activeStateChanged();
emit activeCwdChanged();
});
// 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, "SessionRenamed",
this, SLOT(onSessionRenamed(QString,QString)));
bus.connect(SERVICE, OBJ_PATH, IFACE, "StateChanged",
this, SLOT(onStateChanged(QString,QString)));
bus.connect(SERVICE, OBJ_PATH, IFACE, "ChatUpdated",
this, SLOT(onChatUpdated(QString,qlonglong,QString)));
refreshSessions();
}
void OllieDBusClient::setActiveSessionId(const QString &id)
{
if (m_activeSessionId == id) return;
m_activeSessionId = id;
m_chatText.clear();
m_chatOffset = 0;
emit activeSessionIdChanged();
emit chatTextChanged();
fetchChat();
fetchConfig();
}
// --- Async refresh sessions ---
void OllieDBusClient::refreshSessions()
{
if (m_refreshPending) return; // coalesce rapid-fire signals
m_refreshPending = true;
QDBusPendingCall pending = m_iface->asyncCall("ListSessions");
auto *watcher = new QDBusPendingCallWatcher(pending, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, &OllieDBusClient::onRefreshSessionsFinished);
}
void OllieDBusClient::onRefreshSessionsFinished(QDBusPendingCallWatcher *watcher)
{
m_refreshPending = false;
watcher->deleteLater();
QDBusPendingReply<QStringList> reply = *watcher;
if (reply.isError()) return;
processSessionList(reply.value());
}
void OllieDBusClient::processSessionList(const QStringList &entries)
{
m_sessions.clear();
for (const QString &entry : entries) {
QStringList parts = entry.split('\t');
if (parts.size() >= 4) {
QVariantMap session;
session["id"] = parts[0];
session["state"] = parts[1];
session["model"] = parts[2];
session["agent"] = parts[3];
m_sessions.append(session);
}
}
emit sessionsChanged();
// Update active state
for (const QVariant &v : m_sessions) {
QVariantMap s = v.toMap();
if (s["id"].toString() == m_activeSessionId) {
QString newState = s["state"].toString();
if (newState != m_activeState) {
m_activeState = newState;
emit activeStateChanged();
}
return;
}
}
// Active session no longer exists
if (!m_activeSessionId.isEmpty() && !m_sessions.isEmpty()) {
setActiveSessionId(m_sessions.first().toMap()["id"].toString());
} else if (m_activeSessionId.isEmpty() && !m_sessions.isEmpty()) {
setActiveSessionId(m_sessions.first().toMap()["id"].toString());
} else if (m_sessions.isEmpty() && !m_activeSessionId.isEmpty()) {
m_activeSessionId.clear();
m_activeState = "idle";
m_chatText.clear();
m_chatOffset = 0;
m_activeCwd.clear();
emit activeSessionIdChanged();
emit activeStateChanged();
emit chatTextChanged();
emit activeCwdChanged();
}
}
// --- Async fetch chat ---
void OllieDBusClient::fetchChat()
{
if (m_activeSessionId.isEmpty()) return;
if (m_fetchChatPending) return; // don't pile up requests
m_fetchChatPending = true;
QDBusMessage msg = QDBusMessage::createMethodCall(SERVICE, OBJ_PATH, IFACE, "GetChat");
msg << m_activeSessionId << m_chatOffset;
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(msg);
auto *watcher = new QDBusPendingCallWatcher(pending, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, &OllieDBusClient::onFetchChatFinished);
}
void OllieDBusClient::onFetchChatFinished(QDBusPendingCallWatcher *watcher)
{
m_fetchChatPending = false;
watcher->deleteLater();
QDBusPendingReply<QString, qlonglong> reply = *watcher;
if (reply.isError()) return;
QString text = reply.argumentAt<0>();
qlonglong newOffset = reply.argumentAt<1>();
if (!text.isEmpty()) {
if (m_chatOffset == 0) {
m_chatText = text;
} else {
m_chatText += text;
}
m_chatOffset = newOffset;
emit chatTextChanged();
}
}
// --- Async fetch config ---
void OllieDBusClient::fetchConfig()
{
if (m_activeSessionId.isEmpty()) {
if (!m_activeCwd.isEmpty()) {
m_activeCwd.clear();
emit activeCwdChanged();
}
return;
}
QDBusMessage msg = QDBusMessage::createMethodCall(SERVICE, OBJ_PATH, IFACE, "GetConfig");
msg << m_activeSessionId;
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(msg);
auto *watcher = new QDBusPendingCallWatcher(pending, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, &OllieDBusClient::onFetchConfigFinished);
}
void OllieDBusClient::onFetchConfigFinished(QDBusPendingCallWatcher *watcher)
{
watcher->deleteLater();
QDBusPendingReply<QString> reply = *watcher;
if (reply.isError()) return;
QString config = reply.value();
QString newCwd;
const QStringList lines = config.split('\n', Qt::SkipEmptyParts);
for (const QString &line : lines) {
if (line.startsWith(QLatin1String("cwd="))) {
newCwd = line.mid(4);
break;
}
}
if (newCwd != m_activeCwd) {
m_activeCwd = newCwd;
emit activeCwdChanged();
}
}
// --- User-initiated actions (fire-and-forget async) ---
QString OllieDBusClient::createSession(const QString &cwd)
{
QString dir = cwd.isEmpty() ? QDir::homePath() : cwd;
// Use asyncCall — we'll pick up the new session via the SessionCreated signal
m_iface->asyncCall("CreateSession", dir, QString(), QString(), QString("default"));
return QString(); // ID not available synchronously; signal will trigger refresh
}
bool OllieDBusClient::killSession(const QString &sessionId)
{
if (sessionId == m_activeSessionId) {
m_activeSessionId.clear();
m_chatText.clear();
m_chatOffset = 0;
m_activeState = "idle";
m_activeCwd.clear();
emit activeSessionIdChanged();
emit chatTextChanged();
emit activeStateChanged();
emit activeCwdChanged();
}
m_iface->asyncCall("KillSession", sessionId);
return true;
}
bool OllieDBusClient::renameSession(const QString &sessionId, const QString &newName)
{
QDBusPendingCall pending = m_iface->asyncCall("RenameSession", sessionId, newName);
auto *watcher = new QDBusPendingCallWatcher(pending, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, sessionId, newName](QDBusPendingCallWatcher *w) {
w->deleteLater();
QDBusPendingReply<bool> reply = *w;
if (reply.isError() || !reply.value()) return;
if (m_activeSessionId == sessionId) {
m_activeSessionId = newName;
emit activeSessionIdChanged();
}
refreshSessions();
});
return true;
}
bool OllieDBusClient::setConfig(const QString &sessionId, const QString &key, const QString &value)
{
if (sessionId == m_activeSessionId && key == "cwd") {
m_activeCwd = value;
emit activeCwdChanged();
}
m_iface->asyncCall("SetConfig", sessionId, key, value);
return true;
}
bool OllieDBusClient::submit(const QString &sessionId, const QString &prompt)
{
m_iface->asyncCall("Submit", sessionId, prompt);
return true;
}
bool OllieDBusClient::interrupt(const QString &sessionId)
{
m_iface->asyncCall("Interrupt", sessionId);
return true;
}
// --- D-Bus signal handlers ---
void OllieDBusClient::onSessionCreated(const QString &session_id)
{
Q_UNUSED(session_id);
refreshSessions();
}
void OllieDBusClient::onSessionKilled(const QString &session_id)
{
Q_UNUSED(session_id);
refreshSessions();
}
void OllieDBusClient::onSessionRenamed(const QString &old_id, const QString &new_id)
{
if (m_activeSessionId == old_id) {
m_activeSessionId = new_id;
emit activeSessionIdChanged();
}
refreshSessions();
}
void OllieDBusClient::onStateChanged(const QString &session_id, const QString &new_state)
{
if (session_id == m_activeSessionId) {
m_activeState = new_state;
emit activeStateChanged();
}
refreshSessions();
}
void OllieDBusClient::onChatUpdated(const QString &session_id, qlonglong offset, const QString &new_text)
{
if (session_id != m_activeSessionId) return;
// Append directly from the signal — no need to poll
if (offset == m_chatOffset) {
m_chatText += new_text;
m_chatOffset += new_text.toUtf8().size();
emit chatTextChanged();
} else {
// Offset mismatch — do a full fetch
fetchChat();
}
}