plasmoid: inline session rename, cwd folder dialog, fix rename signal bug

- Double-click session name in list to rename inline (RET to confirm, ESC to cancel)
- Double-click cwd heading to open folder picker and change working directory
- Add setConfig() to QML D-Bus client
- Fix capi.go: chat/state callbacks used stale sessID after rename
- Makefile: kf5 target uses trap to always restore CMakeLists.txt
This commit is contained in:
Levi Neely 2026-07-06 12:33:14 +02:00
parent 28ce943e45
commit 9701d8f67c
6 changed files with 156 additions and 16 deletions

View File

@ -21,11 +21,11 @@ daemon: lib
# Step 2b: Build using KF5/Qt5 (for Plasma 5.27) # Step 2b: Build using KF5/Qt5 (for Plasma 5.27)
kf5: lib kf5: lib
cp CMakeLists.txt CMakeLists.txt.kf6.bak @cp CMakeLists.txt CMakeLists.txt.kf6.bak && \
cp CMakeLists-kf5.txt CMakeLists.txt trap 'mv -f CMakeLists.txt.kf6.bak CMakeLists.txt' EXIT && \
cmake -B $(BUILD_DIR) -S . --fresh cp CMakeLists-kf5.txt CMakeLists.txt && \
cmake --build $(BUILD_DIR) cmake -B $(BUILD_DIR) -S . --fresh && \
mv CMakeLists.txt.kf6.bak CMakeLists.txt cmake --build $(BUILD_DIR)
# Install the KF5 build # Install the KF5 build
kf5-install: kf5 kf5-install: kf5
@ -46,15 +46,13 @@ kf5-install: kf5
/usr/lib/x86_64-linux-gnu/qt5/qml/org/ollie/private/ /usr/lib/x86_64-linux-gnu/qt5/qml/org/ollie/private/
sudo install -m644 plasmoid/plugin/qmldir \ sudo install -m644 plasmoid/plugin/qmldir \
/usr/lib/x86_64-linux-gnu/qt5/qml/org/ollie/private/ /usr/lib/x86_64-linux-gnu/qt5/qml/org/ollie/private/
# Plasmoid package (swap metadata + QML for Plasma 5) # Plasmoid package (use temp copy to avoid clobbering source tree ownership)
cp plasmoid/metadata.json plasmoid/metadata.json.kf6.bak cp -a plasmoid /tmp/ollie-plasmoid-kf5
cp plasmoid/contents/ui/main.qml plasmoid/contents/ui/main.qml.kf6.bak cp plasmoid/metadata-kf5.json /tmp/ollie-plasmoid-kf5/metadata.json
cp plasmoid/metadata-kf5.json plasmoid/metadata.json cp plasmoid/contents/ui/main-kf5.qml /tmp/ollie-plasmoid-kf5/contents/ui/main.qml
cp plasmoid/contents/ui/main-kf5.qml plasmoid/contents/ui/main.qml kpackagetool5 -t Plasma/Applet -u /tmp/ollie-plasmoid-kf5/ 2>/dev/null || \
kpackagetool5 -t Plasma/Applet -u plasmoid/ 2>/dev/null || \ kpackagetool5 -t Plasma/Applet -i /tmp/ollie-plasmoid-kf5/
kpackagetool5 -t Plasma/Applet -i plasmoid/ $(RM) -r /tmp/ollie-plasmoid-kf5
mv plasmoid/metadata.json.kf6.bak plasmoid/metadata.json
mv plasmoid/contents/ui/main.qml.kf6.bak plasmoid/contents/ui/main.qml
# Dolphin service menus + helpers # Dolphin service menus + helpers
mkdir -p ~/.local/share/kio/servicemenus mkdir -p ~/.local/share/kio/servicemenus
install -m755 dolphin/ollie-actions.desktop ~/.local/share/kio/servicemenus/ install -m755 dolphin/ollie-actions.desktop ~/.local/share/kio/servicemenus/

View File

@ -207,7 +207,7 @@ func ollie_create_session(cwdC *C.char, backendC *C.char, modelC *C.char, agentC
sess.logMu.Unlock() sess.logMu.Unlock()
if chatCB != nil { if chatCB != nil {
cID := C.CString(sessID) cID := C.CString(sess.id)
cText := C.CString(text) cText := C.CString(text)
C.ollie_chat_cb_invoke(chatCB, cID, C.int64_t(offset), cText, C.int(len(text))) C.ollie_chat_cb_invoke(chatCB, cID, C.int64_t(offset), cText, C.int(len(text)))
C.free(unsafe.Pointer(cID)) C.free(unsafe.Pointer(cID))
@ -225,7 +225,7 @@ func ollie_create_session(cwdC *C.char, backendC *C.char, modelC *C.char, agentC
} }
current = next current = next
if stateCB != nil { if stateCB != nil {
cID := C.CString(sessID) cID := C.CString(sess.id)
cState := C.CString(current) cState := C.CString(current)
C.ollie_state_cb_invoke(stateCB, cID, cState) C.ollie_state_cb_invoke(stateCB, cID, cState)
C.free(unsafe.Pointer(cID)) C.free(unsafe.Pointer(cID))

View File

@ -1,6 +1,7 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.3
import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.components 3.0 as PlasmaComponents
@ -13,6 +14,17 @@ Item {
readonly property var dbus: Ollie.OllieDBusClient readonly property var dbus: Ollie.OllieDBusClient
FileDialog {
id: folderDialog
title: "Choose working directory"
selectFolder: true
folder: dbus.activeCwd ? "file://" + dbus.activeCwd : shortcuts.home
onAccepted: {
var path = fileUrl.toString().replace("file://", "")
dbus.setConfig(dbus.activeSessionId, "cwd", path)
}
}
Plasmoid.status: dbus.sessions.length > 0 Plasmoid.status: dbus.sessions.length > 0
? PlasmaCore.Types.ActiveStatus ? PlasmaCore.Types.ActiveStatus
: PlasmaCore.Types.PassiveStatus : PlasmaCore.Types.PassiveStatus
@ -90,6 +102,15 @@ Item {
text: dbus.activeCwd || "Ollie" text: dbus.activeCwd || "Ollie"
Layout.fillWidth: true Layout.fillWidth: true
elide: Text.ElideMiddle elide: Text.ElideMiddle
MouseArea {
anchors.fill: parent
onDoubleClicked: {
if (dbus.activeSessionId !== "") {
folderDialog.open()
}
}
}
} }
PlasmaComponents.ToolButton { PlasmaComponents.ToolButton {
@ -111,9 +132,12 @@ Item {
model: dbus.sessions model: dbus.sessions
delegate: ItemDelegate { delegate: ItemDelegate {
id: sessionDelegate
width: sessionList.width width: sessionList.width
highlighted: modelData.id === dbus.activeSessionId highlighted: modelData.id === dbus.activeSessionId
property bool renaming: false
contentItem: RowLayout { contentItem: RowLayout {
spacing: 6 spacing: 6
@ -131,27 +155,60 @@ Item {
} }
Label { Label {
id: sessionLabel
text: modelData.id text: modelData.id
Layout.fillWidth: true Layout.fillWidth: true
elide: Text.ElideRight elide: Text.ElideRight
font.family: "monospace" font.family: "monospace"
font.pixelSize: 11 font.pixelSize: 11
visible: !sessionDelegate.renaming
}
TextField {
id: inlineRenameField
Layout.fillWidth: true
font.family: "monospace"
font.pixelSize: 11
visible: sessionDelegate.renaming
onAccepted: {
var newName = text.trim()
if (newName !== "" && newName !== modelData.id) {
dbus.renameSession(modelData.id, newName)
}
sessionDelegate.renaming = false
}
Keys.onEscapePressed: sessionDelegate.renaming = false
onVisibleChanged: {
if (visible) {
text = modelData.id
selectAll()
forceActiveFocus()
}
}
onActiveFocusChanged: {
if (!activeFocus && sessionDelegate.renaming) {
sessionDelegate.renaming = false
}
}
} }
Label { Label {
text: modelData.agent text: modelData.agent
opacity: 0.7 opacity: 0.7
font.pixelSize: 11 font.pixelSize: 11
visible: !sessionDelegate.renaming
} }
PlasmaComponents.ToolButton { PlasmaComponents.ToolButton {
icon.name: "process-stop" icon.name: "process-stop"
implicitWidth: 22; implicitHeight: 22 implicitWidth: 22; implicitHeight: 22
onClicked: dbus.killSession(modelData.id) onClicked: dbus.killSession(modelData.id)
visible: !sessionDelegate.renaming
} }
} }
onClicked: dbus.activeSessionId = modelData.id onClicked: dbus.activeSessionId = modelData.id
onDoubleClicked: sessionDelegate.renaming = true
} }
} }

View File

@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Dialogs
import org.kde.plasma.plasmoid import org.kde.plasma.plasmoid
import org.kde.plasma.components as PlasmaComponents import org.kde.plasma.components as PlasmaComponents
import org.kde.plasma.extras as PlasmaExtras import org.kde.plasma.extras as PlasmaExtras
@ -13,6 +14,16 @@ PlasmoidItem {
// Convenience alias // Convenience alias
readonly property var dbus: Ollie.OllieDBusClient readonly property var dbus: Ollie.OllieDBusClient
FolderDialog {
id: folderDialog
title: "Choose working directory"
currentFolder: dbus.activeCwd ? "file://" + dbus.activeCwd : StandardPaths.writableLocation(StandardPaths.HomeLocation)
onAccepted: {
let path = selectedFolder.toString().replace("file://", "")
dbus.setConfig(dbus.activeSessionId, "cwd", path)
}
}
preferredRepresentation: compactRepresentation preferredRepresentation: compactRepresentation
toolTipMainText: "Ollie" toolTipMainText: "Ollie"
@ -88,6 +99,15 @@ PlasmoidItem {
text: dbus.activeCwd || "Ollie" text: dbus.activeCwd || "Ollie"
Layout.fillWidth: true Layout.fillWidth: true
elide: Text.ElideMiddle elide: Text.ElideMiddle
MouseArea {
anchors.fill: parent
onDoubleClicked: {
if (dbus.activeSessionId !== "") {
folderDialog.open()
}
}
}
} }
PlasmaComponents.ToolButton { PlasmaComponents.ToolButton {
@ -109,9 +129,12 @@ PlasmoidItem {
model: dbus.sessions model: dbus.sessions
delegate: ItemDelegate { delegate: ItemDelegate {
id: sessionDelegate
width: sessionList.width width: sessionList.width
highlighted: modelData.id === dbus.activeSessionId highlighted: modelData.id === dbus.activeSessionId
property bool renaming: false
contentItem: RowLayout { contentItem: RowLayout {
spacing: 6 spacing: 6
@ -129,27 +152,60 @@ PlasmoidItem {
} }
Label { Label {
id: sessionLabel
text: modelData.id text: modelData.id
Layout.fillWidth: true Layout.fillWidth: true
elide: Text.ElideRight elide: Text.ElideRight
font.family: "monospace" font.family: "monospace"
font.pixelSize: 11 font.pixelSize: 11
visible: !sessionDelegate.renaming
}
TextField {
id: inlineRenameField
Layout.fillWidth: true
font.family: "monospace"
font.pixelSize: 11
visible: sessionDelegate.renaming
onAccepted: {
let newName = text.trim()
if (newName !== "" && newName !== modelData.id) {
dbus.renameSession(modelData.id, newName)
}
sessionDelegate.renaming = false
}
Keys.onEscapePressed: sessionDelegate.renaming = false
onVisibleChanged: {
if (visible) {
text = modelData.id
selectAll()
forceActiveFocus()
}
}
onActiveFocusChanged: {
if (!activeFocus && sessionDelegate.renaming) {
sessionDelegate.renaming = false
}
}
} }
Label { Label {
text: modelData.agent text: modelData.agent
opacity: 0.7 opacity: 0.7
font.pixelSize: 11 font.pixelSize: 11
visible: !sessionDelegate.renaming
} }
PlasmaComponents.ToolButton { PlasmaComponents.ToolButton {
icon.name: "process-stop" icon.name: "process-stop"
implicitWidth: 22; implicitHeight: 22 implicitWidth: 22; implicitHeight: 22
onClicked: dbus.killSession(modelData.id) onClicked: dbus.killSession(modelData.id)
visible: !sessionDelegate.renaming
} }
} }
onClicked: dbus.activeSessionId = modelData.id onClicked: dbus.activeSessionId = modelData.id
onDoubleClicked: sessionDelegate.renaming = true
} }
} }

View File

@ -183,6 +183,33 @@ bool OllieDBusClient::killSession(const QString &sessionId)
return false; return false;
} }
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;
}
return false;
}
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;
}
return false;
}
bool OllieDBusClient::submit(const QString &sessionId, const QString &prompt) bool OllieDBusClient::submit(const QString &sessionId, const QString &prompt)
{ {
QDBusReply<bool> reply = m_iface->call("Submit", sessionId, prompt); QDBusReply<bool> reply = m_iface->call("Submit", sessionId, prompt);

View File

@ -39,6 +39,8 @@ public:
Q_INVOKABLE void fetchChat(); Q_INVOKABLE void fetchChat();
Q_INVOKABLE QString createSession(const QString &cwd); Q_INVOKABLE QString createSession(const QString &cwd);
Q_INVOKABLE bool killSession(const QString &sessionId); Q_INVOKABLE bool killSession(const QString &sessionId);
Q_INVOKABLE bool renameSession(const QString &sessionId, const QString &newName);
Q_INVOKABLE bool setConfig(const QString &sessionId, const QString &key, const QString &value);
Q_INVOKABLE bool submit(const QString &sessionId, const QString &prompt); Q_INVOKABLE bool submit(const QString &sessionId, const QString &prompt);
Q_INVOKABLE bool interrupt(const QString &sessionId); Q_INVOKABLE bool interrupt(const QString &sessionId);