diff --git a/cmd/olliesrv/internal/agent/agent.go b/cmd/olliesrv/internal/agent/agent.go index aeba3c0..cf6152f 100644 --- a/cmd/olliesrv/internal/agent/agent.go +++ b/cmd/olliesrv/internal/agent/agent.go @@ -64,6 +64,7 @@ type Agent struct { save func() // trigger debounced persistence flush func() // immediately flush persistence onStateChange func(agentID, state string) // optional callback for state changes + onProcStart func(agentID string, pid int, tool, cmd string) // optional callback for proc start // Chat log — the rendered output stream, exposed via 9P. chatMu sync.RWMutex diff --git a/cmd/olliesrv/internal/agent/dispatch.go b/cmd/olliesrv/internal/agent/dispatch.go index d6fd33b..1438735 100644 --- a/cmd/olliesrv/internal/agent/dispatch.go +++ b/cmd/olliesrv/internal/agent/dispatch.go @@ -252,6 +252,10 @@ func (ag *Agent) execOne(ctx context.Context, call backend.ToolCall) (toolResult isErr = true } else { result = fmt.Sprintf("\nid=%d cmd=%q\n", pid, cmdDesc) + // Notify proc.start via callback + if ag.onProcStart != nil { + ag.onProcStart(ag.id, pid, call.Name, cmdDesc) + } } } ag.emit(Event{Role: "tool", Name: call.Name, Content: result, OutputFormat: toolOutputFormat(rt, call.Name)}) diff --git a/cmd/olliesrv/internal/agent/state.go b/cmd/olliesrv/internal/agent/state.go index f7fb205..41358b1 100644 --- a/cmd/olliesrv/internal/agent/state.go +++ b/cmd/olliesrv/internal/agent/state.go @@ -82,6 +82,11 @@ func (ag *Agent) SetOnStateChange(fn func(agentID, state string)) { ag.onStateChange = fn } +// SetOnProcStart sets a callback invoked when a background process starts. +func (ag *Agent) SetOnProcStart(fn func(agentID string, pid int, tool, cmd string)) { + ag.onProcStart = fn +} + // setReply sets the agent's last response. func (ag *Agent) setReply(reply string) { ag.stateMu.Lock() diff --git a/cmd/olliesrv/internal/fs/spec.go b/cmd/olliesrv/internal/fs/spec.go index 80190e5..ad7396e 100644 --- a/cmd/olliesrv/internal/fs/spec.go +++ b/cmd/olliesrv/internal/fs/spec.go @@ -245,6 +245,18 @@ func buildTreeSpec(cfg *Config) virtfs.FsNodeDecl { return nil, base, nil }), ), + virtfs.FileNode("event.pub", 0220, + virtfs.Doc("Publish event: write topicpayload"), + virtfs.Write(func(data []byte) error { + line := strings.TrimSpace(string(data)) + topic, payload, _ := strings.Cut(line, "\t") + if topic == "" { + return fmt.Errorf("empty topic") + } + session.PublishEvent(topic, payload) + return nil + }), + ), virtfs.FileNode("generate", 0666, virtfs.Doc("One-shot LLM generation"), virtfs.Rdwr(func(_ context.Context, data []byte) ([]byte, error) { @@ -1105,7 +1117,7 @@ func buildAgentChildren(a *agent.Agent, s *session.Session) []virtfs.FsNodeDecl {"cwd", "print the working directory", func(args []string) ([]byte, error) { return []byte(a.Cwd() + "\n"), nil }}, - {"proc", "manage background procs: proc [top|term|kill|out|dismiss ]", func(args []string) ([]byte, error) { + {"proc", "manage background procs: proc [top|idx|term|kill|out|dismiss ]", func(args []string) ([]byte, error) { ts := a.ToolServer() if ts == nil { return nil, fmt.Errorf("no tool server") @@ -1122,6 +1134,12 @@ func buildAgentChildren(a *agent.Agent, s *session.Session) []virtfs.FsNodeDecl } subcmd := args[0] switch subcmd { + case "idx": + data, err := ts.ListProcsIdx() + if err != nil { + return nil, err + } + return data, nil case "term": if len(args) < 2 { return nil, fmt.Errorf("proc term requires pid") @@ -1172,7 +1190,7 @@ func buildAgentChildren(a *agent.Agent, s *session.Session) []virtfs.FsNodeDecl } return []byte("ok\n"), nil default: - return nil, fmt.Errorf("unknown proc subcommand: %s (use: top, term , kill , out , dismiss )", subcmd) + return nil, fmt.Errorf("unknown proc subcommand: %s (use: top, idx, term , kill , out , dismiss )", subcmd) } }}, {"systemprompt", "print the rendered system prompt", func(_ []string) ([]byte, error) { diff --git a/cmd/olliesrv/internal/fs/support.go b/cmd/olliesrv/internal/fs/support.go index a72189d..25a2622 100644 --- a/cmd/olliesrv/internal/fs/support.go +++ b/cmd/olliesrv/internal/fs/support.go @@ -21,6 +21,11 @@ func wireAgentEvents(sessID string, ag *agent.Agent) { topic := "session." + sessID + ".agent." + agentID + ".state" session.PublishEvent(topic, state) }) + ag.SetOnProcStart(func(agentID string, pid int, tool, cmd string) { + topic := "session." + sessID + ".agent." + agentID + ".proc.start" + payload := fmt.Sprintf("%d\t%s\t%s", pid, tool, cmd) + session.PublishEvent(topic, payload) + }) } // ctlCmd is one control verb: its name, a one-line description for the diff --git a/cmd/olliesrv/internal/toolclient/toolsrv.go b/cmd/olliesrv/internal/toolclient/toolsrv.go index 143e21e..2bcdc90 100644 --- a/cmd/olliesrv/internal/toolclient/toolsrv.go +++ b/cmd/olliesrv/internal/toolclient/toolsrv.go @@ -408,6 +408,23 @@ func (c *ToolsrvConn) ListProcs() ([]byte, error) { return io.ReadAll(fid) } +// ListProcsIdx returns a machine-readable TSV proc listing. +// Format: pidstatusexit_codetoolcmdstart_unixruntime_sec +func (c *ToolsrvConn) ListProcsIdx() ([]byte, error) { + fid, err := c.fsys.Open("proc/idx", plan9.ORDWR) + if err != nil { + return nil, err + } + defer fid.Close() + if _, err := fid.Write([]byte(c.agentID)); err != nil { + return nil, err + } + if _, err := fid.Seek(0, 0); err != nil { + return nil, err + } + return io.ReadAll(fid) +} + // readProcInfo reads info about a single proc. func (c *ToolsrvConn) readProcInfo(name string) map[string]any { path := fmt.Sprintf("proc/%s/stat", name) diff --git a/cmd/toolsrv/internal/exec/exec.go b/cmd/toolsrv/internal/exec/exec.go index 3fe258c..c07a114 100644 --- a/cmd/toolsrv/internal/exec/exec.go +++ b/cmd/toolsrv/internal/exec/exec.go @@ -293,6 +293,16 @@ func executeDirectUnsandboxed(ctx context.Context, cmd, cwd string, env map[stri } } + // Set process group so we can signal all children + execCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true, Pdeathsig: syscall.SIGKILL} + execCmd.Cancel = func() error { + if execCmd.Process != nil { + syscall.Kill(-execCmd.Process.Pid, syscall.SIGTERM) + } + return nil + } + execCmd.WaitDelay = 5 * time.Second + var stdout, stderr bytes.Buffer if streamOut != nil { // Streaming mode: tee to both the stream and local buffer diff --git a/cmd/toolsrv/internal/server/proc.go b/cmd/toolsrv/internal/server/proc.go index 5d5ebbf..e9ec9b8 100644 --- a/cmd/toolsrv/internal/server/proc.go +++ b/cmd/toolsrv/internal/server/proc.go @@ -518,6 +518,40 @@ func (st *State) ListProcsForAgent(agentID string) string { return buf.String() } +// ListProcsIdxForAgent returns a machine-readable TSV list of processes. +// If agentID is empty, returns all processes. +// Format: pidstatusexit_codetoolcmdstart_unixruntime_sec +func (st *State) ListProcsIdxForAgent(agentID string) string { + st.procMu.Lock() + defer st.procMu.Unlock() + var buf bytes.Buffer + for pid, p := range st.procs { + p.mu.Lock() + if agentID != "" && p.AgentID != agentID { + p.mu.Unlock() + continue + } + status := "running" + exitCode := -1 + if p.Exited { + status = "exited" + exitCode = p.ExitCode + } + startUnix := p.StartTime.Unix() + var runtime float64 + if p.Exited && !p.EndTime.IsZero() { + runtime = p.EndTime.Sub(p.StartTime).Seconds() + } else { + runtime = time.Since(p.StartTime).Seconds() + } + tool := p.Tool + cmd := p.Cmd + p.mu.Unlock() + fmt.Fprintf(&buf, "%d\t%s\t%d\t%s\t%s\t%d\t%.1f\n", pid, status, exitCode, tool, cmd, startUnix, runtime) + } + return buf.String() +} + // KillAll sends SIGKILL to all running procs and cancels their contexts. // Called on shutdown to ensure no orphaned processes. func (st *State) KillAll() { diff --git a/cmd/toolsrv/internal/server/server.go b/cmd/toolsrv/internal/server/server.go index 404cb4f..e93bf63 100644 --- a/cmd/toolsrv/internal/server/server.go +++ b/cmd/toolsrv/internal/server/server.go @@ -195,6 +195,13 @@ func Spec(srv *Server) virtfs.FsNodeDecl { return []byte(srv.Fs.ListProcsForAgent(agentID)), nil }), ), + virtfs.FileNode("idx", 0666, + virtfs.Doc("Machine-readable proc index: write agent ID, read TSV (pid status exit_code tool cmd start_unix runtime_sec)"), + virtfs.Rdwr(func(_ context.Context, data []byte) ([]byte, error) { + agentID := strings.TrimSpace(string(data)) + return []byte(srv.Fs.ListProcsIdxForAgent(agentID)), nil + }), + ), virtfs.FileNode("new", 0666, virtfs.Doc("Execute tool: write token + tool + args, read result (blocking)"), virtfs.Rdwr(func(ctx context.Context, data []byte) ([]byte, error) { diff --git a/cmd/toolsrv/main.go b/cmd/toolsrv/main.go index db972d7..9cc639a 100644 --- a/cmd/toolsrv/main.go +++ b/cmd/toolsrv/main.go @@ -241,13 +241,15 @@ monitor: } } -// pushProcInterrupt pushes the completed proc output to the agent's prompt. +// pushProcInterrupt pushes the completed proc output to the agent's prompt +// and publishes a proc.exit event. func pushProcInterrupt(ctx context.Context, proc *server.Proc) { // Build the interrupt message proc.Lock() output := proc.OutputString() exitCode := proc.ExitCode cmd := proc.Cmd + tool := proc.Tool pid := proc.ID sid := proc.SessionID aid := proc.AgentID @@ -290,6 +292,15 @@ func pushProcInterrupt(ctx context.Context, proc *server.Proc) { } defer fsys.Close() + // Publish proc.exit event + eventPath := "event.pub" + eventTopic := fmt.Sprintf("session.%s.agent.%s.proc.exit", sid, aid) + eventPayload := fmt.Sprintf("%d\t%d\t%s\t%s", pid, exitCode, tool, cmd) + if evFid, err := fsys.Open(eventPath, plan9.OWRITE); err == nil { + evFid.Write([]byte(eventTopic + "\t" + eventPayload)) + evFid.Close() + } + // Write to session/{sid}/agent/{aid}/prompt promptPath := fmt.Sprintf("session/%s/agent/%s/prompt", sid, aid) fid, err := fsys.Open(promptPath, plan9.OWRITE) diff --git a/doc/design/proc-window.md b/doc/design/proc-window.md new file mode 100644 index 0000000..16ee260 --- /dev/null +++ b/doc/design/proc-window.md @@ -0,0 +1,144 @@ +# Proc Window Feature Design + +## Overview + +A dedicated window for managing background processes (tools running in background mode). Accessible via context menu on sessions/agents in the tree view. + +## User Flow + +1. Right-click session/agent → "Processes..." +2. Opens ProcWindow showing all background procs for that scope +3. Window tracks the active session (when main GUI switches session, proc window updates) +4. Can also be "pinned" to a specific session/agent + +## UI Components + +### ProcWindow.qml + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Processes - {session}/{agent} [📌] [×] │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ PID │ Tool │ Status │ Runtime │ Actions │ │ +│ ├─────┼───────────┼─────────┼──────────┼─────────────────┤ │ +│ │ 5 │ shell │ running │ 2m 34s │ [▶] [■] [×] │ │ +│ │ 3 │ web_fetch │ exited │ 45s │ [▶] [🗑] │ │ +│ │ 1 │ shell │ exited │ 12s │ [▶] [🗑] │ │ +│ └─────────────────────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────────────┤ +│ Output for PID 5 [Clear] │ +├─────────────────────────────────────────────────────────────┤ +│ $ npm install │ +│ npm WARN deprecated... │ +│ added 123 packages in 45s │ +│ █ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Actions + +| Icon | Action | Description | +|------|--------|-------------| +| ▶ | Stream | Open output stream (updates in real-time) | +| ■ | Term | Send SIGTERM (graceful stop) | +| × | Kill | Send SIGKILL (force kill) | +| 🗑 | Dismiss | Remove from list (exited procs only) | + +### Features + +1. **Live refresh**: Subscribe to proc events or poll `proc idx` +2. **Output streaming**: Read proc output via `proc out ` +3. **Session tracking**: Follow active session in main GUI (optional pin) +4. **Multi-agent view**: When opened on session, show all agents' procs +5. **Auto-scroll**: Output pane auto-scrolls unless user scrolls up + +## Backend Implementation Status + +### ✅ Phase 1: Backend (DONE) + +#### Proc Events +- `session.{sid}.agent.{aid}.proc.start` — emitted when background proc spawns + - Payload: `{pid}\t{tool}\t{cmd}` +- `session.{sid}.agent.{aid}.proc.exit` — emitted when background proc exits + - Payload: `{pid}\t{exit_code}\t{tool}\t{cmd}` + +#### Machine-Readable Proc Index +- `proc idx` ctl command returns TSV format: + - `pidstatusexit_codetoolcmdstart_unixruntime_sec` +- Status is `running` or `exited` +- Exit code is `-1` for running processes + +#### Event Publishing Endpoint +- `event.pub` file at root namespace accepts writes: `topicpayload` +- Used by toolsrv to publish proc.exit events back to olliesrv + +### 🔲 Phase 2: GUI (TODO) + +1. Create ProcModel (C++ model for proc list) +2. Create ProcWindow.qml +3. Add context menu action to SessionTree.qml +4. Wire up event subscription for live updates + +### 🔲 Phase 3: Polish (TODO) + +1. Session tracking / pin toggle +2. Output search/filter +3. Keyboard shortcuts +4. Copy output to clipboard + +## Files Modified (Phase 1) + +- `cmd/olliesrv/internal/agent/agent.go` — added `onProcStart` callback +- `cmd/olliesrv/internal/agent/state.go` — added `SetOnProcStart` +- `cmd/olliesrv/internal/agent/dispatch.go` — call callback on proc start +- `cmd/olliesrv/internal/fs/support.go` — wire callback to publish event +- `cmd/olliesrv/internal/fs/spec.go` — added `event.pub` file, `proc idx` command +- `cmd/olliesrv/internal/toolclient/toolsrv.go` — added `ListProcsIdx` +- `cmd/toolsrv/internal/server/proc.go` — added `ListProcsIdxForAgent` +- `cmd/toolsrv/internal/server/server.go` — added `proc/idx` file +- `cmd/toolsrv/main.go` — publish proc.exit event via event.pub +- `doc/event-topics.md` — documented new proc events + +## API Reference + +### ctl Commands + +```bash +# Human-readable proc list +echo "proc top" | ollie-9p rdwr session/$SID/agent/$AID/ctl + +# Machine-readable proc index (TSV) +echo "proc idx" | ollie-9p rdwr session/$SID/agent/$AID/ctl + +# Get proc output +echo "proc out 5" | ollie-9p rdwr session/$SID/agent/$AID/ctl + +# Signal proc +echo "proc term 5" | ollie-9p rdwr session/$SID/agent/$AID/ctl +echo "proc kill 5" | ollie-9p rdwr session/$SID/agent/$AID/ctl + +# Dismiss exited proc +echo "proc dismiss 5" | ollie-9p rdwr session/$SID/agent/$AID/ctl +``` + +### Event Subscription + +```bash +# All proc events for all sessions/agents +echo "session.*.agent.*.proc.>" | ollie-9p rdwrs event + +# Proc events for specific session +echo "session.abc123.agent.*.proc.>" | ollie-9p rdwrs event + +# Only proc.start events +echo "session.*.agent.*.proc.start" | ollie-9p rdwrs event +``` + +### proc idx Output Format + +``` +pid status exit_code tool cmd start_unix runtime_sec +5 running -1 shell make test 1723456789 45.2 +3 exited 0 web_fetch fetch docs 1723456700 12.5 +``` diff --git a/doc/event-topics.md b/doc/event-topics.md index 9041414..e838ebf 100644 --- a/doc/event-topics.md +++ b/doc/event-topics.md @@ -29,6 +29,13 @@ session.{sessionId}.agent.{agentId}.{type} | `session.{sid}.agent.{aid}.bypass.request` | `{id}\t{cmd}\t{cwd}` | Bypass approval requested | | `session.{sid}.agent.{aid}.bypass.resolved` | `{id}\t{action}` | Bypass resolved (approved/denied) | +## Process Events + +| Topic | Payload | Description | +|-------|---------|-------------| +| `session.{sid}.agent.{aid}.proc.start` | `{pid}\t{tool}\t{cmd}` | Background process started | +| `session.{sid}.agent.{aid}.proc.exit` | `{pid}\t{exit_code}\t{tool}\t{cmd}` | Background process exited | + ## Filter Patterns Subscribe to specific events using the streaming rdwr pattern: diff --git a/kde/gui/ChatPane.qml b/kde/gui/ChatPane.qml index f0ba59a..b23e382 100644 --- a/kde/gui/ChatPane.qml +++ b/kde/gui/ChatPane.qml @@ -620,6 +620,22 @@ Connections { chatPane.hasPendingBypass = false } } + + // Public functions for keyboard shortcuts + function approveBypass() { + if (chatPane.pendingBypass) { + ollie.resolveBypass(chatPane.pendingBypass.sessionId, chatPane.pendingBypass.agentId, chatPane.pendingBypass.id, true) + chatPane.pendingBypass = null + chatPane.hasPendingBypass = false + } + } + function denyBypass() { + if (chatPane.pendingBypass) { + ollie.resolveBypass(chatPane.pendingBypass.sessionId, chatPane.pendingBypass.agentId, chatPane.pendingBypass.id, false) + chatPane.pendingBypass = null + chatPane.hasPendingBypass = false + } + } } } diff --git a/kde/gui/SettingsDialog.qml b/kde/gui/SettingsDialog.qml index 39ca4a6..57b2b50 100644 --- a/kde/gui/SettingsDialog.qml +++ b/kde/gui/SettingsDialog.qml @@ -6,7 +6,7 @@ import QtQuick.Layouts 1.15 Dialog { id: root title: "Settings" - width: 380 + width: 420 modal: false x: (parent ? (parent.width - width) / 2 : 0) y: (parent ? (parent.height - height) / 2 : 0) @@ -117,6 +117,51 @@ Dialog { Layout.topMargin: 8 } + Label { + text: "Keyboard Shortcuts" + font.bold: true + } + + GridLayout { + columns: 2 + columnSpacing: 12 + rowSpacing: 8 + Layout.fillWidth: true + + Label { text: "Approve bypass:" } + TextField { + id: approveKeyField + Layout.fillWidth: true + text: theme.approveBypassKey + placeholderText: "e.g. Ctrl+Y" + onEditingFinished: theme.approveBypassKey = text + } + + Label { text: "Deny bypass:" } + TextField { + id: denyKeyField + Layout.fillWidth: true + text: theme.denyBypassKey + placeholderText: "e.g. Ctrl+N" + onEditingFinished: theme.denyBypassKey = text + } + } + + Label { + text: "Use Qt key sequence format (e.g. Ctrl+Y, Alt+A, F5)" + opacity: 0.5 + font.pixelSize: Qt.application.font.pixelSize * 0.85 + wrapMode: Text.Wrap + Layout.fillWidth: true + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: palette.mid + Layout.topMargin: 8 + } + Label { text: "Font" font.bold: true diff --git a/kde/gui/main.qml b/kde/gui/main.qml index 4176ade..c84c254 100644 --- a/kde/gui/main.qml +++ b/kde/gui/main.qml @@ -78,6 +78,19 @@ ApplicationWindow { } } + // Bypass approval/denial shortcuts (configurable in Settings) + Shortcut { + sequence: theme.approveBypassKey + enabled: theme.approveBypassKey !== "" && chatPane.hasPendingBypass + onActivated: chatPane.approveBypass() + } + + Shortcut { + sequence: theme.denyBypassKey + enabled: theme.denyBypassKey !== "" && chatPane.hasPendingBypass + onActivated: chatPane.denyBypass() + } + property bool sidebarVisible: true property bool quitting: false @@ -210,6 +223,7 @@ ApplicationWindow { } ChatPane { + id: chatPane SplitView.fillWidth: true SplitView.fillHeight: true sidebarVisible: root.sidebarVisible diff --git a/kde/gui/thememanager.cpp b/kde/gui/thememanager.cpp index 25ddeee..13107d2 100644 --- a/kde/gui/thememanager.cpp +++ b/kde/gui/thememanager.cpp @@ -394,6 +394,34 @@ void ThemeManager::setCloseToTray(bool enabled) emit closeToTrayChanged(); } +QString ThemeManager::approveBypassKey() const +{ + return m_approveBypassKey; +} + +void ThemeManager::setApproveBypassKey(const QString &key) +{ + if (key == m_approveBypassKey) + return; + m_approveBypassKey = key; + saveSettings(); + emit shortcutsChanged(); +} + +QString ThemeManager::denyBypassKey() const +{ + return m_denyBypassKey; +} + +void ThemeManager::setDenyBypassKey(const QString &key) +{ + if (key == m_denyBypassKey) + return; + m_denyBypassKey = key; + saveSettings(); + emit shortcutsChanged(); +} + void ThemeManager::resetFont() { m_fontFamily.clear(); @@ -454,6 +482,13 @@ void ThemeManager::loadSettings() m_fontStyle = settings.value("Font/style", "").toString(); m_closeToTray = settings.value("General/closeToTray", false).toBool(); + + m_approveBypassKey = settings.value("Shortcuts/approveBypass", "Ctrl+Y").toString(); + m_denyBypassKey = settings.value("Shortcuts/denyBypass", "Ctrl+N").toString(); + } else { + // Defaults when no config file exists + m_approveBypassKey = "Ctrl+Y"; + m_denyBypassKey = "Ctrl+N"; } } @@ -469,4 +504,6 @@ void ThemeManager::saveSettings() settings.setValue("Font/size", m_fontSize); settings.setValue("Font/style", m_fontStyle); settings.setValue("General/closeToTray", m_closeToTray); + settings.setValue("Shortcuts/approveBypass", m_approveBypassKey); + settings.setValue("Shortcuts/denyBypass", m_denyBypassKey); } diff --git a/kde/gui/thememanager.h b/kde/gui/thememanager.h index 8a8a26a..00a6b3d 100644 --- a/kde/gui/thememanager.h +++ b/kde/gui/thememanager.h @@ -49,6 +49,10 @@ class ThemeManager : public QObject // Close-to-tray Q_PROPERTY(bool closeToTray READ closeToTray WRITE setCloseToTray NOTIFY closeToTrayChanged) + // Shortcut keys + Q_PROPERTY(QString approveBypassKey READ approveBypassKey WRITE setApproveBypassKey NOTIFY shortcutsChanged) + Q_PROPERTY(QString denyBypassKey READ denyBypassKey WRITE setDenyBypassKey NOTIFY shortcutsChanged) + public: enum Mode { System, // Follow KDE system color scheme @@ -99,6 +103,12 @@ public: bool closeToTray() const; void setCloseToTray(bool enabled); + // Shortcut keys + QString approveBypassKey() const; + void setApproveBypassKey(const QString &key); + QString denyBypassKey() const; + void setDenyBypassKey(const QString &key); + Q_INVOKABLE void resetFont(); Q_INVOKABLE QString chooseFolder(const QString &title, const QString &startDir = QString()); @@ -110,6 +120,7 @@ signals: void paletteChanged(); void fontChanged(); void closeToTrayChanged(); + void shortcutsChanged(); private: QPalette paletteFromScheme(const QString &path) const; @@ -126,6 +137,8 @@ private: int m_fontSize; QString m_fontStyle; bool m_closeToTray; + QString m_approveBypassKey; + QString m_denyBypassKey; QFileSystemWatcher *m_watcher; KConfigWatcher::Ptr m_configWatcher; static constexpr const char *SCHEMES_DIR = "/usr/share/color-schemes"; diff --git a/kde/kate/ollie_kate.cpp b/kde/kate/ollie_kate.cpp index d9f6854..518fd91 100644 --- a/kde/kate/ollie_kate.cpp +++ b/kde/kate/ollie_kate.cpp @@ -190,7 +190,11 @@ void OllieKateView::ensureAgent() if (m_sessionId.isEmpty()) { QString cwd = projectBaseDir(); if (cwd.isEmpty()) cwd = QDir::homePath(); - m_9p->write("session/new", ("name=kate\ncwd=" + cwd + "\n").toUtf8()); + QByteArray resp = m_9p->rdwr("session/new", ("name=kate\ncwd=" + cwd + "\n").toUtf8()); + if (resp.isEmpty()) { + qWarning() << "Ollie Kate: failed to create session"; + return; + } m_sessionId = "kate"; }