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,107 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package ocr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// bounded_buffer never retains more than limit bytes. Child-output overflow
|
||||
// cancels its process immediately instead of leaving a stopped pipe reader.
|
||||
type bounded_buffer struct {
|
||||
data bytes.Buffer
|
||||
limit int
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
overflow bool
|
||||
}
|
||||
|
||||
func (buffer *bounded_buffer) Write(data []byte) (int, error) {
|
||||
if err := buffer.ctx.Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(data) > buffer.limit-buffer.Len() {
|
||||
buffer.overflow = true
|
||||
if buffer.cancel != nil {
|
||||
buffer.cancel()
|
||||
}
|
||||
return 0, ErrLimit
|
||||
}
|
||||
return buffer.data.Write(data)
|
||||
}
|
||||
|
||||
func (buffer *bounded_buffer) Bytes() []byte { return buffer.data.Bytes() }
|
||||
func (buffer *bounded_buffer) Len() int { return buffer.data.Len() }
|
||||
|
||||
func (engine *Engine) run(parent context.Context, input []byte, limit int, args ...string) ([]byte, []byte, error) {
|
||||
return run_process(parent, engine.command, input, limit, args...)
|
||||
}
|
||||
|
||||
func run_process(parent context.Context, factory func(context.Context, ...string) *exec.Cmd, input []byte, limit int, args ...string) ([]byte, []byte, error) {
|
||||
ctx, cancel := context.WithCancel(parent)
|
||||
defer cancel()
|
||||
command := factory(ctx, args...)
|
||||
// This reader targets Linux/Xorg. Own the whole process group, including
|
||||
// descendants that might inherit pipes from a wrapper executable.
|
||||
command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
kill := func() error {
|
||||
if command.Process == nil {
|
||||
return os.ErrProcessDone
|
||||
}
|
||||
err := syscall.Kill(-command.Process.Pid, syscall.SIGKILL)
|
||||
if errors.Is(err, syscall.ESRCH) {
|
||||
return os.ErrProcessDone
|
||||
}
|
||||
return err
|
||||
}
|
||||
command.Cancel = kill
|
||||
command.WaitDelay = time.Second
|
||||
command.Env = process_environment(command.Env)
|
||||
command.Stdin = bytes.NewReader(input)
|
||||
output := &bounded_buffer{limit: limit, ctx: ctx, cancel: cancel}
|
||||
diagnostics := &bounded_buffer{limit: maxStderrBytes, ctx: ctx, cancel: cancel}
|
||||
command.Stdout, command.Stderr = output, diagnostics
|
||||
err := command.Run()
|
||||
// WaitDelay closes inherited pipes after a bound, but os/exec only kills the
|
||||
// direct child then. Retire any remaining group before admitting another job.
|
||||
if command.Process != nil {
|
||||
_ = kill()
|
||||
}
|
||||
if parent.Err() != nil {
|
||||
return nil, nil, parent.Err()
|
||||
}
|
||||
if output.overflow || diagnostics.overflow {
|
||||
return nil, nil, ErrLimit
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) || errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) {
|
||||
return nil, nil, ErrUnavailable
|
||||
}
|
||||
// Never forward native stderr: it can contain captured text or paths.
|
||||
return nil, diagnostics.Bytes(), ErrRecognition
|
||||
}
|
||||
return output.Bytes(), diagnostics.Bytes(), nil
|
||||
}
|
||||
|
||||
func process_environment(environment []string) []string {
|
||||
if environment == nil {
|
||||
environment = os.Environ()
|
||||
}
|
||||
result := make([]string, 0, len(environment)+2)
|
||||
for _, entry := range environment {
|
||||
if !strings.HasPrefix(entry, "LC_ALL=") && !strings.HasPrefix(entry, "OMP_THREAD_LIMIT=") {
|
||||
result = append(result, entry)
|
||||
}
|
||||
}
|
||||
// Stable CLI diagnostics; avoid using every core for a desktop OCR request.
|
||||
return append(result, "LC_ALL=C", "OMP_THREAD_LIMIT=1")
|
||||
}
|
||||
Reference in new issue
Block a user