635 lines
21 KiB
C++
635 lines
21 KiB
C++
#include "olliedbusclient.h"
|
|
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QDBusReply>
|
|
#include <QDBusConnection>
|
|
#include <QDBusServiceWatcher>
|
|
#include <QDBusPendingReply>
|
|
#include <QDir>
|
|
#include <QUrl>
|
|
#include <algorithm>
|
|
|
|
|
|
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]() {
|
|
// Recreate the interface — QDBusInterface caches introspection and can
|
|
// become stale after the service restarts.
|
|
delete m_iface;
|
|
m_iface = new QDBusInterface(SERVICE, OBJ_PATH, IFACE,
|
|
QDBusConnection::sessionBus(), 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();
|
|
m_activeModel.clear();
|
|
m_activeBackend.clear();
|
|
emit sessionsChanged();
|
|
emit activeSessionIdChanged();
|
|
emit chatTextChanged();
|
|
emit activeStateChanged();
|
|
emit activeCwdChanged();
|
|
emit activeModelChanged();
|
|
emit activeBackendChanged();
|
|
});
|
|
|
|
// 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)));
|
|
bus.connect(SERVICE, OBJ_PATH, IFACE, "ProcessDetached",
|
|
this, SLOT(onProcessDetached(QString)));
|
|
bus.connect(SERVICE, OBJ_PATH, IFACE, "ProcessExited",
|
|
this, SLOT(onProcessExited(QString,int,int)));
|
|
bus.connect(SERVICE, OBJ_PATH, IFACE, "ConfigChanged",
|
|
this, SLOT(onConfigChanged(QString,QString,QString)));
|
|
|
|
refreshSessions();
|
|
}
|
|
|
|
void OllieDBusClient::setActiveSessionId(const QString &id)
|
|
{
|
|
if (m_activeSessionId == id) return;
|
|
m_activeSessionId = id;
|
|
m_chatText.clear();
|
|
m_chatOffset = 0;
|
|
|
|
// Update state from sessions list
|
|
QString newState = "idle";
|
|
for (const QVariant &s : m_sessions) {
|
|
QVariantMap session = s.toMap();
|
|
if (session["id"].toString() == id) {
|
|
newState = session["state"].toString();
|
|
break;
|
|
}
|
|
}
|
|
if (newState != m_activeState) {
|
|
m_activeState = newState;
|
|
emit activeStateChanged();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
std::sort(m_sessions.begin(), m_sessions.end(), [](const QVariant &a, const QVariant &b) {
|
|
return a.toMap()["id"].toString().compare(b.toMap()["id"].toString(), Qt::CaseInsensitive) < 0;
|
|
});
|
|
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();
|
|
m_activeModel.clear();
|
|
m_activeBackend.clear();
|
|
emit activeSessionIdChanged();
|
|
emit activeStateChanged();
|
|
emit chatTextChanged();
|
|
emit activeCwdChanged();
|
|
emit activeModelChanged();
|
|
emit activeBackendChanged();
|
|
}
|
|
}
|
|
|
|
// --- 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, newModel, newBackend;
|
|
const QStringList lines = config.split('\n', Qt::SkipEmptyParts);
|
|
for (const QString &line : lines) {
|
|
if (line.startsWith(QLatin1String("cwd=")))
|
|
newCwd = line.mid(4);
|
|
else if (line.startsWith(QLatin1String("model=")))
|
|
newModel = line.mid(6);
|
|
else if (line.startsWith(QLatin1String("backend=")))
|
|
newBackend = line.mid(8);
|
|
}
|
|
|
|
if (newCwd != m_activeCwd) {
|
|
m_activeCwd = newCwd;
|
|
emit activeCwdChanged();
|
|
}
|
|
if (newModel != m_activeModel) {
|
|
m_activeModel = newModel;
|
|
emit activeModelChanged();
|
|
}
|
|
if (newBackend != m_activeBackend) {
|
|
m_activeBackend = newBackend;
|
|
emit activeBackendChanged();
|
|
}
|
|
}
|
|
|
|
// --- User-initiated actions (fire-and-forget async) ---
|
|
|
|
QStringList OllieDBusClient::listAgents()
|
|
{
|
|
QDBusReply<QStringList> reply = m_iface->call("ListAgents");
|
|
if (reply.isValid())
|
|
return reply.value();
|
|
return {};
|
|
}
|
|
|
|
QString OllieDBusClient::createSession(const QString &cwd, const QString &backend, const QString &model, const QString &name, const QString &agent, const QString &remote)
|
|
{
|
|
QString dir = cwd.isEmpty() ? QDir::homePath() : cwd;
|
|
QString agentName = agent.isEmpty() ? QString("default") : agent;
|
|
auto *watcher = new QDBusPendingCallWatcher(
|
|
m_iface->asyncCall("CreateSession", dir, backend, model, agentName, QString(), remote),
|
|
this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, name](QDBusPendingCallWatcher *w) {
|
|
QDBusPendingReply<QString> reply = *w;
|
|
if (reply.isError()) {
|
|
emit errorOccurred(reply.error().message());
|
|
} else if (!name.trimmed().isEmpty()) {
|
|
// Rename the newly created session
|
|
QString sessionId = reply.value();
|
|
m_iface->asyncCall("RenameSession", sessionId, name.trimmed());
|
|
}
|
|
w->deleteLater();
|
|
});
|
|
return QString();
|
|
}
|
|
|
|
void OllieDBusClient::createSessionWithDialog()
|
|
{
|
|
// Use xdg-desktop-portal FileChooser — plasmashell is QGuiApplication,
|
|
// so QFileDialog (QtWidgets) cannot be used.
|
|
//
|
|
// We use handle_token to make the request object path predictable, then
|
|
// connect to the Response signal BEFORE making the call. This avoids a
|
|
// race where the portal fires Response before we subscribe to the signal.
|
|
static int handleSeq = 0;
|
|
QString handleToken = QString("ollie_folder_%1").arg(++handleSeq);
|
|
|
|
// Predict the request object path: /org/freedesktop/portal/desktop/request/<sender>/<handle_token>
|
|
QString sender = QDBusConnection::sessionBus().baseService();
|
|
sender.replace('.', '_');
|
|
if (sender.startsWith(':'))
|
|
sender = sender.mid(1);
|
|
QString requestPath = QString("/org/freedesktop/portal/desktop/request/%1/%2")
|
|
.arg(sender, handleToken);
|
|
|
|
// Connect to Response BEFORE making the call to avoid race
|
|
QDBusConnection::sessionBus().connect(
|
|
"org.freedesktop.portal.Desktop",
|
|
requestPath,
|
|
"org.freedesktop.portal.Request",
|
|
"Response",
|
|
this,
|
|
SLOT(onPortalFolderResponse(uint,QVariantMap)));
|
|
|
|
QDBusMessage msg = QDBusMessage::createMethodCall(
|
|
"org.freedesktop.portal.Desktop",
|
|
"/org/freedesktop/portal/desktop",
|
|
"org.freedesktop.portal.FileChooser",
|
|
"OpenFile");
|
|
|
|
QString parentWindow; // empty — no parent window handle in plasmoid context
|
|
QString title = "Choose working directory for new session";
|
|
QVariantMap options;
|
|
options["directory"] = true;
|
|
options["modal"] = true;
|
|
options["handle_token"] = handleToken;
|
|
|
|
msg << parentWindow << title << options;
|
|
|
|
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(msg);
|
|
auto *watcher = new QDBusPendingCallWatcher(pending, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, requestPath](QDBusPendingCallWatcher *w) {
|
|
w->deleteLater();
|
|
QDBusPendingReply<QDBusObjectPath> reply = *w;
|
|
if (reply.isError()) {
|
|
// Clean up the signal connection on failure
|
|
QDBusConnection::sessionBus().disconnect(
|
|
"org.freedesktop.portal.Desktop",
|
|
requestPath,
|
|
"org.freedesktop.portal.Request",
|
|
"Response",
|
|
this,
|
|
SLOT(onPortalFolderResponse(uint,QVariantMap)));
|
|
}
|
|
});
|
|
}
|
|
|
|
void OllieDBusClient::onPortalFolderResponse(uint response, const QVariantMap &results)
|
|
{
|
|
if (response != 0) return; // 0 = success, 1 = cancelled, 2 = other
|
|
|
|
QStringList uris = results.value("uris").toStringList();
|
|
if (uris.isEmpty()) return;
|
|
|
|
// Portal returns file:// URIs
|
|
QString path = QUrl(uris.first()).toLocalFile();
|
|
if (!path.isEmpty()) {
|
|
createSession(path);
|
|
}
|
|
}
|
|
|
|
QStringList OllieDBusClient::peerList(const QString &sessionId)
|
|
{
|
|
QDBusReply<QStringList> reply = m_iface->call("PeerList", sessionId);
|
|
if (reply.isValid())
|
|
return reply.value();
|
|
return {};
|
|
}
|
|
|
|
QVariantMap OllieDBusClient::getChat(const QString &sessionId, int offset)
|
|
{
|
|
QDBusMessage msg = QDBusMessage::createMethodCall(
|
|
"org.ollie.SessionManager", "/org/ollie/SessionManager",
|
|
"org.ollie.SessionManager", "GetChat");
|
|
msg << sessionId << (qlonglong)offset;
|
|
QDBusMessage reply = QDBusConnection::sessionBus().call(msg);
|
|
QVariantMap result;
|
|
if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().size() >= 2) {
|
|
result["text"] = reply.arguments()[0].toString();
|
|
result["offset"] = reply.arguments()[1].toInt();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QVariantMap OllieDBusClient::getConfig(const QString &sessionId)
|
|
{
|
|
QDBusReply<QString> reply = m_iface->call("GetConfig", sessionId);
|
|
QVariantMap result;
|
|
if (reply.isValid()) {
|
|
for (const QString &line : reply.value().split('\n')) {
|
|
int eq = line.indexOf('=');
|
|
if (eq > 0)
|
|
result[line.left(eq)] = line.mid(eq + 1);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QString OllieDBusClient::getState(const QString &sessionId)
|
|
{
|
|
QDBusReply<QString> reply = m_iface->call("GetState", sessionId);
|
|
if (reply.isValid())
|
|
return reply.value();
|
|
return "idle";
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// --- Detached process management ---
|
|
|
|
bool OllieDBusClient::detachProcess(const QString &sessionId)
|
|
{
|
|
QDBusReply<bool> reply = m_iface->call("DetachProcess", sessionId);
|
|
return reply.isValid() && reply.value();
|
|
}
|
|
|
|
QVariantList OllieDBusClient::listDetached(const QString &sessionId)
|
|
{
|
|
QDBusReply<QStringList> reply = m_iface->call("ListDetached", sessionId);
|
|
if (!reply.isValid()) return {};
|
|
|
|
QVariantList result;
|
|
for (const QString &entry : reply.value()) {
|
|
QStringList parts = entry.split('\t');
|
|
if (parts.size() >= 4) {
|
|
QVariantMap proc;
|
|
proc["pid"] = parts[0].toInt();
|
|
proc["command"] = parts[1];
|
|
proc["started"] = parts[2].toLongLong();
|
|
proc["status"] = parts[3];
|
|
result.append(proc);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool OllieDBusClient::signalDetached(const QString &sessionId, int pid, const QString &signal)
|
|
{
|
|
QDBusReply<bool> reply = m_iface->call("SignalDetached", sessionId, pid, signal);
|
|
return reply.isValid() && reply.value();
|
|
}
|
|
|
|
QString OllieDBusClient::getDetachedOutput(const QString &sessionId, int pid)
|
|
{
|
|
QDBusReply<QString> reply = m_iface->call("GetDetachedOutput", sessionId, pid);
|
|
if (reply.isValid()) return reply.value();
|
|
return QString();
|
|
}
|
|
|
|
bool OllieDBusClient::dismissDetached(const QString &sessionId, int pid)
|
|
{
|
|
QDBusReply<bool> reply = m_iface->call("DismissDetached", sessionId, pid);
|
|
return reply.isValid() && reply.value();
|
|
}
|
|
|
|
// --- 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();
|
|
}
|
|
|
|
// Update state in-place without emitting sessionsChanged() — that would
|
|
// cause the ListView to recreate all delegates, breaking inline rename.
|
|
// The QML state indicator for the active session uses dbus.activeState
|
|
// (updated above). For other sessions the dot updates on the next real
|
|
// refresh (session created/killed/renamed).
|
|
for (int i = 0; i < m_sessions.size(); ++i) {
|
|
QVariantMap session = m_sessions[i].toMap();
|
|
if (session["id"].toString() == session_id) {
|
|
session["state"] = new_state;
|
|
m_sessions[i] = session;
|
|
emit sessionStateUpdated(session_id, new_state);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OllieDBusClient::onChatUpdated(const QString &session_id, qlonglong offset, const QString &new_text)
|
|
{
|
|
// Emit for all panes listening to any session
|
|
emit chatAppended(session_id, static_cast<int>(offset), 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();
|
|
}
|
|
}
|
|
|
|
void OllieDBusClient::onProcessDetached(const QString &session_id)
|
|
{
|
|
emit processDetached(session_id);
|
|
}
|
|
|
|
void OllieDBusClient::onProcessExited(const QString &session_id, int pid, int exitCode)
|
|
{
|
|
emit processExited(session_id, pid, exitCode);
|
|
}
|
|
|
|
void OllieDBusClient::onConfigChanged(const QString &session_id, const QString &key, const QString &value)
|
|
{
|
|
Q_UNUSED(key);
|
|
Q_UNUSED(value);
|
|
// Refresh sessions list so the sidebar picks up agent/model changes
|
|
refreshSessions();
|
|
// If this is the active session, also refresh its config
|
|
if (session_id == m_activeSessionId) {
|
|
fetchConfig();
|
|
}
|
|
}
|
|
|
|
QStringList OllieDBusClient::listBackends()
|
|
{
|
|
QDBusReply<QStringList> reply = m_iface->call("ListBackends");
|
|
if (reply.isValid())
|
|
return reply.value();
|
|
return {};
|
|
}
|
|
|
|
QStringList OllieDBusClient::listModels(const QString &sessionId)
|
|
{
|
|
QDBusReply<QStringList> reply = m_iface->call("ListModels", sessionId);
|
|
if (reply.isValid())
|
|
return reply.value();
|
|
return {};
|
|
}
|
|
|
|
QStringList OllieDBusClient::listModelsByBackend(const QString &backend)
|
|
{
|
|
QDBusReply<QStringList> reply = m_iface->call("ListModelsByBackend", backend);
|
|
if (reply.isValid())
|
|
return reply.value();
|
|
return {};
|
|
}
|