Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,86 @@
|
||||
// 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)
|
||||
}
|
||||
Reference in new issue
Block a user