feat: Add support IPC socket control over menu.
This commit is contained in:
143
docs/DESIGN.md
143
docs/DESIGN.md
@@ -519,13 +519,19 @@ A few built-in themes ship with pikl. `--theme monokai` or
|
||||
|
||||
## Sessions
|
||||
|
||||
`--session name` persists the menu's state (filter text,
|
||||
scroll position, selections) across
|
||||
invocations. State lives in
|
||||
`~/.local/state/pikl/sessions/`.
|
||||
|
||||
`--session name` enables filter history across invocations.
|
||||
No `--session` = ephemeral, nothing saved.
|
||||
|
||||
When a session is active, every filter that leads to a
|
||||
selection (single, multi, or quicklist) is appended to
|
||||
`~/.local/state/pikl/sessions/{name}.history`. Empty
|
||||
filters and consecutive duplicates are skipped.
|
||||
|
||||
On startup with a session name, the history file is loaded.
|
||||
Ctrl+P / Ctrl+N in insert mode cycles through previous
|
||||
filters. Selecting a history entry replaces the current
|
||||
filter text.
|
||||
|
||||
Session names are just strings, use shell expansion for
|
||||
dynamic names:
|
||||
|
||||
@@ -534,8 +540,8 @@ pikl --session "walls-$(hostname)"
|
||||
pikl --session "logs-$USER"
|
||||
```
|
||||
|
||||
Session history is a log file alongside the state. Other
|
||||
tools can tail it for observability.
|
||||
Sessions are lightweight by design. No item persistence,
|
||||
no scroll position, no selections. Just filter recall.
|
||||
|
||||
## Watched Sources
|
||||
|
||||
@@ -552,18 +558,96 @@ named pipe.
|
||||
|
||||
## IPC
|
||||
|
||||
While running, pikl-menu listens on a Unix socket
|
||||
(`/run/user/$UID/pikl-$PID.sock`). External tools can:
|
||||
Opt-in external control via Unix socket. Enabled with
|
||||
`--ipc`. Off by default: pikl is ephemeral and shouldn't
|
||||
have side effects unless asked.
|
||||
|
||||
- Push new items
|
||||
- Remove items
|
||||
- Update item fields
|
||||
- Set the filter text
|
||||
- Read current selection
|
||||
- Close the menu
|
||||
Socket path:
|
||||
- Named session: `/run/user/$UID/pikl-{session}.sock`
|
||||
- No session: `/run/user/$UID/pikl-{pid}.sock`
|
||||
|
||||
Protocol is newline-delimited JSON. Simple enough to use
|
||||
with `socat` or any language's Unix socket support.
|
||||
The socket path is logged via tracing on startup and
|
||||
cleaned up on exit.
|
||||
|
||||
### Protocol
|
||||
|
||||
Newline-delimited JSON, one message per line. Simple
|
||||
enough for `socat` or any language with Unix sockets.
|
||||
|
||||
### Client-to-pikl Commands
|
||||
|
||||
**Write commands** (fire-and-forget, no response):
|
||||
|
||||
| Action | Payload | Effect |
|
||||
|---|---|---|
|
||||
| `add_items` | `{"items": [...]}` | Append items |
|
||||
| `replace_items` | `{"items": [...]}` | Replace all items |
|
||||
| `remove_items` | `{"indices": [...]}` | Remove by index |
|
||||
| `set_filter` | `{"text": "..."}` | Set filter text |
|
||||
| `move_up` | (none) | Navigate up |
|
||||
| `move_down` | (none) | Navigate down |
|
||||
| `move_to_top` | (none) | Jump to top |
|
||||
| `move_to_bottom` | (none) | Jump to bottom |
|
||||
| `page_up` | (none) | Page up |
|
||||
| `page_down` | (none) | Page down |
|
||||
| `toggle_select` | (none) | Toggle current item |
|
||||
| `select_all` | (none) | Select all items |
|
||||
| `clear_selections` | (none) | Clear selections |
|
||||
| `confirm` | (none) | Confirm selection |
|
||||
| `cancel` | (none) | Cancel menu |
|
||||
| `close` | (none) | Close menu |
|
||||
|
||||
**Read commands** (require `id` field, response echoes it):
|
||||
|
||||
| Action | Response |
|
||||
|---|---|
|
||||
| `get_state` | `{"id": "...", "state": {"filter": "...", "cursor": 3, "total_items": 150, "total_filtered": 12, "selection_count": 0, "mode": "insert"}}` |
|
||||
| `get_selection` | `{"id": "...", "selection": [{"label": "...", "index": 3}, ...]}` |
|
||||
|
||||
**Event subscription:**
|
||||
|
||||
| Action | Payload | Effect |
|
||||
|---|---|---|
|
||||
| `subscribe` | `{"events": ["hover", "filter", ...]}` | Start receiving events |
|
||||
| `unsubscribe` | (none) | Stop receiving events |
|
||||
|
||||
Subscribed events are pushed as
|
||||
`{"event": "hover", "item": {...}, "index": 3}` lines.
|
||||
|
||||
### Multiple Connections
|
||||
|
||||
Multiple clients can connect simultaneously. Each gets
|
||||
independent subscription state. All writes are serialized
|
||||
through the same action channel.
|
||||
|
||||
### Auth
|
||||
|
||||
Unix socket permissions (user-only access). No additional
|
||||
authentication.
|
||||
|
||||
### Architecture
|
||||
|
||||
IPC is just another frontend. It deserializes JSON
|
||||
messages into `Action` variants and sends them through the
|
||||
same `mpsc` channel as the TUI or action-fd. Event
|
||||
subscriptions tap into the `broadcast` channel. No special
|
||||
core changes needed.
|
||||
|
||||
### Examples
|
||||
|
||||
```sh
|
||||
# Push items into a running pikl
|
||||
echo '{"action": "add_items", "items": [{"label": "new"}]}' \
|
||||
| socat - UNIX-CONNECT:/run/user/1000/pikl-mypicker.sock
|
||||
|
||||
# Read current state
|
||||
echo '{"action": "get_state", "id": "1"}' \
|
||||
| socat - UNIX-CONNECT:/run/user/1000/pikl-mypicker.sock
|
||||
|
||||
# Subscribe to hover events
|
||||
echo '{"action": "subscribe", "events": ["hover"]}' \
|
||||
| socat - UNIX-CONNECT:/run/user/1000/pikl-mypicker.sock
|
||||
```
|
||||
|
||||
## Exit Codes
|
||||
|
||||
@@ -644,8 +728,8 @@ sequentially regardless of where they came from.
|
||||
| TUI | Terminal keypresses (crossterm) | Yes | 1 |
|
||||
| GUI | GUI events (iced) | Yes | 8 |
|
||||
| Action-fd | Pre-validated script from a file descriptor | No (unless `show-ui`) | 1.5 |
|
||||
| IPC | Unix socket, JSON protocol | Yes (bidirectional) | 6 |
|
||||
| Lua | LuaJIT script via mlua | Yes (stateful, conditional) | Post-6 |
|
||||
| IPC | Unix socket, newline-delimited JSON | Yes (bidirectional) | 6 |
|
||||
| Lua | Embedded LuaJIT via mlua (in-process) | Yes (stateful, conditional) | Post-6 |
|
||||
|
||||
This framing means new interaction modes don't require core
|
||||
changes: they're just new frontends that push actions.
|
||||
@@ -821,12 +905,15 @@ complexity:
|
||||
extend pikl without touching Rust.
|
||||
4. **IPC** (phase 6): bidirectional JSON over Unix socket.
|
||||
External tools can read state and send actions while pikl
|
||||
runs interactively. Good for tool integration.
|
||||
5. **Lua** (post phase 6): embedded LuaJIT via mlua. Full
|
||||
stateful scripting: subscribe to events, branch on state,
|
||||
loops, the works. The Lua runtime is just another
|
||||
frontend pushing Actions and subscribing to MenuEvents.
|
||||
For anything complex enough to need a real language.
|
||||
runs interactively. Language-agnostic: anything that can
|
||||
write JSON to a socket works.
|
||||
5. **Lua** (post phase 6): embedded LuaJIT via mlua.
|
||||
In-process scripting for complex hooks and behaviour.
|
||||
Direct access to the action/event system, no socket
|
||||
overhead. Think Neovim's Lua layer: you're extending the
|
||||
tool, not talking to it from outside. IPC and Lua are
|
||||
separate integration points. IPC is for external
|
||||
processes. Lua is for embedded logic.
|
||||
|
||||
No custom DSL. Action-fd stays simple forever. The jump
|
||||
from "I need conditionals" to "use Lua" is intentional:
|
||||
@@ -848,16 +935,10 @@ with the full writeup.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should marks/registers persist across sessions or only
|
||||
within a session?
|
||||
- Accessibility: screen reader support for TUI mode?
|
||||
- Should `--watch` support inotify on Linux and FSEvents on
|
||||
macOS, or use `notify` crate to abstract?
|
||||
- Maximum practical item count before we need virtual
|
||||
scrolling? (Probably around 100k)
|
||||
- Should hooks run in a pool or strictly sequential?
|
||||
Resolved: exec hooks are one subprocess per event.
|
||||
Handler hooks are persistent processes. Debounce and
|
||||
cancel-stale manage concurrency.
|
||||
- Plugin system via WASM for custom filter strategies?
|
||||
(Probably way later if ever)
|
||||
|
||||
Reference in New Issue
Block a user