example: Add IPC remote control example.
Some checks failed
CI / Test (push) Has been cancelled
CI / Lint (push) Has been cancelled

This commit is contained in:
2026-03-14 17:05:18 -04:00
parent aad4c16d6e
commit 3b574e89ab
4 changed files with 316 additions and 1 deletions

View File

@@ -379,6 +379,35 @@ Gentoo,rolling,openrc
CSV
}
# ── Phase 6 demos ────────────────────────────────────────
ipc_demo() {
echo "IPC demo: pikl runs here with --ipc enabled." >&2
echo "Open a second terminal and run:" >&2
echo " ./examples/ipc-remote.sh demo" >&2
echo "" >&2
echo "Use the remote to navigate, filter, add items, and query state." >&2
echo "Press Enter or q in the remote to exit." >&2
echo "" >&2
cat <<'ITEMS' | pikl --ipc --session demo --multi
{"label": "Arch Linux", "category": "rolling", "init": "systemd"}
{"label": "NixOS", "category": "rolling", "init": "systemd"}
{"label": "Void Linux", "category": "rolling", "init": "runit"}
{"label": "Debian", "category": "stable", "init": "systemd"}
{"label": "Alpine", "category": "stable", "init": "openrc"}
{"label": "Fedora", "category": "semi-rolling", "init": "systemd"}
{"label": "Gentoo", "category": "rolling", "init": "openrc"}
ITEMS
}
session_demo() {
echo "Session filter history: select with a filter, exit, relaunch." >&2
echo "Press Ctrl+P to recall your previous filter." >&2
echo "" >&2
printf "apple\nbanana\ncherry\ndate\nelderberry\nfig\ngrape\nhoneydew\n" \
| pikl --session fruit
}
# ── Scenario menu ─────────────────────────────────────────
scenarios=(
@@ -411,6 +440,9 @@ scenarios=(
"CSV + multi-select"
"CSV + field filter"
"---"
"IPC remote control"
"Session filter history"
"---"
"on-select-exec hook (legacy)"
)
@@ -441,6 +473,8 @@ run_scenario() {
*"CSV with --columns"*) csv_columns_alias ;;
*"CSV + multi"*) csv_multi_select ;;
*"CSV + field"*) csv_filter ;;
*"IPC remote"*) ipc_demo ;;
*"Session filter"*) session_demo ;;
*"on-select-exec"*) on_select_hook ;;
"---")
echo "that's a separator, not a scenario" >&2

78
examples/ipc-remote.md Normal file
View File

@@ -0,0 +1,78 @@
# IPC Remote Control
Interactive demo of pikl's IPC socket. Run pikl in one terminal, control it
from another. Watch the menu respond in real time.
## Prerequisites
- `socat` for socket I/O (`pacman -S socat` / `brew install socat`)
- `python3` for pretty-printing JSON responses (optional, falls back to raw output)
## Quick start
**Terminal 1:** start pikl with IPC enabled.
```sh
echo -e "alpha\nbeta\ngamma\ndelta\nepsilon" | pikl --ipc --session demo
```
**Terminal 2:** connect the remote control.
```sh
./examples/ipc-remote.sh demo
```
## What you can do
The remote gives you single-key commands:
| Key | Action |
|-----|--------|
| `j` / `k` | Move cursor down / up |
| `g` / `G` | Jump to top / bottom |
| `f` | Set filter text (then type the query) |
| `F` | Clear filter |
| `Space` | Toggle select on current item |
| `a` | Select all |
| `c` | Clear selections |
| `s` | Print current menu state (JSON) |
| `S` | Print current selection (JSON) |
| `+` | Add new items interactively |
| `r` | Replace all items |
| `Enter` | Confirm selection (exits pikl) |
| `q` | Cancel (exits pikl) |
| `x` | Exit remote without affecting pikl |
## Things to try
1. Press `j` a few times and watch the cursor move in the pikl terminal.
2. Press `s` to see the state, including cursor position and item count.
3. Press `f`, type `al`, and watch the filter narrow the list.
4. Press `F` to clear the filter.
5. Press `+` and add a few items. They appear in the pikl terminal immediately.
6. Press `Space` a couple times to toggle selections, then `S` to see what's selected.
7. Press `Enter` to confirm. pikl exits and prints the selection.
## How it works
The remote sends newline-delimited JSON commands to pikl's Unix socket using
`socat`. Write commands (move, filter, add items) are fire-and-forget. Read
commands (get_state, get_selection) wait for a JSON response.
The socket path for a named session is `/run/user/$UID/pikl-{session}.sock`.
Without `--session`, pikl uses the PID: `/run/user/$UID/pikl-{pid}.sock`.
## Using socat directly
You don't need the remote script. Any tool that speaks Unix sockets works:
```sh
# Query state
echo '{"action":"get_state","id":"1"}' | socat - UNIX-CONNECT:/run/user/$(id -u)/pikl-demo.sock
# Move cursor down
echo '{"action":"move_down"}' | socat -t 0.1 - UNIX-CONNECT:/run/user/$(id -u)/pikl-demo.sock
# Add items
echo '{"action":"add_items","items":["foo","bar"]}' | socat -t 0.1 - UNIX-CONNECT:/run/user/$(id -u)/pikl-demo.sock
```

178
examples/ipc-remote.sh Executable file
View File

@@ -0,0 +1,178 @@
#!/usr/bin/env bash
# IPC remote control for pikl-menu.
#
# Connects to a running pikl instance over its Unix socket and
# lets you send commands interactively. You see the effects in
# the pikl terminal in real time.
#
# Usage:
# Terminal 1: echo -e "alpha\nbeta\ngamma\ndelta" | pikl --ipc --session demo
# Terminal 2: ./examples/ipc-remote.sh demo
#
# Requires: socat (for socket I/O)
#
# The session name is optional. Without it, you'll need to pass
# the full socket path as the first argument.
set -euo pipefail
# ── Socket path ──────────────────────────────────────────
resolve_socket() {
local arg="${1:-}"
if [[ -z "$arg" ]]; then
echo "usage: ipc-remote.sh <session-name | socket-path>" >&2
exit 2
fi
# If it looks like a path, use it directly
if [[ "$arg" == */* || "$arg" == *.sock ]]; then
echo "$arg"
return
fi
# Otherwise treat it as a session name
local uid
uid=$(id -u)
echo "/run/user/${uid}/pikl-${arg}.sock"
}
SOCK=$(resolve_socket "${1:-}")
if [[ ! -S "$SOCK" ]]; then
echo "socket not found: $SOCK" >&2
echo "" >&2
echo "make sure pikl is running with --ipc:" >&2
echo " echo -e 'alpha\nbeta\ngamma' | pikl --ipc --session demo" >&2
exit 1
fi
if ! command -v socat >/dev/null 2>&1; then
echo "socat is required but not installed." >&2
echo "install it with your package manager (pacman -S socat, brew install socat, etc.)" >&2
exit 1
fi
# ── IPC helpers ──────────────────────────────────────────
ipc_send() {
echo "$1" | socat - "UNIX-CONNECT:${SOCK}" 2>/dev/null
}
ipc_fire() {
# Fire-and-forget: send command, don't wait for response
echo "$1" | socat -t 0.1 - "UNIX-CONNECT:${SOCK}" 2>/dev/null || true
}
# ── Commands ─────────────────────────────────────────────
cmd_state() {
local resp
resp=$(ipc_send '{"action":"get_state","id":"q"}')
echo "$resp" | python3 -m json.tool 2>/dev/null || echo "$resp"
}
cmd_selection() {
local resp
resp=$(ipc_send '{"action":"get_selection","id":"q"}')
echo "$resp" | python3 -m json.tool 2>/dev/null || echo "$resp"
}
cmd_move_down() { ipc_fire '{"action":"move_down"}'; }
cmd_move_up() { ipc_fire '{"action":"move_up"}'; }
cmd_top() { ipc_fire '{"action":"move_to_top"}'; }
cmd_bottom() { ipc_fire '{"action":"move_to_bottom"}'; }
cmd_page_down() { ipc_fire '{"action":"page_down"}'; }
cmd_page_up() { ipc_fire '{"action":"page_up"}'; }
cmd_toggle() { ipc_fire '{"action":"toggle_select"}'; }
cmd_select_all() { ipc_fire '{"action":"select_all"}'; }
cmd_clear() { ipc_fire '{"action":"clear_selections"}'; }
cmd_confirm() { ipc_fire '{"action":"confirm"}'; }
cmd_cancel() { ipc_fire '{"action":"cancel"}'; }
cmd_filter() {
local text="${1:-}"
if [[ -z "$text" ]]; then
read -rp "filter text: " text
fi
ipc_fire "{\"action\":\"set_filter\",\"text\":\"${text}\"}"
}
cmd_add() {
local items="${1:-}"
if [[ -z "$items" ]]; then
echo "enter items to add (one per line, empty line to finish):"
local lines=()
while IFS= read -rp "> " line; do
[[ -z "$line" ]] && break
lines+=("\"${line}\"")
done
items=$(IFS=,; echo "${lines[*]}")
fi
ipc_fire "{\"action\":\"add_items\",\"items\":[${items}]}"
}
cmd_replace() {
echo "enter new items (one per line, empty line to finish):"
local lines=()
while IFS= read -rp "> " line; do
[[ -z "$line" ]] && break
lines+=("\"${line}\"")
done
local items
items=$(IFS=,; echo "${lines[*]}")
ipc_fire "{\"action\":\"replace_items\",\"items\":[${items}]}"
}
# ── Interactive loop ─────────────────────────────────────
show_help() {
cat <<'HELP'
pikl IPC remote control
───────────────────────
j / down move cursor down k / up move cursor up
g jump to top G jump to bottom
f <text> set filter F clear filter
space toggle select a select all
c clear selections s get state
S get selection
+ add items r replace all items
enter confirm selection q cancel menu
? show this help x exit remote
HELP
}
main() {
echo "connected to: $SOCK"
show_help
while true; do
read -rp "ipc> " -n1 key || break
echo ""
case "$key" in
j) cmd_move_down ;;
k) cmd_move_up ;;
g) cmd_top ;;
G) cmd_bottom ;;
f) read -rp "filter: " text; cmd_filter "$text" ;;
F) cmd_filter "" ;;
" ") cmd_toggle ;;
a) cmd_select_all ;;
c) cmd_clear ;;
s) cmd_state ;;
S) cmd_selection ;;
+) cmd_add ;;
r) cmd_replace ;;
"") cmd_confirm; echo "confirmed."; break ;;
q) cmd_cancel; echo "cancelled."; break ;;
x) echo "bye."; break ;;
"?") show_help ;;
*) echo "unknown key '$key'. press ? for help." ;;
esac
done
}
main