// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package preferences /* #include #include "native.h" */ import "C" import ( "errors" "fmt" "reflect" "slices" "strings" "unsafe" "git.stormux.org/storm/cthulhu/internal/tentacle" ) type tentacleRow struct { key string status tentacle.EntryStatus installed bool } type tentaclePage struct { snapshot *tentacle.Snapshot rows []tentacleRow origins []string selectedKey string } func tentacle_rows(snapshot *tentacle.Snapshot, origin string) []tentacleRow { if snapshot == nil { return nil } var rows []tentacleRow seen := make(map[string]bool) repositoryIDs := make(map[string]string) for _, repository := range snapshot.Repositories { repositoryIDs[repository.URL] = repository.ID } for _, status := range snapshot.Entries { entry := status.Entry localID := entry.LocalID if localID == "" { localID = entry.ID } key := entry.Repository + "\x00" + entry.RepositoryID + "\x00" + localID seen[key] = true if origin == "" || entry.Repository == origin && entry.RepositoryID == repositoryIDs[origin] { rows = append(rows, tentacleRow{key: key, status: status, installed: true}) } } for _, repository := range snapshot.Repositories { if origin != "" && repository.URL != origin { continue } for _, manifest := range repository.Tentacles { key := repository.URL + "\x00" + repository.ID + "\x00" + manifest.ID if seen[key] { continue } status := tentacle.EntryStatus{ Entry: tentacle.Entry{ID: manifest.ID, LocalID: manifest.ID, Name: manifest.Name, Version: manifest.Version, Repository: repository.URL, RepositoryID: repository.ID}, State: "Available", Message: manifest.Description, } rows = append(rows, tentacleRow{key: key, status: status}) } } return rows } func (page *tentaclePage) selected() *tentacleRow { row := int(C.prefs_tentacle_selected()) if row < 0 || row >= len(page.rows) { return nil } return &page.rows[row] } func (page *tentaclePage) origin() string { index := int(C.prefs_tentacle_repository_selected()) - 1 if index < 0 || index >= len(page.origins) { return "" } return page.origins[index] } func (page *tentaclePage) controls() { selected := page.selected() installed, enabled := false, false if selected != nil { installed, enabled = selected.installed, selected.status.Entry.Enabled } available := page.snapshot != nil && !page.snapshot.Suppressed && page.snapshot.Message == "" C.prefs_tentacle_controls(boolean(available), boolean(installed), boolean(enabled), boolean(page.origin() != "")) } func (page *tentaclePage) render() { page.rows = tentacle_rows(page.snapshot, page.origin()) C.prefs_tentacles_clear() selectedRow := -1 for index, row := range page.rows { entry := row.status.Entry name := entry.Name if name == "" { name = entry.ID } repository := tentacle.RepositoryLabel(entry.Repository) if entry.Repository == "" { repository = "Local development" } texts := []*C.char{C.CString(name), C.CString(row.status.State), C.CString(entry.Version), C.CString(repository), C.CString(row.status.Message), C.CString(strings.Join(row.status.Shortcuts, "; "))} C.prefs_tentacle_append(boolean(entry.Enabled), texts[0], texts[1], texts[2], texts[3], texts[4], texts[5]) for _, text := range texts { C.free(unsafe.Pointer(text)) } if row.key == page.selectedKey { selectedRow = index } } C.prefs_tentacle_select(C.int(selectedRow)) page.controls() } func (page *tentaclePage) populate(snapshot *tentacle.Snapshot) { if selected := page.selected(); selected != nil { page.selectedKey = selected.key } origin := page.origin() var origins []string if snapshot != nil { for _, repository := range snapshot.Repositories { origins = append(origins, repository.URL) } } if !reflect.DeepEqual(page.origins, origins) { C.prefs_tentacle_repositories_clear() selectedPage := 0 labels := make(map[string]int) for _, value := range origins { labels[tentacle.RepositoryLabel(value)]++ } for index, value := range origins { label := tentacle.RepositoryLabel(value) if labels[label] > 1 { label = value } text := C.CString(label) C.prefs_tentacle_repository_append(text) C.free(unsafe.Pointer(text)) if value == origin { selectedPage = index + 1 } } page.origins = origins C.prefs_tentacle_repository_select(C.int(selectedPage)) } page.snapshot = snapshot page.render() } func (page *tentaclePage) action(action int) (*tentacle.ManagementRequest, error) { if action == 16 { if selected := page.selected(); selected != nil { page.selectedKey = selected.key } page.controls() return nil, nil } if action == 17 { page.render() return nil, nil } if action == 15 { return &tentacle.ManagementRequest{Action: "list"}, nil } if page.snapshot == nil || page.snapshot.Suppressed || page.snapshot.Message != "" { message := "Tentacle management is unavailable for this run." if page.snapshot != nil && page.snapshot.Message != "" { message = page.snapshot.Message } return nil, errors.New(message) } if action == 9 { id := C.CString("tentacleRepositoryURL") origin, err := tentacle.RepositoryURL(C.GoString(C.prefs_entry_get(id))) C.free(unsafe.Pointer(id)) if err != nil { return nil, err } return &tentacle.ManagementRequest{Action: "repository-add", URL: origin}, nil } if action == 12 || action == 18 { origin := page.origin() if origin == "" { return nil, errors.New("Select a repository tab first.") } operation := "repository-remove" if action == 18 { operation = "repository-refresh" } return &tentacle.ManagementRequest{Action: operation, URL: origin}, nil } selected := page.selected() if selected == nil { return nil, errors.New("Select a tentacle first.") } page.selectedKey = selected.key if !selected.installed { if action != 11 && action != 19 { return nil, errors.New("Install this tentacle first.") } entry := selected.status.Entry var manifest *tentacle.Manifest for _, repository := range page.snapshot.Repositories { if repository.URL == entry.Repository && repository.ID == entry.RepositoryID { for index := range repository.Tentacles { if repository.Tentacles[index].ID == entry.LocalID { manifest = &repository.Tentacles[index] break } } } } if manifest == nil || len(manifest.Releases) == 0 { return nil, errors.New("No package has been published for this tentacle. Refresh the repository after a release is published.") } permissions := "This tentacle can register reader commands." if slices.Contains(manifest.Capabilities, "presentation") { permissions = "This tentacle can register reader commands and send speech and braille messages." } if slices.Contains(manifest.Capabilities, "interaction") { permissions = "This tentacle can register reader commands, send speech and braille messages, open internal forms, read the focused item and clipboard when invoked, and write clipboard text." } operation, label := "install", "Install" if action == 11 { operation, label = "install-enable", "Install and enable" } message := fmt.Sprintf("%s %s version %s from %s?\n\n%s\n\nOnly install packages from publishers you trust. Tentacles run with your account's access; resource limits do not protect your files or network.\n\nTentacle management changes take effect immediately.", label, entry.Name, entry.Version, entry.Repository, permissions) text, button := C.CString(message), C.CString(label) accepted := C.prefs_tentacle_confirm(text, button) C.free(unsafe.Pointer(text)) C.free(unsafe.Pointer(button)) if accepted == 0 { return nil, nil } return &tentacle.ManagementRequest{Action: operation, ID: entry.LocalID, URL: entry.Repository}, nil } operation := "" switch action { case 11: operation = "enable" if selected.status.Entry.Enabled { operation = "disable" } case 13: operation = "retry" case 14: operation = "remove" case 20: operation = "settings" default: return nil, errors.New("Unknown tentacle action.") } return &tentacle.ManagementRequest{Action: operation, ID: selected.status.Entry.ID}, nil } func (page *tentaclePage) accepted(request *tentacle.ManagementRequest) { if request.Action == "repository-add" { id, empty := C.CString("tentacleRepositoryURL"), C.CString("") C.prefs_entry_set(id, empty) C.free(unsafe.Pointer(id)) C.free(unsafe.Pointer(empty)) } page.controls() }