Files

87 lines
2.5 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"context"
"errors"
"os/exec"
"strings"
"time"
)
type ocrTextCopier interface {
Copy(context.Context, string) error
}
// xclip becomes the X11 selection owner after its short-lived parent exits.
// It releases ownership when another application replaces the clipboard.
type xclipOCRCopier struct{}
func (xclipOCRCopier) Copy(ctx context.Context, value string) error {
if value == "" {
return errors.New("empty OCR selection")
}
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
command := exec.CommandContext(ctx, "xclip", "-selection", "clipboard", "-in")
command.Stdin = strings.NewReader(value)
return command.Run()
}
type ocrCopyRequest struct {
ctx context.Context
generation uint64
text string
automatic bool
}
func (session *ocrSession) copy_selection_locked() {
if session.cursor == nil || session.selectionAnchor < 0 || session.selectionAnchor == session.cursor.Offset() {
session.status_locked("No OCR text selected")
return
}
start, end := min(session.selectionAnchor, session.cursor.Offset()), max(session.selectionAnchor, session.cursor.Offset())
value, ok := session.cursor.Slice(start, end)
if !ok || value == "" || session.copier == nil || session.copyRequests == nil {
session.status_locked("OCR copy unavailable")
return
}
request := ocrCopyRequest{ctx: session.job.ctx, generation: session.generation, text: value}
select {
case session.copyRequests <- request:
default:
// Keep the newest explicit copy request without blocking the input loop.
select {
case <-session.copyRequests:
default:
}
session.copyRequests <- request
}
}
func (session *ocrSession) copy(request ocrCopyRequest) {
if request.ctx.Err() != nil {
return
}
err := session.copier.Copy(request.ctx, request.text)
session.mu.Lock()
defer session.mu.Unlock()
if request.ctx.Err() != nil || request.generation != session.generation || !session.current_locked(session.job) {
return
}
// A successful optional copy must not cancel initial Say All or replace its
// routable braille frame. Explicit copies retain their normal confirmation.
if request.automatic && err == nil && session.job.autoReadAll {
return
}
message := "OCR text copied to clipboard"
if err != nil {
message = "OCR copy unavailable"
session.logger.Debug("OCR copy failed", "error", err)
}
session.status_locked(message)
}