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,89 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Package ocrcapture is used only by the short-lived, headless native helper.
|
||||
// The reader's worker uses ocr.Capturer and never calls GTK/X11 capture in-process.
|
||||
package ocrcapture
|
||||
|
||||
/*
|
||||
#cgo pkg-config: gtk+-3.0 x11
|
||||
#include "native.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"image"
|
||||
"image/png"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/ocr"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, input io.Reader, output io.Writer) error {
|
||||
request, err := ocr.ReadCaptureRequest(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
header := ocr.CaptureHeader{Request: request, Status: "unavailable"}
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
// This backend captures physical X11 pixels, including Xwayland when it
|
||||
// provides them. Native display/source checks decide availability.
|
||||
_ = os.Setenv("GDK_BACKEND", "x11")
|
||||
_ = os.Setenv("NO_AT_BRIDGE", "1")
|
||||
kind := map[ocr.SourceKind]int{ocr.ObjectSource: 1, ocr.WindowSource: 2, ocr.DesktopSource: 3, ocr.ClipboardSource: 4}[request.Kind]
|
||||
native := C.cthulhu_ocr_capture(C.int(kind), C.ulong(request.Foreground), C.ulong(request.Window),
|
||||
C.int(request.Bounds.Min.X), C.int(request.Bounds.Min.Y), C.int(request.Bounds.Max.X), C.int(request.Bounds.Max.Y))
|
||||
defer C.cthulhu_ocr_capture_free(&native)
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if native.status != 0 {
|
||||
header.Status = map[int]string{1: "unavailable", 2: "source", 3: "changed", 4: "limit"}[int(native.status)]
|
||||
return ocr.WriteCaptureResponse(output, header, nil)
|
||||
}
|
||||
header.Status = ""
|
||||
var pixels image.Image
|
||||
if request.Kind == ocr.ClipboardSource {
|
||||
data := C.GoBytes(unsafe.Pointer(native.clipboard), native.clipboardLength)
|
||||
config, decodeErr := png.DecodeConfig(bytes.NewReader(data))
|
||||
if decodeErr != nil {
|
||||
header.Status = "source"
|
||||
} else if config.Width <= 0 || config.Height <= 0 || config.Height > ocr.MaxPixels || config.Width > ocr.MaxPixels/config.Height {
|
||||
header.Status = "limit"
|
||||
} else {
|
||||
pixels, decodeErr = png.Decode(bytes.NewReader(data))
|
||||
if decodeErr != nil {
|
||||
header.Status = "source"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
header.SourceWindow = uint64(native.sourceWindow)
|
||||
header.RootBounds = image.Rect(0, 0, int(native.rootWidth), int(native.rootHeight))
|
||||
header.SourceBounds = image.Rect(int(native.sourceX), int(native.sourceY), int(native.sourceX)+int(native.sourceWidth), int(native.sourceY)+int(native.sourceHeight))
|
||||
header.ScreenBounds = image.Rect(int(native.x), int(native.y), int(native.x)+int(native.width), int(native.y)+int(native.height))
|
||||
width, height := int(native.width), int(native.height)
|
||||
stride := int(C.gdk_pixbuf_get_rowstride(native.pixels))
|
||||
data := C.GoBytes(unsafe.Pointer(C.gdk_pixbuf_get_pixels(native.pixels)), C.int((height-1)*stride+width*3))
|
||||
owned := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
for row := range height {
|
||||
for column := range width {
|
||||
source := row*stride + column*3
|
||||
target := row*owned.Stride + column*4
|
||||
copy(owned.Pix[target:target+3], data[source:source+3])
|
||||
owned.Pix[target+3] = 255
|
||||
}
|
||||
}
|
||||
pixels = owned
|
||||
}
|
||||
return ocr.WriteCaptureResponse(output, header, pixels)
|
||||
}
|
||||
Reference in new issue
Block a user