plasmoid: async D-Bus calls to prevent panel freezes

All synchronous QDBusInterface::call() replaced with asyncCall() +
QDBusPendingCallWatcher. Adds coalescing flags to prevent piling up
refreshSessions/fetchChat requests during rapid signal bursts.

Also removes build dependency from install targets to prevent cmake
re-running over read-only installed artifacts.
This commit is contained in:
Levi Neely 2026-07-07 13:32:07 +02:00
parent d457be9524
commit 327030293b
3 changed files with 101 additions and 63 deletions

View File

@ -24,7 +24,7 @@ kf5:
# --- KF6 Install (default) ---
install: build
install:
@echo "=== Installing ollie-kde (KF6) ==="
# Tray binary
install -m755 $(BUILD_DIR)/ollie-tray /usr/bin/
@ -77,7 +77,7 @@ uninstall:
# --- Individual component installs ---
krunner-install: build
krunner-install:
mkdir -p /usr/lib64/qt6/plugins/kf6/krunner
install -m755 $(BUILD_DIR)/lib/kf6/krunner/krunner_ollie.so \
/usr/lib64/qt6/plugins/kf6/krunner/
@ -92,7 +92,7 @@ dolphin-install:
install -m755 dolphin/ollie-session-here /usr/local/bin/
@echo "Dolphin menus installed. Restart Dolphin or re-open context menu."
plasmoid-install: build
plasmoid-install:
kpackagetool6 -t Plasma/Applet -r org.ollie.plasmoid 2>/dev/null; true
kpackagetool6 -t Plasma/Applet -i plasmoid/
mkdir -p /usr/lib64/qt6/qml/org/ollie/private
@ -115,7 +115,7 @@ plasmoid-uninstall:
# --- KF5 Install ---
kf5-install: kf5
kf5-install:
@echo "=== Installing ollie-kde (KF5) ==="
# Tray binary
install -m755 $(KF5_BUILD_DIR)/ollie-tray /usr/bin/

View File

@ -3,6 +3,7 @@
#include <QDBusReply>
#include <QDBusConnection>
#include <QDBusServiceWatcher>
#include <QDBusPendingReply>
#include <QDir>
static const char *SERVICE = "org.ollie.SessionManager";
@ -63,13 +64,34 @@ void OllieDBusClient::setActiveSessionId(const QString &id)
fetchConfig();
}
// --- Async refresh sessions ---
void OllieDBusClient::refreshSessions()
{
QDBusReply<QStringList> reply = m_iface->call("ListSessions");
if (!reply.isValid()) return;
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 : reply.value()) {
for (const QString &entry : entries) {
QStringList parts = entry.split('\t');
if (parts.size() >= 4) {
QVariantMap session;
@ -112,20 +134,32 @@ void OllieDBusClient::refreshSessions()
}
}
// --- 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;
QDBusMessage reply = QDBusConnection::sessionBus().call(msg);
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(msg);
auto *watcher = new QDBusPendingCallWatcher(pending, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, &OllieDBusClient::onFetchChatFinished);
}
if (reply.type() != QDBusMessage::ReplyMessage) return;
QList<QVariant> args = reply.arguments();
if (args.size() < 2) return;
void OllieDBusClient::onFetchChatFinished(QDBusPendingCallWatcher *watcher)
{
m_fetchChatPending = false;
watcher->deleteLater();
QString text = args[0].toString();
qlonglong newOffset = args[1].toLongLong();
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) {
@ -138,6 +172,8 @@ void OllieDBusClient::fetchChat()
}
}
// --- Async fetch config ---
void OllieDBusClient::fetchConfig()
{
if (m_activeSessionId.isEmpty()) {
@ -150,13 +186,20 @@ void OllieDBusClient::fetchConfig()
QDBusMessage msg = QDBusMessage::createMethodCall(SERVICE, OBJ_PATH, IFACE, "GetConfig");
msg << m_activeSessionId;
QDBusMessage reply = QDBusConnection::sessionBus().call(msg);
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(msg);
auto *watcher = new QDBusPendingCallWatcher(pending, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, &OllieDBusClient::onFetchConfigFinished);
}
if (reply.type() != QDBusMessage::ReplyMessage) return;
QList<QVariant> args = reply.arguments();
if (args.isEmpty()) return;
void OllieDBusClient::onFetchConfigFinished(QDBusPendingCallWatcher *watcher)
{
watcher->deleteLater();
QString config = args[0].toString();
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) {
@ -172,77 +215,63 @@ void OllieDBusClient::fetchConfig()
}
}
// --- User-initiated actions (fire-and-forget async) ---
QString OllieDBusClient::createSession(const QString &cwd)
{
QString dir = cwd.isEmpty() ? QDir::homePath() : cwd;
QDBusReply<QString> reply = m_iface->call("CreateSession", dir, QString(), QString(), "default");
if (reply.isValid()) {
QString id = reply.value();
refreshSessions();
setActiveSessionId(id);
return id;
}
return QString();
// 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)
{
QDBusReply<bool> reply = m_iface->call("KillSession", sessionId);
if (reply.isValid() && reply.value()) {
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();
}
refreshSessions();
return true;
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();
}
return false;
m_iface->asyncCall("KillSession", sessionId);
return true;
}
bool OllieDBusClient::renameSession(const QString &sessionId, const QString &newName)
{
QDBusReply<bool> reply = m_iface->call("RenameSession", sessionId, newName);
if (reply.isValid() && reply.value()) {
if (sessionId == m_activeSessionId) {
m_activeSessionId = newName;
emit activeSessionIdChanged();
}
refreshSessions();
return true;
if (sessionId == m_activeSessionId) {
m_activeSessionId = newName;
emit activeSessionIdChanged();
}
return false;
m_iface->asyncCall("RenameSession", sessionId, newName);
return true;
}
bool OllieDBusClient::setConfig(const QString &sessionId, const QString &key, const QString &value)
{
QDBusReply<bool> reply = m_iface->call("SetConfig", sessionId, key, value);
if (reply.isValid() && reply.value()) {
if (sessionId == m_activeSessionId && key == "cwd") {
m_activeCwd = value;
emit activeCwdChanged();
}
return true;
if (sessionId == m_activeSessionId && key == "cwd") {
m_activeCwd = value;
emit activeCwdChanged();
}
return false;
m_iface->asyncCall("SetConfig", sessionId, key, value);
return true;
}
bool OllieDBusClient::submit(const QString &sessionId, const QString &prompt)
{
QDBusReply<bool> reply = m_iface->call("Submit", sessionId, prompt);
return reply.isValid() && reply.value();
m_iface->asyncCall("Submit", sessionId, prompt);
return true;
}
bool OllieDBusClient::interrupt(const QString &sessionId)
{
QDBusReply<bool> reply = m_iface->call("Interrupt", sessionId);
return reply.isValid() && reply.value();
m_iface->asyncCall("Interrupt", sessionId);
return true;
}
// --- D-Bus signal handlers ---

View File

@ -4,6 +4,7 @@
#include <QObject>
#include <QDBusInterface>
#include <QDBusConnection>
#include <QDBusPendingCallWatcher>
#include <QStringList>
#include <QVariantList>
#include <QTimer>
@ -57,6 +58,10 @@ private slots:
void onStateChanged(const QString &session_id, const QString &new_state);
void onChatUpdated(const QString &session_id, qlonglong offset, const QString &new_text);
void onRefreshSessionsFinished(QDBusPendingCallWatcher *watcher);
void onFetchChatFinished(QDBusPendingCallWatcher *watcher);
void onFetchConfigFinished(QDBusPendingCallWatcher *watcher);
private:
QDBusInterface *m_iface;
QVariantList m_sessions;
@ -66,7 +71,11 @@ private:
QString m_activeState = "idle";
QString m_activeCwd;
bool m_refreshPending = false;
bool m_fetchChatPending = false;
void fetchConfig();
void processSessionList(const QStringList &entries);
};
#endif // OLLIE_DBUS_CLIENT_H