Research 026/04 counted two plain dmenu calls failing with dmenu installed nowhere. Measured, they are one line on each workstation: dunst's `dmenu = /usr/bin/dmenu -p dunst:`. Installing the package fixes both by existing; the line stays the dunst module's. No claim: node-launcher is not in the controller's seat table yet, and the module says so. Two Go tools: menu, shaped like that seat's verb (chosen line, index, typed, cancelled, timed out within 25 s), and session.
71 lines
2.7 KiB
Go
71 lines
2.7 KiB
Go
// The dmenu module's tool (novox/hq research 026/04, 026/05): show the operator a menu of choices in
|
|
// the graphical session and answer the one chosen — the dmenu-compatible command as a tool. A Go
|
|
// bundle the node's runtime launches over stdio (ADR 0188, ADR 0193). It runs as the operator account
|
|
// and reaches the account's X session as session.go finds it; with no session, it says so.
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
var providedBy = map[string]string{
|
|
"dmenu": "the dmenu package, which this module installs",
|
|
}
|
|
|
|
func main() {
|
|
if err := stdio.Serve("", tools()); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func tools() []stdio.Tool {
|
|
return []stdio.Tool{
|
|
{
|
|
Name: "dmenu_menu",
|
|
Description: "Show the operator a menu of choices on their screen and answer the line chosen, its index, or " +
|
|
"that the menu was dismissed or not answered in time (default 20 s, at most 25). The operator may also " +
|
|
"type a line that is not a choice. Needs the operator's graphical session. (d)",
|
|
Input: map[string]any{
|
|
"choices": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "the lines to choose from, at most 1000, none with a line break"},
|
|
"prompt": map[string]any{"type": "string", "description": "a prompt shown left of the input"},
|
|
"lines": map[string]any{"type": "integer", "description": "show the choices as a vertical list of this many lines (default 0: one horizontal line; at most 40)"},
|
|
"case_insensitive": map[string]any{"type": "boolean", "description": "match what is typed regardless of case (default true)"},
|
|
"timeout_seconds": map[string]any{"type": "integer", "description": "close the menu unanswered after this long (default 20, at most 25)"},
|
|
},
|
|
Run: func(args map[string]any) (any, error) {
|
|
choices, err := optList(args, "choices")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
prompt, err := optText(args, "prompt", "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
n, err := optWhole(args, "lines", 0, 0, 40)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ci, err := optFlag(args, "case_insensitive", true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
timeout, err := optWhole(args, "timeout_seconds", 20, 1, MostWait)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Menu(Ask{Choices: choices, Prompt: prompt, Lines: n, CaseInsensitive: ci, Timeout: timeout})
|
|
},
|
|
},
|
|
{
|
|
Name: "dmenu_session",
|
|
Description: "Which X session the menu would appear in and how it was found, or why there is none; and dmenu's version. (r)",
|
|
Input: map[string]any{},
|
|
Run: func(map[string]any) (any, error) { return SessionCheck() },
|
|
},
|
|
}
|
|
}
|