Files
mesh-catalog/modules/xdg/cmd/xdg-tools/main.go
T
jochen 785d32407b xdg: own the account's folders and the machine's default applications
The workstations' XDG conventions had no owner: xdg-user-dirs-update rewrote
the folders file at every login, both machines' default-application lists
named an editor neither has, and the laptop kept two dead links of the
retired predecessor. The module adopts the operator's folders (three as
settings, as the machines differ), disables the update so it cannot undo the
mesh's file, and writes the machine's own mimeapps list, the last one read,
so the person's choices in their own list always win. Autostart is listed,
not owned: each entry is its application's module's.
2026-10-05 11:50:34 +02:00

151 lines
5.5 KiB
Go

// xdg's Go tools bundle: the account's XDG desktop conventions — its folders, its default
// applications and what starts at login. Served by the node's runtime as the operator account.
//
// None needs the display. Folders, lists and autostart entries are files; xdg-mime and gio answer
// from the same files; whether a program runs is read from /proc. Only xdg_default's `set` changes
// anything, and only the person's own list of defaults.
package main
import (
"fmt"
"os"
stdio "git.novox.be/novox/mesh-sdk/go"
)
func main() {
if err := stdio.Serve("", tools(NewMachine())); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func text(args map[string]any, name string) (string, error) {
v, ok := args[name]
if !ok || v == nil {
return "", nil
}
s, isText := v.(string)
if !isText {
return "", fmt.Errorf("%s is text", name)
}
return s, nil
}
func tools(m *Machine) []stdio.Tool {
envArg := map[string]any{"type": "string", "description": "the desktop name dex is given (default: the session's, i3)"}
env := func(args map[string]any) (string, error) {
e, err := text(args, "environment")
if err != nil || e != "" {
return e, err
}
return m.Desktop, nil
}
return []stdio.Tool{
{
Name: "xdg_user_dirs",
Description: "The account's folders (Desktop, Downloads, Documents, …) as user-dirs.dirs names them: " +
"each one as written, the path it resolves to, whether it exists as a directory, whether it points " +
"at the home (not wanted), and the folder xdg-user-dirs would have made instead. Also whether " +
"xdg-user-dirs-update may rewrite the file at login, and the locale it was made in.",
Run: func(map[string]any) (any, error) { return m.UserDirs(), nil },
},
{
Name: "xdg_default",
Description: "The default application for a MIME type (text/html) or a URL scheme (https, mailto): what " +
"xdg-open uses, what GTK programs use, which mimeapps list decided it, the application's desktop " +
"file and whether its program is installed. With set (a desktop file id such as firefox.desktop), " +
"make it the default through xdg-mime: it is written to the person's own list, which the mesh " +
"never writes and which outranks the mesh's defaults.",
Input: map[string]any{
"type": "object",
"properties": map[string]any{
"type": map[string]any{"type": "string", "description": "a MIME type (application/pdf) or a URL scheme (https, mailto)"},
"set": map[string]any{"type": "string", "description": "make this installed desktop file id the default (an action)"},
},
"required": []string{"type"},
},
Run: func(args map[string]any) (any, error) {
t, err := text(args, "type")
if err != nil {
return nil, err
}
mime, err := Normalise(t)
if err != nil {
return nil, err
}
set, err := text(args, "set")
if err != nil {
return nil, err
}
if set != "" {
return m.SetDefault(mime, set)
}
return m.Handle(mime), nil
},
},
{
Name: "xdg_open_handlers",
Description: "Who opens the links and files that matter (http, https, mailto, HTML, folders, text, " +
"Markdown, PDF, images, video, audio, and any types given): the application xdg-open and GTK " +
"programs pick and which list decided. Also every mimeapps list in the order applications read " +
"them, every default naming an application that is not installed, and every installed handler " +
"whose program is missing.",
Input: map[string]any{
"type": "object",
"properties": map[string]any{
"types": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "more MIME types or URL schemes to answer for"},
},
},
Run: func(args map[string]any) (any, error) {
var extra []string
if v, ok := args["types"]; ok && v != nil {
list, isList := v.([]any)
if !isList {
return nil, fmt.Errorf("types is a list of text")
}
for _, x := range list {
s, isText := x.(string)
if !isText {
return nil, fmt.Errorf("types is a list of text")
}
extra = append(extra, s)
}
}
return m.OpenHandlers(extra)
},
},
{
Name: "xdg_autostart",
Description: "Every XDG autostart entry, the account's and the system's: what it runs, Hidden, " +
"OnlyShowIn and NotShowIn, whether dex starts it at login under the window manager (and if not, " +
"why), whether the window manager's own configuration starts the same program too, and how many " +
"of the account's processes run it now.",
Input: map[string]any{"type": "object", "properties": map[string]any{"environment": envArg}},
Run: func(args map[string]any) (any, error) {
e, err := env(args)
if err != nil {
return nil, err
}
return map[string]any{"environment": e, "entries": m.Autostarts(e)}, nil
},
},
{
Name: "xdg_check",
Description: "Whether the account's XDG conventions are sound: user-dirs.dirs is the mesh's, every folder " +
"it names exists and xdg-user-dirs-update cannot rewrite it; the mesh's default applications are " +
"in place, every mimeapps list parses with nothing written twice, no default names an application " +
"that is not installed, no handler's program is missing; no program is started twice at login and " +
"every program dex starts is installed.",
Input: map[string]any{"type": "object", "properties": map[string]any{"environment": envArg}},
Run: func(args map[string]any) (any, error) {
e, err := env(args)
if err != nil {
return nil, err
}
return m.Check(e), nil
},
},
}
}