// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "context" "encoding/json" "errors" "git.stormux.org/storm/cthulhu/internal/platform/session" "os" "os/exec" "time" ) const clipboardReadTimeout = 3 * time.Second func runClipboardWorker(ctx context.Context, requests <-chan struct{}, speech speechEnqueuer, read func(context.Context) (string, error)) <-chan struct{} { done := make(chan struct{}) go func() { defer close(done) for { select { case <-ctx.Done(): return case <-requests: requestCtx, cancel := context.WithTimeout(ctx, clipboardReadTimeout) result, err := read(requestCtx) cancel() if ctx.Err() != nil { return } message := "Error accessing clipboard" if err == nil { message = result } speech.Interrupt() speech.Enqueue(speechRequest{Text: message, Role: "system", Cause: "clipboard"}) } } }() return done } func readClipboardMessage(ctx context.Context) (string, error) { executable, err := os.Executable() if err != nil { return "", err } command := exec.CommandContext(ctx, executable, "--clipboard-read") command.Env = session.GTKEnvironment() command.WaitDelay = time.Second output, err := command.Output() if err != nil { return "", err } var result struct { Message string `json:"message"` } if err := json.Unmarshal(output, &result); err != nil || result.Message == "" { return "", errors.New("invalid clipboard helper response") } return result.Message, nil }